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