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