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