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