Partial fix for #15196: wxRichTextCell caret issues (dghart)
[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 // Copyright: (c) Julian Smart
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10
11 // For compilers that support precompilation, includes "wx.h".
12 #include "wx/wxprec.h"
13
14 #ifdef __BORLANDC__
15 #pragma hdrstop
16 #endif
17
18 #if wxUSE_RICHTEXT
19
20 #include "wx/richtext/richtextbuffer.h"
21
22 #ifndef WX_PRECOMP
23 #include "wx/dc.h"
24 #include "wx/intl.h"
25 #include "wx/log.h"
26 #include "wx/dataobj.h"
27 #include "wx/module.h"
28 #endif
29
30 #include "wx/settings.h"
31 #include "wx/filename.h"
32 #include "wx/clipbrd.h"
33 #include "wx/wfstream.h"
34 #include "wx/mstream.h"
35 #include "wx/sstream.h"
36 #include "wx/textfile.h"
37 #include "wx/hashmap.h"
38 #include "wx/dynarray.h"
39
40 #include "wx/richtext/richtextctrl.h"
41 #include "wx/richtext/richtextstyles.h"
42 #include "wx/richtext/richtextimagedlg.h"
43 #include "wx/richtext/richtextsizepage.h"
44 #include "wx/richtext/richtextxml.h"
45
46 #include "wx/listimpl.cpp"
47 #include "wx/arrimpl.cpp"
48
49 WX_DEFINE_LIST(wxRichTextObjectList)
50 WX_DEFINE_LIST(wxRichTextLineList)
51
52 // Switch off if the platform doesn't like it for some reason
53 #define wxRICHTEXT_USE_OPTIMIZED_DRAWING 1
54
55 // Use GetPartialTextExtents for platforms that support it natively
56 #define wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS 1
57
58 const wxChar wxRichTextLineBreakChar = (wxChar) 29;
59
60 // Helper classes for floating layout
61 struct wxRichTextFloatRectMap
62 {
63 wxRichTextFloatRectMap(int sY, int eY, int w, wxRichTextObject* obj)
64 {
65 startY = sY;
66 endY = eY;
67 width = w;
68 anchor = obj;
69 }
70
71 int startY, endY;
72 int width;
73 wxRichTextObject* anchor;
74 };
75
76 WX_DEFINE_SORTED_ARRAY(wxRichTextFloatRectMap*, wxRichTextFloatRectMapArray);
77
78 int wxRichTextFloatRectMapCmp(wxRichTextFloatRectMap* r1, wxRichTextFloatRectMap* r2)
79 {
80 return r1->startY - r2->startY;
81 }
82
83 class wxRichTextFloatCollector
84 {
85 public:
86 wxRichTextFloatCollector(const wxRect& availableRect);
87 ~wxRichTextFloatCollector();
88
89 // Collect the floating objects info in the given paragraph
90 void CollectFloat(wxRichTextParagraph* para);
91 void CollectFloat(wxRichTextParagraph* para, wxRichTextObject* floating);
92
93 // Return the last paragraph we collected
94 wxRichTextParagraph* LastParagraph();
95
96 // Given the start y position and the height of the line,
97 // find out how wide the line can be
98 wxRect GetAvailableRect(int startY, int endY);
99
100 // Given a floating box, find its fit position
101 int GetFitPosition(int direction, int start, int height) const;
102 int GetFitPosition(const wxRichTextFloatRectMapArray& array, int start, int height) const;
103
104 // Find the last y position
105 int GetLastRectBottom();
106
107 // Draw the floats inside a rect
108 void Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style);
109
110 // HitTest the floats
111 int HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags);
112
113 // Get floating object count
114 int GetFloatingObjectCount() const { return m_left.GetCount() + m_right.GetCount(); }
115
116 // Get floating objects
117 bool GetFloatingObjects(wxRichTextObjectList& objects) const;
118
119 // Delete a float
120 bool DeleteFloat(wxRichTextObject* obj);
121
122 // Do we have this float already?
123 bool HasFloat(wxRichTextObject* obj);
124
125 bool HasFloats() const { return m_left.GetCount() >0 || m_right.GetCount() > 0; }
126
127 static int SearchAdjacentRect(const wxRichTextFloatRectMapArray& array, int point);
128
129 static int GetWidthFromFloatRect(const wxRichTextFloatRectMapArray& array, int index, int startY, int endY);
130
131 static void FreeFloatRectMapArray(wxRichTextFloatRectMapArray& array);
132
133 static void DrawFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style);
134
135 static int HitTestFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags);
136
137 private:
138 wxRichTextFloatRectMapArray m_left;
139 wxRichTextFloatRectMapArray m_right;
140 //int m_width;
141 wxRect m_availableRect;
142 wxRichTextParagraph* m_para;
143 };
144
145 // Delete a float
146 bool wxRichTextFloatCollector::DeleteFloat(wxRichTextObject* obj)
147 {
148 size_t i;
149 for (i = 0; i < m_left.GetCount(); i++)
150 {
151 if (m_left[i]->anchor == obj)
152 {
153 m_left.RemoveAt(i);
154 return true;
155 }
156 }
157 for (i = 0; i < m_right.GetCount(); i++)
158 {
159 if (m_right[i]->anchor == obj)
160 {
161 m_right.RemoveAt(i);
162 return true;
163 }
164 }
165 return false;
166 }
167
168 // Do we have this float already?
169 bool wxRichTextFloatCollector::HasFloat(wxRichTextObject* obj)
170 {
171 size_t i;
172 for (i = 0; i < m_left.GetCount(); i++)
173 {
174 if (m_left[i]->anchor == obj)
175 {
176 return true;
177 }
178 }
179 for (i = 0; i < m_right.GetCount(); i++)
180 {
181 if (m_right[i]->anchor == obj)
182 {
183 return true;
184 }
185 }
186 return false;
187 }
188
189 // Get floating objects
190 bool wxRichTextFloatCollector::GetFloatingObjects(wxRichTextObjectList& objects) const
191 {
192 size_t i;
193 for (i = 0; i < m_left.GetCount(); i++)
194 objects.Append(m_left[i]->anchor);
195 for (i = 0; i < m_right.GetCount(); i++)
196 objects.Append(m_right[i]->anchor);
197 return true;
198 }
199
200
201 /*
202 * Binary search helper function
203 * The argument point is the Y coordinate, and this fuction
204 * always return the floating rect that contain this coordinate
205 * or under this coordinate.
206 */
207 int wxRichTextFloatCollector::SearchAdjacentRect(const wxRichTextFloatRectMapArray& array, int point)
208 {
209 int end = array.GetCount() - 1;
210 int start = 0;
211 int ret = 0;
212
213 wxASSERT(end >= 0);
214
215 while (true)
216 {
217 if (start > end)
218 {
219 break;
220 }
221
222 int mid = (start + end) / 2;
223 if (array[mid]->startY <= point && array[mid]->endY >= point)
224 return mid;
225 else if (array[mid]->startY > point)
226 {
227 end = mid - 1;
228 ret = mid;
229 }
230 else if (array[mid]->endY < point)
231 {
232 start = mid + 1;
233 ret = start;
234 }
235 }
236
237 return ret;
238 }
239
240 int wxRichTextFloatCollector::GetWidthFromFloatRect(const wxRichTextFloatRectMapArray& array, int index, int startY, int endY)
241 {
242 int ret = 0;
243 int len = array.GetCount();
244
245 wxASSERT(index >= 0 && index < len);
246
247 if (array[index]->startY < startY && array[index]->endY > startY)
248 ret = ret < array[index]->width ? array[index]->width : ret;
249 while (index < len && array[index]->startY <= endY)
250 {
251 ret = ret < array[index]->width ? array[index]->width : ret;
252 index++;
253 }
254
255 return ret;
256 }
257
258 wxRichTextFloatCollector::wxRichTextFloatCollector(const wxRect& rect) : m_left(wxRichTextFloatRectMapCmp), m_right(wxRichTextFloatRectMapCmp)
259 {
260 m_availableRect = rect;
261 m_para = NULL;
262 }
263
264 void wxRichTextFloatCollector::FreeFloatRectMapArray(wxRichTextFloatRectMapArray& array)
265 {
266 int len = array.GetCount();
267 for (int i = 0; i < len; i++)
268 delete array[i];
269 }
270
271 wxRichTextFloatCollector::~wxRichTextFloatCollector()
272 {
273 FreeFloatRectMapArray(m_left);
274 FreeFloatRectMapArray(m_right);
275 }
276
277 int wxRichTextFloatCollector::GetFitPosition(const wxRichTextFloatRectMapArray& array, int start, int height) const
278 {
279 if (array.GetCount() == 0)
280 return start;
281
282 int i = SearchAdjacentRect(array, start);
283 int last = start;
284 while (i < (int) array.GetCount())
285 {
286 if (array[i]->startY - last >= height)
287 return last + 1;
288 last = array[i]->endY;
289 i++;
290 }
291
292 return last + 1;
293 }
294
295 int wxRichTextFloatCollector::GetFitPosition(int direction, int start, int height) const
296 {
297 if (direction == wxTEXT_BOX_ATTR_FLOAT_LEFT)
298 return GetFitPosition(m_left, start, height);
299 else if (direction == wxTEXT_BOX_ATTR_FLOAT_RIGHT)
300 return GetFitPosition(m_right, start, height);
301 else
302 {
303 wxASSERT("Never should be here");
304 return start;
305 }
306 }
307
308 // Adds a floating image to the float collector.
309 // The actual positioning is done by wxRichTextParagraph::LayoutFloat.
310 void wxRichTextFloatCollector::CollectFloat(wxRichTextParagraph* para, wxRichTextObject* floating)
311 {
312 int direction = floating->GetFloatDirection();
313
314 wxPoint pos = floating->GetPosition();
315 wxSize size = floating->GetCachedSize();
316 wxRichTextFloatRectMap *map = new wxRichTextFloatRectMap(pos.y, pos.y + size.y, size.x, floating);
317 switch (direction)
318 {
319 case wxTEXT_BOX_ATTR_FLOAT_NONE:
320 delete map;
321 break;
322 case wxTEXT_BOX_ATTR_FLOAT_LEFT:
323 // Just a not-enough simple assertion
324 wxASSERT (m_left.Index(map) == wxNOT_FOUND);
325 m_left.Add(map);
326 break;
327 case wxTEXT_BOX_ATTR_FLOAT_RIGHT:
328 wxASSERT (m_right.Index(map) == wxNOT_FOUND);
329 m_right.Add(map);
330 break;
331 default:
332 delete map;
333 wxASSERT("Unrecognised float attribute.");
334 }
335
336 m_para = para;
337 }
338
339 void wxRichTextFloatCollector::CollectFloat(wxRichTextParagraph* para)
340 {
341 wxRichTextObjectList::compatibility_iterator node = para->GetChildren().GetFirst();
342 while (node)
343 {
344 wxRichTextObject* floating = node->GetData();
345
346 if (floating->IsFloating())
347 {
348 CollectFloat(para, floating);
349 }
350
351 node = node->GetNext();
352 }
353
354 m_para = para;
355 }
356
357 wxRichTextParagraph* wxRichTextFloatCollector::LastParagraph()
358 {
359 return m_para;
360 }
361
362 wxRect wxRichTextFloatCollector::GetAvailableRect(int startY, int endY)
363 {
364 int widthLeft = 0, widthRight = 0;
365 if (m_left.GetCount() != 0)
366 {
367 int i = SearchAdjacentRect(m_left, startY);
368 if (i < (int) m_left.GetCount())
369 widthLeft = GetWidthFromFloatRect(m_left, i, startY, endY);
370 }
371 if (m_right.GetCount() != 0)
372 {
373 int j = SearchAdjacentRect(m_right, startY);
374 if (j < (int) m_right.GetCount())
375 widthRight = GetWidthFromFloatRect(m_right, j, startY, endY);
376 }
377
378 // TODO: actually we want to use the actual image positions to find the
379 // available remaining space, since the image might not be right up against
380 // the left or right edge of the container.
381 return wxRect(widthLeft + m_availableRect.x, 0, m_availableRect.width - widthLeft - widthRight, 0);
382 }
383
384 int wxRichTextFloatCollector::GetLastRectBottom()
385 {
386 int ret = 0;
387 int len = m_left.GetCount();
388 if (len) {
389 ret = ret > m_left[len-1]->endY ? ret : m_left[len-1]->endY;
390 }
391 len = m_right.GetCount();
392 if (len) {
393 ret = ret > m_right[len-1]->endY ? ret : m_right[len-1]->endY;
394 }
395
396 return ret;
397 }
398
399 void wxRichTextFloatCollector::DrawFloat(const wxRichTextFloatRectMapArray& array, wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& WXUNUSED(range), const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
400 {
401 int start = rect.y;
402 int end = rect.y + rect.height;
403 int i, j;
404 i = SearchAdjacentRect(array, start);
405 if (i < 0 || i >= (int) array.GetCount())
406 return;
407 j = SearchAdjacentRect(array, end);
408 if (j < 0 || j >= (int) array.GetCount())
409 j = array.GetCount() - 1;
410 while (i <= j)
411 {
412 wxRichTextObject* obj = array[i]->anchor;
413 wxRichTextRange r = obj->GetRange();
414 obj->Draw(dc, context, r, selection, wxRect(obj->GetPosition(), obj->GetCachedSize()), descent, style);
415 i++;
416 }
417 }
418
419 void wxRichTextFloatCollector::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
420 {
421 if (m_left.GetCount() > 0)
422 DrawFloat(m_left, dc, context, range, selection, rect, descent, style);
423 if (m_right.GetCount() > 0)
424 DrawFloat(m_right, dc, context, range, selection, rect, descent, style);
425 }
426
427 int wxRichTextFloatCollector::HitTestFloat(const wxRichTextFloatRectMapArray& array, wxDC& WXUNUSED(dc), wxRichTextDrawingContext& WXUNUSED(context), const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int WXUNUSED(flags))
428 {
429 int i;
430 if (array.GetCount() == 0)
431 return wxRICHTEXT_HITTEST_NONE;
432 i = SearchAdjacentRect(array, pt.y);
433 if (i < 0 || i >= (int) array.GetCount())
434 return wxRICHTEXT_HITTEST_NONE;
435 if (!array[i]->anchor->IsShown())
436 return wxRICHTEXT_HITTEST_NONE;
437
438 wxPoint point = array[i]->anchor->GetPosition();
439 wxSize size = array[i]->anchor->GetCachedSize();
440 if (point.x <= pt.x && point.x + size.x >= pt.x
441 && point.y <= pt.y && point.y + size.y >= pt.y)
442 {
443 textPosition = array[i]->anchor->GetRange().GetStart();
444 * obj = array[i]->anchor;
445 if (pt.x > (pt.x + pt.x + size.x) / 2)
446 return wxRICHTEXT_HITTEST_BEFORE;
447 else
448 return wxRICHTEXT_HITTEST_AFTER;
449 }
450
451 return wxRICHTEXT_HITTEST_NONE;
452 }
453
454 int wxRichTextFloatCollector::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, int flags)
455 {
456 int ret = HitTestFloat(m_left, dc, context, pt, textPosition, obj, flags);
457 if (ret == wxRICHTEXT_HITTEST_NONE)
458 {
459 ret = HitTestFloat(m_right, dc, context, pt, textPosition, obj, flags);
460 }
461 return ret;
462 }
463
464 // Helpers for efficiency
465 inline void wxCheckSetFont(wxDC& dc, const wxFont& font)
466 {
467 dc.SetFont(font);
468 }
469
470 inline void wxCheckSetPen(wxDC& dc, const wxPen& pen)
471 {
472 const wxPen& pen1 = dc.GetPen();
473 if (pen1.IsOk() && pen.IsOk())
474 {
475 if (pen1.GetWidth() == pen.GetWidth() &&
476 pen1.GetStyle() == pen.GetStyle() &&
477 pen1.GetColour() == pen.GetColour())
478 return;
479 }
480 dc.SetPen(pen);
481 }
482
483 inline void wxCheckSetBrush(wxDC& dc, const wxBrush& brush)
484 {
485 const wxBrush& brush1 = dc.GetBrush();
486 if (brush1.IsOk() && brush.IsOk())
487 {
488 if (brush1.GetStyle() == brush.GetStyle() &&
489 brush1.GetColour() == brush.GetColour())
490 return;
491 }
492 dc.SetBrush(brush);
493 }
494
495 /*!
496 * wxRichTextObject
497 * This is the base for drawable objects.
498 */
499
500 IMPLEMENT_CLASS(wxRichTextObject, wxObject)
501
502 wxRichTextObject::wxRichTextObject(wxRichTextObject* parent)
503 {
504 m_refCount = 1;
505 m_parent = parent;
506 m_descent = 0;
507 m_show = true;
508 }
509
510 wxRichTextObject::~wxRichTextObject()
511 {
512 }
513
514 void wxRichTextObject::Dereference()
515 {
516 m_refCount --;
517 if (m_refCount <= 0)
518 delete this;
519 }
520
521 /// Copy
522 void wxRichTextObject::Copy(const wxRichTextObject& obj)
523 {
524 m_size = obj.m_size;
525 m_maxSize = obj.m_maxSize;
526 m_minSize = obj.m_minSize;
527 m_pos = obj.m_pos;
528 m_range = obj.m_range;
529 m_ownRange = obj.m_ownRange;
530 m_attributes = obj.m_attributes;
531 m_properties = obj.m_properties;
532 m_descent = obj.m_descent;
533 m_show = obj.m_show;
534 }
535
536 // Get/set the top-level container of this object.
537 wxRichTextParagraphLayoutBox* wxRichTextObject::GetContainer() const
538 {
539 const wxRichTextObject* p = this;
540 while (p)
541 {
542 if (p->IsTopLevel())
543 {
544 return wxDynamicCast(p, wxRichTextParagraphLayoutBox);
545 }
546 p = p->GetParent();
547 }
548 return NULL;
549 }
550
551 void wxRichTextObject::SetMargins(int margin)
552 {
553 SetMargins(margin, margin, margin, margin);
554 }
555
556 void wxRichTextObject::SetMargins(int leftMargin, int rightMargin, int topMargin, int bottomMargin)
557 {
558 GetAttributes().GetTextBoxAttr().GetMargins().GetLeft().SetValue(leftMargin, wxTEXT_ATTR_UNITS_PIXELS);
559 GetAttributes().GetTextBoxAttr().GetMargins().GetRight().SetValue(rightMargin, wxTEXT_ATTR_UNITS_PIXELS);
560 GetAttributes().GetTextBoxAttr().GetMargins().GetTop().SetValue(topMargin, wxTEXT_ATTR_UNITS_PIXELS);
561 GetAttributes().GetTextBoxAttr().GetMargins().GetBottom().SetValue(bottomMargin, wxTEXT_ATTR_UNITS_PIXELS);
562 }
563
564 int wxRichTextObject::GetLeftMargin() const
565 {
566 return GetAttributes().GetTextBoxAttr().GetMargins().GetLeft().GetValue();
567 }
568
569 int wxRichTextObject::GetRightMargin() const
570 {
571 return GetAttributes().GetTextBoxAttr().GetMargins().GetRight().GetValue();
572 }
573
574 int wxRichTextObject::GetTopMargin() const
575 {
576 return GetAttributes().GetTextBoxAttr().GetMargins().GetTop().GetValue();
577 }
578
579 int wxRichTextObject::GetBottomMargin() const
580 {
581 return GetAttributes().GetTextBoxAttr().GetMargins().GetBottom().GetValue();
582 }
583
584 // Calculate the available content space in the given rectangle, given the
585 // margins, border and padding specified in the object's attributes.
586 wxRect wxRichTextObject::GetAvailableContentArea(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& outerRect) const
587 {
588 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
589 marginRect = outerRect;
590 wxRichTextAttr attr(GetAttributes());
591 context.ApplyVirtualAttributes(attr, (wxRichTextObject*) this);
592 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
593 return contentRect;
594 }
595
596 // Invalidate the buffer. With no argument, invalidates whole buffer.
597 void wxRichTextObject::Invalidate(const wxRichTextRange& invalidRange)
598 {
599 if (invalidRange != wxRICHTEXT_NONE)
600 {
601 // If this is a floating object, size may not be recalculated
602 // after floats have been collected in an early stage of Layout.
603 // So avoid resetting the cache for floating objects during layout.
604 if (!IsFloating() || !wxRichTextBuffer::GetFloatingLayoutMode())
605 SetCachedSize(wxDefaultSize);
606 SetMaxSize(wxDefaultSize);
607 SetMinSize(wxDefaultSize);
608 }
609 }
610
611 // Convert units in tenths of a millimetre to device units
612 int wxRichTextObject::ConvertTenthsMMToPixels(wxDC& dc, int units) const
613 {
614 // Unscale
615 double scale = 1.0;
616 if (GetBuffer())
617 scale = GetBuffer()->GetScale() / GetBuffer()->GetDimensionScale();
618 int p = ConvertTenthsMMToPixels(dc.GetPPI().x, units, scale);
619
620 return p;
621 }
622
623 // Convert units in tenths of a millimetre to device units
624 int wxRichTextObject::ConvertTenthsMMToPixels(int ppi, int units, double scale)
625 {
626 // There are ppi pixels in 254.1 "1/10 mm"
627
628 double pixels = ((double) units * (double)ppi) / 254.1;
629 if (scale != 1.0)
630 pixels /= scale;
631
632 // If the result is very small, make it at least one pixel in size.
633 if (pixels == 0 && units > 0)
634 pixels = 1;
635
636 return (int) pixels;
637 }
638
639 // Convert units in pixels to tenths of a millimetre
640 int wxRichTextObject::ConvertPixelsToTenthsMM(wxDC& dc, int pixels) const
641 {
642 int p = pixels;
643 double scale = 1.0;
644 if (GetBuffer())
645 scale = GetBuffer()->GetScale();
646
647 return ConvertPixelsToTenthsMM(dc.GetPPI().x, p, scale);
648 }
649
650 int wxRichTextObject::ConvertPixelsToTenthsMM(int ppi, int pixels, double scale)
651 {
652 // There are ppi pixels in 254.1 "1/10 mm"
653
654 double p = double(pixels);
655
656 if (scale != 1.0)
657 p *= scale;
658
659 int units = int( p * 254.1 / (double) ppi );
660 return units;
661 }
662
663 // Draw the borders and background for the given rectangle and attributes.
664 // Width and height are taken to be the outer margin size, not the content.
665 bool wxRichTextObject::DrawBoxAttributes(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, const wxRect& boxRect, int flags)
666 {
667 // Assume boxRect is the area around the content
668 wxRect marginRect = boxRect;
669 wxRect contentRect, borderRect, paddingRect, outlineRect;
670
671 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
672
673 // Margin is transparent. Draw background from margin.
674 if (attr.HasBackgroundColour() || (flags & wxRICHTEXT_DRAW_SELECTED))
675 {
676 wxColour colour;
677 if (flags & wxRICHTEXT_DRAW_SELECTED)
678 {
679 // TODO: get selection colour from control?
680 colour = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
681 }
682 else
683 colour = attr.GetBackgroundColour();
684
685 wxPen pen(colour);
686 wxBrush brush(colour);
687
688 dc.SetPen(pen);
689 dc.SetBrush(brush);
690 dc.DrawRectangle(borderRect);
691 }
692
693 if (flags & wxRICHTEXT_DRAW_GUIDELINES)
694 {
695 wxRichTextAttr editBorderAttr = attr;
696 // TODO: make guideline colour configurable
697 editBorderAttr.GetTextBoxAttr().GetBorder().SetColour(*wxLIGHT_GREY);
698 editBorderAttr.GetTextBoxAttr().GetBorder().SetWidth(1, wxTEXT_ATTR_UNITS_PIXELS);
699 editBorderAttr.GetTextBoxAttr().GetBorder().SetStyle(wxTEXT_BOX_ATTR_BORDER_SOLID);
700
701 DrawBorder(dc, buffer, editBorderAttr.GetTextBoxAttr().GetBorder(), borderRect, flags);
702 }
703
704 if (attr.GetTextBoxAttr().GetBorder().IsValid())
705 DrawBorder(dc, buffer, attr.GetTextBoxAttr().GetBorder(), borderRect);
706
707 if (attr.GetTextBoxAttr().GetOutline().IsValid())
708 DrawBorder(dc, buffer, attr.GetTextBoxAttr().GetOutline(), outlineRect);
709
710 return true;
711 }
712
713 // Draw a border
714 bool wxRichTextObject::DrawBorder(wxDC& dc, wxRichTextBuffer* buffer, const wxTextAttrBorders& attr, const wxRect& rect, int WXUNUSED(flags))
715 {
716 int borderLeft = 0, borderRight = 0, borderTop = 0, borderBottom = 0;
717 wxTextAttrDimensionConverter converter(dc, buffer ? buffer->GetScale() : 1.0);
718
719 if (attr.GetLeft().IsValid() && attr.GetLeft().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
720 {
721 borderLeft = converter.GetPixels(attr.GetLeft().GetWidth());
722 wxColour col(attr.GetLeft().GetColour());
723
724 // If pen width is > 1, resorts to a solid rectangle.
725 if (borderLeft == 1)
726 {
727 int penStyle = wxSOLID;
728 if (attr.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
729 penStyle = wxDOT;
730 else if (attr.GetLeft().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
731 penStyle = wxLONG_DASH;
732 wxPen pen(col, 1, penStyle);
733 dc.SetPen(pen);
734 dc.DrawLine(rect.x, rect.y, rect.x, rect.y + rect.height);
735
736 }
737 else if (borderLeft > 1)
738 {
739 wxPen pen(col);
740 wxBrush brush(col);
741 dc.SetPen(pen);
742 dc.SetBrush(brush);
743 dc.DrawRectangle(rect.x, rect.y, borderLeft, rect.height);
744 }
745 }
746
747 if (attr.GetRight().IsValid() && attr.GetRight().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
748 {
749 borderRight = converter.GetPixels(attr.GetRight().GetWidth());
750
751 wxColour col(attr.GetRight().GetColour());
752
753 // If pen width is > 1, resorts to a solid rectangle.
754 if (borderRight == 1)
755 {
756 int penStyle = wxSOLID;
757 if (attr.GetRight().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
758 penStyle = wxDOT;
759 else if (attr.GetRight().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
760 penStyle = wxLONG_DASH;
761 wxPen pen(col, 1, penStyle);
762 dc.SetPen(pen);
763 dc.DrawLine(rect.x + rect.width, rect.y, rect.x + rect.width, rect.y + rect.height + 1);
764
765 }
766 else if (borderRight > 1)
767 {
768 wxPen pen(col);
769 wxBrush brush(col);
770 dc.SetPen(pen);
771 dc.SetBrush(brush);
772 dc.DrawRectangle(rect.x + rect.width - borderRight, rect.y, borderRight, rect.height + 1);
773 }
774 }
775
776 if (attr.GetTop().IsValid() && attr.GetTop().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
777 {
778 borderTop = converter.GetPixels(attr.GetTop().GetWidth());
779
780 wxColour col(attr.GetTop().GetColour());
781
782 // If pen width is > 1, resorts to a solid rectangle.
783 if (borderTop == 1)
784 {
785 int penStyle = wxSOLID;
786 if (attr.GetTop().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
787 penStyle = wxDOT;
788 else if (attr.GetTop().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
789 penStyle = wxLONG_DASH;
790 wxPen pen(col, 1, penStyle);
791 dc.SetPen(pen);
792 dc.DrawLine(rect.x, rect.y, rect.x + rect.width, rect.y);
793
794 }
795 else if (borderTop > 1)
796 {
797 wxPen pen(col);
798 wxBrush brush(col);
799 dc.SetPen(pen);
800 dc.SetBrush(brush);
801 dc.DrawRectangle(rect.x, rect.y, rect.width, borderTop);
802 }
803 }
804
805 if (attr.GetBottom().IsValid() && attr.GetBottom().GetStyle() != wxTEXT_BOX_ATTR_BORDER_NONE)
806 {
807 borderBottom = converter.GetPixels(attr.GetBottom().GetWidth());
808 wxColour col(attr.GetBottom().GetColour());
809
810 // If pen width is > 1, resorts to a solid rectangle.
811 if (borderBottom == 1)
812 {
813 int penStyle = wxSOLID;
814 if (attr.GetBottom().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DOTTED)
815 penStyle = wxDOT;
816 else if (attr.GetBottom().GetStyle() == wxTEXT_BOX_ATTR_BORDER_DASHED)
817 penStyle = wxLONG_DASH;
818 wxPen pen(col, 1, penStyle);
819 dc.SetPen(pen);
820 dc.DrawLine(rect.x, rect.y + rect.height, rect.x + rect.width, rect.y + rect.height);
821
822 }
823 else if (borderBottom > 1)
824 {
825 wxPen pen(col);
826 wxBrush brush(col);
827 dc.SetPen(pen);
828 dc.SetBrush(brush);
829 dc.DrawRectangle(rect.x, rect.y + rect.height - borderBottom + 1, rect.width, borderBottom);
830 }
831 }
832
833 return true;
834 }
835
836 // Get the various rectangles of the box model in pixels. You can either specify contentRect (inner)
837 // or marginRect (outer), and the other must be the default rectangle (no width or height).
838 // Note that the outline doesn't affect the position of the rectangle, it's drawn in whatever space
839 // is available.
840 //
841 // | Margin | Border | Padding | CONTENT | Padding | Border | Margin |
842
843 bool wxRichTextObject::GetBoxRects(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, wxRect& marginRect, wxRect& borderRect, wxRect& contentRect, wxRect& paddingRect, wxRect& outlineRect)
844 {
845 int borderLeft = 0, borderRight = 0, borderTop = 0, borderBottom = 0;
846 int outlineLeft = 0, outlineRight = 0, outlineTop = 0, outlineBottom = 0;
847 int paddingLeft = 0, paddingRight = 0, paddingTop = 0, paddingBottom = 0;
848 int marginLeft = 0, marginRight = 0, marginTop = 0, marginBottom = 0;
849
850 wxTextAttrDimensionConverter converter(dc, buffer ? buffer->GetScale() : 1.0);
851
852 if (attr.GetTextBoxAttr().GetMargins().GetLeft().IsValid())
853 marginLeft = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetLeft());
854 if (attr.GetTextBoxAttr().GetMargins().GetRight().IsValid())
855 marginRight = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetRight());
856 if (attr.GetTextBoxAttr().GetMargins().GetTop().IsValid())
857 marginTop = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetTop());
858 if (attr.GetTextBoxAttr().GetMargins().GetBottom().IsValid())
859 marginBottom = converter.GetPixels(attr.GetTextBoxAttr().GetMargins().GetBottom());
860
861 if (attr.GetTextBoxAttr().GetBorder().GetLeft().GetWidth().IsValid())
862 borderLeft = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetLeft().GetWidth());
863 if (attr.GetTextBoxAttr().GetBorder().GetRight().GetWidth().IsValid())
864 borderRight = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetRight().GetWidth());
865 if (attr.GetTextBoxAttr().GetBorder().GetTop().GetWidth().IsValid())
866 borderTop = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetTop().GetWidth());
867 if (attr.GetTextBoxAttr().GetBorder().GetBottom().GetWidth().IsValid())
868 borderBottom = converter.GetPixels(attr.GetTextBoxAttr().GetBorder().GetBottom().GetWidth());
869
870 if (attr.GetTextBoxAttr().GetPadding().GetLeft().IsValid())
871 paddingLeft = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetLeft());
872 if (attr.GetTextBoxAttr().GetPadding().GetRight().IsValid())
873 paddingRight = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetRight());
874 if (attr.GetTextBoxAttr().GetPadding().GetTop().IsValid())
875 paddingTop = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetTop());
876 if (attr.GetTextBoxAttr().GetPadding().GetBottom().IsValid())
877 paddingBottom = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetBottom());
878
879 if (attr.GetTextBoxAttr().GetOutline().GetLeft().GetWidth().IsValid())
880 outlineLeft = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetLeft().GetWidth());
881 if (attr.GetTextBoxAttr().GetOutline().GetRight().GetWidth().IsValid())
882 outlineRight = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetRight().GetWidth());
883 if (attr.GetTextBoxAttr().GetOutline().GetTop().GetWidth().IsValid())
884 outlineTop = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetTop().GetWidth());
885 if (attr.GetTextBoxAttr().GetOutline().GetBottom().GetWidth().IsValid())
886 outlineBottom = converter.GetPixels(attr.GetTextBoxAttr().GetOutline().GetBottom().GetWidth());
887
888 int leftTotal = marginLeft + borderLeft + paddingLeft;
889 int rightTotal = marginRight + borderRight + paddingRight;
890 int topTotal = marginTop + borderTop + paddingTop;
891 int bottomTotal = marginBottom + borderBottom + paddingBottom;
892
893 if (marginRect != wxRect())
894 {
895 contentRect.x = marginRect.x + leftTotal;
896 contentRect.y = marginRect.y + topTotal;
897 contentRect.width = marginRect.width - (leftTotal + rightTotal);
898 contentRect.height = marginRect.height - (topTotal + bottomTotal);
899 }
900 else
901 {
902 marginRect.x = contentRect.x - leftTotal;
903 marginRect.y = contentRect.y - topTotal;
904 marginRect.width = contentRect.width + (leftTotal + rightTotal);
905 marginRect.height = contentRect.height + (topTotal + bottomTotal);
906 }
907
908 borderRect.x = marginRect.x + marginLeft;
909 borderRect.y = marginRect.y + marginTop;
910 borderRect.width = marginRect.width - (marginLeft + marginRight);
911 borderRect.height = marginRect.height - (marginTop + marginBottom);
912
913 paddingRect.x = marginRect.x + marginLeft + borderLeft;
914 paddingRect.y = marginRect.y + marginTop + borderTop;
915 paddingRect.width = marginRect.width - (marginLeft + marginRight + borderLeft + borderRight);
916 paddingRect.height = marginRect.height - (marginTop + marginBottom + borderTop + borderBottom);
917
918 // The outline is outside the margin and doesn't influence the overall box position or content size.
919 outlineRect.x = marginRect.x - outlineLeft;
920 outlineRect.y = marginRect.y - outlineTop;
921 outlineRect.width = marginRect.width + (outlineLeft + outlineRight);
922 outlineRect.height = marginRect.height + (outlineTop + outlineBottom);
923
924 return true;
925 }
926
927 // Get the total margin for the object in pixels, taking into account margin, padding and border size
928 bool wxRichTextObject::GetTotalMargin(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& attr, int& leftMargin, int& rightMargin,
929 int& topMargin, int& bottomMargin)
930 {
931 // Assume boxRect is the area around the content
932 wxRect contentRect, marginRect, borderRect, paddingRect, outlineRect;
933 marginRect = wxRect(0, 0, 1000, 1000);
934
935 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
936
937 leftMargin = contentRect.GetLeft() - marginRect.GetLeft();
938 rightMargin = marginRect.GetRight() - contentRect.GetRight();
939 topMargin = contentRect.GetTop() - marginRect.GetTop();
940 bottomMargin = marginRect.GetBottom() - contentRect.GetBottom();
941
942 return true;
943 }
944
945 // Returns the rectangle which the child has available to it given restrictions specified in the
946 // child attribute, e.g. 50% width of the parent, 400 pixels, x position 20% of the parent, etc.
947 // availableContainerSpace might be a parent that the cell has to compute its width relative to.
948 // E.g. a cell that's 50% of its parent.
949 wxRect wxRichTextObject::AdjustAvailableSpace(wxDC& dc, wxRichTextBuffer* buffer, const wxRichTextAttr& WXUNUSED(parentAttr), const wxRichTextAttr& childAttr, const wxRect& availableParentSpace, const wxRect& availableContainerSpace)
950 {
951 wxRect rect = availableParentSpace;
952 double scale = 1.0;
953 if (buffer)
954 scale = buffer->GetScale();
955
956 wxTextAttrDimensionConverter converter(dc, scale, availableContainerSpace.GetSize());
957
958 if (childAttr.GetTextBoxAttr().GetWidth().IsValid())
959 rect.width = converter.GetPixels(childAttr.GetTextBoxAttr().GetWidth());
960
961 if (childAttr.GetTextBoxAttr().GetHeight().IsValid())
962 rect.height = converter.GetPixels(childAttr.GetTextBoxAttr().GetHeight());
963
964 // Can specify either left or right for the position (we're assuming we can't
965 // set the left and right edges to effectively set the size. Would we want to do that?)
966 if (childAttr.GetTextBoxAttr().GetPosition().GetLeft().IsValid())
967 {
968 rect.x = rect.x + converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetLeft());
969 }
970 else if (childAttr.GetTextBoxAttr().GetPosition().GetRight().IsValid())
971 {
972 int x = converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetRight());
973 if (childAttr.GetTextBoxAttr().GetPosition().GetRight().GetPosition() == wxTEXT_BOX_ATTR_POSITION_RELATIVE)
974 rect.x = availableContainerSpace.x + availableContainerSpace.width - rect.width;
975 else
976 rect.x += x;
977 }
978
979 if (childAttr.GetTextBoxAttr().GetPosition().GetTop().IsValid())
980 {
981 rect.y = rect.y + converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetTop());
982 }
983 else if (childAttr.GetTextBoxAttr().GetPosition().GetBottom().IsValid())
984 {
985 int y = converter.GetPixels(childAttr.GetTextBoxAttr().GetPosition().GetBottom());
986 if (childAttr.GetTextBoxAttr().GetPosition().GetBottom().GetPosition() == wxTEXT_BOX_ATTR_POSITION_RELATIVE)
987 rect.y = availableContainerSpace.y + availableContainerSpace.height - rect.height;
988 else
989 rect.y += y;
990 }
991
992 if (rect.GetWidth() > availableParentSpace.GetWidth())
993 rect.SetWidth(availableParentSpace.GetWidth());
994
995 return rect;
996 }
997
998 // Dump to output stream for debugging
999 void wxRichTextObject::Dump(wxTextOutputStream& stream)
1000 {
1001 stream << GetClassInfo()->GetClassName() << wxT("\n");
1002 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");
1003 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");
1004 }
1005
1006 // Gets the containing buffer
1007 wxRichTextBuffer* wxRichTextObject::GetBuffer() const
1008 {
1009 const wxRichTextObject* obj = this;
1010 while (obj && !wxDynamicCast(obj, wxRichTextBuffer))
1011 obj = obj->GetParent();
1012 return wxDynamicCast(obj, wxRichTextBuffer);
1013 }
1014
1015 // Get the absolute object position, by traversing up the child/parent hierarchy
1016 wxPoint wxRichTextObject::GetAbsolutePosition() const
1017 {
1018 wxPoint pt = GetPosition();
1019
1020 wxRichTextObject* p = GetParent();
1021 while (p)
1022 {
1023 pt = pt + p->GetPosition();
1024 p = p->GetParent();
1025 }
1026
1027 return pt;
1028 }
1029
1030 // Hit-testing: returns a flag indicating hit test details, plus
1031 // information about position
1032 int wxRichTextObject::HitTest(wxDC& WXUNUSED(dc), wxRichTextDrawingContext& WXUNUSED(context), const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int WXUNUSED(flags))
1033 {
1034 if (!IsShown())
1035 return wxRICHTEXT_HITTEST_NONE;
1036
1037 wxRect rect = GetRect();
1038 if (pt.x >= rect.x && pt.x < rect.x + rect.width &&
1039 pt.y >= rect.y && pt.y < rect.y + rect.height)
1040 {
1041 *obj = this;
1042 *contextObj = GetParentContainer();
1043 textPosition = GetRange().GetStart();
1044 return wxRICHTEXT_HITTEST_ON;
1045 }
1046 else
1047 return wxRICHTEXT_HITTEST_NONE;
1048 }
1049
1050 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
1051 // lays out the object again using the maximum ('best') size
1052 bool wxRichTextObject::LayoutToBestSize(wxDC& dc, wxRichTextDrawingContext& context, wxRichTextBuffer* buffer,
1053 const wxRichTextAttr& parentAttr, const wxRichTextAttr& attr,
1054 const wxRect& availableParentSpace, const wxRect& availableContainerSpace,
1055 int style)
1056 {
1057 wxRect availableChildRect = AdjustAvailableSpace(dc, buffer, parentAttr, attr, availableParentSpace, availableContainerSpace);
1058 wxRect originalAvailableRect = availableChildRect;
1059 Layout(dc, context, availableChildRect, availableContainerSpace, style);
1060
1061 wxSize maxSize = GetMaxSize();
1062
1063 // Don't ignore if maxSize.x is zero, since we need to redo the paragraph's lines
1064 // on this basis
1065 if (!attr.GetTextBoxAttr().GetWidth().IsValid() && maxSize.x < availableChildRect.width)
1066 {
1067 // Redo the layout with a fixed, minimum size this time.
1068 Invalidate(wxRICHTEXT_ALL);
1069 wxRichTextAttr newAttr(attr);
1070 newAttr.GetTextBoxAttr().GetWidth().SetValue(maxSize.x, wxTEXT_ATTR_UNITS_PIXELS);
1071 newAttr.GetTextBoxAttr().GetWidth().SetPosition(wxTEXT_BOX_ATTR_POSITION_ABSOLUTE);
1072
1073 availableChildRect = AdjustAvailableSpace(dc, buffer, parentAttr, newAttr, availableParentSpace, availableContainerSpace);
1074
1075 // If a paragraph, align the whole paragraph.
1076 // Problem with this: if we're limited by a floating object, a line may be centered
1077 // w.r.t. the smaller resulting box rather than the actual available width.
1078 // FIXME: aligning whole paragraph not compatible with floating objects
1079 if (attr.HasAlignment() && (!wxRichTextBuffer::GetFloatingLayoutMode() || (GetContainer()->GetFloatCollector() && !GetContainer()->GetFloatCollector()->HasFloats())))
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(wxRichTextDrawingContext& context, 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(context);
1413
1414 // Optimization: if there are no virtual attributes, we won't need to
1415 // to split objects in order to paint individually attributed chunks.
1416 // So only merge in this case.
1417 if (!context.GetVirtualAttributesEnabled())
1418 {
1419 if (node->GetNext())
1420 {
1421 wxRichTextObject* nextChild = node->GetNext()->GetData();
1422 if (child->CanMerge(nextChild, context) && child->Merge(nextChild, context))
1423 {
1424 nextChild->Dereference();
1425 m_children.Erase(node->GetNext());
1426 }
1427 else
1428 node = node->GetNext();
1429 }
1430 else
1431 node = node->GetNext();
1432 }
1433 else
1434 {
1435 // If we might have virtual attributes, we first see if we have to split
1436 // objects so that they may be painted with potential virtual attributes,
1437 // since text objects can only draw or measure with a single attributes object
1438 // at a time.
1439 wxRichTextObject* childAfterSplit = child;
1440 if (child->CanSplit(context))
1441 {
1442 childAfterSplit = child->Split(context);
1443 node = m_children.Find(childAfterSplit);
1444 }
1445
1446 if (node->GetNext())
1447 {
1448 wxRichTextObject* nextChild = node->GetNext()->GetData();
1449
1450 // First split child and nextChild so we have smaller fragments to merge.
1451 // Then Merge only has to test per-object virtual attributes
1452 // because for an object with all the same sub-object attributes,
1453 // then any general virtual attributes should be merged with sub-objects by
1454 // the implementation.
1455
1456 wxRichTextObject* nextChildAfterSplit = nextChild;
1457
1458 if (nextChildAfterSplit->CanSplit(context))
1459 nextChildAfterSplit = nextChild->Split(context);
1460
1461 bool splitNextChild = nextChild != nextChildAfterSplit;
1462
1463 // See if we can merge this new fragment with (perhaps the first part of) the next object.
1464 // Note that we use nextChild because if we had split nextChild, the first object always
1465 // remains (and further parts are appended). However we must use childAfterSplit since
1466 // it's the last part of a possibly split child.
1467
1468 if (childAfterSplit->CanMerge(nextChild, context) && childAfterSplit->Merge(nextChild, context))
1469 {
1470 nextChild->Dereference();
1471 m_children.Erase(node->GetNext());
1472
1473 // Don't set node -- we'll see if we can merge again with the next
1474 // child. UNLESS we split this or the next child, in which case we know we have to
1475 // move on to the end of the next child.
1476 if (splitNextChild)
1477 node = m_children.Find(nextChildAfterSplit);
1478 }
1479 else
1480 {
1481 if (splitNextChild)
1482 node = m_children.Find(nextChildAfterSplit); // start from the last object in the split
1483 else
1484 node = node->GetNext();
1485 }
1486 }
1487 else
1488 node = node->GetNext();
1489 }
1490 }
1491 else
1492 node = node->GetNext();
1493 }
1494
1495 // Delete any remaining empty objects, but leave at least one empty object per composite object.
1496 if (GetChildCount() > 1)
1497 {
1498 node = m_children.GetFirst();
1499 while (node)
1500 {
1501 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
1502 wxRichTextObject* child = node->GetData();
1503 if (range == wxRICHTEXT_ALL || !child->GetRange().IsOutside(range))
1504 {
1505 if (child->IsEmpty())
1506 {
1507 child->Dereference();
1508 m_children.Erase(node);
1509 }
1510 node = next;
1511 }
1512 else
1513 node = node->GetNext();
1514 }
1515 }
1516
1517 return true;
1518 }
1519
1520 /// Dump to output stream for debugging
1521 void wxRichTextCompositeObject::Dump(wxTextOutputStream& stream)
1522 {
1523 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1524 while (node)
1525 {
1526 wxRichTextObject* child = node->GetData();
1527 child->Dump(stream);
1528 node = node->GetNext();
1529 }
1530 }
1531
1532 /// Get/set the object size for the given range. Returns false if the range
1533 /// is invalid for this object.
1534 bool wxRichTextCompositeObject::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, const wxPoint& position, const wxSize& parentSize, wxArrayInt* partialExtents) const
1535 {
1536 if (!range.IsWithin(GetRange()))
1537 return false;
1538
1539 wxSize sz;
1540
1541 wxArrayInt childExtents;
1542 wxArrayInt* p;
1543 if (partialExtents)
1544 p = & childExtents;
1545 else
1546 p = NULL;
1547
1548 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1549 while (node)
1550 {
1551 wxRichTextObject* child = node->GetData();
1552 if (!child->GetRange().IsOutside(range))
1553 {
1554 // Floating objects have a zero size within the paragraph.
1555 if (child->IsFloating() && wxRichTextBuffer::GetFloatingLayoutMode())
1556 {
1557 if (partialExtents)
1558 {
1559 int lastSize;
1560 if (partialExtents->GetCount() > 0)
1561 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
1562 else
1563 lastSize = 0;
1564
1565 partialExtents->Add(0 /* zero size */ + lastSize);
1566 }
1567 }
1568 else
1569 {
1570 wxSize childSize;
1571
1572 wxRichTextRange rangeToUse = range;
1573 rangeToUse.LimitTo(child->GetRange());
1574 if (child->IsTopLevel())
1575 rangeToUse = child->GetOwnRange();
1576
1577 int childDescent = 0;
1578
1579 // At present wxRICHTEXT_HEIGHT_ONLY is only fast if we're already cached the size,
1580 // but it's only going to be used after caching has taken place.
1581 if ((flags & wxRICHTEXT_HEIGHT_ONLY) && child->GetCachedSize().y != 0)
1582 {
1583 childDescent = child->GetDescent();
1584 childSize = child->GetCachedSize();
1585
1586 sz.y = wxMax(sz.y, childSize.y);
1587 sz.x += childSize.x;
1588 descent = wxMax(descent, childDescent);
1589 }
1590 else if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y), parentSize, p))
1591 {
1592 sz.y = wxMax(sz.y, childSize.y);
1593 sz.x += childSize.x;
1594 descent = wxMax(descent, childDescent);
1595
1596 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange() || child->IsTopLevel()))
1597 {
1598 child->SetCachedSize(childSize);
1599 child->SetDescent(childDescent);
1600 }
1601
1602 if (partialExtents)
1603 {
1604 int lastSize;
1605 if (partialExtents->GetCount() > 0)
1606 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
1607 else
1608 lastSize = 0;
1609
1610 size_t i;
1611 for (i = 0; i < childExtents.GetCount(); i++)
1612 {
1613 partialExtents->Add(childExtents[i] + lastSize);
1614 }
1615 }
1616 }
1617 }
1618
1619 if (p)
1620 p->Clear();
1621 }
1622
1623 node = node->GetNext();
1624 }
1625 size = sz;
1626 return true;
1627 }
1628
1629 // Invalidate the buffer. With no argument, invalidates whole buffer.
1630 void wxRichTextCompositeObject::Invalidate(const wxRichTextRange& invalidRange)
1631 {
1632 wxRichTextObject::Invalidate(invalidRange);
1633
1634 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1635 while (node)
1636 {
1637 wxRichTextObject* child = node->GetData();
1638 if (invalidRange != wxRICHTEXT_ALL && invalidRange != wxRICHTEXT_NONE && child->GetRange().IsOutside(invalidRange))
1639 {
1640 // Skip
1641 }
1642 else if (child->IsTopLevel())
1643 {
1644 if (wxRichTextBuffer::GetFloatingLayoutMode() && child->IsFloating() && GetBuffer()->GetFloatCollector() && GetBuffer()->GetFloatCollector()->HasFloat(child))
1645 {
1646 // Don't invalidate subhierarchy if we've already been laid out
1647 }
1648 else
1649 {
1650 if (invalidRange == wxRICHTEXT_NONE)
1651 child->Invalidate(wxRICHTEXT_NONE);
1652 else
1653 child->Invalidate(wxRICHTEXT_ALL); // All children must be invalidated if within parent range
1654 }
1655 }
1656 else
1657 child->Invalidate(invalidRange);
1658 node = node->GetNext();
1659 }
1660 }
1661
1662 // Move the object recursively, by adding the offset from old to new
1663 void wxRichTextCompositeObject::Move(const wxPoint& pt)
1664 {
1665 wxPoint oldPos = GetPosition();
1666 SetPosition(pt);
1667 wxPoint offset = pt - oldPos;
1668
1669 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1670 while (node)
1671 {
1672 wxRichTextObject* child = node->GetData();
1673 wxPoint childPos = child->GetPosition() + offset;
1674 child->Move(childPos);
1675 node = node->GetNext();
1676 }
1677 }
1678
1679
1680 /*!
1681 * wxRichTextParagraphLayoutBox
1682 * This box knows how to lay out paragraphs.
1683 */
1684
1685 IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraphLayoutBox, wxRichTextCompositeObject)
1686
1687 wxRichTextParagraphLayoutBox::wxRichTextParagraphLayoutBox(wxRichTextObject* parent):
1688 wxRichTextCompositeObject(parent)
1689 {
1690 Init();
1691 }
1692
1693 wxRichTextParagraphLayoutBox::~wxRichTextParagraphLayoutBox()
1694 {
1695 if (m_floatCollector)
1696 {
1697 delete m_floatCollector;
1698 m_floatCollector = NULL;
1699 }
1700 }
1701
1702 /// Initialize the object.
1703 void wxRichTextParagraphLayoutBox::Init()
1704 {
1705 m_ctrl = NULL;
1706
1707 // For now, assume is the only box and has no initial size.
1708 m_range = wxRichTextRange(0, -1);
1709 m_ownRange = wxRichTextRange(0, -1);
1710
1711 m_invalidRange = wxRICHTEXT_ALL;
1712
1713 m_partialParagraph = false;
1714 m_floatCollector = NULL;
1715 }
1716
1717 void wxRichTextParagraphLayoutBox::Clear()
1718 {
1719 DeleteChildren();
1720
1721 if (m_floatCollector)
1722 delete m_floatCollector;
1723 m_floatCollector = NULL;
1724 m_partialParagraph = false;
1725 }
1726
1727 /// Copy
1728 void wxRichTextParagraphLayoutBox::Copy(const wxRichTextParagraphLayoutBox& obj)
1729 {
1730 Clear();
1731
1732 wxRichTextCompositeObject::Copy(obj);
1733
1734 m_partialParagraph = obj.m_partialParagraph;
1735 m_defaultAttributes = obj.m_defaultAttributes;
1736 }
1737
1738 // Gather information about floating objects; only gather floats for those paragraphs that
1739 // will not be formatted again due to optimization, after which floats will be gathered per-paragraph
1740 // during layout.
1741 bool wxRichTextParagraphLayoutBox::UpdateFloatingObjects(const wxRect& availableRect, wxRichTextObject* untilObj)
1742 {
1743 if (m_floatCollector != NULL)
1744 delete m_floatCollector;
1745 m_floatCollector = new wxRichTextFloatCollector(availableRect);
1746 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1747 // Only gather floats up to the point we'll start formatting paragraphs.
1748 while (untilObj && node && node->GetData() != untilObj)
1749 {
1750 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1751 wxASSERT (child != NULL);
1752 if (child)
1753 m_floatCollector->CollectFloat(child);
1754 node = node->GetNext();
1755 }
1756
1757 return true;
1758 }
1759
1760 // Returns the style sheet associated with the overall buffer.
1761 wxRichTextStyleSheet* wxRichTextParagraphLayoutBox::GetStyleSheet() const
1762 {
1763 return GetBuffer() ? GetBuffer()->GetStyleSheet() : (wxRichTextStyleSheet*) NULL;
1764 }
1765
1766 // Get the number of floating objects at this level
1767 int wxRichTextParagraphLayoutBox::GetFloatingObjectCount() const
1768 {
1769 if (m_floatCollector)
1770 return m_floatCollector->GetFloatingObjectCount();
1771 else
1772 return 0;
1773 }
1774
1775 // Get a list of floating objects
1776 bool wxRichTextParagraphLayoutBox::GetFloatingObjects(wxRichTextObjectList& objects) const
1777 {
1778 if (m_floatCollector)
1779 {
1780 return m_floatCollector->GetFloatingObjects(objects);
1781 }
1782 else
1783 return false;
1784 }
1785
1786 // Calculate ranges
1787 void wxRichTextParagraphLayoutBox::UpdateRanges()
1788 {
1789 long start = 0;
1790 if (GetParent())
1791 start = GetRange().GetStart();
1792 long end;
1793 CalculateRange(start, end);
1794 }
1795
1796 // HitTest
1797 int wxRichTextParagraphLayoutBox::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
1798 {
1799 if (!IsShown())
1800 return wxRICHTEXT_HITTEST_NONE;
1801
1802 int ret = wxRICHTEXT_HITTEST_NONE;
1803 if (wxRichTextBuffer::GetFloatingLayoutMode() && m_floatCollector && (flags & wxRICHTEXT_HITTEST_NO_FLOATING_OBJECTS) == 0)
1804 ret = m_floatCollector->HitTest(dc, context, pt, textPosition, obj, flags);
1805
1806 if (ret == wxRICHTEXT_HITTEST_NONE)
1807 return wxRichTextCompositeObject::HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
1808 else
1809 {
1810 *contextObj = this;
1811 return ret;
1812 }
1813 }
1814
1815 /// Draw the floating objects
1816 void wxRichTextParagraphLayoutBox::DrawFloats(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
1817 {
1818 if (wxRichTextBuffer::GetFloatingLayoutMode() && m_floatCollector)
1819 m_floatCollector->Draw(dc, context, range, selection, rect, descent, style);
1820 }
1821
1822 void wxRichTextParagraphLayoutBox::MoveAnchoredObjectToParagraph(wxRichTextParagraph* from, wxRichTextParagraph* to, wxRichTextObject* obj)
1823 {
1824 if (from == to)
1825 return;
1826
1827 from->RemoveChild(obj);
1828 to->AppendChild(obj);
1829 }
1830
1831 /// Draw the item
1832 bool wxRichTextParagraphLayoutBox::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
1833 {
1834 if (!IsShown())
1835 return true;
1836
1837 wxRect thisRect(GetPosition(), GetCachedSize());
1838
1839 wxRichTextAttr attr(GetAttributes());
1840 context.ApplyVirtualAttributes(attr, this);
1841
1842 int flags = style;
1843 if (selection.IsValid() && GetParentContainer() != this && selection.GetContainer() == this && selection.WithinSelection(GetRange().GetStart(), GetParentContainer()))
1844 flags |= wxRICHTEXT_DRAW_SELECTED;
1845
1846 // Don't draw guidelines if at top level
1847 int theseFlags = flags;
1848 if (!GetParent())
1849 theseFlags &= ~wxRICHTEXT_DRAW_GUIDELINES;
1850 DrawBoxAttributes(dc, GetBuffer(), attr, thisRect, theseFlags);
1851
1852 if (wxRichTextBuffer::GetFloatingLayoutMode())
1853 DrawFloats(dc, context, range, selection, rect, descent, style);
1854
1855 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1856 while (node)
1857 {
1858 wxRichTextObject* child = node->GetData();
1859
1860 if (child && !child->GetRange().IsOutside(range))
1861 {
1862 wxRect childRect(child->GetPosition(), child->GetCachedSize());
1863 wxRichTextRange childRange = range;
1864 if (child->IsTopLevel())
1865 {
1866 childRange = child->GetOwnRange();
1867 }
1868
1869 if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetTop() > rect.GetBottom())
1870 {
1871 // Stop drawing
1872 break;
1873 }
1874 else if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetBottom() < rect.GetTop())
1875 {
1876 // Skip
1877 }
1878 else
1879 child->Draw(dc, context, childRange, selection, rect, descent, style);
1880 }
1881
1882 node = node->GetNext();
1883 }
1884 return true;
1885 }
1886
1887 /// Lay the item out
1888 bool wxRichTextParagraphLayoutBox::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style)
1889 {
1890 SetPosition(rect.GetPosition());
1891
1892 if (!IsShown())
1893 return true;
1894
1895 wxRect availableSpace;
1896 bool formatRect = (style & wxRICHTEXT_LAYOUT_SPECIFIED_RECT) == wxRICHTEXT_LAYOUT_SPECIFIED_RECT;
1897
1898 wxRichTextAttr attr(GetAttributes());
1899 context.ApplyVirtualAttributes(attr, this);
1900
1901 // If only laying out a specific area, the passed rect has a different meaning:
1902 // the visible part of the buffer. This is used in wxRichTextCtrl::OnSize,
1903 // so that during a size, only the visible part will be relaid out, or
1904 // it would take too long causing flicker. As an approximation, we assume that
1905 // everything up to the start of the visible area is laid out correctly.
1906 if (formatRect)
1907 {
1908 wxRect rect2(0, 0, rect.width, rect.height);
1909 availableSpace = GetAvailableContentArea(dc, context, rect2);
1910
1911 // Invalidate the part of the buffer from the first visible line
1912 // to the end. If other parts of the buffer are currently invalid,
1913 // then they too will be taken into account if they are above
1914 // the visible point.
1915 long startPos = 0;
1916 wxRichTextLine* line = GetLineAtYPosition(rect.y);
1917 if (line)
1918 startPos = line->GetAbsoluteRange().GetStart();
1919
1920 Invalidate(wxRichTextRange(startPos, GetOwnRange().GetEnd()));
1921 }
1922 else
1923 {
1924 availableSpace = GetAvailableContentArea(dc, context, rect);
1925 }
1926
1927 // Fix the width if we're at the top level
1928 if (!GetParent())
1929 attr.GetTextBoxAttr().GetWidth().SetValue(rect.GetWidth(), wxTEXT_ATTR_UNITS_PIXELS);
1930
1931 int leftMargin, rightMargin, topMargin, bottomMargin;
1932 wxRichTextObject::GetTotalMargin(dc, GetBuffer(), attr, leftMargin, rightMargin,
1933 topMargin, bottomMargin);
1934
1935 int maxWidth = 0;
1936 int maxHeight = 0;
1937
1938 // The maximum paragraph maximum width, so we can set the overall maximum width for this object
1939 int maxMaxWidth = 0;
1940
1941 // The maximum paragraph minimum width, so we can set the overall minimum width for this object
1942 int maxMinWidth = 0;
1943
1944 // If we have vertical alignment, we must recalculate everything.
1945 bool hasVerticalAlignment = (attr.GetTextBoxAttr().HasVerticalAlignment() &&
1946 (attr.GetTextBoxAttr().GetVerticalAlignment() > wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_TOP));
1947
1948 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1949
1950 bool layoutAll = true;
1951
1952 // Get invalid range, rounding to paragraph start/end.
1953 wxRichTextRange invalidRange = GetInvalidRange(true);
1954
1955 if (invalidRange == wxRICHTEXT_NONE && !formatRect)
1956 return true;
1957
1958 if (invalidRange == wxRICHTEXT_ALL || hasVerticalAlignment)
1959 layoutAll = true;
1960 else // If we know what range is affected, start laying out from that point on.
1961 if (invalidRange.GetStart() >= GetOwnRange().GetStart())
1962 {
1963 wxRichTextParagraph* firstParagraph = GetParagraphAtPosition(invalidRange.GetStart());
1964 if (firstParagraph)
1965 {
1966 wxRichTextObjectList::compatibility_iterator firstNode = m_children.Find(firstParagraph);
1967 wxRichTextObjectList::compatibility_iterator previousNode;
1968 if ( firstNode )
1969 previousNode = firstNode->GetPrevious();
1970 if (firstNode)
1971 {
1972 if (previousNode)
1973 {
1974 wxRichTextParagraph* previousParagraph = wxDynamicCast(previousNode->GetData(), wxRichTextParagraph);
1975 availableSpace.y = previousParagraph->GetPosition().y + previousParagraph->GetCachedSize().y;
1976 }
1977
1978 // Now we're going to start iterating from the first affected paragraph.
1979 node = firstNode;
1980
1981 layoutAll = false;
1982 }
1983 }
1984 }
1985
1986 // Gather information about only those floating objects that will not be formatted,
1987 // after which floats will be gathered per-paragraph during layout.
1988 if (wxRichTextBuffer::GetFloatingLayoutMode())
1989 UpdateFloatingObjects(availableSpace, node ? node->GetData() : (wxRichTextObject*) NULL);
1990
1991 // A way to force speedy rest-of-buffer layout (the 'else' below)
1992 bool forceQuickLayout = false;
1993
1994 // First get the size of the paragraphs we won't be laying out
1995 wxRichTextObjectList::compatibility_iterator n = m_children.GetFirst();
1996 while (n && n != node)
1997 {
1998 wxRichTextParagraph* child = wxDynamicCast(n->GetData(), wxRichTextParagraph);
1999 if (child)
2000 {
2001 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
2002 maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x);
2003 maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x);
2004 }
2005 n = n->GetNext();
2006 }
2007
2008 while (node)
2009 {
2010 // Assume this box only contains paragraphs
2011
2012 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2013 // Unsure if this is needed
2014 // wxCHECK_MSG( child, false, wxT("Unknown object in layout") );
2015
2016 if (child && child->IsShown())
2017 {
2018 // TODO: what if the child hasn't been laid out (e.g. involved in Undo) but still has 'old' lines
2019 if ( !forceQuickLayout &&
2020 (layoutAll ||
2021 child->GetLines().IsEmpty() ||
2022 !child->GetRange().IsOutside(invalidRange)) )
2023 {
2024 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
2025 // lays out the object again using the minimum size
2026 child->LayoutToBestSize(dc, context, GetBuffer(),
2027 attr, child->GetAttributes(), availableSpace, rect, style&~wxRICHTEXT_LAYOUT_SPECIFIED_RECT);
2028
2029 // Layout must set the cached size
2030 availableSpace.y += child->GetCachedSize().y;
2031 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
2032 maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x);
2033 maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x);
2034
2035 // If we're just formatting the visible part of the buffer,
2036 // and we're now past the bottom of the window, and we don't have any
2037 // floating objects (since they may cause wrapping to change for the rest of the
2038 // the buffer), start quick layout.
2039 if (!hasVerticalAlignment && formatRect && child->GetPosition().y > rect.GetBottom() && GetFloatingObjectCount() == 0)
2040 forceQuickLayout = true;
2041 }
2042 else
2043 {
2044 // We're outside the immediately affected range, so now let's just
2045 // move everything up or down. This assumes that all the children have previously
2046 // been laid out and have wrapped line lists associated with them.
2047 // TODO: check all paragraphs before the affected range.
2048
2049 int inc = availableSpace.y - child->GetPosition().y;
2050
2051 while (node)
2052 {
2053 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2054 if (child)
2055 {
2056 if (child->GetLines().GetCount() == 0)
2057 {
2058 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
2059 // lays out the object again using the minimum size
2060 child->LayoutToBestSize(dc, context, GetBuffer(),
2061 attr, child->GetAttributes(), availableSpace, rect, style&~wxRICHTEXT_LAYOUT_SPECIFIED_RECT);
2062
2063 //child->Layout(dc, availableChildRect, style);
2064 }
2065 else
2066 child->Move(wxPoint(child->GetPosition().x, child->GetPosition().y + inc));
2067
2068 availableSpace.y += child->GetCachedSize().y;
2069 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
2070 maxMinWidth = wxMax(maxMinWidth, child->GetMinSize().x);
2071 maxMaxWidth = wxMax(maxMaxWidth, child->GetMaxSize().x);
2072 }
2073
2074 node = node->GetNext();
2075 }
2076 break;
2077 }
2078 }
2079
2080 node = node->GetNext();
2081 }
2082
2083 node = m_children.GetLast();
2084 if (node && node->GetData()->IsShown())
2085 {
2086 wxRichTextObject* child = node->GetData();
2087 maxHeight = child->GetPosition().y - (GetPosition().y + topMargin) + child->GetCachedSize().y;
2088 }
2089 else
2090 maxHeight = 0; // topMargin + bottomMargin;
2091
2092 // Check the bottom edge of any floating object
2093 if (wxRichTextBuffer::GetFloatingLayoutMode() && GetFloatCollector() && GetFloatCollector()->HasFloats())
2094 {
2095 int bottom = GetFloatCollector()->GetLastRectBottom();
2096 if (bottom > maxHeight)
2097 maxHeight = bottom;
2098 }
2099
2100 if (attr.GetTextBoxAttr().GetSize().GetWidth().IsValid())
2101 {
2102 wxRect r = AdjustAvailableSpace(dc, GetBuffer(), wxRichTextAttr() /* not used */, attr, parentRect, parentRect);
2103 int w = r.GetWidth();
2104
2105 // Convert external to content rect
2106 w = w - leftMargin - rightMargin;
2107 maxWidth = wxMax(maxWidth, w);
2108 maxMaxWidth = wxMax(maxMaxWidth, w);
2109 }
2110 else
2111 {
2112 // TODO: Make sure the layout box's position reflects
2113 // the position of the children, but without
2114 // breaking layout of a box within a paragraph.
2115 }
2116
2117 // TODO: (also in para layout) should set the
2118 // object's size to an absolute one if specified,
2119 // but if not specified, calculate it from content.
2120
2121 // We need to add back the margins etc.
2122 {
2123 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
2124 contentRect = wxRect(wxPoint(0, 0), wxSize(maxWidth, maxHeight));
2125 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
2126 SetCachedSize(marginRect.GetSize());
2127 }
2128
2129 // The maximum size is the greatest of all maximum widths for all paragraphs.
2130 {
2131 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
2132 contentRect = wxRect(wxPoint(0, 0), wxSize(maxMaxWidth, maxHeight)); // Actually max height is a lie, we can't know it
2133 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
2134 SetMaxSize(marginRect.GetSize());
2135 }
2136
2137 // The minimum size is the greatest of all minimum widths for all paragraphs.
2138 {
2139 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
2140 contentRect = wxRect(wxPoint(0, 0), wxSize(maxMinWidth, maxHeight)); // Actually max height is a lie, we can't know it
2141 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
2142 SetMinSize(marginRect.GetSize());
2143 }
2144
2145 if (attr.GetTextBoxAttr().HasVerticalAlignment() &&
2146 (attr.GetTextBoxAttr().GetVerticalAlignment() > wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_TOP))
2147 {
2148 int yOffset = 0;
2149 int leftOverSpace = availableSpace.height - topMargin - bottomMargin - maxHeight;
2150 if (leftOverSpace > 0)
2151 {
2152 if (attr.GetTextBoxAttr().GetVerticalAlignment() == wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_CENTRE)
2153 {
2154 yOffset = (leftOverSpace/2);
2155 }
2156 else if (attr.GetTextBoxAttr().GetVerticalAlignment() == wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_BOTTOM)
2157 {
2158 yOffset = leftOverSpace;
2159 }
2160 }
2161
2162 // Move all the children to vertically align the content
2163 // This doesn't take into account floating objects, unfortunately.
2164 if (yOffset != 0)
2165 {
2166 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2167 while (node)
2168 {
2169 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2170 if (child)
2171 child->Move(wxPoint(child->GetPosition().x, child->GetPosition().y + yOffset));
2172
2173 node = node->GetNext();
2174 }
2175 }
2176 }
2177
2178 m_invalidRange = wxRICHTEXT_NONE;
2179
2180 return true;
2181 }
2182
2183 /// Get/set the size for the given range.
2184 bool wxRichTextParagraphLayoutBox::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, const wxPoint& position, const wxSize& parentSize, wxArrayInt* WXUNUSED(partialExtents)) const
2185 {
2186 wxSize sz;
2187
2188 wxRichTextObjectList::compatibility_iterator startPara = wxRichTextObjectList::compatibility_iterator();
2189 wxRichTextObjectList::compatibility_iterator endPara = wxRichTextObjectList::compatibility_iterator();
2190
2191 // First find the first paragraph whose starting position is within the range.
2192 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2193 while (node)
2194 {
2195 // child is a paragraph
2196 wxRichTextObject* child = node->GetData();
2197 const wxRichTextRange& r = child->GetRange();
2198
2199 if (r.GetStart() <= range.GetStart() && r.GetEnd() >= range.GetStart())
2200 {
2201 startPara = node;
2202 break;
2203 }
2204
2205 node = node->GetNext();
2206 }
2207
2208 // Next find the last paragraph containing part of the range
2209 node = m_children.GetFirst();
2210 while (node)
2211 {
2212 // child is a paragraph
2213 wxRichTextObject* child = node->GetData();
2214 const wxRichTextRange& r = child->GetRange();
2215
2216 if (r.GetStart() <= range.GetEnd() && r.GetEnd() >= range.GetEnd())
2217 {
2218 endPara = node;
2219 break;
2220 }
2221
2222 node = node->GetNext();
2223 }
2224
2225 if (!startPara || !endPara)
2226 return false;
2227
2228 // Now we can add up the sizes
2229 for (node = startPara; node ; node = node->GetNext())
2230 {
2231 // child is a paragraph
2232 wxRichTextObject* child = node->GetData();
2233 const wxRichTextRange& childRange = child->GetRange();
2234 wxRichTextRange rangeToFind = range;
2235 rangeToFind.LimitTo(childRange);
2236
2237 if (child->IsTopLevel())
2238 rangeToFind = child->GetOwnRange();
2239
2240 wxSize childSize;
2241
2242 int childDescent = 0;
2243 child->GetRangeSize(rangeToFind, childSize, childDescent, dc, context, flags, position, parentSize);
2244
2245 descent = wxMax(childDescent, descent);
2246
2247 sz.x = wxMax(sz.x, childSize.x);
2248 sz.y += childSize.y;
2249
2250 if (node == endPara)
2251 break;
2252 }
2253
2254 size = sz;
2255
2256 return true;
2257 }
2258
2259 /// Get the paragraph at the given position
2260 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtPosition(long pos, bool caretPosition) const
2261 {
2262 if (caretPosition)
2263 pos ++;
2264
2265 // First find the first paragraph whose starting position is within the range.
2266 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2267 while (node)
2268 {
2269 // child is a paragraph
2270 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2271 // wxASSERT (child != NULL);
2272
2273 if (child)
2274 {
2275 // Return first child in buffer if position is -1
2276 // if (pos == -1)
2277 // return child;
2278
2279 if (child->GetRange().Contains(pos))
2280 return child;
2281 }
2282
2283 node = node->GetNext();
2284 }
2285 return NULL;
2286 }
2287
2288 /// Get the line at the given position
2289 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtPosition(long pos, bool caretPosition) const
2290 {
2291 if (caretPosition)
2292 pos ++;
2293
2294 // First find the first paragraph whose starting position is within the range.
2295 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2296 while (node)
2297 {
2298 wxRichTextObject* obj = (wxRichTextObject*) node->GetData();
2299 if (obj->GetRange().Contains(pos))
2300 {
2301 // child is a paragraph
2302 wxRichTextParagraph* child = wxDynamicCast(obj, wxRichTextParagraph);
2303 // wxASSERT (child != NULL);
2304
2305 if (child)
2306 {
2307 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2308 while (node2)
2309 {
2310 wxRichTextLine* line = node2->GetData();
2311
2312 wxRichTextRange range = line->GetAbsoluteRange();
2313
2314 if (range.Contains(pos) ||
2315
2316 // If the position is end-of-paragraph, then return the last line of
2317 // of the paragraph.
2318 ((range.GetEnd() == child->GetRange().GetEnd()-1) && (pos == child->GetRange().GetEnd())))
2319 return line;
2320
2321 node2 = node2->GetNext();
2322 }
2323 }
2324 }
2325
2326 node = node->GetNext();
2327 }
2328
2329 int lineCount = GetLineCount();
2330 if (lineCount > 0)
2331 return GetLineForVisibleLineNumber(lineCount-1);
2332 else
2333 return NULL;
2334 }
2335
2336 /// Get the line at the given y pixel position, or the last line.
2337 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtYPosition(int y) const
2338 {
2339 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2340 while (node)
2341 {
2342 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2343 // wxASSERT (child != NULL);
2344
2345 if (child)
2346 {
2347 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2348 while (node2)
2349 {
2350 wxRichTextLine* line = node2->GetData();
2351
2352 wxRect rect(line->GetRect());
2353
2354 if (y <= rect.GetBottom())
2355 return line;
2356
2357 node2 = node2->GetNext();
2358 }
2359 }
2360
2361 node = node->GetNext();
2362 }
2363
2364 // Return last line
2365 int lineCount = GetLineCount();
2366 if (lineCount > 0)
2367 return GetLineForVisibleLineNumber(lineCount-1);
2368 else
2369 return NULL;
2370 }
2371
2372 /// Get the number of visible lines
2373 int wxRichTextParagraphLayoutBox::GetLineCount() const
2374 {
2375 int count = 0;
2376
2377 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2378 while (node)
2379 {
2380 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2381 // wxASSERT (child != NULL);
2382
2383 if (child)
2384 count += child->GetLines().GetCount();
2385
2386 node = node->GetNext();
2387 }
2388 return count;
2389 }
2390
2391
2392 /// Get the paragraph for a given line
2393 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphForLine(wxRichTextLine* line) const
2394 {
2395 return GetParagraphAtPosition(line->GetAbsoluteRange().GetStart());
2396 }
2397
2398 /// Get the line size at the given position
2399 wxSize wxRichTextParagraphLayoutBox::GetLineSizeAtPosition(long pos, bool caretPosition) const
2400 {
2401 wxRichTextLine* line = GetLineAtPosition(pos, caretPosition);
2402 if (line)
2403 {
2404 return line->GetSize();
2405 }
2406 else
2407 return wxSize(0, 0);
2408 }
2409
2410
2411 /// Convenience function to add a paragraph of text
2412 wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraph(const wxString& text, wxRichTextAttr* paraStyle)
2413 {
2414 // Don't use the base style, just the default style, and the base style will
2415 // be combined at display time.
2416 // Divide into paragraph and character styles.
2417
2418 wxRichTextAttr defaultCharStyle;
2419 wxRichTextAttr defaultParaStyle;
2420
2421 // If the default style is a named paragraph style, don't apply any character formatting
2422 // to the initial text string.
2423 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
2424 {
2425 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
2426 if (def)
2427 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
2428 }
2429 else
2430 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
2431
2432 wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle;
2433 wxRichTextAttr* cStyle = & defaultCharStyle;
2434
2435 wxRichTextParagraph* para = new wxRichTextParagraph(text, this, pStyle, cStyle);
2436 para->GetAttributes().GetTextBoxAttr().Reset();
2437
2438 AppendChild(para);
2439
2440 UpdateRanges();
2441
2442 return para->GetRange();
2443 }
2444
2445 /// Adds multiple paragraphs, based on newlines.
2446 wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraphs(const wxString& text, wxRichTextAttr* paraStyle)
2447 {
2448 // Don't use the base style, just the default style, and the base style will
2449 // be combined at display time.
2450 // Divide into paragraph and character styles.
2451
2452 wxRichTextAttr defaultCharStyle;
2453 wxRichTextAttr defaultParaStyle;
2454
2455 // If the default style is a named paragraph style, don't apply any character formatting
2456 // to the initial text string.
2457 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
2458 {
2459 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
2460 if (def)
2461 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
2462 }
2463 else
2464 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
2465
2466 wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle;
2467 wxRichTextAttr* cStyle = & defaultCharStyle;
2468
2469 wxRichTextParagraph* firstPara = NULL;
2470 wxRichTextParagraph* lastPara = NULL;
2471
2472 wxRichTextRange range(-1, -1);
2473
2474 size_t i = 0;
2475 size_t len = text.length();
2476 wxString line;
2477 wxRichTextParagraph* para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle);
2478 para->GetAttributes().GetTextBoxAttr().Reset();
2479
2480 AppendChild(para);
2481
2482 firstPara = para;
2483 lastPara = para;
2484
2485 while (i < len)
2486 {
2487 wxChar ch = text[i];
2488 if (ch == wxT('\n') || ch == wxT('\r'))
2489 {
2490 if (i != (len-1))
2491 {
2492 wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
2493 plainText->SetText(line);
2494
2495 para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle);
2496 para->GetAttributes().GetTextBoxAttr().Reset();
2497
2498 AppendChild(para);
2499
2500 lastPara = para;
2501 line = wxEmptyString;
2502 }
2503 }
2504 else
2505 line += ch;
2506
2507 i ++;
2508 }
2509
2510 if (!line.empty())
2511 {
2512 wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
2513 plainText->SetText(line);
2514 }
2515
2516 UpdateRanges();
2517
2518 return wxRichTextRange(firstPara->GetRange().GetStart(), lastPara->GetRange().GetEnd());
2519 }
2520
2521 /// Convenience function to add an image
2522 wxRichTextRange wxRichTextParagraphLayoutBox::AddImage(const wxImage& image, wxRichTextAttr* paraStyle)
2523 {
2524 // Don't use the base style, just the default style, and the base style will
2525 // be combined at display time.
2526 // Divide into paragraph and character styles.
2527
2528 wxRichTextAttr defaultCharStyle;
2529 wxRichTextAttr defaultParaStyle;
2530
2531 // If the default style is a named paragraph style, don't apply any character formatting
2532 // to the initial text string.
2533 if (GetDefaultStyle().HasParagraphStyleName() && GetStyleSheet())
2534 {
2535 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(GetDefaultStyle().GetParagraphStyleName());
2536 if (def)
2537 defaultParaStyle = def->GetStyleMergedWithBase(GetStyleSheet());
2538 }
2539 else
2540 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
2541
2542 wxRichTextAttr* pStyle = paraStyle ? paraStyle : (wxRichTextAttr*) & defaultParaStyle;
2543 wxRichTextAttr* cStyle = & defaultCharStyle;
2544
2545 wxRichTextParagraph* para = new wxRichTextParagraph(this, pStyle);
2546 para->GetAttributes().GetTextBoxAttr().Reset();
2547 AppendChild(para);
2548 para->AppendChild(new wxRichTextImage(image, this, cStyle));
2549
2550 UpdateRanges();
2551
2552 return para->GetRange();
2553 }
2554
2555
2556 /// Insert fragment into this box at the given position. If partialParagraph is true,
2557 /// it is assumed that the last (or only) paragraph is just a piece of data with no paragraph
2558 /// marker.
2559
2560 bool wxRichTextParagraphLayoutBox::InsertFragment(long position, wxRichTextParagraphLayoutBox& fragment)
2561 {
2562 // First, find the first paragraph whose starting position is within the range.
2563 wxRichTextParagraph* para = GetParagraphAtPosition(position);
2564 if (para)
2565 {
2566 wxRichTextAttr originalAttr = para->GetAttributes();
2567
2568 wxRichTextObjectList::compatibility_iterator node = m_children.Find(para);
2569
2570 // Now split at this position, returning the object to insert the new
2571 // ones in front of.
2572 wxRichTextObject* nextObject = para->SplitAt(position);
2573
2574 // Special case: partial paragraph, just one paragraph. Might be a small amount of
2575 // text, for example, so let's optimize.
2576
2577 if (fragment.GetPartialParagraph() && fragment.GetChildren().GetCount() == 1)
2578 {
2579 // Add the first para to this para...
2580 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
2581 if (!firstParaNode)
2582 return false;
2583
2584 // Iterate through the fragment paragraph inserting the content into this paragraph.
2585 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
2586 wxASSERT (firstPara != NULL);
2587
2588 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
2589 while (objectNode)
2590 {
2591 wxRichTextObject* newObj = objectNode->GetData()->Clone();
2592
2593 if (!nextObject)
2594 {
2595 // Append
2596 para->AppendChild(newObj);
2597 }
2598 else
2599 {
2600 // Insert before nextObject
2601 para->InsertChild(newObj, nextObject);
2602 }
2603
2604 objectNode = objectNode->GetNext();
2605 }
2606
2607 return true;
2608 }
2609 else
2610 {
2611 // Procedure for inserting a fragment consisting of a number of
2612 // paragraphs:
2613 //
2614 // 1. Remove and save the content that's after the insertion point, for adding
2615 // back once we've added the fragment.
2616 // 2. Add the content from the first fragment paragraph to the current
2617 // paragraph.
2618 // 3. Add remaining fragment paragraphs after the current paragraph.
2619 // 4. Add back the saved content from the first paragraph. If partialParagraph
2620 // is true, add it to the last paragraph added and not a new one.
2621
2622 // 1. Remove and save objects after split point.
2623 wxList savedObjects;
2624 if (nextObject)
2625 para->MoveToList(nextObject, savedObjects);
2626
2627 // 2. Add the content from the 1st fragment paragraph.
2628 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
2629 if (!firstParaNode)
2630 return false;
2631
2632 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
2633 wxASSERT(firstPara != NULL);
2634
2635 if (!(fragment.GetAttributes().GetFlags() & wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE))
2636 para->SetAttributes(firstPara->GetAttributes());
2637
2638 // Save empty paragraph attributes for appending later
2639 // These are character attributes deliberately set for a new paragraph. Without this,
2640 // we couldn't pass default attributes when appending a new paragraph.
2641 wxRichTextAttr emptyParagraphAttributes;
2642
2643 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
2644
2645 if (objectNode && firstPara->GetChildren().GetCount() == 1 && objectNode->GetData()->IsEmpty())
2646 emptyParagraphAttributes = objectNode->GetData()->GetAttributes();
2647
2648 while (objectNode)
2649 {
2650 wxRichTextObject* newObj = objectNode->GetData()->Clone();
2651
2652 // Append
2653 para->AppendChild(newObj);
2654
2655 objectNode = objectNode->GetNext();
2656 }
2657
2658 // 3. Add remaining fragment paragraphs after the current paragraph.
2659 wxRichTextObjectList::compatibility_iterator nextParagraphNode = node->GetNext();
2660 wxRichTextObject* nextParagraph = NULL;
2661 if (nextParagraphNode)
2662 nextParagraph = nextParagraphNode->GetData();
2663
2664 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst()->GetNext();
2665 wxRichTextParagraph* finalPara = para;
2666
2667 bool needExtraPara = (!i || !fragment.GetPartialParagraph());
2668
2669 // If there was only one paragraph, we need to insert a new one.
2670 while (i)
2671 {
2672 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
2673 wxASSERT( para != NULL );
2674
2675 finalPara = (wxRichTextParagraph*) para->Clone();
2676
2677 if (nextParagraph)
2678 InsertChild(finalPara, nextParagraph);
2679 else
2680 AppendChild(finalPara);
2681
2682 i = i->GetNext();
2683 }
2684
2685 // If there was only one paragraph, or we have full paragraphs in our fragment,
2686 // we need to insert a new one.
2687 if (needExtraPara)
2688 {
2689 finalPara = new wxRichTextParagraph;
2690
2691 if (nextParagraph)
2692 InsertChild(finalPara, nextParagraph);
2693 else
2694 AppendChild(finalPara);
2695 }
2696
2697 // 4. Add back the remaining content.
2698 if (finalPara)
2699 {
2700 if (nextObject)
2701 finalPara->MoveFromList(savedObjects);
2702
2703 // Ensure there's at least one object
2704 if (finalPara->GetChildCount() == 0)
2705 {
2706 wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString);
2707 text->SetAttributes(emptyParagraphAttributes);
2708
2709 finalPara->AppendChild(text);
2710 }
2711 }
2712
2713 if ((fragment.GetAttributes().GetFlags() & wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE) && firstPara)
2714 finalPara->SetAttributes(firstPara->GetAttributes());
2715 else if (finalPara && finalPara != para)
2716 finalPara->SetAttributes(originalAttr);
2717
2718 return true;
2719 }
2720 }
2721 else
2722 {
2723 // Append
2724 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst();
2725 while (i)
2726 {
2727 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
2728 wxASSERT( para != NULL );
2729
2730 AppendChild(para->Clone());
2731
2732 i = i->GetNext();
2733 }
2734
2735 return true;
2736 }
2737 }
2738
2739 /// Make a copy of the fragment corresponding to the given range, putting it in 'fragment'.
2740 /// If there was an incomplete paragraph at the end, partialParagraph is set to true.
2741 bool wxRichTextParagraphLayoutBox::CopyFragment(const wxRichTextRange& range, wxRichTextParagraphLayoutBox& fragment)
2742 {
2743 wxRichTextObjectList::compatibility_iterator i = GetChildren().GetFirst();
2744 while (i)
2745 {
2746 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
2747 wxASSERT( para != NULL );
2748
2749 if (!para->GetRange().IsOutside(range))
2750 {
2751 fragment.AppendChild(para->Clone());
2752 }
2753 i = i->GetNext();
2754 }
2755
2756 // Now top and tail the first and last paragraphs in our new fragment (which might be the same).
2757 if (!fragment.IsEmpty())
2758 {
2759 wxRichTextParagraph* firstPara = wxDynamicCast(fragment.GetChildren().GetFirst()->GetData(), wxRichTextParagraph);
2760 wxASSERT( firstPara != NULL );
2761
2762 wxRichTextParagraph* lastPara = wxDynamicCast(fragment.GetChildren().GetLast()->GetData(), wxRichTextParagraph);
2763 wxASSERT( lastPara != NULL );
2764
2765 if (!firstPara || !lastPara)
2766 return false;
2767
2768 bool isFragment = (range.GetEnd() < lastPara->GetRange().GetEnd());
2769
2770 long firstPos = firstPara->GetRange().GetStart();
2771
2772 // Adjust for renumbering from zero
2773 wxRichTextRange topTailRange(range.GetStart() - firstPos, range.GetEnd() - firstPos);
2774
2775 long end;
2776 fragment.CalculateRange(0, end);
2777
2778 // Chop off the start of the paragraph
2779 if (topTailRange.GetStart() > 0)
2780 {
2781 wxRichTextRange r(0, topTailRange.GetStart()-1);
2782 firstPara->DeleteRange(r);
2783
2784 // Make sure the numbering is correct
2785 fragment.CalculateRange(0, end);
2786
2787 // Now, we've deleted some positions, so adjust the range
2788 // accordingly.
2789 topTailRange.SetStart(range.GetLength());
2790 topTailRange.SetEnd(fragment.GetOwnRange().GetEnd());
2791 }
2792 else
2793 {
2794 topTailRange.SetStart(range.GetLength());
2795 topTailRange.SetEnd(fragment.GetOwnRange().GetEnd());
2796 }
2797
2798 if (topTailRange.GetStart() < lastPara->GetRange().GetEnd())
2799 {
2800 lastPara->DeleteRange(topTailRange);
2801
2802 // Make sure the numbering is correct
2803 long end;
2804 fragment.CalculateRange(0, end);
2805
2806 // We only have part of a paragraph at the end
2807 fragment.SetPartialParagraph(true);
2808 }
2809 else
2810 {
2811 // We have a partial paragraph (don't save last new paragraph marker)
2812 // or complete paragraph
2813 fragment.SetPartialParagraph(isFragment);
2814 }
2815 }
2816
2817 return true;
2818 }
2819
2820 /// Given a position, get the number of the visible line (potentially many to a paragraph),
2821 /// starting from zero at the start of the buffer.
2822 long wxRichTextParagraphLayoutBox::GetVisibleLineNumber(long pos, bool caretPosition, bool startOfLine) const
2823 {
2824 if (caretPosition)
2825 pos ++;
2826
2827 int lineCount = 0;
2828
2829 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2830 while (node)
2831 {
2832 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2833 // wxASSERT( child != NULL );
2834
2835 if (child)
2836 {
2837 if (child->GetRange().Contains(pos))
2838 {
2839 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2840 while (node2)
2841 {
2842 wxRichTextLine* line = node2->GetData();
2843 wxRichTextRange lineRange = line->GetAbsoluteRange();
2844
2845 if (lineRange.Contains(pos) || pos == lineRange.GetStart())
2846 {
2847 // If the caret is displayed at the end of the previous wrapped line,
2848 // we want to return the line it's _displayed_ at (not the actual line
2849 // containing the position).
2850 if (lineRange.GetStart() == pos && !startOfLine && child->GetRange().GetStart() != pos)
2851 return lineCount - 1;
2852 else
2853 return lineCount;
2854 }
2855
2856 lineCount ++;
2857
2858 node2 = node2->GetNext();
2859 }
2860 // If we didn't find it in the lines, it must be
2861 // the last position of the paragraph. So return the last line.
2862 return lineCount-1;
2863 }
2864 else
2865 lineCount += child->GetLines().GetCount();
2866 }
2867
2868 node = node->GetNext();
2869 }
2870
2871 // Not found
2872 return -1;
2873 }
2874
2875 /// Given a line number, get the corresponding wxRichTextLine object.
2876 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineForVisibleLineNumber(long lineNumber) const
2877 {
2878 int lineCount = 0;
2879
2880 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2881 while (node)
2882 {
2883 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2884 // wxASSERT(child != NULL);
2885
2886 if (child)
2887 {
2888 if (lineNumber < (int) (child->GetLines().GetCount() + lineCount))
2889 {
2890 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
2891 while (node2)
2892 {
2893 wxRichTextLine* line = node2->GetData();
2894
2895 if (lineCount == lineNumber)
2896 return line;
2897
2898 lineCount ++;
2899
2900 node2 = node2->GetNext();
2901 }
2902 }
2903 else
2904 lineCount += child->GetLines().GetCount();
2905 }
2906
2907 node = node->GetNext();
2908 }
2909
2910 // Didn't find it
2911 return NULL;
2912 }
2913
2914 /// Delete range from layout.
2915 bool wxRichTextParagraphLayoutBox::DeleteRange(const wxRichTextRange& range)
2916 {
2917 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2918
2919 wxRichTextParagraph* firstPara = NULL;
2920 while (node)
2921 {
2922 wxRichTextParagraph* obj = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2923 // wxASSERT (obj != NULL);
2924
2925 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
2926
2927 if (obj)
2928 {
2929 // Delete the range in each paragraph
2930
2931 if (!obj->GetRange().IsOutside(range))
2932 {
2933 // Deletes the content of this object within the given range
2934 obj->DeleteRange(range);
2935
2936 wxRichTextRange thisRange = obj->GetRange();
2937 wxRichTextAttr thisAttr = obj->GetAttributes();
2938
2939 // If the whole paragraph is within the range to delete,
2940 // delete the whole thing.
2941 if (range.GetStart() <= thisRange.GetStart() && range.GetEnd() >= thisRange.GetEnd())
2942 {
2943 // Delete the whole object
2944 RemoveChild(obj, true);
2945 obj = NULL;
2946 }
2947 else if (!firstPara)
2948 firstPara = obj;
2949
2950 // If the range includes the paragraph end, we need to join this
2951 // and the next paragraph.
2952 if (range.GetEnd() <= thisRange.GetEnd())
2953 {
2954 // We need to move the objects from the next paragraph
2955 // to this paragraph
2956
2957 wxRichTextParagraph* nextParagraph = NULL;
2958 if ((range.GetEnd() < thisRange.GetEnd()) && obj)
2959 nextParagraph = obj;
2960 else
2961 {
2962 // We're ending at the end of the paragraph, so merge the _next_ paragraph.
2963 if (next)
2964 nextParagraph = wxDynamicCast(next->GetData(), wxRichTextParagraph);
2965 }
2966
2967 bool applyFinalParagraphStyle = firstPara && nextParagraph && nextParagraph != firstPara;
2968
2969 wxRichTextAttr nextParaAttr;
2970 if (applyFinalParagraphStyle)
2971 {
2972 // Special case when deleting the end of a paragraph - use _this_ paragraph's style,
2973 // not the next one.
2974 if (range.GetStart() == range.GetEnd() && range.GetStart() == thisRange.GetEnd())
2975 nextParaAttr = thisAttr;
2976 else
2977 nextParaAttr = nextParagraph->GetAttributes();
2978 }
2979
2980 if (firstPara && nextParagraph && firstPara != nextParagraph)
2981 {
2982 // Move the objects to the previous para
2983 wxRichTextObjectList::compatibility_iterator node1 = nextParagraph->GetChildren().GetFirst();
2984
2985 while (node1)
2986 {
2987 wxRichTextObject* obj1 = node1->GetData();
2988
2989 firstPara->AppendChild(obj1);
2990
2991 wxRichTextObjectList::compatibility_iterator next1 = node1->GetNext();
2992 nextParagraph->GetChildren().Erase(node1);
2993
2994 node1 = next1;
2995 }
2996
2997 // Delete the paragraph
2998 RemoveChild(nextParagraph, true);
2999 }
3000
3001 // Avoid empty paragraphs
3002 if (firstPara && firstPara->GetChildren().GetCount() == 0)
3003 {
3004 wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString);
3005 firstPara->AppendChild(text);
3006 }
3007
3008 if (applyFinalParagraphStyle)
3009 firstPara->SetAttributes(nextParaAttr);
3010
3011 return true;
3012 }
3013 }
3014 }
3015
3016 node = next;
3017 }
3018
3019 return true;
3020 }
3021
3022 /// Get any text in this object for the given range
3023 wxString wxRichTextParagraphLayoutBox::GetTextForRange(const wxRichTextRange& range) const
3024 {
3025 int lineCount = 0;
3026 wxString text;
3027 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3028 while (node)
3029 {
3030 wxRichTextObject* child = node->GetData();
3031 if (!child->GetRange().IsOutside(range))
3032 {
3033 wxRichTextRange childRange = range;
3034 childRange.LimitTo(child->GetRange());
3035
3036 wxString childText = child->GetTextForRange(childRange);
3037
3038 text += childText;
3039
3040 if ((childRange.GetEnd() == child->GetRange().GetEnd()) && node->GetNext())
3041 text += wxT("\n");
3042
3043 lineCount ++;
3044 }
3045 node = node->GetNext();
3046 }
3047
3048 return text;
3049 }
3050
3051 /// Get all the text
3052 wxString wxRichTextParagraphLayoutBox::GetText() const
3053 {
3054 return GetTextForRange(GetOwnRange());
3055 }
3056
3057 /// Get the paragraph by number
3058 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtLine(long paragraphNumber) const
3059 {
3060 if ((size_t) paragraphNumber >= GetChildCount())
3061 return NULL;
3062
3063 return (wxRichTextParagraph*) GetChild((size_t) paragraphNumber);
3064 }
3065
3066 /// Get the length of the paragraph
3067 int wxRichTextParagraphLayoutBox::GetParagraphLength(long paragraphNumber) const
3068 {
3069 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
3070 if (para)
3071 return para->GetRange().GetLength() - 1; // don't include newline
3072 else
3073 return 0;
3074 }
3075
3076 /// Get the text of the paragraph
3077 wxString wxRichTextParagraphLayoutBox::GetParagraphText(long paragraphNumber) const
3078 {
3079 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
3080 if (para)
3081 return para->GetTextForRange(para->GetRange());
3082 else
3083 return wxEmptyString;
3084 }
3085
3086 /// Convert zero-based line column and paragraph number to a position.
3087 long wxRichTextParagraphLayoutBox::XYToPosition(long x, long y) const
3088 {
3089 wxRichTextParagraph* para = GetParagraphAtLine(y);
3090 if (para)
3091 {
3092 return para->GetRange().GetStart() + x;
3093 }
3094 else
3095 return -1;
3096 }
3097
3098 /// Convert zero-based position to line column and paragraph number
3099 bool wxRichTextParagraphLayoutBox::PositionToXY(long pos, long* x, long* y) const
3100 {
3101 wxRichTextParagraph* para = GetParagraphAtPosition(pos);
3102 if (para)
3103 {
3104 int count = 0;
3105 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3106 while (node)
3107 {
3108 wxRichTextObject* child = node->GetData();
3109 if (child == para)
3110 break;
3111 count ++;
3112 node = node->GetNext();
3113 }
3114
3115 *y = count;
3116 *x = pos - para->GetRange().GetStart();
3117
3118 return true;
3119 }
3120 else
3121 return false;
3122 }
3123
3124 /// Get the leaf object in a paragraph at this position.
3125 /// Given a line number, get the corresponding wxRichTextLine object.
3126 wxRichTextObject* wxRichTextParagraphLayoutBox::GetLeafObjectAtPosition(long position) const
3127 {
3128 wxRichTextParagraph* para = GetParagraphAtPosition(position);
3129 if (para)
3130 {
3131 wxRichTextObjectList::compatibility_iterator node = para->GetChildren().GetFirst();
3132
3133 while (node)
3134 {
3135 wxRichTextObject* child = node->GetData();
3136 if (child->GetRange().Contains(position))
3137 return child;
3138
3139 node = node->GetNext();
3140 }
3141 if (position == para->GetRange().GetEnd() && para->GetChildCount() > 0)
3142 return para->GetChildren().GetLast()->GetData();
3143 }
3144 return NULL;
3145 }
3146
3147 /// Set character or paragraph text attributes: apply character styles only to immediate text nodes
3148 bool wxRichTextParagraphLayoutBox::SetStyle(const wxRichTextRange& range, const wxRichTextAttr& style, int flags)
3149 {
3150 bool characterStyle = false;
3151 bool paragraphStyle = false;
3152
3153 if (style.IsCharacterStyle())
3154 characterStyle = true;
3155 if (style.IsParagraphStyle())
3156 paragraphStyle = true;
3157
3158 wxRichTextBuffer* buffer = GetBuffer();
3159
3160 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
3161 bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
3162 bool parasOnly = ((flags & wxRICHTEXT_SETSTYLE_PARAGRAPHS_ONLY) != 0);
3163 bool charactersOnly = ((flags & wxRICHTEXT_SETSTYLE_CHARACTERS_ONLY) != 0);
3164 bool resetExistingStyle = ((flags & wxRICHTEXT_SETSTYLE_RESET) != 0);
3165 bool removeStyle = ((flags & wxRICHTEXT_SETSTYLE_REMOVE) != 0);
3166
3167 // Apply paragraph style first, if any
3168 wxRichTextAttr wholeStyle(style);
3169
3170 if (!removeStyle && wholeStyle.HasParagraphStyleName() && buffer->GetStyleSheet())
3171 {
3172 wxRichTextParagraphStyleDefinition* def = buffer->GetStyleSheet()->FindParagraphStyle(wholeStyle.GetParagraphStyleName());
3173 if (def)
3174 wxRichTextApplyStyle(wholeStyle, def->GetStyleMergedWithBase(buffer->GetStyleSheet()));
3175 }
3176
3177 // Limit the attributes to be set to the content to only character attributes.
3178 wxRichTextAttr characterAttributes(wholeStyle);
3179 characterAttributes.SetFlags(characterAttributes.GetFlags() & (wxTEXT_ATTR_CHARACTER));
3180
3181 if (!removeStyle && characterAttributes.HasCharacterStyleName() && buffer->GetStyleSheet())
3182 {
3183 wxRichTextCharacterStyleDefinition* def = buffer->GetStyleSheet()->FindCharacterStyle(characterAttributes.GetCharacterStyleName());
3184 if (def)
3185 wxRichTextApplyStyle(characterAttributes, def->GetStyleMergedWithBase(buffer->GetStyleSheet()));
3186 }
3187
3188 // If we are associated with a control, make undoable; otherwise, apply immediately
3189 // to the data.
3190
3191 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3192
3193 wxRichTextAction* action = NULL;
3194
3195 if (haveControl && withUndo)
3196 {
3197 action = new wxRichTextAction(NULL, _("Change Style"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
3198 action->SetRange(range);
3199 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3200 }
3201
3202 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3203 while (node)
3204 {
3205 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3206 // wxASSERT (para != NULL);
3207
3208 if (para && para->GetChildCount() > 0)
3209 {
3210 // Stop searching if we're beyond the range of interest
3211 if (para->GetRange().GetStart() > range.GetEnd())
3212 break;
3213
3214 if (!para->GetRange().IsOutside(range))
3215 {
3216 // We'll be using a copy of the paragraph to make style changes,
3217 // not updating the buffer directly.
3218 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
3219
3220 if (haveControl && withUndo)
3221 {
3222 newPara = new wxRichTextParagraph(*para);
3223 action->GetNewParagraphs().AppendChild(newPara);
3224
3225 // Also store the old ones for Undo
3226 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
3227 }
3228 else
3229 newPara = para;
3230
3231 // If we're specifying paragraphs only, then we really mean character formatting
3232 // to be included in the paragraph style
3233 if ((paragraphStyle || parasOnly) && !charactersOnly)
3234 {
3235 if (removeStyle)
3236 {
3237 // Removes the given style from the paragraph
3238 wxRichTextRemoveStyle(newPara->GetAttributes(), style);
3239 }
3240 else if (resetExistingStyle)
3241 newPara->GetAttributes() = wholeStyle;
3242 else
3243 {
3244 if (applyMinimal)
3245 {
3246 // Only apply attributes that will make a difference to the combined
3247 // style as seen on the display
3248 wxRichTextAttr combinedAttr(para->GetCombinedAttributes(true));
3249 wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle, & combinedAttr);
3250 }
3251 else
3252 wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle);
3253 }
3254 }
3255
3256 // When applying paragraph styles dynamically, don't change the text objects' attributes
3257 // since they will computed as needed. Only apply the character styling if it's _only_
3258 // character styling. This policy is subject to change and might be put under user control.
3259
3260 // Hm. we might well be applying a mix of paragraph and character styles, in which
3261 // case we _do_ want to apply character styles regardless of what para styles are set.
3262 // But if we're applying a paragraph style, which has some character attributes, but
3263 // we only want the paragraphs to hold this character style, then we _don't_ want to
3264 // apply the character style. So we need to be able to choose.
3265
3266 if (!parasOnly && (characterStyle|charactersOnly) && range.GetStart() != newPara->GetRange().GetEnd())
3267 {
3268 wxRichTextRange childRange(range);
3269 childRange.LimitTo(newPara->GetRange());
3270
3271 // Find the starting position and if necessary split it so
3272 // we can start applying a different style.
3273 // TODO: check that the style actually changes or is different
3274 // from style outside of range
3275 wxRichTextObject* firstObject wxDUMMY_INITIALIZE(NULL);
3276 wxRichTextObject* lastObject wxDUMMY_INITIALIZE(NULL);
3277
3278 if (childRange.GetStart() == newPara->GetRange().GetStart())
3279 firstObject = newPara->GetChildren().GetFirst()->GetData();
3280 else
3281 firstObject = newPara->SplitAt(range.GetStart());
3282
3283 // Increment by 1 because we're apply the style one _after_ the split point
3284 long splitPoint = childRange.GetEnd();
3285 if (splitPoint != newPara->GetRange().GetEnd())
3286 splitPoint ++;
3287
3288 // Find last object
3289 if (splitPoint == newPara->GetRange().GetEnd())
3290 lastObject = newPara->GetChildren().GetLast()->GetData();
3291 else
3292 // lastObject is set as a side-effect of splitting. It's
3293 // returned as the object before the new object.
3294 (void) newPara->SplitAt(splitPoint, & lastObject);
3295
3296 wxASSERT(firstObject != NULL);
3297 wxASSERT(lastObject != NULL);
3298
3299 if (!firstObject || !lastObject)
3300 continue;
3301
3302 wxRichTextObjectList::compatibility_iterator firstNode = newPara->GetChildren().Find(firstObject);
3303 wxRichTextObjectList::compatibility_iterator lastNode = newPara->GetChildren().Find(lastObject);
3304
3305 wxASSERT(firstNode);
3306 wxASSERT(lastNode);
3307
3308 wxRichTextObjectList::compatibility_iterator node2 = firstNode;
3309
3310 while (node2)
3311 {
3312 wxRichTextObject* child = node2->GetData();
3313
3314 if (removeStyle)
3315 {
3316 // Removes the given style from the paragraph
3317 wxRichTextRemoveStyle(child->GetAttributes(), style);
3318 }
3319 else if (resetExistingStyle)
3320 {
3321 // Preserve the URL as it's not really a formatting style but a property of the object
3322 wxString url;
3323 if (child->GetAttributes().HasURL() && !characterAttributes.HasURL())
3324 url = child->GetAttributes().GetURL();
3325
3326 child->GetAttributes() = characterAttributes;
3327
3328 if (!url.IsEmpty())
3329 child->GetAttributes().SetURL(url);
3330 }
3331 else
3332 {
3333 if (applyMinimal)
3334 {
3335 // Only apply attributes that will make a difference to the combined
3336 // style as seen on the display
3337 wxRichTextAttr combinedAttr(newPara->GetCombinedAttributes(child->GetAttributes(), true));
3338 wxRichTextApplyStyle(child->GetAttributes(), characterAttributes, & combinedAttr);
3339 }
3340 else
3341 wxRichTextApplyStyle(child->GetAttributes(), characterAttributes);
3342 }
3343
3344 if (node2 == lastNode)
3345 break;
3346
3347 node2 = node2->GetNext();
3348 }
3349 }
3350 }
3351 }
3352
3353 node = node->GetNext();
3354 }
3355
3356 // Do action, or delay it until end of batch.
3357 if (haveControl && withUndo)
3358 buffer->SubmitAction(action);
3359
3360 return true;
3361 }
3362
3363 // Just change the attributes for this single object.
3364 void wxRichTextParagraphLayoutBox::SetStyle(wxRichTextObject* obj, const wxRichTextAttr& textAttr, int flags)
3365 {
3366 wxRichTextBuffer* buffer = GetBuffer();
3367 bool withUndo = flags & wxRICHTEXT_SETSTYLE_WITH_UNDO;
3368 bool resetExistingStyle = ((flags & wxRICHTEXT_SETSTYLE_RESET) != 0);
3369 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3370
3371 wxRichTextAction *action = NULL;
3372 wxRichTextAttr newAttr = obj->GetAttributes();
3373 if (resetExistingStyle)
3374 newAttr = textAttr;
3375 else
3376 newAttr.Apply(textAttr);
3377
3378 if (haveControl && withUndo)
3379 {
3380 action = new wxRichTextAction(NULL, _("Change Object Style"), wxRICHTEXT_CHANGE_ATTRIBUTES, buffer, obj->GetContainer(), buffer->GetRichTextCtrl());
3381 action->SetRange(obj->GetRange().FromInternal());
3382 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3383 action->MakeObject(obj);
3384
3385 action->GetAttributes() = newAttr;
3386 }
3387 else
3388 obj->GetAttributes() = newAttr;
3389
3390 if (haveControl && withUndo)
3391 buffer->SubmitAction(action);
3392 }
3393
3394 /// Get the text attributes for this position.
3395 bool wxRichTextParagraphLayoutBox::GetStyle(long position, wxRichTextAttr& style)
3396 {
3397 return DoGetStyle(position, style, true);
3398 }
3399
3400 bool wxRichTextParagraphLayoutBox::GetUncombinedStyle(long position, wxRichTextAttr& style)
3401 {
3402 return DoGetStyle(position, style, false);
3403 }
3404
3405 /// Implementation helper for GetStyle. If combineStyles is true, combine base, paragraph and
3406 /// context attributes.
3407 bool wxRichTextParagraphLayoutBox::DoGetStyle(long position, wxRichTextAttr& style, bool combineStyles)
3408 {
3409 wxRichTextObject* obj wxDUMMY_INITIALIZE(NULL);
3410
3411 if (style.IsParagraphStyle())
3412 {
3413 obj = GetParagraphAtPosition(position);
3414 if (obj)
3415 {
3416 if (combineStyles)
3417 {
3418 // Start with the base style
3419 style = GetAttributes();
3420 style.GetTextBoxAttr().Reset();
3421
3422 // Apply the paragraph style
3423 wxRichTextApplyStyle(style, obj->GetAttributes());
3424 }
3425 else
3426 style = obj->GetAttributes();
3427
3428 return true;
3429 }
3430 }
3431 else
3432 {
3433 obj = GetLeafObjectAtPosition(position);
3434 if (obj)
3435 {
3436 if (combineStyles)
3437 {
3438 wxRichTextParagraph* para = wxDynamicCast(obj->GetParent(), wxRichTextParagraph);
3439 style = para ? para->GetCombinedAttributes(obj->GetAttributes()) : obj->GetAttributes();
3440 }
3441 else
3442 style = obj->GetAttributes();
3443
3444 return true;
3445 }
3446 }
3447 return false;
3448 }
3449
3450 static bool wxHasStyle(long flags, long style)
3451 {
3452 return (flags & style) != 0;
3453 }
3454
3455 /// Combines 'style' with 'currentStyle' for the purpose of summarising the attributes of a range of
3456 /// content.
3457 bool wxRichTextParagraphLayoutBox::CollectStyle(wxRichTextAttr& currentStyle, const wxRichTextAttr& style, wxRichTextAttr& clashingAttr, wxRichTextAttr& absentAttr)
3458 {
3459 currentStyle.CollectCommonAttributes(style, clashingAttr, absentAttr);
3460
3461 return true;
3462 }
3463
3464 /// Get the combined style for a range - if any attribute is different within the range,
3465 /// that attribute is not present within the flags.
3466 /// *** Note that this is not recursive, and so assumes that content inside a paragraph is not itself
3467 /// nested.
3468 bool wxRichTextParagraphLayoutBox::GetStyleForRange(const wxRichTextRange& range, wxRichTextAttr& style)
3469 {
3470 style = wxRichTextAttr();
3471
3472 wxRichTextAttr clashingAttrPara, clashingAttrChar;
3473 wxRichTextAttr absentAttrPara, absentAttrChar;
3474
3475 wxRichTextObjectList::compatibility_iterator node = GetChildren().GetFirst();
3476 while (node)
3477 {
3478 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3479 if (para && !(para->GetRange().GetStart() > range.GetEnd() || para->GetRange().GetEnd() < range.GetStart()))
3480 {
3481 if (para->GetChildren().GetCount() == 0)
3482 {
3483 wxRichTextAttr paraStyle = para->GetCombinedAttributes(true /* use box attributes */);
3484
3485 CollectStyle(style, paraStyle, clashingAttrPara, absentAttrPara);
3486 }
3487 else
3488 {
3489 wxRichTextRange paraRange(para->GetRange());
3490 paraRange.LimitTo(range);
3491
3492 // First collect paragraph attributes only
3493 wxRichTextAttr paraStyle = para->GetCombinedAttributes();
3494 paraStyle.SetFlags(paraStyle.GetFlags() & wxTEXT_ATTR_PARAGRAPH);
3495 CollectStyle(style, paraStyle, clashingAttrPara, absentAttrPara);
3496
3497 wxRichTextObjectList::compatibility_iterator childNode = para->GetChildren().GetFirst();
3498
3499 while (childNode)
3500 {
3501 wxRichTextObject* child = childNode->GetData();
3502 if (!(child->GetRange().GetStart() > range.GetEnd() || child->GetRange().GetEnd() < range.GetStart()))
3503 {
3504 wxRichTextAttr childStyle = para->GetCombinedAttributes(child->GetAttributes(), true /* include box attributes */);
3505
3506 // Now collect character attributes only
3507 childStyle.SetFlags(childStyle.GetFlags() & wxTEXT_ATTR_CHARACTER);
3508
3509 CollectStyle(style, childStyle, clashingAttrChar, absentAttrChar);
3510 }
3511
3512 childNode = childNode->GetNext();
3513 }
3514 }
3515 }
3516 node = node->GetNext();
3517 }
3518 return true;
3519 }
3520
3521 /// Set default style
3522 bool wxRichTextParagraphLayoutBox::SetDefaultStyle(const wxRichTextAttr& style)
3523 {
3524 m_defaultAttributes = style;
3525 return true;
3526 }
3527
3528 /// Test if this whole range has character attributes of the specified kind. If any
3529 /// of the attributes are different within the range, the test fails. You
3530 /// can use this to implement, for example, bold button updating. style must have
3531 /// flags indicating which attributes are of interest.
3532 bool wxRichTextParagraphLayoutBox::HasCharacterAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const
3533 {
3534 int foundCount = 0;
3535 int matchingCount = 0;
3536
3537 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3538 while (node)
3539 {
3540 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3541 // wxASSERT (para != NULL);
3542
3543 if (para)
3544 {
3545 // Stop searching if we're beyond the range of interest
3546 if (para->GetRange().GetStart() > range.GetEnd())
3547 return foundCount == matchingCount && foundCount != 0;
3548
3549 if (!para->GetRange().IsOutside(range))
3550 {
3551 wxRichTextObjectList::compatibility_iterator node2 = para->GetChildren().GetFirst();
3552
3553 while (node2)
3554 {
3555 wxRichTextObject* child = node2->GetData();
3556 // Allow for empty string if no buffer
3557 wxRichTextRange childRange = child->GetRange();
3558 if (childRange.GetLength() == 0 && GetRange().GetLength() == 1)
3559 childRange.SetEnd(childRange.GetEnd()+1);
3560
3561 if (!childRange.IsOutside(range) && wxDynamicCast(child, wxRichTextPlainText))
3562 {
3563 foundCount ++;
3564 wxRichTextAttr textAttr = para->GetCombinedAttributes(child->GetAttributes());
3565
3566 if (textAttr.EqPartial(style, false /* strong test - attributes must be valid in both objects */))
3567 matchingCount ++;
3568 }
3569
3570 node2 = node2->GetNext();
3571 }
3572 }
3573 }
3574
3575 node = node->GetNext();
3576 }
3577
3578 return foundCount == matchingCount && foundCount != 0;
3579 }
3580
3581 /// Test if this whole range has paragraph attributes of the specified kind. If any
3582 /// of the attributes are different within the range, the test fails. You
3583 /// can use this to implement, for example, centering button updating. style must have
3584 /// flags indicating which attributes are of interest.
3585 bool wxRichTextParagraphLayoutBox::HasParagraphAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const
3586 {
3587 int foundCount = 0;
3588 int matchingCount = 0;
3589
3590 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3591 while (node)
3592 {
3593 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3594 // wxASSERT (para != NULL);
3595
3596 if (para)
3597 {
3598 // Stop searching if we're beyond the range of interest
3599 if (para->GetRange().GetStart() > range.GetEnd())
3600 return foundCount == matchingCount && foundCount != 0;
3601
3602 if (!para->GetRange().IsOutside(range))
3603 {
3604 wxRichTextAttr textAttr = GetAttributes();
3605 // Apply the paragraph style
3606 wxRichTextApplyStyle(textAttr, para->GetAttributes());
3607
3608 foundCount ++;
3609 if (textAttr.EqPartial(style, false /* strong test */))
3610 matchingCount ++;
3611 }
3612 }
3613
3614 node = node->GetNext();
3615 }
3616 return foundCount == matchingCount && foundCount != 0;
3617 }
3618
3619 void wxRichTextParagraphLayoutBox::PrepareContent(wxRichTextParagraphLayoutBox& container)
3620 {
3621 wxRichTextBuffer* buffer = GetBuffer();
3622 if (buffer && buffer->GetRichTextCtrl())
3623 buffer->GetRichTextCtrl()->PrepareContent(container);
3624 }
3625
3626 /// Set character or paragraph properties
3627 bool wxRichTextParagraphLayoutBox::SetProperties(const wxRichTextRange& range, const wxRichTextProperties& properties, int flags)
3628 {
3629 wxRichTextBuffer* buffer = GetBuffer();
3630
3631 bool withUndo = ((flags & wxRICHTEXT_SETPROPERTIES_WITH_UNDO) != 0);
3632 bool parasOnly = ((flags & wxRICHTEXT_SETPROPERTIES_PARAGRAPHS_ONLY) != 0);
3633 bool charactersOnly = ((flags & wxRICHTEXT_SETPROPERTIES_CHARACTERS_ONLY) != 0);
3634 bool resetExistingProperties = ((flags & wxRICHTEXT_SETPROPERTIES_RESET) != 0);
3635 bool removeProperties = ((flags & wxRICHTEXT_SETPROPERTIES_REMOVE) != 0);
3636
3637 // If we are associated with a control, make undoable; otherwise, apply immediately
3638 // to the data.
3639
3640 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
3641
3642 wxRichTextAction* action = NULL;
3643
3644 if (haveControl && withUndo)
3645 {
3646 action = new wxRichTextAction(NULL, _("Change Properties"), wxRICHTEXT_CHANGE_PROPERTIES, buffer, this, buffer->GetRichTextCtrl());
3647 action->SetRange(range);
3648 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
3649 }
3650
3651 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3652 while (node)
3653 {
3654 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3655 // wxASSERT (para != NULL);
3656
3657 if (para && para->GetChildCount() > 0)
3658 {
3659 // Stop searching if we're beyond the range of interest
3660 if (para->GetRange().GetStart() > range.GetEnd())
3661 break;
3662
3663 if (!para->GetRange().IsOutside(range))
3664 {
3665 // We'll be using a copy of the paragraph to make style changes,
3666 // not updating the buffer directly.
3667 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
3668
3669 if (haveControl && withUndo)
3670 {
3671 newPara = new wxRichTextParagraph(*para);
3672 action->GetNewParagraphs().AppendChild(newPara);
3673
3674 // Also store the old ones for Undo
3675 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
3676 }
3677 else
3678 newPara = para;
3679
3680 if (parasOnly)
3681 {
3682 if (removeProperties)
3683 {
3684 // Removes the given style from the paragraph
3685 // TODO
3686 newPara->GetProperties().RemoveProperties(properties);
3687 }
3688 else if (resetExistingProperties)
3689 newPara->GetProperties() = properties;
3690 else
3691 newPara->GetProperties().MergeProperties(properties);
3692 }
3693
3694 // When applying paragraph styles dynamically, don't change the text objects' attributes
3695 // since they will computed as needed. Only apply the character styling if it's _only_
3696 // character styling. This policy is subject to change and might be put under user control.
3697
3698 // Hm. we might well be applying a mix of paragraph and character styles, in which
3699 // case we _do_ want to apply character styles regardless of what para styles are set.
3700 // But if we're applying a paragraph style, which has some character attributes, but
3701 // we only want the paragraphs to hold this character style, then we _don't_ want to
3702 // apply the character style. So we need to be able to choose.
3703
3704 if (!parasOnly && charactersOnly && range.GetStart() != newPara->GetRange().GetEnd())
3705 {
3706 wxRichTextRange childRange(range);
3707 childRange.LimitTo(newPara->GetRange());
3708
3709 // Find the starting position and if necessary split it so
3710 // we can start applying different properties.
3711 // TODO: check that the properties actually change or are different
3712 // from properties outside of range
3713 wxRichTextObject* firstObject wxDUMMY_INITIALIZE(NULL);
3714 wxRichTextObject* lastObject wxDUMMY_INITIALIZE(NULL);
3715
3716 if (childRange.GetStart() == newPara->GetRange().GetStart())
3717 firstObject = newPara->GetChildren().GetFirst()->GetData();
3718 else
3719 firstObject = newPara->SplitAt(range.GetStart());
3720
3721 // Increment by 1 because we're apply the style one _after_ the split point
3722 long splitPoint = childRange.GetEnd();
3723 if (splitPoint != newPara->GetRange().GetEnd())
3724 splitPoint ++;
3725
3726 // Find last object
3727 if (splitPoint == newPara->GetRange().GetEnd())
3728 lastObject = newPara->GetChildren().GetLast()->GetData();
3729 else
3730 // lastObject is set as a side-effect of splitting. It's
3731 // returned as the object before the new object.
3732 (void) newPara->SplitAt(splitPoint, & lastObject);
3733
3734 wxASSERT(firstObject != NULL);
3735 wxASSERT(lastObject != NULL);
3736
3737 if (!firstObject || !lastObject)
3738 continue;
3739
3740 wxRichTextObjectList::compatibility_iterator firstNode = newPara->GetChildren().Find(firstObject);
3741 wxRichTextObjectList::compatibility_iterator lastNode = newPara->GetChildren().Find(lastObject);
3742
3743 wxASSERT(firstNode);
3744 wxASSERT(lastNode);
3745
3746 wxRichTextObjectList::compatibility_iterator node2 = firstNode;
3747
3748 while (node2)
3749 {
3750 wxRichTextObject* child = node2->GetData();
3751
3752 if (removeProperties)
3753 {
3754 // Removes the given properties from the paragraph
3755 child->GetProperties().RemoveProperties(properties);
3756 }
3757 else if (resetExistingProperties)
3758 child->GetProperties() = properties;
3759 else
3760 {
3761 child->GetProperties().MergeProperties(properties);
3762 }
3763
3764 if (node2 == lastNode)
3765 break;
3766
3767 node2 = node2->GetNext();
3768 }
3769 }
3770 }
3771 }
3772
3773 node = node->GetNext();
3774 }
3775
3776 // Do action, or delay it until end of batch.
3777 if (haveControl && withUndo)
3778 buffer->SubmitAction(action);
3779
3780 return true;
3781 }
3782
3783 void wxRichTextParagraphLayoutBox::Reset()
3784 {
3785 Clear();
3786
3787 wxRichTextBuffer* buffer = GetBuffer();
3788 if (buffer && buffer->GetRichTextCtrl())
3789 {
3790 wxRichTextEvent event(wxEVT_RICHTEXT_BUFFER_RESET, buffer->GetRichTextCtrl()->GetId());
3791 event.SetEventObject(buffer->GetRichTextCtrl());
3792 event.SetContainer(this);
3793
3794 buffer->SendEvent(event, true);
3795 }
3796
3797 AddParagraph(wxEmptyString);
3798
3799 PrepareContent(*this);
3800
3801 InvalidateHierarchy(wxRICHTEXT_ALL);
3802 }
3803
3804 /// Invalidate the buffer. With no argument, invalidates whole buffer.
3805 void wxRichTextParagraphLayoutBox::Invalidate(const wxRichTextRange& invalidRange)
3806 {
3807 wxRichTextCompositeObject::Invalidate(invalidRange);
3808
3809 DoInvalidate(invalidRange);
3810 }
3811
3812 // Do the (in)validation for this object only
3813 void wxRichTextParagraphLayoutBox::DoInvalidate(const wxRichTextRange& invalidRange)
3814 {
3815 if (invalidRange == wxRICHTEXT_ALL)
3816 {
3817 m_invalidRange = wxRICHTEXT_ALL;
3818 }
3819 // Already invalidating everything
3820 else if (m_invalidRange == wxRICHTEXT_ALL)
3821 {
3822 }
3823 else
3824 {
3825 if ((invalidRange.GetStart() < m_invalidRange.GetStart()) || m_invalidRange.GetStart() == -1)
3826 m_invalidRange.SetStart(invalidRange.GetStart());
3827 if (invalidRange.GetEnd() > m_invalidRange.GetEnd())
3828 m_invalidRange.SetEnd(invalidRange.GetEnd());
3829 }
3830 }
3831
3832 // Do the (in)validation both up and down the hierarchy
3833 void wxRichTextParagraphLayoutBox::InvalidateHierarchy(const wxRichTextRange& invalidRange)
3834 {
3835 Invalidate(invalidRange);
3836
3837 if (invalidRange != wxRICHTEXT_NONE)
3838 {
3839 // Now go up the hierarchy
3840 wxRichTextObject* thisObj = this;
3841 wxRichTextObject* p = GetParent();
3842 while (p)
3843 {
3844 wxRichTextParagraphLayoutBox* l = wxDynamicCast(p, wxRichTextParagraphLayoutBox);
3845 if (l)
3846 l->DoInvalidate(thisObj->GetRange());
3847
3848 thisObj = p;
3849 p = p->GetParent();
3850 }
3851 }
3852 }
3853
3854 /// Get invalid range, rounding to entire paragraphs if argument is true.
3855 wxRichTextRange wxRichTextParagraphLayoutBox::GetInvalidRange(bool wholeParagraphs) const
3856 {
3857 if (m_invalidRange == wxRICHTEXT_ALL || m_invalidRange == wxRICHTEXT_NONE)
3858 return m_invalidRange;
3859
3860 wxRichTextRange range = m_invalidRange;
3861
3862 if (wholeParagraphs)
3863 {
3864 wxRichTextParagraph* para1 = GetParagraphAtPosition(range.GetStart());
3865 if (para1)
3866 range.SetStart(para1->GetRange().GetStart());
3867
3868 // FIXME: be more intelligent about this. Check if we have floating objects
3869 // before the end of the range. But it's not clear how we can in general
3870 // tell where it's safe to stop laying out.
3871 // Anyway, this code is central to efficiency when laying in floating mode.
3872 if (!wxRichTextBuffer::GetFloatingLayoutMode())
3873 {
3874 wxRichTextParagraph* para2 = GetParagraphAtPosition(range.GetEnd());
3875 if (para2)
3876 range.SetEnd(para2->GetRange().GetEnd());
3877 }
3878 else
3879 // Floating layout means that all children should be laid out,
3880 // because we can't tell how the whole buffer will be affected.
3881 range.SetEnd(GetOwnRange().GetEnd());
3882 }
3883 return range;
3884 }
3885
3886 /// Apply the style sheet to the buffer, for example if the styles have changed.
3887 bool wxRichTextParagraphLayoutBox::ApplyStyleSheet(wxRichTextStyleSheet* styleSheet)
3888 {
3889 wxASSERT(styleSheet != NULL);
3890 if (!styleSheet)
3891 return false;
3892
3893 int foundCount = 0;
3894
3895 wxRichTextAttr attr(GetBasicStyle());
3896 if (GetBasicStyle().HasParagraphStyleName())
3897 {
3898 wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(GetBasicStyle().GetParagraphStyleName());
3899 if (paraDef)
3900 {
3901 attr.Apply(paraDef->GetStyleMergedWithBase(styleSheet));
3902 SetBasicStyle(attr);
3903 foundCount ++;
3904 }
3905 }
3906
3907 if (GetBasicStyle().HasCharacterStyleName())
3908 {
3909 wxRichTextCharacterStyleDefinition* charDef = styleSheet->FindCharacterStyle(GetBasicStyle().GetCharacterStyleName());
3910 if (charDef)
3911 {
3912 attr.Apply(charDef->GetStyleMergedWithBase(styleSheet));
3913 SetBasicStyle(attr);
3914 foundCount ++;
3915 }
3916 }
3917
3918 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3919 while (node)
3920 {
3921 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
3922 // wxASSERT (para != NULL);
3923
3924 if (para)
3925 {
3926 // Combine paragraph and list styles. If there is a list style in the original attributes,
3927 // the current indentation overrides anything else and is used to find the item indentation.
3928 // Also, for applying paragraph styles, consider having 2 modes: (1) we merge with what we have,
3929 // thereby taking into account all user changes, (2) reset the style completely (except for indentation/list
3930 // exception as above).
3931 // Problem: when changing from one list style to another, there's a danger that the level info will get lost.
3932 // So when changing a list style interactively, could retrieve level based on current style, then
3933 // set appropriate indent and apply new style.
3934
3935 int outline = -1;
3936 int num = -1;
3937 if (para->GetAttributes().HasOutlineLevel())
3938 outline = para->GetAttributes().GetOutlineLevel();
3939 if (para->GetAttributes().HasBulletNumber())
3940 num = para->GetAttributes().GetBulletNumber();
3941
3942 if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
3943 {
3944 int currentIndent = para->GetAttributes().GetLeftIndent();
3945
3946 wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
3947 wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
3948 if (paraDef && !listDef)
3949 {
3950 para->GetAttributes() = paraDef->GetStyleMergedWithBase(styleSheet);
3951 foundCount ++;
3952 }
3953 else if (listDef && !paraDef)
3954 {
3955 // Set overall style defined for the list style definition
3956 para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet);
3957
3958 // Apply the style for this level
3959 wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
3960 foundCount ++;
3961 }
3962 else if (listDef && paraDef)
3963 {
3964 // Combines overall list style, style for level, and paragraph style
3965 para->GetAttributes() = listDef->CombineWithParagraphStyle(currentIndent, paraDef->GetStyleMergedWithBase(styleSheet));
3966 foundCount ++;
3967 }
3968 }
3969 else if (para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
3970 {
3971 int currentIndent = para->GetAttributes().GetLeftIndent();
3972
3973 wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
3974
3975 // Overall list definition style
3976 para->GetAttributes() = listDef->GetStyleMergedWithBase(styleSheet);
3977
3978 // Style for this level
3979 wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
3980
3981 foundCount ++;
3982 }
3983 else if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && para->GetAttributes().GetListStyleName().IsEmpty())
3984 {
3985 wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
3986 if (def)
3987 {
3988 para->GetAttributes() = def->GetStyleMergedWithBase(styleSheet);
3989 foundCount ++;
3990 }
3991 }
3992
3993 if (outline != -1)
3994 para->GetAttributes().SetOutlineLevel(outline);
3995 if (num != -1)
3996 para->GetAttributes().SetBulletNumber(num);
3997 }
3998
3999 node = node->GetNext();
4000 }
4001 return foundCount != 0;
4002 }
4003
4004 /// Set list style
4005 bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
4006 {
4007 wxRichTextBuffer* buffer = GetBuffer();
4008 wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
4009
4010 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
4011 // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
4012 bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
4013 bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
4014
4015 // Current number, if numbering
4016 int n = startFrom;
4017
4018 wxASSERT (!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
4019
4020 // If we are associated with a control, make undoable; otherwise, apply immediately
4021 // to the data.
4022
4023 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
4024
4025 wxRichTextAction* action = NULL;
4026
4027 if (haveControl && withUndo)
4028 {
4029 action = new wxRichTextAction(NULL, _("Change List Style"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
4030 action->SetRange(range);
4031 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
4032 }
4033
4034 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
4035 while (node)
4036 {
4037 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
4038 // wxASSERT (para != NULL);
4039
4040 if (para && para->GetChildCount() > 0)
4041 {
4042 // Stop searching if we're beyond the range of interest
4043 if (para->GetRange().GetStart() > range.GetEnd())
4044 break;
4045
4046 if (!para->GetRange().IsOutside(range))
4047 {
4048 // We'll be using a copy of the paragraph to make style changes,
4049 // not updating the buffer directly.
4050 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
4051
4052 if (haveControl && withUndo)
4053 {
4054 newPara = new wxRichTextParagraph(*para);
4055 action->GetNewParagraphs().AppendChild(newPara);
4056
4057 // Also store the old ones for Undo
4058 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
4059 }
4060 else
4061 newPara = para;
4062
4063 if (def)
4064 {
4065 int thisIndent = newPara->GetAttributes().GetLeftIndent();
4066 int thisLevel = specifyLevel ? specifiedLevel : def->FindLevelForIndent(thisIndent);
4067
4068 // How is numbering going to work?
4069 // If we are renumbering, or numbering for the first time, we need to keep
4070 // track of the number for each level. But we might be simply applying a different
4071 // list style.
4072 // In Word, applying a style to several paragraphs, even if at different levels,
4073 // reverts the level back to the same one. So we could do the same here.
4074 // Renumbering will need to be done when we promote/demote a paragraph.
4075
4076 // Apply the overall list style, and item style for this level
4077 wxRichTextAttr listStyle(def->GetCombinedStyleForLevel(thisLevel, styleSheet));
4078 wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
4079
4080 // Now we need to do numbering
4081 // Preserve the existing list item continuation bullet style, if any
4082 if (para->GetAttributes().HasBulletStyle() && (para->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION))
4083 newPara->GetAttributes().SetBulletStyle(newPara->GetAttributes().GetBulletStyle()|wxTEXT_ATTR_BULLET_STYLE_CONTINUATION);
4084 else
4085 {
4086 if (renumber)
4087 {
4088 newPara->GetAttributes().SetBulletNumber(n);
4089 }
4090
4091 n ++;
4092 }
4093 }
4094 else if (!newPara->GetAttributes().GetListStyleName().IsEmpty())
4095 {
4096 // if def is NULL, remove list style, applying any associated paragraph style
4097 // to restore the attributes
4098
4099 newPara->GetAttributes().SetListStyleName(wxEmptyString);
4100 newPara->GetAttributes().SetLeftIndent(0, 0);
4101 newPara->GetAttributes().SetBulletText(wxEmptyString);
4102 newPara->GetAttributes().SetBulletStyle(0);
4103
4104 // Eliminate the main list-related attributes
4105 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);
4106
4107 if (styleSheet && !newPara->GetAttributes().GetParagraphStyleName().IsEmpty())
4108 {
4109 wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(newPara->GetAttributes().GetParagraphStyleName());
4110 if (def)
4111 {
4112 newPara->GetAttributes() = def->GetStyleMergedWithBase(styleSheet);
4113 }
4114 }
4115 }
4116 }
4117 }
4118
4119 node = node->GetNext();
4120 }
4121
4122 // Do action, or delay it until end of batch.
4123 if (haveControl && withUndo)
4124 buffer->SubmitAction(action);
4125
4126 return true;
4127 }
4128
4129 bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
4130 {
4131 wxRichTextBuffer* buffer = GetBuffer();
4132 if (buffer && buffer->GetStyleSheet())
4133 {
4134 wxRichTextListStyleDefinition* def = buffer->GetStyleSheet()->FindListStyle(defName);
4135 if (def)
4136 return SetListStyle(range, def, flags, startFrom, specifiedLevel);
4137 }
4138 return false;
4139 }
4140
4141 /// Clear list for given range
4142 bool wxRichTextParagraphLayoutBox::ClearListStyle(const wxRichTextRange& range, int flags)
4143 {
4144 return SetListStyle(range, NULL, flags);
4145 }
4146
4147 /// Number/renumber any list elements in the given range
4148 bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
4149 {
4150 return DoNumberList(range, range, 0, def, flags, startFrom, specifiedLevel);
4151 }
4152
4153 /// Number/renumber any list elements in the given range. Also do promotion or demotion of items, if specified
4154 bool wxRichTextParagraphLayoutBox::DoNumberList(const wxRichTextRange& range, const wxRichTextRange& promotionRange, int promoteBy,
4155 wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
4156 {
4157 wxRichTextBuffer* buffer = GetBuffer();
4158 wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
4159
4160 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
4161 // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
4162 #if wxDEBUG_LEVEL
4163 bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
4164 #endif
4165
4166 bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
4167
4168 // Max number of levels
4169 const int maxLevels = 10;
4170
4171 // The level we're looking at now
4172 int currentLevel = -1;
4173
4174 // The item number for each level
4175 int levels[maxLevels];
4176 int i;
4177
4178 // Reset all numbering
4179 for (i = 0; i < maxLevels; i++)
4180 {
4181 if (startFrom != -1)
4182 levels[i] = startFrom-1;
4183 else if (renumber) // start again
4184 levels[i] = 0;
4185 else
4186 levels[i] = -1; // start from the number we found, if any
4187 }
4188
4189 #if wxDEBUG_LEVEL
4190 wxASSERT(!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
4191 #endif
4192
4193 // If we are associated with a control, make undoable; otherwise, apply immediately
4194 // to the data.
4195
4196 bool haveControl = (buffer->GetRichTextCtrl() != NULL);
4197
4198 wxRichTextAction* action = NULL;
4199
4200 if (haveControl && withUndo)
4201 {
4202 action = new wxRichTextAction(NULL, _("Renumber List"), wxRICHTEXT_CHANGE_STYLE, buffer, this, buffer->GetRichTextCtrl());
4203 action->SetRange(range);
4204 action->SetPosition(buffer->GetRichTextCtrl()->GetCaretPosition());
4205 }
4206
4207 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
4208 while (node)
4209 {
4210 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
4211 // wxASSERT (para != NULL);
4212
4213 if (para && para->GetChildCount() > 0)
4214 {
4215 // Stop searching if we're beyond the range of interest
4216 if (para->GetRange().GetStart() > range.GetEnd())
4217 break;
4218
4219 if (!para->GetRange().IsOutside(range))
4220 {
4221 // We'll be using a copy of the paragraph to make style changes,
4222 // not updating the buffer directly.
4223 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
4224
4225 if (haveControl && withUndo)
4226 {
4227 newPara = new wxRichTextParagraph(*para);
4228 action->GetNewParagraphs().AppendChild(newPara);
4229
4230 // Also store the old ones for Undo
4231 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
4232 }
4233 else
4234 newPara = para;
4235
4236 wxRichTextListStyleDefinition* defToUse = def;
4237 if (!defToUse)
4238 {
4239 if (styleSheet && !newPara->GetAttributes().GetListStyleName().IsEmpty())
4240 defToUse = styleSheet->FindListStyle(newPara->GetAttributes().GetListStyleName());
4241 }
4242
4243 if (defToUse)
4244 {
4245 int thisIndent = newPara->GetAttributes().GetLeftIndent();
4246 int thisLevel = defToUse->FindLevelForIndent(thisIndent);
4247
4248 // If we've specified a level to apply to all, change the level.
4249 if (specifiedLevel != -1)
4250 thisLevel = specifiedLevel;
4251
4252 // Do promotion if specified
4253 if ((promoteBy != 0) && !para->GetRange().IsOutside(promotionRange))
4254 {
4255 thisLevel = thisLevel - promoteBy;
4256 if (thisLevel < 0)
4257 thisLevel = 0;
4258 if (thisLevel > 9)
4259 thisLevel = 9;
4260 }
4261
4262 // Apply the overall list style, and item style for this level
4263 wxRichTextAttr listStyle(defToUse->GetCombinedStyleForLevel(thisLevel, styleSheet));
4264 wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
4265
4266 // Preserve the existing list item continuation bullet style, if any
4267 if (para->GetAttributes().HasBulletStyle() && (para->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION))
4268 newPara->GetAttributes().SetBulletStyle(newPara->GetAttributes().GetBulletStyle()|wxTEXT_ATTR_BULLET_STYLE_CONTINUATION);
4269
4270 // OK, we've (re)applied the style, now let's get the numbering right.
4271
4272 if (currentLevel == -1)
4273 currentLevel = thisLevel;
4274
4275 // Same level as before, do nothing except increment level's number afterwards
4276 if (currentLevel == thisLevel)
4277 {
4278 }
4279 // A deeper level: start renumbering all levels after current level
4280 else if (thisLevel > currentLevel)
4281 {
4282 for (i = currentLevel+1; i <= thisLevel; i++)
4283 {
4284 levels[i] = 0;
4285 }
4286 currentLevel = thisLevel;
4287 }
4288 else if (thisLevel < currentLevel)
4289 {
4290 currentLevel = thisLevel;
4291 }
4292
4293 // Use the current numbering if -1 and we have a bullet number already
4294 if (levels[currentLevel] == -1)
4295 {
4296 if (newPara->GetAttributes().HasBulletNumber())
4297 levels[currentLevel] = newPara->GetAttributes().GetBulletNumber();
4298 else
4299 levels[currentLevel] = 1;
4300 }
4301 else
4302 {
4303 if (!(para->GetAttributes().HasBulletStyle() && (para->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION)))
4304 levels[currentLevel] ++;
4305 }
4306
4307 newPara->GetAttributes().SetBulletNumber(levels[currentLevel]);
4308
4309 // Create the bullet text if an outline list
4310 if (listStyle.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
4311 {
4312 wxString text;
4313 for (i = 0; i <= currentLevel; i++)
4314 {
4315 if (!text.IsEmpty())
4316 text += wxT(".");
4317 text += wxString::Format(wxT("%d"), levels[i]);
4318 }
4319 newPara->GetAttributes().SetBulletText(text);
4320 }
4321 }
4322 }
4323 }
4324
4325 node = node->GetNext();
4326 }
4327
4328 // Do action, or delay it until end of batch.
4329 if (haveControl && withUndo)
4330 buffer->SubmitAction(action);
4331
4332 return true;
4333 }
4334
4335 bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
4336 {
4337 wxRichTextBuffer* buffer = GetBuffer();
4338 if (buffer->GetStyleSheet())
4339 {
4340 wxRichTextListStyleDefinition* def = NULL;
4341 if (!defName.IsEmpty())
4342 def = buffer->GetStyleSheet()->FindListStyle(defName);
4343 return NumberList(range, def, flags, startFrom, specifiedLevel);
4344 }
4345 return false;
4346 }
4347
4348 /// Promote the list items within the given range. promoteBy can be a positive or negative number, e.g. 1 or -1
4349 bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int specifiedLevel)
4350 {
4351 // TODO
4352 // One strategy is to first work out the range within which renumbering must occur. Then could pass these two ranges
4353 // to NumberList with a flag indicating promotion is required within one of the ranges.
4354 // Find first and last paragraphs in range. Then for first, calculate new indentation and look back until we find
4355 // a paragraph that either has no list style, or has one that is different or whose indentation is less.
4356 // We start renumbering from the para after that different para we found. We specify that the numbering of that
4357 // list position will start from 1.
4358 // Similarly, we look after the last para in the promote range for an indentation that is less (or no list style).
4359 // We can end the renumbering at this point.
4360
4361 // For now, only renumber within the promotion range.
4362
4363 return DoNumberList(range, range, promoteBy, def, flags, 1, specifiedLevel);
4364 }
4365
4366 bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, const wxString& defName, int flags, int specifiedLevel)
4367 {
4368 wxRichTextBuffer* buffer = GetBuffer();
4369 if (buffer->GetStyleSheet())
4370 {
4371 wxRichTextListStyleDefinition* def = NULL;
4372 if (!defName.IsEmpty())
4373 def = buffer->GetStyleSheet()->FindListStyle(defName);
4374 return PromoteList(promoteBy, range, def, flags, specifiedLevel);
4375 }
4376 return false;
4377 }
4378
4379 /// Fills in the attributes for numbering a paragraph after previousParagraph. It also finds the
4380 /// position of the paragraph that it had to start looking from.
4381 bool wxRichTextParagraphLayoutBox::FindNextParagraphNumber(wxRichTextParagraph* previousParagraph, wxRichTextAttr& attr) const
4382 {
4383 // TODO: add GetNextChild/GetPreviousChild to composite
4384 // Search for a paragraph that isn't a continuation paragraph (no bullet)
4385 while (previousParagraph && previousParagraph->GetAttributes().HasBulletStyle() && previousParagraph->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION)
4386 {
4387 wxRichTextObjectList::compatibility_iterator node = ((wxRichTextCompositeObject*) previousParagraph->GetParent())->GetChildren().Find(previousParagraph);
4388 if (node)
4389 {
4390 node = node->GetPrevious();
4391 if (node)
4392 previousParagraph = wxDynamicCast(node->GetData(), wxRichTextParagraph);
4393 else
4394 previousParagraph = NULL;
4395 }
4396 else
4397 previousParagraph = NULL;
4398 }
4399
4400 if (!previousParagraph || !previousParagraph->GetAttributes().HasFlag(wxTEXT_ATTR_BULLET_STYLE) || previousParagraph->GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE)
4401 return false;
4402
4403 wxRichTextBuffer* buffer = GetBuffer();
4404 wxRichTextStyleSheet* styleSheet = buffer->GetStyleSheet();
4405 if (styleSheet && !previousParagraph->GetAttributes().GetListStyleName().IsEmpty())
4406 {
4407 wxRichTextListStyleDefinition* def = styleSheet->FindListStyle(previousParagraph->GetAttributes().GetListStyleName());
4408 if (def)
4409 {
4410 // int thisIndent = previousParagraph->GetAttributes().GetLeftIndent();
4411 // int thisLevel = def->FindLevelForIndent(thisIndent);
4412
4413 bool isOutline = (previousParagraph->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE) != 0;
4414
4415 attr.SetFlags(previousParagraph->GetAttributes().GetFlags() & (wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_BULLET_NUMBER|wxTEXT_ATTR_BULLET_TEXT|wxTEXT_ATTR_BULLET_NAME));
4416 if (previousParagraph->GetAttributes().HasBulletName())
4417 attr.SetBulletName(previousParagraph->GetAttributes().GetBulletName());
4418 attr.SetBulletStyle(previousParagraph->GetAttributes().GetBulletStyle());
4419 attr.SetListStyleName(previousParagraph->GetAttributes().GetListStyleName());
4420
4421 int nextNumber = previousParagraph->GetAttributes().GetBulletNumber() + 1;
4422 attr.SetBulletNumber(nextNumber);
4423
4424 if (isOutline)
4425 {
4426 wxString text = previousParagraph->GetAttributes().GetBulletText();
4427 if (!text.IsEmpty())
4428 {
4429 int pos = text.Find(wxT('.'), true);
4430 if (pos != wxNOT_FOUND)
4431 {
4432 text = text.Mid(0, text.Length() - pos - 1);
4433 }
4434 else
4435 text = wxEmptyString;
4436 if (!text.IsEmpty())
4437 text += wxT(".");
4438 text += wxString::Format(wxT("%d"), nextNumber);
4439 attr.SetBulletText(text);
4440 }
4441 }
4442
4443 return true;
4444 }
4445 else
4446 return false;
4447 }
4448 else
4449 return false;
4450 }
4451
4452 /*!
4453 * wxRichTextParagraph
4454 * This object represents a single paragraph (or in a straight text editor, a line).
4455 */
4456
4457 IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraph, wxRichTextCompositeObject)
4458
4459 wxArrayInt wxRichTextParagraph::sm_defaultTabs;
4460
4461 wxRichTextParagraph::wxRichTextParagraph(wxRichTextObject* parent, wxRichTextAttr* style):
4462 wxRichTextCompositeObject(parent)
4463 {
4464 if (style)
4465 SetAttributes(*style);
4466 }
4467
4468 wxRichTextParagraph::wxRichTextParagraph(const wxString& text, wxRichTextObject* parent, wxRichTextAttr* paraStyle, wxRichTextAttr* charStyle):
4469 wxRichTextCompositeObject(parent)
4470 {
4471 if (paraStyle)
4472 SetAttributes(*paraStyle);
4473
4474 AppendChild(new wxRichTextPlainText(text, this, charStyle));
4475 }
4476
4477 wxRichTextParagraph::~wxRichTextParagraph()
4478 {
4479 ClearLines();
4480 }
4481
4482 /// Draw the item
4483 bool wxRichTextParagraph::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int WXUNUSED(descent), int style)
4484 {
4485 if (!IsShown())
4486 return true;
4487
4488 // Currently we don't merge these attributes with the parent, but we
4489 // should consider whether we should (e.g. if we set a border colour
4490 // for all paragraphs). But generally box attributes are likely to be
4491 // different for different objects.
4492 wxRect paraRect = GetRect();
4493 wxRichTextAttr attr = GetCombinedAttributes();
4494 context.ApplyVirtualAttributes(attr, this);
4495
4496 DrawBoxAttributes(dc, GetBuffer(), attr, paraRect);
4497
4498 // Draw the bullet, if any
4499 if ((attr.GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE) == 0 && (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_CONTINUATION) == 0)
4500 {
4501 if (attr.GetLeftSubIndent() != 0)
4502 {
4503 int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
4504 int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
4505
4506 wxRichTextAttr bulletAttr(attr);
4507
4508 // Combine with the font of the first piece of content, if one is specified
4509 if (GetChildren().GetCount() > 0)
4510 {
4511 wxRichTextObject* firstObj = (wxRichTextObject*) GetChildren().GetFirst()->GetData();
4512 if (!firstObj->IsFloatable() && firstObj->GetAttributes().HasFont())
4513 {
4514 wxRichTextApplyStyle(bulletAttr, firstObj->GetAttributes());
4515 }
4516 }
4517
4518 // Get line height from first line, if any
4519 wxRichTextLine* line = m_cachedLines.GetFirst() ? (wxRichTextLine* ) m_cachedLines.GetFirst()->GetData() : NULL;
4520
4521 wxPoint linePos;
4522 int lineHeight wxDUMMY_INITIALIZE(0);
4523 if (line)
4524 {
4525 lineHeight = line->GetSize().y;
4526 linePos = line->GetPosition() + GetPosition();
4527 }
4528 else
4529 {
4530 wxFont font;
4531 if (bulletAttr.HasFont() && GetBuffer())
4532 font = GetBuffer()->GetFontTable().FindFont(bulletAttr);
4533 else
4534 font = (*wxNORMAL_FONT);
4535
4536 wxCheckSetFont(dc, font);
4537
4538 lineHeight = dc.GetCharHeight();
4539 linePos = GetPosition();
4540 linePos.y += spaceBeforePara;
4541 }
4542
4543 wxRect bulletRect(GetPosition().x + leftIndent, linePos.y, linePos.x - (GetPosition().x + leftIndent), lineHeight);
4544
4545 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP)
4546 {
4547 if (wxRichTextBuffer::GetRenderer())
4548 wxRichTextBuffer::GetRenderer()->DrawBitmapBullet(this, dc, bulletAttr, bulletRect);
4549 }
4550 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_STANDARD)
4551 {
4552 if (wxRichTextBuffer::GetRenderer())
4553 wxRichTextBuffer::GetRenderer()->DrawStandardBullet(this, dc, bulletAttr, bulletRect);
4554 }
4555 else
4556 {
4557 wxString bulletText = GetBulletText();
4558
4559 if (!bulletText.empty() && wxRichTextBuffer::GetRenderer())
4560 wxRichTextBuffer::GetRenderer()->DrawTextBullet(this, dc, bulletAttr, bulletRect, bulletText);
4561 }
4562 }
4563 }
4564
4565 // Draw the range for each line, one object at a time.
4566
4567 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
4568 while (node)
4569 {
4570 wxRichTextLine* line = node->GetData();
4571 wxRichTextRange lineRange = line->GetAbsoluteRange();
4572
4573 // Lines are specified relative to the paragraph
4574
4575 wxPoint linePosition = line->GetPosition() + GetPosition();
4576
4577 // Don't draw if off the screen
4578 if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) != 0) || ((linePosition.y + line->GetSize().y) >= rect.y && linePosition.y <= rect.y + rect.height))
4579 {
4580 wxPoint objectPosition = linePosition;
4581 int maxDescent = line->GetDescent();
4582
4583 // Loop through objects until we get to the one within range
4584 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
4585
4586 int i = 0;
4587 while (node2)
4588 {
4589 wxRichTextObject* child = node2->GetData();
4590
4591 if ((!child->IsFloating() || !wxRichTextBuffer::GetFloatingLayoutMode()) && child->GetRange().GetLength() > 0 && !child->GetRange().IsOutside(lineRange) && !lineRange.IsOutside(range))
4592 {
4593 // Draw this part of the line at the correct position
4594 wxRichTextRange objectRange(child->GetRange());
4595 objectRange.LimitTo(lineRange);
4596
4597 wxSize objectSize;
4598 if (child->IsTopLevel())
4599 {
4600 objectSize = child->GetCachedSize();
4601 objectRange = child->GetOwnRange();
4602 }
4603 else
4604 {
4605 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING && wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4606 if (i < (int) line->GetObjectSizes().GetCount())
4607 {
4608 objectSize.x = line->GetObjectSizes()[(size_t) i];
4609 }
4610 else
4611 #endif
4612 {
4613 int descent = 0;
4614 child->GetRangeSize(objectRange, objectSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, objectPosition);
4615 }
4616 }
4617
4618 // Use the child object's width, but the whole line's height
4619 wxRect childRect(objectPosition, wxSize(objectSize.x, line->GetSize().y));
4620 child->Draw(dc, context, objectRange, selection, childRect, maxDescent, style);
4621
4622 objectPosition.x += objectSize.x;
4623 i ++;
4624 }
4625 else if (child->GetRange().GetStart() > lineRange.GetEnd())
4626 // Can break out of inner loop now since we've passed this line's range
4627 break;
4628
4629 node2 = node2->GetNext();
4630 }
4631 }
4632
4633 node = node->GetNext();
4634 }
4635
4636 return true;
4637 }
4638
4639 // Get the range width using partial extents calculated for the whole paragraph.
4640 static int wxRichTextGetRangeWidth(const wxRichTextParagraph& para, const wxRichTextRange& range, const wxArrayInt& partialExtents)
4641 {
4642 wxASSERT(partialExtents.GetCount() >= (size_t) range.GetLength());
4643
4644 if (partialExtents.GetCount() < (size_t) range.GetLength())
4645 return 0;
4646
4647 int leftMostPos = 0;
4648 if (range.GetStart() - para.GetRange().GetStart() > 0)
4649 leftMostPos = partialExtents[range.GetStart() - para.GetRange().GetStart() - 1];
4650
4651 int rightMostPos = partialExtents[range.GetEnd() - para.GetRange().GetStart()];
4652
4653 int w = rightMostPos - leftMostPos;
4654
4655 return w;
4656 }
4657
4658 /// Lay the item out
4659 bool wxRichTextParagraph::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style)
4660 {
4661 // Deal with floating objects firstly before the normal layout
4662 wxRichTextBuffer* buffer = GetBuffer();
4663 wxASSERT(buffer);
4664
4665 wxRichTextFloatCollector* collector = GetContainer()->GetFloatCollector();
4666
4667 if (wxRichTextBuffer::GetFloatingLayoutMode())
4668 {
4669 wxASSERT(collector != NULL);
4670 if (collector)
4671 LayoutFloat(dc, context, rect, parentRect, style, collector);
4672 }
4673
4674 wxRichTextAttr attr = GetCombinedAttributes();
4675 context.ApplyVirtualAttributes(attr, this);
4676
4677 // ClearLines();
4678
4679 // Increase the size of the paragraph due to spacing
4680 int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
4681 int spaceAfterPara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingAfter());
4682 int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
4683 int leftSubIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftSubIndent());
4684 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
4685
4686 int lineSpacing = 0;
4687
4688 // Let's assume line spacing of 10 is normal, 15 is 1.5, 20 is 2, etc.
4689 if (attr.HasLineSpacing() && attr.GetLineSpacing() > 0 && attr.HasFont())
4690 {
4691 wxFont font(buffer->GetFontTable().FindFont(attr));
4692 if (font.IsOk())
4693 {
4694 wxCheckSetFont(dc, font);
4695 lineSpacing = (int) (double(dc.GetCharHeight()) * (double(attr.GetLineSpacing())/10.0 - 1.0));
4696 }
4697 }
4698
4699 // Start position for each line relative to the paragraph
4700 int startPositionFirstLine = leftIndent;
4701 int startPositionSubsequentLines = leftIndent + leftSubIndent;
4702
4703 // If we have a bullet in this paragraph, the start position for the first line's text
4704 // is actually leftIndent + leftSubIndent.
4705 if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
4706 startPositionFirstLine = startPositionSubsequentLines;
4707
4708 long lastEndPos = GetRange().GetStart()-1;
4709 long lastCompletedEndPos = lastEndPos;
4710
4711 int currentWidth = 0;
4712 SetPosition(rect.GetPosition());
4713
4714 wxPoint currentPosition(0, spaceBeforePara); // We will calculate lines relative to paragraph
4715 int lineHeight = 0;
4716 int maxWidth = 0;
4717 int maxHeight = currentPosition.y;
4718 int maxAscent = 0;
4719 int maxDescent = 0;
4720 int lineCount = 0;
4721 int lineAscent = 0;
4722 int lineDescent = 0;
4723
4724 wxRichTextObjectList::compatibility_iterator node;
4725
4726 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4727 wxUnusedVar(style);
4728 wxArrayInt partialExtents;
4729
4730 wxSize paraSize;
4731 int paraDescent = 0;
4732
4733 // This calculates the partial text extents
4734 GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, rect.GetPosition(), parentRect.GetSize(), & partialExtents);
4735 #else
4736 node = m_children.GetFirst();
4737 while (node)
4738 {
4739 wxRichTextObject* child = node->GetData();
4740
4741 //child->SetCachedSize(wxDefaultSize);
4742 child->Layout(dc, context, rect, style);
4743
4744 node = node->GetNext();
4745 }
4746 #endif
4747
4748 // Split up lines
4749
4750 // We may need to go back to a previous child, in which case create the new line,
4751 // find the child corresponding to the start position of the string, and
4752 // continue.
4753
4754 wxRect availableRect;
4755
4756 node = m_children.GetFirst();
4757 while (node)
4758 {
4759 wxRichTextObject* child = node->GetData();
4760
4761 // If floating, ignore. We already laid out floats.
4762 // Also ignore if empty object, except if we haven't got any
4763 // size yet.
4764 if ((child->IsFloating() && wxRichTextBuffer::GetFloatingLayoutMode())
4765 || !child->IsShown() || (child->GetRange().GetLength() == 0 && maxHeight > spaceBeforePara)
4766 )
4767 {
4768 node = node->GetNext();
4769 continue;
4770 }
4771
4772 // If this is e.g. a composite text box, it will need to be laid out itself.
4773 // But if just a text fragment or image, for example, this will
4774 // do nothing. NB: won't we need to set the position after layout?
4775 // since for example if position is dependent on vertical line size, we
4776 // can't tell the position until the size is determined. So possibly introduce
4777 // another layout phase.
4778
4779 // We may only be looking at part of a child, if we searched back for wrapping
4780 // and found a suitable point some way into the child. So get the size for the fragment
4781 // if necessary.
4782
4783 long nextBreakPos = GetFirstLineBreakPosition(lastEndPos+1);
4784 long lastPosToUse = child->GetRange().GetEnd();
4785 bool lineBreakInThisObject = (nextBreakPos > -1 && nextBreakPos <= child->GetRange().GetEnd());
4786
4787 if (lineBreakInThisObject)
4788 lastPosToUse = nextBreakPos;
4789
4790 wxSize childSize;
4791 int childDescent = 0;
4792
4793 int startOffset = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
4794 availableRect = wxRect(rect.x + startOffset, rect.y + currentPosition.y,
4795 rect.width - startOffset - rightIndent, rect.height);
4796
4797 if (child->IsTopLevel())
4798 {
4799 wxSize oldSize = child->GetCachedSize();
4800
4801 child->Invalidate(wxRICHTEXT_ALL);
4802 child->SetPosition(wxPoint(0, 0));
4803
4804 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
4805 // lays out the object again using the minimum size
4806 // The position will be determined by its location in its line,
4807 // and not by the child's actual position.
4808 child->LayoutToBestSize(dc, context, buffer,
4809 attr, child->GetAttributes(), availableRect, parentRect, style);
4810
4811 if (oldSize != child->GetCachedSize())
4812 {
4813 partialExtents.Clear();
4814
4815 // Recalculate the partial text extents since the child object changed size
4816 GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, wxPoint(0,0), parentRect.GetSize(), & partialExtents);
4817 }
4818 }
4819
4820 // Problem: we need to layout composites here for which we need the available width,
4821 // but we can't get the available width without using the float collector which
4822 // needs to know the object height.
4823
4824 if ((nextBreakPos == -1) && (lastEndPos == child->GetRange().GetStart() - 1)) // i.e. we want to get the whole thing
4825 {
4826 childSize = child->GetCachedSize();
4827 childDescent = child->GetDescent();
4828 }
4829 else
4830 {
4831 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4832 // Get height only, then the width using the partial extents
4833 GetRangeSize(wxRichTextRange(lastEndPos+1, lastPosToUse), childSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_HEIGHT_ONLY, wxPoint(0,0), parentRect.GetSize());
4834 childSize.x = wxRichTextGetRangeWidth(*this, wxRichTextRange(lastEndPos+1, lastPosToUse), partialExtents);
4835 #else
4836 GetRangeSize(wxRichTextRange(lastEndPos+1, lastPosToUse), childSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED, rect.GetPosition(), parentRect.GetSize());
4837 #endif
4838 }
4839
4840 bool doLoop = true;
4841 int loopIterations = 0;
4842
4843 // If there are nested objects that need to lay themselves out, we have to do this in a
4844 // loop because the height of the object may well depend on the available width.
4845 // And because of floating object positioning, the available width depends on the
4846 // height of the object and whether it will clash with the floating objects.
4847 // So, we see whether the available width changes due to the presence of floating images.
4848 // If it does, then we'll use the new restricted width to find the object height again.
4849 // If this causes another restriction in the available width, we'll try again, until
4850 // either we lose patience or the available width settles down.
4851 do
4852 {
4853 loopIterations ++;
4854
4855 wxRect oldAvailableRect = availableRect;
4856
4857 // Available width depends on the floating objects and the line height.
4858 // Note: the floating objects may be placed vertically along the two sides of
4859 // buffer, so we may have different available line widths with different
4860 // [startY, endY]. So, we can't determine how wide the available
4861 // space is until we know the exact line height.
4862 if (childDescent == 0)
4863 {
4864 lineHeight = wxMax(lineHeight, childSize.y);
4865 lineDescent = maxDescent;
4866 lineAscent = maxAscent;
4867 }
4868 else
4869 {
4870 lineDescent = wxMax(childDescent, maxDescent);
4871 lineAscent = wxMax(childSize.y-childDescent, maxAscent);
4872 }
4873 lineHeight = wxMax(lineHeight, (lineDescent + lineAscent));
4874
4875 if (wxRichTextBuffer::GetFloatingLayoutMode() && collector)
4876 {
4877 wxRect floatAvailableRect = collector->GetAvailableRect(rect.y + currentPosition.y, rect.y + currentPosition.y + lineHeight);
4878
4879 // Adjust availableRect to the space that is available when taking floating objects into account.
4880
4881 if (floatAvailableRect.x + startOffset > availableRect.x)
4882 {
4883 int newX = floatAvailableRect.x + startOffset;
4884 int newW = availableRect.width - (newX - availableRect.x);
4885 availableRect.x = newX;
4886 availableRect.width = newW;
4887 }
4888
4889 if (floatAvailableRect.width < availableRect.width)
4890 availableRect.width = floatAvailableRect.width;
4891 }
4892
4893 currentPosition.x = availableRect.x - rect.x;
4894
4895 if (child->IsTopLevel() && loopIterations <= 20)
4896 {
4897 if (availableRect != oldAvailableRect)
4898 {
4899 wxSize oldSize = child->GetCachedSize();
4900
4901 // Lays out the object first with a given amount of space, and then if no width was specified in attr,
4902 // lays out the object again using the minimum size
4903 child->Invalidate(wxRICHTEXT_ALL);
4904 child->LayoutToBestSize(dc, context, buffer,
4905 attr, child->GetAttributes(), availableRect, parentRect.GetSize(), style);
4906 childSize = child->GetCachedSize();
4907 childDescent = child->GetDescent();
4908
4909 if (oldSize != child->GetCachedSize())
4910 {
4911 partialExtents.Clear();
4912
4913 // Recalculate the partial text extents since the child object changed size
4914 GetRangeSize(GetRange(), paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_CACHE_SIZE, wxPoint(0,0), parentRect.GetSize(), & partialExtents);
4915 }
4916
4917 // Go around the loop finding the available rect for the given floating objects
4918 }
4919 else
4920 doLoop = false;
4921 }
4922 else
4923 doLoop = false;
4924 }
4925 while (doLoop);
4926
4927 if (child->IsTopLevel())
4928 {
4929 // We can move it to the correct position at this point
4930 // TODO: probably need to add margin
4931 child->Move(GetPosition() + wxPoint(currentWidth + (wxMax(leftIndent, leftIndent + leftSubIndent)), currentPosition.y));
4932 }
4933
4934 // Cases:
4935 // 1) There was a line break BEFORE the natural break
4936 // 2) There was a line break AFTER the natural break
4937 // 3) It's the last line
4938 // 4) The child still fits (carry on) - 'else' clause
4939
4940 if ((lineBreakInThisObject && (childSize.x + currentWidth <= availableRect.width))
4941 ||
4942 (childSize.x + currentWidth > availableRect.width)
4943 #if 0
4944 ||
4945 ((childSize.x + currentWidth <= availableRect.width) && !node->GetNext())
4946 #endif
4947 )
4948 {
4949 long wrapPosition = 0;
4950 if ((childSize.x + currentWidth <= availableRect.width) && !node->GetNext() && !lineBreakInThisObject)
4951 wrapPosition = child->GetRange().GetEnd();
4952 else
4953
4954 // Find a place to wrap. This may walk back to previous children,
4955 // for example if a word spans several objects.
4956 // Note: one object must contains only one wxTextAtrr, so the line height will not
4957 // change inside one object. Thus, we can pass the remain line width to the
4958 // FindWrapPosition function.
4959 if (!FindWrapPosition(wxRichTextRange(lastCompletedEndPos+1, child->GetRange().GetEnd()), dc, context, availableRect.width, wrapPosition, & partialExtents))
4960 {
4961 // If the function failed, just cut it off at the end of this child.
4962 wrapPosition = child->GetRange().GetEnd();
4963 }
4964
4965 // FindWrapPosition can still return a value that will put us in an endless wrapping loop
4966 if (wrapPosition <= lastCompletedEndPos)
4967 wrapPosition = wxMax(lastCompletedEndPos+1,child->GetRange().GetEnd());
4968
4969 // Line end position shouldn't be the same as the end, or greater.
4970 if (wrapPosition >= GetRange().GetEnd())
4971 wrapPosition = wxMax(0, GetRange().GetEnd()-1);
4972
4973 // wxLogDebug(wxT("Split at %ld"), wrapPosition);
4974
4975 // Let's find the actual size of the current line now
4976 wxSize actualSize;
4977 wxRichTextRange actualRange(lastCompletedEndPos+1, wrapPosition);
4978
4979 childDescent = 0;
4980
4981 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
4982 if (!child->IsEmpty())
4983 {
4984 // Get height only, then the width using the partial extents
4985 GetRangeSize(actualRange, actualSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED|wxRICHTEXT_HEIGHT_ONLY, wxPoint(0,0), parentRect.GetSize());
4986 actualSize.x = wxRichTextGetRangeWidth(*this, actualRange, partialExtents);
4987 }
4988 else
4989 #endif
4990 GetRangeSize(actualRange, actualSize, childDescent, dc, context, wxRICHTEXT_UNFORMATTED, wxPoint(0,0), parentRect.GetSize());
4991
4992 currentWidth = actualSize.x;
4993
4994 // The descent for the whole line at this point, is the correct max descent
4995 maxDescent = childDescent;
4996 // Maximum ascent
4997 maxAscent = actualSize.y-childDescent;
4998
4999 // lineHeight is given by the height for the whole line, since it will
5000 // take into account ascend/descend.
5001 lineHeight = actualSize.y;
5002
5003 if (lineHeight == 0 && buffer)
5004 {
5005 wxFont font(buffer->GetFontTable().FindFont(attr));
5006 wxCheckSetFont(dc, font);
5007 lineHeight = dc.GetCharHeight();
5008 }
5009
5010 if (maxDescent == 0)
5011 {
5012 int w, h;
5013 dc.GetTextExtent(wxT("X"), & w, &h, & maxDescent);
5014 }
5015
5016 // Add a new line
5017 wxRichTextLine* line = AllocateLine(lineCount);
5018
5019 // Set relative range so we won't have to change line ranges when paragraphs are moved
5020 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
5021 line->SetPosition(currentPosition);
5022 line->SetSize(wxSize(currentWidth, lineHeight));
5023 line->SetDescent(maxDescent);
5024
5025 maxHeight = currentPosition.y + lineHeight;
5026
5027 // Now move down a line. TODO: add margins, spacing
5028 currentPosition.y += lineHeight;
5029 currentPosition.y += lineSpacing;
5030 maxDescent = 0;
5031 maxAscent = 0;
5032 maxWidth = wxMax(maxWidth, currentWidth+startOffset);
5033 currentWidth = 0;
5034
5035 lineCount ++;
5036
5037 // TODO: account for zero-length objects
5038 // wxASSERT(wrapPosition > lastCompletedEndPos);
5039
5040 lastEndPos = wrapPosition;
5041 lastCompletedEndPos = lastEndPos;
5042
5043 lineHeight = 0;
5044
5045 if (wrapPosition < GetRange().GetEnd()-1)
5046 {
5047 // May need to set the node back to a previous one, due to searching back in wrapping
5048 wxRichTextObject* childAfterWrapPosition = FindObjectAtPosition(wrapPosition+1);
5049 if (childAfterWrapPosition)
5050 node = m_children.Find(childAfterWrapPosition);
5051 else
5052 node = node->GetNext();
5053 }
5054 else
5055 node = node->GetNext();
5056
5057 // Apply paragraph styles such as alignment to the wrapped line
5058 ApplyParagraphStyle(line, attr, availableRect, dc);
5059 }
5060 else
5061 {
5062 // We still fit, so don't add a line, and keep going
5063 currentWidth += childSize.x;
5064
5065 if (childDescent == 0)
5066 {
5067 // An object with a zero descend value wants to take up the whole
5068 // height regardless of baseline
5069 lineHeight = wxMax(lineHeight, childSize.y);
5070 }
5071 else
5072 {
5073 maxDescent = wxMax(childDescent, maxDescent);
5074 maxAscent = wxMax(childSize.y-childDescent, maxAscent);
5075 }
5076
5077 lineHeight = wxMax(lineHeight, (maxDescent + maxAscent));
5078
5079 maxWidth = wxMax(maxWidth, currentWidth+startOffset);
5080 lastEndPos = child->GetRange().GetEnd();
5081
5082 node = node->GetNext();
5083 }
5084 }
5085
5086 //wxASSERT(!(lastCompletedEndPos != -1 && lastCompletedEndPos < GetRange().GetEnd()-1));
5087
5088 // Add the last line - it's the current pos -> last para pos
5089 // Substract -1 because the last position is always the end-paragraph position.
5090 if (lastCompletedEndPos <= GetRange().GetEnd()-1)
5091 {
5092 currentPosition.x = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
5093
5094 wxRichTextLine* line = AllocateLine(lineCount);
5095
5096 wxRichTextRange actualRange(lastCompletedEndPos+1, GetRange().GetEnd()-1);
5097
5098 // Set relative range so we won't have to change line ranges when paragraphs are moved
5099 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
5100
5101 line->SetPosition(currentPosition);
5102
5103 if (lineHeight == 0 && buffer)
5104 {
5105 wxFont font(buffer->GetFontTable().FindFont(attr));
5106 wxCheckSetFont(dc, font);
5107 lineHeight = dc.GetCharHeight();
5108 }
5109
5110 if (maxDescent == 0)
5111 {
5112 int w, h;
5113 dc.GetTextExtent(wxT("X"), & w, &h, & maxDescent);
5114 }
5115
5116 line->SetSize(wxSize(currentWidth, lineHeight));
5117 line->SetDescent(maxDescent);
5118 currentPosition.y += lineHeight;
5119 currentPosition.y += lineSpacing;
5120 lineCount ++;
5121
5122 // Apply paragraph styles such as alignment to the wrapped line
5123 ApplyParagraphStyle(line, attr, availableRect, dc);
5124 }
5125
5126 // Remove remaining unused line objects, if any
5127 ClearUnusedLines(lineCount);
5128
5129 // We need to add back the margins etc.
5130 {
5131 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
5132 contentRect = wxRect(wxPoint(0, 0), wxSize(maxWidth, currentPosition.y + spaceAfterPara));
5133 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
5134 SetCachedSize(marginRect.GetSize());
5135 }
5136
5137 // The maximum size is the length of the paragraph stretched out into a line.
5138 // So if there were a single word, or an image, or a fixed-size text box, the object could be shrunk around
5139 // this size. TODO: take into account line breaks.
5140 {
5141 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
5142 contentRect = wxRect(wxPoint(0, 0), wxSize(paraSize.x + wxMax(leftIndent, leftIndent + leftSubIndent) + rightIndent, currentPosition.y + spaceAfterPara));
5143 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
5144 SetMaxSize(marginRect.GetSize());
5145 }
5146
5147 // Find the greatest minimum size. Currently we only look at non-text objects,
5148 // which isn't ideal but it would be slow to find the maximum word width to
5149 // use as the minimum.
5150 {
5151 int minWidth = 0;
5152 node = m_children.GetFirst();
5153 while (node)
5154 {
5155 wxRichTextObject* child = node->GetData();
5156
5157 // If floating, ignore. We already laid out floats.
5158 // Also ignore if empty object, except if we haven't got any
5159 // size yet.
5160 if ((!child->IsFloating() || !wxRichTextBuffer::GetFloatingLayoutMode()) && child->GetRange().GetLength() != 0 && !wxDynamicCast(child, wxRichTextPlainText))
5161 {
5162 if (child->GetCachedSize().x > minWidth)
5163 minWidth = child->GetMinSize().x;
5164 }
5165 node = node->GetNext();
5166 }
5167
5168 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
5169 contentRect = wxRect(wxPoint(0, 0), wxSize(minWidth, currentPosition.y + spaceAfterPara));
5170 GetBoxRects(dc, buffer, attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
5171 SetMinSize(marginRect.GetSize());
5172 }
5173
5174 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
5175 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
5176 // Use the text extents to calculate the size of each fragment in each line
5177 wxRichTextLineList::compatibility_iterator lineNode = m_cachedLines.GetFirst();
5178 while (lineNode)
5179 {
5180 wxRichTextLine* line = lineNode->GetData();
5181 wxRichTextRange lineRange = line->GetAbsoluteRange();
5182
5183 // Loop through objects until we get to the one within range
5184 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
5185
5186 while (node2)
5187 {
5188 wxRichTextObject* child = node2->GetData();
5189
5190 if (child->GetRange().GetLength() > 0 && !child->GetRange().IsOutside(lineRange))
5191 {
5192 wxRichTextRange rangeToUse = lineRange;
5193 rangeToUse.LimitTo(child->GetRange());
5194
5195 // Find the size of the child from the text extents, and store in an array
5196 // for drawing later
5197 int left = 0;
5198 if (rangeToUse.GetStart() > GetRange().GetStart())
5199 left = partialExtents[(rangeToUse.GetStart()-1) - GetRange().GetStart()];
5200 int right = partialExtents[rangeToUse.GetEnd() - GetRange().GetStart()];
5201 int sz = right - left;
5202 line->GetObjectSizes().Add(sz);
5203 }
5204 else if (child->GetRange().GetStart() > lineRange.GetEnd())
5205 // Can break out of inner loop now since we've passed this line's range
5206 break;
5207
5208 node2 = node2->GetNext();
5209 }
5210
5211 lineNode = lineNode->GetNext();
5212 }
5213 #endif
5214 #endif
5215
5216 return true;
5217 }
5218
5219 /// Apply paragraph styles, such as centering, to wrapped lines
5220 /// TODO: take into account box attributes, possibly
5221 void wxRichTextParagraph::ApplyParagraphStyle(wxRichTextLine* line, const wxRichTextAttr& attr, const wxRect& rect, wxDC& dc)
5222 {
5223 if (!attr.HasAlignment())
5224 return;
5225
5226 wxPoint pos = line->GetPosition();
5227 wxPoint originalPos = pos;
5228 wxSize size = line->GetSize();
5229
5230 // centering, right-justification
5231 if (attr.HasAlignment() && attr.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
5232 {
5233 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
5234 pos.x = (rect.GetWidth() - rightIndent - size.x)/2 + pos.x;
5235 line->SetPosition(pos);
5236 }
5237 else if (attr.HasAlignment() && attr.GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
5238 {
5239 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
5240 pos.x = pos.x + rect.GetWidth() - size.x - rightIndent;
5241 line->SetPosition(pos);
5242 }
5243
5244 if (pos != originalPos)
5245 {
5246 wxPoint inc = pos - originalPos;
5247
5248 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5249
5250 while (node)
5251 {
5252 wxRichTextObject* child = node->GetData();
5253 if (child->IsTopLevel() && !child->GetRange().IsOutside(line->GetAbsoluteRange()))
5254 child->Move(child->GetPosition() + inc);
5255
5256 node = node->GetNext();
5257 }
5258 }
5259 }
5260
5261 /// Insert text at the given position
5262 bool wxRichTextParagraph::InsertText(long pos, const wxString& text)
5263 {
5264 wxRichTextObject* childToUse = NULL;
5265 wxRichTextObjectList::compatibility_iterator nodeToUse = wxRichTextObjectList::compatibility_iterator();
5266
5267 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5268 while (node)
5269 {
5270 wxRichTextObject* child = node->GetData();
5271 if (child->GetRange().Contains(pos) && child->GetRange().GetLength() > 0)
5272 {
5273 childToUse = child;
5274 nodeToUse = node;
5275 break;
5276 }
5277
5278 node = node->GetNext();
5279 }
5280
5281 if (childToUse)
5282 {
5283 wxRichTextPlainText* textObject = wxDynamicCast(childToUse, wxRichTextPlainText);
5284 if (textObject)
5285 {
5286 int posInString = pos - textObject->GetRange().GetStart();
5287
5288 wxString newText = textObject->GetText().Mid(0, posInString) +
5289 text + textObject->GetText().Mid(posInString);
5290 textObject->SetText(newText);
5291
5292 int textLength = text.length();
5293
5294 textObject->SetRange(wxRichTextRange(textObject->GetRange().GetStart(),
5295 textObject->GetRange().GetEnd() + textLength));
5296
5297 // Increment the end range of subsequent fragments in this paragraph.
5298 // We'll set the paragraph range itself at a higher level.
5299
5300 wxRichTextObjectList::compatibility_iterator node = nodeToUse->GetNext();
5301 while (node)
5302 {
5303 wxRichTextObject* child = node->GetData();
5304 child->SetRange(wxRichTextRange(textObject->GetRange().GetStart() + textLength,
5305 textObject->GetRange().GetEnd() + textLength));
5306
5307 node = node->GetNext();
5308 }
5309
5310 return true;
5311 }
5312 else
5313 {
5314 // TODO: if not a text object, insert at closest position, e.g. in front of it
5315 }
5316 }
5317 else
5318 {
5319 // Add at end.
5320 // Don't pass parent initially to suppress auto-setting of parent range.
5321 // We'll do that at a higher level.
5322 wxRichTextPlainText* textObject = new wxRichTextPlainText(text, this);
5323
5324 AppendChild(textObject);
5325 return true;
5326 }
5327
5328 return false;
5329 }
5330
5331 void wxRichTextParagraph::Copy(const wxRichTextParagraph& obj)
5332 {
5333 wxRichTextCompositeObject::Copy(obj);
5334 }
5335
5336 /// Clear the cached lines
5337 void wxRichTextParagraph::ClearLines()
5338 {
5339 WX_CLEAR_LIST(wxRichTextLineList, m_cachedLines);
5340 }
5341
5342 /// Get/set the object size for the given range. Returns false if the range
5343 /// is invalid for this object.
5344 bool wxRichTextParagraph::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, const wxPoint& position, const wxSize& parentSize, wxArrayInt* partialExtents) const
5345 {
5346 if (!range.IsWithin(GetRange()))
5347 return false;
5348
5349 if (flags & wxRICHTEXT_UNFORMATTED)
5350 {
5351 // Just use unformatted data, assume no line breaks
5352 wxSize sz;
5353
5354 wxArrayInt childExtents;
5355 wxArrayInt* p;
5356 if (partialExtents)
5357 p = & childExtents;
5358 else
5359 p = NULL;
5360
5361 int maxDescent = 0;
5362 int maxAscent = 0;
5363 int maxLineHeight = 0;
5364
5365 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5366 while (node)
5367 {
5368 wxRichTextObject* child = node->GetData();
5369 if (!child->GetRange().IsOutside(range))
5370 {
5371 // Floating objects have a zero size within the paragraph.
5372 if (child->IsFloating() && wxRichTextBuffer::GetFloatingLayoutMode())
5373 {
5374 if (partialExtents)
5375 {
5376 int lastSize;
5377 if (partialExtents->GetCount() > 0)
5378 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5379 else
5380 lastSize = 0;
5381
5382 partialExtents->Add(0 /* zero size */ + lastSize);
5383 }
5384 }
5385 else
5386 {
5387 wxSize childSize;
5388
5389 wxRichTextRange rangeToUse = range;
5390 rangeToUse.LimitTo(child->GetRange());
5391 int childDescent = 0;
5392
5393 // At present wxRICHTEXT_HEIGHT_ONLY is only fast if we've already cached the size,
5394 // but it's only going to be used after caching has taken place.
5395 if ((flags & wxRICHTEXT_HEIGHT_ONLY) && child->GetCachedSize().y != 0)
5396 {
5397 childDescent = child->GetDescent();
5398 childSize = child->GetCachedSize();
5399
5400 if (childDescent == 0)
5401 {
5402 maxLineHeight = wxMax(maxLineHeight, childSize.y);
5403 }
5404 else
5405 {
5406 maxDescent = wxMax(maxDescent, childDescent);
5407 maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
5408 }
5409
5410 maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
5411
5412 sz.y = wxMax(sz.y, maxLineHeight);
5413 sz.x += childSize.x;
5414 descent = maxDescent;
5415 }
5416 else if (child->IsTopLevel())
5417 {
5418 childDescent = child->GetDescent();
5419 childSize = child->GetCachedSize();
5420
5421 if (childDescent == 0)
5422 {
5423 maxLineHeight = wxMax(maxLineHeight, childSize.y);
5424 }
5425 else
5426 {
5427 maxDescent = wxMax(maxDescent, childDescent);
5428 maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
5429 }
5430
5431 maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
5432
5433 sz.y = wxMax(sz.y, maxLineHeight);
5434 sz.x += childSize.x;
5435 descent = maxDescent;
5436
5437 // FIXME: this won't change the original values.
5438 // Should we be calling GetRangeSize above instead of using cached values?
5439 #if 0
5440 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange()))
5441 {
5442 child->SetCachedSize(childSize);
5443 child->SetDescent(childDescent);
5444 }
5445 #endif
5446
5447 if (partialExtents)
5448 {
5449 int lastSize;
5450 if (partialExtents->GetCount() > 0)
5451 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5452 else
5453 lastSize = 0;
5454
5455 partialExtents->Add(childSize.x + lastSize);
5456 }
5457 }
5458 else if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y), parentSize, p))
5459 {
5460 if (childDescent == 0)
5461 {
5462 maxLineHeight = wxMax(maxLineHeight, childSize.y);
5463 }
5464 else
5465 {
5466 maxDescent = wxMax(maxDescent, childDescent);
5467 maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
5468 }
5469
5470 maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
5471
5472 sz.y = wxMax(sz.y, maxLineHeight);
5473 sz.x += childSize.x;
5474 descent = maxDescent;
5475
5476 if ((flags & wxRICHTEXT_CACHE_SIZE) && (rangeToUse == child->GetRange()))
5477 {
5478 child->SetCachedSize(childSize);
5479 child->SetDescent(childDescent);
5480 }
5481
5482 if (partialExtents)
5483 {
5484 int lastSize;
5485 if (partialExtents->GetCount() > 0)
5486 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
5487 else
5488 lastSize = 0;
5489
5490 size_t i;
5491 for (i = 0; i < childExtents.GetCount(); i++)
5492 {
5493 partialExtents->Add(childExtents[i] + lastSize);
5494 }
5495 }
5496 }
5497 }
5498
5499 if (p)
5500 p->Clear();
5501 }
5502
5503 node = node->GetNext();
5504 }
5505 size = sz;
5506 }
5507 else
5508 {
5509 // Use formatted data, with line breaks
5510 wxSize sz;
5511
5512 // We're going to loop through each line, and then for each line,
5513 // call GetRangeSize for the fragment that comprises that line.
5514 // Only we have to do that multiple times within the line, because
5515 // the line may be broken into pieces. For now ignore line break commands
5516 // (so we can assume that getting the unformatted size for a fragment
5517 // within a line is the actual size)
5518
5519 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5520 while (node)
5521 {
5522 wxRichTextLine* line = node->GetData();
5523 wxRichTextRange lineRange = line->GetAbsoluteRange();
5524 if (!lineRange.IsOutside(range))
5525 {
5526 int maxDescent = 0;
5527 int maxAscent = 0;
5528 int maxLineHeight = 0;
5529 int maxLineWidth = 0;
5530
5531 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
5532 while (node2)
5533 {
5534 wxRichTextObject* child = node2->GetData();
5535
5536 if ((!child->IsFloating() || !wxRichTextBuffer::GetFloatingLayoutMode()) && !child->GetRange().IsOutside(lineRange))
5537 {
5538 wxRichTextRange rangeToUse = lineRange;
5539 rangeToUse.LimitTo(child->GetRange());
5540 if (child->IsTopLevel())
5541 rangeToUse = child->GetOwnRange();
5542
5543 wxSize childSize;
5544 int childDescent = 0;
5545 if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, context, flags, wxPoint(position.x + sz.x, position.y), parentSize))
5546 {
5547 if (childDescent == 0)
5548 {
5549 // Assume that if descent is zero, this child can occupy the full line height
5550 // and does not need space for the line's maximum descent. So we influence
5551 // the overall max line height only.
5552 maxLineHeight = wxMax(maxLineHeight, childSize.y);
5553 }
5554 else
5555 {
5556 maxAscent = wxMax(maxAscent, (childSize.y - childDescent));
5557 maxDescent = wxMax(maxAscent, childDescent);
5558 }
5559 maxLineHeight = wxMax(maxLineHeight, (maxAscent + maxDescent));
5560 maxLineWidth += childSize.x;
5561 }
5562 }
5563
5564 node2 = node2->GetNext();
5565 }
5566
5567 descent = wxMax(descent, maxDescent);
5568
5569 // Increase size by a line (TODO: paragraph spacing)
5570 sz.y += maxLineHeight;
5571 sz.x = wxMax(sz.x, maxLineWidth);
5572 }
5573 node = node->GetNext();
5574 }
5575 size = sz;
5576 }
5577 return true;
5578 }
5579
5580 /// Finds the absolute position and row height for the given character position
5581 bool wxRichTextParagraph::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
5582 {
5583 if (index == -1)
5584 {
5585 wxRichTextLine* line = ((wxRichTextParagraphLayoutBox*)GetParent())->GetLineAtPosition(0);
5586 if (line)
5587 *height = line->GetSize().y;
5588 else
5589 *height = dc.GetCharHeight();
5590
5591 // -1 means 'the start of the buffer'.
5592 pt = GetPosition();
5593 if (line)
5594 pt = pt + line->GetPosition();
5595
5596 return true;
5597 }
5598
5599 // The final position in a paragraph is taken to mean the position
5600 // at the start of the next paragraph.
5601 if (index == GetRange().GetEnd())
5602 {
5603 wxRichTextParagraphLayoutBox* parent = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
5604 wxASSERT( parent != NULL );
5605
5606 // Find the height at the next paragraph, if any
5607 wxRichTextLine* line = parent->GetLineAtPosition(index + 1);
5608 if (line)
5609 {
5610 *height = line->GetSize().y;
5611 pt = line->GetAbsolutePosition();
5612 }
5613 else
5614 {
5615 *height = dc.GetCharHeight();
5616 int indent = ConvertTenthsMMToPixels(dc, m_attributes.GetLeftIndent());
5617 pt = wxPoint(indent, GetCachedSize().y);
5618 }
5619
5620 return true;
5621 }
5622
5623 if (index < GetRange().GetStart() || index > GetRange().GetEnd())
5624 return false;
5625
5626 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5627 while (node)
5628 {
5629 wxRichTextLine* line = node->GetData();
5630 wxRichTextRange lineRange = line->GetAbsoluteRange();
5631 if (index >= lineRange.GetStart() && index <= lineRange.GetEnd())
5632 {
5633 // If this is the last point in the line, and we're forcing the
5634 // returned value to be the start of the next line, do the required
5635 // thing.
5636 if (index == lineRange.GetEnd() && forceLineStart)
5637 {
5638 if (node->GetNext())
5639 {
5640 wxRichTextLine* nextLine = node->GetNext()->GetData();
5641 *height = nextLine->GetSize().y;
5642 pt = nextLine->GetAbsolutePosition();
5643 return true;
5644 }
5645 }
5646
5647 pt.y = line->GetPosition().y + GetPosition().y;
5648
5649 wxRichTextRange r(lineRange.GetStart(), index);
5650 wxSize rangeSize;
5651 int descent = 0;
5652
5653 // We find the size of the line up to this point,
5654 // then we can add this size to the line start position and
5655 // paragraph start position to find the actual position.
5656
5657 if (GetRangeSize(r, rangeSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, line->GetPosition()+ GetPosition()))
5658 {
5659 pt.x = line->GetPosition().x + GetPosition().x + rangeSize.x;
5660 *height = line->GetSize().y;
5661
5662 return true;
5663 }
5664
5665 }
5666
5667 node = node->GetNext();
5668 }
5669
5670 return false;
5671 }
5672
5673 /// Hit-testing: returns a flag indicating hit test details, plus
5674 /// information about position
5675 int wxRichTextParagraph::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
5676 {
5677 if (!IsShown())
5678 return wxRICHTEXT_HITTEST_NONE;
5679
5680 // If we're in the top-level container, then we can return
5681 // a suitable hit test code even if the point is outside the container area,
5682 // so that we can position the caret sensibly even if we don't
5683 // click on valid content. If we're not at the top-level, and the point
5684 // is not within this paragraph object, then we don't want to stop more
5685 // precise hit-testing from working prematurely, so return immediately.
5686 // NEW STRATEGY: use the parent boundary to test whether we're in the
5687 // right region, not the paragraph, since the paragraph may be positioned
5688 // some way in from where the user clicks.
5689 {
5690 long tmpPos;
5691 wxRichTextObject* tempObj, *tempContextObj;
5692 if (GetParent() && GetParent()->wxRichTextObject::HitTest(dc, context, pt, tmpPos, & tempObj, & tempContextObj, flags) == wxRICHTEXT_HITTEST_NONE)
5693 return wxRICHTEXT_HITTEST_NONE;
5694 }
5695
5696 wxRichTextObjectList::compatibility_iterator objNode = m_children.GetFirst();
5697 while (objNode)
5698 {
5699 wxRichTextObject* child = objNode->GetData();
5700 // Don't recurse if we have wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS,
5701 // and also, if this seems composite but actually is marked as atomic,
5702 // don't recurse.
5703 if (child->IsTopLevel() && ((flags & wxRICHTEXT_HITTEST_NO_NESTED_OBJECTS) == 0) &&
5704 (! (((flags & wxRICHTEXT_HITTEST_HONOUR_ATOMIC) != 0) && child->IsAtomic())))
5705 {
5706 {
5707 int hitTest = child->HitTest(dc, context, pt, textPosition, obj, contextObj);
5708 if (hitTest != wxRICHTEXT_HITTEST_NONE)
5709 return hitTest;
5710 }
5711 }
5712
5713 objNode = objNode->GetNext();
5714 }
5715
5716 wxPoint paraPos = GetPosition();
5717
5718 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
5719 while (node)
5720 {
5721 wxRichTextLine* line = node->GetData();
5722 wxPoint linePos = paraPos + line->GetPosition();
5723 wxSize lineSize = line->GetSize();
5724 wxRichTextRange lineRange = line->GetAbsoluteRange();
5725
5726 if (pt.y <= linePos.y + lineSize.y)
5727 {
5728 if (pt.x < linePos.x)
5729 {
5730 textPosition = lineRange.GetStart();
5731 *obj = FindObjectAtPosition(textPosition);
5732 *contextObj = GetContainer();
5733 return wxRICHTEXT_HITTEST_BEFORE|wxRICHTEXT_HITTEST_OUTSIDE;
5734 }
5735 else if (pt.x >= (linePos.x + lineSize.x))
5736 {
5737 textPosition = lineRange.GetEnd();
5738 *obj = FindObjectAtPosition(textPosition);
5739 *contextObj = GetContainer();
5740 return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
5741 }
5742 else
5743 {
5744 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
5745 wxArrayInt partialExtents;
5746
5747 wxSize paraSize;
5748 int paraDescent;
5749
5750 // This calculates the partial text extents
5751 GetRangeSize(lineRange, paraSize, paraDescent, dc, context, wxRICHTEXT_UNFORMATTED, linePos, wxDefaultSize, & partialExtents);
5752
5753 int lastX = linePos.x;
5754 size_t i;
5755 for (i = 0; i < partialExtents.GetCount(); i++)
5756 {
5757 int nextX = partialExtents[i] + linePos.x;
5758
5759 if (pt.x >= lastX && pt.x <= nextX)
5760 {
5761 textPosition = i + lineRange.GetStart(); // minus 1?
5762
5763 *obj = FindObjectAtPosition(textPosition);
5764 *contextObj = GetContainer();
5765
5766 // So now we know it's between i-1 and i.
5767 // Let's see if we can be more precise about
5768 // which side of the position it's on.
5769
5770 int midPoint = (nextX + lastX)/2;
5771 if (pt.x >= midPoint)
5772 return wxRICHTEXT_HITTEST_AFTER;
5773 else
5774 return wxRICHTEXT_HITTEST_BEFORE;
5775 }
5776
5777 lastX = nextX;
5778 }
5779 #else
5780 long i;
5781 int lastX = linePos.x;
5782 for (i = lineRange.GetStart(); i <= lineRange.GetEnd(); i++)
5783 {
5784 wxSize childSize;
5785 int descent = 0;
5786
5787 wxRichTextRange rangeToUse(lineRange.GetStart(), i);
5788
5789 GetRangeSize(rangeToUse, childSize, descent, dc, context, wxRICHTEXT_UNFORMATTED, linePos);
5790
5791 int nextX = childSize.x + linePos.x;
5792
5793 if (pt.x >= lastX && pt.x <= nextX)
5794 {
5795 textPosition = i;
5796
5797 *obj = FindObjectAtPosition(textPosition);
5798 *contextObj = GetContainer();
5799
5800 // So now we know it's between i-1 and i.
5801 // Let's see if we can be more precise about
5802 // which side of the position it's on.
5803
5804 int midPoint = (nextX + lastX)/2;
5805 if (pt.x >= midPoint)
5806 return wxRICHTEXT_HITTEST_AFTER;
5807 else
5808 return wxRICHTEXT_HITTEST_BEFORE;
5809 }
5810 else
5811 {
5812 lastX = nextX;
5813 }
5814 }
5815 #endif
5816 }
5817 }
5818
5819 node = node->GetNext();
5820 }
5821
5822 return wxRICHTEXT_HITTEST_NONE;
5823 }
5824
5825 /// Split an object at this position if necessary, and return
5826 /// the previous object, or NULL if inserting at beginning.
5827 wxRichTextObject* wxRichTextParagraph::SplitAt(long pos, wxRichTextObject** previousObject)
5828 {
5829 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5830 while (node)
5831 {
5832 wxRichTextObject* child = node->GetData();
5833
5834 if (pos == child->GetRange().GetStart())
5835 {
5836 if (previousObject)
5837 {
5838 if (node->GetPrevious())
5839 *previousObject = node->GetPrevious()->GetData();
5840 else
5841 *previousObject = NULL;
5842 }
5843
5844 return child;
5845 }
5846
5847 if (child->GetRange().Contains(pos))
5848 {
5849 // This should create a new object, transferring part of
5850 // the content to the old object and the rest to the new object.
5851 wxRichTextObject* newObject = child->DoSplit(pos);
5852
5853 // If we couldn't split this object, just insert in front of it.
5854 if (!newObject)
5855 {
5856 // Maybe this is an empty string, try the next one
5857 // return child;
5858 }
5859 else
5860 {
5861 // Insert the new object after 'child'
5862 if (node->GetNext())
5863 m_children.Insert(node->GetNext(), newObject);
5864 else
5865 m_children.Append(newObject);
5866 newObject->SetParent(this);
5867
5868 if (previousObject)
5869 *previousObject = child;
5870
5871 return newObject;
5872 }
5873 }
5874
5875 node = node->GetNext();
5876 }
5877 if (previousObject)
5878 *previousObject = NULL;
5879 return NULL;
5880 }
5881
5882 /// Move content to a list from obj on
5883 void wxRichTextParagraph::MoveToList(wxRichTextObject* obj, wxList& list)
5884 {
5885 wxRichTextObjectList::compatibility_iterator node = m_children.Find(obj);
5886 while (node)
5887 {
5888 wxRichTextObject* child = node->GetData();
5889 list.Append(child);
5890
5891 wxRichTextObjectList::compatibility_iterator oldNode = node;
5892
5893 node = node->GetNext();
5894
5895 m_children.DeleteNode(oldNode);
5896 }
5897 }
5898
5899 /// Add content back from list
5900 void wxRichTextParagraph::MoveFromList(wxList& list)
5901 {
5902 for (wxList::compatibility_iterator node = list.GetFirst(); node; node = node->GetNext())
5903 {
5904 AppendChild((wxRichTextObject*) node->GetData());
5905 }
5906 }
5907
5908 /// Calculate range
5909 void wxRichTextParagraph::CalculateRange(long start, long& end)
5910 {
5911 wxRichTextCompositeObject::CalculateRange(start, end);
5912
5913 // Add one for end of paragraph
5914 end ++;
5915
5916 m_range.SetRange(start, end);
5917 }
5918
5919 /// Find the object at the given position
5920 wxRichTextObject* wxRichTextParagraph::FindObjectAtPosition(long position)
5921 {
5922 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5923 while (node)
5924 {
5925 wxRichTextObject* obj = node->GetData();
5926 if (obj->GetRange().Contains(position) ||
5927 obj->GetRange().GetStart() == position ||
5928 obj->GetRange().GetEnd() == position)
5929 return obj;
5930
5931 node = node->GetNext();
5932 }
5933 return NULL;
5934 }
5935
5936 /// Get the plain text searching from the start or end of the range.
5937 /// The resulting string may be shorter than the range given.
5938 bool wxRichTextParagraph::GetContiguousPlainText(wxString& text, const wxRichTextRange& range, bool fromStart)
5939 {
5940 text = wxEmptyString;
5941
5942 if (fromStart)
5943 {
5944 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
5945 while (node)
5946 {
5947 wxRichTextObject* obj = node->GetData();
5948 if (!obj->GetRange().IsOutside(range))
5949 {
5950 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
5951 if (textObj)
5952 {
5953 text += textObj->GetTextForRange(range);
5954 }
5955 else
5956 {
5957 text += wxT(" ");
5958 }
5959 }
5960
5961 node = node->GetNext();
5962 }
5963 }
5964 else
5965 {
5966 wxRichTextObjectList::compatibility_iterator node = m_children.GetLast();
5967 while (node)
5968 {
5969 wxRichTextObject* obj = node->GetData();
5970 if (!obj->GetRange().IsOutside(range))
5971 {
5972 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
5973 if (textObj)
5974 {
5975 text = textObj->GetTextForRange(range) + text;
5976 }
5977 else
5978 {
5979 text = wxT(" ") + text;
5980 }
5981 }
5982
5983 node = node->GetPrevious();
5984 }
5985 }
5986
5987 return true;
5988 }
5989
5990 /// Find a suitable wrap position.
5991 bool wxRichTextParagraph::FindWrapPosition(const wxRichTextRange& range, wxDC& dc, wxRichTextDrawingContext& context, int availableSpace, long& wrapPosition, wxArrayInt* partialExtents)
5992 {
5993 if (range.GetLength() <= 0)
5994 return false;
5995
5996 // Find the first position where the line exceeds the available space.
5997 wxSize sz;
5998 long breakPosition = range.GetEnd();
5999
6000 #if wxRICHTEXT_USE_PARTIAL_TEXT_EXTENTS
6001 if (partialExtents && partialExtents->GetCount() >= (size_t) (GetRange().GetLength()-1)) // the final position in a paragraph is the newline
6002 {
6003 int widthBefore;
6004
6005 if (range.GetStart() > GetRange().GetStart())
6006 widthBefore = (*partialExtents)[range.GetStart() - GetRange().GetStart() - 1];
6007 else
6008 widthBefore = 0;
6009
6010 size_t i;
6011 for (i = (size_t) range.GetStart(); i <= (size_t) range.GetEnd(); i++)
6012 {
6013 int widthFromStartOfThisRange = (*partialExtents)[i - GetRange().GetStart()] - widthBefore;
6014
6015 if (widthFromStartOfThisRange > availableSpace)
6016 {
6017 breakPosition = i-1;
6018 break;
6019 }
6020 }
6021 }
6022 else
6023 #endif
6024 {
6025 // Binary chop for speed
6026 long minPos = range.GetStart();
6027 long maxPos = range.GetEnd();
6028 while (true)
6029 {
6030 if (minPos == maxPos)
6031 {
6032 int descent = 0;
6033 GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
6034
6035 if (sz.x > availableSpace)
6036 breakPosition = minPos - 1;
6037 break;
6038 }
6039 else if ((maxPos - minPos) == 1)
6040 {
6041 int descent = 0;
6042 GetRangeSize(wxRichTextRange(range.GetStart(), minPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
6043
6044 if (sz.x > availableSpace)
6045 breakPosition = minPos - 1;
6046 else
6047 {
6048 GetRangeSize(wxRichTextRange(range.GetStart(), maxPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
6049 if (sz.x > availableSpace)
6050 breakPosition = maxPos-1;
6051 }
6052 break;
6053 }
6054 else
6055 {
6056 long nextPos = minPos + ((maxPos - minPos) / 2);
6057
6058 int descent = 0;
6059 GetRangeSize(wxRichTextRange(range.GetStart(), nextPos), sz, descent, dc, context, wxRICHTEXT_UNFORMATTED);
6060
6061 if (sz.x > availableSpace)
6062 {
6063 maxPos = nextPos;
6064 }
6065 else
6066 {
6067 minPos = nextPos;
6068 }
6069 }
6070 }
6071 }
6072
6073 // Now we know the last position on the line.
6074 // Let's try to find a word break.
6075
6076 wxString plainText;
6077 if (GetContiguousPlainText(plainText, wxRichTextRange(range.GetStart(), breakPosition), false))
6078 {
6079 int newLinePos = plainText.Find(wxRichTextLineBreakChar);
6080 if (newLinePos != wxNOT_FOUND)
6081 {
6082 breakPosition = wxMax(0, range.GetStart() + newLinePos);
6083 }
6084 else
6085 {
6086 int spacePos = plainText.Find(wxT(' '), true);
6087 int tabPos = plainText.Find(wxT('\t'), true);
6088 int pos = wxMax(spacePos, tabPos);
6089 if (pos != wxNOT_FOUND)
6090 {
6091 int positionsFromEndOfString = plainText.length() - pos - 1;
6092 breakPosition = breakPosition - positionsFromEndOfString;
6093 }
6094 }
6095 }
6096
6097 wrapPosition = breakPosition;
6098
6099 return true;
6100 }
6101
6102 /// Get the bullet text for this paragraph.
6103 wxString wxRichTextParagraph::GetBulletText()
6104 {
6105 if (GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE ||
6106 (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP))
6107 return wxEmptyString;
6108
6109 int number = GetAttributes().GetBulletNumber();
6110
6111 wxString text;
6112 if ((GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ARABIC) || (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE))
6113 {
6114 text.Printf(wxT("%d"), number);
6115 }
6116 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_UPPER)
6117 {
6118 // TODO: Unicode, and also check if number > 26
6119 text.Printf(wxT("%c"), (wxChar) (number+64));
6120 }
6121 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_LOWER)
6122 {
6123 // TODO: Unicode, and also check if number > 26
6124 text.Printf(wxT("%c"), (wxChar) (number+96));
6125 }
6126 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_UPPER)
6127 {
6128 text = wxRichTextDecimalToRoman(number);
6129 }
6130 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_LOWER)
6131 {
6132 text = wxRichTextDecimalToRoman(number);
6133 text.MakeLower();
6134 }
6135 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL)
6136 {
6137 text = GetAttributes().GetBulletText();
6138 }
6139
6140 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
6141 {
6142 // The outline style relies on the text being computed statically,
6143 // since it depends on other levels points (e.g. 1.2.1.1). So normally the bullet text
6144 // should be stored in the attributes; if not, just use the number for this
6145 // level, as previously computed.
6146 if (!GetAttributes().GetBulletText().IsEmpty())
6147 text = GetAttributes().GetBulletText();
6148 }
6149
6150 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PARENTHESES)
6151 {
6152 text = wxT("(") + text + wxT(")");
6153 }
6154 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_RIGHT_PARENTHESIS)
6155 {
6156 text = text + wxT(")");
6157 }
6158
6159 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PERIOD)
6160 {
6161 text += wxT(".");
6162 }
6163
6164 return text;
6165 }
6166
6167 /// Allocate or reuse a line object
6168 wxRichTextLine* wxRichTextParagraph::AllocateLine(int pos)
6169 {
6170 if (pos < (int) m_cachedLines.GetCount())
6171 {
6172 wxRichTextLine* line = m_cachedLines.Item(pos)->GetData();
6173 line->Init(this);
6174 return line;
6175 }
6176 else
6177 {
6178 wxRichTextLine* line = new wxRichTextLine(this);
6179 m_cachedLines.Append(line);
6180 return line;
6181 }
6182 }
6183
6184 /// Clear remaining unused line objects, if any
6185 bool wxRichTextParagraph::ClearUnusedLines(int lineCount)
6186 {
6187 int cachedLineCount = m_cachedLines.GetCount();
6188 if ((int) cachedLineCount > lineCount)
6189 {
6190 for (int i = 0; i < (int) (cachedLineCount - lineCount); i ++)
6191 {
6192 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetLast();
6193 wxRichTextLine* line = node->GetData();
6194 m_cachedLines.Erase(node);
6195 delete line;
6196 }
6197 }
6198 return true;
6199 }
6200
6201 /// Get combined attributes of the base style, paragraph style and character style. We use this to dynamically
6202 /// retrieve the actual style.
6203 wxRichTextAttr wxRichTextParagraph::GetCombinedAttributes(const wxRichTextAttr& contentStyle, bool includingBoxAttr) const
6204 {
6205 wxRichTextAttr attr;
6206 wxRichTextParagraphLayoutBox* buf = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
6207 if (buf)
6208 {
6209 attr = buf->GetBasicStyle();
6210 if (!includingBoxAttr)
6211 {
6212 attr.GetTextBoxAttr().Reset();
6213 // The background colour will be painted by the container, and we don't
6214 // want to unnecessarily overwrite the background when we're drawing text
6215 // because this may erase the guideline (which appears just under the text
6216 // if there's no padding).
6217 attr.SetFlags(attr.GetFlags() & ~wxTEXT_ATTR_BACKGROUND_COLOUR);
6218 }
6219 wxRichTextApplyStyle(attr, GetAttributes());
6220 }
6221 else
6222 attr = GetAttributes();
6223
6224 wxRichTextApplyStyle(attr, contentStyle);
6225 return attr;
6226 }
6227
6228 /// Get combined attributes of the base style and paragraph style.
6229 wxRichTextAttr wxRichTextParagraph::GetCombinedAttributes(bool includingBoxAttr) const
6230 {
6231 wxRichTextAttr attr;
6232 wxRichTextParagraphLayoutBox* buf = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
6233 if (buf)
6234 {
6235 attr = buf->GetBasicStyle();
6236 if (!includingBoxAttr)
6237 attr.GetTextBoxAttr().Reset();
6238 wxRichTextApplyStyle(attr, GetAttributes());
6239 }
6240 else
6241 attr = GetAttributes();
6242
6243 return attr;
6244 }
6245
6246 // Create default tabstop array
6247 void wxRichTextParagraph::InitDefaultTabs()
6248 {
6249 // create a default tab list at 10 mm each.
6250 for (int i = 0; i < 20; ++i)
6251 {
6252 sm_defaultTabs.Add(i*100);
6253 }
6254 }
6255
6256 // Clear default tabstop array
6257 void wxRichTextParagraph::ClearDefaultTabs()
6258 {
6259 sm_defaultTabs.Clear();
6260 }
6261
6262 void wxRichTextParagraph::LayoutFloat(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style, wxRichTextFloatCollector* floatCollector)
6263 {
6264 wxRichTextObjectList::compatibility_iterator node = GetChildren().GetFirst();
6265 while (node)
6266 {
6267 wxRichTextObject* anchored = node->GetData();
6268 if (anchored && anchored->IsFloating() && !floatCollector->HasFloat(anchored))
6269 {
6270 int x = 0;
6271 wxRichTextAttr parentAttr(GetAttributes());
6272 context.ApplyVirtualAttributes(parentAttr, this);
6273 #if 1
6274 // 27-09-2012
6275 wxRect availableSpace = GetParent()->GetAvailableContentArea(dc, context, rect);
6276
6277 anchored->LayoutToBestSize(dc, context, GetBuffer(),
6278 parentAttr, anchored->GetAttributes(),
6279 parentRect, availableSpace,
6280 style);
6281 wxSize size = anchored->GetCachedSize();
6282 #else
6283 wxSize size;
6284 int descent = 0;
6285 anchored->GetRangeSize(anchored->GetRange(), size, descent, dc, context, style);
6286 #endif
6287
6288 int offsetY = 0;
6289 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().IsValid())
6290 {
6291 offsetY = anchored->GetAttributes().GetTextBoxAttr().GetTop().GetValue();
6292 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
6293 {
6294 offsetY = ConvertTenthsMMToPixels(dc, offsetY);
6295 }
6296 }
6297
6298 int pos = floatCollector->GetFitPosition(anchored->GetAttributes().GetTextBoxAttr().GetFloatMode(), rect.y + offsetY, size.y);
6299
6300 /* Update the offset */
6301 int newOffsetY = pos - rect.y;
6302 if (newOffsetY != offsetY)
6303 {
6304 if (anchored->GetAttributes().GetTextBoxAttr().GetTop().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
6305 newOffsetY = ConvertPixelsToTenthsMM(dc, newOffsetY);
6306 anchored->GetAttributes().GetTextBoxAttr().GetTop().SetValue(newOffsetY);
6307 }
6308
6309 if (anchored->GetAttributes().GetTextBoxAttr().GetFloatMode() == wxTEXT_BOX_ATTR_FLOAT_LEFT)
6310 x = rect.x;
6311 else if (anchored->GetAttributes().GetTextBoxAttr().GetFloatMode() == wxTEXT_BOX_ATTR_FLOAT_RIGHT)
6312 x = rect.x + rect.width - size.x;
6313
6314 //anchored->SetPosition(wxPoint(x, pos));
6315 anchored->Move(wxPoint(x, pos)); // should move children
6316 anchored->SetCachedSize(size);
6317 floatCollector->CollectFloat(this, anchored);
6318 }
6319
6320 node = node->GetNext();
6321 }
6322 }
6323
6324 // Get the first position from pos that has a line break character.
6325 long wxRichTextParagraph::GetFirstLineBreakPosition(long pos)
6326 {
6327 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
6328 while (node)
6329 {
6330 wxRichTextObject* obj = node->GetData();
6331 if (pos >= obj->GetRange().GetStart() && pos <= obj->GetRange().GetEnd())
6332 {
6333 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
6334 if (textObj)
6335 {
6336 long breakPos = textObj->GetFirstLineBreakPosition(pos);
6337 if (breakPos > -1)
6338 return breakPos;
6339 }
6340 }
6341 node = node->GetNext();
6342 }
6343 return -1;
6344 }
6345
6346 /*!
6347 * wxRichTextLine
6348 * This object represents a line in a paragraph, and stores
6349 * offsets from the start of the paragraph representing the
6350 * start and end positions of the line.
6351 */
6352
6353 wxRichTextLine::wxRichTextLine(wxRichTextParagraph* parent)
6354 {
6355 Init(parent);
6356 }
6357
6358 /// Initialisation
6359 void wxRichTextLine::Init(wxRichTextParagraph* parent)
6360 {
6361 m_parent = parent;
6362 m_range.SetRange(-1, -1);
6363 m_pos = wxPoint(0, 0);
6364 m_size = wxSize(0, 0);
6365 m_descent = 0;
6366 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
6367 m_objectSizes.Clear();
6368 #endif
6369 }
6370
6371 /// Copy
6372 void wxRichTextLine::Copy(const wxRichTextLine& obj)
6373 {
6374 m_range = obj.m_range;
6375 #if wxRICHTEXT_USE_OPTIMIZED_LINE_DRAWING
6376 m_objectSizes = obj.m_objectSizes;
6377 #endif
6378 }
6379
6380 /// Get the absolute object position
6381 wxPoint wxRichTextLine::GetAbsolutePosition() const
6382 {
6383 return m_parent->GetPosition() + m_pos;
6384 }
6385
6386 /// Get the absolute range
6387 wxRichTextRange wxRichTextLine::GetAbsoluteRange() const
6388 {
6389 wxRichTextRange range(m_range.GetStart() + m_parent->GetRange().GetStart(), 0);
6390 range.SetEnd(range.GetStart() + m_range.GetLength()-1);
6391 return range;
6392 }
6393
6394 /*!
6395 * wxRichTextPlainText
6396 * This object represents a single piece of text.
6397 */
6398
6399 IMPLEMENT_DYNAMIC_CLASS(wxRichTextPlainText, wxRichTextObject)
6400
6401 wxRichTextPlainText::wxRichTextPlainText(const wxString& text, wxRichTextObject* parent, wxRichTextAttr* style):
6402 wxRichTextObject(parent)
6403 {
6404 if (style)
6405 SetAttributes(*style);
6406
6407 m_text = text;
6408 }
6409
6410 #define USE_KERNING_FIX 1
6411
6412 // If insufficient tabs are defined, this is the tab width used
6413 #define WIDTH_FOR_DEFAULT_TABS 50
6414
6415 /// Draw the item
6416 bool wxRichTextPlainText::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int WXUNUSED(style))
6417 {
6418 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
6419 wxASSERT (para != NULL);
6420
6421 wxRichTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes(), false /* no box attributes */) : GetAttributes());
6422 context.ApplyVirtualAttributes(textAttr, this);
6423
6424 // Let's make the assumption for now that for content in a paragraph, including
6425 // text, we never have a discontinuous selection. So we only deal with a
6426 // single range.
6427 wxRichTextRange selectionRange;
6428 if (selection.IsValid())
6429 {
6430 wxRichTextRangeArray selectionRanges = selection.GetSelectionForObject(this);
6431 if (selectionRanges.GetCount() > 0)
6432 selectionRange = selectionRanges[0];
6433 else
6434 selectionRange = wxRICHTEXT_NO_SELECTION;
6435 }
6436 else
6437 selectionRange = wxRICHTEXT_NO_SELECTION;
6438
6439 int offset = GetRange().GetStart();
6440
6441 wxString str = m_text;
6442 if (context.HasVirtualText(this))
6443 {
6444 if (!context.GetVirtualText(this, str) || str.Length() != m_text.Length())
6445 str = m_text;
6446 }
6447
6448 // Replace line break characters with spaces
6449 wxString toRemove = wxRichTextLineBreakChar;
6450 str.Replace(toRemove, wxT(" "));
6451 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & (wxTEXT_ATTR_EFFECT_CAPITALS|wxTEXT_ATTR_EFFECT_SMALL_CAPITALS)))
6452 str.MakeUpper();
6453
6454 long len = range.GetLength();
6455 wxString stringChunk = str.Mid(range.GetStart() - offset, (size_t) len);
6456
6457 // Test for the optimized situations where all is selected, or none
6458 // is selected.
6459
6460 wxFont textFont(GetBuffer()->GetFontTable().FindFont(textAttr));
6461 wxCheckSetFont(dc, textFont);
6462 int charHeight = dc.GetCharHeight();
6463
6464 int x, y;
6465 if ( textFont.IsOk() )
6466 {
6467 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SMALL_CAPITALS))
6468 {
6469 textFont.SetPointSize((int) (textFont.GetPointSize()*0.75));
6470 wxCheckSetFont(dc, textFont);
6471 charHeight = dc.GetCharHeight();
6472 }
6473
6474 if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT) )
6475 {
6476 if (textFont.IsUsingSizeInPixels())
6477 {
6478 double size = static_cast<double>(textFont.GetPixelSize().y) / wxSCRIPT_MUL_FACTOR;
6479 textFont.SetPixelSize(wxSize(0, static_cast<int>(size)));
6480 x = rect.x;
6481 y = rect.y;
6482 }
6483 else
6484 {
6485 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6486 textFont.SetPointSize(static_cast<int>(size));
6487 x = rect.x;
6488 y = rect.y;
6489 }
6490 wxCheckSetFont(dc, textFont);
6491 }
6492 else if ( textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) )
6493 {
6494 if (textFont.IsUsingSizeInPixels())
6495 {
6496 double size = static_cast<double>(textFont.GetPixelSize().y) / wxSCRIPT_MUL_FACTOR;
6497 textFont.SetPixelSize(wxSize(0, static_cast<int>(size)));
6498 x = rect.x;
6499 int sub_height = static_cast<int>(static_cast<double>(charHeight) / wxSCRIPT_MUL_FACTOR);
6500 y = rect.y + (rect.height - sub_height + (descent - m_descent));
6501 }
6502 else
6503 {
6504 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6505 textFont.SetPointSize(static_cast<int>(size));
6506 x = rect.x;
6507 int sub_height = static_cast<int>(static_cast<double>(charHeight) / wxSCRIPT_MUL_FACTOR);
6508 y = rect.y + (rect.height - sub_height + (descent - m_descent));
6509 }
6510 wxCheckSetFont(dc, textFont);
6511 }
6512 else
6513 {
6514 x = rect.x;
6515 y = rect.y + (rect.height - charHeight - (descent - m_descent));
6516 }
6517 }
6518 else
6519 {
6520 x = rect.x;
6521 y = rect.y + (rect.height - charHeight - (descent - m_descent));
6522 }
6523
6524 // TODO: new selection code
6525
6526 // (a) All selected.
6527 if (selectionRange.GetStart() <= range.GetStart() && selectionRange.GetEnd() >= range.GetEnd())
6528 {
6529 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, true);
6530 }
6531 // (b) None selected.
6532 else if (selectionRange.GetEnd() < range.GetStart() || selectionRange.GetStart() > range.GetEnd())
6533 {
6534 // Draw all unselected
6535 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, false);
6536 }
6537 else
6538 {
6539 // (c) Part selected, part not
6540 // Let's draw unselected chunk, selected chunk, then unselected chunk.
6541
6542 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6543
6544 // 1. Initial unselected chunk, if any, up until start of selection.
6545 if (selectionRange.GetStart() > range.GetStart() && selectionRange.GetStart() <= range.GetEnd())
6546 {
6547 int r1 = range.GetStart();
6548 int s1 = selectionRange.GetStart()-1;
6549 int fragmentLen = s1 - r1 + 1;
6550 if (fragmentLen < 0)
6551 {
6552 wxLogDebug(wxT("Mid(%d, %d"), (int)(r1 - offset), (int)fragmentLen);
6553 }
6554 wxString stringFragment = str.Mid(r1 - offset, fragmentLen);
6555
6556 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
6557
6558 #if USE_KERNING_FIX
6559 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
6560 {
6561 // Compensate for kerning difference
6562 wxString stringFragment2(str.Mid(r1 - offset, fragmentLen+1));
6563 wxString stringFragment3(str.Mid(r1 - offset + fragmentLen, 1));
6564
6565 wxCoord w1, h1, w2, h2, w3, h3;
6566 dc.GetTextExtent(stringFragment, & w1, & h1);
6567 dc.GetTextExtent(stringFragment2, & w2, & h2);
6568 dc.GetTextExtent(stringFragment3, & w3, & h3);
6569
6570 int kerningDiff = (w1 + w3) - w2;
6571 x = x - kerningDiff;
6572 }
6573 #endif
6574 }
6575
6576 // 2. Selected chunk, if any.
6577 if (selectionRange.GetEnd() >= range.GetStart())
6578 {
6579 int s1 = wxMax(selectionRange.GetStart(), range.GetStart());
6580 int s2 = wxMin(selectionRange.GetEnd(), range.GetEnd());
6581
6582 int fragmentLen = s2 - s1 + 1;
6583 if (fragmentLen < 0)
6584 {
6585 wxLogDebug(wxT("Mid(%d, %d"), (int)(s1 - offset), (int)fragmentLen);
6586 }
6587 wxString stringFragment = str.Mid(s1 - offset, fragmentLen);
6588
6589 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, true);
6590
6591 #if USE_KERNING_FIX
6592 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
6593 {
6594 // Compensate for kerning difference
6595 wxString stringFragment2(str.Mid(s1 - offset, fragmentLen+1));
6596 wxString stringFragment3(str.Mid(s1 - offset + fragmentLen, 1));
6597
6598 wxCoord w1, h1, w2, h2, w3, h3;
6599 dc.GetTextExtent(stringFragment, & w1, & h1);
6600 dc.GetTextExtent(stringFragment2, & w2, & h2);
6601 dc.GetTextExtent(stringFragment3, & w3, & h3);
6602
6603 int kerningDiff = (w1 + w3) - w2;
6604 x = x - kerningDiff;
6605 }
6606 #endif
6607 }
6608
6609 // 3. Remaining unselected chunk, if any
6610 if (selectionRange.GetEnd() < range.GetEnd())
6611 {
6612 int s2 = wxMin(selectionRange.GetEnd()+1, range.GetEnd());
6613 int r2 = range.GetEnd();
6614
6615 int fragmentLen = r2 - s2 + 1;
6616 if (fragmentLen < 0)
6617 {
6618 wxLogDebug(wxT("Mid(%d, %d"), (int)(s2 - offset), (int)fragmentLen);
6619 }
6620 wxString stringFragment = str.Mid(s2 - offset, fragmentLen);
6621
6622 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
6623 }
6624 }
6625
6626 return true;
6627 }
6628
6629 bool wxRichTextPlainText::DrawTabbedString(wxDC& dc, const wxRichTextAttr& attr, const wxRect& rect,wxString& str, wxCoord& x, wxCoord& y, bool selected)
6630 {
6631 bool hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
6632
6633 wxArrayInt tabArray;
6634 int tabCount;
6635 if (hasTabs)
6636 {
6637 if (attr.GetTabs().IsEmpty())
6638 tabArray = wxRichTextParagraph::GetDefaultTabs();
6639 else
6640 tabArray = attr.GetTabs();
6641 tabCount = tabArray.GetCount();
6642
6643 for (int i = 0; i < tabCount; ++i)
6644 {
6645 int pos = tabArray[i];
6646 pos = ConvertTenthsMMToPixels(dc, pos);
6647 tabArray[i] = pos;
6648 }
6649 }
6650 else
6651 tabCount = 0;
6652
6653 int nextTabPos = -1;
6654 int tabPos = -1;
6655 wxCoord w, h;
6656
6657 if (selected)
6658 {
6659 wxColour highlightColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
6660 wxColour highlightTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
6661
6662 wxCheckSetBrush(dc, wxBrush(highlightColour));
6663 wxCheckSetPen(dc, wxPen(highlightColour));
6664 dc.SetTextForeground(highlightTextColour);
6665 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6666 }
6667 else
6668 {
6669 dc.SetTextForeground(attr.GetTextColour());
6670
6671 if (attr.HasFlag(wxTEXT_ATTR_BACKGROUND_COLOUR) && attr.GetBackgroundColour().IsOk())
6672 {
6673 dc.SetBackgroundMode(wxBRUSHSTYLE_SOLID);
6674 dc.SetTextBackground(attr.GetBackgroundColour());
6675 }
6676 else
6677 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
6678 }
6679
6680 wxCoord x_orig = GetParent()->GetPosition().x;
6681 while (hasTabs)
6682 {
6683 // the string has a tab
6684 // break up the string at the Tab
6685 wxString stringChunk = str.BeforeFirst(wxT('\t'));
6686 str = str.AfterFirst(wxT('\t'));
6687 dc.GetTextExtent(stringChunk, & w, & h);
6688 tabPos = x + w;
6689 bool not_found = true;
6690 for (int i = 0; i < tabCount && not_found; ++i)
6691 {
6692 nextTabPos = tabArray.Item(i) + x_orig;
6693
6694 // Find the next tab position.
6695 // Even if we're at the end of the tab array, we must still draw the chunk.
6696
6697 if (nextTabPos > tabPos || (i == (tabCount - 1)))
6698 {
6699 if (nextTabPos <= tabPos)
6700 {
6701 int defaultTabWidth = ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
6702 nextTabPos = tabPos + defaultTabWidth;
6703 }
6704
6705 not_found = false;
6706 if (selected)
6707 {
6708 w = nextTabPos - x;
6709 wxRect selRect(x, rect.y, w, rect.GetHeight());
6710 dc.DrawRectangle(selRect);
6711 }
6712 dc.DrawText(stringChunk, x, y);
6713
6714 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
6715 {
6716 wxPen oldPen = dc.GetPen();
6717 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
6718 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
6719 wxCheckSetPen(dc, oldPen);
6720 }
6721
6722 x = nextTabPos;
6723 }
6724 }
6725 hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
6726 }
6727
6728 if (!str.IsEmpty())
6729 {
6730 dc.GetTextExtent(str, & w, & h);
6731 if (selected)
6732 {
6733 wxRect selRect(x, rect.y, w, rect.GetHeight());
6734 dc.DrawRectangle(selRect);
6735 }
6736 dc.DrawText(str, x, y);
6737
6738 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
6739 {
6740 wxPen oldPen = dc.GetPen();
6741 wxCheckSetPen(dc, wxPen(attr.GetTextColour(), 1));
6742 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
6743 wxCheckSetPen(dc, oldPen);
6744 }
6745
6746 x += w;
6747 }
6748
6749 return true;
6750 }
6751
6752 /// Lay the item out
6753 bool wxRichTextPlainText::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& WXUNUSED(rect), const wxRect& WXUNUSED(parentRect), int WXUNUSED(style))
6754 {
6755 // Only lay out if we haven't already cached the size
6756 if (m_size.x == -1)
6757 GetRangeSize(GetRange(), m_size, m_descent, dc, context, 0, wxPoint(0, 0));
6758 m_maxSize = m_size;
6759 // Eventually we want to have a reasonable estimate of minimum size.
6760 m_minSize = wxSize(0, 0);
6761 return true;
6762 }
6763
6764 /// Copy
6765 void wxRichTextPlainText::Copy(const wxRichTextPlainText& obj)
6766 {
6767 wxRichTextObject::Copy(obj);
6768
6769 m_text = obj.m_text;
6770 }
6771
6772 /// Get/set the object size for the given range. Returns false if the range
6773 /// is invalid for this object.
6774 bool wxRichTextPlainText::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int WXUNUSED(flags), const wxPoint& position, const wxSize& WXUNUSED(parentSize), wxArrayInt* partialExtents) const
6775 {
6776 if (!range.IsWithin(GetRange()))
6777 return false;
6778
6779 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
6780 wxASSERT (para != NULL);
6781
6782 int relativeX = position.x - GetParent()->GetPosition().x;
6783
6784 wxRichTextAttr textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
6785 context.ApplyVirtualAttributes(textAttr, (wxRichTextObject*) this);
6786
6787 // Always assume unformatted text, since at this level we have no knowledge
6788 // of line breaks - and we don't need it, since we'll calculate size within
6789 // formatted text by doing it in chunks according to the line ranges
6790
6791 bool bScript(false);
6792 wxFont font(GetBuffer()->GetFontTable().FindFont(textAttr));
6793 if (font.IsOk())
6794 {
6795 if ( textAttr.HasTextEffects() && ( (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUPERSCRIPT)
6796 || (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SUBSCRIPT) ) )
6797 {
6798 wxFont textFont = font;
6799 if (textFont.IsUsingSizeInPixels())
6800 {
6801 double size = static_cast<double>(textFont.GetPixelSize().y) / wxSCRIPT_MUL_FACTOR;
6802 textFont.SetPixelSize(wxSize(0, static_cast<int>(size)));
6803 }
6804 else
6805 {
6806 double size = static_cast<double>(textFont.GetPointSize()) / wxSCRIPT_MUL_FACTOR;
6807 textFont.SetPointSize(static_cast<int>(size));
6808 }
6809 wxCheckSetFont(dc, textFont);
6810 bScript = true;
6811 }
6812 else if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_SMALL_CAPITALS))
6813 {
6814 wxFont textFont = font;
6815 textFont.SetPointSize((int) (textFont.GetPointSize()*0.75));
6816 wxCheckSetFont(dc, textFont);
6817 bScript = true;
6818 }
6819 else
6820 {
6821 wxCheckSetFont(dc, font);
6822 }
6823 }
6824
6825 bool haveDescent = false;
6826 int startPos = range.GetStart() - GetRange().GetStart();
6827 long len = range.GetLength();
6828
6829 wxString str(m_text);
6830 if (context.HasVirtualText(this))
6831 {
6832 if (!context.GetVirtualText(this, str) || str.Length() != m_text.Length())
6833 str = m_text;
6834 }
6835
6836 wxString toReplace = wxRichTextLineBreakChar;
6837 str.Replace(toReplace, wxT(" "));
6838
6839 wxString stringChunk = str.Mid(startPos, (size_t) len);
6840
6841 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & (wxTEXT_ATTR_EFFECT_CAPITALS|wxTEXT_ATTR_EFFECT_SMALL_CAPITALS)))
6842 stringChunk.MakeUpper();
6843
6844 wxCoord w, h;
6845 int width = 0;
6846 if (stringChunk.Find(wxT('\t')) != wxNOT_FOUND)
6847 {
6848 // the string has a tab
6849 wxArrayInt tabArray;
6850 if (textAttr.GetTabs().IsEmpty())
6851 tabArray = wxRichTextParagraph::GetDefaultTabs();
6852 else
6853 tabArray = textAttr.GetTabs();
6854
6855 int tabCount = tabArray.GetCount();
6856
6857 for (int i = 0; i < tabCount; ++i)
6858 {
6859 int pos = tabArray[i];
6860 pos = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, pos);
6861 tabArray[i] = pos;
6862 }
6863
6864 int nextTabPos = -1;
6865
6866 while (stringChunk.Find(wxT('\t')) >= 0)
6867 {
6868 int absoluteWidth = 0;
6869
6870 // the string has a tab
6871 // break up the string at the Tab
6872 wxString stringFragment = stringChunk.BeforeFirst(wxT('\t'));
6873 stringChunk = stringChunk.AfterFirst(wxT('\t'));
6874
6875 if (partialExtents)
6876 {
6877 int oldWidth;
6878 if (partialExtents->GetCount() > 0)
6879 oldWidth = (*partialExtents)[partialExtents->GetCount()-1];
6880 else
6881 oldWidth = 0;
6882
6883 // Add these partial extents
6884 wxArrayInt p;
6885 dc.GetPartialTextExtents(stringFragment, p);
6886 size_t j;
6887 for (j = 0; j < p.GetCount(); j++)
6888 partialExtents->Add(oldWidth + p[j]);
6889
6890 if (partialExtents->GetCount() > 0)
6891 absoluteWidth = (*partialExtents)[(*partialExtents).GetCount()-1] + relativeX;
6892 else
6893 absoluteWidth = relativeX;
6894 }
6895 else
6896 {
6897 dc.GetTextExtent(stringFragment, & w, & h);
6898 width += w;
6899 absoluteWidth = width + relativeX;
6900 haveDescent = true;
6901 }
6902
6903 bool notFound = true;
6904 for (int i = 0; i < tabCount && notFound; ++i)
6905 {
6906 nextTabPos = tabArray.Item(i);
6907
6908 // Find the next tab position.
6909 // Even if we're at the end of the tab array, we must still process the chunk.
6910
6911 if (nextTabPos > absoluteWidth || (i == (tabCount - 1)))
6912 {
6913 if (nextTabPos <= absoluteWidth)
6914 {
6915 int defaultTabWidth = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, WIDTH_FOR_DEFAULT_TABS);
6916 nextTabPos = absoluteWidth + defaultTabWidth;
6917 }
6918
6919 notFound = false;
6920 width = nextTabPos - relativeX;
6921
6922 if (partialExtents)
6923 partialExtents->Add(width);
6924 }
6925 }
6926 }
6927 }
6928
6929 if (!stringChunk.IsEmpty())
6930 {
6931 if (partialExtents)
6932 {
6933 int oldWidth;
6934 if (partialExtents->GetCount() > 0)
6935 oldWidth = (*partialExtents)[partialExtents->GetCount()-1];
6936 else
6937 oldWidth = 0;
6938
6939 // Add these partial extents
6940 wxArrayInt p;
6941 dc.GetPartialTextExtents(stringChunk, p);
6942 size_t j;
6943 for (j = 0; j < p.GetCount(); j++)
6944 partialExtents->Add(oldWidth + p[j]);
6945 }
6946 else
6947 {
6948 dc.GetTextExtent(stringChunk, & w, & h, & descent);
6949 width += w;
6950 haveDescent = true;
6951 }
6952 }
6953
6954 if (partialExtents)
6955 {
6956 int charHeight = dc.GetCharHeight();
6957 if ((*partialExtents).GetCount() > 0)
6958 w = (*partialExtents)[partialExtents->GetCount()-1];
6959 else
6960 w = 0;
6961 size = wxSize(w, charHeight);
6962 }
6963 else
6964 {
6965 size = wxSize(width, dc.GetCharHeight());
6966 }
6967
6968 if (!haveDescent)
6969 dc.GetTextExtent(wxT("X"), & w, & h, & descent);
6970
6971 if ( bScript )
6972 dc.SetFont(font);
6973
6974 return true;
6975 }
6976
6977 /// Do a split, returning an object containing the second part, and setting
6978 /// the first part in 'this'.
6979 wxRichTextObject* wxRichTextPlainText::DoSplit(long pos)
6980 {
6981 long index = pos - GetRange().GetStart();
6982
6983 if (index < 0 || index >= (int) m_text.length())
6984 return NULL;
6985
6986 wxString firstPart = m_text.Mid(0, index);
6987 wxString secondPart = m_text.Mid(index);
6988
6989 m_text = firstPart;
6990
6991 wxRichTextPlainText* newObject = new wxRichTextPlainText(secondPart);
6992 newObject->SetAttributes(GetAttributes());
6993 newObject->SetProperties(GetProperties());
6994
6995 newObject->SetRange(wxRichTextRange(pos, GetRange().GetEnd()));
6996 GetRange().SetEnd(pos-1);
6997
6998 return newObject;
6999 }
7000
7001 /// Calculate range
7002 void wxRichTextPlainText::CalculateRange(long start, long& end)
7003 {
7004 end = start + m_text.length() - 1;
7005 m_range.SetRange(start, end);
7006 }
7007
7008 /// Delete range
7009 bool wxRichTextPlainText::DeleteRange(const wxRichTextRange& range)
7010 {
7011 wxRichTextRange r = range;
7012
7013 r.LimitTo(GetRange());
7014
7015 if (r.GetStart() == GetRange().GetStart() && r.GetEnd() == GetRange().GetEnd())
7016 {
7017 m_text.Empty();
7018 return true;
7019 }
7020
7021 long startIndex = r.GetStart() - GetRange().GetStart();
7022 long len = r.GetLength();
7023
7024 m_text = m_text.Mid(0, startIndex) + m_text.Mid(startIndex+len);
7025 return true;
7026 }
7027
7028 /// Get text for the given range.
7029 wxString wxRichTextPlainText::GetTextForRange(const wxRichTextRange& range) const
7030 {
7031 wxRichTextRange r = range;
7032
7033 r.LimitTo(GetRange());
7034
7035 long startIndex = r.GetStart() - GetRange().GetStart();
7036 long len = r.GetLength();
7037
7038 return m_text.Mid(startIndex, len);
7039 }
7040
7041 /// Returns true if this object can merge itself with the given one.
7042 bool wxRichTextPlainText::CanMerge(wxRichTextObject* object, wxRichTextDrawingContext& context) const
7043 {
7044 // JACS 2013-01-27
7045 if (!context.GetVirtualAttributesEnabled())
7046 {
7047 return object->GetClassInfo() == wxCLASSINFO(wxRichTextPlainText) &&
7048 (m_text.empty() || (wxTextAttrEq(GetAttributes(), object->GetAttributes()) && m_properties == object->GetProperties()));
7049 }
7050 else
7051 {
7052 wxRichTextPlainText* otherObj = wxDynamicCast(object, wxRichTextPlainText);
7053 if (!otherObj || m_text.empty())
7054 return false;
7055
7056 if (!wxTextAttrEq(GetAttributes(), object->GetAttributes()) || !(m_properties == object->GetProperties()))
7057 return false;
7058
7059 // Check if differing virtual attributes makes it impossible to merge
7060 // these strings.
7061
7062 bool hasVirtualAttr1 = context.HasVirtualAttributes((wxRichTextObject*) this);
7063 bool hasVirtualAttr2 = context.HasVirtualAttributes((wxRichTextObject*) object);
7064 if (!hasVirtualAttr1 && !hasVirtualAttr2)
7065 return true;
7066 else if (hasVirtualAttr1 != hasVirtualAttr2)
7067 return false;
7068 else
7069 {
7070 wxRichTextAttr virtualAttr1 = context.GetVirtualAttributes((wxRichTextObject*) this);
7071 wxRichTextAttr virtualAttr2 = context.GetVirtualAttributes((wxRichTextObject*) object);
7072 return virtualAttr1 == virtualAttr2;
7073 }
7074 }
7075 }
7076
7077 /// Returns true if this object merged itself with the given one.
7078 /// The calling code will then delete the given object.
7079 bool wxRichTextPlainText::Merge(wxRichTextObject* object, wxRichTextDrawingContext& WXUNUSED(context))
7080 {
7081 wxRichTextPlainText* textObject = wxDynamicCast(object, wxRichTextPlainText);
7082 wxASSERT( textObject != NULL );
7083
7084 if (textObject)
7085 {
7086 m_text += textObject->GetText();
7087 wxRichTextApplyStyle(m_attributes, textObject->GetAttributes());
7088 return true;
7089 }
7090 else
7091 return false;
7092 }
7093
7094 bool wxRichTextPlainText::CanSplit(wxRichTextDrawingContext& context) const
7095 {
7096 // If this object has any virtual attributes at all, whether for the whole object
7097 // or individual ones, we should try splitting it by calling Split.
7098 // Must be more than one character in order to be able to split.
7099 return m_text.Length() > 1 && context.HasVirtualAttributes((wxRichTextObject*) this);
7100 }
7101
7102 wxRichTextObject* wxRichTextPlainText::Split(wxRichTextDrawingContext& context)
7103 {
7104 int count = context.GetVirtualSubobjectAttributesCount(this);
7105 if (count > 0 && GetParent())
7106 {
7107 wxRichTextCompositeObject* parent = wxDynamicCast(GetParent(), wxRichTextCompositeObject);
7108 wxRichTextObjectList::compatibility_iterator node = parent->GetChildren().Find(this);
7109 if (node)
7110 {
7111 const wxRichTextAttr emptyAttr;
7112 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
7113
7114 wxArrayInt positions;
7115 wxRichTextAttrArray attributes;
7116 if (context.GetVirtualSubobjectAttributes(this, positions, attributes) && positions.GetCount() > 0)
7117 {
7118 wxASSERT(positions.GetCount() == attributes.GetCount());
7119
7120 // We will gather up runs of text with the same virtual attributes
7121
7122 int len = m_text.Length();
7123 int i = 0;
7124
7125 // runStart and runEnd represent the accumulated run with a consistent attribute
7126 // that hasn't yet been appended
7127 int runStart = -1;
7128 int runEnd = -1;
7129 wxRichTextAttr currentAttr;
7130 wxString text = m_text;
7131 wxRichTextPlainText* lastPlainText = this;
7132
7133 for (i = 0; i < (int) positions.GetCount(); i++)
7134 {
7135 int pos = positions[i];
7136 wxASSERT(pos >= 0 && pos < len);
7137 if (pos >= 0 && pos < len)
7138 {
7139 const wxRichTextAttr& attr = attributes[i];
7140
7141 if (pos == 0)
7142 {
7143 runStart = 0;
7144 currentAttr = attr;
7145 }
7146 // Check if there was a gap from the last known attribute and this.
7147 // In that case, we need to do something with the span of non-attributed text.
7148 else if ((pos-1) > runEnd)
7149 {
7150 if (runEnd == -1)
7151 {
7152 // We hadn't processed anything previously, so the previous run is from the text start
7153 // to just before this position. The current attribute remains empty.
7154 runStart = 0;
7155 runEnd = pos-1;
7156 }
7157 else
7158 {
7159 // If the previous attribute matches the gap's attribute (i.e., no attributes)
7160 // then just extend the run.
7161 if (currentAttr.IsDefault())
7162 {
7163 runEnd = pos-1;
7164 }
7165 else
7166 {
7167 // We need to add an object, or reuse the existing one.
7168 if (runStart == 0)
7169 {
7170 lastPlainText = this;
7171 SetText(text.Mid(runStart, runEnd - runStart + 1));
7172 }
7173 else
7174 {
7175 wxRichTextPlainText* obj = new wxRichTextPlainText;
7176 lastPlainText = obj;
7177 obj->SetAttributes(GetAttributes());
7178 obj->SetProperties(GetProperties());
7179 obj->SetParent(parent);
7180
7181 obj->SetText(text.Mid(runStart, runEnd - runStart + 1));
7182 if (next)
7183 parent->GetChildren().Insert(next, obj);
7184 else
7185 parent->GetChildren().Append(obj);
7186 }
7187
7188 runStart = runEnd+1;
7189 runEnd = pos-1;
7190
7191 currentAttr = emptyAttr;
7192 }
7193 }
7194 }
7195
7196 wxASSERT(runEnd == pos-1);
7197
7198 // Now we only have to deal with the previous run
7199 if (currentAttr == attr)
7200 {
7201 // If we still have the same attributes, then we
7202 // simply increase the run size.
7203 runEnd = pos;
7204 }
7205 else
7206 {
7207 if (runEnd >= 0)
7208 {
7209 // We need to add an object, or reuse the existing one.
7210 if (runStart == 0)
7211 {
7212 lastPlainText = this;
7213 SetText(text.Mid(runStart, runEnd - runStart + 1));
7214 }
7215 else
7216 {
7217 wxRichTextPlainText* obj = new wxRichTextPlainText;
7218 lastPlainText = obj;
7219 obj->SetAttributes(GetAttributes());
7220 obj->SetProperties(GetProperties());
7221 obj->SetParent(parent);
7222
7223 obj->SetText(text.Mid(runStart, runEnd - runStart + 1));
7224 if (next)
7225 parent->GetChildren().Insert(next, obj);
7226 else
7227 parent->GetChildren().Append(obj);
7228 }
7229 }
7230
7231 runStart = pos;
7232 runEnd = pos;
7233
7234 currentAttr = attr;
7235 }
7236 }
7237 }
7238
7239 // We may still have a run to add, and possibly a no-attribute text fragment after that.
7240 // If the whole string was already a single attribute (the run covers the whole string), don't split.
7241 if ((runStart != -1) && !(runStart == 0 && runEnd == (len-1)))
7242 {
7243 // If the current attribute is empty, merge the run with the next fragment
7244 // which by definition (because it's not specified) has empty attributes.
7245 if (currentAttr.IsDefault())
7246 runEnd = (len-1);
7247
7248 if (runEnd < (len-1))
7249 {
7250 // We need to add an object, or reuse the existing one.
7251 if (runStart == 0)
7252 {
7253 lastPlainText = this;
7254 SetText(text.Mid(runStart, runEnd - runStart + 1));
7255 }
7256 else
7257 {
7258 wxRichTextPlainText* obj = new wxRichTextPlainText;
7259 lastPlainText = obj;
7260 obj->SetAttributes(GetAttributes());
7261 obj->SetProperties(GetProperties());
7262 obj->SetParent(parent);
7263
7264 obj->SetText(text.Mid(runStart, runEnd - runStart + 1));
7265 if (next)
7266 parent->GetChildren().Insert(next, obj);
7267 else
7268 parent->GetChildren().Append(obj);
7269 }
7270
7271 runStart = runEnd+1;
7272 runEnd = (len-1);
7273 }
7274
7275 // Now the last, non-attributed fragment at the end, if any
7276 if ((runStart < len) && !(runStart == 0 && runEnd == (len-1)))
7277 {
7278 wxASSERT(runStart != 0);
7279
7280 wxRichTextPlainText* obj = new wxRichTextPlainText;
7281 obj->SetAttributes(GetAttributes());
7282 obj->SetProperties(GetProperties());
7283 obj->SetParent(parent);
7284
7285 obj->SetText(text.Mid(runStart, runEnd - runStart + 1));
7286 if (next)
7287 parent->GetChildren().Insert(next, obj);
7288 else
7289 parent->GetChildren().Append(obj);
7290
7291 lastPlainText = obj;
7292 }
7293 }
7294
7295 return lastPlainText;
7296 }
7297 }
7298 }
7299 return this;
7300 }
7301
7302 /// Dump to output stream for debugging
7303 void wxRichTextPlainText::Dump(wxTextOutputStream& stream)
7304 {
7305 wxRichTextObject::Dump(stream);
7306 stream << m_text << wxT("\n");
7307 }
7308
7309 /// Get the first position from pos that has a line break character.
7310 long wxRichTextPlainText::GetFirstLineBreakPosition(long pos)
7311 {
7312 int i;
7313 int len = m_text.length();
7314 int startPos = pos - m_range.GetStart();
7315 for (i = startPos; i < len; i++)
7316 {
7317 wxChar ch = m_text[i];
7318 if (ch == wxRichTextLineBreakChar)
7319 {
7320 return i + m_range.GetStart();
7321 }
7322 }
7323 return -1;
7324 }
7325
7326 /*!
7327 * wxRichTextBuffer
7328 * This is a kind of box, used to represent the whole buffer
7329 */
7330
7331 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBuffer, wxRichTextParagraphLayoutBox)
7332
7333 wxList wxRichTextBuffer::sm_handlers;
7334 wxList wxRichTextBuffer::sm_drawingHandlers;
7335 wxRichTextFieldTypeHashMap wxRichTextBuffer::sm_fieldTypes;
7336 wxRichTextRenderer* wxRichTextBuffer::sm_renderer = NULL;
7337 int wxRichTextBuffer::sm_bulletRightMargin = 20;
7338 float wxRichTextBuffer::sm_bulletProportion = (float) 0.3;
7339 bool wxRichTextBuffer::sm_floatingLayoutMode = true;
7340
7341 /// Initialisation
7342 void wxRichTextBuffer::Init()
7343 {
7344 m_commandProcessor = new wxCommandProcessor;
7345 m_styleSheet = NULL;
7346 m_modified = false;
7347 m_batchedCommandDepth = 0;
7348 m_batchedCommand = NULL;
7349 m_suppressUndo = 0;
7350 m_handlerFlags = 0;
7351 m_scale = 1.0;
7352 m_dimensionScale = 1.0;
7353 m_fontScale = 1.0;
7354 SetMargins(4);
7355 }
7356
7357 /// Initialisation
7358 wxRichTextBuffer::~wxRichTextBuffer()
7359 {
7360 delete m_commandProcessor;
7361 delete m_batchedCommand;
7362
7363 ClearStyleStack();
7364 ClearEventHandlers();
7365 }
7366
7367 void wxRichTextBuffer::ResetAndClearCommands()
7368 {
7369 Reset();
7370
7371 GetCommandProcessor()->ClearCommands();
7372
7373 Modify(false);
7374 Invalidate(wxRICHTEXT_ALL);
7375 }
7376
7377 void wxRichTextBuffer::Copy(const wxRichTextBuffer& obj)
7378 {
7379 wxRichTextParagraphLayoutBox::Copy(obj);
7380
7381 m_styleSheet = obj.m_styleSheet;
7382 m_modified = obj.m_modified;
7383 m_batchedCommandDepth = 0;
7384 if (m_batchedCommand)
7385 delete m_batchedCommand;
7386 m_batchedCommand = NULL;
7387 m_suppressUndo = obj.m_suppressUndo;
7388 m_invalidRange = obj.m_invalidRange;
7389 m_dimensionScale = obj.m_dimensionScale;
7390 m_fontScale = obj.m_fontScale;
7391 }
7392
7393 /// Push style sheet to top of stack
7394 bool wxRichTextBuffer::PushStyleSheet(wxRichTextStyleSheet* styleSheet)
7395 {
7396 if (m_styleSheet)
7397 styleSheet->InsertSheet(m_styleSheet);
7398
7399 SetStyleSheet(styleSheet);
7400
7401 return true;
7402 }
7403
7404 /// Pop style sheet from top of stack
7405 wxRichTextStyleSheet* wxRichTextBuffer::PopStyleSheet()
7406 {
7407 if (m_styleSheet)
7408 {
7409 wxRichTextStyleSheet* oldSheet = m_styleSheet;
7410 m_styleSheet = oldSheet->GetNextSheet();
7411 oldSheet->Unlink();
7412
7413 return oldSheet;
7414 }
7415 else
7416 return NULL;
7417 }
7418
7419 /// Submit command to insert paragraphs
7420 bool wxRichTextBuffer::InsertParagraphsWithUndo(long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int flags)
7421 {
7422 return ctrl->GetFocusObject()->InsertParagraphsWithUndo(this, pos, paragraphs, ctrl, flags);
7423 }
7424
7425 /// Submit command to insert paragraphs
7426 bool wxRichTextParagraphLayoutBox::InsertParagraphsWithUndo(wxRichTextBuffer* buffer, long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int WXUNUSED(flags))
7427 {
7428 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7429
7430 action->GetNewParagraphs() = paragraphs;
7431
7432 action->SetPosition(pos);
7433
7434 wxRichTextRange range = wxRichTextRange(pos, pos + paragraphs.GetOwnRange().GetEnd() - 1);
7435 if (!paragraphs.GetPartialParagraph())
7436 range.SetEnd(range.GetEnd()+1);
7437
7438 // Set the range we'll need to delete in Undo
7439 action->SetRange(range);
7440
7441 buffer->SubmitAction(action);
7442
7443 return true;
7444 }
7445
7446 /// Submit command to insert the given text
7447 bool wxRichTextBuffer::InsertTextWithUndo(long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
7448 {
7449 if (ctrl)
7450 return ctrl->GetFocusObject()->InsertTextWithUndo(this, pos, text, ctrl, flags);
7451 else
7452 return wxRichTextParagraphLayoutBox::InsertTextWithUndo(this, pos, text, ctrl, flags);
7453 }
7454
7455 /// Submit command to insert the given text
7456 bool wxRichTextParagraphLayoutBox::InsertTextWithUndo(wxRichTextBuffer* buffer, long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
7457 {
7458 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7459
7460 wxRichTextAttr* p = NULL;
7461 wxRichTextAttr paraAttr;
7462 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7463 {
7464 // Get appropriate paragraph style
7465 paraAttr = GetStyleForNewParagraph(buffer, pos, false, false);
7466 if (!paraAttr.IsDefault())
7467 p = & paraAttr;
7468 }
7469
7470 action->GetNewParagraphs().AddParagraphs(text, p);
7471
7472 int length = action->GetNewParagraphs().GetOwnRange().GetLength();
7473
7474 if (!text.empty() && text.Last() != wxT('\n'))
7475 {
7476 // Don't count the newline when undoing
7477 length --;
7478 action->GetNewParagraphs().SetPartialParagraph(true);
7479 }
7480 else if (!text.empty() && text.Last() == wxT('\n'))
7481 length --;
7482
7483 action->SetPosition(pos);
7484
7485 // Set the range we'll need to delete in Undo
7486 action->SetRange(wxRichTextRange(pos, pos + length - 1));
7487
7488 buffer->SubmitAction(action);
7489
7490 return true;
7491 }
7492
7493 /// Submit command to insert the given text
7494 bool wxRichTextBuffer::InsertNewlineWithUndo(long pos, wxRichTextCtrl* ctrl, int flags)
7495 {
7496 return ctrl->GetFocusObject()->InsertNewlineWithUndo(this, pos, ctrl, flags);
7497 }
7498
7499 /// Submit command to insert the given text
7500 bool wxRichTextParagraphLayoutBox::InsertNewlineWithUndo(wxRichTextBuffer* buffer, long pos, wxRichTextCtrl* ctrl, int flags)
7501 {
7502 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7503
7504 wxRichTextAttr* p = NULL;
7505 wxRichTextAttr paraAttr;
7506 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7507 {
7508 paraAttr = GetStyleForNewParagraph(buffer, pos, false, true /* look for next paragraph style */);
7509 if (!paraAttr.IsDefault())
7510 p = & paraAttr;
7511 }
7512
7513 wxRichTextAttr attr(buffer->GetDefaultStyle());
7514 // Don't include box attributes such as margins
7515 attr.GetTextBoxAttr().Reset();
7516
7517 wxRichTextParagraph* newPara = new wxRichTextParagraph(wxEmptyString, this, & attr);
7518 action->GetNewParagraphs().AppendChild(newPara);
7519 action->GetNewParagraphs().UpdateRanges();
7520 action->GetNewParagraphs().SetPartialParagraph(false);
7521 wxRichTextParagraph* para = GetParagraphAtPosition(pos, false);
7522 long pos1 = pos;
7523
7524 if (p)
7525 newPara->SetAttributes(*p);
7526
7527 if (flags & wxRICHTEXT_INSERT_INTERACTIVE)
7528 {
7529 if (para && para->GetRange().GetEnd() == pos)
7530 pos1 ++;
7531
7532 // Now see if we need to number the paragraph.
7533 if (newPara->GetAttributes().HasBulletNumber())
7534 {
7535 wxRichTextAttr numberingAttr;
7536 if (FindNextParagraphNumber(para, numberingAttr))
7537 wxRichTextApplyStyle(newPara->GetAttributes(), (const wxRichTextAttr&) numberingAttr);
7538 }
7539 }
7540
7541 action->SetPosition(pos);
7542
7543 // Use the default character style
7544 if (!buffer->GetDefaultStyle().IsDefault() && newPara->GetChildren().GetFirst())
7545 {
7546 // Check whether the default style merely reflects the paragraph/basic style,
7547 // in which case don't apply it.
7548 wxRichTextAttr defaultStyle(buffer->GetDefaultStyle());
7549 defaultStyle.GetTextBoxAttr().Reset();
7550 wxRichTextAttr toApply;
7551 if (para)
7552 {
7553 wxRichTextAttr combinedAttr = para->GetCombinedAttributes(true /* include box attributes */);
7554 wxRichTextAttr newAttr;
7555 // This filters out attributes that are accounted for by the current
7556 // paragraph/basic style
7557 wxRichTextApplyStyle(toApply, defaultStyle, & combinedAttr);
7558 }
7559 else
7560 toApply = defaultStyle;
7561
7562 if (!toApply.IsDefault())
7563 newPara->GetChildren().GetFirst()->GetData()->SetAttributes(toApply);
7564 }
7565
7566 // Set the range we'll need to delete in Undo
7567 action->SetRange(wxRichTextRange(pos1, pos1));
7568
7569 buffer->SubmitAction(action);
7570
7571 return true;
7572 }
7573
7574 /// Submit command to insert the given image
7575 bool wxRichTextBuffer::InsertImageWithUndo(long pos, const wxRichTextImageBlock& imageBlock, wxRichTextCtrl* ctrl, int flags,
7576 const wxRichTextAttr& textAttr)
7577 {
7578 return ctrl->GetFocusObject()->InsertImageWithUndo(this, pos, imageBlock, ctrl, flags, textAttr);
7579 }
7580
7581 /// Submit command to insert the given image
7582 bool wxRichTextParagraphLayoutBox::InsertImageWithUndo(wxRichTextBuffer* buffer, long pos, const wxRichTextImageBlock& imageBlock,
7583 wxRichTextCtrl* ctrl, int flags,
7584 const wxRichTextAttr& textAttr)
7585 {
7586 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7587
7588 wxRichTextAttr* p = NULL;
7589 wxRichTextAttr paraAttr;
7590 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7591 {
7592 paraAttr = GetStyleForNewParagraph(buffer, pos);
7593 if (!paraAttr.IsDefault())
7594 p = & paraAttr;
7595 }
7596
7597 wxRichTextAttr attr(buffer->GetDefaultStyle());
7598
7599 // Don't include box attributes such as margins
7600 attr.GetTextBoxAttr().Reset();
7601
7602 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
7603 if (p)
7604 newPara->SetAttributes(*p);
7605
7606 wxRichTextImage* imageObject = new wxRichTextImage(imageBlock, newPara);
7607 newPara->AppendChild(imageObject);
7608 imageObject->SetAttributes(textAttr);
7609 action->GetNewParagraphs().AppendChild(newPara);
7610 action->GetNewParagraphs().UpdateRanges();
7611
7612 action->GetNewParagraphs().SetPartialParagraph(true);
7613
7614 action->SetPosition(pos);
7615
7616 // Set the range we'll need to delete in Undo
7617 action->SetRange(wxRichTextRange(pos, pos));
7618
7619 buffer->SubmitAction(action);
7620
7621 return true;
7622 }
7623
7624 // Insert an object with no change of it
7625 wxRichTextObject* wxRichTextBuffer::InsertObjectWithUndo(long pos, wxRichTextObject *object, wxRichTextCtrl* ctrl, int flags)
7626 {
7627 return ctrl->GetFocusObject()->InsertObjectWithUndo(this, pos, object, ctrl, flags);
7628 }
7629
7630 // Insert an object with no change of it
7631 wxRichTextObject* wxRichTextParagraphLayoutBox::InsertObjectWithUndo(wxRichTextBuffer* buffer, long pos, wxRichTextObject *object, wxRichTextCtrl* ctrl, int flags)
7632 {
7633 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Object"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7634
7635 wxRichTextAttr* p = NULL;
7636 wxRichTextAttr paraAttr;
7637 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7638 {
7639 paraAttr = GetStyleForNewParagraph(buffer, pos);
7640 if (!paraAttr.IsDefault())
7641 p = & paraAttr;
7642 }
7643
7644 wxRichTextAttr attr(buffer->GetDefaultStyle());
7645
7646 // Don't include box attributes such as margins
7647 attr.GetTextBoxAttr().Reset();
7648
7649 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
7650 if (p)
7651 newPara->SetAttributes(*p);
7652
7653 newPara->AppendChild(object);
7654 action->GetNewParagraphs().AppendChild(newPara);
7655 action->GetNewParagraphs().UpdateRanges();
7656
7657 action->GetNewParagraphs().SetPartialParagraph(true);
7658
7659 action->SetPosition(pos);
7660
7661 // Set the range we'll need to delete in Undo
7662 action->SetRange(wxRichTextRange(pos, pos));
7663
7664 buffer->SubmitAction(action);
7665
7666 wxRichTextObject* obj = GetLeafObjectAtPosition(pos);
7667 return obj;
7668 }
7669
7670 wxRichTextField* wxRichTextParagraphLayoutBox::InsertFieldWithUndo(wxRichTextBuffer* buffer, long pos, const wxString& fieldType,
7671 const wxRichTextProperties& properties,
7672 wxRichTextCtrl* ctrl, int flags,
7673 const wxRichTextAttr& textAttr)
7674 {
7675 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Field"), wxRICHTEXT_INSERT, buffer, this, ctrl, false);
7676
7677 wxRichTextAttr* p = NULL;
7678 wxRichTextAttr paraAttr;
7679 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
7680 {
7681 paraAttr = GetStyleForNewParagraph(buffer, pos);
7682 if (!paraAttr.IsDefault())
7683 p = & paraAttr;
7684 }
7685
7686 wxRichTextAttr attr(buffer->GetDefaultStyle());
7687
7688 // Don't include box attributes such as margins
7689 attr.GetTextBoxAttr().Reset();
7690
7691 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
7692 if (p)
7693 newPara->SetAttributes(*p);
7694
7695 wxRichTextField* fieldObject = new wxRichTextField();
7696 fieldObject->wxRichTextObject::SetProperties(properties);
7697 fieldObject->SetFieldType(fieldType);
7698 fieldObject->SetAttributes(textAttr);
7699 newPara->AppendChild(fieldObject);
7700 action->GetNewParagraphs().AppendChild(newPara);
7701 action->GetNewParagraphs().UpdateRanges();
7702 action->GetNewParagraphs().SetPartialParagraph(true);
7703 action->SetPosition(pos);
7704
7705 // Set the range we'll need to delete in Undo
7706 action->SetRange(wxRichTextRange(pos, pos));
7707
7708 buffer->SubmitAction(action);
7709
7710 wxRichTextField* obj = wxDynamicCast(GetLeafObjectAtPosition(pos), wxRichTextField);
7711 return obj;
7712 }
7713
7714 /// Get the style that is appropriate for a new paragraph at this position.
7715 /// If the previous paragraph has a paragraph style name, look up the next-paragraph
7716 /// style.
7717 wxRichTextAttr wxRichTextParagraphLayoutBox::GetStyleForNewParagraph(wxRichTextBuffer* buffer, long pos, bool caretPosition, bool lookUpNewParaStyle) const
7718 {
7719 wxRichTextParagraph* para = GetParagraphAtPosition(pos, caretPosition);
7720 if (para)
7721 {
7722 wxRichTextAttr attr;
7723 bool foundAttributes = false;
7724
7725 // Look for a matching paragraph style
7726 if (lookUpNewParaStyle && !para->GetAttributes().GetParagraphStyleName().IsEmpty() && buffer->GetStyleSheet())
7727 {
7728 wxRichTextParagraphStyleDefinition* paraDef = buffer->GetStyleSheet()->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
7729 if (paraDef)
7730 {
7731 // If we're not at the end of the paragraph, then we apply THIS style, and not the designated next style.
7732 if (para->GetRange().GetEnd() == pos && !paraDef->GetNextStyle().IsEmpty())
7733 {
7734 wxRichTextParagraphStyleDefinition* nextParaDef = buffer->GetStyleSheet()->FindParagraphStyle(paraDef->GetNextStyle());
7735 if (nextParaDef)
7736 {
7737 foundAttributes = true;
7738 attr = nextParaDef->GetStyleMergedWithBase(buffer->GetStyleSheet());
7739 }
7740 }
7741
7742 // If we didn't find the 'next style', use this style instead.
7743 if (!foundAttributes)
7744 {
7745 foundAttributes = true;
7746 attr = paraDef->GetStyleMergedWithBase(buffer->GetStyleSheet());
7747 }
7748 }
7749 }
7750
7751 // Also apply list style if present
7752 if (lookUpNewParaStyle && !para->GetAttributes().GetListStyleName().IsEmpty() && buffer->GetStyleSheet())
7753 {
7754 wxRichTextListStyleDefinition* listDef = buffer->GetStyleSheet()->FindListStyle(para->GetAttributes().GetListStyleName());
7755 if (listDef)
7756 {
7757 int thisIndent = para->GetAttributes().GetLeftIndent();
7758 int thisLevel = para->GetAttributes().HasOutlineLevel() ? para->GetAttributes().GetOutlineLevel() : listDef->FindLevelForIndent(thisIndent);
7759
7760 // Apply the overall list style, and item style for this level
7761 wxRichTextAttr listStyle(listDef->GetCombinedStyleForLevel(thisLevel, buffer->GetStyleSheet()));
7762 wxRichTextApplyStyle(attr, listStyle);
7763 attr.SetOutlineLevel(thisLevel);
7764 if (para->GetAttributes().HasBulletNumber())
7765 attr.SetBulletNumber(para->GetAttributes().GetBulletNumber());
7766 }
7767 }
7768
7769 if (!foundAttributes)
7770 {
7771 attr = para->GetAttributes();
7772 int flags = attr.GetFlags();
7773
7774 // Eliminate character styles
7775 flags &= ( (~ wxTEXT_ATTR_FONT) |
7776 (~ wxTEXT_ATTR_TEXT_COLOUR) |
7777 (~ wxTEXT_ATTR_BACKGROUND_COLOUR) );
7778 attr.SetFlags(flags);
7779 }
7780
7781 return attr;
7782 }
7783 else
7784 return wxRichTextAttr();
7785 }
7786
7787 /// Submit command to delete this range
7788 bool wxRichTextBuffer::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl)
7789 {
7790 return ctrl->GetFocusObject()->DeleteRangeWithUndo(range, ctrl, this);
7791 }
7792
7793 /// Submit command to delete this range
7794 bool wxRichTextParagraphLayoutBox::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl, wxRichTextBuffer* buffer)
7795 {
7796 wxRichTextAction* action = new wxRichTextAction(NULL, _("Delete"), wxRICHTEXT_DELETE, buffer, this, ctrl);
7797
7798 action->SetPosition(ctrl->GetCaretPosition());
7799
7800 // Set the range to delete
7801 action->SetRange(range);
7802
7803 // Copy the fragment that we'll need to restore in Undo
7804 CopyFragment(range, action->GetOldParagraphs());
7805
7806 // See if we're deleting a paragraph marker, in which case we need to
7807 // make a note not to copy the attributes from the 2nd paragraph to the 1st.
7808 if (range.GetStart() == range.GetEnd())
7809 {
7810 wxRichTextParagraph* para = GetParagraphAtPosition(range.GetStart());
7811 if (para && para->GetRange().GetEnd() == range.GetEnd())
7812 {
7813 wxRichTextParagraph* nextPara = GetParagraphAtPosition(range.GetStart()+1);
7814 if (nextPara && nextPara != para)
7815 {
7816 action->GetOldParagraphs().GetChildren().GetFirst()->GetData()->SetAttributes(nextPara->GetAttributes());
7817 action->GetOldParagraphs().GetAttributes().SetFlags(action->GetOldParagraphs().GetAttributes().GetFlags() | wxTEXT_ATTR_KEEP_FIRST_PARA_STYLE);
7818 }
7819 }
7820 }
7821
7822 buffer->SubmitAction(action);
7823
7824 return true;
7825 }
7826
7827 /// Collapse undo/redo commands
7828 bool wxRichTextBuffer::BeginBatchUndo(const wxString& cmdName)
7829 {
7830 if (m_batchedCommandDepth == 0)
7831 {
7832 wxASSERT(m_batchedCommand == NULL);
7833 if (m_batchedCommand)
7834 {
7835 GetCommandProcessor()->Store(m_batchedCommand);
7836 }
7837 m_batchedCommand = new wxRichTextCommand(cmdName);
7838 }
7839
7840 m_batchedCommandDepth ++;
7841
7842 return true;
7843 }
7844
7845 /// Collapse undo/redo commands
7846 bool wxRichTextBuffer::EndBatchUndo()
7847 {
7848 m_batchedCommandDepth --;
7849
7850 wxASSERT(m_batchedCommandDepth >= 0);
7851 wxASSERT(m_batchedCommand != NULL);
7852
7853 if (m_batchedCommandDepth == 0)
7854 {
7855 GetCommandProcessor()->Store(m_batchedCommand);
7856 m_batchedCommand = NULL;
7857 }
7858
7859 return true;
7860 }
7861
7862 /// Submit immediately, or delay according to whether collapsing is on
7863 bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action)
7864 {
7865 if (action && !action->GetNewParagraphs().IsEmpty())
7866 PrepareContent(action->GetNewParagraphs());
7867
7868 if (BatchingUndo() && m_batchedCommand && !SuppressingUndo())
7869 {
7870 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
7871 cmd->AddAction(action);
7872 cmd->Do();
7873 cmd->GetActions().Clear();
7874 delete cmd;
7875
7876 m_batchedCommand->AddAction(action);
7877 }
7878 else
7879 {
7880 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
7881 cmd->AddAction(action);
7882
7883 // Only store it if we're not suppressing undo.
7884 return GetCommandProcessor()->Submit(cmd, !SuppressingUndo());
7885 }
7886
7887 return true;
7888 }
7889
7890 /// Begin suppressing undo/redo commands.
7891 bool wxRichTextBuffer::BeginSuppressUndo()
7892 {
7893 m_suppressUndo ++;
7894
7895 return true;
7896 }
7897
7898 /// End suppressing undo/redo commands.
7899 bool wxRichTextBuffer::EndSuppressUndo()
7900 {
7901 m_suppressUndo --;
7902
7903 return true;
7904 }
7905
7906 /// Begin using a style
7907 bool wxRichTextBuffer::BeginStyle(const wxRichTextAttr& style)
7908 {
7909 wxRichTextAttr newStyle(GetDefaultStyle());
7910 newStyle.GetTextBoxAttr().Reset();
7911
7912 // Save the old default style
7913 m_attributeStack.Append((wxObject*) new wxRichTextAttr(newStyle));
7914
7915 wxRichTextApplyStyle(newStyle, style);
7916 newStyle.SetFlags(style.GetFlags()|newStyle.GetFlags());
7917
7918 SetDefaultStyle(newStyle);
7919
7920 return true;
7921 }
7922
7923 /// End the style
7924 bool wxRichTextBuffer::EndStyle()
7925 {
7926 if (!m_attributeStack.GetFirst())
7927 {
7928 wxLogDebug(_("Too many EndStyle calls!"));
7929 return false;
7930 }
7931
7932 wxList::compatibility_iterator node = m_attributeStack.GetLast();
7933 wxRichTextAttr* attr = (wxRichTextAttr*)node->GetData();
7934 m_attributeStack.Erase(node);
7935
7936 SetDefaultStyle(*attr);
7937
7938 delete attr;
7939 return true;
7940 }
7941
7942 /// End all styles
7943 bool wxRichTextBuffer::EndAllStyles()
7944 {
7945 while (m_attributeStack.GetCount() != 0)
7946 EndStyle();
7947 return true;
7948 }
7949
7950 /// Clear the style stack
7951 void wxRichTextBuffer::ClearStyleStack()
7952 {
7953 for (wxList::compatibility_iterator node = m_attributeStack.GetFirst(); node; node = node->GetNext())
7954 delete (wxRichTextAttr*) node->GetData();
7955 m_attributeStack.Clear();
7956 }
7957
7958 /// Begin using bold
7959 bool wxRichTextBuffer::BeginBold()
7960 {
7961 wxRichTextAttr attr;
7962 attr.SetFontWeight(wxFONTWEIGHT_BOLD);
7963
7964 return BeginStyle(attr);
7965 }
7966
7967 /// Begin using italic
7968 bool wxRichTextBuffer::BeginItalic()
7969 {
7970 wxRichTextAttr attr;
7971 attr.SetFontStyle(wxFONTSTYLE_ITALIC);
7972
7973 return BeginStyle(attr);
7974 }
7975
7976 /// Begin using underline
7977 bool wxRichTextBuffer::BeginUnderline()
7978 {
7979 wxRichTextAttr attr;
7980 attr.SetFontUnderlined(true);
7981
7982 return BeginStyle(attr);
7983 }
7984
7985 /// Begin using point size
7986 bool wxRichTextBuffer::BeginFontSize(int pointSize)
7987 {
7988 wxRichTextAttr attr;
7989 attr.SetFontSize(pointSize);
7990
7991 return BeginStyle(attr);
7992 }
7993
7994 /// Begin using this font
7995 bool wxRichTextBuffer::BeginFont(const wxFont& font)
7996 {
7997 wxRichTextAttr attr;
7998 attr.SetFont(font);
7999
8000 return BeginStyle(attr);
8001 }
8002
8003 /// Begin using this colour
8004 bool wxRichTextBuffer::BeginTextColour(const wxColour& colour)
8005 {
8006 wxRichTextAttr attr;
8007 attr.SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
8008 attr.SetTextColour(colour);
8009
8010 return BeginStyle(attr);
8011 }
8012
8013 /// Begin using alignment
8014 bool wxRichTextBuffer::BeginAlignment(wxTextAttrAlignment alignment)
8015 {
8016 wxRichTextAttr attr;
8017 attr.SetFlags(wxTEXT_ATTR_ALIGNMENT);
8018 attr.SetAlignment(alignment);
8019
8020 return BeginStyle(attr);
8021 }
8022
8023 /// Begin left indent
8024 bool wxRichTextBuffer::BeginLeftIndent(int leftIndent, int leftSubIndent)
8025 {
8026 wxRichTextAttr attr;
8027 attr.SetFlags(wxTEXT_ATTR_LEFT_INDENT);
8028 attr.SetLeftIndent(leftIndent, leftSubIndent);
8029
8030 return BeginStyle(attr);
8031 }
8032
8033 /// Begin right indent
8034 bool wxRichTextBuffer::BeginRightIndent(int rightIndent)
8035 {
8036 wxRichTextAttr attr;
8037 attr.SetFlags(wxTEXT_ATTR_RIGHT_INDENT);
8038 attr.SetRightIndent(rightIndent);
8039
8040 return BeginStyle(attr);
8041 }
8042
8043 /// Begin paragraph spacing
8044 bool wxRichTextBuffer::BeginParagraphSpacing(int before, int after)
8045 {
8046 long flags = 0;
8047 if (before != 0)
8048 flags |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
8049 if (after != 0)
8050 flags |= wxTEXT_ATTR_PARA_SPACING_AFTER;
8051
8052 wxRichTextAttr attr;
8053 attr.SetFlags(flags);
8054 attr.SetParagraphSpacingBefore(before);
8055 attr.SetParagraphSpacingAfter(after);
8056
8057 return BeginStyle(attr);
8058 }
8059
8060 /// Begin line spacing
8061 bool wxRichTextBuffer::BeginLineSpacing(int lineSpacing)
8062 {
8063 wxRichTextAttr attr;
8064 attr.SetFlags(wxTEXT_ATTR_LINE_SPACING);
8065 attr.SetLineSpacing(lineSpacing);
8066
8067 return BeginStyle(attr);
8068 }
8069
8070 /// Begin numbered bullet
8071 bool wxRichTextBuffer::BeginNumberedBullet(int bulletNumber, int leftIndent, int leftSubIndent, int bulletStyle)
8072 {
8073 wxRichTextAttr attr;
8074 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
8075 attr.SetBulletStyle(bulletStyle);
8076 attr.SetBulletNumber(bulletNumber);
8077 attr.SetLeftIndent(leftIndent, leftSubIndent);
8078
8079 return BeginStyle(attr);
8080 }
8081
8082 /// Begin symbol bullet
8083 bool wxRichTextBuffer::BeginSymbolBullet(const wxString& symbol, int leftIndent, int leftSubIndent, int bulletStyle)
8084 {
8085 wxRichTextAttr attr;
8086 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
8087 attr.SetBulletStyle(bulletStyle);
8088 attr.SetLeftIndent(leftIndent, leftSubIndent);
8089 attr.SetBulletText(symbol);
8090
8091 return BeginStyle(attr);
8092 }
8093
8094 /// Begin standard bullet
8095 bool wxRichTextBuffer::BeginStandardBullet(const wxString& bulletName, int leftIndent, int leftSubIndent, int bulletStyle)
8096 {
8097 wxRichTextAttr attr;
8098 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
8099 attr.SetBulletStyle(bulletStyle);
8100 attr.SetLeftIndent(leftIndent, leftSubIndent);
8101 attr.SetBulletName(bulletName);
8102
8103 return BeginStyle(attr);
8104 }
8105
8106 /// Begin named character style
8107 bool wxRichTextBuffer::BeginCharacterStyle(const wxString& characterStyle)
8108 {
8109 if (GetStyleSheet())
8110 {
8111 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
8112 if (def)
8113 {
8114 wxRichTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
8115 return BeginStyle(attr);
8116 }
8117 }
8118 return false;
8119 }
8120
8121 /// Begin named paragraph style
8122 bool wxRichTextBuffer::BeginParagraphStyle(const wxString& paragraphStyle)
8123 {
8124 if (GetStyleSheet())
8125 {
8126 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(paragraphStyle);
8127 if (def)
8128 {
8129 wxRichTextAttr attr = def->GetStyleMergedWithBase(GetStyleSheet());
8130 return BeginStyle(attr);
8131 }
8132 }
8133 return false;
8134 }
8135
8136 /// Begin named list style
8137 bool wxRichTextBuffer::BeginListStyle(const wxString& listStyle, int level, int number)
8138 {
8139 if (GetStyleSheet())
8140 {
8141 wxRichTextListStyleDefinition* def = GetStyleSheet()->FindListStyle(listStyle);
8142 if (def)
8143 {
8144 wxRichTextAttr attr(def->GetCombinedStyleForLevel(level));
8145
8146 attr.SetBulletNumber(number);
8147
8148 return BeginStyle(attr);
8149 }
8150 }
8151 return false;
8152 }
8153
8154 /// Begin URL
8155 bool wxRichTextBuffer::BeginURL(const wxString& url, const wxString& characterStyle)
8156 {
8157 wxRichTextAttr attr;
8158
8159 if (!characterStyle.IsEmpty() && GetStyleSheet())
8160 {
8161 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
8162 if (def)
8163 {
8164 attr = def->GetStyleMergedWithBase(GetStyleSheet());
8165 }
8166 }
8167 attr.SetURL(url);
8168
8169 return BeginStyle(attr);
8170 }
8171
8172 /// Adds a handler to the end
8173 void wxRichTextBuffer::AddHandler(wxRichTextFileHandler *handler)
8174 {
8175 sm_handlers.Append(handler);
8176 }
8177
8178 /// Inserts a handler at the front
8179 void wxRichTextBuffer::InsertHandler(wxRichTextFileHandler *handler)
8180 {
8181 sm_handlers.Insert( handler );
8182 }
8183
8184 /// Removes a handler
8185 bool wxRichTextBuffer::RemoveHandler(const wxString& name)
8186 {
8187 wxRichTextFileHandler *handler = FindHandler(name);
8188 if (handler)
8189 {
8190 sm_handlers.DeleteObject(handler);
8191 delete handler;
8192 return true;
8193 }
8194 else
8195 return false;
8196 }
8197
8198 /// Finds a handler by filename or, if supplied, type
8199 wxRichTextFileHandler *wxRichTextBuffer::FindHandlerFilenameOrType(const wxString& filename,
8200 wxRichTextFileType imageType)
8201 {
8202 if (imageType != wxRICHTEXT_TYPE_ANY)
8203 return FindHandler(imageType);
8204 else if (!filename.IsEmpty())
8205 {
8206 wxString path, file, ext;
8207 wxFileName::SplitPath(filename, & path, & file, & ext);
8208 return FindHandler(ext, imageType);
8209 }
8210 else
8211 return NULL;
8212 }
8213
8214
8215 /// Finds a handler by name
8216 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& name)
8217 {
8218 wxList::compatibility_iterator node = sm_handlers.GetFirst();
8219 while (node)
8220 {
8221 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
8222 if (handler->GetName().Lower() == name.Lower()) return handler;
8223
8224 node = node->GetNext();
8225 }
8226 return NULL;
8227 }
8228
8229 /// Finds a handler by extension and type
8230 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& extension, wxRichTextFileType type)
8231 {
8232 wxList::compatibility_iterator node = sm_handlers.GetFirst();
8233 while (node)
8234 {
8235 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
8236 if ( handler->GetExtension().Lower() == extension.Lower() &&
8237 (type == wxRICHTEXT_TYPE_ANY || handler->GetType() == type) )
8238 return handler;
8239 node = node->GetNext();
8240 }
8241 return 0;
8242 }
8243
8244 /// Finds a handler by type
8245 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(wxRichTextFileType type)
8246 {
8247 wxList::compatibility_iterator node = sm_handlers.GetFirst();
8248 while (node)
8249 {
8250 wxRichTextFileHandler *handler = (wxRichTextFileHandler *)node->GetData();
8251 if (handler->GetType() == type) return handler;
8252 node = node->GetNext();
8253 }
8254 return NULL;
8255 }
8256
8257 void wxRichTextBuffer::InitStandardHandlers()
8258 {
8259 if (!FindHandler(wxRICHTEXT_TYPE_TEXT))
8260 AddHandler(new wxRichTextPlainTextHandler);
8261 }
8262
8263 void wxRichTextBuffer::CleanUpHandlers()
8264 {
8265 wxList::compatibility_iterator node = sm_handlers.GetFirst();
8266 while (node)
8267 {
8268 wxRichTextFileHandler* handler = (wxRichTextFileHandler*)node->GetData();
8269 wxList::compatibility_iterator next = node->GetNext();
8270 delete handler;
8271 node = next;
8272 }
8273
8274 sm_handlers.Clear();
8275 }
8276
8277 wxString wxRichTextBuffer::GetExtWildcard(bool combine, bool save, wxArrayInt* types)
8278 {
8279 if (types)
8280 types->Clear();
8281
8282 wxString wildcard;
8283
8284 wxList::compatibility_iterator node = GetHandlers().GetFirst();
8285 int count = 0;
8286 while (node)
8287 {
8288 wxRichTextFileHandler* handler = (wxRichTextFileHandler*) node->GetData();
8289 if (handler->IsVisible() && ((save && handler->CanSave()) || (!save && handler->CanLoad())))
8290 {
8291 if (combine)
8292 {
8293 if (count > 0)
8294 wildcard += wxT(";");
8295 wildcard += wxT("*.") + handler->GetExtension();
8296 }
8297 else
8298 {
8299 if (count > 0)
8300 wildcard += wxT("|");
8301 wildcard += handler->GetName();
8302 wildcard += wxT(" ");
8303 wildcard += _("files");
8304 wildcard += wxT(" (*.");
8305 wildcard += handler->GetExtension();
8306 wildcard += wxT(")|*.");
8307 wildcard += handler->GetExtension();
8308 if (types)
8309 types->Add(handler->GetType());
8310 }
8311 count ++;
8312 }
8313
8314 node = node->GetNext();
8315 }
8316
8317 if (combine)
8318 wildcard = wxT("(") + wildcard + wxT(")|") + wildcard;
8319 return wildcard;
8320 }
8321
8322 #if wxUSE_FFILE && wxUSE_STREAMS
8323 /// Load a file
8324 bool wxRichTextBuffer::LoadFile(const wxString& filename, wxRichTextFileType type)
8325 {
8326 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
8327 if (handler)
8328 {
8329 SetDefaultStyle(wxRichTextAttr());
8330 handler->SetFlags(GetHandlerFlags());
8331 bool success = handler->LoadFile(this, filename);
8332 Invalidate(wxRICHTEXT_ALL);
8333 return success;
8334 }
8335 else
8336 return false;
8337 }
8338
8339 /// Save a file
8340 bool wxRichTextBuffer::SaveFile(const wxString& filename, wxRichTextFileType type)
8341 {
8342 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
8343 if (handler)
8344 {
8345 handler->SetFlags(GetHandlerFlags());
8346 return handler->SaveFile(this, filename);
8347 }
8348 else
8349 return false;
8350 }
8351 #endif // wxUSE_FFILE && wxUSE_STREAMS
8352
8353 #if wxUSE_STREAMS
8354 /// Load from a stream
8355 bool wxRichTextBuffer::LoadFile(wxInputStream& stream, wxRichTextFileType type)
8356 {
8357 wxRichTextFileHandler* handler = FindHandler(type);
8358 if (handler)
8359 {
8360 SetDefaultStyle(wxRichTextAttr());
8361 handler->SetFlags(GetHandlerFlags());
8362 bool success = handler->LoadFile(this, stream);
8363 Invalidate(wxRICHTEXT_ALL);
8364 return success;
8365 }
8366 else
8367 return false;
8368 }
8369
8370 /// Save to a stream
8371 bool wxRichTextBuffer::SaveFile(wxOutputStream& stream, wxRichTextFileType type)
8372 {
8373 wxRichTextFileHandler* handler = FindHandler(type);
8374 if (handler)
8375 {
8376 handler->SetFlags(GetHandlerFlags());
8377 return handler->SaveFile(this, stream);
8378 }
8379 else
8380 return false;
8381 }
8382 #endif // wxUSE_STREAMS
8383
8384 /// Copy the range to the clipboard
8385 bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range)
8386 {
8387 bool success = false;
8388 wxRichTextParagraphLayoutBox* container = this;
8389 if (GetRichTextCtrl())
8390 container = GetRichTextCtrl()->GetFocusObject();
8391
8392 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
8393
8394 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
8395 {
8396 wxTheClipboard->Clear();
8397
8398 // Add composite object
8399
8400 wxDataObjectComposite* compositeObject = new wxDataObjectComposite();
8401
8402 {
8403 wxString text = container->GetTextForRange(range);
8404
8405 #ifdef __WXMSW__
8406 text = wxTextFile::Translate(text, wxTextFileType_Dos);
8407 #endif
8408
8409 compositeObject->Add(new wxTextDataObject(text), false /* not preferred */);
8410 }
8411
8412 // Add rich text buffer data object. This needs the XML handler to be present.
8413
8414 if (FindHandler(wxRICHTEXT_TYPE_XML))
8415 {
8416 wxRichTextBuffer* richTextBuf = new wxRichTextBuffer;
8417 container->CopyFragment(range, *richTextBuf);
8418
8419 compositeObject->Add(new wxRichTextBufferDataObject(richTextBuf), true /* preferred */);
8420 }
8421
8422 if (wxTheClipboard->SetData(compositeObject))
8423 success = true;
8424
8425 wxTheClipboard->Close();
8426 }
8427
8428 #else
8429 wxUnusedVar(range);
8430 #endif
8431 return success;
8432 }
8433
8434 /// Paste the clipboard content to the buffer
8435 bool wxRichTextBuffer::PasteFromClipboard(long position)
8436 {
8437 bool success = false;
8438 wxRichTextParagraphLayoutBox* container = this;
8439 if (GetRichTextCtrl())
8440 container = GetRichTextCtrl()->GetFocusObject();
8441
8442 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
8443 if (CanPasteFromClipboard())
8444 {
8445 if (wxTheClipboard->Open())
8446 {
8447 if (wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())))
8448 {
8449 wxRichTextBufferDataObject data;
8450 wxTheClipboard->GetData(data);
8451 wxRichTextBuffer* richTextBuffer = data.GetRichTextBuffer();
8452 if (richTextBuffer)
8453 {
8454 container->InsertParagraphsWithUndo(this, position+1, *richTextBuffer, GetRichTextCtrl(), 0);
8455 if (GetRichTextCtrl())
8456 GetRichTextCtrl()->ShowPosition(position + richTextBuffer->GetOwnRange().GetEnd());
8457 delete richTextBuffer;
8458 }
8459 }
8460 else if (wxTheClipboard->IsSupported(wxDF_TEXT)
8461 #if wxUSE_UNICODE
8462 || wxTheClipboard->IsSupported(wxDF_UNICODETEXT)
8463 #endif
8464 )
8465 {
8466 wxTextDataObject data;
8467 wxTheClipboard->GetData(data);
8468 wxString text(data.GetText());
8469 #ifdef __WXMSW__
8470 wxString text2;
8471 text2.Alloc(text.Length()+1);
8472 size_t i;
8473 for (i = 0; i < text.Length(); i++)
8474 {
8475 wxChar ch = text[i];
8476 if (ch != wxT('\r'))
8477 text2 += ch;
8478 }
8479 #else
8480 wxString text2 = text;
8481 #endif
8482 container->InsertTextWithUndo(this, position+1, text2, GetRichTextCtrl(), wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE);
8483
8484 if (GetRichTextCtrl())
8485 GetRichTextCtrl()->ShowPosition(position + text2.Length());
8486
8487 success = true;
8488 }
8489 else if (wxTheClipboard->IsSupported(wxDF_BITMAP))
8490 {
8491 wxBitmapDataObject data;
8492 wxTheClipboard->GetData(data);
8493 wxBitmap bitmap(data.GetBitmap());
8494 wxImage image(bitmap.ConvertToImage());
8495
8496 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, container, GetRichTextCtrl(), false);
8497
8498 action->GetNewParagraphs().AddImage(image);
8499
8500 if (action->GetNewParagraphs().GetChildCount() == 1)
8501 action->GetNewParagraphs().SetPartialParagraph(true);
8502
8503 action->SetPosition(position+1);
8504
8505 // Set the range we'll need to delete in Undo
8506 action->SetRange(wxRichTextRange(position+1, position+1));
8507
8508 SubmitAction(action);
8509
8510 success = true;
8511 }
8512 wxTheClipboard->Close();
8513 }
8514 }
8515 #else
8516 wxUnusedVar(position);
8517 #endif
8518 return success;
8519 }
8520
8521 /// Can we paste from the clipboard?
8522 bool wxRichTextBuffer::CanPasteFromClipboard() const
8523 {
8524 bool canPaste = false;
8525 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
8526 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
8527 {
8528 if (wxTheClipboard->IsSupported(wxDF_TEXT)
8529 #if wxUSE_UNICODE
8530 || wxTheClipboard->IsSupported(wxDF_UNICODETEXT)
8531 #endif
8532 || wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())) ||
8533 wxTheClipboard->IsSupported(wxDF_BITMAP))
8534 {
8535 canPaste = true;
8536 }
8537 wxTheClipboard->Close();
8538 }
8539 #endif
8540 return canPaste;
8541 }
8542
8543 /// Dumps contents of buffer for debugging purposes
8544 void wxRichTextBuffer::Dump()
8545 {
8546 wxString text;
8547 {
8548 wxStringOutputStream stream(& text);
8549 wxTextOutputStream textStream(stream);
8550 Dump(textStream);
8551 }
8552
8553 wxLogDebug(text);
8554 }
8555
8556 /// Add an event handler
8557 bool wxRichTextBuffer::AddEventHandler(wxEvtHandler* handler)
8558 {
8559 m_eventHandlers.Append(handler);
8560 return true;
8561 }
8562
8563 /// Remove an event handler
8564 bool wxRichTextBuffer::RemoveEventHandler(wxEvtHandler* handler, bool deleteHandler)
8565 {
8566 wxList::compatibility_iterator node = m_eventHandlers.Find(handler);
8567 if (node)
8568 {
8569 m_eventHandlers.Erase(node);
8570 if (deleteHandler)
8571 delete handler;
8572
8573 return true;
8574 }
8575 else
8576 return false;
8577 }
8578
8579 /// Clear event handlers
8580 void wxRichTextBuffer::ClearEventHandlers()
8581 {
8582 m_eventHandlers.Clear();
8583 }
8584
8585 /// Send event to event handlers. If sendToAll is true, will send to all event handlers,
8586 /// otherwise will stop at the first successful one.
8587 bool wxRichTextBuffer::SendEvent(wxEvent& event, bool sendToAll)
8588 {
8589 bool success = false;
8590 for (wxList::compatibility_iterator node = m_eventHandlers.GetFirst(); node; node = node->GetNext())
8591 {
8592 wxEvtHandler* handler = (wxEvtHandler*) node->GetData();
8593 if (handler->ProcessEvent(event))
8594 {
8595 success = true;
8596 if (!sendToAll)
8597 return true;
8598 }
8599 }
8600 return success;
8601 }
8602
8603 /// Set style sheet and notify of the change
8604 bool wxRichTextBuffer::SetStyleSheetAndNotify(wxRichTextStyleSheet* sheet)
8605 {
8606 wxRichTextStyleSheet* oldSheet = GetStyleSheet();
8607
8608 wxWindowID winid = wxID_ANY;
8609 if (GetRichTextCtrl())
8610 winid = GetRichTextCtrl()->GetId();
8611
8612 wxRichTextEvent event(wxEVT_RICHTEXT_STYLESHEET_REPLACING, winid);
8613 event.SetEventObject(GetRichTextCtrl());
8614 event.SetContainer(GetRichTextCtrl()->GetFocusObject());
8615 event.SetOldStyleSheet(oldSheet);
8616 event.SetNewStyleSheet(sheet);
8617 event.Allow();
8618
8619 if (SendEvent(event) && !event.IsAllowed())
8620 {
8621 if (sheet != oldSheet)
8622 delete sheet;
8623
8624 return false;
8625 }
8626
8627 if (oldSheet && oldSheet != sheet)
8628 delete oldSheet;
8629
8630 SetStyleSheet(sheet);
8631
8632 event.SetEventType(wxEVT_RICHTEXT_STYLESHEET_REPLACED);
8633 event.SetOldStyleSheet(NULL);
8634 event.Allow();
8635
8636 return SendEvent(event);
8637 }
8638
8639 /// Set renderer, deleting old one
8640 void wxRichTextBuffer::SetRenderer(wxRichTextRenderer* renderer)
8641 {
8642 if (sm_renderer)
8643 delete sm_renderer;
8644 sm_renderer = renderer;
8645 }
8646
8647 /// Hit-testing: returns a flag indicating hit test details, plus
8648 /// information about position
8649 int wxRichTextBuffer::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
8650 {
8651 int ret = wxRichTextParagraphLayoutBox::HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
8652 if (ret != wxRICHTEXT_HITTEST_NONE)
8653 {
8654 return ret;
8655 }
8656 else
8657 {
8658 textPosition = m_ownRange.GetEnd()-1;
8659 *obj = this;
8660 *contextObj = this;
8661 return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
8662 }
8663 }
8664
8665 void wxRichTextBuffer::SetFontScale(double fontScale)
8666 {
8667 m_fontScale = fontScale;
8668 m_fontTable.SetFontScale(fontScale);
8669 }
8670
8671 void wxRichTextBuffer::SetDimensionScale(double dimScale)
8672 {
8673 m_dimensionScale = dimScale;
8674 }
8675
8676 bool wxRichTextStdRenderer::DrawStandardBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxRichTextAttr& bulletAttr, const wxRect& rect)
8677 {
8678 if (bulletAttr.GetTextColour().IsOk())
8679 {
8680 wxCheckSetPen(dc, wxPen(bulletAttr.GetTextColour()));
8681 wxCheckSetBrush(dc, wxBrush(bulletAttr.GetTextColour()));
8682 }
8683 else
8684 {
8685 wxCheckSetPen(dc, *wxBLACK_PEN);
8686 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
8687 }
8688
8689 wxFont font;
8690 if (bulletAttr.HasFont())
8691 {
8692 font = paragraph->GetBuffer()->GetFontTable().FindFont(bulletAttr);
8693 }
8694 else
8695 font = (*wxNORMAL_FONT);
8696
8697 wxCheckSetFont(dc, font);
8698
8699 int charHeight = dc.GetCharHeight();
8700
8701 int bulletWidth = (int) (((float) charHeight) * wxRichTextBuffer::GetBulletProportion());
8702 int bulletHeight = bulletWidth;
8703
8704 int x = rect.x;
8705
8706 // Calculate the top position of the character (as opposed to the whole line height)
8707 int y = rect.y + (rect.height - charHeight);
8708
8709 // Calculate where the bullet should be positioned
8710 y = y + (charHeight+1)/2 - (bulletHeight+1)/2;
8711
8712 // The margin between a bullet and text.
8713 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
8714
8715 if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
8716 x = rect.x + rect.width - bulletWidth - margin;
8717 else if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
8718 x = x + (rect.width)/2 - bulletWidth/2;
8719
8720 if (bulletAttr.GetBulletName() == wxT("standard/square"))
8721 {
8722 dc.DrawRectangle(x, y, bulletWidth, bulletHeight);
8723 }
8724 else if (bulletAttr.GetBulletName() == wxT("standard/diamond"))
8725 {
8726 wxPoint pts[5];
8727 pts[0].x = x; pts[0].y = y + bulletHeight/2;
8728 pts[1].x = x + bulletWidth/2; pts[1].y = y;
8729 pts[2].x = x + bulletWidth; pts[2].y = y + bulletHeight/2;
8730 pts[3].x = x + bulletWidth/2; pts[3].y = y + bulletHeight;
8731
8732 dc.DrawPolygon(4, pts);
8733 }
8734 else if (bulletAttr.GetBulletName() == wxT("standard/triangle"))
8735 {
8736 wxPoint pts[3];
8737 pts[0].x = x; pts[0].y = y;
8738 pts[1].x = x + bulletWidth; pts[1].y = y + bulletHeight/2;
8739 pts[2].x = x; pts[2].y = y + bulletHeight;
8740
8741 dc.DrawPolygon(3, pts);
8742 }
8743 else if (bulletAttr.GetBulletName() == wxT("standard/circle-outline"))
8744 {
8745 wxCheckSetBrush(dc, *wxTRANSPARENT_BRUSH);
8746 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
8747 }
8748 else // "standard/circle", and catch-all
8749 {
8750 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
8751 }
8752
8753 return true;
8754 }
8755
8756 bool wxRichTextStdRenderer::DrawTextBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxRichTextAttr& attr, const wxRect& rect, const wxString& text)
8757 {
8758 if (!text.empty())
8759 {
8760 wxFont font;
8761 if ((attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL) && !attr.GetBulletFont().IsEmpty() && attr.HasFont())
8762 {
8763 wxRichTextAttr fontAttr;
8764 if (attr.HasFontPixelSize())
8765 fontAttr.SetFontPixelSize(attr.GetFontSize());
8766 else
8767 fontAttr.SetFontPointSize(attr.GetFontSize());
8768 fontAttr.SetFontStyle(attr.GetFontStyle());
8769 fontAttr.SetFontWeight(attr.GetFontWeight());
8770 fontAttr.SetFontUnderlined(attr.GetFontUnderlined());
8771 fontAttr.SetFontFaceName(attr.GetBulletFont());
8772 font = paragraph->GetBuffer()->GetFontTable().FindFont(fontAttr);
8773 }
8774 else if (attr.HasFont())
8775 font = paragraph->GetBuffer()->GetFontTable().FindFont(attr);
8776 else
8777 font = (*wxNORMAL_FONT);
8778
8779 wxCheckSetFont(dc, font);
8780
8781 if (attr.GetTextColour().IsOk())
8782 dc.SetTextForeground(attr.GetTextColour());
8783
8784 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
8785
8786 int charHeight = dc.GetCharHeight();
8787 wxCoord tw, th;
8788 dc.GetTextExtent(text, & tw, & th);
8789
8790 int x = rect.x;
8791
8792 // Calculate the top position of the character (as opposed to the whole line height)
8793 int y = rect.y + (rect.height - charHeight);
8794
8795 // The margin between a bullet and text.
8796 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
8797
8798 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
8799 x = (rect.x + rect.width) - tw - margin;
8800 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
8801 x = x + (rect.width)/2 - tw/2;
8802
8803 dc.DrawText(text, x, y);
8804
8805 return true;
8806 }
8807 else
8808 return false;
8809 }
8810
8811 bool wxRichTextStdRenderer::DrawBitmapBullet(wxRichTextParagraph* WXUNUSED(paragraph), wxDC& WXUNUSED(dc), const wxRichTextAttr& WXUNUSED(attr), const wxRect& WXUNUSED(rect))
8812 {
8813 // Currently unimplemented. The intention is to store bitmaps by name in a media store associated
8814 // with the buffer. The store will allow retrieval from memory, disk or other means.
8815 return false;
8816 }
8817
8818 /// Enumerate the standard bullet names currently supported
8819 bool wxRichTextStdRenderer::EnumerateStandardBulletNames(wxArrayString& bulletNames)
8820 {
8821 bulletNames.Add(wxTRANSLATE("standard/circle"));
8822 bulletNames.Add(wxTRANSLATE("standard/circle-outline"));
8823 bulletNames.Add(wxTRANSLATE("standard/square"));
8824 bulletNames.Add(wxTRANSLATE("standard/diamond"));
8825 bulletNames.Add(wxTRANSLATE("standard/triangle"));
8826
8827 return true;
8828 }
8829
8830 /*!
8831 * wxRichTextBox
8832 */
8833
8834 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBox, wxRichTextParagraphLayoutBox)
8835
8836 wxRichTextBox::wxRichTextBox(wxRichTextObject* parent):
8837 wxRichTextParagraphLayoutBox(parent)
8838 {
8839 }
8840
8841 /// Draw the item
8842 bool wxRichTextBox::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8843 {
8844 if (!IsShown())
8845 return true;
8846
8847 // TODO: if the active object in the control, draw an indication.
8848 // We need to add the concept of active object, and not just focus object,
8849 // so we can apply commands (properties, delete, ...) to objects such as text boxes and images.
8850 // Ultimately we would like to be able to interactively resize an active object
8851 // using drag handles.
8852 return wxRichTextParagraphLayoutBox::Draw(dc, context, range, selection, rect, descent, style);
8853 }
8854
8855 /// Copy
8856 void wxRichTextBox::Copy(const wxRichTextBox& obj)
8857 {
8858 wxRichTextParagraphLayoutBox::Copy(obj);
8859 }
8860
8861 // Edit properties via a GUI
8862 bool wxRichTextBox::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
8863 {
8864 wxRichTextObjectPropertiesDialog boxDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Box Properties"));
8865 boxDlg.SetAttributes(GetAttributes());
8866
8867 if (boxDlg.ShowModal() == wxID_OK)
8868 {
8869 // By passing wxRICHTEXT_SETSTYLE_RESET, indeterminate attributes set by the user will be set as
8870 // indeterminate in the object.
8871 boxDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
8872 return true;
8873 }
8874 else
8875 return false;
8876 }
8877
8878 /*!
8879 * wxRichTextField
8880 */
8881
8882 IMPLEMENT_DYNAMIC_CLASS(wxRichTextField, wxRichTextParagraphLayoutBox)
8883
8884 wxRichTextField::wxRichTextField(const wxString& fieldType, wxRichTextObject* parent):
8885 wxRichTextParagraphLayoutBox(parent)
8886 {
8887 SetFieldType(fieldType);
8888 }
8889
8890 /// Draw the item
8891 bool wxRichTextField::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
8892 {
8893 if (!IsShown())
8894 return true;
8895
8896 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8897 if (fieldType && fieldType->Draw(this, dc, context, range, selection, rect, descent, style))
8898 return true;
8899
8900 // Fallback; but don't draw guidelines.
8901 style &= ~wxRICHTEXT_DRAW_GUIDELINES;
8902 return wxRichTextParagraphLayoutBox::Draw(dc, context, range, selection, rect, descent, style);
8903 }
8904
8905 bool wxRichTextField::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& parentRect, int style)
8906 {
8907 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8908 if (fieldType && fieldType->Layout(this, dc, context, rect, parentRect, style))
8909 return true;
8910
8911 // Fallback
8912 return wxRichTextParagraphLayoutBox::Layout(dc, context, rect, parentRect, style);
8913 }
8914
8915 bool wxRichTextField::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, const wxPoint& position, const wxSize& parentSize, wxArrayInt* partialExtents) const
8916 {
8917 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8918 if (fieldType)
8919 return fieldType->GetRangeSize((wxRichTextField*) this, range, size, descent, dc, context, flags, position, parentSize, partialExtents);
8920
8921 return wxRichTextParagraphLayoutBox::GetRangeSize(range, size, descent, dc, context, flags, position, parentSize, partialExtents);
8922 }
8923
8924 /// Calculate range
8925 void wxRichTextField::CalculateRange(long start, long& end)
8926 {
8927 if (IsTopLevel())
8928 wxRichTextParagraphLayoutBox::CalculateRange(start, end);
8929 else
8930 wxRichTextObject::CalculateRange(start, end);
8931 }
8932
8933 /// Copy
8934 void wxRichTextField::Copy(const wxRichTextField& obj)
8935 {
8936 wxRichTextParagraphLayoutBox::Copy(obj);
8937
8938 UpdateField(GetBuffer());
8939 }
8940
8941 // Edit properties via a GUI
8942 bool wxRichTextField::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
8943 {
8944 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8945 if (fieldType)
8946 return fieldType->EditProperties(this, parent, buffer);
8947
8948 return false;
8949 }
8950
8951 bool wxRichTextField::CanEditProperties() const
8952 {
8953 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8954 if (fieldType)
8955 return fieldType->CanEditProperties((wxRichTextField*) this);
8956
8957 return false;
8958 }
8959
8960 wxString wxRichTextField::GetPropertiesMenuLabel() const
8961 {
8962 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8963 if (fieldType)
8964 return fieldType->GetPropertiesMenuLabel((wxRichTextField*) this);
8965
8966 return wxEmptyString;
8967 }
8968
8969 bool wxRichTextField::UpdateField(wxRichTextBuffer* buffer)
8970 {
8971 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8972 if (fieldType)
8973 return fieldType->UpdateField(buffer, (wxRichTextField*) this);
8974
8975 return false;
8976 }
8977
8978 bool wxRichTextField::IsTopLevel() const
8979 {
8980 wxRichTextFieldType* fieldType = wxRichTextBuffer::FindFieldType(GetFieldType());
8981 if (fieldType)
8982 return fieldType->IsTopLevel((wxRichTextField*) this);
8983
8984 return true;
8985 }
8986
8987 IMPLEMENT_CLASS(wxRichTextFieldType, wxObject)
8988
8989 IMPLEMENT_CLASS(wxRichTextFieldTypeStandard, wxRichTextFieldType)
8990
8991 wxRichTextFieldTypeStandard::wxRichTextFieldTypeStandard(const wxString& name, const wxString& label, int displayStyle)
8992 {
8993 Init();
8994
8995 SetName(name);
8996 SetLabel(label);
8997 SetDisplayStyle(displayStyle);
8998 }
8999
9000 wxRichTextFieldTypeStandard::wxRichTextFieldTypeStandard(const wxString& name, const wxBitmap& bitmap, int displayStyle)
9001 {
9002 Init();
9003
9004 SetName(name);
9005 SetBitmap(bitmap);
9006 SetDisplayStyle(displayStyle);
9007 }
9008
9009 void wxRichTextFieldTypeStandard::Init()
9010 {
9011 m_displayStyle = wxRICHTEXT_FIELD_STYLE_RECTANGLE;
9012 m_font = wxFont(6, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
9013 m_textColour = *wxWHITE;
9014 m_borderColour = *wxBLACK;
9015 m_backgroundColour = *wxBLACK;
9016 m_verticalPadding = 1;
9017 m_horizontalPadding = 3;
9018 m_horizontalMargin = 2;
9019 m_verticalMargin = 0;
9020 }
9021
9022 void wxRichTextFieldTypeStandard::Copy(const wxRichTextFieldTypeStandard& field)
9023 {
9024 wxRichTextFieldType::Copy(field);
9025
9026 m_label = field.m_label;
9027 m_displayStyle = field.m_displayStyle;
9028 m_font = field.m_font;
9029 m_textColour = field.m_textColour;
9030 m_borderColour = field.m_borderColour;
9031 m_backgroundColour = field.m_backgroundColour;
9032 m_verticalPadding = field.m_verticalPadding;
9033 m_horizontalPadding = field.m_horizontalPadding;
9034 m_horizontalMargin = field.m_horizontalMargin;
9035 m_bitmap = field.m_bitmap;
9036 }
9037
9038 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))
9039 {
9040 if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_COMPOSITE)
9041 return false; // USe default composite drawing
9042 else // if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_RECTANGLE || m_displayStyle == wxRICHTEXT_FIELD_STYLE_NOBORDER)
9043 {
9044 int borderSize = 1;
9045
9046 wxPen borderPen(m_borderColour, 1, wxSOLID);
9047 wxBrush backgroundBrush(m_backgroundColour);
9048 wxColour textColour(m_textColour);
9049
9050 if (selection.WithinSelection(obj->GetRange().GetStart(), obj))
9051 {
9052 wxColour highlightColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
9053 wxColour highlightTextColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
9054
9055 borderPen = wxPen(highlightTextColour, 1, wxSOLID);
9056 backgroundBrush = wxBrush(highlightColour);
9057
9058 wxCheckSetBrush(dc, backgroundBrush);
9059 wxCheckSetPen(dc, wxPen(highlightColour, 1, wxSOLID));
9060 dc.DrawRectangle(rect);
9061 }
9062
9063 if (m_displayStyle != wxRICHTEXT_FIELD_STYLE_NO_BORDER)
9064 borderSize = 0;
9065
9066 // objectRect is the area where the content is drawn, after margins around it have been taken into account
9067 wxRect objectRect = wxRect(wxPoint(rect.x + m_horizontalMargin, rect.y + wxMax(0, rect.height - descent - obj->GetCachedSize().y)),
9068 wxSize(obj->GetCachedSize().x - 2*m_horizontalMargin - borderSize, obj->GetCachedSize().y));
9069
9070 // clientArea is where the text is actually written
9071 wxRect clientArea = objectRect;
9072
9073 if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_RECTANGLE)
9074 {
9075 dc.SetPen(borderPen);
9076 dc.SetBrush(backgroundBrush);
9077 dc.DrawRoundedRectangle(objectRect, 4.0);
9078 }
9079 else if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_START_TAG)
9080 {
9081 int arrowLength = objectRect.height/2;
9082 clientArea.width -= (arrowLength - m_horizontalPadding);
9083
9084 wxPoint pts[5];
9085 pts[0].x = objectRect.x; pts[0].y = objectRect.y;
9086 pts[1].x = objectRect.x + objectRect.width - arrowLength; pts[1].y = objectRect.y;
9087 pts[2].x = objectRect.x + objectRect.width; pts[2].y = objectRect.y + (objectRect.height/2);
9088 pts[3].x = objectRect.x + objectRect.width - arrowLength; pts[3].y = objectRect.y + objectRect.height;
9089 pts[4].x = objectRect.x; pts[4].y = objectRect.y + objectRect.height;
9090 dc.SetPen(borderPen);
9091 dc.SetBrush(backgroundBrush);
9092 dc.DrawPolygon(5, pts);
9093 }
9094 else if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_END_TAG)
9095 {
9096 int arrowLength = objectRect.height/2;
9097 clientArea.width -= (arrowLength - m_horizontalPadding);
9098 clientArea.x += (arrowLength - m_horizontalPadding);
9099
9100 wxPoint pts[5];
9101 pts[0].x = objectRect.x + objectRect.width; pts[0].y = objectRect.y;
9102 pts[1].x = objectRect.x + arrowLength; pts[1].y = objectRect.y;
9103 pts[2].x = objectRect.x; pts[2].y = objectRect.y + (objectRect.height/2);
9104 pts[3].x = objectRect.x + arrowLength; pts[3].y = objectRect.y + objectRect.height;
9105 pts[4].x = objectRect.x + objectRect.width; pts[4].y = objectRect.y + objectRect.height;
9106 dc.SetPen(borderPen);
9107 dc.SetBrush(backgroundBrush);
9108 dc.DrawPolygon(5, pts);
9109 }
9110
9111 if (m_bitmap.IsOk())
9112 {
9113 int x = clientArea.x + (clientArea.width - m_bitmap.GetWidth())/2;
9114 int y = clientArea.y + m_verticalPadding;
9115 dc.DrawBitmap(m_bitmap, x, y, true);
9116
9117 if (selection.WithinSelection(obj->GetRange().GetStart(), obj))
9118 {
9119 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
9120 wxCheckSetPen(dc, *wxBLACK_PEN);
9121 dc.SetLogicalFunction(wxINVERT);
9122 dc.DrawRectangle(wxRect(x, y, m_bitmap.GetWidth(), m_bitmap.GetHeight()));
9123 dc.SetLogicalFunction(wxCOPY);
9124 }
9125 }
9126 else
9127 {
9128 wxString label(m_label);
9129 if (label.IsEmpty())
9130 label = wxT("??");
9131 int w, h, maxDescent;
9132 dc.SetFont(m_font);
9133 dc.GetTextExtent(m_label, & w, &h, & maxDescent);
9134 dc.SetTextForeground(textColour);
9135
9136 int x = clientArea.x + (clientArea.width - w)/2;
9137 int y = clientArea.y + (clientArea.height - (h - maxDescent))/2;
9138 dc.DrawText(m_label, x, y);
9139 }
9140 }
9141
9142 return true;
9143 }
9144
9145 bool wxRichTextFieldTypeStandard::Layout(wxRichTextField* obj, wxDC& dc, wxRichTextDrawingContext& context, const wxRect& WXUNUSED(rect), const wxRect& WXUNUSED(parentRect), int style)
9146 {
9147 if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_COMPOSITE)
9148 return false; // USe default composite layout
9149
9150 wxSize size = GetSize(obj, dc, context, style);
9151 obj->SetCachedSize(size);
9152 obj->SetMinSize(size);
9153 obj->SetMaxSize(size);
9154 return true;
9155 }
9156
9157 bool wxRichTextFieldTypeStandard::GetRangeSize(wxRichTextField* obj, const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, const wxPoint& position, const wxSize& parentSize, wxArrayInt* partialExtents) const
9158 {
9159 if (IsTopLevel(obj))
9160 return obj->wxRichTextParagraphLayoutBox::GetRangeSize(range, size, descent, dc, context, flags, position, parentSize);
9161 else
9162 {
9163 wxSize sz = GetSize(obj, dc, context, 0);
9164 if (partialExtents)
9165 {
9166 int lastSize;
9167 if (partialExtents->GetCount() > 0)
9168 lastSize = (*partialExtents)[partialExtents->GetCount()-1];
9169 else
9170 lastSize = 0;
9171 partialExtents->Add(lastSize + sz.x);
9172 }
9173 size = sz;
9174 return true;
9175 }
9176 }
9177
9178 wxSize wxRichTextFieldTypeStandard::GetSize(wxRichTextField* WXUNUSED(obj), wxDC& dc, wxRichTextDrawingContext& WXUNUSED(context), int WXUNUSED(style)) const
9179 {
9180 int borderSize = 1;
9181 int w = 0, h = 0, maxDescent = 0;
9182
9183 wxSize sz;
9184 if (m_bitmap.IsOk())
9185 {
9186 w = m_bitmap.GetWidth();
9187 h = m_bitmap.GetHeight();
9188
9189 sz = wxSize(w + m_horizontalMargin*2, h + m_verticalMargin*2);
9190 }
9191 else
9192 {
9193 wxString label(m_label);
9194 if (label.IsEmpty())
9195 label = wxT("??");
9196 dc.SetFont(m_font);
9197 dc.GetTextExtent(label, & w, &h, & maxDescent);
9198
9199 sz = wxSize(w + m_horizontalPadding*2 + m_horizontalMargin*2, h + m_verticalPadding *2 + m_verticalMargin*2);
9200 }
9201
9202 if (m_displayStyle != wxRICHTEXT_FIELD_STYLE_NO_BORDER)
9203 {
9204 sz.x += borderSize*2;
9205 sz.y += borderSize*2;
9206 }
9207
9208 if (m_displayStyle == wxRICHTEXT_FIELD_STYLE_START_TAG || m_displayStyle == wxRICHTEXT_FIELD_STYLE_END_TAG)
9209 {
9210 // Add space for the arrow
9211 sz.x += (sz.y/2 - m_horizontalPadding);
9212 }
9213
9214 return sz;
9215 }
9216
9217 IMPLEMENT_DYNAMIC_CLASS(wxRichTextCell, wxRichTextBox)
9218
9219 wxRichTextCell::wxRichTextCell(wxRichTextObject* parent):
9220 wxRichTextBox(parent)
9221 {
9222 }
9223
9224 /// Draw the item
9225 bool wxRichTextCell::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
9226 {
9227 return wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style);
9228 }
9229
9230 int wxRichTextCell::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
9231 {
9232 int ret = wxRichTextParagraphLayoutBox::HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
9233 if (ret != wxRICHTEXT_HITTEST_NONE)
9234 {
9235 return ret;
9236 }
9237 else
9238 {
9239 textPosition = m_ownRange.GetEnd()-1;
9240 *obj = this;
9241 *contextObj = this;
9242 return wxRICHTEXT_HITTEST_AFTER|wxRICHTEXT_HITTEST_OUTSIDE;
9243 }
9244 }
9245
9246 /// Copy
9247 void wxRichTextCell::Copy(const wxRichTextCell& obj)
9248 {
9249 wxRichTextBox::Copy(obj);
9250 }
9251
9252 // Edit properties via a GUI
9253 bool wxRichTextCell::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
9254 {
9255 // We need to gather common attributes for all selected cells.
9256
9257 wxRichTextTable* table = wxDynamicCast(GetParent(), wxRichTextTable);
9258 bool multipleCells = false;
9259 wxRichTextAttr attr;
9260
9261 if (table && buffer && buffer->GetRichTextCtrl() && buffer->GetRichTextCtrl()->GetSelection().IsValid() &&
9262 buffer->GetRichTextCtrl()->GetSelection().GetContainer() == GetParent())
9263 {
9264 wxRichTextAttr clashingAttr, absentAttr;
9265 const wxRichTextSelection& sel = buffer->GetRichTextCtrl()->GetSelection();
9266 size_t i;
9267 int selectedCellCount = 0;
9268 for (i = 0; i < sel.GetCount(); i++)
9269 {
9270 const wxRichTextRange& range = sel[i];
9271 wxRichTextCell* cell = table->GetCell(range.GetStart());
9272 if (cell)
9273 {
9274 wxRichTextAttr cellStyle = cell->GetAttributes();
9275
9276 CollectStyle(attr, cellStyle, clashingAttr, absentAttr);
9277
9278 selectedCellCount ++;
9279 }
9280 }
9281 multipleCells = selectedCellCount > 1;
9282 }
9283 else
9284 {
9285 attr = GetAttributes();
9286 }
9287
9288 wxString caption;
9289 if (multipleCells)
9290 caption = _("Multiple Cell Properties");
9291 else
9292 caption = _("Cell Properties");
9293
9294 wxRichTextObjectPropertiesDialog cellDlg(this, wxGetTopLevelParent(parent), wxID_ANY, caption);
9295 cellDlg.SetAttributes(attr);
9296
9297 wxRichTextSizePage* sizePage = wxDynamicCast(cellDlg.FindPage(wxCLASSINFO(wxRichTextSizePage)), wxRichTextSizePage);
9298 if (sizePage)
9299 {
9300 // We don't want position and floating controls for a cell.
9301 sizePage->ShowPositionControls(false);
9302 sizePage->ShowFloatingControls(false);
9303 }
9304
9305 if (cellDlg.ShowModal() == wxID_OK)
9306 {
9307 if (multipleCells)
9308 {
9309 const wxRichTextSelection& sel = buffer->GetRichTextCtrl()->GetSelection();
9310 // Apply the style; we interpret indeterminate attributes as 'don't touch this attribute'
9311 // since it may represent clashing attributes across multiple objects.
9312 table->SetCellStyle(sel, attr);
9313 }
9314 else
9315 // For a single object, indeterminate attributes set by the user should be reflected in the
9316 // actual object style, so pass the wxRICHTEXT_SETSTYLE_RESET flag to assign
9317 // the style directly instead of applying (which ignores indeterminate attributes,
9318 // leaving them as they were).
9319 cellDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
9320 return true;
9321 }
9322 else
9323 return false;
9324 }
9325
9326 // The next 2 methods return span values. Note that the default is 1, not 0
9327 int wxRichTextCell::GetColspan() const
9328 {
9329 int span = 1;
9330 if (GetProperties().HasProperty(wxT("colspan")))
9331 {
9332 span = GetProperties().GetPropertyLong(wxT("colspan"));
9333 }
9334
9335 return span;
9336 }
9337
9338 int wxRichTextCell::GetRowspan() const
9339 {
9340 int span = 1;
9341 if (GetProperties().HasProperty(wxT("rowspan")))
9342 {
9343 span = GetProperties().GetPropertyLong(wxT("rowspan"));
9344 }
9345
9346 return span;
9347 }
9348
9349 WX_DEFINE_OBJARRAY(wxRichTextObjectPtrArrayArray)
9350
9351 IMPLEMENT_DYNAMIC_CLASS(wxRichTextTable, wxRichTextBox)
9352
9353 wxRichTextTable::wxRichTextTable(wxRichTextObject* parent): wxRichTextBox(parent)
9354 {
9355 m_rowCount = 0;
9356 m_colCount = 0;
9357 }
9358
9359 // Draws the object.
9360 bool wxRichTextTable::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& range, const wxRichTextSelection& selection, const wxRect& rect, int descent, int style)
9361 {
9362 wxRichTextBox::Draw(dc, context, range, selection, rect, descent, style);
9363
9364 // Now draw the table outline, if any, to ensure there are no breaks caused by
9365 // different-coloured cell dividers overwriting the overall table border.
9366 int colCount = GetColumnCount();
9367 int rowCount = GetRowCount();
9368 int col, row;
9369 for (col = 0; col < colCount; col++)
9370 {
9371 for (row = 0; row < rowCount; row++)
9372 {
9373 if (row == 0 || row == (rowCount-1) || col == 0 || col == (colCount-1))
9374 {
9375 wxRichTextCell* cell = GetCell(row, col);
9376 if (cell && !cell->GetRange().IsOutside(range))
9377 {
9378 wxRect childRect(cell->GetPosition(), cell->GetCachedSize());
9379 wxRichTextAttr attr(cell->GetAttributes());
9380 if (row != 0)
9381 attr.GetTextBoxAttr().GetBorder().GetTop().Reset();
9382 if (row != (rowCount-1))
9383 attr.GetTextBoxAttr().GetBorder().GetBottom().Reset();
9384 if (col != 0)
9385 attr.GetTextBoxAttr().GetBorder().GetLeft().Reset();
9386 if (col != (colCount-1))
9387 attr.GetTextBoxAttr().GetBorder().GetRight().Reset();
9388
9389 if (attr.GetTextBoxAttr().GetBorder().IsValid())
9390 {
9391 wxRect boxRect(cell->GetPosition(), cell->GetCachedSize());
9392 wxRect marginRect = boxRect;
9393 wxRect contentRect, borderRect, paddingRect, outlineRect;
9394
9395 cell->GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
9396 cell->DrawBorder(dc, GetBuffer(), attr.GetTextBoxAttr().GetBorder(), borderRect);
9397 }
9398 }
9399 }
9400 }
9401 }
9402
9403 return true;
9404 }
9405
9406 WX_DECLARE_OBJARRAY(wxRect, wxRichTextRectArray);
9407 WX_DEFINE_OBJARRAY(wxRichTextRectArray);
9408
9409
9410 // Helper function for Layout() that clears the space needed by a cell with rowspan > 1
9411 int GetRowspanDisplacement(const wxRichTextTable* table, int row, int col, int paddingX, const wxArrayInt& colWidths)
9412 {
9413 // If one or more cells above-left of this one has rowspan > 1, the affected cells below it
9414 // will have been hidden and have width 0. As a result they are ignored by the layout algorithm,
9415 // and all cells to their right are effectively shifted left. As a result there's no hole for
9416 // the spanning cell to fill.
9417 // So search back along the current row for hidden cells. However there's also the annoying issue of a
9418 // rowspanning cell that also has colspam. So we can't rely on the rowspanning cell being directly above
9419 // the first hidden one we come to. We also can't rely on a cell being hidden only by one type of span;
9420 // there's nothing to stop a cell being hidden by colspan, and then again hidden from above by rowspan.
9421 // The answer is to look above each hidden cell in turn, which I think covers all bases.
9422 int deltaX = 0;
9423 for (int prevcol = 0; prevcol < col; ++prevcol)
9424 {
9425 if (!table->GetCell(row, prevcol)->IsShown())
9426 {
9427 // We've found a hidden cell. If it's hidden because of colspan to its left, it's
9428 // already been taken into account; but not if there's a rowspanning cell above
9429 for (int prevrow = row-1; prevrow >= 0; --prevrow)
9430 {
9431 wxRichTextCell* cell = table->GetCell(prevrow, prevcol);
9432 if (cell && cell->IsShown())
9433 {
9434 int rowSpan = cell->GetRowspan();
9435 if (rowSpan > 1 && rowSpan > (row-prevrow))
9436 {
9437 // There is a rowspanning cell above above the hidden one, so we need
9438 // to right-shift the index cell by this column's width. Furthermore,
9439 // if the cell also colspans, we need to shift by all affected columns
9440 for (int colSpan = 0; colSpan < cell->GetColspan(); ++colSpan)
9441 deltaX += (colWidths[prevcol+colSpan] + paddingX);
9442 break;
9443 }
9444 }
9445 }
9446 }
9447 }
9448 return deltaX;
9449 }
9450
9451 // Helper function for Layout() that expands any cell with rowspan > 1
9452 void ExpandCellsWithRowspan(const wxRichTextTable* table, int paddingY, int& bottomY, wxDC& dc, wxRichTextDrawingContext& context, const wxRect& availableSpace, int style)
9453 {
9454 // This is called when the table's cell layout is otherwise complete.
9455 // For any cell with rowspan > 1, expand downwards into the row(s) below.
9456
9457 // Start by finding the current 'y' of the top of each row, plus the bottom of the available area for cells.
9458 // Deduce this from the top of a visible cell in the row below. (If none are visible, the row will be invisible anyway and can be ignored.)
9459 const int rowCount = table->GetRowCount();
9460 const int colCount = table->GetColumnCount();
9461 wxArrayInt rowTops;
9462 rowTops.Add(0, rowCount+1);
9463 int row;
9464 for (row = 0; row < rowCount; ++row)
9465 {
9466 for (int column = 0; column < colCount; ++column)
9467 {
9468 wxRichTextCell* cell = table->GetCell(row, column);
9469 if (cell && cell->IsShown())
9470 {
9471 rowTops[row] = cell->GetPosition().y;
9472 break;
9473 }
9474 }
9475 }
9476 rowTops[rowCount] = bottomY + paddingY; // The table bottom, which was passed to us
9477
9478 bool needsRelay = false;
9479
9480 for (row = 0; row < rowCount-1; ++row) // -1 as the bottom row can't rowspan
9481 {
9482 for (int col = 0; col < colCount; ++col)
9483 {
9484 wxRichTextCell* cell = table->GetCell(row, col);
9485 if (cell && cell->IsShown())
9486 {
9487 int span = cell->GetRowspan();
9488 if (span > 1)
9489 {
9490 span = wxMin(span, rowCount-row); // Don't try to span below the table!
9491 if (span < 2)
9492 continue;
9493
9494 int availableHeight = rowTops[row+span] - rowTops[row] - paddingY;
9495 wxSize newSize = wxSize(cell->GetCachedSize().GetWidth(), availableHeight);
9496 wxRect availableCellSpace = wxRect(cell->GetPosition(), newSize);
9497 cell->Invalidate(wxRICHTEXT_ALL);
9498 cell->Layout(dc, context, availableCellSpace, availableSpace, style);
9499 // Ensure there's room in the span to display its contents, else it'll overwrite lower rows
9500 int overhang = cell->GetCachedSize().GetHeight() - availableHeight;
9501 cell->SetCachedSize(newSize);
9502
9503 if (overhang > 0)
9504 {
9505 // There are 3 things to get right:
9506 // 1) The easiest is the rows below the span: they need to be downshifted by the overhang, and so does the table bottom itself
9507 // 2) The rows within the span, including the one holding this cell, need to be deepened by their share of the overhang
9508 // e.g. if rowspan == 3, each row should increase in depth by 1/3rd of the overhang.
9509 // 3) The cell with the rowspan shouldn't be touched in 2); its height will be set to the whole span later.
9510 int deltaY = overhang / span;
9511 int spare = overhang % span;
9512
9513 // Each row in the span needs to by deepened by its share of the overhang (give the first row any spare).
9514 // This is achieved by increasing the value stored in the following row's rowTops
9515 for (int spannedRows = 0; spannedRows < span; ++spannedRows)
9516 {
9517 rowTops[row+spannedRows+1] += ((deltaY * (spannedRows+1)) + (spannedRows == 0 ? spare:0));
9518 }
9519
9520 // Any rows below the span need shifting down
9521 for (int rowsBelow = row + span+1; rowsBelow <= rowCount; ++rowsBelow)
9522 {
9523 rowTops[rowsBelow] += overhang;
9524 }
9525
9526 needsRelay = true;
9527 }
9528 }
9529 }
9530 }
9531 }
9532
9533 if (!needsRelay)
9534 return;
9535
9536 // There were overflowing rowspanning cells, so layout yet again to make the increased row depths show
9537 for (row = 0; row < rowCount; ++row)
9538 {
9539 for (int col = 0; col < colCount; ++col)
9540 {
9541 wxRichTextCell* cell = table->GetCell(row, col);
9542 if (cell && cell->IsShown())
9543 {
9544 wxPoint position(cell->GetPosition().x, rowTops[row]);
9545
9546 // GetRowspan() will usually return 1, but may be greater
9547 wxSize size(cell->GetCachedSize().GetWidth(), rowTops[row + cell->GetRowspan()] - rowTops[row] - paddingY);
9548
9549 wxRect availableCellSpace = wxRect(position, size);
9550 cell->Invalidate(wxRICHTEXT_ALL);
9551 cell->Layout(dc, context, availableCellSpace, availableSpace, style);
9552 cell->SetCachedSize(size);
9553 }
9554 }
9555
9556 bottomY = rowTops[rowCount] - paddingY;
9557 }
9558 }
9559
9560 // Lays the object out. rect is the space available for layout. Often it will
9561 // be the specified overall space for this object, if trying to constrain
9562 // layout to a particular size, or it could be the total space available in the
9563 // parent. rect is the overall size, so we must subtract margins and padding.
9564 // to get the actual available space.
9565 bool wxRichTextTable::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& WXUNUSED(parentRect), int style)
9566 {
9567 SetPosition(rect.GetPosition());
9568
9569 // The meaty bit. Calculate sizes of all cells and rows. Try to use
9570 // minimum size if within alloted size, then divide up remaining size
9571 // between rows/cols.
9572
9573 double scale = 1.0;
9574 wxRichTextBuffer* buffer = GetBuffer();
9575 if (buffer) scale = buffer->GetScale();
9576
9577 wxRect availableSpace = GetAvailableContentArea(dc, context, rect);
9578 wxTextAttrDimensionConverter converter(dc, scale, availableSpace.GetSize());
9579
9580 wxRichTextAttr attr(GetAttributes());
9581 context.ApplyVirtualAttributes(attr, this);
9582
9583 bool tableHasPercentWidth = (attr.GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE);
9584 // If we have no fixed table size, and assuming we're not pushed for
9585 // space, then we don't have to try to stretch the table to fit the contents.
9586 bool stretchToFitTableWidth = tableHasPercentWidth;
9587
9588 int tableWidth = rect.width;
9589 if (attr.GetTextBoxAttr().GetWidth().IsValid() && !tableHasPercentWidth)
9590 {
9591 tableWidth = converter.GetPixels(attr.GetTextBoxAttr().GetWidth());
9592
9593 // Fixed table width, so we do want to stretch columns out if necessary.
9594 stretchToFitTableWidth = true;
9595
9596 // Shouldn't be able to exceed the size passed to this function
9597 tableWidth = wxMin(rect.width, tableWidth);
9598 }
9599
9600 // Get internal padding
9601 int paddingLeft = 0, paddingTop = 0;
9602 if (attr.GetTextBoxAttr().GetPadding().GetLeft().IsValid())
9603 paddingLeft = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetLeft());
9604 if (attr.GetTextBoxAttr().GetPadding().GetTop().IsValid())
9605 paddingTop = converter.GetPixels(attr.GetTextBoxAttr().GetPadding().GetTop());
9606
9607 // Assume that left and top padding are also used for inter-cell padding.
9608 int paddingX = paddingLeft;
9609 int paddingY = paddingTop;
9610
9611 int totalLeftMargin = 0, totalRightMargin = 0, totalTopMargin = 0, totalBottomMargin = 0;
9612 GetTotalMargin(dc, buffer, attr, totalLeftMargin, totalRightMargin, totalTopMargin, totalBottomMargin);
9613
9614 // Internal table width - the area for content
9615 int internalTableWidth = tableWidth - totalLeftMargin - totalRightMargin;
9616
9617 int rowCount = m_cells.GetCount();
9618 if (m_colCount == 0 || rowCount == 0)
9619 {
9620 wxRect overallRect(rect.x, rect.y, totalLeftMargin + totalRightMargin, totalTopMargin + totalBottomMargin);
9621 SetCachedSize(overallRect.GetSize());
9622
9623 // Zero content size
9624 SetMinSize(overallRect.GetSize());
9625 SetMaxSize(GetMinSize());
9626 return true;
9627 }
9628
9629 // The final calculated widths
9630 wxArrayInt colWidths;
9631 colWidths.Add(0, m_colCount);
9632
9633 wxArrayInt absoluteColWidths;
9634 absoluteColWidths.Add(0, m_colCount);
9635
9636 wxArrayInt percentageColWidths;
9637 percentageColWidths.Add(0, m_colCount);
9638 // wxArrayInt percentageColWidthsSpanning(m_colCount);
9639 // These are only relevant when the first column contains spanning information.
9640 // wxArrayInt columnSpans(m_colCount); // Each contains 1 for non-spanning cell, > 1 for spanning cell.
9641 wxArrayInt maxColWidths;
9642 maxColWidths.Add(0, m_colCount);
9643 wxArrayInt minColWidths;
9644 minColWidths.Add(0, m_colCount);
9645
9646 wxSize tableSize(tableWidth, 0);
9647
9648 int i, j, k;
9649
9650 for (i = 0; i < m_colCount; i++)
9651 {
9652 absoluteColWidths[i] = 0;
9653 // absoluteColWidthsSpanning[i] = 0;
9654 percentageColWidths[i] = -1;
9655 // percentageColWidthsSpanning[i] = -1;
9656 colWidths[i] = 0;
9657 maxColWidths[i] = 0;
9658 minColWidths[i] = 0;
9659 // columnSpans[i] = 1;
9660 }
9661
9662 // (0) Determine which cells are visible according to spans
9663 // 1 2 3 4 5
9664 // __________________
9665 // | | | | | 1
9666 // |------| |----|
9667 // |------| | | 2
9668 // |------| | | 3
9669 // |------------------|
9670 // |__________________| 4
9671
9672 // To calculate cell visibility:
9673 // First find all spanning cells. Build an array of span records with start x, y and end x, y.
9674 // Then for each cell, test whether we're within one of those cells, and unless we're at the start of
9675 // that cell, hide the cell.
9676
9677 // We can also use this array to match the size of spanning cells to the grid. Or just do
9678 // this when we iterate through all cells.
9679
9680 // 0.1: add spanning cells to an array
9681 wxRichTextRectArray rectArray;
9682 for (j = 0; j < m_rowCount; j++)
9683 {
9684 for (i = 0; i < m_colCount; i++)
9685 {
9686 wxRichTextCell* cell = GetCell(j, i);
9687 int colSpan = cell->GetColspan();
9688 int rowSpan = cell->GetRowspan();
9689 if (colSpan > 1 || rowSpan > 1)
9690 {
9691 rectArray.Add(wxRect(i, j, colSpan, rowSpan));
9692 }
9693 }
9694 }
9695 // 0.2: find which cells are subsumed by a spanning cell
9696 for (j = 0; j < m_rowCount; j++)
9697 {
9698 for (i = 0; i < m_colCount; i++)
9699 {
9700 wxRichTextCell* cell = GetCell(j, i);
9701 if (rectArray.GetCount() == 0)
9702 {
9703 cell->Show(true);
9704 }
9705 else
9706 {
9707 int colSpan = cell->GetColspan();
9708 int rowSpan = cell->GetRowspan();
9709
9710 if (colSpan > 1 || rowSpan > 1)
9711 {
9712 // Assume all spanning cells are shown
9713 cell->Show(true);
9714 }
9715 else
9716 {
9717 bool shown = true;
9718 for (k = 0; k < (int) rectArray.GetCount(); k++)
9719 {
9720 if (rectArray[k].Contains(wxPoint(i, j)))
9721 {
9722 shown = false;
9723 break;
9724 }
9725 }
9726 cell->Show(shown);
9727 }
9728 }
9729 }
9730 }
9731
9732 // Find the first spanned cell in each row that spans the most columns and doesn't
9733 // overlap with a spanned cell starting at a previous column position.
9734 // This means we need to keep an array of rects so we can check. However
9735 // it does also mean that some spans simply may not be taken into account
9736 // where there are different spans happening on different rows. In these cases,
9737 // they will simply be as wide as their constituent columns.
9738
9739 // (1) Do an initial layout for all cells to get minimum and maximum size, and get
9740 // the absolute or percentage width of each column.
9741
9742 for (j = 0; j < m_rowCount; j++)
9743 {
9744 // First get the overall margins so we can calculate percentage widths based on
9745 // the available content space for all cells on the row
9746
9747 int overallRowContentMargin = 0;
9748 int visibleCellCount = 0;
9749
9750 for (i = 0; i < m_colCount; i++)
9751 {
9752 wxRichTextBox* cell = GetCell(j, i);
9753 if (cell->IsShown())
9754 {
9755 int cellTotalLeftMargin = 0, cellTotalRightMargin = 0, cellTotalTopMargin = 0, cellTotalBottomMargin = 0;
9756 GetTotalMargin(dc, buffer, cell->GetAttributes(), cellTotalLeftMargin, cellTotalRightMargin, cellTotalTopMargin, cellTotalBottomMargin);
9757
9758 overallRowContentMargin += (cellTotalLeftMargin + cellTotalRightMargin);
9759 visibleCellCount ++;
9760 }
9761 }
9762
9763 // Add in inter-cell padding
9764 overallRowContentMargin += ((visibleCellCount-1) * paddingX);
9765
9766 int rowContentWidth = internalTableWidth - overallRowContentMargin;
9767 wxSize rowTableSize(rowContentWidth, 0);
9768 wxTextAttrDimensionConverter converter(dc, scale, rowTableSize);
9769
9770 for (i = 0; i < m_colCount; i++)
9771 {
9772 wxRichTextCell* cell = GetCell(j, i);
9773 if (cell->IsShown())
9774 {
9775 int colSpan = cell->GetColspan();
9776
9777 // Lay out cell to find min/max widths
9778 cell->Invalidate(wxRICHTEXT_ALL);
9779 cell->Layout(dc, context, availableSpace, availableSpace, style);
9780
9781 if (colSpan == 1)
9782 {
9783 int absoluteCellWidth = -1;
9784 int percentageCellWidth = -1;
9785
9786 // I think we need to calculate percentages from the internal table size,
9787 // minus the padding between cells which we'll need to calculate from the
9788 // (number of VISIBLE cells - 1)*paddingX. Then percentages that add up to 100%
9789 // will add up to 100%. In CSS, the width specifies the cell's content rect width,
9790 // so if we want to conform to that we'll need to add in the overall cell margins.
9791 // However, this will make it difficult to specify percentages that add up to
9792 // 100% and still fit within the table width.
9793 // Let's say two cells have 50% width. They have 10 pixels of overall margin each.
9794 // The table content rect is 500 pixels and the inter-cell padding is 20 pixels.
9795 // If we're using internal content size for the width, we would calculate the
9796 // the overall cell width for n cells as:
9797 // (500 - 20*(n-1) - overallCellMargin1 - overallCellMargin2 - ...) * percentage / 100
9798 // + thisOverallCellMargin
9799 // = 500 - 20 - 10 - 10) * 0.5 + 10 = 240 pixels overall cell width.
9800 // Adding this back, we get 240 + 240 + 20 = 500 pixels.
9801
9802 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().IsValid())
9803 {
9804 int w = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetWidth());
9805 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
9806 {
9807 percentageCellWidth = w;
9808 }
9809 else
9810 {
9811 absoluteCellWidth = w;
9812 }
9813 // Override absolute width with minimum width if necessary
9814 if (cell->GetMinSize().x > 0 && absoluteCellWidth !=1 && cell->GetMinSize().x > absoluteCellWidth)
9815 absoluteCellWidth = cell->GetMinSize().x;
9816 }
9817
9818 if (absoluteCellWidth != -1)
9819 {
9820 if (absoluteCellWidth > absoluteColWidths[i])
9821 absoluteColWidths[i] = absoluteCellWidth;
9822 }
9823
9824 if (percentageCellWidth != -1)
9825 {
9826 if (percentageCellWidth > percentageColWidths[i])
9827 percentageColWidths[i] = percentageCellWidth;
9828 }
9829
9830 if (colSpan == 1 && cell->GetMinSize().x && cell->GetMinSize().x > minColWidths[i])
9831 minColWidths[i] = cell->GetMinSize().x;
9832 if (colSpan == 1 && cell->GetMaxSize().x && cell->GetMaxSize().x > maxColWidths[i])
9833 maxColWidths[i] = cell->GetMaxSize().x;
9834 }
9835 }
9836 }
9837 }
9838
9839 // (2) Allocate initial column widths from minimum widths, absolute values and proportions
9840 // TODO: simply merge this into (1).
9841 for (i = 0; i < m_colCount; i++)
9842 {
9843 if (absoluteColWidths[i] > 0)
9844 {
9845 colWidths[i] = absoluteColWidths[i];
9846 }
9847 else if (percentageColWidths[i] > 0)
9848 {
9849 colWidths[i] = percentageColWidths[i];
9850
9851 // This is rubbish - we calculated the absolute widths from percentages, so
9852 // we can't do it again here.
9853 //colWidths[i] = (int) (double(percentageColWidths[i]) * double(tableWidth) / 100.0 + 0.5);
9854 }
9855 }
9856
9857 // (3) Process absolute or proportional widths of spanning columns,
9858 // now that we know what our fixed column widths are going to be.
9859 // Spanned cells will try to adjust columns so the span will fit.
9860 // Even existing fixed column widths can be expanded if necessary.
9861 // Actually, currently fixed columns widths aren't adjusted; instead,
9862 // the algorithm favours earlier rows and adjusts unspecified column widths
9863 // the first time only. After that, we can't know whether the column has been
9864 // specified explicitly or not. (We could make a note if necessary.)
9865 for (j = 0; j < m_rowCount; j++)
9866 {
9867 // First get the overall margins so we can calculate percentage widths based on
9868 // the available content space for all cells on the row
9869
9870 int overallRowContentMargin = 0;
9871 int visibleCellCount = 0;
9872
9873 for (i = 0; i < m_colCount; i++)
9874 {
9875 wxRichTextBox* cell = GetCell(j, i);
9876 if (cell->IsShown())
9877 {
9878 int cellTotalLeftMargin = 0, cellTotalRightMargin = 0, cellTotalTopMargin = 0, cellTotalBottomMargin = 0;
9879 GetTotalMargin(dc, buffer, cell->GetAttributes(), cellTotalLeftMargin, cellTotalRightMargin, cellTotalTopMargin, cellTotalBottomMargin);
9880
9881 overallRowContentMargin += (cellTotalLeftMargin + cellTotalRightMargin);
9882 visibleCellCount ++;
9883 }
9884 }
9885
9886 // Add in inter-cell padding
9887 overallRowContentMargin += ((visibleCellCount-1) * paddingX);
9888
9889 int rowContentWidth = internalTableWidth - overallRowContentMargin;
9890 wxSize rowTableSize(rowContentWidth, 0);
9891 wxTextAttrDimensionConverter converter(dc, scale, rowTableSize);
9892
9893 for (i = 0; i < m_colCount; i++)
9894 {
9895 wxRichTextCell* cell = GetCell(j, i);
9896 if (cell->IsShown())
9897 {
9898 int colSpan = cell->GetColspan();
9899 if (colSpan > 1)
9900 {
9901 int spans = wxMin(colSpan, m_colCount - i);
9902 int cellWidth = 0;
9903 if (spans > 0)
9904 {
9905 if (cell->GetAttributes().GetTextBoxAttr().GetWidth().IsValid())
9906 {
9907 cellWidth = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetWidth());
9908 // Override absolute width with minimum width if necessary
9909 if (cell->GetMinSize().x > 0 && cellWidth !=1 && cell->GetMinSize().x > cellWidth)
9910 cellWidth = cell->GetMinSize().x;
9911 }
9912 else
9913 {
9914 // Do we want to do this? It's the only chance we get to
9915 // use the cell's min/max sizes, so we need to work out
9916 // how we're going to balance the unspecified spanning cell
9917 // width with the possibility more-constrained constituent cell widths.
9918 // Say there's a tiny bitmap giving it a max width of 10 pixels. We
9919 // don't want to constraint all the spanned columns to fit into this cell.
9920 // OK, let's say that if any of the constituent columns don't fit,
9921 // then we simply stop constraining the columns; instead, we'll just fit the spanning
9922 // cells to the columns later.
9923 cellWidth = cell->GetMinSize().x;
9924 if (cell->GetMaxSize().x > cellWidth)
9925 cellWidth = cell->GetMaxSize().x;
9926 }
9927
9928 // Subtract the padding between cells
9929 int spanningWidth = cellWidth;
9930 spanningWidth -= paddingX * (spans-1);
9931
9932 if (spanningWidth > 0)
9933 {
9934 // Now share the spanning width between columns within that span
9935 // TODO: take into account min widths of columns within the span
9936 int spanningWidthLeft = spanningWidth;
9937 int stretchColCount = 0;
9938 for (k = i; k < (i+spans); k++)
9939 {
9940 if (colWidths[k] > 0) // absolute or proportional width has been specified
9941 spanningWidthLeft -= colWidths[k];
9942 else
9943 stretchColCount ++;
9944 }
9945 // Now divide what's left between the remaining columns
9946 int colShare = 0;
9947 if (stretchColCount > 0)
9948 colShare = spanningWidthLeft / stretchColCount;
9949 int colShareRemainder = spanningWidthLeft - (colShare * stretchColCount);
9950
9951 // If fixed-width columns are currently too big, then we'll later
9952 // stretch the spanned cell to fit.
9953
9954 if (spanningWidthLeft > 0)
9955 {
9956 for (k = i; k < (i+spans); k++)
9957 {
9958 if (colWidths[k] <= 0) // absolute or proportional width has not been specified
9959 {
9960 int newWidth = colShare;
9961 if (k == (i+spans-1))
9962 newWidth += colShareRemainder; // ensure all pixels are filled
9963 colWidths[k] = newWidth;
9964 }
9965 }
9966 }
9967 }
9968 }
9969 }
9970 }
9971 }
9972 }
9973
9974 // (4) Next, share any remaining space out between columns that have not yet been calculated.
9975 // TODO: take into account min widths of columns within the span
9976 int tableWidthMinusPadding = internalTableWidth - (m_colCount-1)*paddingX;
9977 int widthLeft = tableWidthMinusPadding;
9978 int stretchColCount = 0;
9979 for (i = 0; i < m_colCount; i++)
9980 {
9981 // Subtract min width from width left, then
9982 // add the colShare to the min width
9983 if (colWidths[i] > 0) // absolute or proportional width has been specified
9984 widthLeft -= colWidths[i];
9985 else
9986 {
9987 if (minColWidths[i] > 0)
9988 widthLeft -= minColWidths[i];
9989
9990 stretchColCount ++;
9991 }
9992 }
9993
9994 // Now divide what's left between the remaining columns
9995 int colShare = 0;
9996 if (stretchColCount > 0)
9997 colShare = widthLeft / stretchColCount;
9998 int colShareRemainder = widthLeft - (colShare * stretchColCount);
9999
10000 // Check we don't have enough space, in which case shrink all columns, overriding
10001 // any absolute/proportional widths
10002 // TODO: actually we would like to divide up the shrinkage according to size.
10003 // How do we calculate the proportions that will achieve this?
10004 // Could first choose an arbitrary value for stretching cells, and then calculate
10005 // factors to multiply each width by.
10006 // TODO: want to record this fact and pass to an iteration that tries e.g. min widths
10007 if (widthLeft < 0 || (stretchToFitTableWidth && (stretchColCount == 0)))
10008 {
10009 colShare = tableWidthMinusPadding / m_colCount;
10010 colShareRemainder = tableWidthMinusPadding - (colShare * m_colCount);
10011 for (i = 0; i < m_colCount; i++)
10012 {
10013 colWidths[i] = 0;
10014 minColWidths[i] = 0;
10015 }
10016 }
10017
10018 // We have to adjust the columns if either we need to shrink the
10019 // table to fit the parent/table width, or we explicitly set the
10020 // table width and need to stretch out the table.
10021 if (widthLeft < 0 || stretchToFitTableWidth)
10022 {
10023 for (i = 0; i < m_colCount; i++)
10024 {
10025 if (colWidths[i] <= 0) // absolute or proportional width has not been specified
10026 {
10027 if (minColWidths[i] > 0)
10028 colWidths[i] = minColWidths[i] + colShare;
10029 else
10030 colWidths[i] = colShare;
10031 if (i == (m_colCount-1))
10032 colWidths[i] += colShareRemainder; // ensure all pixels are filled
10033 }
10034 }
10035 }
10036
10037 // TODO: if spanned cells have no specified or max width, make them the
10038 // as big as the columns they span. Do this for all spanned cells in all
10039 // rows, of course. Size any spanned cells left over at the end - even if they
10040 // have width > 0, make sure they're limited to the appropriate column edge.
10041
10042
10043 /*
10044 Sort out confusion between content width
10045 and overall width later. For now, assume we specify overall width.
10046
10047 So, now we've laid out the table to fit into the given space
10048 and have used specified widths and minimum widths.
10049
10050 Now we need to consider how we will try to take maximum width into account.
10051
10052 */
10053
10054 // (??) TODO: take max width into account
10055
10056 // (6) Lay out all cells again with the current values
10057
10058 int maxRight = 0;
10059 int y = availableSpace.y;
10060 for (j = 0; j < m_rowCount; j++)
10061 {
10062 int x = availableSpace.x; // TODO: take into account centering etc.
10063 int maxCellHeight = 0;
10064 int maxSpecifiedCellHeight = 0;
10065
10066 wxArrayInt actualWidths;
10067 actualWidths.Add(0, m_colCount);
10068
10069 wxTextAttrDimensionConverter converter(dc, scale);
10070 for (i = 0; i < m_colCount; i++)
10071 {
10072 wxRichTextCell* cell = GetCell(j, i);
10073 if (cell->IsShown())
10074 {
10075 // Get max specified cell height
10076 // Don't handle percentages for height
10077 if (cell->GetAttributes().GetTextBoxAttr().GetHeight().IsValid() && cell->GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() != wxTEXT_ATTR_UNITS_PERCENTAGE)
10078 {
10079 int h = converter.GetPixels(cell->GetAttributes().GetTextBoxAttr().GetHeight());
10080 if (h > maxSpecifiedCellHeight)
10081 maxSpecifiedCellHeight = h;
10082 }
10083
10084 if (colWidths[i] > 0) // absolute or proportional width has been specified
10085 {
10086 int colSpan = cell->GetColspan();
10087 wxRect availableCellSpace;
10088
10089 // Take into account spans
10090 if (colSpan > 1)
10091 {
10092 // Calculate the size of this spanning cell from its constituent columns
10093 int xx = 0;
10094 int spans = wxMin(colSpan, m_colCount - i);
10095 for (k = i; k < (i+spans); k++)
10096 {
10097 if (k != i)
10098 xx += paddingX;
10099 xx += colWidths[k];
10100 }
10101 availableCellSpace = wxRect(x, y, xx, -1);
10102 }
10103 else
10104 availableCellSpace = wxRect(x, y, colWidths[i], -1);
10105
10106 // Store actual width so we can force cell to be the appropriate width on the final loop
10107 actualWidths[i] = availableCellSpace.GetWidth();
10108
10109 // We now need to shift right by the width of any rowspanning cells above-left of us
10110 int deltaX = GetRowspanDisplacement(this, j, i, paddingX, colWidths);
10111 availableCellSpace.SetX(availableCellSpace.GetX() + deltaX);
10112
10113 // Lay out cell
10114 cell->Invalidate(wxRICHTEXT_ALL);
10115 cell->Layout(dc, context, availableCellSpace, availableSpace, style);
10116
10117 // TODO: use GetCachedSize().x to compute 'natural' size
10118
10119 x += (availableCellSpace.GetWidth() + paddingX);
10120 if ((cell->GetCachedSize().y > maxCellHeight) && (cell->GetRowspan() < 2))
10121 maxCellHeight = cell->GetCachedSize().y;
10122 }
10123 }
10124 }
10125
10126 maxCellHeight = wxMax(maxCellHeight, maxSpecifiedCellHeight);
10127
10128 for (i = 0; i < m_colCount; i++)
10129 {
10130 wxRichTextCell* cell = GetCell(j, i);
10131 if (cell->IsShown())
10132 {
10133 wxRect availableCellSpace = wxRect(cell->GetPosition(), wxSize(actualWidths[i], maxCellHeight));
10134 // Lay out cell with new height
10135 cell->Invalidate(wxRICHTEXT_ALL);
10136 cell->Layout(dc, context, availableCellSpace, availableSpace, style);
10137
10138 // Make sure the cell size really is the appropriate size,
10139 // not the calculated box size
10140 cell->SetCachedSize(wxSize(actualWidths[i], maxCellHeight));
10141
10142 maxRight = wxMax(maxRight, cell->GetPosition().x + cell->GetCachedSize().x);
10143 }
10144 }
10145
10146 y += maxCellHeight;
10147 if (j < (m_rowCount-1))
10148 y += paddingY;
10149 }
10150
10151 // Finally we need to expand any cell with rowspan > 1. We couldn't earlier; lower rows' heights weren't known
10152 ExpandCellsWithRowspan(this, paddingY, y, dc, context, availableSpace, style);
10153
10154 // We need to add back the margins etc.
10155 {
10156 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
10157 contentRect = wxRect(wxPoint(0, 0), wxSize(maxRight - availableSpace.x, y - availableSpace.y));
10158 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
10159 SetCachedSize(marginRect.GetSize());
10160 }
10161
10162 // TODO: calculate max size
10163 {
10164 SetMaxSize(GetCachedSize());
10165 }
10166
10167 // TODO: calculate min size
10168 {
10169 SetMinSize(GetCachedSize());
10170 }
10171
10172 // TODO: currently we use either a fixed table width or the parent's size.
10173 // We also want to be able to calculate the table width from its content,
10174 // whether using fixed column widths or cell content min/max width.
10175 // Probably need a boolean flag to say whether we need to stretch cells
10176 // to fit the table width, or to simply use min/max cell widths. The
10177 // trouble with this is that if cell widths are not specified, they
10178 // will be tiny; we could use arbitrary defaults but this seems unsatisfactory.
10179 // Anyway, ignoring that problem, we probably need to factor layout into a function
10180 // that can can calculate the maximum unconstrained layout in case table size is
10181 // not specified. Then LayoutToBestSize() can choose to use either parent size to
10182 // constrain Layout(), or the previously-calculated max size to constraint layout.
10183
10184 return true;
10185 }
10186
10187 // Finds the absolute position and row height for the given character position
10188 bool wxRichTextTable::FindPosition(wxDC& dc, wxRichTextDrawingContext& context, long index, wxPoint& pt, int* height, bool forceLineStart)
10189 {
10190 wxRichTextCell* child = GetCell(index+1);
10191 if (child)
10192 {
10193 // Find the position at the start of the child cell, since the table doesn't
10194 // have any caret position of its own.
10195 return child->FindPosition(dc, context, -1, pt, height, forceLineStart);
10196 }
10197 else
10198 return false;
10199 }
10200
10201 // Get the cell at the given character position (in the range of the table).
10202 wxRichTextCell* wxRichTextTable::GetCell(long pos) const
10203 {
10204 int row = 0, col = 0;
10205 if (GetCellRowColumnPosition(pos, row, col))
10206 {
10207 return GetCell(row, col);
10208 }
10209 else
10210 return NULL;
10211 }
10212
10213 // Get the row/column for a given character position
10214 bool wxRichTextTable::GetCellRowColumnPosition(long pos, int& row, int& col) const
10215 {
10216 if (m_colCount == 0 || m_rowCount == 0)
10217 return false;
10218
10219 row = (int) (pos / m_colCount);
10220 col = pos - (row * m_colCount);
10221
10222 wxASSERT(row < m_rowCount && col < m_colCount);
10223
10224 if (row < m_rowCount && col < m_colCount)
10225 return true;
10226 else
10227 return false;
10228 }
10229
10230 // Calculate range, taking row/cell ordering into account instead of relying
10231 // on list ordering.
10232 void wxRichTextTable::CalculateRange(long start, long& end)
10233 {
10234 long current = start;
10235 long lastEnd = current;
10236
10237 if (IsTopLevel())
10238 {
10239 current = 0;
10240 lastEnd = 0;
10241 }
10242
10243 int i, j;
10244 for (i = 0; i < m_rowCount; i++)
10245 {
10246 for (j = 0; j < m_colCount; j++)
10247 {
10248 wxRichTextCell* child = GetCell(i, j);
10249 if (child)
10250 {
10251 long childEnd = 0;
10252
10253 child->CalculateRange(current, childEnd);
10254
10255 lastEnd = childEnd;
10256 current = childEnd + 1;
10257 }
10258 }
10259 }
10260
10261 // A top-level object always has a range of size 1,
10262 // because its children don't count at this level.
10263 end = start;
10264 m_range.SetRange(start, start);
10265
10266 // An object with no children has zero length
10267 if (m_children.GetCount() == 0)
10268 lastEnd --;
10269 m_ownRange.SetRange(0, lastEnd);
10270 }
10271
10272 // Gets the range size.
10273 bool wxRichTextTable::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, wxRichTextDrawingContext& context, int flags, const wxPoint& position, const wxSize& parentSize, wxArrayInt* partialExtents) const
10274 {
10275 return wxRichTextBox::GetRangeSize(range, size, descent, dc, context, flags, position, parentSize, partialExtents);
10276 }
10277
10278 // Deletes content in the given range.
10279 bool wxRichTextTable::DeleteRange(const wxRichTextRange& WXUNUSED(range))
10280 {
10281 // TODO: implement deletion of cells
10282 return true;
10283 }
10284
10285 // Gets any text in this object for the given range.
10286 wxString wxRichTextTable::GetTextForRange(const wxRichTextRange& range) const
10287 {
10288 return wxRichTextBox::GetTextForRange(range);
10289 }
10290
10291 // Copies this object.
10292 void wxRichTextTable::Copy(const wxRichTextTable& obj)
10293 {
10294 wxRichTextBox::Copy(obj);
10295
10296 ClearTable();
10297
10298 m_rowCount = obj.m_rowCount;
10299 m_colCount = obj.m_colCount;
10300
10301 m_cells.Add(wxRichTextObjectPtrArray(), m_rowCount);
10302
10303 int i, j;
10304 for (i = 0; i < m_rowCount; i++)
10305 {
10306 wxRichTextObjectPtrArray& colArray = m_cells[i];
10307 for (j = 0; j < m_colCount; j++)
10308 {
10309 wxRichTextCell* cell = wxDynamicCast(obj.GetCell(i, j)->Clone(), wxRichTextCell);
10310 AppendChild(cell);
10311
10312 colArray.Add(cell);
10313 }
10314 }
10315 }
10316
10317 void wxRichTextTable::ClearTable()
10318 {
10319 m_cells.Clear();
10320 DeleteChildren();
10321 m_rowCount = 0;
10322 m_colCount = 0;
10323 }
10324
10325 bool wxRichTextTable::CreateTable(int rows, int cols)
10326 {
10327 ClearTable();
10328
10329 wxRichTextAttr cellattr;
10330 cellattr.SetTextColour(GetBasicStyle().GetTextColour());
10331
10332 m_rowCount = rows;
10333 m_colCount = cols;
10334
10335 m_cells.Add(wxRichTextObjectPtrArray(), rows);
10336
10337 int i, j;
10338 for (i = 0; i < rows; i++)
10339 {
10340 wxRichTextObjectPtrArray& colArray = m_cells[i];
10341 for (j = 0; j < cols; j++)
10342 {
10343 wxRichTextCell* cell = new wxRichTextCell;
10344 cell->GetAttributes() = cellattr;
10345
10346 AppendChild(cell);
10347 cell->AddParagraph(wxEmptyString);
10348
10349 colArray.Add(cell);
10350 }
10351 }
10352
10353 return true;
10354 }
10355
10356 wxRichTextCell* wxRichTextTable::GetCell(int row, int col) const
10357 {
10358 wxASSERT(row < m_rowCount);
10359 wxASSERT(col < m_colCount);
10360
10361 if (row < m_rowCount && col < m_colCount)
10362 {
10363 wxRichTextObjectPtrArray& colArray = m_cells[row];
10364 wxRichTextObject* obj = colArray[col];
10365 return wxDynamicCast(obj, wxRichTextCell);
10366 }
10367 else
10368 return NULL;
10369 }
10370
10371 // Returns a selection object specifying the selections between start and end character positions.
10372 // For example, a table would deduce what cells (of range length 1) are selected when dragging across the table.
10373 wxRichTextSelection wxRichTextTable::GetSelection(long start, long end) const
10374 {
10375 wxRichTextSelection selection;
10376 selection.SetContainer((wxRichTextTable*) this);
10377
10378 if (start > end)
10379 {
10380 long tmp = end;
10381 end = start;
10382 start = tmp;
10383 }
10384
10385 wxASSERT( start >= 0 && end < (m_colCount * m_rowCount));
10386
10387 if (end >= (m_colCount * m_rowCount))
10388 return selection;
10389
10390 // We need to find the rectangle of cells that is described by the rectangle
10391 // with start, end as the diagonal. Make sure we don't add cells that are
10392 // not currenty visible because they are overlapped by spanning cells.
10393 /*
10394 --------------------------
10395 | 0 | 1 | 2 | 3 | 4 |
10396 --------------------------
10397 | 5 | 6 | 7 | 8 | 9 |
10398 --------------------------
10399 | 10 | 11 | 12 | 13 | 14 |
10400 --------------------------
10401 | 15 | 16 | 17 | 18 | 19 |
10402 --------------------------
10403
10404 Let's say we select 6 -> 18.
10405
10406 Left and right edge cols of rectangle are 1 and 3 inclusive. Find least/greatest to find
10407 which is left and which is right.
10408
10409 Top and bottom edge rows are 1 and 3 inclusive. Again, find least/greatest to find top and bottom.
10410
10411 Now go through rows from 1 to 3 and only add cells that are (a) within above column range
10412 and (b) shown.
10413
10414
10415 */
10416
10417 int leftCol = start - m_colCount * int(start/m_colCount);
10418 int rightCol = end - m_colCount * int(end/m_colCount);
10419
10420 int topRow = int(start/m_colCount);
10421 int bottomRow = int(end/m_colCount);
10422
10423 if (leftCol > rightCol)
10424 {
10425 int tmp = rightCol;
10426 rightCol = leftCol;
10427 leftCol = tmp;
10428 }
10429
10430 if (topRow > bottomRow)
10431 {
10432 int tmp = bottomRow;
10433 bottomRow = topRow;
10434 topRow = tmp;
10435 }
10436
10437 int i, j;
10438 for (i = topRow; i <= bottomRow; i++)
10439 {
10440 for (j = leftCol; j <= rightCol; j++)
10441 {
10442 wxRichTextCell* cell = GetCell(i, j);
10443 if (cell && cell->IsShown())
10444 selection.Add(cell->GetRange());
10445 }
10446 }
10447
10448 return selection;
10449 }
10450
10451 // Sets the attributes for the cells specified by the selection.
10452 bool wxRichTextTable::SetCellStyle(const wxRichTextSelection& selection, const wxRichTextAttr& style, int flags)
10453 {
10454 if (selection.GetContainer() != this)
10455 return false;
10456
10457 wxRichTextBuffer* buffer = GetBuffer();
10458 bool haveControl = (buffer && buffer->GetRichTextCtrl() != NULL);
10459 bool withUndo = haveControl && ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
10460
10461 if (withUndo)
10462 buffer->BeginBatchUndo(_("Set Cell Style"));
10463
10464 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
10465 while (node)
10466 {
10467 wxRichTextCell* cell = wxDynamicCast(node->GetData(), wxRichTextCell);
10468 if (cell && selection.WithinSelection(cell->GetRange().GetStart()))
10469 SetStyle(cell, style, flags);
10470 node = node->GetNext();
10471 }
10472
10473 // Do action, or delay it until end of batch.
10474 if (withUndo)
10475 buffer->EndBatchUndo();
10476
10477 return true;
10478 }
10479
10480 wxPosition wxRichTextTable::GetFocusedCell() const
10481 {
10482 wxPosition position(-1, -1);
10483 const wxRichTextObject* focus = GetBuffer()->GetRichTextCtrl()->GetFocusObject();
10484
10485 for (int row = 0; row < GetRowCount(); ++row)
10486 {
10487 for (int col = 0; col < GetColumnCount(); ++col)
10488 {
10489 if (GetCell(row, col) == focus)
10490 {
10491 position.SetRow(row);
10492 position.SetCol(col);
10493 return position;
10494 }
10495 }
10496 }
10497
10498 return position;
10499 }
10500
10501 int wxRichTextTable::HitTest(wxDC& dc, wxRichTextDrawingContext& context, const wxPoint& pt, long& textPosition, wxRichTextObject** obj, wxRichTextObject** contextObj, int flags)
10502 {
10503 for (int row = 0; row < GetRowCount(); ++row)
10504 {
10505 for (int col = 0; col < GetColumnCount(); ++col)
10506 {
10507 wxRichTextCell* cell = GetCell(row, col);
10508 if (cell->wxRichTextObject::HitTest(dc, context, pt, textPosition, obj, contextObj, flags) != wxRICHTEXT_HITTEST_NONE)
10509 {
10510 return cell->HitTest(dc, context, pt, textPosition, obj, contextObj, flags);
10511 }
10512 }
10513 }
10514
10515 return wxRICHTEXT_HITTEST_NONE;
10516 }
10517
10518 bool wxRichTextTable::DeleteRows(int startRow, int noRows)
10519 {
10520 wxASSERT((startRow + noRows) <= m_rowCount);
10521 if ((startRow + noRows) > m_rowCount)
10522 return false;
10523
10524 wxCHECK_MSG(noRows != m_rowCount, false, "Trying to delete all the cells in a table");
10525
10526 wxRichTextBuffer* buffer = GetBuffer();
10527 wxRichTextCtrl* rtc = buffer->GetRichTextCtrl();
10528
10529 wxPosition position = GetFocusedCell();
10530 int focusCol = position.GetCol();
10531 int focusRow = position.GetRow();
10532 if (focusRow >= startRow && focusRow < (startRow+noRows))
10533 {
10534 // Deleting a focused cell causes a segfault later when laying out, due to GetFocusedObject() returning an invalid object
10535 if ((startRow + noRows) < m_rowCount)
10536 {
10537 // There are more rows after the one(s) to be deleted, so set focus in the first of them
10538 rtc->SetFocusObject(GetCell(startRow + noRows, focusCol));
10539 }
10540 else
10541 {
10542 // Otherwise set focus in the preceding row
10543 rtc->SetFocusObject(GetCell(startRow - 1, focusCol));
10544 }
10545 }
10546
10547 wxRichTextAction* action = NULL;
10548 wxRichTextTable* clone = NULL;
10549 if (!rtc->SuppressingUndo())
10550 {
10551 // Create a clone containing the current state of the table. It will be used to Undo the action
10552 clone = wxStaticCast(this->Clone(), wxRichTextTable);
10553 clone->SetParent(GetParent());
10554 action = new wxRichTextAction(NULL, _("Delete row"), wxRICHTEXT_CHANGE_OBJECT, buffer, this, rtc);
10555 action->SetObject(this);
10556 action->SetPosition(GetRange().GetStart());
10557 }
10558
10559 int i, j;
10560 for (i = startRow; i < (startRow+noRows); i++)
10561 {
10562 wxRichTextObjectPtrArray& colArray = m_cells[startRow];
10563 for (j = 0; j < (int) colArray.GetCount(); j++)
10564 {
10565 wxRichTextObject* cell = colArray[j];
10566 RemoveChild(cell, true);
10567 }
10568
10569 // Keep deleting at the same position, since we move all
10570 // the others up
10571 m_cells.RemoveAt(startRow);
10572 }
10573
10574 m_rowCount = m_rowCount - noRows;
10575
10576 if (!rtc->SuppressingUndo())
10577 {
10578 buffer->SubmitAction(action);
10579 // Finally store the original-state clone; doing so earlier would cause various failures
10580 action->StoreObject(clone);
10581 }
10582
10583 return true;
10584 }
10585
10586 bool wxRichTextTable::DeleteColumns(int startCol, int noCols)
10587 {
10588 wxASSERT((startCol + noCols) <= m_colCount);
10589 if ((startCol + noCols) > m_colCount)
10590 return false;
10591
10592 wxCHECK_MSG(noCols != m_colCount, false, "Trying to delete all the cells in a table");
10593
10594 wxRichTextBuffer* buffer = GetBuffer();
10595 wxRichTextCtrl* rtc = buffer->GetRichTextCtrl();
10596
10597 wxPosition position = GetFocusedCell();
10598 int focusCol = position.GetCol();
10599 int focusRow = position.GetRow();
10600 if (focusCol >= startCol && focusCol < (startCol+noCols))
10601 {
10602 // Deleting a focused cell causes a segfault later when laying out, due to GetFocusedObject() returning an invalid object
10603 if ((startCol + noCols) < m_colCount)
10604 {
10605 // There are more columns after the one(s) to be deleted, so set focus in the first of them
10606 rtc->SetFocusObject(GetCell(focusRow, startCol + noCols));
10607 }
10608 else
10609 {
10610 // Otherwise set focus in the preceding column
10611 rtc->SetFocusObject(GetCell(focusRow, startCol - 1));
10612 }
10613 }
10614
10615 wxRichTextAction* action = NULL;
10616 wxRichTextTable* clone = NULL;
10617 if (!rtc->SuppressingUndo())
10618 {
10619 // Create a clone containing the current state of the table. It will be used to Undo the action
10620 clone = wxStaticCast(this->Clone(), wxRichTextTable);
10621 clone->SetParent(GetParent());
10622 action = new wxRichTextAction(NULL, _("Delete column"), wxRICHTEXT_CHANGE_OBJECT, buffer, this, rtc);
10623 action->SetObject(this);
10624 action->SetPosition(GetRange().GetStart());
10625 }
10626
10627 bool deleteRows = (noCols == m_colCount);
10628
10629 int i, j;
10630 for (i = 0; i < m_rowCount; i++)
10631 {
10632 wxRichTextObjectPtrArray& colArray = m_cells[deleteRows ? 0 : i];
10633 for (j = 0; j < noCols; j++)
10634 {
10635 wxRichTextObject* cell = colArray[startCol];
10636 RemoveChild(cell, true);
10637 colArray.RemoveAt(startCol);
10638 }
10639
10640 if (deleteRows)
10641 m_cells.RemoveAt(0);
10642 }
10643
10644 if (deleteRows)
10645 m_rowCount = 0;
10646 m_colCount = m_colCount - noCols;
10647
10648 if (!rtc->SuppressingUndo())
10649 {
10650 buffer->SubmitAction(action);
10651 // Finally store the original-state clone; doing so earlier would cause various failures
10652 action->StoreObject(clone);
10653 }
10654
10655 return true;
10656 }
10657
10658 bool wxRichTextTable::AddRows(int startRow, int noRows, const wxRichTextAttr& attr)
10659 {
10660 wxASSERT(startRow <= m_rowCount);
10661 if (startRow > m_rowCount)
10662 return false;
10663
10664 wxRichTextBuffer* buffer = GetBuffer();
10665 wxRichTextAction* action = NULL;
10666 wxRichTextTable* clone = NULL;
10667 if (!buffer->GetRichTextCtrl()->SuppressingUndo())
10668 {
10669 // Create a clone containing the current state of the table. It will be used to Undo the action
10670 clone = wxStaticCast(this->Clone(), wxRichTextTable);
10671 clone->SetParent(GetParent());
10672 action = new wxRichTextAction(NULL, _("Add row"), wxRICHTEXT_CHANGE_OBJECT, buffer, this, buffer->GetRichTextCtrl());
10673 action->SetObject(this);
10674 action->SetPosition(GetRange().GetStart());
10675 }
10676
10677 wxRichTextAttr cellattr = attr;
10678 if (!cellattr.GetTextColour().IsOk())
10679 cellattr.SetTextColour(buffer->GetBasicStyle().GetTextColour());
10680
10681 int i, j;
10682 for (i = 0; i < noRows; i++)
10683 {
10684 int idx;
10685 if (startRow == m_rowCount)
10686 {
10687 m_cells.Add(wxRichTextObjectPtrArray());
10688 idx = m_cells.GetCount() - 1;
10689 }
10690 else
10691 {
10692 m_cells.Insert(wxRichTextObjectPtrArray(), startRow+i);
10693 idx = startRow+i;
10694 }
10695
10696 wxRichTextObjectPtrArray& colArray = m_cells[idx];
10697 for (j = 0; j < m_colCount; j++)
10698 {
10699 wxRichTextCell* cell = new wxRichTextCell;
10700 cell->GetAttributes() = cellattr;
10701
10702 AppendChild(cell);
10703 cell->AddParagraph(wxEmptyString);
10704 colArray.Add(cell);
10705 }
10706 }
10707
10708 m_rowCount = m_rowCount + noRows;
10709
10710 if (!buffer->GetRichTextCtrl()->SuppressingUndo())
10711 {
10712 buffer->SubmitAction(action);
10713 // Finally store the original-state clone; doing so earlier would cause various failures
10714 action->StoreObject(clone);
10715 }
10716
10717 return true;
10718 }
10719
10720 bool wxRichTextTable::AddColumns(int startCol, int noCols, const wxRichTextAttr& attr)
10721 {
10722 wxASSERT(startCol <= m_colCount);
10723 if (startCol > m_colCount)
10724 return false;
10725
10726 wxRichTextBuffer* buffer = GetBuffer();
10727 wxRichTextAction* action = NULL;
10728 wxRichTextTable* clone = NULL;
10729 if (!buffer->GetRichTextCtrl()->SuppressingUndo())
10730 {
10731 // Create a clone containing the current state of the table. It will be used to Undo the action
10732 clone = wxStaticCast(this->Clone(), wxRichTextTable);
10733 clone->SetParent(GetParent());
10734 action = new wxRichTextAction(NULL, _("Add column"), wxRICHTEXT_CHANGE_OBJECT, buffer, this, buffer->GetRichTextCtrl());
10735 action->SetObject(this);
10736 action->SetPosition(GetRange().GetStart());
10737 }
10738
10739 wxRichTextAttr cellattr = attr;
10740 if (!cellattr.GetTextColour().IsOk())
10741 cellattr.SetTextColour(buffer->GetBasicStyle().GetTextColour());
10742
10743 int i, j;
10744 for (i = 0; i < m_rowCount; i++)
10745 {
10746 wxRichTextObjectPtrArray& colArray = m_cells[i];
10747 for (j = 0; j < noCols; j++)
10748 {
10749 wxRichTextCell* cell = new wxRichTextCell;
10750 cell->GetAttributes() = cellattr;
10751
10752 AppendChild(cell);
10753 cell->AddParagraph(wxEmptyString);
10754
10755 if (startCol == m_colCount)
10756 colArray.Add(cell);
10757 else
10758 colArray.Insert(cell, startCol+j);
10759 }
10760 }
10761
10762 m_colCount = m_colCount + noCols;
10763
10764 if (!buffer->GetRichTextCtrl()->SuppressingUndo())
10765 {
10766 buffer->SubmitAction(action);
10767 // Finally store the original-state clone; doing so earlier would cause various failures
10768 action->StoreObject(clone);
10769 }
10770
10771 return true;
10772 }
10773
10774 // Edit properties via a GUI
10775 bool wxRichTextTable::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
10776 {
10777 wxRichTextObjectPropertiesDialog boxDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Table Properties"));
10778 boxDlg.SetAttributes(GetAttributes());
10779
10780 if (boxDlg.ShowModal() == wxID_OK)
10781 {
10782 boxDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
10783 return true;
10784 }
10785 else
10786 return false;
10787 }
10788
10789 /*
10790 * Module to initialise and clean up handlers
10791 */
10792
10793 class wxRichTextModule: public wxModule
10794 {
10795 DECLARE_DYNAMIC_CLASS(wxRichTextModule)
10796 public:
10797 wxRichTextModule() {}
10798 bool OnInit()
10799 {
10800 wxRichTextBuffer::SetRenderer(new wxRichTextStdRenderer);
10801 wxRichTextBuffer::InitStandardHandlers();
10802 wxRichTextParagraph::InitDefaultTabs();
10803
10804 wxRichTextXMLHandler::RegisterNodeName(wxT("text"), wxT("wxRichTextPlainText"));
10805 wxRichTextXMLHandler::RegisterNodeName(wxT("symbol"), wxT("wxRichTextPlainText"));
10806 wxRichTextXMLHandler::RegisterNodeName(wxT("image"), wxT("wxRichTextImage"));
10807 wxRichTextXMLHandler::RegisterNodeName(wxT("paragraph"), wxT("wxRichTextParagraph"));
10808 wxRichTextXMLHandler::RegisterNodeName(wxT("paragraphlayout"), wxT("wxRichTextParagraphLayoutBox"));
10809 wxRichTextXMLHandler::RegisterNodeName(wxT("textbox"), wxT("wxRichTextBox"));
10810 wxRichTextXMLHandler::RegisterNodeName(wxT("cell"), wxT("wxRichTextCell"));
10811 wxRichTextXMLHandler::RegisterNodeName(wxT("table"), wxT("wxRichTextTable"));
10812 wxRichTextXMLHandler::RegisterNodeName(wxT("field"), wxT("wxRichTextField"));
10813
10814 return true;
10815 }
10816 void OnExit()
10817 {
10818 wxRichTextBuffer::CleanUpHandlers();
10819 wxRichTextBuffer::CleanUpDrawingHandlers();
10820 wxRichTextBuffer::CleanUpFieldTypes();
10821 wxRichTextXMLHandler::ClearNodeToClassMap();
10822 wxRichTextDecimalToRoman(-1);
10823 wxRichTextParagraph::ClearDefaultTabs();
10824 wxRichTextCtrl::ClearAvailableFontNames();
10825 wxRichTextBuffer::SetRenderer(NULL);
10826 }
10827 };
10828
10829 IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule)
10830
10831
10832 // If the richtext lib is dynamically loaded after the app has already started
10833 // (such as from wxPython) then the built-in module system will not init this
10834 // module. Provide this function to do it manually.
10835 void wxRichTextModuleInit()
10836 {
10837 wxModule* module = new wxRichTextModule;
10838 wxModule::RegisterModule(module);
10839 wxModule::InitializeModules();
10840 }
10841
10842
10843 /*!
10844 * Commands for undo/redo
10845 *
10846 */
10847
10848 wxRichTextCommand::wxRichTextCommand(const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
10849 wxRichTextParagraphLayoutBox* container, wxRichTextCtrl* ctrl, bool ignoreFirstTime): wxCommand(true, name)
10850 {
10851 /* wxRichTextAction* action = */ new wxRichTextAction(this, name, id, buffer, container, ctrl, ignoreFirstTime);
10852 }
10853
10854 wxRichTextCommand::wxRichTextCommand(const wxString& name): wxCommand(true, name)
10855 {
10856 }
10857
10858 wxRichTextCommand::~wxRichTextCommand()
10859 {
10860 ClearActions();
10861 }
10862
10863 void wxRichTextCommand::AddAction(wxRichTextAction* action)
10864 {
10865 if (!m_actions.Member(action))
10866 m_actions.Append(action);
10867 }
10868
10869 bool wxRichTextCommand::Do()
10870 {
10871 for (wxList::compatibility_iterator node = m_actions.GetFirst(); node; node = node->GetNext())
10872 {
10873 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
10874 action->Do();
10875 }
10876
10877 return true;
10878 }
10879
10880 bool wxRichTextCommand::Undo()
10881 {
10882 for (wxList::compatibility_iterator node = m_actions.GetLast(); node; node = node->GetPrevious())
10883 {
10884 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
10885 action->Undo();
10886 }
10887
10888 return true;
10889 }
10890
10891 void wxRichTextCommand::ClearActions()
10892 {
10893 WX_CLEAR_LIST(wxList, m_actions);
10894 }
10895
10896 /*!
10897 * Individual action
10898 *
10899 */
10900
10901 wxRichTextAction::wxRichTextAction(wxRichTextCommand* cmd, const wxString& name, wxRichTextCommandId id,
10902 wxRichTextBuffer* buffer, wxRichTextParagraphLayoutBox* container,
10903 wxRichTextCtrl* ctrl, bool ignoreFirstTime)
10904 {
10905 m_buffer = buffer;
10906 m_object = NULL;
10907 m_containerAddress.Create(buffer, container);
10908 m_ignoreThis = ignoreFirstTime;
10909 m_cmdId = id;
10910 m_position = -1;
10911 m_ctrl = ctrl;
10912 m_name = name;
10913 m_newParagraphs.SetDefaultStyle(buffer->GetDefaultStyle());
10914 m_newParagraphs.SetBasicStyle(buffer->GetBasicStyle());
10915 if (cmd)
10916 cmd->AddAction(this);
10917 }
10918
10919 wxRichTextAction::~wxRichTextAction()
10920 {
10921 if (m_object)
10922 delete m_object;
10923 }
10924
10925 // Returns the container that this action refers to, using the container address and top-level buffer.
10926 wxRichTextParagraphLayoutBox* wxRichTextAction::GetContainer() const
10927 {
10928 wxRichTextParagraphLayoutBox* container = wxDynamicCast(GetContainerAddress().GetObject(m_buffer), wxRichTextParagraphLayoutBox);
10929 return container;
10930 }
10931
10932
10933 void wxRichTextAction::CalculateRefreshOptimizations(wxArrayInt& optimizationLineCharPositions, wxArrayInt& optimizationLineYPositions)
10934 {
10935 // Store a list of line start character and y positions so we can figure out which area
10936 // we need to refresh
10937
10938 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
10939 wxRichTextParagraphLayoutBox* container = GetContainer();
10940 wxASSERT(container != NULL);
10941 if (!container)
10942 return;
10943
10944 // NOTE: we're assuming that the buffer is laid out correctly at this point.
10945 // If we had several actions, which only invalidate and leave layout until the
10946 // paint handler is called, then this might not be true. So we may need to switch
10947 // optimisation on only when we're simply adding text and not simultaneously
10948 // deleting a selection, for example. Or, we make sure the buffer is laid out correctly
10949 // first, but of course this means we'll be doing it twice.
10950 if (!m_buffer->IsDirty() && m_ctrl) // can only do optimisation if the buffer is already laid out correctly
10951 {
10952 wxSize clientSize = m_ctrl->GetUnscaledSize(m_ctrl->GetClientSize());
10953 wxPoint firstVisiblePt = m_ctrl->GetUnscaledPoint(m_ctrl->GetFirstVisiblePoint());
10954 int lastY = firstVisiblePt.y + clientSize.y;
10955
10956 wxRichTextParagraph* para = container->GetParagraphAtPosition(GetRange().GetStart());
10957 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(para);
10958 while (node)
10959 {
10960 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
10961 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
10962 while (node2)
10963 {
10964 wxRichTextLine* line = node2->GetData();
10965 wxPoint pt = line->GetAbsolutePosition();
10966 wxRichTextRange range = line->GetAbsoluteRange();
10967
10968 if (pt.y > lastY)
10969 {
10970 node2 = wxRichTextLineList::compatibility_iterator();
10971 node = wxRichTextObjectList::compatibility_iterator();
10972 }
10973 else if (range.GetStart() > GetPosition() && pt.y >= firstVisiblePt.y)
10974 {
10975 optimizationLineCharPositions.Add(range.GetStart());
10976 optimizationLineYPositions.Add(pt.y);
10977 }
10978
10979 if (node2)
10980 node2 = node2->GetNext();
10981 }
10982
10983 if (node)
10984 node = node->GetNext();
10985 }
10986 }
10987 #endif
10988 }
10989
10990 bool wxRichTextAction::Do()
10991 {
10992 m_buffer->Modify(true);
10993
10994 wxRichTextParagraphLayoutBox* container = GetContainer();
10995 wxASSERT(container != NULL);
10996 if (!container)
10997 return false;
10998
10999 switch (m_cmdId)
11000 {
11001 case wxRICHTEXT_INSERT:
11002 {
11003 // Store a list of line start character and y positions so we can figure out which area
11004 // we need to refresh
11005 wxArrayInt optimizationLineCharPositions;
11006 wxArrayInt optimizationLineYPositions;
11007
11008 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
11009 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
11010 #endif
11011
11012 container->InsertFragment(GetRange().GetStart(), m_newParagraphs);
11013 container->UpdateRanges();
11014
11015 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
11016 // Layout() would stop prematurely at the top level.
11017 container->InvalidateHierarchy(wxRichTextRange(wxMax(0, GetRange().GetStart()-1), GetRange().GetEnd()));
11018
11019 long newCaretPosition = GetPosition() + m_newParagraphs.GetOwnRange().GetLength();
11020
11021 // Character position to caret position
11022 newCaretPosition --;
11023
11024 // Don't take into account the last newline
11025 if (m_newParagraphs.GetPartialParagraph())
11026 newCaretPosition --;
11027 else
11028 if (m_newParagraphs.GetChildren().GetCount() > 1)
11029 {
11030 wxRichTextObject* p = (wxRichTextObject*) m_newParagraphs.GetChildren().GetLast()->GetData();
11031 if (p->GetRange().GetLength() == 1)
11032 newCaretPosition --;
11033 }
11034
11035 newCaretPosition = wxMin(newCaretPosition, (container->GetOwnRange().GetEnd()-1));
11036
11037 UpdateAppearance(newCaretPosition, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions, true /* do */);
11038
11039 wxRichTextEvent cmdEvent(
11040 wxEVT_RICHTEXT_CONTENT_INSERTED,
11041 m_ctrl ? m_ctrl->GetId() : -1);
11042 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
11043 cmdEvent.SetRange(GetRange());
11044 cmdEvent.SetPosition(GetRange().GetStart());
11045 cmdEvent.SetContainer(container);
11046
11047 m_buffer->SendEvent(cmdEvent);
11048
11049 break;
11050 }
11051 case wxRICHTEXT_DELETE:
11052 {
11053 wxArrayInt optimizationLineCharPositions;
11054 wxArrayInt optimizationLineYPositions;
11055
11056 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
11057 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
11058 #endif
11059
11060 container->DeleteRange(GetRange());
11061 container->UpdateRanges();
11062 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
11063 // Layout() would stop prematurely at the top level.
11064 container->InvalidateHierarchy(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
11065
11066 long caretPos = GetRange().GetStart()-1;
11067 if (caretPos >= container->GetOwnRange().GetEnd())
11068 caretPos --;
11069
11070 UpdateAppearance(caretPos, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions, true /* do */);
11071
11072 wxRichTextEvent cmdEvent(
11073 wxEVT_RICHTEXT_CONTENT_DELETED,
11074 m_ctrl ? m_ctrl->GetId() : -1);
11075 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
11076 cmdEvent.SetRange(GetRange());
11077 cmdEvent.SetPosition(GetRange().GetStart());
11078 cmdEvent.SetContainer(container);
11079
11080 m_buffer->SendEvent(cmdEvent);
11081
11082 break;
11083 }
11084 case wxRICHTEXT_CHANGE_STYLE:
11085 case wxRICHTEXT_CHANGE_PROPERTIES:
11086 {
11087 ApplyParagraphs(GetNewParagraphs());
11088
11089 // Invalidate the whole buffer if there were floating objects
11090 if (wxRichTextBuffer::GetFloatingLayoutMode() && container->GetFloatingObjectCount() > 0)
11091 m_buffer->InvalidateHierarchy(wxRICHTEXT_ALL);
11092 else
11093 {
11094 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
11095 // Layout() would stop prematurely at the top level.
11096 container->InvalidateHierarchy(GetRange());
11097 }
11098
11099 UpdateAppearance(GetPosition());
11100
11101 wxRichTextEvent cmdEvent(
11102 m_cmdId == wxRICHTEXT_CHANGE_STYLE ? wxEVT_RICHTEXT_STYLE_CHANGED : wxEVT_RICHTEXT_PROPERTIES_CHANGED,
11103 m_ctrl ? m_ctrl->GetId() : -1);
11104 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
11105 cmdEvent.SetRange(GetRange());
11106 cmdEvent.SetPosition(GetRange().GetStart());
11107 cmdEvent.SetContainer(container);
11108
11109 m_buffer->SendEvent(cmdEvent);
11110
11111 break;
11112 }
11113 case wxRICHTEXT_CHANGE_ATTRIBUTES:
11114 {
11115 wxRichTextObject* obj = m_objectAddress.GetObject(m_buffer); // container->GetChildAtPosition(GetRange().GetStart());
11116 if (obj)
11117 {
11118 wxRichTextAttr oldAttr = obj->GetAttributes();
11119 obj->GetAttributes() = m_attributes;
11120 m_attributes = oldAttr;
11121 }
11122
11123 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
11124 // Layout() would stop prematurely at the top level.
11125 // Invalidate the whole buffer if there were floating objects
11126 if (wxRichTextBuffer::GetFloatingLayoutMode() && container->GetFloatingObjectCount() > 0)
11127 m_buffer->InvalidateHierarchy(wxRICHTEXT_ALL);
11128 else
11129 container->InvalidateHierarchy(GetRange());
11130
11131 UpdateAppearance(GetPosition());
11132
11133 wxRichTextEvent cmdEvent(
11134 wxEVT_RICHTEXT_STYLE_CHANGED,
11135 m_ctrl ? m_ctrl->GetId() : -1);
11136 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
11137 cmdEvent.SetRange(GetRange());
11138 cmdEvent.SetPosition(GetRange().GetStart());
11139 cmdEvent.SetContainer(container);
11140
11141 m_buffer->SendEvent(cmdEvent);
11142
11143 break;
11144 }
11145 case wxRICHTEXT_CHANGE_OBJECT:
11146 {
11147 wxRichTextObject* obj = m_objectAddress.GetObject(m_buffer);
11148 if (obj && m_object && m_ctrl)
11149 {
11150 // The plan is to swap the current object with the stored, previous-state, clone
11151 // We can't get 'node' from the containing buffer (as it doesn't directly store objects)
11152 // so use the parent paragraph
11153 wxRichTextParagraph* para = wxDynamicCast(obj->GetParent(), wxRichTextParagraph);
11154 wxCHECK_MSG(para, false, "Invalid parent paragraph");
11155
11156 // The stored object, m_object, may have a stale parent paragraph. This would cause
11157 // a crash during layout, so use obj's parent para, which should be the correct one.
11158 // (An alternative would be to return the parent too from m_objectAddress.GetObject(),
11159 // or to set obj's parent there before returning)
11160 m_object->SetParent(para);
11161
11162 wxRichTextObjectList::compatibility_iterator node = para->GetChildren().Find(obj);
11163 if (node)
11164 {
11165 wxRichTextObject* obj = node->GetData();
11166 node->SetData(m_object);
11167 m_object = obj;
11168 }
11169 }
11170
11171 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
11172 // Layout() would stop prematurely at the top level.
11173 // Invalidate the whole buffer if there were floating objects
11174 if (wxRichTextBuffer::GetFloatingLayoutMode() && container->GetFloatingObjectCount() > 0)
11175 m_buffer->InvalidateHierarchy(wxRICHTEXT_ALL);
11176 else
11177 container->InvalidateHierarchy(GetRange());
11178
11179 UpdateAppearance(GetPosition());
11180
11181 // TODO: send new kind of modification event
11182
11183 break;
11184 }
11185 default:
11186 break;
11187 }
11188
11189 return true;
11190 }
11191
11192 bool wxRichTextAction::Undo()
11193 {
11194 m_buffer->Modify(true);
11195
11196 wxRichTextParagraphLayoutBox* container = GetContainer();
11197 wxASSERT(container != NULL);
11198 if (!container)
11199 return false;
11200
11201 switch (m_cmdId)
11202 {
11203 case wxRICHTEXT_INSERT:
11204 {
11205 wxArrayInt optimizationLineCharPositions;
11206 wxArrayInt optimizationLineYPositions;
11207
11208 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
11209 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
11210 #endif
11211
11212 container->DeleteRange(GetRange());
11213 container->UpdateRanges();
11214
11215 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
11216 // Layout() would stop prematurely at the top level.
11217 container->InvalidateHierarchy(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
11218
11219 long newCaretPosition = GetPosition() - 1;
11220
11221 UpdateAppearance(newCaretPosition, true, /* send update event */ & optimizationLineCharPositions, & optimizationLineYPositions, false /* undo */);
11222
11223 wxRichTextEvent cmdEvent(
11224 wxEVT_RICHTEXT_CONTENT_DELETED,
11225 m_ctrl ? m_ctrl->GetId() : -1);
11226 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
11227 cmdEvent.SetRange(GetRange());
11228 cmdEvent.SetPosition(GetRange().GetStart());
11229 cmdEvent.SetContainer(container);
11230
11231 m_buffer->SendEvent(cmdEvent);
11232
11233 break;
11234 }
11235 case wxRICHTEXT_DELETE:
11236 {
11237 wxArrayInt optimizationLineCharPositions;
11238 wxArrayInt optimizationLineYPositions;
11239
11240 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
11241 CalculateRefreshOptimizations(optimizationLineCharPositions, optimizationLineYPositions);
11242 #endif
11243
11244 container->InsertFragment(GetRange().GetStart(), m_oldParagraphs);
11245 container->UpdateRanges();
11246
11247 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
11248 // Layout() would stop prematurely at the top level.
11249 container->InvalidateHierarchy(GetRange());
11250
11251 UpdateAppearance(GetPosition(), true, /* send update event */ & optimizationLineCharPositions, & optimizationLineYPositions, false /* undo */);
11252
11253 wxRichTextEvent cmdEvent(
11254 wxEVT_RICHTEXT_CONTENT_INSERTED,
11255 m_ctrl ? m_ctrl->GetId() : -1);
11256 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
11257 cmdEvent.SetRange(GetRange());
11258 cmdEvent.SetPosition(GetRange().GetStart());
11259 cmdEvent.SetContainer(container);
11260
11261 m_buffer->SendEvent(cmdEvent);
11262
11263 break;
11264 }
11265 case wxRICHTEXT_CHANGE_STYLE:
11266 case wxRICHTEXT_CHANGE_PROPERTIES:
11267 {
11268 ApplyParagraphs(GetOldParagraphs());
11269 // InvalidateHierarchy goes up the hierarchy as well as down, otherwise with a nested object,
11270 // Layout() would stop prematurely at the top level.
11271 container->InvalidateHierarchy(GetRange());
11272
11273 UpdateAppearance(GetPosition());
11274
11275 wxRichTextEvent cmdEvent(
11276 m_cmdId == wxRICHTEXT_CHANGE_STYLE ? wxEVT_RICHTEXT_STYLE_CHANGED : wxEVT_RICHTEXT_PROPERTIES_CHANGED,
11277 m_ctrl ? m_ctrl->GetId() : -1);
11278 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
11279 cmdEvent.SetRange(GetRange());
11280 cmdEvent.SetPosition(GetRange().GetStart());
11281 cmdEvent.SetContainer(container);
11282
11283 m_buffer->SendEvent(cmdEvent);
11284
11285 break;
11286 }
11287 case wxRICHTEXT_CHANGE_ATTRIBUTES:
11288 case wxRICHTEXT_CHANGE_OBJECT:
11289 {
11290 return Do();
11291 }
11292 default:
11293 break;
11294 }
11295
11296 return true;
11297 }
11298
11299 /// Update the control appearance
11300 void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent, wxArrayInt* optimizationLineCharPositions, wxArrayInt* optimizationLineYPositions, bool isDoCmd)
11301 {
11302 wxRichTextParagraphLayoutBox* container = GetContainer();
11303 wxASSERT(container != NULL);
11304 if (!container)
11305 return;
11306
11307 if (m_ctrl)
11308 {
11309 m_ctrl->SetFocusObject(container);
11310 m_ctrl->SetCaretPosition(caretPosition);
11311
11312 if (!m_ctrl->IsFrozen())
11313 {
11314 wxRect containerRect = container->GetRect();
11315
11316 m_ctrl->LayoutContent();
11317
11318 // Refresh everything if there were floating objects or the container changed size
11319 // (we can't yet optimize in these cases, since more complex interaction with other content occurs)
11320 if ((wxRichTextBuffer::GetFloatingLayoutMode() && container->GetFloatingObjectCount() > 0) || (container->GetParent() && containerRect != container->GetRect()))
11321 {
11322 m_ctrl->Refresh(false);
11323 }
11324 else
11325
11326 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
11327 // Find refresh rectangle if we are in a position to optimise refresh
11328 if ((m_cmdId == wxRICHTEXT_INSERT || m_cmdId == wxRICHTEXT_DELETE) && optimizationLineCharPositions)
11329 {
11330 size_t i;
11331
11332 wxSize clientSize = m_ctrl->GetUnscaledSize(m_ctrl->GetClientSize());
11333 wxPoint firstVisiblePt = m_ctrl->GetUnscaledPoint(m_ctrl->GetFirstVisiblePoint());
11334
11335 // Start/end positions
11336 int firstY = 0;
11337 int lastY = firstVisiblePt.y + clientSize.y;
11338
11339 bool foundEnd = false;
11340
11341 // position offset - how many characters were inserted
11342 int positionOffset = GetRange().GetLength();
11343
11344 // Determine whether this is Do or Undo, and adjust positionOffset accordingly
11345 if ((m_cmdId == wxRICHTEXT_DELETE && isDoCmd) || (m_cmdId == wxRICHTEXT_INSERT && !isDoCmd))
11346 positionOffset = - positionOffset;
11347
11348 // find the first line which is being drawn at the same position as it was
11349 // before. Since we're talking about a simple insertion, we can assume
11350 // that the rest of the window does not need to be redrawn.
11351 long pos = GetRange().GetStart();
11352
11353 wxRichTextParagraph* para = container->GetParagraphAtPosition(pos, false /* is not caret pos */);
11354 // Since we support floating layout, we should redraw the whole para instead of just
11355 // the first line touching the invalid range.
11356 if (para)
11357 {
11358 // In case something was drawn above the paragraph,
11359 // such as a line break, allow a little extra.
11360 firstY = para->GetPosition().y - 4;
11361 }
11362
11363 wxRichTextObjectList::compatibility_iterator node = container->GetChildren().Find(para);
11364 while (node)
11365 {
11366 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
11367 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
11368 while (node2)
11369 {
11370 wxRichTextLine* line = node2->GetData();
11371 wxPoint pt = line->GetAbsolutePosition();
11372 wxRichTextRange range = line->GetAbsoluteRange();
11373
11374 // we want to find the first line that is in the same position
11375 // as before. This will mean we're at the end of the changed text.
11376
11377 if (pt.y > lastY) // going past the end of the window, no more info
11378 {
11379 node2 = wxRichTextLineList::compatibility_iterator();
11380 node = wxRichTextObjectList::compatibility_iterator();
11381 }
11382 // Detect last line in the buffer
11383 else if (!node2->GetNext() && para->GetRange().Contains(container->GetOwnRange().GetEnd()))
11384 {
11385 // If deleting text, make sure we refresh below as well as above
11386 if (positionOffset >= 0)
11387 {
11388 foundEnd = true;
11389 lastY = pt.y + line->GetSize().y;
11390 }
11391
11392 node2 = wxRichTextLineList::compatibility_iterator();
11393 node = wxRichTextObjectList::compatibility_iterator();
11394
11395 break;
11396 }
11397 else
11398 {
11399 // search for this line being at the same position as before
11400 for (i = 0; i < optimizationLineCharPositions->GetCount(); i++)
11401 {
11402 if (((*optimizationLineCharPositions)[i] + positionOffset == range.GetStart()) &&
11403 ((*optimizationLineYPositions)[i] == pt.y))
11404 {
11405 // Stop, we're now the same as we were
11406 foundEnd = true;
11407
11408 lastY = pt.y + line->GetSize().y;
11409
11410 node2 = wxRichTextLineList::compatibility_iterator();
11411 node = wxRichTextObjectList::compatibility_iterator();
11412
11413 break;
11414 }
11415 }
11416 }
11417
11418 if (node2)
11419 node2 = node2->GetNext();
11420 }
11421
11422 if (node)
11423 node = node->GetNext();
11424 }
11425
11426 firstY = wxMax(firstVisiblePt.y, firstY);
11427 if (!foundEnd)
11428 lastY = firstVisiblePt.y + clientSize.y;
11429
11430 // Convert to device coordinates
11431 wxRect rect(m_ctrl->GetPhysicalPoint(m_ctrl->GetScaledPoint(wxPoint(firstVisiblePt.x, firstY))), m_ctrl->GetScaledSize(wxSize(clientSize.x, lastY - firstY)));
11432 m_ctrl->RefreshRect(rect);
11433 }
11434 else
11435 #endif
11436 m_ctrl->Refresh(false);
11437
11438 m_ctrl->PositionCaret();
11439
11440 // This causes styles to persist when doing programmatic
11441 // content creation except when Freeze/Thaw is used, so
11442 // disable this and check for the consequences.
11443 // m_ctrl->SetDefaultStyleToCursorStyle();
11444
11445 if (sendUpdateEvent)
11446 wxTextCtrl::SendTextUpdatedEvent(m_ctrl);
11447 }
11448 }
11449 }
11450
11451 /// Replace the buffer paragraphs with the new ones.
11452 void wxRichTextAction::ApplyParagraphs(const wxRichTextParagraphLayoutBox& fragment)
11453 {
11454 wxRichTextParagraphLayoutBox* container = GetContainer();
11455 wxASSERT(container != NULL);
11456 if (!container)
11457 return;
11458
11459 wxRichTextObjectList::compatibility_iterator node = fragment.GetChildren().GetFirst();
11460 while (node)
11461 {
11462 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
11463 wxASSERT (para != NULL);
11464
11465 // We'll replace the existing paragraph by finding the paragraph at this position,
11466 // delete its node data, and setting a copy as the new node data.
11467 // TODO: make more efficient by simply swapping old and new paragraph objects.
11468
11469 wxRichTextParagraph* existingPara = container->GetParagraphAtPosition(para->GetRange().GetStart());
11470 if (existingPara)
11471 {
11472 wxRichTextObjectList::compatibility_iterator bufferParaNode = container->GetChildren().Find(existingPara);
11473 if (bufferParaNode)
11474 {
11475 wxRichTextParagraph* newPara = new wxRichTextParagraph(*para);
11476 newPara->SetParent(container);
11477
11478 bufferParaNode->SetData(newPara);
11479
11480 delete existingPara;
11481 }
11482 }
11483
11484 node = node->GetNext();
11485 }
11486 }
11487
11488
11489 /*!
11490 * wxRichTextRange
11491 * This stores beginning and end positions for a range of data.
11492 */
11493
11494 WX_DEFINE_OBJARRAY(wxRichTextRangeArray);
11495
11496 /// Limit this range to be within 'range'
11497 bool wxRichTextRange::LimitTo(const wxRichTextRange& range)
11498 {
11499 if (m_start < range.m_start)
11500 m_start = range.m_start;
11501
11502 if (m_end > range.m_end)
11503 m_end = range.m_end;
11504
11505 return true;
11506 }
11507
11508 /*!
11509 * wxRichTextImage implementation
11510 * This object represents an image.
11511 */
11512
11513 IMPLEMENT_DYNAMIC_CLASS(wxRichTextImage, wxRichTextObject)
11514
11515 wxRichTextImage::wxRichTextImage(const wxImage& image, wxRichTextObject* parent, wxRichTextAttr* charStyle):
11516 wxRichTextObject(parent)
11517 {
11518 Init();
11519 m_imageBlock.MakeImageBlockDefaultQuality(image, wxBITMAP_TYPE_PNG);
11520 if (charStyle)
11521 SetAttributes(*charStyle);
11522 }
11523
11524 wxRichTextImage::wxRichTextImage(const wxRichTextImageBlock& imageBlock, wxRichTextObject* parent, wxRichTextAttr* charStyle):
11525 wxRichTextObject(parent)
11526 {
11527 Init();
11528 m_imageBlock = imageBlock;
11529 if (charStyle)
11530 SetAttributes(*charStyle);
11531 }
11532
11533 wxRichTextImage::~wxRichTextImage()
11534 {
11535 }
11536
11537 void wxRichTextImage::Init()
11538 {
11539 m_originalImageSize = wxSize(-1, -1);
11540 }
11541
11542 /// Create a cached image at the required size
11543 bool wxRichTextImage::LoadImageCache(wxDC& dc, bool resetCache, const wxSize& parentSize)
11544 {
11545 if (!m_imageBlock.IsOk())
11546 return false;
11547
11548 // If we have an original image size, use that to compute the cached bitmap size
11549 // instead of loading the image each time. This way we can avoid loading
11550 // the image so long as the new cached bitmap size hasn't changed.
11551
11552 wxImage image;
11553 if (resetCache || m_originalImageSize.GetWidth() <= 0 || m_originalImageSize.GetHeight() <= 0)
11554 {
11555 m_imageCache = wxNullBitmap;
11556
11557 m_imageBlock.Load(image);
11558 if (!image.IsOk())
11559 return false;
11560
11561 m_originalImageSize = wxSize(image.GetWidth(), image.GetHeight());
11562 }
11563
11564 int width = m_originalImageSize.GetWidth();
11565 int height = m_originalImageSize.GetHeight();
11566
11567 int parentWidth = 0;
11568 int parentHeight = 0;
11569
11570 int maxWidth = -1;
11571 int maxHeight = -1;
11572
11573 wxSize sz = parentSize;
11574 if (sz == wxDefaultSize)
11575 {
11576 if (GetParent() && GetParent()->GetParent())
11577 sz = GetParent()->GetParent()->GetCachedSize();
11578 }
11579
11580 if (sz != wxDefaultSize)
11581 {
11582 wxRichTextBuffer* buffer = GetBuffer();
11583 if (buffer)
11584 {
11585 // Find the actual space available when margin is taken into account
11586 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
11587 marginRect = wxRect(0, 0, sz.x, sz.y);
11588 if (GetParent() && GetParent()->GetParent())
11589 {
11590 buffer->GetBoxRects(dc, buffer, GetParent()->GetParent()->GetAttributes(), marginRect, borderRect, contentRect, paddingRect, outlineRect);
11591 sz = contentRect.GetSize();
11592 }
11593
11594 // Use a minimum size to stop images becoming very small
11595 parentWidth = wxMax(100, sz.GetWidth());
11596 parentHeight = wxMax(100, sz.GetHeight());
11597
11598 if (buffer->GetRichTextCtrl())
11599 // Start with a maximum width of the control size, even if not specified by the content,
11600 // to minimize the amount of picture overlapping the right-hand side
11601 maxWidth = parentWidth;
11602 }
11603 }
11604
11605 if (GetAttributes().GetTextBoxAttr().GetWidth().IsValid() && GetAttributes().GetTextBoxAttr().GetWidth().GetValue() > 0)
11606 {
11607 if (parentWidth > 0 && GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
11608 width = (int) ((GetAttributes().GetTextBoxAttr().GetWidth().GetValue() * parentWidth)/100.0);
11609 else if (GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
11610 width = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetWidth().GetValue());
11611 else if (GetAttributes().GetTextBoxAttr().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
11612 width = GetAttributes().GetTextBoxAttr().GetWidth().GetValue();
11613 }
11614
11615 // Limit to max width
11616
11617 if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().IsValid() && GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue() > 0)
11618 {
11619 int mw = -1;
11620
11621 if (parentWidth > 0 && GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
11622 mw = (int) ((GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue() * parentWidth)/100.0);
11623 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
11624 mw = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue());
11625 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
11626 mw = GetAttributes().GetTextBoxAttr().GetMaxSize().GetWidth().GetValue();
11627
11628 // If we already have a smaller max width due to the constraints of the control size,
11629 // don't use the larger max width.
11630 if (mw != -1 && ((maxWidth == -1) || (mw < maxWidth)))
11631 maxWidth = mw;
11632 }
11633
11634 if (maxWidth > 0 && width > maxWidth)
11635 width = maxWidth;
11636
11637 // Preserve the aspect ratio
11638 if (width != m_originalImageSize.GetWidth())
11639 height = (int) (float(m_originalImageSize.GetHeight()) * (float(width)/float(m_originalImageSize.GetWidth())));
11640
11641 if (GetAttributes().GetTextBoxAttr().GetHeight().IsValid() && GetAttributes().GetTextBoxAttr().GetHeight().GetValue() > 0)
11642 {
11643 if (parentHeight > 0 && GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
11644 height = (int) ((GetAttributes().GetTextBoxAttr().GetHeight().GetValue() * parentHeight)/100.0);
11645 else if (GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
11646 height = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetHeight().GetValue());
11647 else if (GetAttributes().GetTextBoxAttr().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
11648 height = GetAttributes().GetTextBoxAttr().GetHeight().GetValue();
11649
11650 // Preserve the aspect ratio
11651 if (height != m_originalImageSize.GetHeight())
11652 width = (int) (float(m_originalImageSize.GetWidth()) * (float(height)/float(m_originalImageSize.GetHeight())));
11653 }
11654
11655 // Limit to max height
11656
11657 if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().IsValid() && GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue() > 0)
11658 {
11659 if (parentHeight > 0 && GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
11660 maxHeight = (int) ((GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue() * parentHeight)/100.0);
11661 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
11662 maxHeight = ConvertTenthsMMToPixels(dc, GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue());
11663 else if (GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
11664 maxHeight = GetAttributes().GetTextBoxAttr().GetMaxSize().GetHeight().GetValue();
11665 }
11666
11667 if (maxHeight > 0 && height > maxHeight)
11668 {
11669 height = maxHeight;
11670
11671 // Preserve the aspect ratio
11672 if (height != m_originalImageSize.GetHeight())
11673 width = (int) (float(m_originalImageSize.GetWidth()) * (float(height)/float(m_originalImageSize.GetHeight())));
11674 }
11675
11676 // Prevent the use of zero size
11677 width = wxMax(1, width);
11678 height = wxMax(1, height);
11679
11680 if (m_imageCache.IsOk() && m_imageCache.GetWidth() == width && m_imageCache.GetHeight() == height)
11681 {
11682 // Do nothing, we didn't need to change the image cache
11683 }
11684 else
11685 {
11686 if (!image.IsOk())
11687 {
11688 m_imageBlock.Load(image);
11689 if (!image.IsOk())
11690 return false;
11691 }
11692
11693 if (image.GetWidth() == width && image.GetHeight() == height)
11694 m_imageCache = wxBitmap(image);
11695 else
11696 {
11697 // If the original width and height is small, e.g. 400 or below,
11698 // scale up and then down to improve image quality. This can make
11699 // a big difference, with not much performance hit.
11700 int upscaleThreshold = 400;
11701 wxImage img;
11702 if (image.GetWidth() <= upscaleThreshold || image.GetHeight() <= upscaleThreshold)
11703 {
11704 img = image.Scale(image.GetWidth()*2, image.GetHeight()*2);
11705 img.Rescale(width, height, wxIMAGE_QUALITY_HIGH);
11706 }
11707 else
11708 img = image.Scale(width, height, wxIMAGE_QUALITY_HIGH);
11709 m_imageCache = wxBitmap(img);
11710 }
11711 }
11712
11713 return m_imageCache.IsOk();
11714 }
11715
11716 /// Draw the item
11717 bool wxRichTextImage::Draw(wxDC& dc, wxRichTextDrawingContext& context, const wxRichTextRange& WXUNUSED(range), const wxRichTextSelection& selection, const wxRect& rect, int WXUNUSED(descent), int WXUNUSED(style))
11718 {
11719 if (!IsShown())
11720 return true;
11721
11722 // Don't need cached size AFAIK
11723 // wxSize size = GetCachedSize();
11724 if (!LoadImageCache(dc))
11725 return false;
11726
11727 wxRichTextAttr attr(GetAttributes());
11728 context.ApplyVirtualAttributes(attr, this);
11729
11730 DrawBoxAttributes(dc, GetBuffer(), attr, wxRect(rect.GetPosition(), GetCachedSize()));
11731
11732 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
11733 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
11734 marginRect = rect; // outer rectangle, will calculate contentRect
11735 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
11736
11737 dc.DrawBitmap(m_imageCache, contentRect.x, contentRect.y, true);
11738
11739 if (selection.WithinSelection(GetRange().GetStart(), this))
11740 {
11741 wxCheckSetBrush(dc, *wxBLACK_BRUSH);
11742 wxCheckSetPen(dc, *wxBLACK_PEN);
11743 dc.SetLogicalFunction(wxINVERT);
11744 dc.DrawRectangle(contentRect);
11745 dc.SetLogicalFunction(wxCOPY);
11746 }
11747
11748 return true;
11749 }
11750
11751 /// Lay the item out
11752 bool wxRichTextImage::Layout(wxDC& dc, wxRichTextDrawingContext& context, const wxRect& rect, const wxRect& WXUNUSED(parentRect), int WXUNUSED(style))
11753 {
11754 if (!LoadImageCache(dc))
11755 return false;
11756
11757 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
11758 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
11759 contentRect = wxRect(wxPoint(0,0), imageSize);
11760
11761 wxRichTextAttr attr(GetAttributes());
11762 context.ApplyVirtualAttributes(attr, this);
11763
11764 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
11765
11766 wxSize overallSize = marginRect.GetSize();
11767
11768 SetCachedSize(overallSize);
11769 SetMaxSize(overallSize);
11770 SetMinSize(overallSize);
11771 SetPosition(rect.GetPosition());
11772
11773 return true;
11774 }
11775
11776 /// Get/set the object size for the given range. Returns false if the range
11777 /// is invalid for this object.
11778 bool wxRichTextImage::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& WXUNUSED(descent), wxDC& dc, wxRichTextDrawingContext& context, int WXUNUSED(flags), const wxPoint& WXUNUSED(position), const wxSize& parentSize, wxArrayInt* partialExtents) const
11779 {
11780 if (!range.IsWithin(GetRange()))
11781 return false;
11782
11783 if (!((wxRichTextImage*)this)->LoadImageCache(dc, false, parentSize))
11784 {
11785 size.x = 0; size.y = 0;
11786 if (partialExtents)
11787 partialExtents->Add(0);
11788 return false;
11789 }
11790
11791 wxRichTextAttr attr(GetAttributes());
11792 context.ApplyVirtualAttributes(attr, (wxRichTextObject*) this);
11793
11794 wxSize imageSize(m_imageCache.GetWidth(), m_imageCache.GetHeight());
11795 wxRect marginRect, borderRect, contentRect, paddingRect, outlineRect;
11796 contentRect = wxRect(wxPoint(0,0), imageSize);
11797 GetBoxRects(dc, GetBuffer(), attr, marginRect, borderRect, contentRect, paddingRect, outlineRect);
11798
11799 wxSize overallSize = marginRect.GetSize();
11800
11801 if (partialExtents)
11802 partialExtents->Add(overallSize.x);
11803
11804 size = overallSize;
11805
11806 return true;
11807 }
11808
11809 // Get the 'natural' size for an object. For an image, it would be the
11810 // image size.
11811 wxTextAttrSize wxRichTextImage::GetNaturalSize() const
11812 {
11813 wxTextAttrSize size;
11814 if (GetImageCache().IsOk())
11815 {
11816 size.SetWidth(GetImageCache().GetWidth(), wxTEXT_ATTR_UNITS_PIXELS);
11817 size.SetHeight(GetImageCache().GetHeight(), wxTEXT_ATTR_UNITS_PIXELS);
11818 }
11819 return size;
11820 }
11821
11822
11823 /// Copy
11824 void wxRichTextImage::Copy(const wxRichTextImage& obj)
11825 {
11826 wxRichTextObject::Copy(obj);
11827
11828 m_imageBlock = obj.m_imageBlock;
11829 m_originalImageSize = obj.m_originalImageSize;
11830 }
11831
11832 /// Edit properties via a GUI
11833 bool wxRichTextImage::EditProperties(wxWindow* parent, wxRichTextBuffer* buffer)
11834 {
11835 wxRichTextObjectPropertiesDialog imageDlg(this, wxGetTopLevelParent(parent), wxID_ANY, _("Picture Properties"));
11836 imageDlg.SetAttributes(GetAttributes());
11837
11838 if (imageDlg.ShowModal() == wxID_OK)
11839 {
11840 // By passing wxRICHTEXT_SETSTYLE_RESET, indeterminate attributes set by the user will be set as
11841 // indeterminate in the object.
11842 imageDlg.ApplyStyle(buffer->GetRichTextCtrl(), wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_RESET);
11843 return true;
11844 }
11845 else
11846 return false;
11847 }
11848
11849 /*!
11850 * Utilities
11851 *
11852 */
11853
11854 /// Compare two attribute objects
11855 bool wxTextAttrEq(const wxRichTextAttr& attr1, const wxRichTextAttr& attr2)
11856 {
11857 return (attr1 == attr2);
11858 }
11859
11860 /// Compare tabs
11861 bool wxRichTextTabsEq(const wxArrayInt& tabs1, const wxArrayInt& tabs2)
11862 {
11863 if (tabs1.GetCount() != tabs2.GetCount())
11864 return false;
11865
11866 size_t i;
11867 for (i = 0; i < tabs1.GetCount(); i++)
11868 {
11869 if (tabs1[i] != tabs2[i])
11870 return false;
11871 }
11872 return true;
11873 }
11874
11875 bool wxRichTextApplyStyle(wxRichTextAttr& destStyle, const wxRichTextAttr& style, wxRichTextAttr* compareWith)
11876 {
11877 return destStyle.Apply(style, compareWith);
11878 }
11879
11880 // Remove attributes
11881 bool wxRichTextRemoveStyle(wxRichTextAttr& destStyle, const wxRichTextAttr& style)
11882 {
11883 return destStyle.RemoveStyle(style);
11884 }
11885
11886 /// Combine two bitlists, specifying the bits of interest with separate flags.
11887 bool wxRichTextCombineBitlists(int& valueA, int valueB, int& flagsA, int flagsB)
11888 {
11889 return wxRichTextAttr::CombineBitlists(valueA, valueB, flagsA, flagsB);
11890 }
11891
11892 /// Compare two bitlists
11893 bool wxRichTextBitlistsEqPartial(int valueA, int valueB, int flags)
11894 {
11895 return wxRichTextAttr::BitlistsEqPartial(valueA, valueB, flags);
11896 }
11897
11898 /// Split into paragraph and character styles
11899 bool wxRichTextSplitParaCharStyles(const wxRichTextAttr& style, wxRichTextAttr& parStyle, wxRichTextAttr& charStyle)
11900 {
11901 return wxRichTextAttr::SplitParaCharStyles(style, parStyle, charStyle);
11902 }
11903
11904 /// Convert a decimal to Roman numerals
11905 wxString wxRichTextDecimalToRoman(long n)
11906 {
11907 static wxArrayInt decimalNumbers;
11908 static wxArrayString romanNumbers;
11909
11910 // Clean up arrays
11911 if (n == -1)
11912 {
11913 decimalNumbers.Clear();
11914 romanNumbers.Clear();
11915 return wxEmptyString;
11916 }
11917
11918 if (decimalNumbers.GetCount() == 0)
11919 {
11920 #define wxRichTextAddDecRom(n, r) decimalNumbers.Add(n); romanNumbers.Add(r);
11921
11922 wxRichTextAddDecRom(1000, wxT("M"));
11923 wxRichTextAddDecRom(900, wxT("CM"));
11924 wxRichTextAddDecRom(500, wxT("D"));
11925 wxRichTextAddDecRom(400, wxT("CD"));
11926 wxRichTextAddDecRom(100, wxT("C"));
11927 wxRichTextAddDecRom(90, wxT("XC"));
11928 wxRichTextAddDecRom(50, wxT("L"));
11929 wxRichTextAddDecRom(40, wxT("XL"));
11930 wxRichTextAddDecRom(10, wxT("X"));
11931 wxRichTextAddDecRom(9, wxT("IX"));
11932 wxRichTextAddDecRom(5, wxT("V"));
11933 wxRichTextAddDecRom(4, wxT("IV"));
11934 wxRichTextAddDecRom(1, wxT("I"));
11935 }
11936
11937 int i = 0;
11938 wxString roman;
11939
11940 while (n > 0 && i < 13)
11941 {
11942 if (n >= decimalNumbers[i])
11943 {
11944 n -= decimalNumbers[i];
11945 roman += romanNumbers[i];
11946 }
11947 else
11948 {
11949 i ++;
11950 }
11951 }
11952 if (roman.IsEmpty())
11953 roman = wxT("0");
11954 return roman;
11955 }
11956
11957 /*!
11958 * wxRichTextFileHandler
11959 * Base class for file handlers
11960 */
11961
11962 IMPLEMENT_CLASS(wxRichTextFileHandler, wxObject)
11963
11964 #if wxUSE_FFILE && wxUSE_STREAMS
11965 bool wxRichTextFileHandler::LoadFile(wxRichTextBuffer *buffer, const wxString& filename)
11966 {
11967 wxFFileInputStream stream(filename);
11968 if (stream.IsOk())
11969 return LoadFile(buffer, stream);
11970
11971 return false;
11972 }
11973
11974 bool wxRichTextFileHandler::SaveFile(wxRichTextBuffer *buffer, const wxString& filename)
11975 {
11976 wxFFileOutputStream stream(filename);
11977 if (stream.IsOk())
11978 return SaveFile(buffer, stream);
11979
11980 return false;
11981 }
11982 #endif // wxUSE_FFILE && wxUSE_STREAMS
11983
11984 /// Can we handle this filename (if using files)? By default, checks the extension.
11985 bool wxRichTextFileHandler::CanHandle(const wxString& filename) const
11986 {
11987 wxString path, file, ext;
11988 wxFileName::SplitPath(filename, & path, & file, & ext);
11989
11990 return (ext.Lower() == GetExtension());
11991 }
11992
11993 /*!
11994 * wxRichTextTextHandler
11995 * Plain text handler
11996 */
11997
11998 IMPLEMENT_CLASS(wxRichTextPlainTextHandler, wxRichTextFileHandler)
11999
12000 #if wxUSE_STREAMS
12001 bool wxRichTextPlainTextHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream)
12002 {
12003 if (!stream.IsOk())
12004 return false;
12005
12006 wxString str;
12007 int lastCh = 0;
12008
12009 while (!stream.Eof())
12010 {
12011 int ch = stream.GetC();
12012
12013 if (!stream.Eof())
12014 {
12015 if (ch == 10 && lastCh != 13)
12016 str += wxT('\n');
12017
12018 if (ch > 0 && ch != 10)
12019 str += wxChar(ch);
12020
12021 lastCh = ch;
12022 }
12023 }
12024
12025 buffer->ResetAndClearCommands();
12026 buffer->Clear();
12027 buffer->AddParagraphs(str);
12028 buffer->UpdateRanges();
12029
12030 return true;
12031 }
12032
12033 bool wxRichTextPlainTextHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
12034 {
12035 if (!stream.IsOk())
12036 return false;
12037
12038 wxString text = buffer->GetText();
12039
12040 wxString newLine = wxRichTextLineBreakChar;
12041 text.Replace(newLine, wxT("\n"));
12042
12043 wxCharBuffer buf = text.ToAscii();
12044
12045 stream.Write((const char*) buf, text.length());
12046 return true;
12047 }
12048 #endif // wxUSE_STREAMS
12049
12050 /*
12051 * Stores information about an image, in binary in-memory form
12052 */
12053
12054 wxRichTextImageBlock::wxRichTextImageBlock()
12055 {
12056 Init();
12057 }
12058
12059 wxRichTextImageBlock::wxRichTextImageBlock(const wxRichTextImageBlock& block):wxObject()
12060 {
12061 Init();
12062 Copy(block);
12063 }
12064
12065 wxRichTextImageBlock::~wxRichTextImageBlock()
12066 {
12067 wxDELETEA(m_data);
12068 }
12069
12070 void wxRichTextImageBlock::Init()
12071 {
12072 m_data = NULL;
12073 m_dataSize = 0;
12074 m_imageType = wxBITMAP_TYPE_INVALID;
12075 }
12076
12077 void wxRichTextImageBlock::Clear()
12078 {
12079 wxDELETEA(m_data);
12080 m_dataSize = 0;
12081 m_imageType = wxBITMAP_TYPE_INVALID;
12082 }
12083
12084
12085 // Load the original image into a memory block.
12086 // If the image is not a JPEG, we must convert it into a JPEG
12087 // to conserve space.
12088 // If it's not a JPEG we can make use of 'image', already scaled, so we don't have to
12089 // load the image a 2nd time.
12090
12091 bool wxRichTextImageBlock::MakeImageBlock(const wxString& filename, wxBitmapType imageType,
12092 wxImage& image, bool convertToJPEG)
12093 {
12094 m_imageType = imageType;
12095
12096 wxString filenameToRead(filename);
12097 bool removeFile = false;
12098
12099 if (imageType == wxBITMAP_TYPE_INVALID)
12100 return false; // Could not determine image type
12101
12102 if ((imageType != wxBITMAP_TYPE_JPEG) && convertToJPEG)
12103 {
12104 wxString tempFile =
12105 wxFileName::CreateTempFileName(_("image"));
12106
12107 wxASSERT(!tempFile.IsEmpty());
12108
12109 image.SaveFile(tempFile, wxBITMAP_TYPE_JPEG);
12110 filenameToRead = tempFile;
12111 removeFile = true;
12112
12113 m_imageType = wxBITMAP_TYPE_JPEG;
12114 }
12115 wxFile file;
12116 if (!file.Open(filenameToRead))
12117 return false;
12118
12119 m_dataSize = (size_t) file.Length();
12120 file.Close();
12121
12122 if (m_data)
12123 delete[] m_data;
12124 m_data = ReadBlock(filenameToRead, m_dataSize);
12125
12126 if (removeFile)
12127 wxRemoveFile(filenameToRead);
12128
12129 return (m_data != NULL);
12130 }
12131
12132 // Make an image block from the wxImage in the given
12133 // format.
12134 bool wxRichTextImageBlock::MakeImageBlock(wxImage& image, wxBitmapType imageType, int quality)
12135 {
12136 image.SetOption(wxT("quality"), quality);
12137
12138 if (imageType == wxBITMAP_TYPE_INVALID)
12139 return false; // Could not determine image type
12140
12141 return DoMakeImageBlock(image, imageType);
12142 }
12143
12144 // Uses a const wxImage for efficiency, but can't set quality (only relevant for JPEG)
12145 bool wxRichTextImageBlock::MakeImageBlockDefaultQuality(const wxImage& image, wxBitmapType imageType)
12146 {
12147 if (imageType == wxBITMAP_TYPE_INVALID)
12148 return false; // Could not determine image type
12149
12150 return DoMakeImageBlock(image, imageType);
12151 }
12152
12153 // Makes the image block
12154 bool wxRichTextImageBlock::DoMakeImageBlock(const wxImage& image, wxBitmapType imageType)
12155 {
12156 wxMemoryOutputStream memStream;
12157 if (!image.SaveFile(memStream, imageType))
12158 {
12159 return false;
12160 }
12161
12162 unsigned char* block = new unsigned char[memStream.GetSize()];
12163 if (!block)
12164 return false;
12165
12166 if (m_data)
12167 delete[] m_data;
12168 m_data = block;
12169
12170 m_imageType = imageType;
12171 m_dataSize = memStream.GetSize();
12172
12173 memStream.CopyTo(m_data, m_dataSize);
12174
12175 return (m_data != NULL);
12176 }
12177
12178 // Write to a file
12179 bool wxRichTextImageBlock::Write(const wxString& filename)
12180 {
12181 return WriteBlock(filename, m_data, m_dataSize);
12182 }
12183
12184 void wxRichTextImageBlock::Copy(const wxRichTextImageBlock& block)
12185 {
12186 m_imageType = block.m_imageType;
12187 wxDELETEA(m_data);
12188 m_dataSize = block.m_dataSize;
12189 if (m_dataSize == 0)
12190 return;
12191
12192 m_data = new unsigned char[m_dataSize];
12193 unsigned int i;
12194 for (i = 0; i < m_dataSize; i++)
12195 m_data[i] = block.m_data[i];
12196 }
12197
12198 //// Operators
12199 void wxRichTextImageBlock::operator=(const wxRichTextImageBlock& block)
12200 {
12201 Copy(block);
12202 }
12203
12204 // Load a wxImage from the block
12205 bool wxRichTextImageBlock::Load(wxImage& image)
12206 {
12207 if (!m_data)
12208 return false;
12209
12210 // Read in the image.
12211 #if wxUSE_STREAMS
12212 wxMemoryInputStream mstream(m_data, m_dataSize);
12213 bool success = image.LoadFile(mstream, GetImageType());
12214 #else
12215 wxString tempFile = wxFileName::CreateTempFileName(_("image"));
12216 wxASSERT(!tempFile.IsEmpty());
12217
12218 if (!WriteBlock(tempFile, m_data, m_dataSize))
12219 {
12220 return false;
12221 }
12222 success = image.LoadFile(tempFile, GetImageType());
12223 wxRemoveFile(tempFile);
12224 #endif
12225
12226 return success;
12227 }
12228
12229 // Write data in hex to a stream
12230 bool wxRichTextImageBlock::WriteHex(wxOutputStream& stream)
12231 {
12232 if (m_dataSize == 0)
12233 return true;
12234
12235 int bufSize = 100000;
12236 if (int(2*m_dataSize) < bufSize)
12237 bufSize = 2*m_dataSize;
12238 char* buf = new char[bufSize+1];
12239
12240 int left = m_dataSize;
12241 int n, i, j;
12242 j = 0;
12243 while (left > 0)
12244 {
12245 if (left*2 > bufSize)
12246 {
12247 n = bufSize; left -= (bufSize/2);
12248 }
12249 else
12250 {
12251 n = left*2; left = 0;
12252 }
12253
12254 char* b = buf;
12255 for (i = 0; i < (n/2); i++)
12256 {
12257 wxDecToHex(m_data[j], b, b+1);
12258 b += 2; j ++;
12259 }
12260
12261 buf[n] = 0;
12262 stream.Write((const char*) buf, n);
12263 }
12264 delete[] buf;
12265 return true;
12266 }
12267
12268 // Read data in hex from a stream
12269 bool wxRichTextImageBlock::ReadHex(wxInputStream& stream, int length, wxBitmapType imageType)
12270 {
12271 int dataSize = length/2;
12272
12273 if (m_data)
12274 delete[] m_data;
12275
12276 // create a null terminated temporary string:
12277 char str[3];
12278 str[2] = '\0';
12279
12280 m_data = new unsigned char[dataSize];
12281 int i;
12282 for (i = 0; i < dataSize; i ++)
12283 {
12284 str[0] = (char)stream.GetC();
12285 str[1] = (char)stream.GetC();
12286
12287 m_data[i] = (unsigned char)wxHexToDec(str);
12288 }
12289
12290 m_dataSize = dataSize;
12291 m_imageType = imageType;
12292
12293 return true;
12294 }
12295
12296 // Allocate and read from stream as a block of memory
12297 unsigned char* wxRichTextImageBlock::ReadBlock(wxInputStream& stream, size_t size)
12298 {
12299 unsigned char* block = new unsigned char[size];
12300 if (!block)
12301 return NULL;
12302
12303 stream.Read(block, size);
12304
12305 return block;
12306 }
12307
12308 unsigned char* wxRichTextImageBlock::ReadBlock(const wxString& filename, size_t size)
12309 {
12310 wxFileInputStream stream(filename);
12311 if (!stream.IsOk())
12312 return NULL;
12313
12314 return ReadBlock(stream, size);
12315 }
12316
12317 // Write memory block to stream
12318 bool wxRichTextImageBlock::WriteBlock(wxOutputStream& stream, unsigned char* block, size_t size)
12319 {
12320 stream.Write((void*) block, size);
12321 return stream.IsOk();
12322
12323 }
12324
12325 // Write memory block to file
12326 bool wxRichTextImageBlock::WriteBlock(const wxString& filename, unsigned char* block, size_t size)
12327 {
12328 wxFileOutputStream outStream(filename);
12329 if (!outStream.IsOk())
12330 return false;
12331
12332 return WriteBlock(outStream, block, size);
12333 }
12334
12335 // Gets the extension for the block's type
12336 wxString wxRichTextImageBlock::GetExtension() const
12337 {
12338 wxImageHandler* handler = wxImage::FindHandler(GetImageType());
12339 if (handler)
12340 return handler->GetExtension();
12341 else
12342 return wxEmptyString;
12343 }
12344
12345 #if wxUSE_DATAOBJ
12346
12347 /*!
12348 * The data object for a wxRichTextBuffer
12349 */
12350
12351 const wxChar *wxRichTextBufferDataObject::ms_richTextBufferFormatId = wxT("wxRichText");
12352
12353 wxRichTextBufferDataObject::wxRichTextBufferDataObject(wxRichTextBuffer* richTextBuffer)
12354 {
12355 m_richTextBuffer = richTextBuffer;
12356
12357 // this string should uniquely identify our format, but is otherwise
12358 // arbitrary
12359 m_formatRichTextBuffer.SetId(GetRichTextBufferFormatId());
12360
12361 SetFormat(m_formatRichTextBuffer);
12362 }
12363
12364 wxRichTextBufferDataObject::~wxRichTextBufferDataObject()
12365 {
12366 delete m_richTextBuffer;
12367 }
12368
12369 // after a call to this function, the richTextBuffer is owned by the caller and it
12370 // is responsible for deleting it!
12371 wxRichTextBuffer* wxRichTextBufferDataObject::GetRichTextBuffer()
12372 {
12373 wxRichTextBuffer* richTextBuffer = m_richTextBuffer;
12374 m_richTextBuffer = NULL;
12375
12376 return richTextBuffer;
12377 }
12378
12379 wxDataFormat wxRichTextBufferDataObject::GetPreferredFormat(Direction WXUNUSED(dir)) const
12380 {
12381 return m_formatRichTextBuffer;
12382 }
12383
12384 size_t wxRichTextBufferDataObject::GetDataSize() const
12385 {
12386 if (!m_richTextBuffer)
12387 return 0;
12388
12389 wxString bufXML;
12390
12391 {
12392 wxStringOutputStream stream(& bufXML);
12393 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
12394 {
12395 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
12396 return 0;
12397 }
12398 }
12399
12400 #if wxUSE_UNICODE
12401 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
12402 return strlen(buffer) + 1;
12403 #else
12404 return bufXML.Length()+1;
12405 #endif
12406 }
12407
12408 bool wxRichTextBufferDataObject::GetDataHere(void *pBuf) const
12409 {
12410 if (!pBuf || !m_richTextBuffer)
12411 return false;
12412
12413 wxString bufXML;
12414
12415 {
12416 wxStringOutputStream stream(& bufXML);
12417 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
12418 {
12419 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
12420 return 0;
12421 }
12422 }
12423
12424 #if wxUSE_UNICODE
12425 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
12426 size_t len = strlen(buffer);
12427 memcpy((char*) pBuf, (const char*) buffer, len);
12428 ((char*) pBuf)[len] = 0;
12429 #else
12430 size_t len = bufXML.Length();
12431 memcpy((char*) pBuf, (const char*) bufXML.c_str(), len);
12432 ((char*) pBuf)[len] = 0;
12433 #endif
12434
12435 return true;
12436 }
12437
12438 bool wxRichTextBufferDataObject::SetData(size_t WXUNUSED(len), const void *buf)
12439 {
12440 wxDELETE(m_richTextBuffer);
12441
12442 wxString bufXML((const char*) buf, wxConvUTF8);
12443
12444 m_richTextBuffer = new wxRichTextBuffer;
12445
12446 wxStringInputStream stream(bufXML);
12447 if (!m_richTextBuffer->LoadFile(stream, wxRICHTEXT_TYPE_XML))
12448 {
12449 wxLogError(wxT("Could not read the buffer from an XML stream.\nYou may have forgotten to add the XML file handler."));
12450
12451 wxDELETE(m_richTextBuffer);
12452
12453 return false;
12454 }
12455 return true;
12456 }
12457
12458 #endif
12459 // wxUSE_DATAOBJ
12460
12461
12462 /*
12463 * wxRichTextFontTable
12464 * Manages quick access to a pool of fonts for rendering rich text
12465 */
12466
12467 WX_DECLARE_STRING_HASH_MAP_WITH_DECL(wxFont, wxRichTextFontTableHashMap, class WXDLLIMPEXP_RICHTEXT);
12468
12469 class wxRichTextFontTableData: public wxObjectRefData
12470 {
12471 public:
12472 wxRichTextFontTableData() {}
12473
12474 wxFont FindFont(const wxRichTextAttr& fontSpec, double fontScale);
12475
12476 wxRichTextFontTableHashMap m_hashMap;
12477 };
12478
12479 wxFont wxRichTextFontTableData::FindFont(const wxRichTextAttr& fontSpec, double fontScale)
12480 {
12481 wxString facename(fontSpec.GetFontFaceName());
12482
12483 int fontSize = fontSpec.GetFontSize();
12484 if (fontScale != 1.0)
12485 fontSize = (int) ((double(fontSize) * fontScale) + 0.5);
12486
12487 wxString units;
12488 if (fontSpec.HasFontPixelSize() && !fontSpec.HasFontPointSize())
12489 units = wxT("px");
12490 else
12491 units = wxT("pt");
12492 wxString spec = wxString::Format(wxT("%d-%s-%d-%d-%d-%d-%s-%d"),
12493 fontSize, units.c_str(), fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), (int) fontSpec.GetFontUnderlined(), (int) fontSpec.GetFontStrikethrough(),
12494 facename.c_str(), (int) fontSpec.GetFontEncoding());
12495
12496 wxRichTextFontTableHashMap::iterator entry = m_hashMap.find(spec);
12497 if ( entry == m_hashMap.end() )
12498 {
12499 if (fontSpec.HasFontPixelSize() && !fontSpec.HasFontPointSize())
12500 {
12501 wxFont font(wxSize(0, fontSize), wxFONTFAMILY_DEFAULT, fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), fontSpec.GetFontUnderlined(), facename);
12502 if (fontSpec.HasFontStrikethrough() && fontSpec.GetFontStrikethrough())
12503 font.SetStrikethrough(true);
12504 m_hashMap[spec] = font;
12505 return font;
12506 }
12507 else
12508 {
12509 wxFont font(fontSize, wxFONTFAMILY_DEFAULT, fontSpec.GetFontStyle(), fontSpec.GetFontWeight(), fontSpec.GetFontUnderlined(), facename.c_str());
12510 if (fontSpec.HasFontStrikethrough() && fontSpec.GetFontStrikethrough())
12511 font.SetStrikethrough(true);
12512
12513 m_hashMap[spec] = font;
12514 return font;
12515 }
12516 }
12517 else
12518 {
12519 return entry->second;
12520 }
12521 }
12522
12523 IMPLEMENT_DYNAMIC_CLASS(wxRichTextFontTable, wxObject)
12524
12525 wxRichTextFontTable::wxRichTextFontTable()
12526 {
12527 m_refData = new wxRichTextFontTableData;
12528 m_fontScale = 1.0;
12529 }
12530
12531 wxRichTextFontTable::wxRichTextFontTable(const wxRichTextFontTable& table)
12532 : wxObject()
12533 {
12534 (*this) = table;
12535 }
12536
12537 wxRichTextFontTable::~wxRichTextFontTable()
12538 {
12539 UnRef();
12540 }
12541
12542 bool wxRichTextFontTable::operator == (const wxRichTextFontTable& table) const
12543 {
12544 return (m_refData == table.m_refData);
12545 }
12546
12547 void wxRichTextFontTable::operator= (const wxRichTextFontTable& table)
12548 {
12549 Ref(table);
12550 m_fontScale = table.m_fontScale;
12551 }
12552
12553 wxFont wxRichTextFontTable::FindFont(const wxRichTextAttr& fontSpec)
12554 {
12555 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
12556 if (data)
12557 return data->FindFont(fontSpec, m_fontScale);
12558 else
12559 return wxFont();
12560 }
12561
12562 void wxRichTextFontTable::Clear()
12563 {
12564 wxRichTextFontTableData* data = (wxRichTextFontTableData*) m_refData;
12565 if (data)
12566 data->m_hashMap.clear();
12567 }
12568
12569 void wxRichTextFontTable::SetFontScale(double fontScale)
12570 {
12571 if (fontScale != m_fontScale)
12572 Clear();
12573 m_fontScale = fontScale;
12574 }
12575
12576 // wxTextBoxAttr
12577
12578 void wxTextBoxAttr::Reset()
12579 {
12580 m_flags = 0;
12581 m_floatMode = wxTEXT_BOX_ATTR_FLOAT_NONE;
12582 m_clearMode = wxTEXT_BOX_ATTR_CLEAR_NONE;
12583 m_collapseMode = wxTEXT_BOX_ATTR_COLLAPSE_NONE;
12584 m_verticalAlignment = wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT_NONE;
12585 m_boxStyleName = wxEmptyString;
12586
12587 m_margins.Reset();
12588 m_padding.Reset();
12589 m_position.Reset();
12590
12591 m_size.Reset();
12592 m_minSize.Reset();
12593 m_maxSize.Reset();
12594
12595 m_border.Reset();
12596 m_outline.Reset();
12597 }
12598
12599 // Equality test
12600 bool wxTextBoxAttr::operator== (const wxTextBoxAttr& attr) const
12601 {
12602 return (
12603 m_flags == attr.m_flags &&
12604 m_floatMode == attr.m_floatMode &&
12605 m_clearMode == attr.m_clearMode &&
12606 m_collapseMode == attr.m_collapseMode &&
12607 m_verticalAlignment == attr.m_verticalAlignment &&
12608
12609 m_margins == attr.m_margins &&
12610 m_padding == attr.m_padding &&
12611 m_position == attr.m_position &&
12612
12613 m_size == attr.m_size &&
12614 m_minSize == attr.m_minSize &&
12615 m_maxSize == attr.m_maxSize &&
12616
12617 m_border == attr.m_border &&
12618 m_outline == attr.m_outline &&
12619
12620 m_boxStyleName == attr.m_boxStyleName
12621 );
12622 }
12623
12624 // Partial equality test
12625 bool wxTextBoxAttr::EqPartial(const wxTextBoxAttr& attr, bool weakTest) const
12626 {
12627 if (!weakTest &&
12628 ((!HasFloatMode() && attr.HasFloatMode()) ||
12629 (!HasClearMode() && attr.HasClearMode()) ||
12630 (!HasCollapseBorders() && attr.HasCollapseBorders()) ||
12631 (!HasVerticalAlignment() && attr.HasVerticalAlignment()) ||
12632 (!HasBoxStyleName() && attr.HasBoxStyleName())))
12633 {
12634 return false;
12635 }
12636 if (attr.HasFloatMode() && HasFloatMode() && (GetFloatMode() != attr.GetFloatMode()))
12637 return false;
12638
12639 if (attr.HasClearMode() && HasClearMode() && (GetClearMode() != attr.GetClearMode()))
12640 return false;
12641
12642 if (attr.HasCollapseBorders() && HasCollapseBorders() && (attr.GetCollapseBorders() != GetCollapseBorders()))
12643 return false;
12644
12645 if (attr.HasVerticalAlignment() && HasVerticalAlignment() && (attr.GetVerticalAlignment() != GetVerticalAlignment()))
12646 return false;
12647
12648 if (attr.HasBoxStyleName() && HasBoxStyleName() && (attr.GetBoxStyleName() != GetBoxStyleName()))
12649 return false;
12650
12651 // Position
12652
12653 if (!m_position.EqPartial(attr.m_position, weakTest))
12654 return false;
12655
12656 // Size
12657
12658 if (!m_size.EqPartial(attr.m_size, weakTest))
12659 return false;
12660 if (!m_minSize.EqPartial(attr.m_minSize, weakTest))
12661 return false;
12662 if (!m_maxSize.EqPartial(attr.m_maxSize, weakTest))
12663 return false;
12664
12665 // Margins
12666
12667 if (!m_margins.EqPartial(attr.m_margins, weakTest))
12668 return false;
12669
12670 // Padding
12671
12672 if (!m_padding.EqPartial(attr.m_padding, weakTest))
12673 return false;
12674
12675 // Border
12676
12677 if (!GetBorder().EqPartial(attr.GetBorder(), weakTest))
12678 return false;
12679
12680 // Outline
12681
12682 if (!GetOutline().EqPartial(attr.GetOutline(), weakTest))
12683 return false;
12684
12685 return true;
12686 }
12687
12688 // Merges the given attributes. If compareWith
12689 // is non-NULL, then it will be used to mask out those attributes that are the same in style
12690 // and compareWith, for situations where we don't want to explicitly set inherited attributes.
12691 bool wxTextBoxAttr::Apply(const wxTextBoxAttr& attr, const wxTextBoxAttr* compareWith)
12692 {
12693 if (attr.HasFloatMode())
12694 {
12695 if (!(compareWith && compareWith->HasFloatMode() && compareWith->GetFloatMode() == attr.GetFloatMode()))
12696 SetFloatMode(attr.GetFloatMode());
12697 }
12698
12699 if (attr.HasClearMode())
12700 {
12701 if (!(compareWith && compareWith->HasClearMode() && compareWith->GetClearMode() == attr.GetClearMode()))
12702 SetClearMode(attr.GetClearMode());
12703 }
12704
12705 if (attr.HasCollapseBorders())
12706 {
12707 if (!(compareWith && compareWith->HasCollapseBorders() && compareWith->GetCollapseBorders() == attr.GetCollapseBorders()))
12708 SetCollapseBorders(attr.GetCollapseBorders());
12709 }
12710
12711 if (attr.HasVerticalAlignment())
12712 {
12713 if (!(compareWith && compareWith->HasVerticalAlignment() && compareWith->GetVerticalAlignment() == attr.GetVerticalAlignment()))
12714 SetVerticalAlignment(attr.GetVerticalAlignment());
12715 }
12716
12717 if (attr.HasBoxStyleName())
12718 {
12719 if (!(compareWith && compareWith->HasBoxStyleName() && compareWith->GetBoxStyleName() == attr.GetBoxStyleName()))
12720 SetBoxStyleName(attr.GetBoxStyleName());
12721 }
12722
12723 m_margins.Apply(attr.m_margins, compareWith ? (& attr.m_margins) : (const wxTextAttrDimensions*) NULL);
12724 m_padding.Apply(attr.m_padding, compareWith ? (& attr.m_padding) : (const wxTextAttrDimensions*) NULL);
12725 m_position.Apply(attr.m_position, compareWith ? (& attr.m_position) : (const wxTextAttrDimensions*) NULL);
12726
12727 m_size.Apply(attr.m_size, compareWith ? (& attr.m_size) : (const wxTextAttrSize*) NULL);
12728 m_minSize.Apply(attr.m_minSize, compareWith ? (& attr.m_minSize) : (const wxTextAttrSize*) NULL);
12729 m_maxSize.Apply(attr.m_maxSize, compareWith ? (& attr.m_maxSize) : (const wxTextAttrSize*) NULL);
12730
12731 m_border.Apply(attr.m_border, compareWith ? (& attr.m_border) : (const wxTextAttrBorders*) NULL);
12732 m_outline.Apply(attr.m_outline, compareWith ? (& attr.m_outline) : (const wxTextAttrBorders*) NULL);
12733
12734 return true;
12735 }
12736
12737 // Remove specified attributes from this object
12738 bool wxTextBoxAttr::RemoveStyle(const wxTextBoxAttr& attr)
12739 {
12740 if (attr.HasFloatMode())
12741 RemoveFlag(wxTEXT_BOX_ATTR_FLOAT);
12742
12743 if (attr.HasClearMode())
12744 RemoveFlag(wxTEXT_BOX_ATTR_CLEAR);
12745
12746 if (attr.HasCollapseBorders())
12747 RemoveFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
12748
12749 if (attr.HasVerticalAlignment())
12750 RemoveFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
12751
12752 if (attr.HasBoxStyleName())
12753 {
12754 SetBoxStyleName(wxEmptyString);
12755 RemoveFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
12756 }
12757
12758 m_margins.RemoveStyle(attr.m_margins);
12759 m_padding.RemoveStyle(attr.m_padding);
12760 m_position.RemoveStyle(attr.m_position);
12761
12762 m_size.RemoveStyle(attr.m_size);
12763 m_minSize.RemoveStyle(attr.m_minSize);
12764 m_maxSize.RemoveStyle(attr.m_maxSize);
12765
12766 m_border.RemoveStyle(attr.m_border);
12767 m_outline.RemoveStyle(attr.m_outline);
12768
12769 return true;
12770 }
12771
12772 // Collects the attributes that are common to a range of content, building up a note of
12773 // which attributes are absent in some objects and which clash in some objects.
12774 void wxTextBoxAttr::CollectCommonAttributes(const wxTextBoxAttr& attr, wxTextBoxAttr& clashingAttr, wxTextBoxAttr& absentAttr)
12775 {
12776 if (attr.HasFloatMode())
12777 {
12778 if (!clashingAttr.HasFloatMode() && !absentAttr.HasFloatMode())
12779 {
12780 if (HasFloatMode())
12781 {
12782 if (GetFloatMode() != attr.GetFloatMode())
12783 {
12784 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_FLOAT);
12785 RemoveFlag(wxTEXT_BOX_ATTR_FLOAT);
12786 }
12787 }
12788 else
12789 SetFloatMode(attr.GetFloatMode());
12790 }
12791 }
12792 else
12793 absentAttr.AddFlag(wxTEXT_BOX_ATTR_FLOAT);
12794
12795 if (attr.HasClearMode())
12796 {
12797 if (!clashingAttr.HasClearMode() && !absentAttr.HasClearMode())
12798 {
12799 if (HasClearMode())
12800 {
12801 if (GetClearMode() != attr.GetClearMode())
12802 {
12803 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_CLEAR);
12804 RemoveFlag(wxTEXT_BOX_ATTR_CLEAR);
12805 }
12806 }
12807 else
12808 SetClearMode(attr.GetClearMode());
12809 }
12810 }
12811 else
12812 absentAttr.AddFlag(wxTEXT_BOX_ATTR_CLEAR);
12813
12814 if (attr.HasCollapseBorders())
12815 {
12816 if (!clashingAttr.HasCollapseBorders() && !absentAttr.HasCollapseBorders())
12817 {
12818 if (HasCollapseBorders())
12819 {
12820 if (GetCollapseBorders() != attr.GetCollapseBorders())
12821 {
12822 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
12823 RemoveFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
12824 }
12825 }
12826 else
12827 SetCollapseBorders(attr.GetCollapseBorders());
12828 }
12829 }
12830 else
12831 absentAttr.AddFlag(wxTEXT_BOX_ATTR_COLLAPSE_BORDERS);
12832
12833 if (attr.HasVerticalAlignment())
12834 {
12835 if (!clashingAttr.HasVerticalAlignment() && !absentAttr.HasVerticalAlignment())
12836 {
12837 if (HasVerticalAlignment())
12838 {
12839 if (GetVerticalAlignment() != attr.GetVerticalAlignment())
12840 {
12841 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
12842 RemoveFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
12843 }
12844 }
12845 else
12846 SetVerticalAlignment(attr.GetVerticalAlignment());
12847 }
12848 }
12849 else
12850 absentAttr.AddFlag(wxTEXT_BOX_ATTR_VERTICAL_ALIGNMENT);
12851
12852 if (attr.HasBoxStyleName())
12853 {
12854 if (!clashingAttr.HasBoxStyleName() && !absentAttr.HasBoxStyleName())
12855 {
12856 if (HasBoxStyleName())
12857 {
12858 if (GetBoxStyleName() != attr.GetBoxStyleName())
12859 {
12860 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
12861 RemoveFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
12862 }
12863 }
12864 else
12865 SetBoxStyleName(attr.GetBoxStyleName());
12866 }
12867 }
12868 else
12869 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BOX_STYLE_NAME);
12870
12871 m_margins.CollectCommonAttributes(attr.m_margins, clashingAttr.m_margins, absentAttr.m_margins);
12872 m_padding.CollectCommonAttributes(attr.m_padding, clashingAttr.m_padding, absentAttr.m_padding);
12873 m_position.CollectCommonAttributes(attr.m_position, clashingAttr.m_position, absentAttr.m_position);
12874
12875 m_size.CollectCommonAttributes(attr.m_size, clashingAttr.m_size, absentAttr.m_size);
12876 m_minSize.CollectCommonAttributes(attr.m_minSize, clashingAttr.m_minSize, absentAttr.m_minSize);
12877 m_maxSize.CollectCommonAttributes(attr.m_maxSize, clashingAttr.m_maxSize, absentAttr.m_maxSize);
12878
12879 m_border.CollectCommonAttributes(attr.m_border, clashingAttr.m_border, absentAttr.m_border);
12880 m_outline.CollectCommonAttributes(attr.m_outline, clashingAttr.m_outline, absentAttr.m_outline);
12881 }
12882
12883 bool wxTextBoxAttr::IsDefault() const
12884 {
12885 return GetFlags() == 0 && !m_border.IsValid() && !m_outline.IsValid() &&
12886 !m_size.IsValid() && !m_minSize.IsValid() && !m_maxSize.IsValid() &&
12887 !m_position.IsValid() && !m_padding.IsValid() && !m_margins.IsValid();
12888 }
12889
12890 // wxRichTextAttr
12891
12892 void wxRichTextAttr::Copy(const wxRichTextAttr& attr)
12893 {
12894 wxTextAttr::Copy(attr);
12895
12896 m_textBoxAttr = attr.m_textBoxAttr;
12897 }
12898
12899 bool wxRichTextAttr::operator==(const wxRichTextAttr& attr) const
12900 {
12901 if (!(wxTextAttr::operator==(attr)))
12902 return false;
12903
12904 return (m_textBoxAttr == attr.m_textBoxAttr);
12905 }
12906
12907 // Partial equality test
12908 bool wxRichTextAttr::EqPartial(const wxRichTextAttr& attr, bool weakTest) const
12909 {
12910 if (!(wxTextAttr::EqPartial(attr, weakTest)))
12911 return false;
12912
12913 return m_textBoxAttr.EqPartial(attr.m_textBoxAttr, weakTest);
12914 }
12915
12916 // Merges the given attributes. If compareWith
12917 // is non-NULL, then it will be used to mask out those attributes that are the same in style
12918 // and compareWith, for situations where we don't want to explicitly set inherited attributes.
12919 bool wxRichTextAttr::Apply(const wxRichTextAttr& style, const wxRichTextAttr* compareWith)
12920 {
12921 wxTextAttr::Apply(style, compareWith);
12922
12923 return m_textBoxAttr.Apply(style.m_textBoxAttr, compareWith ? (& compareWith->m_textBoxAttr) : (const wxTextBoxAttr*) NULL);
12924 }
12925
12926 // Remove specified attributes from this object
12927 bool wxRichTextAttr::RemoveStyle(const wxRichTextAttr& attr)
12928 {
12929 wxTextAttr::RemoveStyle(*this, attr);
12930
12931 return m_textBoxAttr.RemoveStyle(attr.m_textBoxAttr);
12932 }
12933
12934 // Collects the attributes that are common to a range of content, building up a note of
12935 // which attributes are absent in some objects and which clash in some objects.
12936 void wxRichTextAttr::CollectCommonAttributes(const wxRichTextAttr& attr, wxRichTextAttr& clashingAttr, wxRichTextAttr& absentAttr)
12937 {
12938 wxTextAttrCollectCommonAttributes(*this, attr, clashingAttr, absentAttr);
12939
12940 m_textBoxAttr.CollectCommonAttributes(attr.m_textBoxAttr, clashingAttr.m_textBoxAttr, absentAttr.m_textBoxAttr);
12941 }
12942
12943 // Partial equality test
12944 bool wxTextAttrBorder::EqPartial(const wxTextAttrBorder& border, bool weakTest) const
12945 {
12946 if (!weakTest &&
12947 ((!HasStyle() && border.HasStyle()) ||
12948 (!HasColour() && border.HasColour()) ||
12949 (!HasWidth() && border.HasWidth())))
12950 {
12951 return false;
12952 }
12953
12954 if (border.HasStyle() && HasStyle() && (border.GetStyle() != GetStyle()))
12955 return false;
12956
12957 if (border.HasColour() && HasColour() && (border.GetColourLong() != GetColourLong()))
12958 return false;
12959
12960 if (border.HasWidth() && HasWidth() && !(border.GetWidth() == GetWidth()))
12961 return false;
12962
12963 return true;
12964 }
12965
12966 // Apply border to 'this', but not if the same as compareWith
12967 bool wxTextAttrBorder::Apply(const wxTextAttrBorder& border, const wxTextAttrBorder* compareWith)
12968 {
12969 if (border.HasStyle())
12970 {
12971 if (!(compareWith && (border.GetStyle() == compareWith->GetStyle())))
12972 SetStyle(border.GetStyle());
12973 }
12974 if (border.HasColour())
12975 {
12976 if (!(compareWith && (border.GetColourLong() == compareWith->GetColourLong())))
12977 SetColour(border.GetColourLong());
12978 }
12979 if (border.HasWidth())
12980 {
12981 if (!(compareWith && (border.GetWidth() == compareWith->GetWidth())))
12982 SetWidth(border.GetWidth());
12983 }
12984
12985 return true;
12986 }
12987
12988 // Remove specified attributes from this object
12989 bool wxTextAttrBorder::RemoveStyle(const wxTextAttrBorder& attr)
12990 {
12991 if (attr.HasStyle() && HasStyle())
12992 SetFlags(GetFlags() & ~wxTEXT_BOX_ATTR_BORDER_STYLE);
12993 if (attr.HasColour() && HasColour())
12994 SetFlags(GetFlags() & ~wxTEXT_BOX_ATTR_BORDER_COLOUR);
12995 if (attr.HasWidth() && HasWidth())
12996 m_borderWidth.Reset();
12997
12998 return true;
12999 }
13000
13001 // Collects the attributes that are common to a range of content, building up a note of
13002 // which attributes are absent in some objects and which clash in some objects.
13003 void wxTextAttrBorder::CollectCommonAttributes(const wxTextAttrBorder& attr, wxTextAttrBorder& clashingAttr, wxTextAttrBorder& absentAttr)
13004 {
13005 if (attr.HasStyle())
13006 {
13007 if (!clashingAttr.HasStyle() && !absentAttr.HasStyle())
13008 {
13009 if (HasStyle())
13010 {
13011 if (GetStyle() != attr.GetStyle())
13012 {
13013 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
13014 RemoveFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
13015 }
13016 }
13017 else
13018 SetStyle(attr.GetStyle());
13019 }
13020 }
13021 else
13022 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_STYLE);
13023
13024 if (attr.HasColour())
13025 {
13026 if (!clashingAttr.HasColour() && !absentAttr.HasColour())
13027 {
13028 if (HasColour())
13029 {
13030 if (GetColour() != attr.GetColour())
13031 {
13032 clashingAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
13033 RemoveFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
13034 }
13035 }
13036 else
13037 SetColour(attr.GetColourLong());
13038 }
13039 }
13040 else
13041 absentAttr.AddFlag(wxTEXT_BOX_ATTR_BORDER_COLOUR);
13042
13043 m_borderWidth.CollectCommonAttributes(attr.m_borderWidth, clashingAttr.m_borderWidth, absentAttr.m_borderWidth);
13044 }
13045
13046 // Partial equality test
13047 bool wxTextAttrBorders::EqPartial(const wxTextAttrBorders& borders, bool weakTest) const
13048 {
13049 return m_left.EqPartial(borders.m_left, weakTest) && m_right.EqPartial(borders.m_right, weakTest) &&
13050 m_top.EqPartial(borders.m_top, weakTest) && m_bottom.EqPartial(borders.m_bottom, weakTest);
13051 }
13052
13053 // Apply border to 'this', but not if the same as compareWith
13054 bool wxTextAttrBorders::Apply(const wxTextAttrBorders& borders, const wxTextAttrBorders* compareWith)
13055 {
13056 m_left.Apply(borders.m_left, compareWith ? (& compareWith->m_left) : (const wxTextAttrBorder*) NULL);
13057 m_right.Apply(borders.m_right, compareWith ? (& compareWith->m_right) : (const wxTextAttrBorder*) NULL);
13058 m_top.Apply(borders.m_top, compareWith ? (& compareWith->m_top) : (const wxTextAttrBorder*) NULL);
13059 m_bottom.Apply(borders.m_bottom, compareWith ? (& compareWith->m_bottom) : (const wxTextAttrBorder*) NULL);
13060 return true;
13061 }
13062
13063 // Remove specified attributes from this object
13064 bool wxTextAttrBorders::RemoveStyle(const wxTextAttrBorders& attr)
13065 {
13066 m_left.RemoveStyle(attr.m_left);
13067 m_right.RemoveStyle(attr.m_right);
13068 m_top.RemoveStyle(attr.m_top);
13069 m_bottom.RemoveStyle(attr.m_bottom);
13070 return true;
13071 }
13072
13073 // Collects the attributes that are common to a range of content, building up a note of
13074 // which attributes are absent in some objects and which clash in some objects.
13075 void wxTextAttrBorders::CollectCommonAttributes(const wxTextAttrBorders& attr, wxTextAttrBorders& clashingAttr, wxTextAttrBorders& absentAttr)
13076 {
13077 m_left.CollectCommonAttributes(attr.m_left, clashingAttr.m_left, absentAttr.m_left);
13078 m_right.CollectCommonAttributes(attr.m_right, clashingAttr.m_right, absentAttr.m_right);
13079 m_top.CollectCommonAttributes(attr.m_top, clashingAttr.m_top, absentAttr.m_top);
13080 m_bottom.CollectCommonAttributes(attr.m_bottom, clashingAttr.m_bottom, absentAttr.m_bottom);
13081 }
13082
13083 // Set style of all borders
13084 void wxTextAttrBorders::SetStyle(int style)
13085 {
13086 m_left.SetStyle(style);
13087 m_right.SetStyle(style);
13088 m_top.SetStyle(style);
13089 m_bottom.SetStyle(style);
13090 }
13091
13092 // Set colour of all borders
13093 void wxTextAttrBorders::SetColour(unsigned long colour)
13094 {
13095 m_left.SetColour(colour);
13096 m_right.SetColour(colour);
13097 m_top.SetColour(colour);
13098 m_bottom.SetColour(colour);
13099 }
13100
13101 void wxTextAttrBorders::SetColour(const wxColour& colour)
13102 {
13103 m_left.SetColour(colour);
13104 m_right.SetColour(colour);
13105 m_top.SetColour(colour);
13106 m_bottom.SetColour(colour);
13107 }
13108
13109 // Set width of all borders
13110 void wxTextAttrBorders::SetWidth(const wxTextAttrDimension& width)
13111 {
13112 m_left.SetWidth(width);
13113 m_right.SetWidth(width);
13114 m_top.SetWidth(width);
13115 m_bottom.SetWidth(width);
13116 }
13117
13118 // Partial equality test
13119 bool wxTextAttrDimension::EqPartial(const wxTextAttrDimension& dim, bool weakTest) const
13120 {
13121 if (!weakTest && !IsValid() && dim.IsValid())
13122 return false;
13123
13124 if (dim.IsValid() && IsValid() && !((*this) == dim))
13125 return false;
13126 else
13127 return true;
13128 }
13129
13130 bool wxTextAttrDimension::Apply(const wxTextAttrDimension& dim, const wxTextAttrDimension* compareWith)
13131 {
13132 if (dim.IsValid())
13133 {
13134 if (!(compareWith && dim == (*compareWith)))
13135 (*this) = dim;
13136 }
13137
13138 return true;
13139 }
13140
13141 // Collects the attributes that are common to a range of content, building up a note of
13142 // which attributes are absent in some objects and which clash in some objects.
13143 void wxTextAttrDimension::CollectCommonAttributes(const wxTextAttrDimension& attr, wxTextAttrDimension& clashingAttr, wxTextAttrDimension& absentAttr)
13144 {
13145 if (attr.IsValid())
13146 {
13147 if (!clashingAttr.IsValid() && !absentAttr.IsValid())
13148 {
13149 if (IsValid())
13150 {
13151 if (!((*this) == attr))
13152 {
13153 clashingAttr.SetValid(true);
13154 SetValid(false);
13155 }
13156 }
13157 else
13158 (*this) = attr;
13159 }
13160 }
13161 else
13162 absentAttr.SetValid(true);
13163 }
13164
13165 wxTextAttrDimensionConverter::wxTextAttrDimensionConverter(wxDC& dc, double scale, const wxSize& parentSize)
13166 {
13167 m_ppi = dc.GetPPI().x; m_scale = scale; m_parentSize = parentSize;
13168 }
13169
13170 wxTextAttrDimensionConverter::wxTextAttrDimensionConverter(int ppi, double scale, const wxSize& parentSize)
13171 {
13172 m_ppi = ppi; m_scale = scale; m_parentSize = parentSize;
13173 }
13174
13175 int wxTextAttrDimensionConverter::ConvertTenthsMMToPixels(int units) const
13176 {
13177 return wxRichTextObject::ConvertTenthsMMToPixels(m_ppi, units, m_scale);
13178 }
13179
13180 int wxTextAttrDimensionConverter::ConvertPixelsToTenthsMM(int pixels) const
13181 {
13182 return wxRichTextObject::ConvertPixelsToTenthsMM(m_ppi, pixels, m_scale);
13183 }
13184
13185 int wxTextAttrDimensionConverter::GetPixels(const wxTextAttrDimension& dim, int direction) const
13186 {
13187 if (dim.GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
13188 return ConvertTenthsMMToPixels(dim.GetValue());
13189 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
13190 return dim.GetValue();
13191 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PERCENTAGE)
13192 {
13193 wxASSERT(m_parentSize != wxDefaultSize);
13194 if (direction == wxHORIZONTAL)
13195 return (int) (double(m_parentSize.x) * double(dim.GetValue()) / 100.0);
13196 else
13197 return (int) (double(m_parentSize.y) * double(dim.GetValue()) / 100.0);
13198 }
13199 else
13200 {
13201 wxASSERT(false);
13202 return 0;
13203 }
13204 }
13205
13206 int wxTextAttrDimensionConverter::GetTenthsMM(const wxTextAttrDimension& dim) const
13207 {
13208 if (dim.GetUnits() == wxTEXT_ATTR_UNITS_TENTHS_MM)
13209 return dim.GetValue();
13210 else if (dim.GetUnits() == wxTEXT_ATTR_UNITS_PIXELS)
13211 return ConvertPixelsToTenthsMM(dim.GetValue());
13212 else
13213 {
13214 wxASSERT(false);
13215 return 0;
13216 }
13217 }
13218
13219 // Partial equality test
13220 bool wxTextAttrDimensions::EqPartial(const wxTextAttrDimensions& dims, bool weakTest) const
13221 {
13222 if (!m_left.EqPartial(dims.m_left, weakTest))
13223 return false;
13224
13225 if (!m_right.EqPartial(dims.m_right, weakTest))
13226 return false;
13227
13228 if (!m_top.EqPartial(dims.m_top, weakTest))
13229 return false;
13230
13231 if (!m_bottom.EqPartial(dims.m_bottom, weakTest))
13232 return false;
13233
13234 return true;
13235 }
13236
13237 // Apply border to 'this', but not if the same as compareWith
13238 bool wxTextAttrDimensions::Apply(const wxTextAttrDimensions& dims, const wxTextAttrDimensions* compareWith)
13239 {
13240 m_left.Apply(dims.m_left, compareWith ? (& compareWith->m_left) : (const wxTextAttrDimension*) NULL);
13241 m_right.Apply(dims.m_right, compareWith ? (& compareWith->m_right): (const wxTextAttrDimension*) NULL);
13242 m_top.Apply(dims.m_top, compareWith ? (& compareWith->m_top): (const wxTextAttrDimension*) NULL);
13243 m_bottom.Apply(dims.m_bottom, compareWith ? (& compareWith->m_bottom): (const wxTextAttrDimension*) NULL);
13244
13245 return true;
13246 }
13247
13248 // Remove specified attributes from this object
13249 bool wxTextAttrDimensions::RemoveStyle(const wxTextAttrDimensions& attr)
13250 {
13251 if (attr.m_left.IsValid())
13252 m_left.Reset();
13253 if (attr.m_right.IsValid())
13254 m_right.Reset();
13255 if (attr.m_top.IsValid())
13256 m_top.Reset();
13257 if (attr.m_bottom.IsValid())
13258 m_bottom.Reset();
13259
13260 return true;
13261 }
13262
13263 // Collects the attributes that are common to a range of content, building up a note of
13264 // which attributes are absent in some objects and which clash in some objects.
13265 void wxTextAttrDimensions::CollectCommonAttributes(const wxTextAttrDimensions& attr, wxTextAttrDimensions& clashingAttr, wxTextAttrDimensions& absentAttr)
13266 {
13267 m_left.CollectCommonAttributes(attr.m_left, clashingAttr.m_left, absentAttr.m_left);
13268 m_right.CollectCommonAttributes(attr.m_right, clashingAttr.m_right, absentAttr.m_right);
13269 m_top.CollectCommonAttributes(attr.m_top, clashingAttr.m_top, absentAttr.m_top);
13270 m_bottom.CollectCommonAttributes(attr.m_bottom, clashingAttr.m_bottom, absentAttr.m_bottom);
13271 }
13272
13273 // Partial equality test
13274 bool wxTextAttrSize::EqPartial(const wxTextAttrSize& size, bool weakTest) const
13275 {
13276 if (!m_width.EqPartial(size.m_width, weakTest))
13277 return false;
13278
13279 if (!m_height.EqPartial(size.m_height, weakTest))
13280 return false;
13281
13282 return true;
13283 }
13284
13285 // Apply border to 'this', but not if the same as compareWith
13286 bool wxTextAttrSize::Apply(const wxTextAttrSize& size, const wxTextAttrSize* compareWith)
13287 {
13288 m_width.Apply(size.m_width, compareWith ? (& compareWith->m_width) : (const wxTextAttrDimension*) NULL);
13289 m_height.Apply(size.m_height, compareWith ? (& compareWith->m_height): (const wxTextAttrDimension*) NULL);
13290
13291 return true;
13292 }
13293
13294 // Remove specified attributes from this object
13295 bool wxTextAttrSize::RemoveStyle(const wxTextAttrSize& attr)
13296 {
13297 if (attr.m_width.IsValid())
13298 m_width.Reset();
13299 if (attr.m_height.IsValid())
13300 m_height.Reset();
13301
13302 return true;
13303 }
13304
13305 // Collects the attributes that are common to a range of content, building up a note of
13306 // which attributes are absent in some objects and which clash in some objects.
13307 void wxTextAttrSize::CollectCommonAttributes(const wxTextAttrSize& attr, wxTextAttrSize& clashingAttr, wxTextAttrSize& absentAttr)
13308 {
13309 m_width.CollectCommonAttributes(attr.m_width, clashingAttr.m_width, absentAttr.m_width);
13310 m_height.CollectCommonAttributes(attr.m_height, clashingAttr.m_height, absentAttr.m_height);
13311 }
13312
13313 // Collects the attributes that are common to a range of content, building up a note of
13314 // which attributes are absent in some objects and which clash in some objects.
13315 void wxTextAttrCollectCommonAttributes(wxTextAttr& currentStyle, const wxTextAttr& attr, wxTextAttr& clashingAttr, wxTextAttr& absentAttr)
13316 {
13317 absentAttr.SetFlags(absentAttr.GetFlags() | (~attr.GetFlags() & wxTEXT_ATTR_ALL));
13318 absentAttr.SetTextEffectFlags(absentAttr.GetTextEffectFlags() | (~attr.GetTextEffectFlags() & 0xFFFF));
13319
13320 long forbiddenFlags = clashingAttr.GetFlags()|absentAttr.GetFlags();
13321
13322 // If different font size units are being used, this is a clash.
13323 if (((attr.GetFlags() & wxTEXT_ATTR_FONT_SIZE) | (currentStyle.GetFlags() & wxTEXT_ATTR_FONT_SIZE)) == wxTEXT_ATTR_FONT_SIZE)
13324 {
13325 currentStyle.SetFontSize(0);
13326 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_SIZE);
13327 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_SIZE);
13328 }
13329 else
13330 {
13331 if (attr.HasFontPointSize() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_POINT_SIZE))
13332 {
13333 if (currentStyle.HasFontPointSize())
13334 {
13335 if (currentStyle.GetFontSize() != attr.GetFontSize())
13336 {
13337 // Clash of attr - mark as such
13338 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_POINT_SIZE);
13339 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_POINT_SIZE);
13340 }
13341 }
13342 else
13343 currentStyle.SetFontSize(attr.GetFontSize());
13344 }
13345 else if (!attr.HasFontPointSize() && currentStyle.HasFontPointSize())
13346 {
13347 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_POINT_SIZE);
13348 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_POINT_SIZE);
13349 }
13350
13351 if (attr.HasFontPixelSize() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_PIXEL_SIZE))
13352 {
13353 if (currentStyle.HasFontPixelSize())
13354 {
13355 if (currentStyle.GetFontSize() != attr.GetFontSize())
13356 {
13357 // Clash of attr - mark as such
13358 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_PIXEL_SIZE);
13359 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_PIXEL_SIZE);
13360 }
13361 }
13362 else
13363 currentStyle.SetFontPixelSize(attr.GetFontSize());
13364 }
13365 else if (!attr.HasFontPixelSize() && currentStyle.HasFontPixelSize())
13366 {
13367 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_PIXEL_SIZE);
13368 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_PIXEL_SIZE);
13369 }
13370 }
13371
13372 if (attr.HasFontItalic() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_ITALIC))
13373 {
13374 if (currentStyle.HasFontItalic())
13375 {
13376 if (currentStyle.GetFontStyle() != attr.GetFontStyle())
13377 {
13378 // Clash of attr - mark as such
13379 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_ITALIC);
13380 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_ITALIC);
13381 }
13382 }
13383 else
13384 currentStyle.SetFontStyle(attr.GetFontStyle());
13385 }
13386 else if (!attr.HasFontItalic() && currentStyle.HasFontItalic())
13387 {
13388 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_ITALIC);
13389 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_ITALIC);
13390 }
13391
13392 if (attr.HasFontFamily() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_FAMILY))
13393 {
13394 if (currentStyle.HasFontFamily())
13395 {
13396 if (currentStyle.GetFontFamily() != attr.GetFontFamily())
13397 {
13398 // Clash of attr - mark as such
13399 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FAMILY);
13400 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FAMILY);
13401 }
13402 }
13403 else
13404 currentStyle.SetFontFamily(attr.GetFontFamily());
13405 }
13406 else if (!attr.HasFontFamily() && currentStyle.HasFontFamily())
13407 {
13408 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FAMILY);
13409 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FAMILY);
13410 }
13411
13412 if (attr.HasFontWeight() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_WEIGHT))
13413 {
13414 if (currentStyle.HasFontWeight())
13415 {
13416 if (currentStyle.GetFontWeight() != attr.GetFontWeight())
13417 {
13418 // Clash of attr - mark as such
13419 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_WEIGHT);
13420 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_WEIGHT);
13421 }
13422 }
13423 else
13424 currentStyle.SetFontWeight(attr.GetFontWeight());
13425 }
13426 else if (!attr.HasFontWeight() && currentStyle.HasFontWeight())
13427 {
13428 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_WEIGHT);
13429 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_WEIGHT);
13430 }
13431
13432 if (attr.HasFontFaceName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_FACE))
13433 {
13434 if (currentStyle.HasFontFaceName())
13435 {
13436 wxString faceName1(currentStyle.GetFontFaceName());
13437 wxString faceName2(attr.GetFontFaceName());
13438
13439 if (faceName1 != faceName2)
13440 {
13441 // Clash of attr - mark as such
13442 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FACE);
13443 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FACE);
13444 }
13445 }
13446 else
13447 currentStyle.SetFontFaceName(attr.GetFontFaceName());
13448 }
13449 else if (!attr.HasFontFaceName() && currentStyle.HasFontFaceName())
13450 {
13451 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_FACE);
13452 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_FACE);
13453 }
13454
13455 if (attr.HasFontUnderlined() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_UNDERLINE))
13456 {
13457 if (currentStyle.HasFontUnderlined())
13458 {
13459 if (currentStyle.GetFontUnderlined() != attr.GetFontUnderlined())
13460 {
13461 // Clash of attr - mark as such
13462 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_UNDERLINE);
13463 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_UNDERLINE);
13464 }
13465 }
13466 else
13467 currentStyle.SetFontUnderlined(attr.GetFontUnderlined());
13468 }
13469 else if (!attr.HasFontUnderlined() && currentStyle.HasFontUnderlined())
13470 {
13471 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_UNDERLINE);
13472 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_UNDERLINE);
13473 }
13474
13475 if (attr.HasFontStrikethrough() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_FONT_STRIKETHROUGH))
13476 {
13477 if (currentStyle.HasFontStrikethrough())
13478 {
13479 if (currentStyle.GetFontStrikethrough() != attr.GetFontStrikethrough())
13480 {
13481 // Clash of attr - mark as such
13482 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_STRIKETHROUGH);
13483 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_STRIKETHROUGH);
13484 }
13485 }
13486 else
13487 currentStyle.SetFontStrikethrough(attr.GetFontStrikethrough());
13488 }
13489 else if (!attr.HasFontStrikethrough() && currentStyle.HasFontStrikethrough())
13490 {
13491 clashingAttr.AddFlag(wxTEXT_ATTR_FONT_STRIKETHROUGH);
13492 currentStyle.RemoveFlag(wxTEXT_ATTR_FONT_STRIKETHROUGH);
13493 }
13494
13495 if (attr.HasTextColour() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_TEXT_COLOUR))
13496 {
13497 if (currentStyle.HasTextColour())
13498 {
13499 if (currentStyle.GetTextColour() != attr.GetTextColour())
13500 {
13501 // Clash of attr - mark as such
13502 clashingAttr.AddFlag(wxTEXT_ATTR_TEXT_COLOUR);
13503 currentStyle.RemoveFlag(wxTEXT_ATTR_TEXT_COLOUR);
13504 }
13505 }
13506 else
13507 currentStyle.SetTextColour(attr.GetTextColour());
13508 }
13509 else if (!attr.HasTextColour() && currentStyle.HasTextColour())
13510 {
13511 clashingAttr.AddFlag(wxTEXT_ATTR_TEXT_COLOUR);
13512 currentStyle.RemoveFlag(wxTEXT_ATTR_TEXT_COLOUR);
13513 }
13514
13515 if (attr.HasBackgroundColour() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BACKGROUND_COLOUR))
13516 {
13517 if (currentStyle.HasBackgroundColour())
13518 {
13519 if (currentStyle.GetBackgroundColour() != attr.GetBackgroundColour())
13520 {
13521 // Clash of attr - mark as such
13522 clashingAttr.AddFlag(wxTEXT_ATTR_BACKGROUND_COLOUR);
13523 currentStyle.RemoveFlag(wxTEXT_ATTR_BACKGROUND_COLOUR);
13524 }
13525 }
13526 else
13527 currentStyle.SetBackgroundColour(attr.GetBackgroundColour());
13528 }
13529 else if (!attr.HasBackgroundColour() && currentStyle.HasBackgroundColour())
13530 {
13531 clashingAttr.AddFlag(wxTEXT_ATTR_BACKGROUND_COLOUR);
13532 currentStyle.RemoveFlag(wxTEXT_ATTR_BACKGROUND_COLOUR);
13533 }
13534
13535 if (attr.HasAlignment() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_ALIGNMENT))
13536 {
13537 if (currentStyle.HasAlignment())
13538 {
13539 if (currentStyle.GetAlignment() != attr.GetAlignment())
13540 {
13541 // Clash of attr - mark as such
13542 clashingAttr.AddFlag(wxTEXT_ATTR_ALIGNMENT);
13543 currentStyle.RemoveFlag(wxTEXT_ATTR_ALIGNMENT);
13544 }
13545 }
13546 else
13547 currentStyle.SetAlignment(attr.GetAlignment());
13548 }
13549 else if (!attr.HasAlignment() && currentStyle.HasAlignment())
13550 {
13551 clashingAttr.AddFlag(wxTEXT_ATTR_ALIGNMENT);
13552 currentStyle.RemoveFlag(wxTEXT_ATTR_ALIGNMENT);
13553 }
13554
13555 if (attr.HasTabs() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_TABS))
13556 {
13557 if (currentStyle.HasTabs())
13558 {
13559 if (!wxRichTextTabsEq(currentStyle.GetTabs(), attr.GetTabs()))
13560 {
13561 // Clash of attr - mark as such
13562 clashingAttr.AddFlag(wxTEXT_ATTR_TABS);
13563 currentStyle.RemoveFlag(wxTEXT_ATTR_TABS);
13564 }
13565 }
13566 else
13567 currentStyle.SetTabs(attr.GetTabs());
13568 }
13569 else if (!attr.HasTabs() && currentStyle.HasTabs())
13570 {
13571 clashingAttr.AddFlag(wxTEXT_ATTR_TABS);
13572 currentStyle.RemoveFlag(wxTEXT_ATTR_TABS);
13573 }
13574
13575 if (attr.HasLeftIndent() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LEFT_INDENT))
13576 {
13577 if (currentStyle.HasLeftIndent())
13578 {
13579 if (currentStyle.GetLeftIndent() != attr.GetLeftIndent() || currentStyle.GetLeftSubIndent() != attr.GetLeftSubIndent())
13580 {
13581 // Clash of attr - mark as such
13582 clashingAttr.AddFlag(wxTEXT_ATTR_LEFT_INDENT);
13583 currentStyle.RemoveFlag(wxTEXT_ATTR_LEFT_INDENT);
13584 }
13585 }
13586 else
13587 currentStyle.SetLeftIndent(attr.GetLeftIndent(), attr.GetLeftSubIndent());
13588 }
13589 else if (!attr.HasLeftIndent() && currentStyle.HasLeftIndent())
13590 {
13591 clashingAttr.AddFlag(wxTEXT_ATTR_LEFT_INDENT);
13592 currentStyle.RemoveFlag(wxTEXT_ATTR_LEFT_INDENT);
13593 }
13594
13595 if (attr.HasRightIndent() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_RIGHT_INDENT))
13596 {
13597 if (currentStyle.HasRightIndent())
13598 {
13599 if (currentStyle.GetRightIndent() != attr.GetRightIndent())
13600 {
13601 // Clash of attr - mark as such
13602 clashingAttr.AddFlag(wxTEXT_ATTR_RIGHT_INDENT);
13603 currentStyle.RemoveFlag(wxTEXT_ATTR_RIGHT_INDENT);
13604 }
13605 }
13606 else
13607 currentStyle.SetRightIndent(attr.GetRightIndent());
13608 }
13609 else if (!attr.HasRightIndent() && currentStyle.HasRightIndent())
13610 {
13611 clashingAttr.AddFlag(wxTEXT_ATTR_RIGHT_INDENT);
13612 currentStyle.RemoveFlag(wxTEXT_ATTR_RIGHT_INDENT);
13613 }
13614
13615 if (attr.HasParagraphSpacingAfter() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARA_SPACING_AFTER))
13616 {
13617 if (currentStyle.HasParagraphSpacingAfter())
13618 {
13619 if (currentStyle.GetParagraphSpacingAfter() != attr.GetParagraphSpacingAfter())
13620 {
13621 // Clash of attr - mark as such
13622 clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_AFTER);
13623 currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_AFTER);
13624 }
13625 }
13626 else
13627 currentStyle.SetParagraphSpacingAfter(attr.GetParagraphSpacingAfter());
13628 }
13629 else if (!attr.HasParagraphSpacingAfter() && currentStyle.HasParagraphSpacingAfter())
13630 {
13631 clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_AFTER);
13632 currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_AFTER);
13633 }
13634
13635 if (attr.HasParagraphSpacingBefore() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARA_SPACING_BEFORE))
13636 {
13637 if (currentStyle.HasParagraphSpacingBefore())
13638 {
13639 if (currentStyle.GetParagraphSpacingBefore() != attr.GetParagraphSpacingBefore())
13640 {
13641 // Clash of attr - mark as such
13642 clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE);
13643 currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE);
13644 }
13645 }
13646 else
13647 currentStyle.SetParagraphSpacingBefore(attr.GetParagraphSpacingBefore());
13648 }
13649 else if (!attr.HasParagraphSpacingBefore() && currentStyle.HasParagraphSpacingBefore())
13650 {
13651 clashingAttr.AddFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE);
13652 currentStyle.RemoveFlag(wxTEXT_ATTR_PARA_SPACING_BEFORE);
13653 }
13654
13655 if (attr.HasLineSpacing() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LINE_SPACING))
13656 {
13657 if (currentStyle.HasLineSpacing())
13658 {
13659 if (currentStyle.GetLineSpacing() != attr.GetLineSpacing())
13660 {
13661 // Clash of attr - mark as such
13662 clashingAttr.AddFlag(wxTEXT_ATTR_LINE_SPACING);
13663 currentStyle.RemoveFlag(wxTEXT_ATTR_LINE_SPACING);
13664 }
13665 }
13666 else
13667 currentStyle.SetLineSpacing(attr.GetLineSpacing());
13668 }
13669 else if (!attr.HasLineSpacing() && currentStyle.HasLineSpacing())
13670 {
13671 clashingAttr.AddFlag(wxTEXT_ATTR_LINE_SPACING);
13672 currentStyle.RemoveFlag(wxTEXT_ATTR_LINE_SPACING);
13673 }
13674
13675 if (attr.HasCharacterStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_CHARACTER_STYLE_NAME))
13676 {
13677 if (currentStyle.HasCharacterStyleName())
13678 {
13679 if (currentStyle.GetCharacterStyleName() != attr.GetCharacterStyleName())
13680 {
13681 // Clash of attr - mark as such
13682 clashingAttr.AddFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME);
13683 currentStyle.RemoveFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME);
13684 }
13685 }
13686 else
13687 currentStyle.SetCharacterStyleName(attr.GetCharacterStyleName());
13688 }
13689 else if (!attr.HasCharacterStyleName() && currentStyle.HasCharacterStyleName())
13690 {
13691 clashingAttr.AddFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME);
13692 currentStyle.RemoveFlag(wxTEXT_ATTR_CHARACTER_STYLE_NAME);
13693 }
13694
13695 if (attr.HasParagraphStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_PARAGRAPH_STYLE_NAME))
13696 {
13697 if (currentStyle.HasParagraphStyleName())
13698 {
13699 if (currentStyle.GetParagraphStyleName() != attr.GetParagraphStyleName())
13700 {
13701 // Clash of attr - mark as such
13702 clashingAttr.AddFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
13703 currentStyle.RemoveFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
13704 }
13705 }
13706 else
13707 currentStyle.SetParagraphStyleName(attr.GetParagraphStyleName());
13708 }
13709 else if (!attr.HasParagraphStyleName() && currentStyle.HasParagraphStyleName())
13710 {
13711 clashingAttr.AddFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
13712 currentStyle.RemoveFlag(wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
13713 }
13714
13715 if (attr.HasListStyleName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_LIST_STYLE_NAME))
13716 {
13717 if (currentStyle.HasListStyleName())
13718 {
13719 if (currentStyle.GetListStyleName() != attr.GetListStyleName())
13720 {
13721 // Clash of attr - mark as such
13722 clashingAttr.AddFlag(wxTEXT_ATTR_LIST_STYLE_NAME);
13723 currentStyle.RemoveFlag(wxTEXT_ATTR_LIST_STYLE_NAME);
13724 }
13725 }
13726 else
13727 currentStyle.SetListStyleName(attr.GetListStyleName());
13728 }
13729 else if (!attr.HasListStyleName() && currentStyle.HasListStyleName())
13730 {
13731 clashingAttr.AddFlag(wxTEXT_ATTR_LIST_STYLE_NAME);
13732 currentStyle.RemoveFlag(wxTEXT_ATTR_LIST_STYLE_NAME);
13733 }
13734
13735 if (attr.HasBulletStyle() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_STYLE))
13736 {
13737 if (currentStyle.HasBulletStyle())
13738 {
13739 if (currentStyle.GetBulletStyle() != attr.GetBulletStyle())
13740 {
13741 // Clash of attr - mark as such
13742 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_STYLE);
13743 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_STYLE);
13744 }
13745 }
13746 else
13747 currentStyle.SetBulletStyle(attr.GetBulletStyle());
13748 }
13749 else if (!attr.HasBulletStyle() && currentStyle.HasBulletStyle())
13750 {
13751 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_STYLE);
13752 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_STYLE);
13753 }
13754
13755 if (attr.HasBulletNumber() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_NUMBER))
13756 {
13757 if (currentStyle.HasBulletNumber())
13758 {
13759 if (currentStyle.GetBulletNumber() != attr.GetBulletNumber())
13760 {
13761 // Clash of attr - mark as such
13762 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NUMBER);
13763 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NUMBER);
13764 }
13765 }
13766 else
13767 currentStyle.SetBulletNumber(attr.GetBulletNumber());
13768 }
13769 else if (!attr.HasBulletNumber() && currentStyle.HasBulletNumber())
13770 {
13771 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NUMBER);
13772 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NUMBER);
13773 }
13774
13775 if (attr.HasBulletText() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_TEXT))
13776 {
13777 if (currentStyle.HasBulletText())
13778 {
13779 if (currentStyle.GetBulletText() != attr.GetBulletText())
13780 {
13781 // Clash of attr - mark as such
13782 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_TEXT);
13783 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_TEXT);
13784 }
13785 }
13786 else
13787 {
13788 currentStyle.SetBulletText(attr.GetBulletText());
13789 currentStyle.SetBulletFont(attr.GetBulletFont());
13790 }
13791 }
13792 else if (!attr.HasBulletText() && currentStyle.HasBulletText())
13793 {
13794 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_TEXT);
13795 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_TEXT);
13796 }
13797
13798 if (attr.HasBulletName() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_BULLET_NAME))
13799 {
13800 if (currentStyle.HasBulletName())
13801 {
13802 if (currentStyle.GetBulletName() != attr.GetBulletName())
13803 {
13804 // Clash of attr - mark as such
13805 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NAME);
13806 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NAME);
13807 }
13808 }
13809 else
13810 {
13811 currentStyle.SetBulletName(attr.GetBulletName());
13812 }
13813 }
13814 else if (!attr.HasBulletName() && currentStyle.HasBulletName())
13815 {
13816 clashingAttr.AddFlag(wxTEXT_ATTR_BULLET_NAME);
13817 currentStyle.RemoveFlag(wxTEXT_ATTR_BULLET_NAME);
13818 }
13819
13820 if (attr.HasURL() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_URL))
13821 {
13822 if (currentStyle.HasURL())
13823 {
13824 if (currentStyle.GetURL() != attr.GetURL())
13825 {
13826 // Clash of attr - mark as such
13827 clashingAttr.AddFlag(wxTEXT_ATTR_URL);
13828 currentStyle.RemoveFlag(wxTEXT_ATTR_URL);
13829 }
13830 }
13831 else
13832 {
13833 currentStyle.SetURL(attr.GetURL());
13834 }
13835 }
13836 else if (!attr.HasURL() && currentStyle.HasURL())
13837 {
13838 clashingAttr.AddFlag(wxTEXT_ATTR_URL);
13839 currentStyle.RemoveFlag(wxTEXT_ATTR_URL);
13840 }
13841
13842 if (attr.HasTextEffects() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_EFFECTS))
13843 {
13844 if (currentStyle.HasTextEffects())
13845 {
13846 // We need to find the bits in the new attr that are different:
13847 // just look at those bits that are specified by the new attr.
13848
13849 // We need to remove the bits and flags that are not common between current attr
13850 // and new attr. In so doing we need to take account of the styles absent from one or more of the
13851 // previous styles.
13852
13853 int currentRelevantTextEffects = currentStyle.GetTextEffects() & attr.GetTextEffectFlags();
13854 int newRelevantTextEffects = attr.GetTextEffects() & attr.GetTextEffectFlags();
13855
13856 if (currentRelevantTextEffects != newRelevantTextEffects)
13857 {
13858 // Find the text effects that were different, using XOR
13859 int differentEffects = currentRelevantTextEffects ^ newRelevantTextEffects;
13860
13861 // Clash of attr - mark as such
13862 clashingAttr.SetTextEffectFlags(clashingAttr.GetTextEffectFlags() | differentEffects);
13863 currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~differentEffects);
13864 }
13865 }
13866 else
13867 {
13868 currentStyle.SetTextEffects(attr.GetTextEffects());
13869 currentStyle.SetTextEffectFlags(attr.GetTextEffectFlags());
13870 }
13871
13872 // Mask out the flags and values that cannot be common because they were absent in one or more objecrs
13873 // that we've looked at so far
13874 currentStyle.SetTextEffects(currentStyle.GetTextEffects() & ~absentAttr.GetTextEffectFlags());
13875 currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~absentAttr.GetTextEffectFlags());
13876
13877 if (currentStyle.GetTextEffectFlags() == 0)
13878 currentStyle.RemoveFlag(wxTEXT_ATTR_EFFECTS);
13879 }
13880 else if (!attr.HasTextEffects() && currentStyle.HasTextEffects())
13881 {
13882 clashingAttr.AddFlag(wxTEXT_ATTR_EFFECTS);
13883 currentStyle.RemoveFlag(wxTEXT_ATTR_EFFECTS);
13884 }
13885
13886 if (attr.HasOutlineLevel() && !wxHasStyle(forbiddenFlags, wxTEXT_ATTR_OUTLINE_LEVEL))
13887 {
13888 if (currentStyle.HasOutlineLevel())
13889 {
13890 if (currentStyle.GetOutlineLevel() != attr.GetOutlineLevel())
13891 {
13892 // Clash of attr - mark as such
13893 clashingAttr.AddFlag(wxTEXT_ATTR_OUTLINE_LEVEL);
13894 currentStyle.RemoveFlag(wxTEXT_ATTR_OUTLINE_LEVEL);
13895 }
13896 }
13897 else
13898 currentStyle.SetOutlineLevel(attr.GetOutlineLevel());
13899 }
13900 else if (!attr.HasOutlineLevel() && currentStyle.HasOutlineLevel())
13901 {
13902 clashingAttr.AddFlag(wxTEXT_ATTR_OUTLINE_LEVEL);
13903 currentStyle.RemoveFlag(wxTEXT_ATTR_OUTLINE_LEVEL);
13904 }
13905 }
13906
13907 WX_DEFINE_OBJARRAY(wxRichTextVariantArray);
13908
13909 // JACS 2013-01-27
13910 WX_DEFINE_OBJARRAY(wxRichTextAttrArray);
13911
13912 IMPLEMENT_DYNAMIC_CLASS(wxRichTextProperties, wxObject)
13913
13914 bool wxRichTextProperties::operator==(const wxRichTextProperties& props) const
13915 {
13916 if (m_properties.GetCount() != props.GetCount())
13917 return false;
13918
13919 size_t i;
13920 for (i = 0; i < m_properties.GetCount(); i++)
13921 {
13922 const wxVariant& var1 = m_properties[i];
13923 int idx = props.Find(var1.GetName());
13924 if (idx == -1)
13925 return false;
13926 const wxVariant& var2 = props.m_properties[idx];
13927 if (!(var1 == var2))
13928 return false;
13929 }
13930
13931 return true;
13932 }
13933
13934 wxArrayString wxRichTextProperties::GetPropertyNames() const
13935 {
13936 wxArrayString arr;
13937 size_t i;
13938 for (i = 0; i < m_properties.GetCount(); i++)
13939 {
13940 arr.Add(m_properties[i].GetName());
13941 }
13942 return arr;
13943 }
13944
13945 int wxRichTextProperties::Find(const wxString& name) const
13946 {
13947 size_t i;
13948 for (i = 0; i < m_properties.GetCount(); i++)
13949 {
13950 if (m_properties[i].GetName() == name)
13951 return (int) i;
13952 }
13953 return -1;
13954 }
13955
13956 bool wxRichTextProperties::Remove(const wxString& name)
13957 {
13958 int idx = Find(name);
13959 if (idx != -1)
13960 {
13961 m_properties.RemoveAt(idx);
13962 return true;
13963 }
13964 else
13965 return false;
13966 }
13967
13968 wxVariant* wxRichTextProperties::FindOrCreateProperty(const wxString& name)
13969 {
13970 int idx = Find(name);
13971 if (idx == wxNOT_FOUND)
13972 SetProperty(name, wxString());
13973 idx = Find(name);
13974 if (idx != wxNOT_FOUND)
13975 {
13976 return & (*this)[idx];
13977 }
13978 else
13979 return NULL;
13980 }
13981
13982 const wxVariant& wxRichTextProperties::GetProperty(const wxString& name) const
13983 {
13984 static const wxVariant nullVariant;
13985 int idx = Find(name);
13986 if (idx != -1)
13987 return m_properties[idx];
13988 else
13989 return nullVariant;
13990 }
13991
13992 wxString wxRichTextProperties::GetPropertyString(const wxString& name) const
13993 {
13994 return GetProperty(name).GetString();
13995 }
13996
13997 long wxRichTextProperties::GetPropertyLong(const wxString& name) const
13998 {
13999 return GetProperty(name).GetLong();
14000 }
14001
14002 bool wxRichTextProperties::GetPropertyBool(const wxString& name) const
14003 {
14004 return GetProperty(name).GetBool();
14005 }
14006
14007 double wxRichTextProperties::GetPropertyDouble(const wxString& name) const
14008 {
14009 return GetProperty(name).GetDouble();
14010 }
14011
14012 void wxRichTextProperties::SetProperty(const wxVariant& variant)
14013 {
14014 wxASSERT(!variant.GetName().IsEmpty());
14015
14016 int idx = Find(variant.GetName());
14017
14018 if (idx == -1)
14019 m_properties.Add(variant);
14020 else
14021 m_properties[idx] = variant;
14022 }
14023
14024 void wxRichTextProperties::SetProperty(const wxString& name, const wxVariant& variant)
14025 {
14026 int idx = Find(name);
14027 wxVariant var(variant);
14028 var.SetName(name);
14029
14030 if (idx == -1)
14031 m_properties.Add(var);
14032 else
14033 m_properties[idx] = var;
14034 }
14035
14036 void wxRichTextProperties::SetProperty(const wxString& name, const wxString& value)
14037 {
14038 SetProperty(name, wxVariant(value, name));
14039 }
14040
14041 void wxRichTextProperties::SetProperty(const wxString& name, long value)
14042 {
14043 SetProperty(name, wxVariant(value, name));
14044 }
14045
14046 void wxRichTextProperties::SetProperty(const wxString& name, double value)
14047 {
14048 SetProperty(name, wxVariant(value, name));
14049 }
14050
14051 void wxRichTextProperties::SetProperty(const wxString& name, bool value)
14052 {
14053 SetProperty(name, wxVariant(value, name));
14054 }
14055
14056 void wxRichTextProperties::RemoveProperties(const wxRichTextProperties& properties)
14057 {
14058 size_t i;
14059 for (i = 0; i < properties.GetCount(); i++)
14060 {
14061 wxString name = properties.GetProperties()[i].GetName();
14062 if (HasProperty(name))
14063 Remove(name);
14064 }
14065 }
14066
14067 void wxRichTextProperties::MergeProperties(const wxRichTextProperties& properties)
14068 {
14069 size_t i;
14070 for (i = 0; i < properties.GetCount(); i++)
14071 {
14072 SetProperty(properties.GetProperties()[i]);
14073 }
14074 }
14075
14076 wxRichTextObject* wxRichTextObjectAddress::GetObject(wxRichTextParagraphLayoutBox* topLevelContainer) const
14077 {
14078 if (m_address.GetCount() == 0)
14079 return topLevelContainer;
14080
14081 wxRichTextCompositeObject* p = topLevelContainer;
14082 size_t i = 0;
14083 while (p && i < m_address.GetCount())
14084 {
14085 int pos = m_address[i];
14086 wxASSERT(pos >= 0 && pos < (int) p->GetChildren().GetCount());
14087 if (pos < 0 || pos >= (int) p->GetChildren().GetCount())
14088 return NULL;
14089
14090 wxRichTextObject* p1 = p->GetChild(pos);
14091 if (i == (m_address.GetCount()-1))
14092 return p1;
14093
14094 p = wxDynamicCast(p1, wxRichTextCompositeObject);
14095 i ++;
14096 }
14097 return NULL;
14098 }
14099
14100 bool wxRichTextObjectAddress::Create(wxRichTextParagraphLayoutBox* topLevelContainer, wxRichTextObject* obj)
14101 {
14102 m_address.Clear();
14103
14104 if (topLevelContainer == obj)
14105 return true;
14106
14107 wxRichTextObject* o = obj;
14108 while (o)
14109 {
14110 wxRichTextCompositeObject* p = wxDynamicCast(o->GetParent(), wxRichTextCompositeObject);
14111 if (!p)
14112 return false;
14113
14114 int pos = p->GetChildren().IndexOf(o);
14115 if (pos == -1)
14116 return false;
14117
14118 m_address.Insert(pos, 0);
14119
14120 if (p == topLevelContainer)
14121 return true;
14122
14123 o = p;
14124 }
14125 return false;
14126 }
14127
14128 // Equality test
14129 bool wxRichTextSelection::operator==(const wxRichTextSelection& sel) const
14130 {
14131 if (m_container != sel.m_container)
14132 return false;
14133 if (m_ranges.GetCount() != sel.m_ranges.GetCount())
14134 return false;
14135 size_t i;
14136 for (i = 0; i < m_ranges.GetCount(); i++)
14137 if (!(m_ranges[i] == sel.m_ranges[i]))
14138 return false;
14139 return true;
14140 }
14141
14142 // Get the selections appropriate to the specified object, if any; returns wxRICHTEXT_NO_SELECTION if none
14143 // or none at the level of the object's container.
14144 wxRichTextRangeArray wxRichTextSelection::GetSelectionForObject(wxRichTextObject* obj) const
14145 {
14146 if (IsValid())
14147 {
14148 wxRichTextParagraphLayoutBox* container = obj->GetParentContainer();
14149
14150 if (container == m_container)
14151 return m_ranges;
14152
14153 container = obj->GetContainer();
14154 while (container)
14155 {
14156 if (container->GetParent())
14157 {
14158 // If we found that our object's container is within the range of
14159 // a selection higher up, then assume the whole original object
14160 // is also selected.
14161 wxRichTextParagraphLayoutBox* parentContainer = container->GetParentContainer();
14162 if (parentContainer == m_container)
14163 {
14164 if (WithinSelection(container->GetRange().GetStart(), m_ranges))
14165 {
14166 wxRichTextRangeArray ranges;
14167 ranges.Add(obj->GetRange());
14168 return ranges;
14169 }
14170 }
14171
14172 container = parentContainer;
14173 }
14174 else
14175 {
14176 container = NULL;
14177 break;
14178 }
14179 }
14180 }
14181 return wxRichTextRangeArray();
14182 }
14183
14184 // Is the given position within the selection?
14185 bool wxRichTextSelection::WithinSelection(long pos, wxRichTextObject* obj) const
14186 {
14187 if (!IsValid())
14188 return false;
14189 else
14190 {
14191 wxRichTextRangeArray selectionRanges = GetSelectionForObject(obj);
14192 return WithinSelection(pos, selectionRanges);
14193 }
14194 }
14195
14196 // Is the given position within the selection range?
14197 bool wxRichTextSelection::WithinSelection(long pos, const wxRichTextRangeArray& ranges)
14198 {
14199 size_t i;
14200 for (i = 0; i < ranges.GetCount(); i++)
14201 {
14202 const wxRichTextRange& range = ranges[i];
14203 if (pos >= range.GetStart() && pos <= range.GetEnd())
14204 return true;
14205 }
14206 return false;
14207 }
14208
14209 // Is the given range completely within the selection range?
14210 bool wxRichTextSelection::WithinSelection(const wxRichTextRange& range, const wxRichTextRangeArray& ranges)
14211 {
14212 size_t i;
14213 for (i = 0; i < ranges.GetCount(); i++)
14214 {
14215 const wxRichTextRange& eachRange = ranges[i];
14216 if (range.IsWithin(eachRange))
14217 return true;
14218 }
14219 return false;
14220 }
14221
14222 IMPLEMENT_CLASS(wxRichTextDrawingHandler, wxObject)
14223 IMPLEMENT_CLASS(wxRichTextDrawingContext, wxObject)
14224
14225 wxRichTextDrawingContext::wxRichTextDrawingContext(wxRichTextBuffer* buffer)
14226 {
14227 Init();
14228 m_buffer = buffer;
14229 if (m_buffer && m_buffer->GetRichTextCtrl())
14230 EnableVirtualAttributes(m_buffer->GetRichTextCtrl()->GetVirtualAttributesEnabled());
14231 }
14232
14233 bool wxRichTextDrawingContext::HasVirtualAttributes(wxRichTextObject* obj) const
14234 {
14235 if (!GetVirtualAttributesEnabled())
14236 return false;
14237
14238 wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst();
14239 while (node)
14240 {
14241 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
14242 if (handler->HasVirtualAttributes(obj))
14243 return true;
14244
14245 node = node->GetNext();
14246 }
14247 return false;
14248 }
14249
14250 wxRichTextAttr wxRichTextDrawingContext::GetVirtualAttributes(wxRichTextObject* obj) const
14251 {
14252 wxRichTextAttr attr;
14253 if (!GetVirtualAttributesEnabled())
14254 return attr;
14255
14256 // We apply all handlers, so we can may combine several different attributes
14257 wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst();
14258 while (node)
14259 {
14260 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
14261 if (handler->HasVirtualAttributes(obj))
14262 {
14263 bool success = handler->GetVirtualAttributes(attr, obj);
14264 wxASSERT(success);
14265 wxUnusedVar(success);
14266 }
14267
14268 node = node->GetNext();
14269 }
14270 return attr;
14271 }
14272
14273 bool wxRichTextDrawingContext::ApplyVirtualAttributes(wxRichTextAttr& attr, wxRichTextObject* obj) const
14274 {
14275 if (!GetVirtualAttributesEnabled())
14276 return false;
14277
14278 if (HasVirtualAttributes(obj))
14279 {
14280 wxRichTextAttr a(GetVirtualAttributes(obj));
14281 attr.Apply(a);
14282 return true;
14283 }
14284 else
14285 return false;
14286 }
14287
14288 int wxRichTextDrawingContext::GetVirtualSubobjectAttributesCount(wxRichTextObject* obj) const
14289 {
14290 if (!GetVirtualAttributesEnabled())
14291 return 0;
14292
14293 wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst();
14294 while (node)
14295 {
14296 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
14297 int count = handler->GetVirtualSubobjectAttributesCount(obj);
14298 if (count > 0)
14299 return count;
14300
14301 node = node->GetNext();
14302 }
14303 return 0;
14304 }
14305
14306 int wxRichTextDrawingContext::GetVirtualSubobjectAttributes(wxRichTextObject* obj, wxArrayInt& positions, wxRichTextAttrArray& attributes) const
14307 {
14308 if (!GetVirtualAttributesEnabled())
14309 return 0;
14310
14311 wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst();
14312 while (node)
14313 {
14314 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
14315 if (handler->GetVirtualSubobjectAttributes(obj, positions, attributes))
14316 return positions.GetCount();
14317
14318 node = node->GetNext();
14319 }
14320 return 0;
14321 }
14322
14323 bool wxRichTextDrawingContext::HasVirtualText(const wxRichTextPlainText* obj) const
14324 {
14325 if (!GetVirtualAttributesEnabled())
14326 return false;
14327
14328 wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst();
14329 while (node)
14330 {
14331 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
14332 if (handler->HasVirtualText(obj))
14333 return true;
14334
14335 node = node->GetNext();
14336 }
14337 return false;
14338 }
14339
14340 bool wxRichTextDrawingContext::GetVirtualText(const wxRichTextPlainText* obj, wxString& text) const
14341 {
14342 if (!GetVirtualAttributesEnabled())
14343 return false;
14344
14345 wxList::compatibility_iterator node = m_buffer->GetDrawingHandlers().GetFirst();
14346 while (node)
14347 {
14348 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
14349 if (handler->GetVirtualText(obj, text))
14350 return true;
14351
14352 node = node->GetNext();
14353 }
14354 return false;
14355 }
14356
14357 /// Adds a handler to the end
14358 void wxRichTextBuffer::AddDrawingHandler(wxRichTextDrawingHandler *handler)
14359 {
14360 sm_drawingHandlers.Append(handler);
14361 }
14362
14363 /// Inserts a handler at the front
14364 void wxRichTextBuffer::InsertDrawingHandler(wxRichTextDrawingHandler *handler)
14365 {
14366 sm_drawingHandlers.Insert( handler );
14367 }
14368
14369 /// Removes a handler
14370 bool wxRichTextBuffer::RemoveDrawingHandler(const wxString& name)
14371 {
14372 wxRichTextDrawingHandler *handler = FindDrawingHandler(name);
14373 if (handler)
14374 {
14375 sm_drawingHandlers.DeleteObject(handler);
14376 delete handler;
14377 return true;
14378 }
14379 else
14380 return false;
14381 }
14382
14383 wxRichTextDrawingHandler* wxRichTextBuffer::FindDrawingHandler(const wxString& name)
14384 {
14385 wxList::compatibility_iterator node = sm_drawingHandlers.GetFirst();
14386 while (node)
14387 {
14388 wxRichTextDrawingHandler *handler = (wxRichTextDrawingHandler*)node->GetData();
14389 if (handler->GetName().Lower() == name.Lower()) return handler;
14390
14391 node = node->GetNext();
14392 }
14393 return NULL;
14394 }
14395
14396 void wxRichTextBuffer::CleanUpDrawingHandlers()
14397 {
14398 wxList::compatibility_iterator node = sm_drawingHandlers.GetFirst();
14399 while (node)
14400 {
14401 wxRichTextDrawingHandler* handler = (wxRichTextDrawingHandler*)node->GetData();
14402 wxList::compatibility_iterator next = node->GetNext();
14403 delete handler;
14404 node = next;
14405 }
14406
14407 sm_drawingHandlers.Clear();
14408 }
14409
14410 void wxRichTextBuffer::AddFieldType(wxRichTextFieldType *fieldType)
14411 {
14412 sm_fieldTypes[fieldType->GetName()] = fieldType;
14413 }
14414
14415 bool wxRichTextBuffer::RemoveFieldType(const wxString& name)
14416 {
14417 wxRichTextFieldTypeHashMap::iterator it = sm_fieldTypes.find(name);
14418 if (it == sm_fieldTypes.end())
14419 return false;
14420 else
14421 {
14422 wxRichTextFieldType* fieldType = it->second;
14423 sm_fieldTypes.erase(it);
14424 delete fieldType;
14425 return true;
14426 }
14427 }
14428
14429 wxRichTextFieldType *wxRichTextBuffer::FindFieldType(const wxString& name)
14430 {
14431 wxRichTextFieldTypeHashMap::iterator it = sm_fieldTypes.find(name);
14432 if (it == sm_fieldTypes.end())
14433 return NULL;
14434 else
14435 return it->second;
14436 }
14437
14438 void wxRichTextBuffer::CleanUpFieldTypes()
14439 {
14440 wxRichTextFieldTypeHashMap::iterator it;
14441 for( it = sm_fieldTypes.begin(); it != sm_fieldTypes.end(); ++it )
14442 {
14443 wxRichTextFieldType* fieldType = it->second;
14444 delete fieldType;
14445 }
14446
14447 sm_fieldTypes.clear();
14448 }
14449
14450 #endif
14451 // wxUSE_RICHTEXT