]> git.saurik.com Git - wxWidgets.git/blob - src/richtext/richtextbuffer.cpp
Some typo fixes
[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/filename.h"
32 #include "wx/clipbrd.h"
33 #include "wx/wfstream.h"
34 #include "wx/mstream.h"
35 #include "wx/sstream.h"
36 #include "wx/textfile.h"
37
38 #include "wx/richtext/richtextctrl.h"
39 #include "wx/richtext/richtextstyles.h"
40
41 #include "wx/listimpl.cpp"
42
43 WX_DEFINE_LIST(wxRichTextObjectList)
44 WX_DEFINE_LIST(wxRichTextLineList)
45
46 // Switch off if the platform doesn't like it for some reason
47 #define wxRICHTEXT_USE_OPTIMIZED_DRAWING 1
48
49 /*!
50 * wxRichTextObject
51 * This is the base for drawable objects.
52 */
53
54 IMPLEMENT_CLASS(wxRichTextObject, wxObject)
55
56 wxRichTextObject::wxRichTextObject(wxRichTextObject* parent)
57 {
58 m_dirty = false;
59 m_refCount = 1;
60 m_parent = parent;
61 m_leftMargin = 0;
62 m_rightMargin = 0;
63 m_topMargin = 0;
64 m_bottomMargin = 0;
65 m_descent = 0;
66 }
67
68 wxRichTextObject::~wxRichTextObject()
69 {
70 }
71
72 void wxRichTextObject::Dereference()
73 {
74 m_refCount --;
75 if (m_refCount <= 0)
76 delete this;
77 }
78
79 /// Copy
80 void wxRichTextObject::Copy(const wxRichTextObject& obj)
81 {
82 m_size = obj.m_size;
83 m_pos = obj.m_pos;
84 m_dirty = obj.m_dirty;
85 m_range = obj.m_range;
86 m_attributes = obj.m_attributes;
87 m_descent = obj.m_descent;
88 }
89
90 void wxRichTextObject::SetMargins(int margin)
91 {
92 m_leftMargin = m_rightMargin = m_topMargin = m_bottomMargin = margin;
93 }
94
95 void wxRichTextObject::SetMargins(int leftMargin, int rightMargin, int topMargin, int bottomMargin)
96 {
97 m_leftMargin = leftMargin;
98 m_rightMargin = rightMargin;
99 m_topMargin = topMargin;
100 m_bottomMargin = bottomMargin;
101 }
102
103 // Convert units in tenths of a millimetre to device units
104 int wxRichTextObject::ConvertTenthsMMToPixels(wxDC& dc, int units)
105 {
106 int p = ConvertTenthsMMToPixels(dc.GetPPI().x, units);
107
108 // Unscale
109 wxRichTextBuffer* buffer = GetBuffer();
110 if (buffer)
111 p = (int) ((double)p / buffer->GetScale());
112 return p;
113 }
114
115 // Convert units in tenths of a millimetre to device units
116 int wxRichTextObject::ConvertTenthsMMToPixels(int ppi, int units)
117 {
118 // There are ppi pixels in 254.1 "1/10 mm"
119
120 double pixels = ((double) units * (double)ppi) / 254.1;
121
122 return (int) pixels;
123 }
124
125 /// Dump to output stream for debugging
126 void wxRichTextObject::Dump(wxTextOutputStream& stream)
127 {
128 stream << GetClassInfo()->GetClassName() << wxT("\n");
129 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");
130 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");
131 }
132
133 /// Gets the containing buffer
134 wxRichTextBuffer* wxRichTextObject::GetBuffer() const
135 {
136 const wxRichTextObject* obj = this;
137 while (obj && !obj->IsKindOf(CLASSINFO(wxRichTextBuffer)))
138 obj = obj->GetParent();
139 return wxDynamicCast(obj, wxRichTextBuffer);
140 }
141
142 /*!
143 * wxRichTextCompositeObject
144 * This is the base for drawable objects.
145 */
146
147 IMPLEMENT_CLASS(wxRichTextCompositeObject, wxRichTextObject)
148
149 wxRichTextCompositeObject::wxRichTextCompositeObject(wxRichTextObject* parent):
150 wxRichTextObject(parent)
151 {
152 }
153
154 wxRichTextCompositeObject::~wxRichTextCompositeObject()
155 {
156 DeleteChildren();
157 }
158
159 /// Get the nth child
160 wxRichTextObject* wxRichTextCompositeObject::GetChild(size_t n) const
161 {
162 wxASSERT ( n < m_children.GetCount() );
163
164 return m_children.Item(n)->GetData();
165 }
166
167 /// Append a child, returning the position
168 size_t wxRichTextCompositeObject::AppendChild(wxRichTextObject* child)
169 {
170 m_children.Append(child);
171 child->SetParent(this);
172 return m_children.GetCount() - 1;
173 }
174
175 /// Insert the child in front of the given object, or at the beginning
176 bool wxRichTextCompositeObject::InsertChild(wxRichTextObject* child, wxRichTextObject* inFrontOf)
177 {
178 if (inFrontOf)
179 {
180 wxRichTextObjectList::compatibility_iterator node = m_children.Find(inFrontOf);
181 m_children.Insert(node, child);
182 }
183 else
184 m_children.Insert(child);
185 child->SetParent(this);
186
187 return true;
188 }
189
190 /// Delete the child
191 bool wxRichTextCompositeObject::RemoveChild(wxRichTextObject* child, bool deleteChild)
192 {
193 wxRichTextObjectList::compatibility_iterator node = m_children.Find(child);
194 if (node)
195 {
196 wxRichTextObject* obj = node->GetData();
197 m_children.Erase(node);
198 if (deleteChild)
199 delete obj;
200
201 return true;
202 }
203 return false;
204 }
205
206 /// Delete all children
207 bool wxRichTextCompositeObject::DeleteChildren()
208 {
209 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
210 while (node)
211 {
212 wxRichTextObjectList::compatibility_iterator oldNode = node;
213
214 wxRichTextObject* child = node->GetData();
215 child->Dereference(); // Only delete if reference count is zero
216
217 node = node->GetNext();
218 m_children.Erase(oldNode);
219 }
220
221 return true;
222 }
223
224 /// Get the child count
225 size_t wxRichTextCompositeObject::GetChildCount() const
226 {
227 return m_children.GetCount();
228 }
229
230 /// Copy
231 void wxRichTextCompositeObject::Copy(const wxRichTextCompositeObject& obj)
232 {
233 wxRichTextObject::Copy(obj);
234
235 DeleteChildren();
236
237 wxRichTextObjectList::compatibility_iterator node = obj.m_children.GetFirst();
238 while (node)
239 {
240 wxRichTextObject* child = node->GetData();
241 wxRichTextObject* newChild = child->Clone();
242 newChild->SetParent(this);
243 m_children.Append(newChild);
244
245 node = node->GetNext();
246 }
247 }
248
249 /// Hit-testing: returns a flag indicating hit test details, plus
250 /// information about position
251 int wxRichTextCompositeObject::HitTest(wxDC& dc, const wxPoint& pt, long& textPosition)
252 {
253 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
254 while (node)
255 {
256 wxRichTextObject* child = node->GetData();
257
258 int ret = child->HitTest(dc, pt, textPosition);
259 if (ret != wxRICHTEXT_HITTEST_NONE)
260 return ret;
261
262 node = node->GetNext();
263 }
264
265 return wxRICHTEXT_HITTEST_NONE;
266 }
267
268 /// Finds the absolute position and row height for the given character position
269 bool wxRichTextCompositeObject::FindPosition(wxDC& dc, long index, wxPoint& pt, int* height, bool forceLineStart)
270 {
271 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
272 while (node)
273 {
274 wxRichTextObject* child = node->GetData();
275
276 if (child->FindPosition(dc, index, pt, height, forceLineStart))
277 return true;
278
279 node = node->GetNext();
280 }
281
282 return false;
283 }
284
285 /// Calculate range
286 void wxRichTextCompositeObject::CalculateRange(long start, long& end)
287 {
288 long current = start;
289 long lastEnd = current;
290
291 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
292 while (node)
293 {
294 wxRichTextObject* child = node->GetData();
295 long childEnd = 0;
296
297 child->CalculateRange(current, childEnd);
298 lastEnd = childEnd;
299
300 current = childEnd + 1;
301
302 node = node->GetNext();
303 }
304
305 end = lastEnd;
306
307 // An object with no children has zero length
308 if (m_children.GetCount() == 0)
309 end --;
310
311 m_range.SetRange(start, end);
312 }
313
314 /// Delete range from layout.
315 bool wxRichTextCompositeObject::DeleteRange(const wxRichTextRange& range)
316 {
317 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
318
319 while (node)
320 {
321 wxRichTextObject* obj = (wxRichTextObject*) node->GetData();
322 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
323
324 // Delete the range in each paragraph
325
326 // When a chunk has been deleted, internally the content does not
327 // now match the ranges.
328 // However, so long as deletion is not done on the same object twice this is OK.
329 // If you may delete content from the same object twice, recalculate
330 // the ranges inbetween DeleteRange calls by calling CalculateRanges, and
331 // adjust the range you're deleting accordingly.
332
333 if (!obj->GetRange().IsOutside(range))
334 {
335 obj->DeleteRange(range);
336
337 // Delete an empty object, or paragraph within this range.
338 if (obj->IsEmpty() ||
339 (range.GetStart() <= obj->GetRange().GetStart() && range.GetEnd() >= obj->GetRange().GetEnd()))
340 {
341 // An empty paragraph has length 1, so won't be deleted unless the
342 // whole range is deleted.
343 RemoveChild(obj, true);
344 }
345 }
346
347 node = next;
348 }
349
350 return true;
351 }
352
353 /// Get any text in this object for the given range
354 wxString wxRichTextCompositeObject::GetTextForRange(const wxRichTextRange& range) const
355 {
356 wxString text;
357 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
358 while (node)
359 {
360 wxRichTextObject* child = node->GetData();
361 wxRichTextRange childRange = range;
362 if (!child->GetRange().IsOutside(range))
363 {
364 childRange.LimitTo(child->GetRange());
365
366 wxString childText = child->GetTextForRange(childRange);
367
368 text += childText;
369 }
370 node = node->GetNext();
371 }
372
373 return text;
374 }
375
376 /// Recursively merge all pieces that can be merged.
377 bool wxRichTextCompositeObject::Defragment()
378 {
379 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
380 while (node)
381 {
382 wxRichTextObject* child = node->GetData();
383 wxRichTextCompositeObject* composite = wxDynamicCast(child, wxRichTextCompositeObject);
384 if (composite)
385 composite->Defragment();
386
387 if (node->GetNext())
388 {
389 wxRichTextObject* nextChild = node->GetNext()->GetData();
390 if (child->CanMerge(nextChild) && child->Merge(nextChild))
391 {
392 nextChild->Dereference();
393 m_children.Erase(node->GetNext());
394
395 // Don't set node -- we'll see if we can merge again with the next
396 // child.
397 }
398 else
399 node = node->GetNext();
400 }
401 else
402 node = node->GetNext();
403 }
404
405 return true;
406 }
407
408 /// Dump to output stream for debugging
409 void wxRichTextCompositeObject::Dump(wxTextOutputStream& stream)
410 {
411 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
412 while (node)
413 {
414 wxRichTextObject* child = node->GetData();
415 child->Dump(stream);
416 node = node->GetNext();
417 }
418 }
419
420
421 /*!
422 * wxRichTextBox
423 * This defines a 2D space to lay out objects
424 */
425
426 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBox, wxRichTextCompositeObject)
427
428 wxRichTextBox::wxRichTextBox(wxRichTextObject* parent):
429 wxRichTextCompositeObject(parent)
430 {
431 }
432
433 /// Draw the item
434 bool wxRichTextBox::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& WXUNUSED(rect), int descent, int style)
435 {
436 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
437 while (node)
438 {
439 wxRichTextObject* child = node->GetData();
440
441 wxRect childRect = wxRect(child->GetPosition(), child->GetCachedSize());
442 child->Draw(dc, range, selectionRange, childRect, descent, style);
443
444 node = node->GetNext();
445 }
446 return true;
447 }
448
449 /// Lay the item out
450 bool wxRichTextBox::Layout(wxDC& dc, const wxRect& rect, int style)
451 {
452 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
453 while (node)
454 {
455 wxRichTextObject* child = node->GetData();
456 child->Layout(dc, rect, style);
457
458 node = node->GetNext();
459 }
460 m_dirty = false;
461 return true;
462 }
463
464 /// Get/set the size for the given range. Assume only has one child.
465 bool wxRichTextBox::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int flags, wxPoint position) const
466 {
467 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
468 if (node)
469 {
470 wxRichTextObject* child = node->GetData();
471 return child->GetRangeSize(range, size, descent, dc, flags, position);
472 }
473 else
474 return false;
475 }
476
477 /// Copy
478 void wxRichTextBox::Copy(const wxRichTextBox& obj)
479 {
480 wxRichTextCompositeObject::Copy(obj);
481 }
482
483
484 /*!
485 * wxRichTextParagraphLayoutBox
486 * This box knows how to lay out paragraphs.
487 */
488
489 IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraphLayoutBox, wxRichTextBox)
490
491 wxRichTextParagraphLayoutBox::wxRichTextParagraphLayoutBox(wxRichTextObject* parent):
492 wxRichTextBox(parent)
493 {
494 Init();
495 }
496
497 /// Initialize the object.
498 void wxRichTextParagraphLayoutBox::Init()
499 {
500 m_ctrl = NULL;
501
502 // For now, assume is the only box and has no initial size.
503 m_range = wxRichTextRange(0, -1);
504
505 m_invalidRange.SetRange(-1, -1);
506 m_leftMargin = 4;
507 m_rightMargin = 4;
508 m_topMargin = 4;
509 m_bottomMargin = 4;
510 m_partialParagraph = false;
511 }
512
513 /// Draw the item
514 bool wxRichTextParagraphLayoutBox::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int descent, int style)
515 {
516 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
517 while (node)
518 {
519 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
520 wxASSERT (child != NULL);
521
522 if (child && !child->GetRange().IsOutside(range))
523 {
524 wxRect childRect(child->GetPosition(), child->GetCachedSize());
525
526 if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetTop() > rect.GetBottom())
527 {
528 // Stop drawing
529 break;
530 }
531 else if (((style & wxRICHTEXT_DRAW_IGNORE_CACHE) == 0) && childRect.GetBottom() < rect.GetTop())
532 {
533 // Skip
534 }
535 else
536 child->Draw(dc, range, selectionRange, childRect, descent, style);
537 }
538
539 node = node->GetNext();
540 }
541 return true;
542 }
543
544 /// Lay the item out
545 bool wxRichTextParagraphLayoutBox::Layout(wxDC& dc, const wxRect& rect, int style)
546 {
547 wxRect availableSpace;
548 bool formatRect = (style & wxRICHTEXT_LAYOUT_SPECIFIED_RECT) == wxRICHTEXT_LAYOUT_SPECIFIED_RECT;
549
550 // If only laying out a specific area, the passed rect has a different meaning:
551 // the visible part of the buffer. This is used in wxRichTextCtrl::OnSize,
552 // so that during a size, only the visible part will be relaid out, or
553 // it would take too long causing flicker. As an approximation, we assume that
554 // everything up to the start of the visible area is laid out correctly.
555 if (formatRect)
556 {
557 availableSpace = wxRect(0 + m_leftMargin,
558 0 + m_topMargin,
559 rect.width - m_leftMargin - m_rightMargin,
560 rect.height);
561
562 // Invalidate the part of the buffer from the first visible line
563 // to the end. If other parts of the buffer are currently invalid,
564 // then they too will be taken into account if they are above
565 // the visible point.
566 long startPos = 0;
567 wxRichTextLine* line = GetLineAtYPosition(rect.y);
568 if (line)
569 startPos = line->GetAbsoluteRange().GetStart();
570
571 Invalidate(wxRichTextRange(startPos, GetRange().GetEnd()));
572 }
573 else
574 availableSpace = wxRect(rect.x + m_leftMargin,
575 rect.y + m_topMargin,
576 rect.width - m_leftMargin - m_rightMargin,
577 rect.height - m_topMargin - m_bottomMargin);
578
579 int maxWidth = 0;
580
581 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
582
583 bool layoutAll = true;
584
585 // Get invalid range, rounding to paragraph start/end.
586 wxRichTextRange invalidRange = GetInvalidRange(true);
587
588 if (invalidRange == wxRICHTEXT_NONE && !formatRect)
589 return true;
590
591 if (invalidRange == wxRICHTEXT_ALL)
592 layoutAll = true;
593 else // If we know what range is affected, start laying out from that point on.
594 if (invalidRange.GetStart() > GetRange().GetStart())
595 {
596 wxRichTextParagraph* firstParagraph = GetParagraphAtPosition(invalidRange.GetStart());
597 if (firstParagraph)
598 {
599 wxRichTextObjectList::compatibility_iterator firstNode = m_children.Find(firstParagraph);
600 wxRichTextObjectList::compatibility_iterator previousNode;
601 if ( firstNode )
602 previousNode = firstNode->GetPrevious();
603 if (firstNode && previousNode)
604 {
605 wxRichTextParagraph* previousParagraph = wxDynamicCast(previousNode->GetData(), wxRichTextParagraph);
606 availableSpace.y = previousParagraph->GetPosition().y + previousParagraph->GetCachedSize().y;
607
608 // Now we're going to start iterating from the first affected paragraph.
609 node = firstNode;
610
611 layoutAll = false;
612 }
613 }
614 }
615
616 // A way to force speedy rest-of-buffer layout (the 'else' below)
617 bool forceQuickLayout = false;
618
619 while (node)
620 {
621 // Assume this box only contains paragraphs
622
623 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
624 wxCHECK_MSG( child, false, _T("Unknown object in layout") );
625
626 // TODO: what if the child hasn't been laid out (e.g. involved in Undo) but still has 'old' lines
627 if ( !forceQuickLayout &&
628 (layoutAll ||
629 child->GetLines().IsEmpty() ||
630 !child->GetRange().IsOutside(invalidRange)) )
631 {
632 child->Layout(dc, availableSpace, style);
633
634 // Layout must set the cached size
635 availableSpace.y += child->GetCachedSize().y;
636 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
637
638 // If we're just formatting the visible part of the buffer,
639 // and we're now past the bottom of the window, start quick
640 // layout.
641 if (formatRect && child->GetPosition().y > rect.GetBottom())
642 forceQuickLayout = true;
643 }
644 else
645 {
646 // We're outside the immediately affected range, so now let's just
647 // move everything up or down. This assumes that all the children have previously
648 // been laid out and have wrapped line lists associated with them.
649 // TODO: check all paragraphs before the affected range.
650
651 int inc = availableSpace.y - child->GetPosition().y;
652
653 while (node)
654 {
655 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
656 if (child)
657 {
658 if (child->GetLines().GetCount() == 0)
659 child->Layout(dc, availableSpace, style);
660 else
661 child->SetPosition(wxPoint(child->GetPosition().x, child->GetPosition().y + inc));
662
663 availableSpace.y += child->GetCachedSize().y;
664 maxWidth = wxMax(maxWidth, child->GetCachedSize().x);
665 }
666
667 node = node->GetNext();
668 }
669 break;
670 }
671
672 node = node->GetNext();
673 }
674
675 SetCachedSize(wxSize(maxWidth, availableSpace.y));
676
677 m_dirty = false;
678 m_invalidRange = wxRICHTEXT_NONE;
679
680 return true;
681 }
682
683 /// Copy
684 void wxRichTextParagraphLayoutBox::Copy(const wxRichTextParagraphLayoutBox& obj)
685 {
686 wxRichTextBox::Copy(obj);
687
688 m_partialParagraph = obj.m_partialParagraph;
689 }
690
691 /// Get/set the size for the given range.
692 bool wxRichTextParagraphLayoutBox::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int flags, wxPoint position) const
693 {
694 wxSize sz;
695
696 wxRichTextObjectList::compatibility_iterator startPara = wxRichTextObjectList::compatibility_iterator();
697 wxRichTextObjectList::compatibility_iterator endPara = wxRichTextObjectList::compatibility_iterator();
698
699 // First find the first paragraph whose starting position is within the range.
700 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
701 while (node)
702 {
703 // child is a paragraph
704 wxRichTextObject* child = node->GetData();
705 const wxRichTextRange& r = child->GetRange();
706
707 if (r.GetStart() <= range.GetStart() && r.GetEnd() >= range.GetStart())
708 {
709 startPara = node;
710 break;
711 }
712
713 node = node->GetNext();
714 }
715
716 // Next find the last paragraph containing part of the range
717 node = m_children.GetFirst();
718 while (node)
719 {
720 // child is a paragraph
721 wxRichTextObject* child = node->GetData();
722 const wxRichTextRange& r = child->GetRange();
723
724 if (r.GetStart() <= range.GetEnd() && r.GetEnd() >= range.GetEnd())
725 {
726 endPara = node;
727 break;
728 }
729
730 node = node->GetNext();
731 }
732
733 if (!startPara || !endPara)
734 return false;
735
736 // Now we can add up the sizes
737 for (node = startPara; node ; node = node->GetNext())
738 {
739 // child is a paragraph
740 wxRichTextObject* child = node->GetData();
741 const wxRichTextRange& childRange = child->GetRange();
742 wxRichTextRange rangeToFind = range;
743 rangeToFind.LimitTo(childRange);
744
745 wxSize childSize;
746
747 int childDescent = 0;
748 child->GetRangeSize(rangeToFind, childSize, childDescent, dc, flags, position);
749
750 descent = wxMax(childDescent, descent);
751
752 sz.x = wxMax(sz.x, childSize.x);
753 sz.y += childSize.y;
754
755 if (node == endPara)
756 break;
757 }
758
759 size = sz;
760
761 return true;
762 }
763
764 /// Get the paragraph at the given position
765 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtPosition(long pos, bool caretPosition) const
766 {
767 if (caretPosition)
768 pos ++;
769
770 // First find the first paragraph whose starting position is within the range.
771 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
772 while (node)
773 {
774 // child is a paragraph
775 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
776 wxASSERT (child != NULL);
777
778 // Return first child in buffer if position is -1
779 // if (pos == -1)
780 // return child;
781
782 if (child->GetRange().Contains(pos))
783 return child;
784
785 node = node->GetNext();
786 }
787 return NULL;
788 }
789
790 /// Get the line at the given position
791 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtPosition(long pos, bool caretPosition) const
792 {
793 if (caretPosition)
794 pos ++;
795
796 // First find the first paragraph whose starting position is within the range.
797 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
798 while (node)
799 {
800 // child is a paragraph
801 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
802 wxASSERT (child != NULL);
803
804 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
805 while (node2)
806 {
807 wxRichTextLine* line = node2->GetData();
808
809 wxRichTextRange range = line->GetAbsoluteRange();
810
811 if (range.Contains(pos) ||
812
813 // If the position is end-of-paragraph, then return the last line of
814 // of the paragraph.
815 (range.GetEnd() == child->GetRange().GetEnd()-1) && (pos == child->GetRange().GetEnd()))
816 return line;
817
818 node2 = node2->GetNext();
819 }
820
821 node = node->GetNext();
822 }
823
824 int lineCount = GetLineCount();
825 if (lineCount > 0)
826 return GetLineForVisibleLineNumber(lineCount-1);
827 else
828 return NULL;
829 }
830
831 /// Get the line at the given y pixel position, or the last line.
832 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineAtYPosition(int y) const
833 {
834 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
835 while (node)
836 {
837 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
838 wxASSERT (child != NULL);
839
840 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
841 while (node2)
842 {
843 wxRichTextLine* line = node2->GetData();
844
845 wxRect rect(line->GetRect());
846
847 if (y <= rect.GetBottom())
848 return line;
849
850 node2 = node2->GetNext();
851 }
852
853 node = node->GetNext();
854 }
855
856 // Return last line
857 int lineCount = GetLineCount();
858 if (lineCount > 0)
859 return GetLineForVisibleLineNumber(lineCount-1);
860 else
861 return NULL;
862 }
863
864 /// Get the number of visible lines
865 int wxRichTextParagraphLayoutBox::GetLineCount() const
866 {
867 int count = 0;
868
869 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
870 while (node)
871 {
872 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
873 wxASSERT (child != NULL);
874
875 count += child->GetLines().GetCount();
876 node = node->GetNext();
877 }
878 return count;
879 }
880
881
882 /// Get the paragraph for a given line
883 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphForLine(wxRichTextLine* line) const
884 {
885 return GetParagraphAtPosition(line->GetAbsoluteRange().GetStart());
886 }
887
888 /// Get the line size at the given position
889 wxSize wxRichTextParagraphLayoutBox::GetLineSizeAtPosition(long pos, bool caretPosition) const
890 {
891 wxRichTextLine* line = GetLineAtPosition(pos, caretPosition);
892 if (line)
893 {
894 return line->GetSize();
895 }
896 else
897 return wxSize(0, 0);
898 }
899
900
901 /// Convenience function to add a paragraph of text
902 wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraph(const wxString& text, wxTextAttrEx* paraStyle)
903 {
904 // Don't use the base style, just the default style, and the base style will
905 // be combined at display time.
906 // Divide into paragraph and character styles.
907
908 wxTextAttrEx defaultCharStyle;
909 wxTextAttrEx defaultParaStyle;
910
911 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
912 wxTextAttrEx* pStyle = paraStyle ? paraStyle : (wxTextAttrEx*) & defaultParaStyle;
913 wxTextAttrEx* cStyle = & defaultCharStyle;
914
915 wxRichTextParagraph* para = new wxRichTextParagraph(text, this, pStyle, cStyle);
916
917 AppendChild(para);
918
919 UpdateRanges();
920 SetDirty(true);
921
922 return para->GetRange();
923 }
924
925 /// Adds multiple paragraphs, based on newlines.
926 wxRichTextRange wxRichTextParagraphLayoutBox::AddParagraphs(const wxString& text, wxTextAttrEx* paraStyle)
927 {
928 // Don't use the base style, just the default style, and the base style will
929 // be combined at display time.
930 // Divide into paragraph and character styles.
931
932 wxTextAttrEx defaultCharStyle;
933 wxTextAttrEx defaultParaStyle;
934 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
935
936 wxTextAttrEx* pStyle = paraStyle ? paraStyle : (wxTextAttrEx*) & defaultParaStyle;
937 wxTextAttrEx* cStyle = & defaultCharStyle;
938
939 wxRichTextParagraph* firstPara = NULL;
940 wxRichTextParagraph* lastPara = NULL;
941
942 wxRichTextRange range(-1, -1);
943
944 size_t i = 0;
945 size_t len = text.length();
946 wxString line;
947 wxRichTextParagraph* para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle);
948
949 AppendChild(para);
950
951 firstPara = para;
952 lastPara = para;
953
954 while (i < len)
955 {
956 wxChar ch = text[i];
957 if (ch == wxT('\n') || ch == wxT('\r'))
958 {
959 wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
960 plainText->SetText(line);
961
962 para = new wxRichTextParagraph(wxEmptyString, this, pStyle, cStyle);
963
964 AppendChild(para);
965
966 lastPara = para;
967 line = wxEmptyString;
968 }
969 else
970 line += ch;
971
972 i ++;
973 }
974
975 if (!line.empty())
976 {
977 wxRichTextPlainText* plainText = (wxRichTextPlainText*) para->GetChildren().GetFirst()->GetData();
978 plainText->SetText(line);
979 }
980
981 UpdateRanges();
982
983 SetDirty(false);
984
985 return wxRichTextRange(firstPara->GetRange().GetStart(), lastPara->GetRange().GetEnd());
986 }
987
988 /// Convenience function to add an image
989 wxRichTextRange wxRichTextParagraphLayoutBox::AddImage(const wxImage& image, wxTextAttrEx* paraStyle)
990 {
991 // Don't use the base style, just the default style, and the base style will
992 // be combined at display time.
993 // Divide into paragraph and character styles.
994
995 wxTextAttrEx defaultCharStyle;
996 wxTextAttrEx defaultParaStyle;
997 wxRichTextSplitParaCharStyles(GetDefaultStyle(), defaultParaStyle, defaultCharStyle);
998
999 wxTextAttrEx* pStyle = paraStyle ? paraStyle : (wxTextAttrEx*) & defaultParaStyle;
1000 wxTextAttrEx* cStyle = & defaultCharStyle;
1001
1002 wxRichTextParagraph* para = new wxRichTextParagraph(this, pStyle);
1003 AppendChild(para);
1004 para->AppendChild(new wxRichTextImage(image, this, cStyle));
1005
1006 UpdateRanges();
1007 SetDirty(true);
1008
1009 return para->GetRange();
1010 }
1011
1012
1013 /// Insert fragment into this box at the given position. If partialParagraph is true,
1014 /// it is assumed that the last (or only) paragraph is just a piece of data with no paragraph
1015 /// marker.
1016
1017 bool wxRichTextParagraphLayoutBox::InsertFragment(long position, wxRichTextParagraphLayoutBox& fragment)
1018 {
1019 SetDirty(true);
1020
1021 // First, find the first paragraph whose starting position is within the range.
1022 wxRichTextParagraph* para = GetParagraphAtPosition(position);
1023 if (para)
1024 {
1025 wxRichTextObjectList::compatibility_iterator node = m_children.Find(para);
1026
1027 // Now split at this position, returning the object to insert the new
1028 // ones in front of.
1029 wxRichTextObject* nextObject = para->SplitAt(position);
1030
1031 // Special case: partial paragraph, just one paragraph. Might be a small amount of
1032 // text, for example, so let's optimize.
1033
1034 if (fragment.GetPartialParagraph() && fragment.GetChildren().GetCount() == 1)
1035 {
1036 // Add the first para to this para...
1037 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
1038 if (!firstParaNode)
1039 return false;
1040
1041 // Iterate through the fragment paragraph inserting the content into this paragraph.
1042 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
1043 wxASSERT (firstPara != NULL);
1044
1045 // Apply the new paragraph attributes to the existing paragraph
1046 wxTextAttrEx attr(para->GetAttributes());
1047 wxRichTextApplyStyle(attr, firstPara->GetAttributes());
1048 para->SetAttributes(attr);
1049
1050 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
1051 while (objectNode)
1052 {
1053 wxRichTextObject* newObj = objectNode->GetData()->Clone();
1054
1055 if (!nextObject)
1056 {
1057 // Append
1058 para->AppendChild(newObj);
1059 }
1060 else
1061 {
1062 // Insert before nextObject
1063 para->InsertChild(newObj, nextObject);
1064 }
1065
1066 objectNode = objectNode->GetNext();
1067 }
1068
1069 return true;
1070 }
1071 else
1072 {
1073 // Procedure for inserting a fragment consisting of a number of
1074 // paragraphs:
1075 //
1076 // 1. Remove and save the content that's after the insertion point, for adding
1077 // back once we've added the fragment.
1078 // 2. Add the content from the first fragment paragraph to the current
1079 // paragraph.
1080 // 3. Add remaining fragment paragraphs after the current paragraph.
1081 // 4. Add back the saved content from the first paragraph. If partialParagraph
1082 // is true, add it to the last paragraph added and not a new one.
1083
1084 // 1. Remove and save objects after split point.
1085 wxList savedObjects;
1086 if (nextObject)
1087 para->MoveToList(nextObject, savedObjects);
1088
1089 // 2. Add the content from the 1st fragment paragraph.
1090 wxRichTextObjectList::compatibility_iterator firstParaNode = fragment.GetChildren().GetFirst();
1091 if (!firstParaNode)
1092 return false;
1093
1094 wxRichTextParagraph* firstPara = wxDynamicCast(firstParaNode->GetData(), wxRichTextParagraph);
1095 wxASSERT(firstPara != NULL);
1096
1097 wxRichTextObjectList::compatibility_iterator objectNode = firstPara->GetChildren().GetFirst();
1098 while (objectNode)
1099 {
1100 wxRichTextObject* newObj = objectNode->GetData()->Clone();
1101
1102 // Append
1103 para->AppendChild(newObj);
1104
1105 objectNode = objectNode->GetNext();
1106 }
1107
1108 // 3. Add remaining fragment paragraphs after the current paragraph.
1109 wxRichTextObjectList::compatibility_iterator nextParagraphNode = node->GetNext();
1110 wxRichTextObject* nextParagraph = NULL;
1111 if (nextParagraphNode)
1112 nextParagraph = nextParagraphNode->GetData();
1113
1114 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst()->GetNext();
1115 wxRichTextParagraph* finalPara = para;
1116
1117 // If there was only one paragraph, we need to insert a new one.
1118 if (!i)
1119 {
1120 finalPara = new wxRichTextParagraph;
1121
1122 // TODO: These attributes should come from the subsequent paragraph
1123 // when originally deleted, since the subsequent para takes on
1124 // the previous para's attributes.
1125 finalPara->SetAttributes(firstPara->GetAttributes());
1126
1127 if (nextParagraph)
1128 InsertChild(finalPara, nextParagraph);
1129 else
1130 AppendChild(finalPara);
1131 }
1132 else while (i)
1133 {
1134 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
1135 wxASSERT( para != NULL );
1136
1137 finalPara = (wxRichTextParagraph*) para->Clone();
1138
1139 if (nextParagraph)
1140 InsertChild(finalPara, nextParagraph);
1141 else
1142 AppendChild(finalPara);
1143
1144 i = i->GetNext();
1145 }
1146
1147 // 4. Add back the remaining content.
1148 if (finalPara)
1149 {
1150 finalPara->MoveFromList(savedObjects);
1151
1152 // Ensure there's at least one object
1153 if (finalPara->GetChildCount() == 0)
1154 {
1155 wxRichTextPlainText* text = new wxRichTextPlainText(wxEmptyString);
1156
1157 finalPara->AppendChild(text);
1158 }
1159 }
1160
1161 return true;
1162 }
1163 }
1164 else
1165 {
1166 // Append
1167 wxRichTextObjectList::compatibility_iterator i = fragment.GetChildren().GetFirst();
1168 while (i)
1169 {
1170 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
1171 wxASSERT( para != NULL );
1172
1173 AppendChild(para->Clone());
1174
1175 i = i->GetNext();
1176 }
1177
1178 return true;
1179 }
1180 }
1181
1182 /// Make a copy of the fragment corresponding to the given range, putting it in 'fragment'.
1183 /// If there was an incomplete paragraph at the end, partialParagraph is set to true.
1184 bool wxRichTextParagraphLayoutBox::CopyFragment(const wxRichTextRange& range, wxRichTextParagraphLayoutBox& fragment)
1185 {
1186 wxRichTextObjectList::compatibility_iterator i = GetChildren().GetFirst();
1187 while (i)
1188 {
1189 wxRichTextParagraph* para = wxDynamicCast(i->GetData(), wxRichTextParagraph);
1190 wxASSERT( para != NULL );
1191
1192 if (!para->GetRange().IsOutside(range))
1193 {
1194 fragment.AppendChild(para->Clone());
1195 }
1196 i = i->GetNext();
1197 }
1198
1199 // Now top and tail the first and last paragraphs in our new fragment (which might be the same).
1200 if (!fragment.IsEmpty())
1201 {
1202 wxRichTextRange topTailRange(range);
1203
1204 wxRichTextParagraph* firstPara = wxDynamicCast(fragment.GetChildren().GetFirst()->GetData(), wxRichTextParagraph);
1205 wxASSERT( firstPara != NULL );
1206
1207 // Chop off the start of the paragraph
1208 if (topTailRange.GetStart() > firstPara->GetRange().GetStart())
1209 {
1210 wxRichTextRange r(firstPara->GetRange().GetStart(), topTailRange.GetStart()-1);
1211 firstPara->DeleteRange(r);
1212
1213 // Make sure the numbering is correct
1214 long end;
1215 fragment.CalculateRange(firstPara->GetRange().GetStart(), end);
1216
1217 // Now, we've deleted some positions, so adjust the range
1218 // accordingly.
1219 topTailRange.SetEnd(topTailRange.GetEnd() - r.GetLength());
1220 }
1221
1222 wxRichTextParagraph* lastPara = wxDynamicCast(fragment.GetChildren().GetLast()->GetData(), wxRichTextParagraph);
1223 wxASSERT( lastPara != NULL );
1224
1225 if (topTailRange.GetEnd() < (lastPara->GetRange().GetEnd()-1))
1226 {
1227 wxRichTextRange r(topTailRange.GetEnd()+1, lastPara->GetRange().GetEnd()-1); /* -1 since actual text ends 1 position before end of para marker */
1228 lastPara->DeleteRange(r);
1229
1230 // Make sure the numbering is correct
1231 long end;
1232 fragment.CalculateRange(firstPara->GetRange().GetStart(), end);
1233
1234 // We only have part of a paragraph at the end
1235 fragment.SetPartialParagraph(true);
1236 }
1237 else
1238 {
1239 if (topTailRange.GetEnd() == (lastPara->GetRange().GetEnd() - 1))
1240 // We have a partial paragraph (don't save last new paragraph marker)
1241 fragment.SetPartialParagraph(true);
1242 else
1243 // We have a complete paragraph
1244 fragment.SetPartialParagraph(false);
1245 }
1246 }
1247
1248 return true;
1249 }
1250
1251 /// Given a position, get the number of the visible line (potentially many to a paragraph),
1252 /// starting from zero at the start of the buffer.
1253 long wxRichTextParagraphLayoutBox::GetVisibleLineNumber(long pos, bool caretPosition, bool startOfLine) const
1254 {
1255 if (caretPosition)
1256 pos ++;
1257
1258 int lineCount = 0;
1259
1260 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1261 while (node)
1262 {
1263 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1264 wxASSERT( child != NULL );
1265
1266 if (child->GetRange().Contains(pos))
1267 {
1268 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
1269 while (node2)
1270 {
1271 wxRichTextLine* line = node2->GetData();
1272 wxRichTextRange lineRange = line->GetAbsoluteRange();
1273
1274 if (lineRange.Contains(pos))
1275 {
1276 // If the caret is displayed at the end of the previous wrapped line,
1277 // we want to return the line it's _displayed_ at (not the actual line
1278 // containing the position).
1279 if (lineRange.GetStart() == pos && !startOfLine && child->GetRange().GetStart() != pos)
1280 return lineCount - 1;
1281 else
1282 return lineCount;
1283 }
1284
1285 lineCount ++;
1286
1287 node2 = node2->GetNext();
1288 }
1289 // If we didn't find it in the lines, it must be
1290 // the last position of the paragraph. So return the last line.
1291 return lineCount-1;
1292 }
1293 else
1294 lineCount += child->GetLines().GetCount();
1295
1296 node = node->GetNext();
1297 }
1298
1299 // Not found
1300 return -1;
1301 }
1302
1303 /// Given a line number, get the corresponding wxRichTextLine object.
1304 wxRichTextLine* wxRichTextParagraphLayoutBox::GetLineForVisibleLineNumber(long lineNumber) const
1305 {
1306 int lineCount = 0;
1307
1308 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1309 while (node)
1310 {
1311 wxRichTextParagraph* child = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1312 wxASSERT(child != NULL);
1313
1314 if (lineNumber < (int) (child->GetLines().GetCount() + lineCount))
1315 {
1316 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
1317 while (node2)
1318 {
1319 wxRichTextLine* line = node2->GetData();
1320
1321 if (lineCount == lineNumber)
1322 return line;
1323
1324 lineCount ++;
1325
1326 node2 = node2->GetNext();
1327 }
1328 }
1329 else
1330 lineCount += child->GetLines().GetCount();
1331
1332 node = node->GetNext();
1333 }
1334
1335 // Didn't find it
1336 return NULL;
1337 }
1338
1339 /// Delete range from layout.
1340 bool wxRichTextParagraphLayoutBox::DeleteRange(const wxRichTextRange& range)
1341 {
1342 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1343
1344 while (node)
1345 {
1346 wxRichTextParagraph* obj = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1347 wxASSERT (obj != NULL);
1348
1349 wxRichTextObjectList::compatibility_iterator next = node->GetNext();
1350
1351 // Delete the range in each paragraph
1352
1353 if (!obj->GetRange().IsOutside(range))
1354 {
1355 // Deletes the content of this object within the given range
1356 obj->DeleteRange(range);
1357
1358 // If the whole paragraph is within the range to delete,
1359 // delete the whole thing.
1360 if (range.GetStart() <= obj->GetRange().GetStart() && range.GetEnd() >= obj->GetRange().GetEnd())
1361 {
1362 // Delete the whole object
1363 RemoveChild(obj, true);
1364 }
1365 // If the range includes the paragraph end, we need to join this
1366 // and the next paragraph.
1367 else if (range.Contains(obj->GetRange().GetEnd()))
1368 {
1369 // We need to move the objects from the next paragraph
1370 // to this paragraph
1371
1372 if (next)
1373 {
1374 wxRichTextParagraph* nextParagraph = wxDynamicCast(next->GetData(), wxRichTextParagraph);
1375 next = next->GetNext();
1376 if (nextParagraph)
1377 {
1378 // Delete the stuff we need to delete
1379 nextParagraph->DeleteRange(range);
1380
1381 // Move the objects to the previous para
1382 wxRichTextObjectList::compatibility_iterator node1 = nextParagraph->GetChildren().GetFirst();
1383
1384 while (node1)
1385 {
1386 wxRichTextObject* obj1 = node1->GetData();
1387
1388 // If the object is empty, optimise it out
1389 if (obj1->IsEmpty())
1390 {
1391 delete obj1;
1392 }
1393 else
1394 {
1395 obj->AppendChild(obj1);
1396 }
1397
1398 wxRichTextObjectList::compatibility_iterator next1 = node1->GetNext();
1399 nextParagraph->GetChildren().Erase(node1);
1400
1401 node1 = next1;
1402 }
1403
1404 // Delete the paragraph
1405 RemoveChild(nextParagraph, true);
1406
1407 }
1408 }
1409
1410 }
1411 }
1412
1413 node = next;
1414 }
1415
1416 return true;
1417 }
1418
1419 /// Get any text in this object for the given range
1420 wxString wxRichTextParagraphLayoutBox::GetTextForRange(const wxRichTextRange& range) const
1421 {
1422 int lineCount = 0;
1423 wxString text;
1424 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1425 while (node)
1426 {
1427 wxRichTextObject* child = node->GetData();
1428 if (!child->GetRange().IsOutside(range))
1429 {
1430 // if (lineCount > 0)
1431 // text += wxT("\n");
1432 wxRichTextRange childRange = range;
1433 childRange.LimitTo(child->GetRange());
1434
1435 wxString childText = child->GetTextForRange(childRange);
1436
1437 text += childText;
1438
1439 if (childRange.GetEnd() == child->GetRange().GetEnd())
1440 text += wxT("\n");
1441
1442 lineCount ++;
1443 }
1444 node = node->GetNext();
1445 }
1446
1447 return text;
1448 }
1449
1450 /// Get all the text
1451 wxString wxRichTextParagraphLayoutBox::GetText() const
1452 {
1453 return GetTextForRange(GetRange());
1454 }
1455
1456 /// Get the paragraph by number
1457 wxRichTextParagraph* wxRichTextParagraphLayoutBox::GetParagraphAtLine(long paragraphNumber) const
1458 {
1459 if ((size_t) paragraphNumber >= GetChildCount())
1460 return NULL;
1461
1462 return (wxRichTextParagraph*) GetChild((size_t) paragraphNumber);
1463 }
1464
1465 /// Get the length of the paragraph
1466 int wxRichTextParagraphLayoutBox::GetParagraphLength(long paragraphNumber) const
1467 {
1468 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
1469 if (para)
1470 return para->GetRange().GetLength() - 1; // don't include newline
1471 else
1472 return 0;
1473 }
1474
1475 /// Get the text of the paragraph
1476 wxString wxRichTextParagraphLayoutBox::GetParagraphText(long paragraphNumber) const
1477 {
1478 wxRichTextParagraph* para = GetParagraphAtLine(paragraphNumber);
1479 if (para)
1480 return para->GetTextForRange(para->GetRange());
1481 else
1482 return wxEmptyString;
1483 }
1484
1485 /// Convert zero-based line column and paragraph number to a position.
1486 long wxRichTextParagraphLayoutBox::XYToPosition(long x, long y) const
1487 {
1488 wxRichTextParagraph* para = GetParagraphAtLine(y);
1489 if (para)
1490 {
1491 return para->GetRange().GetStart() + x;
1492 }
1493 else
1494 return -1;
1495 }
1496
1497 /// Convert zero-based position to line column and paragraph number
1498 bool wxRichTextParagraphLayoutBox::PositionToXY(long pos, long* x, long* y) const
1499 {
1500 wxRichTextParagraph* para = GetParagraphAtPosition(pos);
1501 if (para)
1502 {
1503 int count = 0;
1504 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1505 while (node)
1506 {
1507 wxRichTextObject* child = node->GetData();
1508 if (child == para)
1509 break;
1510 count ++;
1511 node = node->GetNext();
1512 }
1513
1514 *y = count;
1515 *x = pos - para->GetRange().GetStart();
1516
1517 return true;
1518 }
1519 else
1520 return false;
1521 }
1522
1523 /// Get the leaf object in a paragraph at this position.
1524 /// Given a line number, get the corresponding wxRichTextLine object.
1525 wxRichTextObject* wxRichTextParagraphLayoutBox::GetLeafObjectAtPosition(long position) const
1526 {
1527 wxRichTextParagraph* para = GetParagraphAtPosition(position);
1528 if (para)
1529 {
1530 wxRichTextObjectList::compatibility_iterator node = para->GetChildren().GetFirst();
1531
1532 while (node)
1533 {
1534 wxRichTextObject* child = node->GetData();
1535 if (child->GetRange().Contains(position))
1536 return child;
1537
1538 node = node->GetNext();
1539 }
1540 if (position == para->GetRange().GetEnd() && para->GetChildCount() > 0)
1541 return para->GetChildren().GetLast()->GetData();
1542 }
1543 return NULL;
1544 }
1545
1546 /// Set character or paragraph text attributes: apply character styles only to immediate text nodes
1547 bool wxRichTextParagraphLayoutBox::SetStyle(const wxRichTextRange& range, const wxRichTextAttr& style, int flags)
1548 {
1549 bool characterStyle = false;
1550 bool paragraphStyle = false;
1551
1552 if (style.IsCharacterStyle())
1553 characterStyle = true;
1554 if (style.IsParagraphStyle())
1555 paragraphStyle = true;
1556
1557 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
1558 bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
1559 bool parasOnly = ((flags & wxRICHTEXT_SETSTYLE_PARAGRAPHS_ONLY) != 0);
1560 bool charactersOnly = ((flags & wxRICHTEXT_SETSTYLE_CHARACTERS_ONLY) != 0);
1561 bool resetExistingStyle = ((flags & wxRICHTEXT_SETSTYLE_RESET) != 0);
1562
1563 // Apply paragraph style first, if any
1564 wxRichTextAttr wholeStyle(style);
1565
1566 if (wholeStyle.HasParagraphStyleName() && GetStyleSheet())
1567 {
1568 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(wholeStyle.GetParagraphStyleName());
1569 if (def)
1570 wxRichTextApplyStyle(wholeStyle, def->GetStyle());
1571 }
1572
1573 // Limit the attributes to be set to the content to only character attributes.
1574 wxRichTextAttr characterAttributes(wholeStyle);
1575 characterAttributes.SetFlags(characterAttributes.GetFlags() & (wxTEXT_ATTR_CHARACTER));
1576
1577 if (characterAttributes.HasCharacterStyleName() && GetStyleSheet())
1578 {
1579 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterAttributes.GetCharacterStyleName());
1580 if (def)
1581 wxRichTextApplyStyle(characterAttributes, def->GetStyle());
1582 }
1583
1584 // If we are associated with a control, make undoable; otherwise, apply immediately
1585 // to the data.
1586
1587 bool haveControl = (GetRichTextCtrl() != NULL);
1588
1589 wxRichTextAction* action = NULL;
1590
1591 if (haveControl && withUndo)
1592 {
1593 action = new wxRichTextAction(NULL, _("Change Style"), wxRICHTEXT_CHANGE_STYLE, & GetRichTextCtrl()->GetBuffer(), GetRichTextCtrl());
1594 action->SetRange(range);
1595 action->SetPosition(GetRichTextCtrl()->GetCaretPosition());
1596 }
1597
1598 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
1599 while (node)
1600 {
1601 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
1602 wxASSERT (para != NULL);
1603
1604 if (para && para->GetChildCount() > 0)
1605 {
1606 // Stop searching if we're beyond the range of interest
1607 if (para->GetRange().GetStart() > range.GetEnd())
1608 break;
1609
1610 if (!para->GetRange().IsOutside(range))
1611 {
1612 // We'll be using a copy of the paragraph to make style changes,
1613 // not updating the buffer directly.
1614 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
1615
1616 if (haveControl && withUndo)
1617 {
1618 newPara = new wxRichTextParagraph(*para);
1619 action->GetNewParagraphs().AppendChild(newPara);
1620
1621 // Also store the old ones for Undo
1622 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
1623 }
1624 else
1625 newPara = para;
1626
1627 // If we're specifying paragraphs only, then we really mean character formatting
1628 // to be included in the paragraph style
1629 if ((paragraphStyle || parasOnly) && !charactersOnly)
1630 {
1631 if (resetExistingStyle)
1632 newPara->GetAttributes() = wholeStyle;
1633 else
1634 {
1635 if (applyMinimal)
1636 {
1637 // Only apply attributes that will make a difference to the combined
1638 // style as seen on the display
1639 wxRichTextAttr combinedAttr(para->GetCombinedAttributes());
1640 wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle, & combinedAttr);
1641 }
1642 else
1643 wxRichTextApplyStyle(newPara->GetAttributes(), wholeStyle);
1644 }
1645 }
1646
1647 // When applying paragraph styles dynamically, don't change the text objects' attributes
1648 // since they will computed as needed. Only apply the character styling if it's _only_
1649 // character styling. This policy is subject to change and might be put under user control.
1650
1651 // Hm. we might well be applying a mix of paragraph and character styles, in which
1652 // case we _do_ want to apply character styles regardless of what para styles are set.
1653 // But if we're applying a paragraph style, which has some character attributes, but
1654 // we only want the paragraphs to hold this character style, then we _don't_ want to
1655 // apply the character style. So we need to be able to choose.
1656
1657 // if (!paragraphStyle && characterStyle && range.GetStart() != newPara->GetRange().GetEnd())
1658 if (!parasOnly && characterStyle && range.GetStart() != newPara->GetRange().GetEnd())
1659 {
1660 wxRichTextRange childRange(range);
1661 childRange.LimitTo(newPara->GetRange());
1662
1663 // Find the starting position and if necessary split it so
1664 // we can start applying a different style.
1665 // TODO: check that the style actually changes or is different
1666 // from style outside of range
1667 wxRichTextObject* firstObject wxDUMMY_INITIALIZE(NULL);
1668 wxRichTextObject* lastObject wxDUMMY_INITIALIZE(NULL);
1669
1670 if (childRange.GetStart() == newPara->GetRange().GetStart())
1671 firstObject = newPara->GetChildren().GetFirst()->GetData();
1672 else
1673 firstObject = newPara->SplitAt(range.GetStart());
1674
1675 // Increment by 1 because we're apply the style one _after_ the split point
1676 long splitPoint = childRange.GetEnd();
1677 if (splitPoint != newPara->GetRange().GetEnd())
1678 splitPoint ++;
1679
1680 // Find last object
1681 if (splitPoint == newPara->GetRange().GetEnd() || splitPoint == (newPara->GetRange().GetEnd() - 1))
1682 lastObject = newPara->GetChildren().GetLast()->GetData();
1683 else
1684 // lastObject is set as a side-effect of splitting. It's
1685 // returned as the object before the new object.
1686 (void) newPara->SplitAt(splitPoint, & lastObject);
1687
1688 wxASSERT(firstObject != NULL);
1689 wxASSERT(lastObject != NULL);
1690
1691 if (!firstObject || !lastObject)
1692 continue;
1693
1694 wxRichTextObjectList::compatibility_iterator firstNode = newPara->GetChildren().Find(firstObject);
1695 wxRichTextObjectList::compatibility_iterator lastNode = newPara->GetChildren().Find(lastObject);
1696
1697 wxASSERT(firstNode);
1698 wxASSERT(lastNode);
1699
1700 wxRichTextObjectList::compatibility_iterator node2 = firstNode;
1701
1702 while (node2)
1703 {
1704 wxRichTextObject* child = node2->GetData();
1705
1706 if (resetExistingStyle)
1707 child->GetAttributes() = characterAttributes;
1708 else
1709 {
1710 if (applyMinimal)
1711 {
1712 // Only apply attributes that will make a difference to the combined
1713 // style as seen on the display
1714 wxRichTextAttr combinedAttr(newPara->GetCombinedAttributes(child->GetAttributes()));
1715 wxRichTextApplyStyle(child->GetAttributes(), characterAttributes, & combinedAttr);
1716 }
1717 else
1718 wxRichTextApplyStyle(child->GetAttributes(), characterAttributes);
1719 }
1720
1721 if (node2 == lastNode)
1722 break;
1723
1724 node2 = node2->GetNext();
1725 }
1726 }
1727 }
1728 }
1729
1730 node = node->GetNext();
1731 }
1732
1733 // Do action, or delay it until end of batch.
1734 if (haveControl && withUndo)
1735 GetRichTextCtrl()->GetBuffer().SubmitAction(action);
1736
1737 return true;
1738 }
1739
1740 /// Set text attributes
1741 bool wxRichTextParagraphLayoutBox::SetStyle(const wxRichTextRange& range, const wxTextAttrEx& style, int flags)
1742 {
1743 wxRichTextAttr richStyle = style;
1744 return SetStyle(range, richStyle, flags);
1745 }
1746
1747 /// Get the text attributes for this position.
1748 bool wxRichTextParagraphLayoutBox::GetStyle(long position, wxTextAttrEx& style)
1749 {
1750 return DoGetStyle(position, style, true);
1751 }
1752
1753 /// Get the text attributes for this position.
1754 bool wxRichTextParagraphLayoutBox::GetStyle(long position, wxRichTextAttr& style)
1755 {
1756 wxTextAttrEx textAttrEx(style);
1757 if (GetStyle(position, textAttrEx))
1758 {
1759 style = textAttrEx;
1760 return true;
1761 }
1762 else
1763 return false;
1764 }
1765
1766 /// Get the content (uncombined) attributes for this position.
1767 bool wxRichTextParagraphLayoutBox::GetUncombinedStyle(long position, wxTextAttrEx& style)
1768 {
1769 return DoGetStyle(position, style, false);
1770 }
1771
1772 bool wxRichTextParagraphLayoutBox::GetUncombinedStyle(long position, wxRichTextAttr& style)
1773 {
1774 wxTextAttrEx textAttrEx(style);
1775 if (GetUncombinedStyle(position, textAttrEx))
1776 {
1777 style = textAttrEx;
1778 return true;
1779 }
1780 else
1781 return false;
1782 }
1783
1784 /// Implementation helper for GetStyle. If combineStyles is true, combine base, paragraph and
1785 /// context attributes.
1786 bool wxRichTextParagraphLayoutBox::DoGetStyle(long position, wxTextAttrEx& style, bool combineStyles)
1787 {
1788 wxRichTextObject* obj wxDUMMY_INITIALIZE(NULL);
1789
1790 if (style.IsParagraphStyle())
1791 {
1792 obj = GetParagraphAtPosition(position);
1793 if (obj)
1794 {
1795 if (combineStyles)
1796 {
1797 // Start with the base style
1798 style = GetAttributes();
1799
1800 // Apply the paragraph style
1801 wxRichTextApplyStyle(style, obj->GetAttributes());
1802 }
1803 else
1804 style = obj->GetAttributes();
1805
1806 return true;
1807 }
1808 }
1809 else
1810 {
1811 obj = GetLeafObjectAtPosition(position);
1812 if (obj)
1813 {
1814 if (combineStyles)
1815 {
1816 wxRichTextParagraph* para = wxDynamicCast(obj->GetParent(), wxRichTextParagraph);
1817 style = para ? para->GetCombinedAttributes(obj->GetAttributes()) : obj->GetAttributes();
1818 }
1819 else
1820 style = obj->GetAttributes();
1821
1822 return true;
1823 }
1824 }
1825 return false;
1826 }
1827
1828 static bool wxHasStyle(long flags, long style)
1829 {
1830 return (flags & style) != 0;
1831 }
1832
1833 /// Combines 'style' with 'currentStyle' for the purpose of summarising the attributes of a range of
1834 /// content.
1835 bool wxRichTextParagraphLayoutBox::CollectStyle(wxTextAttrEx& currentStyle, const wxTextAttrEx& style, long& multipleStyleAttributes, int& multipleTextEffectAttributes)
1836 {
1837 if (style.HasFont())
1838 {
1839 if (style.HasSize() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_FONT_SIZE))
1840 {
1841 if (currentStyle.GetFont().Ok() && currentStyle.HasSize())
1842 {
1843 if (currentStyle.GetFont().GetPointSize() != style.GetFont().GetPointSize())
1844 {
1845 // Clash of style - mark as such
1846 multipleStyleAttributes |= wxTEXT_ATTR_FONT_SIZE;
1847 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_FONT_SIZE);
1848 }
1849 }
1850 else
1851 {
1852 if (!currentStyle.GetFont().Ok())
1853 wxSetFontPreservingStyles(currentStyle, *wxNORMAL_FONT);
1854 wxFont font(currentStyle.GetFont());
1855 font.SetPointSize(style.GetFont().GetPointSize());
1856
1857 wxSetFontPreservingStyles(currentStyle, font);
1858 currentStyle.SetFlags(currentStyle.GetFlags() | wxTEXT_ATTR_FONT_SIZE);
1859 }
1860 }
1861
1862 if (style.HasItalic() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_FONT_ITALIC))
1863 {
1864 if (currentStyle.GetFont().Ok() && currentStyle.HasItalic())
1865 {
1866 if (currentStyle.GetFont().GetStyle() != style.GetFont().GetStyle())
1867 {
1868 // Clash of style - mark as such
1869 multipleStyleAttributes |= wxTEXT_ATTR_FONT_ITALIC;
1870 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_FONT_ITALIC);
1871 }
1872 }
1873 else
1874 {
1875 if (!currentStyle.GetFont().Ok())
1876 wxSetFontPreservingStyles(currentStyle, *wxNORMAL_FONT);
1877 wxFont font(currentStyle.GetFont());
1878 font.SetStyle(style.GetFont().GetStyle());
1879 wxSetFontPreservingStyles(currentStyle, font);
1880 currentStyle.SetFlags(currentStyle.GetFlags() | wxTEXT_ATTR_FONT_ITALIC);
1881 }
1882 }
1883
1884 if (style.HasWeight() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_FONT_WEIGHT))
1885 {
1886 if (currentStyle.GetFont().Ok() && currentStyle.HasWeight())
1887 {
1888 if (currentStyle.GetFont().GetWeight() != style.GetFont().GetWeight())
1889 {
1890 // Clash of style - mark as such
1891 multipleStyleAttributes |= wxTEXT_ATTR_FONT_WEIGHT;
1892 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_FONT_WEIGHT);
1893 }
1894 }
1895 else
1896 {
1897 if (!currentStyle.GetFont().Ok())
1898 wxSetFontPreservingStyles(currentStyle, *wxNORMAL_FONT);
1899 wxFont font(currentStyle.GetFont());
1900 font.SetWeight(style.GetFont().GetWeight());
1901 wxSetFontPreservingStyles(currentStyle, font);
1902 currentStyle.SetFlags(currentStyle.GetFlags() | wxTEXT_ATTR_FONT_WEIGHT);
1903 }
1904 }
1905
1906 if (style.HasFaceName() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_FONT_FACE))
1907 {
1908 if (currentStyle.GetFont().Ok() && currentStyle.HasFaceName())
1909 {
1910 wxString faceName1(currentStyle.GetFont().GetFaceName());
1911 wxString faceName2(style.GetFont().GetFaceName());
1912
1913 if (faceName1 != faceName2)
1914 {
1915 // Clash of style - mark as such
1916 multipleStyleAttributes |= wxTEXT_ATTR_FONT_FACE;
1917 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_FONT_FACE);
1918 }
1919 }
1920 else
1921 {
1922 if (!currentStyle.GetFont().Ok())
1923 wxSetFontPreservingStyles(currentStyle, *wxNORMAL_FONT);
1924 wxFont font(currentStyle.GetFont());
1925 font.SetFaceName(style.GetFont().GetFaceName());
1926 wxSetFontPreservingStyles(currentStyle, font);
1927 currentStyle.SetFlags(currentStyle.GetFlags() | wxTEXT_ATTR_FONT_FACE);
1928 }
1929 }
1930
1931 if (style.HasUnderlined() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_FONT_UNDERLINE))
1932 {
1933 if (currentStyle.GetFont().Ok() && currentStyle.HasUnderlined())
1934 {
1935 if (currentStyle.GetFont().GetUnderlined() != style.GetFont().GetUnderlined())
1936 {
1937 // Clash of style - mark as such
1938 multipleStyleAttributes |= wxTEXT_ATTR_FONT_UNDERLINE;
1939 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_FONT_UNDERLINE);
1940 }
1941 }
1942 else
1943 {
1944 if (!currentStyle.GetFont().Ok())
1945 wxSetFontPreservingStyles(currentStyle, *wxNORMAL_FONT);
1946 wxFont font(currentStyle.GetFont());
1947 font.SetUnderlined(style.GetFont().GetUnderlined());
1948 wxSetFontPreservingStyles(currentStyle, font);
1949 currentStyle.SetFlags(currentStyle.GetFlags() | wxTEXT_ATTR_FONT_UNDERLINE);
1950 }
1951 }
1952 }
1953
1954 if (style.HasTextColour() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_TEXT_COLOUR))
1955 {
1956 if (currentStyle.HasTextColour())
1957 {
1958 if (currentStyle.GetTextColour() != style.GetTextColour())
1959 {
1960 // Clash of style - mark as such
1961 multipleStyleAttributes |= wxTEXT_ATTR_TEXT_COLOUR;
1962 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_TEXT_COLOUR);
1963 }
1964 }
1965 else
1966 currentStyle.SetTextColour(style.GetTextColour());
1967 }
1968
1969 if (style.HasBackgroundColour() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_BACKGROUND_COLOUR))
1970 {
1971 if (currentStyle.HasBackgroundColour())
1972 {
1973 if (currentStyle.GetBackgroundColour() != style.GetBackgroundColour())
1974 {
1975 // Clash of style - mark as such
1976 multipleStyleAttributes |= wxTEXT_ATTR_BACKGROUND_COLOUR;
1977 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_BACKGROUND_COLOUR);
1978 }
1979 }
1980 else
1981 currentStyle.SetBackgroundColour(style.GetBackgroundColour());
1982 }
1983
1984 if (style.HasAlignment() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_ALIGNMENT))
1985 {
1986 if (currentStyle.HasAlignment())
1987 {
1988 if (currentStyle.GetAlignment() != style.GetAlignment())
1989 {
1990 // Clash of style - mark as such
1991 multipleStyleAttributes |= wxTEXT_ATTR_ALIGNMENT;
1992 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_ALIGNMENT);
1993 }
1994 }
1995 else
1996 currentStyle.SetAlignment(style.GetAlignment());
1997 }
1998
1999 if (style.HasTabs() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_TABS))
2000 {
2001 if (currentStyle.HasTabs())
2002 {
2003 if (!wxRichTextTabsEq(currentStyle.GetTabs(), style.GetTabs()))
2004 {
2005 // Clash of style - mark as such
2006 multipleStyleAttributes |= wxTEXT_ATTR_TABS;
2007 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_TABS);
2008 }
2009 }
2010 else
2011 currentStyle.SetTabs(style.GetTabs());
2012 }
2013
2014 if (style.HasLeftIndent() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_LEFT_INDENT))
2015 {
2016 if (currentStyle.HasLeftIndent())
2017 {
2018 if (currentStyle.GetLeftIndent() != style.GetLeftIndent() || currentStyle.GetLeftSubIndent() != style.GetLeftSubIndent())
2019 {
2020 // Clash of style - mark as such
2021 multipleStyleAttributes |= wxTEXT_ATTR_LEFT_INDENT;
2022 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_LEFT_INDENT);
2023 }
2024 }
2025 else
2026 currentStyle.SetLeftIndent(style.GetLeftIndent(), style.GetLeftSubIndent());
2027 }
2028
2029 if (style.HasRightIndent() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_RIGHT_INDENT))
2030 {
2031 if (currentStyle.HasRightIndent())
2032 {
2033 if (currentStyle.GetRightIndent() != style.GetRightIndent())
2034 {
2035 // Clash of style - mark as such
2036 multipleStyleAttributes |= wxTEXT_ATTR_RIGHT_INDENT;
2037 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_RIGHT_INDENT);
2038 }
2039 }
2040 else
2041 currentStyle.SetRightIndent(style.GetRightIndent());
2042 }
2043
2044 if (style.HasParagraphSpacingAfter() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_PARA_SPACING_AFTER))
2045 {
2046 if (currentStyle.HasParagraphSpacingAfter())
2047 {
2048 if (currentStyle.GetParagraphSpacingAfter() != style.GetParagraphSpacingAfter())
2049 {
2050 // Clash of style - mark as such
2051 multipleStyleAttributes |= wxTEXT_ATTR_PARA_SPACING_AFTER;
2052 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_PARA_SPACING_AFTER);
2053 }
2054 }
2055 else
2056 currentStyle.SetParagraphSpacingAfter(style.GetParagraphSpacingAfter());
2057 }
2058
2059 if (style.HasParagraphSpacingBefore() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_PARA_SPACING_BEFORE))
2060 {
2061 if (currentStyle.HasParagraphSpacingBefore())
2062 {
2063 if (currentStyle.GetParagraphSpacingBefore() != style.GetParagraphSpacingBefore())
2064 {
2065 // Clash of style - mark as such
2066 multipleStyleAttributes |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
2067 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_PARA_SPACING_BEFORE);
2068 }
2069 }
2070 else
2071 currentStyle.SetParagraphSpacingBefore(style.GetParagraphSpacingBefore());
2072 }
2073
2074 if (style.HasLineSpacing() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_LINE_SPACING))
2075 {
2076 if (currentStyle.HasLineSpacing())
2077 {
2078 if (currentStyle.GetLineSpacing() != style.GetLineSpacing())
2079 {
2080 // Clash of style - mark as such
2081 multipleStyleAttributes |= wxTEXT_ATTR_LINE_SPACING;
2082 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_LINE_SPACING);
2083 }
2084 }
2085 else
2086 currentStyle.SetLineSpacing(style.GetLineSpacing());
2087 }
2088
2089 if (style.HasCharacterStyleName() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_CHARACTER_STYLE_NAME))
2090 {
2091 if (currentStyle.HasCharacterStyleName())
2092 {
2093 if (currentStyle.GetCharacterStyleName() != style.GetCharacterStyleName())
2094 {
2095 // Clash of style - mark as such
2096 multipleStyleAttributes |= wxTEXT_ATTR_CHARACTER_STYLE_NAME;
2097 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_CHARACTER_STYLE_NAME);
2098 }
2099 }
2100 else
2101 currentStyle.SetCharacterStyleName(style.GetCharacterStyleName());
2102 }
2103
2104 if (style.HasParagraphStyleName() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_PARAGRAPH_STYLE_NAME))
2105 {
2106 if (currentStyle.HasParagraphStyleName())
2107 {
2108 if (currentStyle.GetParagraphStyleName() != style.GetParagraphStyleName())
2109 {
2110 // Clash of style - mark as such
2111 multipleStyleAttributes |= wxTEXT_ATTR_PARAGRAPH_STYLE_NAME;
2112 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_PARAGRAPH_STYLE_NAME);
2113 }
2114 }
2115 else
2116 currentStyle.SetParagraphStyleName(style.GetParagraphStyleName());
2117 }
2118
2119 if (style.HasListStyleName() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_LIST_STYLE_NAME))
2120 {
2121 if (currentStyle.HasListStyleName())
2122 {
2123 if (currentStyle.GetListStyleName() != style.GetListStyleName())
2124 {
2125 // Clash of style - mark as such
2126 multipleStyleAttributes |= wxTEXT_ATTR_LIST_STYLE_NAME;
2127 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_LIST_STYLE_NAME);
2128 }
2129 }
2130 else
2131 currentStyle.SetListStyleName(style.GetListStyleName());
2132 }
2133
2134 if (style.HasBulletStyle() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_BULLET_STYLE))
2135 {
2136 if (currentStyle.HasBulletStyle())
2137 {
2138 if (currentStyle.GetBulletStyle() != style.GetBulletStyle())
2139 {
2140 // Clash of style - mark as such
2141 multipleStyleAttributes |= wxTEXT_ATTR_BULLET_STYLE;
2142 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_BULLET_STYLE);
2143 }
2144 }
2145 else
2146 currentStyle.SetBulletStyle(style.GetBulletStyle());
2147 }
2148
2149 if (style.HasBulletNumber() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_BULLET_NUMBER))
2150 {
2151 if (currentStyle.HasBulletNumber())
2152 {
2153 if (currentStyle.GetBulletNumber() != style.GetBulletNumber())
2154 {
2155 // Clash of style - mark as such
2156 multipleStyleAttributes |= wxTEXT_ATTR_BULLET_NUMBER;
2157 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_BULLET_NUMBER);
2158 }
2159 }
2160 else
2161 currentStyle.SetBulletNumber(style.GetBulletNumber());
2162 }
2163
2164 if (style.HasBulletText() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_BULLET_TEXT))
2165 {
2166 if (currentStyle.HasBulletText())
2167 {
2168 if (currentStyle.GetBulletText() != style.GetBulletText())
2169 {
2170 // Clash of style - mark as such
2171 multipleStyleAttributes |= wxTEXT_ATTR_BULLET_TEXT;
2172 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_BULLET_TEXT);
2173 }
2174 }
2175 else
2176 {
2177 currentStyle.SetBulletText(style.GetBulletText());
2178 currentStyle.SetBulletFont(style.GetBulletFont());
2179 }
2180 }
2181
2182 if (style.HasBulletName() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_BULLET_NAME))
2183 {
2184 if (currentStyle.HasBulletName())
2185 {
2186 if (currentStyle.GetBulletName() != style.GetBulletName())
2187 {
2188 // Clash of style - mark as such
2189 multipleStyleAttributes |= wxTEXT_ATTR_BULLET_NAME;
2190 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_BULLET_NAME);
2191 }
2192 }
2193 else
2194 {
2195 currentStyle.SetBulletName(style.GetBulletName());
2196 }
2197 }
2198
2199 if (style.HasURL() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_URL))
2200 {
2201 if (currentStyle.HasURL())
2202 {
2203 if (currentStyle.GetURL() != style.GetURL())
2204 {
2205 // Clash of style - mark as such
2206 multipleStyleAttributes |= wxTEXT_ATTR_URL;
2207 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_URL);
2208 }
2209 }
2210 else
2211 {
2212 currentStyle.SetURL(style.GetURL());
2213 }
2214 }
2215
2216 if (style.HasTextEffects() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_EFFECTS))
2217 {
2218 if (currentStyle.HasTextEffects())
2219 {
2220 // We need to find the bits in the new style that are different:
2221 // just look at those bits that are specified by the new style.
2222
2223 int currentRelevantTextEffects = currentStyle.GetTextEffects() & style.GetTextEffectFlags();
2224 int newRelevantTextEffects = style.GetTextEffects() & style.GetTextEffectFlags();
2225
2226 if (currentRelevantTextEffects != newRelevantTextEffects)
2227 {
2228 // Find the text effects that were different, using XOR
2229 int differentEffects = currentRelevantTextEffects ^ newRelevantTextEffects;
2230
2231 // Clash of style - mark as such
2232 multipleTextEffectAttributes |= differentEffects;
2233 currentStyle.SetTextEffectFlags(currentStyle.GetTextEffectFlags() & ~differentEffects);
2234 }
2235 }
2236 else
2237 {
2238 currentStyle.SetTextEffects(style.GetTextEffects());
2239 currentStyle.SetTextEffectFlags(style.GetTextEffectFlags());
2240 }
2241 }
2242
2243 if (style.HasOutlineLevel() && !wxHasStyle(multipleStyleAttributes, wxTEXT_ATTR_OUTLINE_LEVEL))
2244 {
2245 if (currentStyle.HasOutlineLevel())
2246 {
2247 if (currentStyle.GetOutlineLevel() != style.GetOutlineLevel())
2248 {
2249 // Clash of style - mark as such
2250 multipleStyleAttributes |= wxTEXT_ATTR_OUTLINE_LEVEL;
2251 currentStyle.SetFlags(currentStyle.GetFlags() & ~wxTEXT_ATTR_OUTLINE_LEVEL);
2252 }
2253 }
2254 else
2255 currentStyle.SetOutlineLevel(style.GetOutlineLevel());
2256 }
2257
2258 return true;
2259 }
2260
2261 /// Get the combined style for a range - if any attribute is different within the range,
2262 /// that attribute is not present within the flags.
2263 /// *** Note that this is not recursive, and so assumes that content inside a paragraph is not itself
2264 /// nested.
2265 bool wxRichTextParagraphLayoutBox::GetStyleForRange(const wxRichTextRange& range, wxTextAttrEx& style)
2266 {
2267 style = wxTextAttrEx();
2268
2269 // The attributes that aren't valid because of multiple styles within the range
2270 long multipleStyleAttributes = 0;
2271 int multipleTextEffectAttributes = 0;
2272
2273 wxRichTextObjectList::compatibility_iterator node = GetChildren().GetFirst();
2274 while (node)
2275 {
2276 wxRichTextParagraph* para = (wxRichTextParagraph*) node->GetData();
2277 if (!(para->GetRange().GetStart() > range.GetEnd() || para->GetRange().GetEnd() < range.GetStart()))
2278 {
2279 if (para->GetChildren().GetCount() == 0)
2280 {
2281 wxTextAttrEx paraStyle = para->GetCombinedAttributes();
2282
2283 CollectStyle(style, paraStyle, multipleStyleAttributes, multipleTextEffectAttributes);
2284 }
2285 else
2286 {
2287 wxRichTextRange paraRange(para->GetRange());
2288 paraRange.LimitTo(range);
2289
2290 // First collect paragraph attributes only
2291 wxTextAttrEx paraStyle = para->GetCombinedAttributes();
2292 paraStyle.SetFlags(paraStyle.GetFlags() & wxTEXT_ATTR_PARAGRAPH);
2293 CollectStyle(style, paraStyle, multipleStyleAttributes, multipleTextEffectAttributes);
2294
2295 wxRichTextObjectList::compatibility_iterator childNode = para->GetChildren().GetFirst();
2296
2297 while (childNode)
2298 {
2299 wxRichTextObject* child = childNode->GetData();
2300 if (!(child->GetRange().GetStart() > range.GetEnd() || child->GetRange().GetEnd() < range.GetStart()))
2301 {
2302 wxTextAttrEx childStyle = para->GetCombinedAttributes(child->GetAttributes());
2303
2304 // Now collect character attributes only
2305 childStyle.SetFlags(childStyle.GetFlags() & wxTEXT_ATTR_CHARACTER);
2306
2307 CollectStyle(style, childStyle, multipleStyleAttributes, multipleTextEffectAttributes);
2308 }
2309
2310 childNode = childNode->GetNext();
2311 }
2312 }
2313 }
2314 node = node->GetNext();
2315 }
2316 return true;
2317 }
2318
2319 /// Set default style
2320 bool wxRichTextParagraphLayoutBox::SetDefaultStyle(const wxTextAttrEx& style)
2321 {
2322 m_defaultAttributes = style;
2323 return true;
2324 }
2325
2326 /// Test if this whole range has character attributes of the specified kind. If any
2327 /// of the attributes are different within the range, the test fails. You
2328 /// can use this to implement, for example, bold button updating. style must have
2329 /// flags indicating which attributes are of interest.
2330 bool wxRichTextParagraphLayoutBox::HasCharacterAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const
2331 {
2332 int foundCount = 0;
2333 int matchingCount = 0;
2334
2335 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2336 while (node)
2337 {
2338 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2339 wxASSERT (para != NULL);
2340
2341 if (para)
2342 {
2343 // Stop searching if we're beyond the range of interest
2344 if (para->GetRange().GetStart() > range.GetEnd())
2345 return foundCount == matchingCount;
2346
2347 if (!para->GetRange().IsOutside(range))
2348 {
2349 wxRichTextObjectList::compatibility_iterator node2 = para->GetChildren().GetFirst();
2350
2351 while (node2)
2352 {
2353 wxRichTextObject* child = node2->GetData();
2354 if (!child->GetRange().IsOutside(range) && child->IsKindOf(CLASSINFO(wxRichTextPlainText)))
2355 {
2356 foundCount ++;
2357 wxTextAttrEx textAttr = para->GetCombinedAttributes(child->GetAttributes());
2358
2359 if (wxTextAttrEqPartial(textAttr, style, style.GetFlags()))
2360 matchingCount ++;
2361 }
2362
2363 node2 = node2->GetNext();
2364 }
2365 }
2366 }
2367
2368 node = node->GetNext();
2369 }
2370
2371 return foundCount == matchingCount;
2372 }
2373
2374 bool wxRichTextParagraphLayoutBox::HasCharacterAttributes(const wxRichTextRange& range, const wxTextAttrEx& style) const
2375 {
2376 wxRichTextAttr richStyle = style;
2377 return HasCharacterAttributes(range, richStyle);
2378 }
2379
2380 /// Test if this whole range has paragraph attributes of the specified kind. If any
2381 /// of the attributes are different within the range, the test fails. You
2382 /// can use this to implement, for example, centering button updating. style must have
2383 /// flags indicating which attributes are of interest.
2384 bool wxRichTextParagraphLayoutBox::HasParagraphAttributes(const wxRichTextRange& range, const wxRichTextAttr& style) const
2385 {
2386 int foundCount = 0;
2387 int matchingCount = 0;
2388
2389 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2390 while (node)
2391 {
2392 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2393 wxASSERT (para != NULL);
2394
2395 if (para)
2396 {
2397 // Stop searching if we're beyond the range of interest
2398 if (para->GetRange().GetStart() > range.GetEnd())
2399 return foundCount == matchingCount;
2400
2401 if (!para->GetRange().IsOutside(range))
2402 {
2403 wxTextAttrEx textAttr = GetAttributes();
2404 // Apply the paragraph style
2405 wxRichTextApplyStyle(textAttr, para->GetAttributes());
2406
2407 foundCount ++;
2408 if (wxTextAttrEqPartial(textAttr, style, style.GetFlags()))
2409 matchingCount ++;
2410 }
2411 }
2412
2413 node = node->GetNext();
2414 }
2415 return foundCount == matchingCount;
2416 }
2417
2418 bool wxRichTextParagraphLayoutBox::HasParagraphAttributes(const wxRichTextRange& range, const wxTextAttrEx& style) const
2419 {
2420 wxRichTextAttr richStyle = style;
2421 return HasParagraphAttributes(range, richStyle);
2422 }
2423
2424 void wxRichTextParagraphLayoutBox::Clear()
2425 {
2426 DeleteChildren();
2427 }
2428
2429 void wxRichTextParagraphLayoutBox::Reset()
2430 {
2431 Clear();
2432
2433 AddParagraph(wxEmptyString);
2434
2435 Invalidate(wxRICHTEXT_ALL);
2436 }
2437
2438 /// Invalidate the buffer. With no argument, invalidates whole buffer.
2439 void wxRichTextParagraphLayoutBox::Invalidate(const wxRichTextRange& invalidRange)
2440 {
2441 SetDirty(true);
2442
2443 if (invalidRange == wxRICHTEXT_ALL)
2444 {
2445 m_invalidRange = wxRICHTEXT_ALL;
2446 return;
2447 }
2448
2449 // Already invalidating everything
2450 if (m_invalidRange == wxRICHTEXT_ALL)
2451 return;
2452
2453 if ((invalidRange.GetStart() < m_invalidRange.GetStart()) || m_invalidRange.GetStart() == -1)
2454 m_invalidRange.SetStart(invalidRange.GetStart());
2455 if (invalidRange.GetEnd() > m_invalidRange.GetEnd())
2456 m_invalidRange.SetEnd(invalidRange.GetEnd());
2457 }
2458
2459 /// Get invalid range, rounding to entire paragraphs if argument is true.
2460 wxRichTextRange wxRichTextParagraphLayoutBox::GetInvalidRange(bool wholeParagraphs) const
2461 {
2462 if (m_invalidRange == wxRICHTEXT_ALL || m_invalidRange == wxRICHTEXT_NONE)
2463 return m_invalidRange;
2464
2465 wxRichTextRange range = m_invalidRange;
2466
2467 if (wholeParagraphs)
2468 {
2469 wxRichTextParagraph* para1 = GetParagraphAtPosition(range.GetStart());
2470 wxRichTextParagraph* para2 = GetParagraphAtPosition(range.GetEnd());
2471 if (para1)
2472 range.SetStart(para1->GetRange().GetStart());
2473 if (para2)
2474 range.SetEnd(para2->GetRange().GetEnd());
2475 }
2476 return range;
2477 }
2478
2479 /// Apply the style sheet to the buffer, for example if the styles have changed.
2480 bool wxRichTextParagraphLayoutBox::ApplyStyleSheet(wxRichTextStyleSheet* styleSheet)
2481 {
2482 wxASSERT(styleSheet != NULL);
2483 if (!styleSheet)
2484 return false;
2485
2486 int foundCount = 0;
2487
2488 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2489 while (node)
2490 {
2491 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2492 wxASSERT (para != NULL);
2493
2494 if (para)
2495 {
2496 // Combine paragraph and list styles. If there is a list style in the original attributes,
2497 // the current indentation overrides anything else and is used to find the item indentation.
2498 // Also, for applying paragraph styles, consider having 2 modes: (1) we merge with what we have,
2499 // thereby taking into account all user changes, (2) reset the style completely (except for indentation/list
2500 // exception as above).
2501 // Problem: when changing from one list style to another, there's a danger that the level info will get lost.
2502 // So when changing a list style interactively, could retrieve level based on current style, then
2503 // set appropriate indent and apply new style.
2504
2505 if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
2506 {
2507 int currentIndent = para->GetAttributes().GetLeftIndent();
2508
2509 wxRichTextParagraphStyleDefinition* paraDef = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
2510 wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
2511 if (paraDef && !listDef)
2512 {
2513 para->GetAttributes() = paraDef->GetStyle();
2514 foundCount ++;
2515 }
2516 else if (listDef && !paraDef)
2517 {
2518 // Set overall style defined for the list style definition
2519 para->GetAttributes() = listDef->GetStyle();
2520
2521 // Apply the style for this level
2522 wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
2523 foundCount ++;
2524 }
2525 else if (listDef && paraDef)
2526 {
2527 // Combines overall list style, style for level, and paragraph style
2528 para->GetAttributes() = listDef->CombineWithParagraphStyle(currentIndent, paraDef->GetStyle());
2529 foundCount ++;
2530 }
2531 }
2532 else if (para->GetAttributes().GetParagraphStyleName().IsEmpty() && !para->GetAttributes().GetListStyleName().IsEmpty())
2533 {
2534 int currentIndent = para->GetAttributes().GetLeftIndent();
2535
2536 wxRichTextListStyleDefinition* listDef = styleSheet->FindListStyle(para->GetAttributes().GetListStyleName());
2537
2538 // Overall list definition style
2539 para->GetAttributes() = listDef->GetStyle();
2540
2541 // Style for this level
2542 wxRichTextApplyStyle(para->GetAttributes(), * listDef->GetLevelAttributes(listDef->FindLevelForIndent(currentIndent)));
2543
2544 foundCount ++;
2545 }
2546 else if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && para->GetAttributes().GetListStyleName().IsEmpty())
2547 {
2548 wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
2549 if (def)
2550 {
2551 para->GetAttributes() = def->GetStyle();
2552 foundCount ++;
2553 }
2554 }
2555 }
2556
2557 node = node->GetNext();
2558 }
2559 return foundCount != 0;
2560 }
2561
2562 /// Set list style
2563 bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
2564 {
2565 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
2566 // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
2567 bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
2568 bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
2569
2570 // Current number, if numbering
2571 int n = startFrom;
2572
2573 wxASSERT (!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
2574
2575 // If we are associated with a control, make undoable; otherwise, apply immediately
2576 // to the data.
2577
2578 bool haveControl = (GetRichTextCtrl() != NULL);
2579
2580 wxRichTextAction* action = NULL;
2581
2582 if (haveControl && withUndo)
2583 {
2584 action = new wxRichTextAction(NULL, _("Change List Style"), wxRICHTEXT_CHANGE_STYLE, & GetRichTextCtrl()->GetBuffer(), GetRichTextCtrl());
2585 action->SetRange(range);
2586 action->SetPosition(GetRichTextCtrl()->GetCaretPosition());
2587 }
2588
2589 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2590 while (node)
2591 {
2592 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2593 wxASSERT (para != NULL);
2594
2595 if (para && para->GetChildCount() > 0)
2596 {
2597 // Stop searching if we're beyond the range of interest
2598 if (para->GetRange().GetStart() > range.GetEnd())
2599 break;
2600
2601 if (!para->GetRange().IsOutside(range))
2602 {
2603 // We'll be using a copy of the paragraph to make style changes,
2604 // not updating the buffer directly.
2605 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
2606
2607 if (haveControl && withUndo)
2608 {
2609 newPara = new wxRichTextParagraph(*para);
2610 action->GetNewParagraphs().AppendChild(newPara);
2611
2612 // Also store the old ones for Undo
2613 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
2614 }
2615 else
2616 newPara = para;
2617
2618 if (def)
2619 {
2620 int thisIndent = newPara->GetAttributes().GetLeftIndent();
2621 int thisLevel = specifyLevel ? specifiedLevel : def->FindLevelForIndent(thisIndent);
2622
2623 // How is numbering going to work?
2624 // If we are renumbering, or numbering for the first time, we need to keep
2625 // track of the number for each level. But we might be simply applying a different
2626 // list style.
2627 // In Word, applying a style to several paragraphs, even if at different levels,
2628 // reverts the level back to the same one. So we could do the same here.
2629 // Renumbering will need to be done when we promote/demote a paragraph.
2630
2631 // Apply the overall list style, and item style for this level
2632 wxTextAttrEx listStyle(def->GetCombinedStyleForLevel(thisLevel));
2633 wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
2634
2635 // Now we need to do numbering
2636 if (renumber)
2637 {
2638 newPara->GetAttributes().SetBulletNumber(n);
2639 }
2640
2641 n ++;
2642 }
2643 else if (!newPara->GetAttributes().GetListStyleName().IsEmpty())
2644 {
2645 // if def is NULL, remove list style, applying any associated paragraph style
2646 // to restore the attributes
2647
2648 newPara->GetAttributes().SetListStyleName(wxEmptyString);
2649 newPara->GetAttributes().SetLeftIndent(0, 0);
2650 newPara->GetAttributes().SetBulletText(wxEmptyString);
2651
2652 // Eliminate the main list-related attributes
2653 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);
2654
2655 wxRichTextStyleSheet* styleSheet = GetStyleSheet();
2656 if (styleSheet && !newPara->GetAttributes().GetParagraphStyleName().IsEmpty())
2657 {
2658 wxRichTextParagraphStyleDefinition* def = styleSheet->FindParagraphStyle(newPara->GetAttributes().GetParagraphStyleName());
2659 if (def)
2660 {
2661 newPara->GetAttributes() = def->GetStyle();
2662 }
2663 }
2664 }
2665 }
2666 }
2667
2668 node = node->GetNext();
2669 }
2670
2671 // Do action, or delay it until end of batch.
2672 if (haveControl && withUndo)
2673 GetRichTextCtrl()->GetBuffer().SubmitAction(action);
2674
2675 return true;
2676 }
2677
2678 bool wxRichTextParagraphLayoutBox::SetListStyle(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
2679 {
2680 if (GetStyleSheet())
2681 {
2682 wxRichTextListStyleDefinition* def = GetStyleSheet()->FindListStyle(defName);
2683 if (def)
2684 return SetListStyle(range, def, flags, startFrom, specifiedLevel);
2685 }
2686 return false;
2687 }
2688
2689 /// Clear list for given range
2690 bool wxRichTextParagraphLayoutBox::ClearListStyle(const wxRichTextRange& range, int flags)
2691 {
2692 return SetListStyle(range, NULL, flags);
2693 }
2694
2695 /// Number/renumber any list elements in the given range
2696 bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
2697 {
2698 return DoNumberList(range, range, 0, def, flags, startFrom, specifiedLevel);
2699 }
2700
2701 /// Number/renumber any list elements in the given range. Also do promotion or demotion of items, if specified
2702 bool wxRichTextParagraphLayoutBox::DoNumberList(const wxRichTextRange& range, const wxRichTextRange& promotionRange, int promoteBy,
2703 wxRichTextListStyleDefinition* def, int flags, int startFrom, int specifiedLevel)
2704 {
2705 bool withUndo = ((flags & wxRICHTEXT_SETSTYLE_WITH_UNDO) != 0);
2706 // bool applyMinimal = ((flags & wxRICHTEXT_SETSTYLE_OPTIMIZE) != 0);
2707 #ifdef __WXDEBUG__
2708 bool specifyLevel = ((flags & wxRICHTEXT_SETSTYLE_SPECIFY_LEVEL) != 0);
2709 #endif
2710
2711 bool renumber = ((flags & wxRICHTEXT_SETSTYLE_RENUMBER) != 0);
2712
2713 // Max number of levels
2714 const int maxLevels = 10;
2715
2716 // The level we're looking at now
2717 int currentLevel = -1;
2718
2719 // The item number for each level
2720 int levels[maxLevels];
2721 int i;
2722
2723 // Reset all numbering
2724 for (i = 0; i < maxLevels; i++)
2725 {
2726 if (startFrom != -1)
2727 levels[i] = startFrom-1;
2728 else if (renumber) // start again
2729 levels[i] = 0;
2730 else
2731 levels[i] = -1; // start from the number we found, if any
2732 }
2733
2734 wxASSERT(!specifyLevel || (specifyLevel && (specifiedLevel >= 0)));
2735
2736 // If we are associated with a control, make undoable; otherwise, apply immediately
2737 // to the data.
2738
2739 bool haveControl = (GetRichTextCtrl() != NULL);
2740
2741 wxRichTextAction* action = NULL;
2742
2743 if (haveControl && withUndo)
2744 {
2745 action = new wxRichTextAction(NULL, _("Renumber List"), wxRICHTEXT_CHANGE_STYLE, & GetRichTextCtrl()->GetBuffer(), GetRichTextCtrl());
2746 action->SetRange(range);
2747 action->SetPosition(GetRichTextCtrl()->GetCaretPosition());
2748 }
2749
2750 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
2751 while (node)
2752 {
2753 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
2754 wxASSERT (para != NULL);
2755
2756 if (para && para->GetChildCount() > 0)
2757 {
2758 // Stop searching if we're beyond the range of interest
2759 if (para->GetRange().GetStart() > range.GetEnd())
2760 break;
2761
2762 if (!para->GetRange().IsOutside(range))
2763 {
2764 // We'll be using a copy of the paragraph to make style changes,
2765 // not updating the buffer directly.
2766 wxRichTextParagraph* newPara wxDUMMY_INITIALIZE(NULL);
2767
2768 if (haveControl && withUndo)
2769 {
2770 newPara = new wxRichTextParagraph(*para);
2771 action->GetNewParagraphs().AppendChild(newPara);
2772
2773 // Also store the old ones for Undo
2774 action->GetOldParagraphs().AppendChild(new wxRichTextParagraph(*para));
2775 }
2776 else
2777 newPara = para;
2778
2779 wxRichTextListStyleDefinition* defToUse = def;
2780 if (!defToUse)
2781 {
2782 wxRichTextStyleSheet* sheet = GetStyleSheet();
2783
2784 if (sheet && !newPara->GetAttributes().GetListStyleName().IsEmpty())
2785 defToUse = sheet->FindListStyle(newPara->GetAttributes().GetListStyleName());
2786 }
2787
2788 if (defToUse)
2789 {
2790 int thisIndent = newPara->GetAttributes().GetLeftIndent();
2791 int thisLevel = defToUse->FindLevelForIndent(thisIndent);
2792
2793 // If we've specified a level to apply to all, change the level.
2794 if (specifiedLevel != -1)
2795 thisLevel = specifiedLevel;
2796
2797 // Do promotion if specified
2798 if ((promoteBy != 0) && !para->GetRange().IsOutside(promotionRange))
2799 {
2800 thisLevel = thisLevel - promoteBy;
2801 if (thisLevel < 0)
2802 thisLevel = 0;
2803 if (thisLevel > 9)
2804 thisLevel = 9;
2805 }
2806
2807 // Apply the overall list style, and item style for this level
2808 wxTextAttrEx listStyle(defToUse->GetCombinedStyleForLevel(thisLevel));
2809 wxRichTextApplyStyle(newPara->GetAttributes(), listStyle);
2810
2811 // OK, we've (re)applied the style, now let's get the numbering right.
2812
2813 if (currentLevel == -1)
2814 currentLevel = thisLevel;
2815
2816 // Same level as before, do nothing except increment level's number afterwards
2817 if (currentLevel == thisLevel)
2818 {
2819 }
2820 // A deeper level: start renumbering all levels after current level
2821 else if (thisLevel > currentLevel)
2822 {
2823 for (i = currentLevel+1; i <= thisLevel; i++)
2824 {
2825 levels[i] = 0;
2826 }
2827 currentLevel = thisLevel;
2828 }
2829 else if (thisLevel < currentLevel)
2830 {
2831 currentLevel = thisLevel;
2832 }
2833
2834 // Use the current numbering if -1 and we have a bullet number already
2835 if (levels[currentLevel] == -1)
2836 {
2837 if (newPara->GetAttributes().HasBulletNumber())
2838 levels[currentLevel] = newPara->GetAttributes().GetBulletNumber();
2839 else
2840 levels[currentLevel] = 1;
2841 }
2842 else
2843 {
2844 levels[currentLevel] ++;
2845 }
2846
2847 newPara->GetAttributes().SetBulletNumber(levels[currentLevel]);
2848
2849 // Create the bullet text if an outline list
2850 if (listStyle.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
2851 {
2852 wxString text;
2853 for (i = 0; i <= currentLevel; i++)
2854 {
2855 if (!text.IsEmpty())
2856 text += wxT(".");
2857 text += wxString::Format(wxT("%d"), levels[i]);
2858 }
2859 newPara->GetAttributes().SetBulletText(text);
2860 }
2861 }
2862 }
2863 }
2864
2865 node = node->GetNext();
2866 }
2867
2868 // Do action, or delay it until end of batch.
2869 if (haveControl && withUndo)
2870 GetRichTextCtrl()->GetBuffer().SubmitAction(action);
2871
2872 return true;
2873 }
2874
2875 bool wxRichTextParagraphLayoutBox::NumberList(const wxRichTextRange& range, const wxString& defName, int flags, int startFrom, int specifiedLevel)
2876 {
2877 if (GetStyleSheet())
2878 {
2879 wxRichTextListStyleDefinition* def = NULL;
2880 if (!defName.IsEmpty())
2881 def = GetStyleSheet()->FindListStyle(defName);
2882 return NumberList(range, def, flags, startFrom, specifiedLevel);
2883 }
2884 return false;
2885 }
2886
2887 /// Promote the list items within the given range. promoteBy can be a positive or negative number, e.g. 1 or -1
2888 bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, wxRichTextListStyleDefinition* def, int flags, int specifiedLevel)
2889 {
2890 // TODO
2891 // One strategy is to first work out the range within which renumbering must occur. Then could pass these two ranges
2892 // to NumberList with a flag indicating promotion is required within one of the ranges.
2893 // Find first and last paragraphs in range. Then for first, calculate new indentation and look back until we find
2894 // a paragraph that either has no list style, or has one that is different or whose indentation is less.
2895 // We start renumbering from the para after that different para we found. We specify that the numbering of that
2896 // list position will start from 1.
2897 // Similarly, we look after the last para in the promote range for an indentation that is less (or no list style).
2898 // We can end the renumbering at this point.
2899
2900 // For now, only renumber within the promotion range.
2901
2902 return DoNumberList(range, range, promoteBy, def, flags, 1, specifiedLevel);
2903 }
2904
2905 bool wxRichTextParagraphLayoutBox::PromoteList(int promoteBy, const wxRichTextRange& range, const wxString& defName, int flags, int specifiedLevel)
2906 {
2907 if (GetStyleSheet())
2908 {
2909 wxRichTextListStyleDefinition* def = NULL;
2910 if (!defName.IsEmpty())
2911 def = GetStyleSheet()->FindListStyle(defName);
2912 return PromoteList(promoteBy, range, def, flags, specifiedLevel);
2913 }
2914 return false;
2915 }
2916
2917 /// Fills in the attributes for numbering a paragraph after previousParagraph. It also finds the
2918 /// position of the paragraph that it had to start looking from.
2919 bool wxRichTextParagraphLayoutBox::FindNextParagraphNumber(wxRichTextParagraph* previousParagraph, wxRichTextAttr& attr) const
2920 {
2921 if (!previousParagraph->GetAttributes().HasFlag(wxTEXT_ATTR_BULLET_STYLE) || previousParagraph->GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE)
2922 return false;
2923
2924 wxRichTextStyleSheet* sheet = GetStyleSheet();
2925 if (sheet && !previousParagraph->GetAttributes().GetListStyleName().IsEmpty())
2926 {
2927 wxRichTextListStyleDefinition* def = sheet->FindListStyle(previousParagraph->GetAttributes().GetListStyleName());
2928 if (def)
2929 {
2930 // int thisIndent = previousParagraph->GetAttributes().GetLeftIndent();
2931 // int thisLevel = def->FindLevelForIndent(thisIndent);
2932
2933 bool isOutline = (previousParagraph->GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE) != 0;
2934
2935 attr.SetFlags(previousParagraph->GetAttributes().GetFlags() & (wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_BULLET_NUMBER|wxTEXT_ATTR_BULLET_TEXT|wxTEXT_ATTR_BULLET_NAME));
2936 if (previousParagraph->GetAttributes().HasBulletName())
2937 attr.SetBulletName(previousParagraph->GetAttributes().GetBulletName());
2938 attr.SetBulletStyle(previousParagraph->GetAttributes().GetBulletStyle());
2939 attr.SetListStyleName(previousParagraph->GetAttributes().GetListStyleName());
2940
2941 int nextNumber = previousParagraph->GetAttributes().GetBulletNumber() + 1;
2942 attr.SetBulletNumber(nextNumber);
2943
2944 if (isOutline)
2945 {
2946 wxString text = previousParagraph->GetAttributes().GetBulletText();
2947 if (!text.IsEmpty())
2948 {
2949 int pos = text.Find(wxT('.'), true);
2950 if (pos != wxNOT_FOUND)
2951 {
2952 text = text.Mid(0, text.Length() - pos - 1);
2953 }
2954 else
2955 text = wxEmptyString;
2956 if (!text.IsEmpty())
2957 text += wxT(".");
2958 text += wxString::Format(wxT("%d"), nextNumber);
2959 attr.SetBulletText(text);
2960 }
2961 }
2962
2963 return true;
2964 }
2965 else
2966 return false;
2967 }
2968 else
2969 return false;
2970 }
2971
2972 /*!
2973 * wxRichTextParagraph
2974 * This object represents a single paragraph (or in a straight text editor, a line).
2975 */
2976
2977 IMPLEMENT_DYNAMIC_CLASS(wxRichTextParagraph, wxRichTextBox)
2978
2979 wxArrayInt wxRichTextParagraph::sm_defaultTabs;
2980
2981 wxRichTextParagraph::wxRichTextParagraph(wxRichTextObject* parent, wxTextAttrEx* style):
2982 wxRichTextBox(parent)
2983 {
2984 if (style)
2985 SetAttributes(*style);
2986 }
2987
2988 wxRichTextParagraph::wxRichTextParagraph(const wxString& text, wxRichTextObject* parent, wxTextAttrEx* paraStyle, wxTextAttrEx* charStyle):
2989 wxRichTextBox(parent)
2990 {
2991 if (paraStyle)
2992 SetAttributes(*paraStyle);
2993
2994 AppendChild(new wxRichTextPlainText(text, this, charStyle));
2995 }
2996
2997 wxRichTextParagraph::~wxRichTextParagraph()
2998 {
2999 ClearLines();
3000 }
3001
3002 /// Draw the item
3003 bool wxRichTextParagraph::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& WXUNUSED(rect), int WXUNUSED(descent), int style)
3004 {
3005 wxTextAttrEx attr = GetCombinedAttributes();
3006
3007 // Draw the bullet, if any
3008 if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
3009 {
3010 if (attr.GetLeftSubIndent() != 0)
3011 {
3012 int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
3013 int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
3014
3015 wxTextAttrEx bulletAttr(GetCombinedAttributes());
3016
3017 // Get line height from first line, if any
3018 wxRichTextLine* line = m_cachedLines.GetFirst() ? (wxRichTextLine* ) m_cachedLines.GetFirst()->GetData() : (wxRichTextLine*) NULL;
3019
3020 wxPoint linePos;
3021 int lineHeight wxDUMMY_INITIALIZE(0);
3022 if (line)
3023 {
3024 lineHeight = line->GetSize().y;
3025 linePos = line->GetPosition() + GetPosition();
3026 }
3027 else
3028 {
3029 wxFont font;
3030 if (bulletAttr.GetFont().Ok())
3031 font = bulletAttr.GetFont();
3032 else
3033 font = (*wxNORMAL_FONT);
3034
3035 dc.SetFont(font);
3036
3037 lineHeight = dc.GetCharHeight();
3038 linePos = GetPosition();
3039 linePos.y += spaceBeforePara;
3040 }
3041
3042 wxRect bulletRect(GetPosition().x + leftIndent, linePos.y, linePos.x - (GetPosition().x + leftIndent), lineHeight);
3043
3044 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP)
3045 {
3046 if (wxRichTextBuffer::GetRenderer())
3047 wxRichTextBuffer::GetRenderer()->DrawBitmapBullet(this, dc, bulletAttr, bulletRect);
3048 }
3049 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_STANDARD)
3050 {
3051 if (wxRichTextBuffer::GetRenderer())
3052 wxRichTextBuffer::GetRenderer()->DrawStandardBullet(this, dc, bulletAttr, bulletRect);
3053 }
3054 else
3055 {
3056 wxString bulletText = GetBulletText();
3057
3058 if (!bulletText.empty() && wxRichTextBuffer::GetRenderer())
3059 wxRichTextBuffer::GetRenderer()->DrawTextBullet(this, dc, bulletAttr, bulletRect, bulletText);
3060 }
3061 }
3062 }
3063
3064 // Draw the range for each line, one object at a time.
3065
3066 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
3067 while (node)
3068 {
3069 wxRichTextLine* line = node->GetData();
3070 wxRichTextRange lineRange = line->GetAbsoluteRange();
3071
3072 int maxDescent = line->GetDescent();
3073
3074 // Lines are specified relative to the paragraph
3075
3076 wxPoint linePosition = line->GetPosition() + GetPosition();
3077 wxPoint objectPosition = linePosition;
3078
3079 // Loop through objects until we get to the one within range
3080 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
3081 while (node2)
3082 {
3083 wxRichTextObject* child = node2->GetData();
3084
3085 if (!child->GetRange().IsOutside(lineRange) && !lineRange.IsOutside(range))
3086 {
3087 // Draw this part of the line at the correct position
3088 wxRichTextRange objectRange(child->GetRange());
3089 objectRange.LimitTo(lineRange);
3090
3091 wxSize objectSize;
3092 int descent = 0;
3093 child->GetRangeSize(objectRange, objectSize, descent, dc, wxRICHTEXT_UNFORMATTED, objectPosition);
3094
3095 // Use the child object's width, but the whole line's height
3096 wxRect childRect(objectPosition, wxSize(objectSize.x, line->GetSize().y));
3097 child->Draw(dc, objectRange, selectionRange, childRect, maxDescent, style);
3098
3099 objectPosition.x += objectSize.x;
3100 }
3101 else if (child->GetRange().GetStart() > lineRange.GetEnd())
3102 // Can break out of inner loop now since we've passed this line's range
3103 break;
3104
3105 node2 = node2->GetNext();
3106 }
3107
3108 node = node->GetNext();
3109 }
3110
3111 return true;
3112 }
3113
3114 /// Lay the item out
3115 bool wxRichTextParagraph::Layout(wxDC& dc, const wxRect& rect, int style)
3116 {
3117 wxTextAttrEx attr = GetCombinedAttributes();
3118
3119 // ClearLines();
3120
3121 // Increase the size of the paragraph due to spacing
3122 int spaceBeforePara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingBefore());
3123 int spaceAfterPara = ConvertTenthsMMToPixels(dc, attr.GetParagraphSpacingAfter());
3124 int leftIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftIndent());
3125 int leftSubIndent = ConvertTenthsMMToPixels(dc, attr.GetLeftSubIndent());
3126 int rightIndent = ConvertTenthsMMToPixels(dc, attr.GetRightIndent());
3127
3128 int lineSpacing = 0;
3129
3130 // Let's assume line spacing of 10 is normal, 15 is 1.5, 20 is 2, etc.
3131 if (attr.GetLineSpacing() > 10 && attr.GetFont().Ok())
3132 {
3133 dc.SetFont(attr.GetFont());
3134 lineSpacing = (ConvertTenthsMMToPixels(dc, dc.GetCharHeight()) * attr.GetLineSpacing())/10;
3135 }
3136
3137 // Available space for text on each line differs.
3138 int availableTextSpaceFirstLine = rect.GetWidth() - leftIndent - rightIndent;
3139
3140 // Bullets start the text at the same position as subsequent lines
3141 if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
3142 availableTextSpaceFirstLine -= leftSubIndent;
3143
3144 int availableTextSpaceSubsequentLines = rect.GetWidth() - leftIndent - rightIndent - leftSubIndent;
3145
3146 // Start position for each line relative to the paragraph
3147 int startPositionFirstLine = leftIndent;
3148 int startPositionSubsequentLines = leftIndent + leftSubIndent;
3149
3150 // If we have a bullet in this paragraph, the start position for the first line's text
3151 // is actually leftIndent + leftSubIndent.
3152 if (attr.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE)
3153 startPositionFirstLine = startPositionSubsequentLines;
3154
3155 long lastEndPos = GetRange().GetStart()-1;
3156 long lastCompletedEndPos = lastEndPos;
3157
3158 int currentWidth = 0;
3159 SetPosition(rect.GetPosition());
3160
3161 wxPoint currentPosition(0, spaceBeforePara); // We will calculate lines relative to paragraph
3162 int lineHeight = 0;
3163 int maxWidth = 0;
3164 int maxDescent = 0;
3165
3166 int lineCount = 0;
3167
3168 // Split up lines
3169
3170 // We may need to go back to a previous child, in which case create the new line,
3171 // find the child corresponding to the start position of the string, and
3172 // continue.
3173
3174 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3175 while (node)
3176 {
3177 wxRichTextObject* child = node->GetData();
3178
3179 // If this is e.g. a composite text box, it will need to be laid out itself.
3180 // But if just a text fragment or image, for example, this will
3181 // do nothing. NB: won't we need to set the position after layout?
3182 // since for example if position is dependent on vertical line size, we
3183 // can't tell the position until the size is determined. So possibly introduce
3184 // another layout phase.
3185
3186 child->Layout(dc, rect, style);
3187
3188 // Available width depends on whether we're on the first or subsequent lines
3189 int availableSpaceForText = (lineCount == 0 ? availableTextSpaceFirstLine : availableTextSpaceSubsequentLines);
3190
3191 currentPosition.x = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
3192
3193 // We may only be looking at part of a child, if we searched back for wrapping
3194 // and found a suitable point some way into the child. So get the size for the fragment
3195 // if necessary.
3196
3197 wxSize childSize;
3198 int childDescent = 0;
3199 if (lastEndPos == child->GetRange().GetStart() - 1)
3200 {
3201 childSize = child->GetCachedSize();
3202 childDescent = child->GetDescent();
3203 }
3204 else
3205 GetRangeSize(wxRichTextRange(lastEndPos+1, child->GetRange().GetEnd()), childSize, childDescent, dc, wxRICHTEXT_UNFORMATTED,rect.GetPosition());
3206
3207 if (childSize.x + currentWidth > availableSpaceForText)
3208 {
3209 long wrapPosition = 0;
3210
3211 // Find a place to wrap. This may walk back to previous children,
3212 // for example if a word spans several objects.
3213 if (!FindWrapPosition(wxRichTextRange(lastCompletedEndPos+1, child->GetRange().GetEnd()), dc, availableSpaceForText, wrapPosition))
3214 {
3215 // If the function failed, just cut it off at the end of this child.
3216 wrapPosition = child->GetRange().GetEnd();
3217 }
3218
3219 // FindWrapPosition can still return a value that will put us in an endless wrapping loop
3220 if (wrapPosition <= lastCompletedEndPos)
3221 wrapPosition = wxMax(lastCompletedEndPos+1,child->GetRange().GetEnd());
3222
3223 // wxLogDebug(wxT("Split at %ld"), wrapPosition);
3224
3225 // Let's find the actual size of the current line now
3226 wxSize actualSize;
3227 wxRichTextRange actualRange(lastCompletedEndPos+1, wrapPosition);
3228 GetRangeSize(actualRange, actualSize, childDescent, dc, wxRICHTEXT_UNFORMATTED);
3229 currentWidth = actualSize.x;
3230 lineHeight = wxMax(lineHeight, actualSize.y);
3231 maxDescent = wxMax(childDescent, maxDescent);
3232
3233 // Add a new line
3234 wxRichTextLine* line = AllocateLine(lineCount);
3235
3236 // Set relative range so we won't have to change line ranges when paragraphs are moved
3237 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
3238 line->SetPosition(currentPosition);
3239 line->SetSize(wxSize(currentWidth, lineHeight));
3240 line->SetDescent(maxDescent);
3241
3242 // Now move down a line. TODO: add margins, spacing
3243 currentPosition.y += lineHeight;
3244 currentPosition.y += lineSpacing;
3245 currentWidth = 0;
3246 maxDescent = 0;
3247 maxWidth = wxMax(maxWidth, currentWidth);
3248
3249 lineCount ++;
3250
3251 // TODO: account for zero-length objects, such as fields
3252 wxASSERT(wrapPosition > lastCompletedEndPos);
3253
3254 lastEndPos = wrapPosition;
3255 lastCompletedEndPos = lastEndPos;
3256
3257 lineHeight = 0;
3258
3259 // May need to set the node back to a previous one, due to searching back in wrapping
3260 wxRichTextObject* childAfterWrapPosition = FindObjectAtPosition(wrapPosition+1);
3261 if (childAfterWrapPosition)
3262 node = m_children.Find(childAfterWrapPosition);
3263 else
3264 node = node->GetNext();
3265 }
3266 else
3267 {
3268 // We still fit, so don't add a line, and keep going
3269 currentWidth += childSize.x;
3270 lineHeight = wxMax(lineHeight, childSize.y);
3271 maxDescent = wxMax(childDescent, maxDescent);
3272
3273 maxWidth = wxMax(maxWidth, currentWidth);
3274 lastEndPos = child->GetRange().GetEnd();
3275
3276 node = node->GetNext();
3277 }
3278 }
3279
3280 // Add the last line - it's the current pos -> last para pos
3281 // Substract -1 because the last position is always the end-paragraph position.
3282 if (lastCompletedEndPos <= GetRange().GetEnd()-1)
3283 {
3284 currentPosition.x = (lineCount == 0 ? startPositionFirstLine : startPositionSubsequentLines);
3285
3286 wxRichTextLine* line = AllocateLine(lineCount);
3287
3288 wxRichTextRange actualRange(lastCompletedEndPos+1, GetRange().GetEnd()-1);
3289
3290 // Set relative range so we won't have to change line ranges when paragraphs are moved
3291 line->SetRange(wxRichTextRange(actualRange.GetStart() - GetRange().GetStart(), actualRange.GetEnd() - GetRange().GetStart()));
3292
3293 line->SetPosition(currentPosition);
3294
3295 if (lineHeight == 0)
3296 {
3297 if (attr.GetFont().Ok())
3298 dc.SetFont(attr.GetFont());
3299 lineHeight = dc.GetCharHeight();
3300 }
3301 if (maxDescent == 0)
3302 {
3303 int w, h;
3304 dc.GetTextExtent(wxT("X"), & w, &h, & maxDescent);
3305 }
3306
3307 line->SetSize(wxSize(currentWidth, lineHeight));
3308 line->SetDescent(maxDescent);
3309 currentPosition.y += lineHeight;
3310 currentPosition.y += lineSpacing;
3311 lineCount ++;
3312 }
3313
3314 // Remove remaining unused line objects, if any
3315 ClearUnusedLines(lineCount);
3316
3317 // Apply styles to wrapped lines
3318 ApplyParagraphStyle(attr, rect);
3319
3320 SetCachedSize(wxSize(maxWidth, currentPosition.y + spaceBeforePara + spaceAfterPara));
3321
3322 m_dirty = false;
3323
3324 return true;
3325 }
3326
3327 /// Apply paragraph styles, such as centering, to wrapped lines
3328 void wxRichTextParagraph::ApplyParagraphStyle(const wxTextAttrEx& attr, const wxRect& rect)
3329 {
3330 if (!attr.HasAlignment())
3331 return;
3332
3333 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
3334 while (node)
3335 {
3336 wxRichTextLine* line = node->GetData();
3337
3338 wxPoint pos = line->GetPosition();
3339 wxSize size = line->GetSize();
3340
3341 // centering, right-justification
3342 if (attr.HasAlignment() && GetAttributes().GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
3343 {
3344 pos.x = (rect.GetWidth() - size.x)/2 + pos.x;
3345 line->SetPosition(pos);
3346 }
3347 else if (attr.HasAlignment() && GetAttributes().GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
3348 {
3349 pos.x = pos.x + rect.GetWidth() - size.x;
3350 line->SetPosition(pos);
3351 }
3352
3353 node = node->GetNext();
3354 }
3355 }
3356
3357 /// Insert text at the given position
3358 bool wxRichTextParagraph::InsertText(long pos, const wxString& text)
3359 {
3360 wxRichTextObject* childToUse = NULL;
3361 wxRichTextObjectList::compatibility_iterator nodeToUse = wxRichTextObjectList::compatibility_iterator();
3362
3363 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3364 while (node)
3365 {
3366 wxRichTextObject* child = node->GetData();
3367 if (child->GetRange().Contains(pos) && child->GetRange().GetLength() > 0)
3368 {
3369 childToUse = child;
3370 nodeToUse = node;
3371 break;
3372 }
3373
3374 node = node->GetNext();
3375 }
3376
3377 if (childToUse)
3378 {
3379 wxRichTextPlainText* textObject = wxDynamicCast(childToUse, wxRichTextPlainText);
3380 if (textObject)
3381 {
3382 int posInString = pos - textObject->GetRange().GetStart();
3383
3384 wxString newText = textObject->GetText().Mid(0, posInString) +
3385 text + textObject->GetText().Mid(posInString);
3386 textObject->SetText(newText);
3387
3388 int textLength = text.length();
3389
3390 textObject->SetRange(wxRichTextRange(textObject->GetRange().GetStart(),
3391 textObject->GetRange().GetEnd() + textLength));
3392
3393 // Increment the end range of subsequent fragments in this paragraph.
3394 // We'll set the paragraph range itself at a higher level.
3395
3396 wxRichTextObjectList::compatibility_iterator node = nodeToUse->GetNext();
3397 while (node)
3398 {
3399 wxRichTextObject* child = node->GetData();
3400 child->SetRange(wxRichTextRange(textObject->GetRange().GetStart() + textLength,
3401 textObject->GetRange().GetEnd() + textLength));
3402
3403 node = node->GetNext();
3404 }
3405
3406 return true;
3407 }
3408 else
3409 {
3410 // TODO: if not a text object, insert at closest position, e.g. in front of it
3411 }
3412 }
3413 else
3414 {
3415 // Add at end.
3416 // Don't pass parent initially to suppress auto-setting of parent range.
3417 // We'll do that at a higher level.
3418 wxRichTextPlainText* textObject = new wxRichTextPlainText(text, this);
3419
3420 AppendChild(textObject);
3421 return true;
3422 }
3423
3424 return false;
3425 }
3426
3427 void wxRichTextParagraph::Copy(const wxRichTextParagraph& obj)
3428 {
3429 wxRichTextBox::Copy(obj);
3430 }
3431
3432 /// Clear the cached lines
3433 void wxRichTextParagraph::ClearLines()
3434 {
3435 WX_CLEAR_LIST(wxRichTextLineList, m_cachedLines);
3436 }
3437
3438 /// Get/set the object size for the given range. Returns false if the range
3439 /// is invalid for this object.
3440 bool wxRichTextParagraph::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int flags, wxPoint position) const
3441 {
3442 if (!range.IsWithin(GetRange()))
3443 return false;
3444
3445 if (flags & wxRICHTEXT_UNFORMATTED)
3446 {
3447 // Just use unformatted data, assume no line breaks
3448 // TODO: take into account line breaks
3449
3450 wxSize sz;
3451
3452 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3453 while (node)
3454 {
3455 wxRichTextObject* child = node->GetData();
3456 if (!child->GetRange().IsOutside(range))
3457 {
3458 wxSize childSize;
3459
3460 wxRichTextRange rangeToUse = range;
3461 rangeToUse.LimitTo(child->GetRange());
3462 int childDescent = 0;
3463
3464 if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, flags, position))
3465 {
3466 sz.y = wxMax(sz.y, childSize.y);
3467 sz.x += childSize.x;
3468 descent = wxMax(descent, childDescent);
3469 }
3470 }
3471
3472 node = node->GetNext();
3473 }
3474 size = sz;
3475 }
3476 else
3477 {
3478 // Use formatted data, with line breaks
3479 wxSize sz;
3480
3481 // We're going to loop through each line, and then for each line,
3482 // call GetRangeSize for the fragment that comprises that line.
3483 // Only we have to do that multiple times within the line, because
3484 // the line may be broken into pieces. For now ignore line break commands
3485 // (so we can assume that getting the unformatted size for a fragment
3486 // within a line is the actual size)
3487
3488 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
3489 while (node)
3490 {
3491 wxRichTextLine* line = node->GetData();
3492 wxRichTextRange lineRange = line->GetAbsoluteRange();
3493 if (!lineRange.IsOutside(range))
3494 {
3495 wxSize lineSize;
3496
3497 wxRichTextObjectList::compatibility_iterator node2 = m_children.GetFirst();
3498 while (node2)
3499 {
3500 wxRichTextObject* child = node2->GetData();
3501
3502 if (!child->GetRange().IsOutside(lineRange))
3503 {
3504 wxRichTextRange rangeToUse = lineRange;
3505 rangeToUse.LimitTo(child->GetRange());
3506
3507 wxSize childSize;
3508 int childDescent = 0;
3509 if (child->GetRangeSize(rangeToUse, childSize, childDescent, dc, flags, position))
3510 {
3511 lineSize.y = wxMax(lineSize.y, childSize.y);
3512 lineSize.x += childSize.x;
3513 }
3514 descent = wxMax(descent, childDescent);
3515 }
3516
3517 node2 = node2->GetNext();
3518 }
3519
3520 // Increase size by a line (TODO: paragraph spacing)
3521 sz.y += lineSize.y;
3522 sz.x = wxMax(sz.x, lineSize.x);
3523 }
3524 node = node->GetNext();
3525 }
3526 size = sz;
3527 }
3528 return true;
3529 }
3530
3531 /// Finds the absolute position and row height for the given character position
3532 bool wxRichTextParagraph::FindPosition(wxDC& dc, long index, wxPoint& pt, int* height, bool forceLineStart)
3533 {
3534 if (index == -1)
3535 {
3536 wxRichTextLine* line = ((wxRichTextParagraphLayoutBox*)GetParent())->GetLineAtPosition(0);
3537 if (line)
3538 *height = line->GetSize().y;
3539 else
3540 *height = dc.GetCharHeight();
3541
3542 // -1 means 'the start of the buffer'.
3543 pt = GetPosition();
3544 if (line)
3545 pt = pt + line->GetPosition();
3546
3547 return true;
3548 }
3549
3550 // The final position in a paragraph is taken to mean the position
3551 // at the start of the next paragraph.
3552 if (index == GetRange().GetEnd())
3553 {
3554 wxRichTextParagraphLayoutBox* parent = wxDynamicCast(GetParent(), wxRichTextParagraphLayoutBox);
3555 wxASSERT( parent != NULL );
3556
3557 // Find the height at the next paragraph, if any
3558 wxRichTextLine* line = parent->GetLineAtPosition(index + 1);
3559 if (line)
3560 {
3561 *height = line->GetSize().y;
3562 pt = line->GetAbsolutePosition();
3563 }
3564 else
3565 {
3566 *height = dc.GetCharHeight();
3567 int indent = ConvertTenthsMMToPixels(dc, m_attributes.GetLeftIndent());
3568 pt = wxPoint(indent, GetCachedSize().y);
3569 }
3570
3571 return true;
3572 }
3573
3574 if (index < GetRange().GetStart() || index > GetRange().GetEnd())
3575 return false;
3576
3577 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
3578 while (node)
3579 {
3580 wxRichTextLine* line = node->GetData();
3581 wxRichTextRange lineRange = line->GetAbsoluteRange();
3582 if (index >= lineRange.GetStart() && index <= lineRange.GetEnd())
3583 {
3584 // If this is the last point in the line, and we're forcing the
3585 // returned value to be the start of the next line, do the required
3586 // thing.
3587 if (index == lineRange.GetEnd() && forceLineStart)
3588 {
3589 if (node->GetNext())
3590 {
3591 wxRichTextLine* nextLine = node->GetNext()->GetData();
3592 *height = nextLine->GetSize().y;
3593 pt = nextLine->GetAbsolutePosition();
3594 return true;
3595 }
3596 }
3597
3598 pt.y = line->GetPosition().y + GetPosition().y;
3599
3600 wxRichTextRange r(lineRange.GetStart(), index);
3601 wxSize rangeSize;
3602 int descent = 0;
3603
3604 // We find the size of the line up to this point,
3605 // then we can add this size to the line start position and
3606 // paragraph start position to find the actual position.
3607
3608 if (GetRangeSize(r, rangeSize, descent, dc, wxRICHTEXT_UNFORMATTED, line->GetPosition()+ GetPosition()))
3609 {
3610 pt.x = line->GetPosition().x + GetPosition().x + rangeSize.x;
3611 *height = line->GetSize().y;
3612
3613 return true;
3614 }
3615
3616 }
3617
3618 node = node->GetNext();
3619 }
3620
3621 return false;
3622 }
3623
3624 /// Hit-testing: returns a flag indicating hit test details, plus
3625 /// information about position
3626 int wxRichTextParagraph::HitTest(wxDC& dc, const wxPoint& pt, long& textPosition)
3627 {
3628 wxPoint paraPos = GetPosition();
3629
3630 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetFirst();
3631 while (node)
3632 {
3633 wxRichTextLine* line = node->GetData();
3634 wxPoint linePos = paraPos + line->GetPosition();
3635 wxSize lineSize = line->GetSize();
3636 wxRichTextRange lineRange = line->GetAbsoluteRange();
3637
3638 if (pt.y >= linePos.y && pt.y <= linePos.y + lineSize.y)
3639 {
3640 if (pt.x < linePos.x)
3641 {
3642 textPosition = lineRange.GetStart();
3643 return wxRICHTEXT_HITTEST_BEFORE;
3644 }
3645 else if (pt.x >= (linePos.x + lineSize.x))
3646 {
3647 textPosition = lineRange.GetEnd();
3648 return wxRICHTEXT_HITTEST_AFTER;
3649 }
3650 else
3651 {
3652 long i;
3653 int lastX = linePos.x;
3654 for (i = lineRange.GetStart(); i <= lineRange.GetEnd(); i++)
3655 {
3656 wxSize childSize;
3657 int descent = 0;
3658
3659 wxRichTextRange rangeToUse(lineRange.GetStart(), i);
3660
3661 GetRangeSize(rangeToUse, childSize, descent, dc, wxRICHTEXT_UNFORMATTED, linePos);
3662
3663 int nextX = childSize.x + linePos.x;
3664
3665 if (pt.x >= lastX && pt.x <= nextX)
3666 {
3667 textPosition = i;
3668
3669 // So now we know it's between i-1 and i.
3670 // Let's see if we can be more precise about
3671 // which side of the position it's on.
3672
3673 int midPoint = (nextX - lastX)/2 + lastX;
3674 if (pt.x >= midPoint)
3675 return wxRICHTEXT_HITTEST_AFTER;
3676 else
3677 return wxRICHTEXT_HITTEST_BEFORE;
3678 }
3679 else
3680 {
3681 lastX = nextX;
3682 }
3683 }
3684 }
3685 }
3686
3687 node = node->GetNext();
3688 }
3689
3690 return wxRICHTEXT_HITTEST_NONE;
3691 }
3692
3693 /// Split an object at this position if necessary, and return
3694 /// the previous object, or NULL if inserting at beginning.
3695 wxRichTextObject* wxRichTextParagraph::SplitAt(long pos, wxRichTextObject** previousObject)
3696 {
3697 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3698 while (node)
3699 {
3700 wxRichTextObject* child = node->GetData();
3701
3702 if (pos == child->GetRange().GetStart())
3703 {
3704 if (previousObject)
3705 {
3706 if (node->GetPrevious())
3707 *previousObject = node->GetPrevious()->GetData();
3708 else
3709 *previousObject = NULL;
3710 }
3711
3712 return child;
3713 }
3714
3715 if (child->GetRange().Contains(pos))
3716 {
3717 // This should create a new object, transferring part of
3718 // the content to the old object and the rest to the new object.
3719 wxRichTextObject* newObject = child->DoSplit(pos);
3720
3721 // If we couldn't split this object, just insert in front of it.
3722 if (!newObject)
3723 {
3724 // Maybe this is an empty string, try the next one
3725 // return child;
3726 }
3727 else
3728 {
3729 // Insert the new object after 'child'
3730 if (node->GetNext())
3731 m_children.Insert(node->GetNext(), newObject);
3732 else
3733 m_children.Append(newObject);
3734 newObject->SetParent(this);
3735
3736 if (previousObject)
3737 *previousObject = child;
3738
3739 return newObject;
3740 }
3741 }
3742
3743 node = node->GetNext();
3744 }
3745 if (previousObject)
3746 *previousObject = NULL;
3747 return NULL;
3748 }
3749
3750 /// Move content to a list from obj on
3751 void wxRichTextParagraph::MoveToList(wxRichTextObject* obj, wxList& list)
3752 {
3753 wxRichTextObjectList::compatibility_iterator node = m_children.Find(obj);
3754 while (node)
3755 {
3756 wxRichTextObject* child = node->GetData();
3757 list.Append(child);
3758
3759 wxRichTextObjectList::compatibility_iterator oldNode = node;
3760
3761 node = node->GetNext();
3762
3763 m_children.DeleteNode(oldNode);
3764 }
3765 }
3766
3767 /// Add content back from list
3768 void wxRichTextParagraph::MoveFromList(wxList& list)
3769 {
3770 for (wxList::compatibility_iterator node = list.GetFirst(); node; node = node->GetNext())
3771 {
3772 AppendChild((wxRichTextObject*) node->GetData());
3773 }
3774 }
3775
3776 /// Calculate range
3777 void wxRichTextParagraph::CalculateRange(long start, long& end)
3778 {
3779 wxRichTextCompositeObject::CalculateRange(start, end);
3780
3781 // Add one for end of paragraph
3782 end ++;
3783
3784 m_range.SetRange(start, end);
3785 }
3786
3787 /// Find the object at the given position
3788 wxRichTextObject* wxRichTextParagraph::FindObjectAtPosition(long position)
3789 {
3790 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3791 while (node)
3792 {
3793 wxRichTextObject* obj = node->GetData();
3794 if (obj->GetRange().Contains(position))
3795 return obj;
3796
3797 node = node->GetNext();
3798 }
3799 return NULL;
3800 }
3801
3802 /// Get the plain text searching from the start or end of the range.
3803 /// The resulting string may be shorter than the range given.
3804 bool wxRichTextParagraph::GetContiguousPlainText(wxString& text, const wxRichTextRange& range, bool fromStart)
3805 {
3806 text = wxEmptyString;
3807
3808 if (fromStart)
3809 {
3810 wxRichTextObjectList::compatibility_iterator node = m_children.GetFirst();
3811 while (node)
3812 {
3813 wxRichTextObject* obj = node->GetData();
3814 if (!obj->GetRange().IsOutside(range))
3815 {
3816 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
3817 if (textObj)
3818 {
3819 text += textObj->GetTextForRange(range);
3820 }
3821 else
3822 return true;
3823 }
3824
3825 node = node->GetNext();
3826 }
3827 }
3828 else
3829 {
3830 wxRichTextObjectList::compatibility_iterator node = m_children.GetLast();
3831 while (node)
3832 {
3833 wxRichTextObject* obj = node->GetData();
3834 if (!obj->GetRange().IsOutside(range))
3835 {
3836 wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
3837 if (textObj)
3838 {
3839 text = textObj->GetTextForRange(range) + text;
3840 }
3841 else
3842 return true;
3843 }
3844
3845 node = node->GetPrevious();
3846 }
3847 }
3848
3849 return true;
3850 }
3851
3852 /// Find a suitable wrap position.
3853 bool wxRichTextParagraph::FindWrapPosition(const wxRichTextRange& range, wxDC& dc, int availableSpace, long& wrapPosition)
3854 {
3855 // Find the first position where the line exceeds the available space.
3856 wxSize sz;
3857 long i;
3858 long breakPosition = range.GetEnd();
3859 for (i = range.GetStart(); i <= range.GetEnd(); i++)
3860 {
3861 int descent = 0;
3862 GetRangeSize(wxRichTextRange(range.GetStart(), i), sz, descent, dc, wxRICHTEXT_UNFORMATTED);
3863
3864 if (sz.x > availableSpace)
3865 {
3866 breakPosition = i-1;
3867 break;
3868 }
3869 }
3870
3871 // Now we know the last position on the line.
3872 // Let's try to find a word break.
3873
3874 wxString plainText;
3875 if (GetContiguousPlainText(plainText, wxRichTextRange(range.GetStart(), breakPosition), false))
3876 {
3877 int spacePos = plainText.Find(wxT(' '), true);
3878 if (spacePos != wxNOT_FOUND)
3879 {
3880 int positionsFromEndOfString = plainText.length() - spacePos - 1;
3881 breakPosition = breakPosition - positionsFromEndOfString;
3882 }
3883 }
3884
3885 wrapPosition = breakPosition;
3886
3887 return true;
3888 }
3889
3890 /// Get the bullet text for this paragraph.
3891 wxString wxRichTextParagraph::GetBulletText()
3892 {
3893 if (GetAttributes().GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE ||
3894 (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_BITMAP))
3895 return wxEmptyString;
3896
3897 int number = GetAttributes().GetBulletNumber();
3898
3899 wxString text;
3900 if ((GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ARABIC) || (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE))
3901 {
3902 text.Printf(wxT("%d"), number);
3903 }
3904 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_UPPER)
3905 {
3906 // TODO: Unicode, and also check if number > 26
3907 text.Printf(wxT("%c"), (wxChar) (number+64));
3908 }
3909 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_LETTERS_LOWER)
3910 {
3911 // TODO: Unicode, and also check if number > 26
3912 text.Printf(wxT("%c"), (wxChar) (number+96));
3913 }
3914 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_UPPER)
3915 {
3916 text = wxRichTextDecimalToRoman(number);
3917 }
3918 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ROMAN_LOWER)
3919 {
3920 text = wxRichTextDecimalToRoman(number);
3921 text.MakeLower();
3922 }
3923 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL)
3924 {
3925 text = GetAttributes().GetBulletText();
3926 }
3927
3928 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_OUTLINE)
3929 {
3930 // The outline style relies on the text being computed statically,
3931 // since it depends on other levels points (e.g. 1.2.1.1). So normally the bullet text
3932 // should be stored in the attributes; if not, just use the number for this
3933 // level, as previously computed.
3934 if (!GetAttributes().GetBulletText().IsEmpty())
3935 text = GetAttributes().GetBulletText();
3936 }
3937
3938 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PARENTHESES)
3939 {
3940 text = wxT("(") + text + wxT(")");
3941 }
3942 else if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_RIGHT_PARENTHESIS)
3943 {
3944 text = text + wxT(")");
3945 }
3946
3947 if (GetAttributes().GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_PERIOD)
3948 {
3949 text += wxT(".");
3950 }
3951
3952 return text;
3953 }
3954
3955 /// Allocate or reuse a line object
3956 wxRichTextLine* wxRichTextParagraph::AllocateLine(int pos)
3957 {
3958 if (pos < (int) m_cachedLines.GetCount())
3959 {
3960 wxRichTextLine* line = m_cachedLines.Item(pos)->GetData();
3961 line->Init(this);
3962 return line;
3963 }
3964 else
3965 {
3966 wxRichTextLine* line = new wxRichTextLine(this);
3967 m_cachedLines.Append(line);
3968 return line;
3969 }
3970 }
3971
3972 /// Clear remaining unused line objects, if any
3973 bool wxRichTextParagraph::ClearUnusedLines(int lineCount)
3974 {
3975 int cachedLineCount = m_cachedLines.GetCount();
3976 if ((int) cachedLineCount > lineCount)
3977 {
3978 for (int i = 0; i < (int) (cachedLineCount - lineCount); i ++)
3979 {
3980 wxRichTextLineList::compatibility_iterator node = m_cachedLines.GetLast();
3981 wxRichTextLine* line = node->GetData();
3982 m_cachedLines.Erase(node);
3983 delete line;
3984 }
3985 }
3986 return true;
3987 }
3988
3989 /// Get combined attributes of the base style, paragraph style and character style. We use this to dynamically
3990 /// retrieve the actual style.
3991 wxTextAttrEx wxRichTextParagraph::GetCombinedAttributes(const wxTextAttrEx& contentStyle) const
3992 {
3993 wxTextAttrEx attr;
3994 wxRichTextBuffer* buf = wxDynamicCast(GetParent(), wxRichTextBuffer);
3995 if (buf)
3996 {
3997 attr = buf->GetBasicStyle();
3998 wxRichTextApplyStyle(attr, GetAttributes());
3999 }
4000 else
4001 attr = GetAttributes();
4002
4003 wxRichTextApplyStyle(attr, contentStyle);
4004 return attr;
4005 }
4006
4007 /// Get combined attributes of the base style and paragraph style.
4008 wxTextAttrEx wxRichTextParagraph::GetCombinedAttributes() const
4009 {
4010 wxTextAttrEx attr;
4011 wxRichTextBuffer* buf = wxDynamicCast(GetParent(), wxRichTextBuffer);
4012 if (buf)
4013 {
4014 attr = buf->GetBasicStyle();
4015 wxRichTextApplyStyle(attr, GetAttributes());
4016 }
4017 else
4018 attr = GetAttributes();
4019
4020 return attr;
4021 }
4022
4023 /// Create default tabstop array
4024 void wxRichTextParagraph::InitDefaultTabs()
4025 {
4026 // create a default tab list at 10 mm each.
4027 for (int i = 0; i < 20; ++i)
4028 {
4029 sm_defaultTabs.Add(i*100);
4030 }
4031 }
4032
4033 /// Clear default tabstop array
4034 void wxRichTextParagraph::ClearDefaultTabs()
4035 {
4036 sm_defaultTabs.Clear();
4037 }
4038
4039
4040 /*!
4041 * wxRichTextLine
4042 * This object represents a line in a paragraph, and stores
4043 * offsets from the start of the paragraph representing the
4044 * start and end positions of the line.
4045 */
4046
4047 wxRichTextLine::wxRichTextLine(wxRichTextParagraph* parent)
4048 {
4049 Init(parent);
4050 }
4051
4052 /// Initialisation
4053 void wxRichTextLine::Init(wxRichTextParagraph* parent)
4054 {
4055 m_parent = parent;
4056 m_range.SetRange(-1, -1);
4057 m_pos = wxPoint(0, 0);
4058 m_size = wxSize(0, 0);
4059 m_descent = 0;
4060 }
4061
4062 /// Copy
4063 void wxRichTextLine::Copy(const wxRichTextLine& obj)
4064 {
4065 m_range = obj.m_range;
4066 }
4067
4068 /// Get the absolute object position
4069 wxPoint wxRichTextLine::GetAbsolutePosition() const
4070 {
4071 return m_parent->GetPosition() + m_pos;
4072 }
4073
4074 /// Get the absolute range
4075 wxRichTextRange wxRichTextLine::GetAbsoluteRange() const
4076 {
4077 wxRichTextRange range(m_range.GetStart() + m_parent->GetRange().GetStart(), 0);
4078 range.SetEnd(range.GetStart() + m_range.GetLength()-1);
4079 return range;
4080 }
4081
4082 /*!
4083 * wxRichTextPlainText
4084 * This object represents a single piece of text.
4085 */
4086
4087 IMPLEMENT_DYNAMIC_CLASS(wxRichTextPlainText, wxRichTextObject)
4088
4089 wxRichTextPlainText::wxRichTextPlainText(const wxString& text, wxRichTextObject* parent, wxTextAttrEx* style):
4090 wxRichTextObject(parent)
4091 {
4092 if (style)
4093 SetAttributes(*style);
4094
4095 m_text = text;
4096 }
4097
4098 #define USE_KERNING_FIX 1
4099
4100 /// Draw the item
4101 bool wxRichTextPlainText::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int descent, int WXUNUSED(style))
4102 {
4103 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
4104 wxASSERT (para != NULL);
4105
4106 wxTextAttrEx textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
4107
4108 int offset = GetRange().GetStart();
4109
4110 long len = range.GetLength();
4111 wxString stringChunk = m_text.Mid(range.GetStart() - offset, (size_t) len);
4112 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
4113 stringChunk.MakeUpper();
4114
4115 int charHeight = dc.GetCharHeight();
4116
4117 int x = rect.x;
4118 int y = rect.y + (rect.height - charHeight - (descent - m_descent));
4119
4120 // Test for the optimized situations where all is selected, or none
4121 // is selected.
4122
4123 if (textAttr.GetFont().Ok())
4124 dc.SetFont(textAttr.GetFont());
4125
4126 // (a) All selected.
4127 if (selectionRange.GetStart() <= range.GetStart() && selectionRange.GetEnd() >= range.GetEnd())
4128 {
4129 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, true);
4130 }
4131 // (b) None selected.
4132 else if (selectionRange.GetEnd() < range.GetStart() || selectionRange.GetStart() > range.GetEnd())
4133 {
4134 // Draw all unselected
4135 DrawTabbedString(dc, textAttr, rect, stringChunk, x, y, false);
4136 }
4137 else
4138 {
4139 // (c) Part selected, part not
4140 // Let's draw unselected chunk, selected chunk, then unselected chunk.
4141
4142 dc.SetBackgroundMode(wxTRANSPARENT);
4143
4144 // 1. Initial unselected chunk, if any, up until start of selection.
4145 if (selectionRange.GetStart() > range.GetStart() && selectionRange.GetStart() <= range.GetEnd())
4146 {
4147 int r1 = range.GetStart();
4148 int s1 = selectionRange.GetStart()-1;
4149 int fragmentLen = s1 - r1 + 1;
4150 if (fragmentLen < 0)
4151 wxLogDebug(wxT("Mid(%d, %d"), (int)(r1 - offset), (int)fragmentLen);
4152 wxString stringFragment = m_text.Mid(r1 - offset, fragmentLen);
4153
4154 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
4155
4156 #if USE_KERNING_FIX
4157 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
4158 {
4159 // Compensate for kerning difference
4160 wxString stringFragment2(m_text.Mid(r1 - offset, fragmentLen+1));
4161 wxString stringFragment3(m_text.Mid(r1 - offset + fragmentLen, 1));
4162
4163 wxCoord w1, h1, w2, h2, w3, h3;
4164 dc.GetTextExtent(stringFragment, & w1, & h1);
4165 dc.GetTextExtent(stringFragment2, & w2, & h2);
4166 dc.GetTextExtent(stringFragment3, & w3, & h3);
4167
4168 int kerningDiff = (w1 + w3) - w2;
4169 x = x - kerningDiff;
4170 }
4171 #endif
4172 }
4173
4174 // 2. Selected chunk, if any.
4175 if (selectionRange.GetEnd() >= range.GetStart())
4176 {
4177 int s1 = wxMax(selectionRange.GetStart(), range.GetStart());
4178 int s2 = wxMin(selectionRange.GetEnd(), range.GetEnd());
4179
4180 int fragmentLen = s2 - s1 + 1;
4181 if (fragmentLen < 0)
4182 wxLogDebug(wxT("Mid(%d, %d"), (int)(s1 - offset), (int)fragmentLen);
4183 wxString stringFragment = m_text.Mid(s1 - offset, fragmentLen);
4184
4185 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, true);
4186
4187 #if USE_KERNING_FIX
4188 if (stringChunk.Find(wxT("\t")) == wxNOT_FOUND)
4189 {
4190 // Compensate for kerning difference
4191 wxString stringFragment2(m_text.Mid(s1 - offset, fragmentLen+1));
4192 wxString stringFragment3(m_text.Mid(s1 - offset + fragmentLen, 1));
4193
4194 wxCoord w1, h1, w2, h2, w3, h3;
4195 dc.GetTextExtent(stringFragment, & w1, & h1);
4196 dc.GetTextExtent(stringFragment2, & w2, & h2);
4197 dc.GetTextExtent(stringFragment3, & w3, & h3);
4198
4199 int kerningDiff = (w1 + w3) - w2;
4200 x = x - kerningDiff;
4201 }
4202 #endif
4203 }
4204
4205 // 3. Remaining unselected chunk, if any
4206 if (selectionRange.GetEnd() < range.GetEnd())
4207 {
4208 int s2 = wxMin(selectionRange.GetEnd()+1, range.GetEnd());
4209 int r2 = range.GetEnd();
4210
4211 int fragmentLen = r2 - s2 + 1;
4212 if (fragmentLen < 0)
4213 wxLogDebug(wxT("Mid(%d, %d"), (int)(s2 - offset), (int)fragmentLen);
4214 wxString stringFragment = m_text.Mid(s2 - offset, fragmentLen);
4215
4216 DrawTabbedString(dc, textAttr, rect, stringFragment, x, y, false);
4217 }
4218 }
4219
4220 return true;
4221 }
4222
4223 bool wxRichTextPlainText::DrawTabbedString(wxDC& dc, const wxTextAttrEx& attr, const wxRect& rect,wxString& str, wxCoord& x, wxCoord& y, bool selected)
4224 {
4225 bool hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
4226
4227 wxArrayInt tabArray;
4228 int tabCount;
4229 if (hasTabs)
4230 {
4231 if (attr.GetTabs().IsEmpty())
4232 tabArray = wxRichTextParagraph::GetDefaultTabs();
4233 else
4234 tabArray = attr.GetTabs();
4235 tabCount = tabArray.GetCount();
4236
4237 for (int i = 0; i < tabCount; ++i)
4238 {
4239 int pos = tabArray[i];
4240 pos = ConvertTenthsMMToPixels(dc, pos);
4241 tabArray[i] = pos;
4242 }
4243 }
4244 else
4245 tabCount = 0;
4246
4247 int nextTabPos = -1;
4248 int tabPos = -1;
4249 wxCoord w, h;
4250
4251 if (selected)
4252 {
4253 dc.SetBrush(*wxBLACK_BRUSH);
4254 dc.SetPen(*wxBLACK_PEN);
4255 dc.SetTextForeground(*wxWHITE);
4256 dc.SetBackgroundMode(wxTRANSPARENT);
4257 }
4258 else
4259 {
4260 dc.SetTextForeground(attr.GetTextColour());
4261 dc.SetBackgroundMode(wxTRANSPARENT);
4262 }
4263
4264 while (hasTabs)
4265 {
4266 // the string has a tab
4267 // break up the string at the Tab
4268 wxString stringChunk = str.BeforeFirst(wxT('\t'));
4269 str = str.AfterFirst(wxT('\t'));
4270 dc.GetTextExtent(stringChunk, & w, & h);
4271 tabPos = x + w;
4272 bool not_found = true;
4273 for (int i = 0; i < tabCount && not_found; ++i)
4274 {
4275 nextTabPos = tabArray.Item(i);
4276 if (nextTabPos > tabPos)
4277 {
4278 not_found = false;
4279 if (selected)
4280 {
4281 w = nextTabPos - x;
4282 wxRect selRect(x, rect.y, w, rect.GetHeight());
4283 dc.DrawRectangle(selRect);
4284 }
4285 dc.DrawText(stringChunk, x, y);
4286
4287 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
4288 {
4289 wxPen oldPen = dc.GetPen();
4290 dc.SetPen(wxPen(attr.GetTextColour(), 1));
4291 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
4292 dc.SetPen(oldPen);
4293 }
4294
4295 x = nextTabPos;
4296 }
4297 }
4298 hasTabs = (str.Find(wxT('\t')) != wxNOT_FOUND);
4299 }
4300
4301 if (!str.IsEmpty())
4302 {
4303 dc.GetTextExtent(str, & w, & h);
4304 if (selected)
4305 {
4306 wxRect selRect(x, rect.y, w, rect.GetHeight());
4307 dc.DrawRectangle(selRect);
4308 }
4309 dc.DrawText(str, x, y);
4310
4311 if (attr.HasTextEffects() && (attr.GetTextEffects() & wxTEXT_ATTR_EFFECT_STRIKETHROUGH))
4312 {
4313 wxPen oldPen = dc.GetPen();
4314 dc.SetPen(wxPen(attr.GetTextColour(), 1));
4315 dc.DrawLine(x, (int) (y+(h/2)+0.5), x+w, (int) (y+(h/2)+0.5));
4316 dc.SetPen(oldPen);
4317 }
4318
4319 x += w;
4320 }
4321 return true;
4322
4323 }
4324
4325 /// Lay the item out
4326 bool wxRichTextPlainText::Layout(wxDC& dc, const wxRect& WXUNUSED(rect), int WXUNUSED(style))
4327 {
4328 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
4329 wxASSERT (para != NULL);
4330
4331 wxTextAttrEx textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
4332
4333 if (textAttr.GetFont().Ok())
4334 dc.SetFont(textAttr.GetFont());
4335
4336 wxString str = m_text;
4337 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
4338 str.MakeUpper();
4339
4340 wxCoord w, h;
4341 dc.GetTextExtent(str, & w, & h, & m_descent);
4342 m_size = wxSize(w, dc.GetCharHeight());
4343
4344 return true;
4345 }
4346
4347 /// Copy
4348 void wxRichTextPlainText::Copy(const wxRichTextPlainText& obj)
4349 {
4350 wxRichTextObject::Copy(obj);
4351
4352 m_text = obj.m_text;
4353 }
4354
4355 /// Get/set the object size for the given range. Returns false if the range
4356 /// is invalid for this object.
4357 bool wxRichTextPlainText::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& descent, wxDC& dc, int WXUNUSED(flags), wxPoint position) const
4358 {
4359 if (!range.IsWithin(GetRange()))
4360 return false;
4361
4362 wxRichTextParagraph* para = wxDynamicCast(GetParent(), wxRichTextParagraph);
4363 wxASSERT (para != NULL);
4364
4365 wxTextAttrEx textAttr(para ? para->GetCombinedAttributes(GetAttributes()) : GetAttributes());
4366
4367 // Always assume unformatted text, since at this level we have no knowledge
4368 // of line breaks - and we don't need it, since we'll calculate size within
4369 // formatted text by doing it in chunks according to the line ranges
4370
4371 if (textAttr.GetFont().Ok())
4372 dc.SetFont(textAttr.GetFont());
4373
4374 int startPos = range.GetStart() - GetRange().GetStart();
4375 long len = range.GetLength();
4376 wxString stringChunk = m_text.Mid(startPos, (size_t) len);
4377
4378 if (textAttr.HasTextEffects() && (textAttr.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
4379 stringChunk.MakeUpper();
4380
4381 wxCoord w, h;
4382 int width = 0;
4383 if (stringChunk.Find(wxT('\t')) != wxNOT_FOUND)
4384 {
4385 // the string has a tab
4386 wxArrayInt tabArray;
4387 if (textAttr.GetTabs().IsEmpty())
4388 tabArray = wxRichTextParagraph::GetDefaultTabs();
4389 else
4390 tabArray = textAttr.GetTabs();
4391
4392 int tabCount = tabArray.GetCount();
4393
4394 for (int i = 0; i < tabCount; ++i)
4395 {
4396 int pos = tabArray[i];
4397 pos = ((wxRichTextPlainText*) this)->ConvertTenthsMMToPixels(dc, pos);
4398 tabArray[i] = pos;
4399 }
4400
4401 int nextTabPos = -1;
4402
4403 while (stringChunk.Find(wxT('\t')) >= 0)
4404 {
4405 // the string has a tab
4406 // break up the string at the Tab
4407 wxString stringFragment = stringChunk.BeforeFirst(wxT('\t'));
4408 stringChunk = stringChunk.AfterFirst(wxT('\t'));
4409 dc.GetTextExtent(stringFragment, & w, & h);
4410 width += w;
4411 int absoluteWidth = width + position.x;
4412 bool notFound = true;
4413 for (int i = 0; i < tabCount && notFound; ++i)
4414 {
4415 nextTabPos = tabArray.Item(i);
4416 if (nextTabPos > absoluteWidth)
4417 {
4418 notFound = false;
4419 width = nextTabPos - position.x;
4420 }
4421 }
4422 }
4423 }
4424 dc.GetTextExtent(stringChunk, & w, & h, & descent);
4425 width += w;
4426 size = wxSize(width, dc.GetCharHeight());
4427
4428 return true;
4429 }
4430
4431 /// Do a split, returning an object containing the second part, and setting
4432 /// the first part in 'this'.
4433 wxRichTextObject* wxRichTextPlainText::DoSplit(long pos)
4434 {
4435 int index = pos - GetRange().GetStart();
4436 if (index < 0 || index >= (int) m_text.length())
4437 return NULL;
4438
4439 wxString firstPart = m_text.Mid(0, index);
4440 wxString secondPart = m_text.Mid(index);
4441
4442 m_text = firstPart;
4443
4444 wxRichTextPlainText* newObject = new wxRichTextPlainText(secondPart);
4445 newObject->SetAttributes(GetAttributes());
4446
4447 newObject->SetRange(wxRichTextRange(pos, GetRange().GetEnd()));
4448 GetRange().SetEnd(pos-1);
4449
4450 return newObject;
4451 }
4452
4453 /// Calculate range
4454 void wxRichTextPlainText::CalculateRange(long start, long& end)
4455 {
4456 end = start + m_text.length() - 1;
4457 m_range.SetRange(start, end);
4458 }
4459
4460 /// Delete range
4461 bool wxRichTextPlainText::DeleteRange(const wxRichTextRange& range)
4462 {
4463 wxRichTextRange r = range;
4464
4465 r.LimitTo(GetRange());
4466
4467 if (r.GetStart() == GetRange().GetStart() && r.GetEnd() == GetRange().GetEnd())
4468 {
4469 m_text.Empty();
4470 return true;
4471 }
4472
4473 long startIndex = r.GetStart() - GetRange().GetStart();
4474 long len = r.GetLength();
4475
4476 m_text = m_text.Mid(0, startIndex) + m_text.Mid(startIndex+len);
4477 return true;
4478 }
4479
4480 /// Get text for the given range.
4481 wxString wxRichTextPlainText::GetTextForRange(const wxRichTextRange& range) const
4482 {
4483 wxRichTextRange r = range;
4484
4485 r.LimitTo(GetRange());
4486
4487 long startIndex = r.GetStart() - GetRange().GetStart();
4488 long len = r.GetLength();
4489
4490 return m_text.Mid(startIndex, len);
4491 }
4492
4493 /// Returns true if this object can merge itself with the given one.
4494 bool wxRichTextPlainText::CanMerge(wxRichTextObject* object) const
4495 {
4496 return object->GetClassInfo() == CLASSINFO(wxRichTextPlainText) &&
4497 (m_text.empty() || wxTextAttrEq(GetAttributes(), object->GetAttributes()));
4498 }
4499
4500 /// Returns true if this object merged itself with the given one.
4501 /// The calling code will then delete the given object.
4502 bool wxRichTextPlainText::Merge(wxRichTextObject* object)
4503 {
4504 wxRichTextPlainText* textObject = wxDynamicCast(object, wxRichTextPlainText);
4505 wxASSERT( textObject != NULL );
4506
4507 if (textObject)
4508 {
4509 m_text += textObject->GetText();
4510 return true;
4511 }
4512 else
4513 return false;
4514 }
4515
4516 /// Dump to output stream for debugging
4517 void wxRichTextPlainText::Dump(wxTextOutputStream& stream)
4518 {
4519 wxRichTextObject::Dump(stream);
4520 stream << m_text << wxT("\n");
4521 }
4522
4523 /*!
4524 * wxRichTextBuffer
4525 * This is a kind of box, used to represent the whole buffer
4526 */
4527
4528 IMPLEMENT_DYNAMIC_CLASS(wxRichTextBuffer, wxRichTextParagraphLayoutBox)
4529
4530 wxList wxRichTextBuffer::sm_handlers;
4531 wxRichTextRenderer* wxRichTextBuffer::sm_renderer = NULL;
4532 int wxRichTextBuffer::sm_bulletRightMargin = 20;
4533 float wxRichTextBuffer::sm_bulletProportion = (float) 0.3;
4534
4535 /// Initialisation
4536 void wxRichTextBuffer::Init()
4537 {
4538 m_commandProcessor = new wxCommandProcessor;
4539 m_styleSheet = NULL;
4540 m_modified = false;
4541 m_batchedCommandDepth = 0;
4542 m_batchedCommand = NULL;
4543 m_suppressUndo = 0;
4544 m_handlerFlags = 0;
4545 m_scale = 1.0;
4546 }
4547
4548 /// Initialisation
4549 wxRichTextBuffer::~wxRichTextBuffer()
4550 {
4551 delete m_commandProcessor;
4552 delete m_batchedCommand;
4553
4554 ClearStyleStack();
4555 ClearEventHandlers();
4556 }
4557
4558 void wxRichTextBuffer::ResetAndClearCommands()
4559 {
4560 Reset();
4561
4562 GetCommandProcessor()->ClearCommands();
4563
4564 Modify(false);
4565 Invalidate(wxRICHTEXT_ALL);
4566 }
4567
4568 void wxRichTextBuffer::Copy(const wxRichTextBuffer& obj)
4569 {
4570 wxRichTextParagraphLayoutBox::Copy(obj);
4571
4572 m_styleSheet = obj.m_styleSheet;
4573 m_modified = obj.m_modified;
4574 m_batchedCommandDepth = obj.m_batchedCommandDepth;
4575 m_batchedCommand = obj.m_batchedCommand;
4576 m_suppressUndo = obj.m_suppressUndo;
4577 }
4578
4579 /// Push style sheet to top of stack
4580 bool wxRichTextBuffer::PushStyleSheet(wxRichTextStyleSheet* styleSheet)
4581 {
4582 if (m_styleSheet)
4583 styleSheet->InsertSheet(m_styleSheet);
4584
4585 SetStyleSheet(styleSheet);
4586
4587 return true;
4588 }
4589
4590 /// Pop style sheet from top of stack
4591 wxRichTextStyleSheet* wxRichTextBuffer::PopStyleSheet()
4592 {
4593 if (m_styleSheet)
4594 {
4595 wxRichTextStyleSheet* oldSheet = m_styleSheet;
4596 m_styleSheet = oldSheet->GetNextSheet();
4597 oldSheet->Unlink();
4598
4599 return oldSheet;
4600 }
4601 else
4602 return NULL;
4603 }
4604
4605 /// Submit command to insert paragraphs
4606 bool wxRichTextBuffer::InsertParagraphsWithUndo(long pos, const wxRichTextParagraphLayoutBox& paragraphs, wxRichTextCtrl* ctrl, int flags)
4607 {
4608 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
4609
4610 wxTextAttrEx attr(GetDefaultStyle());
4611
4612 wxTextAttrEx* p = NULL;
4613 wxTextAttrEx paraAttr;
4614 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4615 {
4616 paraAttr = GetStyleForNewParagraph(pos);
4617 if (!paraAttr.IsDefault())
4618 p = & paraAttr;
4619 }
4620 else
4621 p = & attr;
4622
4623 action->GetNewParagraphs() = paragraphs;
4624
4625 if (p)
4626 {
4627 wxRichTextObjectList::compatibility_iterator node = m_children.GetLast();
4628 while (node)
4629 {
4630 wxRichTextParagraph* obj = (wxRichTextParagraph*) node->GetData();
4631 obj->SetAttributes(*p);
4632 node = node->GetPrevious();
4633 }
4634 }
4635
4636 action->SetPosition(pos);
4637
4638 // Set the range we'll need to delete in Undo
4639 action->SetRange(wxRichTextRange(pos, pos + paragraphs.GetRange().GetEnd() - 1));
4640
4641 SubmitAction(action);
4642
4643 return true;
4644 }
4645
4646 /// Submit command to insert the given text
4647 bool wxRichTextBuffer::InsertTextWithUndo(long pos, const wxString& text, wxRichTextCtrl* ctrl, int flags)
4648 {
4649 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
4650
4651 wxTextAttrEx* p = NULL;
4652 wxTextAttrEx paraAttr;
4653 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4654 {
4655 paraAttr = GetStyleForNewParagraph(pos);
4656 if (!paraAttr.IsDefault())
4657 p = & paraAttr;
4658 }
4659
4660 action->GetNewParagraphs().AddParagraphs(text, p);
4661
4662 int length = action->GetNewParagraphs().GetRange().GetLength();
4663
4664 if (text.length() > 0 && text.Last() != wxT('\n'))
4665 {
4666 // Don't count the newline when undoing
4667 length --;
4668 action->GetNewParagraphs().SetPartialParagraph(true);
4669 }
4670
4671 action->SetPosition(pos);
4672
4673 // Set the range we'll need to delete in Undo
4674 action->SetRange(wxRichTextRange(pos, pos + length - 1));
4675
4676 SubmitAction(action);
4677
4678 return true;
4679 }
4680
4681 /// Submit command to insert the given text
4682 bool wxRichTextBuffer::InsertNewlineWithUndo(long pos, wxRichTextCtrl* ctrl, int flags)
4683 {
4684 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Text"), wxRICHTEXT_INSERT, this, ctrl, false);
4685
4686 wxTextAttrEx* p = NULL;
4687 wxTextAttrEx paraAttr;
4688 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4689 {
4690 paraAttr = GetStyleForNewParagraph(pos);
4691 if (!paraAttr.IsDefault())
4692 p = & paraAttr;
4693 }
4694
4695 wxTextAttrEx attr(GetDefaultStyle());
4696
4697 wxRichTextParagraph* newPara = new wxRichTextParagraph(wxEmptyString, this, & attr);
4698 action->GetNewParagraphs().AppendChild(newPara);
4699 action->GetNewParagraphs().UpdateRanges();
4700 action->GetNewParagraphs().SetPartialParagraph(false);
4701 action->SetPosition(pos);
4702
4703 if (p)
4704 newPara->SetAttributes(*p);
4705
4706 // Set the range we'll need to delete in Undo
4707 action->SetRange(wxRichTextRange(pos, pos));
4708
4709 SubmitAction(action);
4710
4711 return true;
4712 }
4713
4714 /// Submit command to insert the given image
4715 bool wxRichTextBuffer::InsertImageWithUndo(long pos, const wxRichTextImageBlock& imageBlock, wxRichTextCtrl* ctrl, int flags)
4716 {
4717 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, ctrl, false);
4718
4719 wxTextAttrEx* p = NULL;
4720 wxTextAttrEx paraAttr;
4721 if (flags & wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE)
4722 {
4723 paraAttr = GetStyleForNewParagraph(pos);
4724 if (!paraAttr.IsDefault())
4725 p = & paraAttr;
4726 }
4727
4728 wxTextAttrEx attr(GetDefaultStyle());
4729
4730 wxRichTextParagraph* newPara = new wxRichTextParagraph(this, & attr);
4731 if (p)
4732 newPara->SetAttributes(*p);
4733
4734 wxRichTextImage* imageObject = new wxRichTextImage(imageBlock, newPara);
4735 newPara->AppendChild(imageObject);
4736 action->GetNewParagraphs().AppendChild(newPara);
4737 action->GetNewParagraphs().UpdateRanges();
4738
4739 action->GetNewParagraphs().SetPartialParagraph(true);
4740
4741 action->SetPosition(pos);
4742
4743 // Set the range we'll need to delete in Undo
4744 action->SetRange(wxRichTextRange(pos, pos));
4745
4746 SubmitAction(action);
4747
4748 return true;
4749 }
4750
4751 /// Get the style that is appropriate for a new paragraph at this position.
4752 /// If the previous paragraph has a paragraph style name, look up the next-paragraph
4753 /// style.
4754 wxRichTextAttr wxRichTextBuffer::GetStyleForNewParagraph(long pos, bool caretPosition) const
4755 {
4756 wxRichTextParagraph* para = GetParagraphAtPosition(pos, caretPosition);
4757 if (para)
4758 {
4759 wxRichTextAttr attr;
4760 bool foundAttributes = false;
4761
4762 // Look for a matching paragraph style
4763 if (!para->GetAttributes().GetParagraphStyleName().IsEmpty() && GetStyleSheet())
4764 {
4765 wxRichTextParagraphStyleDefinition* paraDef = GetStyleSheet()->FindParagraphStyle(para->GetAttributes().GetParagraphStyleName());
4766 if (paraDef)
4767 {
4768 if (!paraDef->GetNextStyle().IsEmpty())
4769 {
4770 wxRichTextParagraphStyleDefinition* nextParaDef = GetStyleSheet()->FindParagraphStyle(paraDef->GetNextStyle());
4771 if (nextParaDef)
4772 {
4773 foundAttributes = true;
4774 attr = nextParaDef->GetStyle();
4775 }
4776 }
4777
4778 // If we didn't find the 'next style', use this style instead.
4779 if (!foundAttributes)
4780 {
4781 foundAttributes = true;
4782 attr = paraDef->GetStyle();
4783 }
4784 }
4785 }
4786 if (!foundAttributes)
4787 {
4788 attr = para->GetAttributes();
4789 int flags = attr.GetFlags();
4790
4791 // Eliminate character styles
4792 flags &= ( (~ wxTEXT_ATTR_FONT) |
4793 (~ wxTEXT_ATTR_TEXT_COLOUR) |
4794 (~ wxTEXT_ATTR_BACKGROUND_COLOUR) );
4795 attr.SetFlags(flags);
4796 }
4797
4798 // Now see if we need to number the paragraph.
4799 if (attr.HasBulletStyle())
4800 {
4801 wxRichTextAttr numberingAttr;
4802 if (FindNextParagraphNumber(para, numberingAttr))
4803 wxRichTextApplyStyle(attr, (const wxRichTextAttr&) numberingAttr);
4804 }
4805
4806 return attr;
4807 }
4808 else
4809 return wxRichTextAttr();
4810 }
4811
4812 /// Submit command to delete this range
4813 bool wxRichTextBuffer::DeleteRangeWithUndo(const wxRichTextRange& range, wxRichTextCtrl* ctrl)
4814 {
4815 wxRichTextAction* action = new wxRichTextAction(NULL, _("Delete"), wxRICHTEXT_DELETE, this, ctrl);
4816
4817 action->SetPosition(ctrl->GetCaretPosition());
4818
4819 // Set the range to delete
4820 action->SetRange(range);
4821
4822 // Copy the fragment that we'll need to restore in Undo
4823 CopyFragment(range, action->GetOldParagraphs());
4824
4825 // Special case: if there is only one (non-partial) paragraph,
4826 // we must save the *next* paragraph's style, because that
4827 // is the style we must apply when inserting the content back
4828 // when undoing the delete. (This is because we're merging the
4829 // paragraph with the previous paragraph and throwing away
4830 // the style, and we need to restore it.)
4831 if (!action->GetOldParagraphs().GetPartialParagraph() && action->GetOldParagraphs().GetChildCount() == 1)
4832 {
4833 wxRichTextParagraph* lastPara = GetParagraphAtPosition(range.GetStart());
4834 if (lastPara)
4835 {
4836 wxRichTextParagraph* nextPara = GetParagraphAtPosition(range.GetEnd()+1);
4837 if (nextPara)
4838 {
4839 wxRichTextParagraph* para = (wxRichTextParagraph*) action->GetOldParagraphs().GetChild(0);
4840 para->SetAttributes(nextPara->GetAttributes());
4841 }
4842 }
4843 }
4844
4845 SubmitAction(action);
4846
4847 return true;
4848 }
4849
4850 /// Collapse undo/redo commands
4851 bool wxRichTextBuffer::BeginBatchUndo(const wxString& cmdName)
4852 {
4853 if (m_batchedCommandDepth == 0)
4854 {
4855 wxASSERT(m_batchedCommand == NULL);
4856 if (m_batchedCommand)
4857 {
4858 GetCommandProcessor()->Submit(m_batchedCommand);
4859 }
4860 m_batchedCommand = new wxRichTextCommand(cmdName);
4861 }
4862
4863 m_batchedCommandDepth ++;
4864
4865 return true;
4866 }
4867
4868 /// Collapse undo/redo commands
4869 bool wxRichTextBuffer::EndBatchUndo()
4870 {
4871 m_batchedCommandDepth --;
4872
4873 wxASSERT(m_batchedCommandDepth >= 0);
4874 wxASSERT(m_batchedCommand != NULL);
4875
4876 if (m_batchedCommandDepth == 0)
4877 {
4878 GetCommandProcessor()->Submit(m_batchedCommand);
4879 m_batchedCommand = NULL;
4880 }
4881
4882 return true;
4883 }
4884
4885 /// Submit immediately, or delay according to whether collapsing is on
4886 bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action)
4887 {
4888 if (BatchingUndo() && m_batchedCommand && !SuppressingUndo())
4889 m_batchedCommand->AddAction(action);
4890 else
4891 {
4892 wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName());
4893 cmd->AddAction(action);
4894
4895 // Only store it if we're not suppressing undo.
4896 return GetCommandProcessor()->Submit(cmd, !SuppressingUndo());
4897 }
4898
4899 return true;
4900 }
4901
4902 /// Begin suppressing undo/redo commands.
4903 bool wxRichTextBuffer::BeginSuppressUndo()
4904 {
4905 m_suppressUndo ++;
4906
4907 return true;
4908 }
4909
4910 /// End suppressing undo/redo commands.
4911 bool wxRichTextBuffer::EndSuppressUndo()
4912 {
4913 m_suppressUndo --;
4914
4915 return true;
4916 }
4917
4918 /// Begin using a style
4919 bool wxRichTextBuffer::BeginStyle(const wxTextAttrEx& style)
4920 {
4921 wxTextAttrEx newStyle(GetDefaultStyle());
4922
4923 // Save the old default style
4924 m_attributeStack.Append((wxObject*) new wxTextAttrEx(GetDefaultStyle()));
4925
4926 wxRichTextApplyStyle(newStyle, style);
4927 newStyle.SetFlags(style.GetFlags()|newStyle.GetFlags());
4928
4929 SetDefaultStyle(newStyle);
4930
4931 // wxLogDebug("Default style size = %d", GetDefaultStyle().GetFont().GetPointSize());
4932
4933 return true;
4934 }
4935
4936 /// End the style
4937 bool wxRichTextBuffer::EndStyle()
4938 {
4939 if (!m_attributeStack.GetFirst())
4940 {
4941 wxLogDebug(_("Too many EndStyle calls!"));
4942 return false;
4943 }
4944
4945 wxList::compatibility_iterator node = m_attributeStack.GetLast();
4946 wxTextAttrEx* attr = (wxTextAttrEx*)node->GetData();
4947 m_attributeStack.Erase(node);
4948
4949 SetDefaultStyle(*attr);
4950
4951 delete attr;
4952 return true;
4953 }
4954
4955 /// End all styles
4956 bool wxRichTextBuffer::EndAllStyles()
4957 {
4958 while (m_attributeStack.GetCount() != 0)
4959 EndStyle();
4960 return true;
4961 }
4962
4963 /// Clear the style stack
4964 void wxRichTextBuffer::ClearStyleStack()
4965 {
4966 for (wxList::compatibility_iterator node = m_attributeStack.GetFirst(); node; node = node->GetNext())
4967 delete (wxTextAttrEx*) node->GetData();
4968 m_attributeStack.Clear();
4969 }
4970
4971 /// Begin using bold
4972 bool wxRichTextBuffer::BeginBold()
4973 {
4974 wxFont font(GetBasicStyle().GetFont());
4975 font.SetWeight(wxBOLD);
4976
4977 wxTextAttrEx attr;
4978 attr.SetFont(font,wxTEXT_ATTR_FONT_WEIGHT);
4979
4980 return BeginStyle(attr);
4981 }
4982
4983 /// Begin using italic
4984 bool wxRichTextBuffer::BeginItalic()
4985 {
4986 wxFont font(GetBasicStyle().GetFont());
4987 font.SetStyle(wxITALIC);
4988
4989 wxTextAttrEx attr;
4990 attr.SetFont(font, wxTEXT_ATTR_FONT_ITALIC);
4991
4992 return BeginStyle(attr);
4993 }
4994
4995 /// Begin using underline
4996 bool wxRichTextBuffer::BeginUnderline()
4997 {
4998 wxFont font(GetBasicStyle().GetFont());
4999 font.SetUnderlined(true);
5000
5001 wxTextAttrEx attr;
5002 attr.SetFont(font, wxTEXT_ATTR_FONT_UNDERLINE);
5003
5004 return BeginStyle(attr);
5005 }
5006
5007 /// Begin using point size
5008 bool wxRichTextBuffer::BeginFontSize(int pointSize)
5009 {
5010 wxFont font(GetBasicStyle().GetFont());
5011 font.SetPointSize(pointSize);
5012
5013 wxTextAttrEx attr;
5014 attr.SetFont(font, wxTEXT_ATTR_FONT_SIZE);
5015
5016 return BeginStyle(attr);
5017 }
5018
5019 /// Begin using this font
5020 bool wxRichTextBuffer::BeginFont(const wxFont& font)
5021 {
5022 wxTextAttrEx attr;
5023 attr.SetFlags(wxTEXT_ATTR_FONT);
5024 attr.SetFont(font);
5025
5026 return BeginStyle(attr);
5027 }
5028
5029 /// Begin using this colour
5030 bool wxRichTextBuffer::BeginTextColour(const wxColour& colour)
5031 {
5032 wxTextAttrEx attr;
5033 attr.SetFlags(wxTEXT_ATTR_TEXT_COLOUR);
5034 attr.SetTextColour(colour);
5035
5036 return BeginStyle(attr);
5037 }
5038
5039 /// Begin using alignment
5040 bool wxRichTextBuffer::BeginAlignment(wxTextAttrAlignment alignment)
5041 {
5042 wxTextAttrEx attr;
5043 attr.SetFlags(wxTEXT_ATTR_ALIGNMENT);
5044 attr.SetAlignment(alignment);
5045
5046 return BeginStyle(attr);
5047 }
5048
5049 /// Begin left indent
5050 bool wxRichTextBuffer::BeginLeftIndent(int leftIndent, int leftSubIndent)
5051 {
5052 wxTextAttrEx attr;
5053 attr.SetFlags(wxTEXT_ATTR_LEFT_INDENT);
5054 attr.SetLeftIndent(leftIndent, leftSubIndent);
5055
5056 return BeginStyle(attr);
5057 }
5058
5059 /// Begin right indent
5060 bool wxRichTextBuffer::BeginRightIndent(int rightIndent)
5061 {
5062 wxTextAttrEx attr;
5063 attr.SetFlags(wxTEXT_ATTR_RIGHT_INDENT);
5064 attr.SetRightIndent(rightIndent);
5065
5066 return BeginStyle(attr);
5067 }
5068
5069 /// Begin paragraph spacing
5070 bool wxRichTextBuffer::BeginParagraphSpacing(int before, int after)
5071 {
5072 long flags = 0;
5073 if (before != 0)
5074 flags |= wxTEXT_ATTR_PARA_SPACING_BEFORE;
5075 if (after != 0)
5076 flags |= wxTEXT_ATTR_PARA_SPACING_AFTER;
5077
5078 wxTextAttrEx attr;
5079 attr.SetFlags(flags);
5080 attr.SetParagraphSpacingBefore(before);
5081 attr.SetParagraphSpacingAfter(after);
5082
5083 return BeginStyle(attr);
5084 }
5085
5086 /// Begin line spacing
5087 bool wxRichTextBuffer::BeginLineSpacing(int lineSpacing)
5088 {
5089 wxTextAttrEx attr;
5090 attr.SetFlags(wxTEXT_ATTR_LINE_SPACING);
5091 attr.SetLineSpacing(lineSpacing);
5092
5093 return BeginStyle(attr);
5094 }
5095
5096 /// Begin numbered bullet
5097 bool wxRichTextBuffer::BeginNumberedBullet(int bulletNumber, int leftIndent, int leftSubIndent, int bulletStyle)
5098 {
5099 wxTextAttrEx attr;
5100 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
5101 attr.SetBulletStyle(bulletStyle);
5102 attr.SetBulletNumber(bulletNumber);
5103 attr.SetLeftIndent(leftIndent, leftSubIndent);
5104
5105 return BeginStyle(attr);
5106 }
5107
5108 /// Begin symbol bullet
5109 bool wxRichTextBuffer::BeginSymbolBullet(const wxString& symbol, int leftIndent, int leftSubIndent, int bulletStyle)
5110 {
5111 wxTextAttrEx attr;
5112 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
5113 attr.SetBulletStyle(bulletStyle);
5114 attr.SetLeftIndent(leftIndent, leftSubIndent);
5115 attr.SetBulletText(symbol);
5116
5117 return BeginStyle(attr);
5118 }
5119
5120 /// Begin standard bullet
5121 bool wxRichTextBuffer::BeginStandardBullet(const wxString& bulletName, int leftIndent, int leftSubIndent, int bulletStyle)
5122 {
5123 wxTextAttrEx attr;
5124 attr.SetFlags(wxTEXT_ATTR_BULLET_STYLE|wxTEXT_ATTR_LEFT_INDENT);
5125 attr.SetBulletStyle(bulletStyle);
5126 attr.SetLeftIndent(leftIndent, leftSubIndent);
5127 attr.SetBulletName(bulletName);
5128
5129 return BeginStyle(attr);
5130 }
5131
5132 /// Begin named character style
5133 bool wxRichTextBuffer::BeginCharacterStyle(const wxString& characterStyle)
5134 {
5135 if (GetStyleSheet())
5136 {
5137 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
5138 if (def)
5139 {
5140 wxTextAttrEx attr = def->GetStyle();
5141 return BeginStyle(attr);
5142 }
5143 }
5144 return false;
5145 }
5146
5147 /// Begin named paragraph style
5148 bool wxRichTextBuffer::BeginParagraphStyle(const wxString& paragraphStyle)
5149 {
5150 if (GetStyleSheet())
5151 {
5152 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->FindParagraphStyle(paragraphStyle);
5153 if (def)
5154 {
5155 wxTextAttrEx attr = def->GetStyle();
5156 return BeginStyle(attr);
5157 }
5158 }
5159 return false;
5160 }
5161
5162 /// Begin named list style
5163 bool wxRichTextBuffer::BeginListStyle(const wxString& listStyle, int level, int number)
5164 {
5165 if (GetStyleSheet())
5166 {
5167 wxRichTextListStyleDefinition* def = GetStyleSheet()->FindListStyle(listStyle);
5168 if (def)
5169 {
5170 wxTextAttrEx attr(def->GetCombinedStyleForLevel(level));
5171
5172 attr.SetBulletNumber(number);
5173
5174 return BeginStyle(attr);
5175 }
5176 }
5177 return false;
5178 }
5179
5180 /// Begin URL
5181 bool wxRichTextBuffer::BeginURL(const wxString& url, const wxString& characterStyle)
5182 {
5183 wxTextAttrEx attr;
5184
5185 if (!characterStyle.IsEmpty() && GetStyleSheet())
5186 {
5187 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->FindCharacterStyle(characterStyle);
5188 if (def)
5189 {
5190 attr = def->GetStyle();
5191 }
5192 }
5193 attr.SetURL(url);
5194
5195 return BeginStyle(attr);
5196 }
5197
5198 /// Adds a handler to the end
5199 void wxRichTextBuffer::AddHandler(wxRichTextFileHandler *handler)
5200 {
5201 sm_handlers.Append(handler);
5202 }
5203
5204 /// Inserts a handler at the front
5205 void wxRichTextBuffer::InsertHandler(wxRichTextFileHandler *handler)
5206 {
5207 sm_handlers.Insert( handler );
5208 }
5209
5210 /// Removes a handler
5211 bool wxRichTextBuffer::RemoveHandler(const wxString& name)
5212 {
5213 wxRichTextFileHandler *handler = FindHandler(name);
5214 if (handler)
5215 {
5216 sm_handlers.DeleteObject(handler);
5217 delete handler;
5218 return true;
5219 }
5220 else
5221 return false;
5222 }
5223
5224 /// Finds a handler by filename or, if supplied, type
5225 wxRichTextFileHandler *wxRichTextBuffer::FindHandlerFilenameOrType(const wxString& filename, int imageType)
5226 {
5227 if (imageType != wxRICHTEXT_TYPE_ANY)
5228 return FindHandler(imageType);
5229 else if (!filename.IsEmpty())
5230 {
5231 wxString path, file, ext;
5232 wxSplitPath(filename, & path, & file, & ext);
5233 return FindHandler(ext, imageType);
5234 }
5235 else
5236 return NULL;
5237 }
5238
5239
5240 /// Finds a handler by name
5241 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& name)
5242 {
5243 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5244 while (node)
5245 {
5246 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
5247 if (handler->GetName().Lower() == name.Lower()) return handler;
5248
5249 node = node->GetNext();
5250 }
5251 return NULL;
5252 }
5253
5254 /// Finds a handler by extension and type
5255 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(const wxString& extension, int type)
5256 {
5257 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5258 while (node)
5259 {
5260 wxRichTextFileHandler *handler = (wxRichTextFileHandler*)node->GetData();
5261 if ( handler->GetExtension().Lower() == extension.Lower() &&
5262 (type == wxRICHTEXT_TYPE_ANY || handler->GetType() == type) )
5263 return handler;
5264 node = node->GetNext();
5265 }
5266 return 0;
5267 }
5268
5269 /// Finds a handler by type
5270 wxRichTextFileHandler* wxRichTextBuffer::FindHandler(int type)
5271 {
5272 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5273 while (node)
5274 {
5275 wxRichTextFileHandler *handler = (wxRichTextFileHandler *)node->GetData();
5276 if (handler->GetType() == type) return handler;
5277 node = node->GetNext();
5278 }
5279 return NULL;
5280 }
5281
5282 void wxRichTextBuffer::InitStandardHandlers()
5283 {
5284 if (!FindHandler(wxRICHTEXT_TYPE_TEXT))
5285 AddHandler(new wxRichTextPlainTextHandler);
5286 }
5287
5288 void wxRichTextBuffer::CleanUpHandlers()
5289 {
5290 wxList::compatibility_iterator node = sm_handlers.GetFirst();
5291 while (node)
5292 {
5293 wxRichTextFileHandler* handler = (wxRichTextFileHandler*)node->GetData();
5294 wxList::compatibility_iterator next = node->GetNext();
5295 delete handler;
5296 node = next;
5297 }
5298
5299 sm_handlers.Clear();
5300 }
5301
5302 wxString wxRichTextBuffer::GetExtWildcard(bool combine, bool save, wxArrayInt* types)
5303 {
5304 if (types)
5305 types->Clear();
5306
5307 wxString wildcard;
5308
5309 wxList::compatibility_iterator node = GetHandlers().GetFirst();
5310 int count = 0;
5311 while (node)
5312 {
5313 wxRichTextFileHandler* handler = (wxRichTextFileHandler*) node->GetData();
5314 if (handler->IsVisible() && ((save && handler->CanSave()) || !save && handler->CanLoad()))
5315 {
5316 if (combine)
5317 {
5318 if (count > 0)
5319 wildcard += wxT(";");
5320 wildcard += wxT("*.") + handler->GetExtension();
5321 }
5322 else
5323 {
5324 if (count > 0)
5325 wildcard += wxT("|");
5326 wildcard += handler->GetName();
5327 wildcard += wxT(" ");
5328 wildcard += _("files");
5329 wildcard += wxT(" (*.");
5330 wildcard += handler->GetExtension();
5331 wildcard += wxT(")|*.");
5332 wildcard += handler->GetExtension();
5333 if (types)
5334 types->Add(handler->GetType());
5335 }
5336 count ++;
5337 }
5338
5339 node = node->GetNext();
5340 }
5341
5342 if (combine)
5343 wildcard = wxT("(") + wildcard + wxT(")|") + wildcard;
5344 return wildcard;
5345 }
5346
5347 /// Load a file
5348 bool wxRichTextBuffer::LoadFile(const wxString& filename, int type)
5349 {
5350 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
5351 if (handler)
5352 {
5353 SetDefaultStyle(wxTextAttrEx());
5354 handler->SetFlags(GetHandlerFlags());
5355 bool success = handler->LoadFile(this, filename);
5356 Invalidate(wxRICHTEXT_ALL);
5357 return success;
5358 }
5359 else
5360 return false;
5361 }
5362
5363 /// Save a file
5364 bool wxRichTextBuffer::SaveFile(const wxString& filename, int type)
5365 {
5366 wxRichTextFileHandler* handler = FindHandlerFilenameOrType(filename, type);
5367 if (handler)
5368 {
5369 handler->SetFlags(GetHandlerFlags());
5370 return handler->SaveFile(this, filename);
5371 }
5372 else
5373 return false;
5374 }
5375
5376 /// Load from a stream
5377 bool wxRichTextBuffer::LoadFile(wxInputStream& stream, int type)
5378 {
5379 wxRichTextFileHandler* handler = FindHandler(type);
5380 if (handler)
5381 {
5382 SetDefaultStyle(wxTextAttrEx());
5383 handler->SetFlags(GetHandlerFlags());
5384 bool success = handler->LoadFile(this, stream);
5385 Invalidate(wxRICHTEXT_ALL);
5386 return success;
5387 }
5388 else
5389 return false;
5390 }
5391
5392 /// Save to a stream
5393 bool wxRichTextBuffer::SaveFile(wxOutputStream& stream, int type)
5394 {
5395 wxRichTextFileHandler* handler = FindHandler(type);
5396 if (handler)
5397 {
5398 handler->SetFlags(GetHandlerFlags());
5399 return handler->SaveFile(this, stream);
5400 }
5401 else
5402 return false;
5403 }
5404
5405 /// Copy the range to the clipboard
5406 bool wxRichTextBuffer::CopyToClipboard(const wxRichTextRange& range)
5407 {
5408 bool success = false;
5409 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
5410
5411 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
5412 {
5413 wxTheClipboard->Clear();
5414
5415 // Add composite object
5416
5417 wxDataObjectComposite* compositeObject = new wxDataObjectComposite();
5418
5419 {
5420 wxString text = GetTextForRange(range);
5421
5422 #ifdef __WXMSW__
5423 text = wxTextFile::Translate(text, wxTextFileType_Dos);
5424 #endif
5425
5426 compositeObject->Add(new wxTextDataObject(text), false /* not preferred */);
5427 }
5428
5429 // Add rich text buffer data object. This needs the XML handler to be present.
5430
5431 if (FindHandler(wxRICHTEXT_TYPE_XML))
5432 {
5433 wxRichTextBuffer* richTextBuf = new wxRichTextBuffer;
5434 CopyFragment(range, *richTextBuf);
5435
5436 compositeObject->Add(new wxRichTextBufferDataObject(richTextBuf), true /* preferred */);
5437 }
5438
5439 if (wxTheClipboard->SetData(compositeObject))
5440 success = true;
5441
5442 wxTheClipboard->Close();
5443 }
5444
5445 #else
5446 wxUnusedVar(range);
5447 #endif
5448 return success;
5449 }
5450
5451 /// Paste the clipboard content to the buffer
5452 bool wxRichTextBuffer::PasteFromClipboard(long position)
5453 {
5454 bool success = false;
5455 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
5456 if (CanPasteFromClipboard())
5457 {
5458 if (wxTheClipboard->Open())
5459 {
5460 if (wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())))
5461 {
5462 wxRichTextBufferDataObject data;
5463 wxTheClipboard->GetData(data);
5464 wxRichTextBuffer* richTextBuffer = data.GetRichTextBuffer();
5465 if (richTextBuffer)
5466 {
5467 InsertParagraphsWithUndo(position+1, *richTextBuffer, GetRichTextCtrl(), wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE);
5468 delete richTextBuffer;
5469 }
5470 }
5471 else if (wxTheClipboard->IsSupported(wxDF_TEXT) || wxTheClipboard->IsSupported(wxDF_UNICODETEXT))
5472 {
5473 wxTextDataObject data;
5474 wxTheClipboard->GetData(data);
5475 wxString text(data.GetText());
5476 text.Replace(_T("\r\n"), _T("\n"));
5477
5478 InsertTextWithUndo(position+1, text, GetRichTextCtrl());
5479
5480 success = true;
5481 }
5482 else if (wxTheClipboard->IsSupported(wxDF_BITMAP))
5483 {
5484 wxBitmapDataObject data;
5485 wxTheClipboard->GetData(data);
5486 wxBitmap bitmap(data.GetBitmap());
5487 wxImage image(bitmap.ConvertToImage());
5488
5489 wxRichTextAction* action = new wxRichTextAction(NULL, _("Insert Image"), wxRICHTEXT_INSERT, this, GetRichTextCtrl(), false);
5490
5491 action->GetNewParagraphs().AddImage(image);
5492
5493 if (action->GetNewParagraphs().GetChildCount() == 1)
5494 action->GetNewParagraphs().SetPartialParagraph(true);
5495
5496 action->SetPosition(position);
5497
5498 // Set the range we'll need to delete in Undo
5499 action->SetRange(wxRichTextRange(position, position));
5500
5501 SubmitAction(action);
5502
5503 success = true;
5504 }
5505 wxTheClipboard->Close();
5506 }
5507 }
5508 #else
5509 wxUnusedVar(position);
5510 #endif
5511 return success;
5512 }
5513
5514 /// Can we paste from the clipboard?
5515 bool wxRichTextBuffer::CanPasteFromClipboard() const
5516 {
5517 bool canPaste = false;
5518 #if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
5519 if (!wxTheClipboard->IsOpened() && wxTheClipboard->Open())
5520 {
5521 if (wxTheClipboard->IsSupported(wxDF_TEXT) || wxTheClipboard->IsSupported(wxDF_UNICODETEXT) ||
5522 wxTheClipboard->IsSupported(wxDataFormat(wxRichTextBufferDataObject::GetRichTextBufferFormatId())) ||
5523 wxTheClipboard->IsSupported(wxDF_BITMAP))
5524 {
5525 canPaste = true;
5526 }
5527 wxTheClipboard->Close();
5528 }
5529 #endif
5530 return canPaste;
5531 }
5532
5533 /// Dumps contents of buffer for debugging purposes
5534 void wxRichTextBuffer::Dump()
5535 {
5536 wxString text;
5537 {
5538 wxStringOutputStream stream(& text);
5539 wxTextOutputStream textStream(stream);
5540 Dump(textStream);
5541 }
5542
5543 wxLogDebug(text);
5544 }
5545
5546 /// Add an event handler
5547 bool wxRichTextBuffer::AddEventHandler(wxEvtHandler* handler)
5548 {
5549 m_eventHandlers.Append(handler);
5550 return true;
5551 }
5552
5553 /// Remove an event handler
5554 bool wxRichTextBuffer::RemoveEventHandler(wxEvtHandler* handler, bool deleteHandler)
5555 {
5556 wxList::compatibility_iterator node = m_eventHandlers.Find(handler);
5557 if (node)
5558 {
5559 m_eventHandlers.Erase(node);
5560 if (deleteHandler)
5561 delete handler;
5562
5563 return true;
5564 }
5565 else
5566 return false;
5567 }
5568
5569 /// Clear event handlers
5570 void wxRichTextBuffer::ClearEventHandlers()
5571 {
5572 m_eventHandlers.Clear();
5573 }
5574
5575 /// Send event to event handlers. If sendToAll is true, will send to all event handlers,
5576 /// otherwise will stop at the first successful one.
5577 bool wxRichTextBuffer::SendEvent(wxEvent& event, bool sendToAll)
5578 {
5579 bool success = false;
5580 for (wxList::compatibility_iterator node = m_eventHandlers.GetFirst(); node; node = node->GetNext())
5581 {
5582 wxEvtHandler* handler = (wxEvtHandler*) node->GetData();
5583 if (handler->ProcessEvent(event))
5584 {
5585 success = true;
5586 if (!sendToAll)
5587 return true;
5588 }
5589 }
5590 return success;
5591 }
5592
5593 /// Set style sheet and notify of the change
5594 bool wxRichTextBuffer::SetStyleSheetAndNotify(wxRichTextStyleSheet* sheet)
5595 {
5596 wxRichTextStyleSheet* oldSheet = GetStyleSheet();
5597
5598 wxWindowID id = wxID_ANY;
5599 if (GetRichTextCtrl())
5600 id = GetRichTextCtrl()->GetId();
5601
5602 wxRichTextEvent event(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACING, id);
5603 event.SetEventObject(GetRichTextCtrl());
5604 event.SetOldStyleSheet(oldSheet);
5605 event.SetNewStyleSheet(sheet);
5606 event.Allow();
5607
5608 if (SendEvent(event) && !event.IsAllowed())
5609 {
5610 if (sheet != oldSheet)
5611 delete sheet;
5612
5613 return false;
5614 }
5615
5616 if (oldSheet && oldSheet != sheet)
5617 delete oldSheet;
5618
5619 SetStyleSheet(sheet);
5620
5621 event.SetEventType(wxEVT_COMMAND_RICHTEXT_STYLESHEET_REPLACED);
5622 event.SetOldStyleSheet(NULL);
5623 event.Allow();
5624
5625 return SendEvent(event);
5626 }
5627
5628 /// Set renderer, deleting old one
5629 void wxRichTextBuffer::SetRenderer(wxRichTextRenderer* renderer)
5630 {
5631 if (sm_renderer)
5632 delete sm_renderer;
5633 sm_renderer = renderer;
5634 }
5635
5636 bool wxRichTextStdRenderer::DrawStandardBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxTextAttrEx& bulletAttr, const wxRect& rect)
5637 {
5638 if (bulletAttr.GetTextColour().Ok())
5639 {
5640 dc.SetPen(wxPen(bulletAttr.GetTextColour()));
5641 dc.SetBrush(wxBrush(bulletAttr.GetTextColour()));
5642 }
5643 else
5644 {
5645 dc.SetPen(*wxBLACK_PEN);
5646 dc.SetBrush(*wxBLACK_BRUSH);
5647 }
5648
5649 wxFont font;
5650 if (bulletAttr.GetFont().Ok())
5651 font = bulletAttr.GetFont();
5652 else
5653 font = (*wxNORMAL_FONT);
5654
5655 dc.SetFont(font);
5656
5657 int charHeight = dc.GetCharHeight();
5658
5659 int bulletWidth = (int) (((float) charHeight) * wxRichTextBuffer::GetBulletProportion());
5660 int bulletHeight = bulletWidth;
5661
5662 int x = rect.x;
5663
5664 // Calculate the top position of the character (as opposed to the whole line height)
5665 int y = rect.y + (rect.height - charHeight);
5666
5667 // Calculate where the bullet should be positioned
5668 y = y + (charHeight+1)/2 - (bulletHeight+1)/2;
5669
5670 // The margin between a bullet and text.
5671 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
5672
5673 if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
5674 x = rect.x + rect.width - bulletWidth - margin;
5675 else if (bulletAttr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
5676 x = x + (rect.width)/2 - bulletWidth/2;
5677
5678 if (bulletAttr.GetBulletName() == wxT("standard/square"))
5679 {
5680 dc.DrawRectangle(x, y, bulletWidth, bulletHeight);
5681 }
5682 else if (bulletAttr.GetBulletName() == wxT("standard/diamond"))
5683 {
5684 wxPoint pts[5];
5685 pts[0].x = x; pts[0].y = y + bulletHeight/2;
5686 pts[1].x = x + bulletWidth/2; pts[1].y = y;
5687 pts[2].x = x + bulletWidth; pts[2].y = y + bulletHeight/2;
5688 pts[3].x = x + bulletWidth/2; pts[3].y = y + bulletHeight;
5689
5690 dc.DrawPolygon(4, pts);
5691 }
5692 else if (bulletAttr.GetBulletName() == wxT("standard/triangle"))
5693 {
5694 wxPoint pts[3];
5695 pts[0].x = x; pts[0].y = y;
5696 pts[1].x = x + bulletWidth; pts[1].y = y + bulletHeight/2;
5697 pts[2].x = x; pts[2].y = y + bulletHeight;
5698
5699 dc.DrawPolygon(3, pts);
5700 }
5701 else // "standard/circle", and catch-all
5702 {
5703 dc.DrawEllipse(x, y, bulletWidth, bulletHeight);
5704 }
5705
5706 return true;
5707 }
5708
5709 bool wxRichTextStdRenderer::DrawTextBullet(wxRichTextParagraph* paragraph, wxDC& dc, const wxTextAttrEx& attr, const wxRect& rect, const wxString& text)
5710 {
5711 if (!text.empty())
5712 {
5713 wxFont font;
5714 if ((attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_SYMBOL) && !attr.GetBulletFont().IsEmpty() && attr.GetFont().Ok())
5715 {
5716 font = (*wxTheFontList->FindOrCreateFont(attr.GetFont().GetPointSize(), attr.GetFont().GetFamily(),
5717 attr.GetFont().GetStyle(), attr.GetFont().GetWeight(), attr.GetFont().GetUnderlined(),
5718 attr.GetBulletFont()));
5719 }
5720 else if (attr.GetFont().Ok())
5721 font = attr.GetFont();
5722 else
5723 font = (*wxNORMAL_FONT);
5724
5725 dc.SetFont(font);
5726
5727 if (attr.GetTextColour().Ok())
5728 dc.SetTextForeground(attr.GetTextColour());
5729
5730 dc.SetBackgroundMode(wxTRANSPARENT);
5731
5732 int charHeight = dc.GetCharHeight();
5733 wxCoord tw, th;
5734 dc.GetTextExtent(text, & tw, & th);
5735
5736 int x = rect.x;
5737
5738 // Calculate the top position of the character (as opposed to the whole line height)
5739 int y = rect.y + (rect.height - charHeight);
5740
5741 // The margin between a bullet and text.
5742 int margin = paragraph->ConvertTenthsMMToPixels(dc, wxRichTextBuffer::GetBulletRightMargin());
5743
5744 if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_RIGHT)
5745 x = (rect.x + rect.width) - tw - margin;
5746 else if (attr.GetBulletStyle() & wxTEXT_ATTR_BULLET_STYLE_ALIGN_CENTRE)
5747 x = x + (rect.width)/2 - tw/2;
5748
5749 dc.DrawText(text, x, y);
5750
5751 return true;
5752 }
5753 else
5754 return false;
5755 }
5756
5757 bool wxRichTextStdRenderer::DrawBitmapBullet(wxRichTextParagraph* WXUNUSED(paragraph), wxDC& WXUNUSED(dc), const wxTextAttrEx& WXUNUSED(attr), const wxRect& WXUNUSED(rect))
5758 {
5759 // Currently unimplemented. The intention is to store bitmaps by name in a media store associated
5760 // with the buffer. The store will allow retrieval from memory, disk or other means.
5761 return false;
5762 }
5763
5764 /// Enumerate the standard bullet names currently supported
5765 bool wxRichTextStdRenderer::EnumerateStandardBulletNames(wxArrayString& bulletNames)
5766 {
5767 bulletNames.Add(wxT("standard/circle"));
5768 bulletNames.Add(wxT("standard/square"));
5769 bulletNames.Add(wxT("standard/diamond"));
5770 bulletNames.Add(wxT("standard/triangle"));
5771
5772 return true;
5773 }
5774
5775 /*
5776 * Module to initialise and clean up handlers
5777 */
5778
5779 class wxRichTextModule: public wxModule
5780 {
5781 DECLARE_DYNAMIC_CLASS(wxRichTextModule)
5782 public:
5783 wxRichTextModule() {}
5784 bool OnInit()
5785 {
5786 wxRichTextBuffer::SetRenderer(new wxRichTextStdRenderer);
5787 wxRichTextBuffer::InitStandardHandlers();
5788 wxRichTextParagraph::InitDefaultTabs();
5789 return true;
5790 };
5791 void OnExit()
5792 {
5793 wxRichTextBuffer::CleanUpHandlers();
5794 wxRichTextDecimalToRoman(-1);
5795 wxRichTextParagraph::ClearDefaultTabs();
5796 wxRichTextCtrl::ClearAvailableFontNames();
5797 wxRichTextBuffer::SetRenderer(NULL);
5798 };
5799 };
5800
5801 IMPLEMENT_DYNAMIC_CLASS(wxRichTextModule, wxModule)
5802
5803
5804 // If the richtext lib is dynamically loaded after the app has already started
5805 // (such as from wxPython) then the built-in module system will not init this
5806 // module. Provide this function to do it manually.
5807 void wxRichTextModuleInit()
5808 {
5809 wxModule* module = new wxRichTextModule;
5810 module->Init();
5811 wxModule::RegisterModule(module);
5812 }
5813
5814
5815 /*!
5816 * Commands for undo/redo
5817 *
5818 */
5819
5820 wxRichTextCommand::wxRichTextCommand(const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
5821 wxRichTextCtrl* ctrl, bool ignoreFirstTime): wxCommand(true, name)
5822 {
5823 /* wxRichTextAction* action = */ new wxRichTextAction(this, name, id, buffer, ctrl, ignoreFirstTime);
5824 }
5825
5826 wxRichTextCommand::wxRichTextCommand(const wxString& name): wxCommand(true, name)
5827 {
5828 }
5829
5830 wxRichTextCommand::~wxRichTextCommand()
5831 {
5832 ClearActions();
5833 }
5834
5835 void wxRichTextCommand::AddAction(wxRichTextAction* action)
5836 {
5837 if (!m_actions.Member(action))
5838 m_actions.Append(action);
5839 }
5840
5841 bool wxRichTextCommand::Do()
5842 {
5843 for (wxList::compatibility_iterator node = m_actions.GetFirst(); node; node = node->GetNext())
5844 {
5845 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
5846 action->Do();
5847 }
5848
5849 return true;
5850 }
5851
5852 bool wxRichTextCommand::Undo()
5853 {
5854 for (wxList::compatibility_iterator node = m_actions.GetLast(); node; node = node->GetPrevious())
5855 {
5856 wxRichTextAction* action = (wxRichTextAction*) node->GetData();
5857 action->Undo();
5858 }
5859
5860 return true;
5861 }
5862
5863 void wxRichTextCommand::ClearActions()
5864 {
5865 WX_CLEAR_LIST(wxList, m_actions);
5866 }
5867
5868 /*!
5869 * Individual action
5870 *
5871 */
5872
5873 wxRichTextAction::wxRichTextAction(wxRichTextCommand* cmd, const wxString& name, wxRichTextCommandId id, wxRichTextBuffer* buffer,
5874 wxRichTextCtrl* ctrl, bool ignoreFirstTime)
5875 {
5876 m_buffer = buffer;
5877 m_ignoreThis = ignoreFirstTime;
5878 m_cmdId = id;
5879 m_position = -1;
5880 m_ctrl = ctrl;
5881 m_name = name;
5882 m_newParagraphs.SetDefaultStyle(buffer->GetDefaultStyle());
5883 m_newParagraphs.SetBasicStyle(buffer->GetBasicStyle());
5884 if (cmd)
5885 cmd->AddAction(this);
5886 }
5887
5888 wxRichTextAction::~wxRichTextAction()
5889 {
5890 }
5891
5892 bool wxRichTextAction::Do()
5893 {
5894 m_buffer->Modify(true);
5895
5896 switch (m_cmdId)
5897 {
5898 case wxRICHTEXT_INSERT:
5899 {
5900 // Store a list of line start character and y positions so we can figure out which area
5901 // we need to refresh
5902 wxArrayInt optimizationLineCharPositions;
5903 wxArrayInt optimizationLineYPositions;
5904
5905 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
5906 // NOTE: we're assuming that the buffer is laid out correctly at this point.
5907 // If we had several actions, which only invalidate and leave layout until the
5908 // paint handler is called, then this might not be true. So we may need to switch
5909 // optimisation on only when we're simply adding text and not simultaneously
5910 // deleting a selection, for example. Or, we make sure the buffer is laid out correctly
5911 // first, but of course this means we'll be doing it twice.
5912 if (!m_buffer->GetDirty() && m_ctrl) // can only do optimisation if the buffer is already laid out correctly
5913 {
5914 wxSize clientSize = m_ctrl->GetClientSize();
5915 wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint();
5916 int lastY = firstVisiblePt.y + clientSize.y;
5917
5918 wxRichTextParagraph* para = m_buffer->GetParagraphAtPosition(GetPosition());
5919 wxRichTextObjectList::compatibility_iterator node = m_buffer->GetChildren().Find(para);
5920 while (node)
5921 {
5922 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
5923 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
5924 while (node2)
5925 {
5926 wxRichTextLine* line = node2->GetData();
5927 wxPoint pt = line->GetAbsolutePosition();
5928 wxRichTextRange range = line->GetAbsoluteRange();
5929
5930 if (pt.y > lastY)
5931 {
5932 node2 = wxRichTextLineList::compatibility_iterator();
5933 node = wxRichTextObjectList::compatibility_iterator();
5934 }
5935 else if (range.GetStart() > GetPosition() && pt.y >= firstVisiblePt.y)
5936 {
5937 optimizationLineCharPositions.Add(range.GetStart());
5938 optimizationLineYPositions.Add(pt.y);
5939 }
5940
5941 if (node2)
5942 node2 = node2->GetNext();
5943 }
5944
5945 if (node)
5946 node = node->GetNext();
5947 }
5948 }
5949 #endif
5950
5951 m_buffer->InsertFragment(GetPosition(), m_newParagraphs);
5952 m_buffer->UpdateRanges();
5953 m_buffer->Invalidate(GetRange());
5954
5955 long newCaretPosition = GetPosition() + m_newParagraphs.GetRange().GetLength();
5956
5957 // Character position to caret position
5958 newCaretPosition --;
5959
5960 // Don't take into account the last newline
5961 if (m_newParagraphs.GetPartialParagraph())
5962 newCaretPosition --;
5963
5964 newCaretPosition = wxMin(newCaretPosition, (m_buffer->GetRange().GetEnd()-1));
5965
5966 if (optimizationLineCharPositions.GetCount() > 0)
5967 UpdateAppearance(newCaretPosition, true /* send update event */, & optimizationLineCharPositions, & optimizationLineYPositions);
5968 else
5969 UpdateAppearance(newCaretPosition, true /* send update event */);
5970
5971 wxRichTextEvent cmdEvent(
5972 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
5973 m_ctrl ? m_ctrl->GetId() : -1);
5974 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
5975 cmdEvent.SetRange(GetRange());
5976 cmdEvent.SetPosition(GetRange().GetStart());
5977
5978 m_buffer->SendEvent(cmdEvent);
5979
5980 break;
5981 }
5982 case wxRICHTEXT_DELETE:
5983 {
5984 m_buffer->DeleteRange(GetRange());
5985 m_buffer->UpdateRanges();
5986 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
5987
5988 UpdateAppearance(GetRange().GetStart()-1, true /* send update event */);
5989
5990 wxRichTextEvent cmdEvent(
5991 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
5992 m_ctrl ? m_ctrl->GetId() : -1);
5993 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
5994 cmdEvent.SetRange(GetRange());
5995 cmdEvent.SetPosition(GetRange().GetStart());
5996
5997 m_buffer->SendEvent(cmdEvent);
5998
5999 break;
6000 }
6001 case wxRICHTEXT_CHANGE_STYLE:
6002 {
6003 ApplyParagraphs(GetNewParagraphs());
6004 m_buffer->Invalidate(GetRange());
6005
6006 UpdateAppearance(GetPosition());
6007
6008 wxRichTextEvent cmdEvent(
6009 wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED,
6010 m_ctrl ? m_ctrl->GetId() : -1);
6011 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6012 cmdEvent.SetRange(GetRange());
6013 cmdEvent.SetPosition(GetRange().GetStart());
6014
6015 m_buffer->SendEvent(cmdEvent);
6016
6017 break;
6018 }
6019 default:
6020 break;
6021 }
6022
6023 return true;
6024 }
6025
6026 bool wxRichTextAction::Undo()
6027 {
6028 m_buffer->Modify(true);
6029
6030 switch (m_cmdId)
6031 {
6032 case wxRICHTEXT_INSERT:
6033 {
6034 m_buffer->DeleteRange(GetRange());
6035 m_buffer->UpdateRanges();
6036 m_buffer->Invalidate(wxRichTextRange(GetRange().GetStart(), GetRange().GetStart()));
6037
6038 long newCaretPosition = GetPosition() - 1;
6039
6040 UpdateAppearance(newCaretPosition, true /* send update event */);
6041
6042 wxRichTextEvent cmdEvent(
6043 wxEVT_COMMAND_RICHTEXT_CONTENT_DELETED,
6044 m_ctrl ? m_ctrl->GetId() : -1);
6045 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6046 cmdEvent.SetRange(GetRange());
6047 cmdEvent.SetPosition(GetRange().GetStart());
6048
6049 m_buffer->SendEvent(cmdEvent);
6050
6051 break;
6052 }
6053 case wxRICHTEXT_DELETE:
6054 {
6055 m_buffer->InsertFragment(GetRange().GetStart(), m_oldParagraphs);
6056 m_buffer->UpdateRanges();
6057 m_buffer->Invalidate(GetRange());
6058
6059 UpdateAppearance(GetPosition(), true /* send update event */);
6060
6061 wxRichTextEvent cmdEvent(
6062 wxEVT_COMMAND_RICHTEXT_CONTENT_INSERTED,
6063 m_ctrl ? m_ctrl->GetId() : -1);
6064 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6065 cmdEvent.SetRange(GetRange());
6066 cmdEvent.SetPosition(GetRange().GetStart());
6067
6068 m_buffer->SendEvent(cmdEvent);
6069
6070 break;
6071 }
6072 case wxRICHTEXT_CHANGE_STYLE:
6073 {
6074 ApplyParagraphs(GetOldParagraphs());
6075 m_buffer->Invalidate(GetRange());
6076
6077 UpdateAppearance(GetPosition());
6078
6079 wxRichTextEvent cmdEvent(
6080 wxEVT_COMMAND_RICHTEXT_STYLE_CHANGED,
6081 m_ctrl ? m_ctrl->GetId() : -1);
6082 cmdEvent.SetEventObject(m_ctrl ? (wxObject*) m_ctrl : (wxObject*) m_buffer);
6083 cmdEvent.SetRange(GetRange());
6084 cmdEvent.SetPosition(GetRange().GetStart());
6085
6086 m_buffer->SendEvent(cmdEvent);
6087
6088 break;
6089 }
6090 default:
6091 break;
6092 }
6093
6094 return true;
6095 }
6096
6097 /// Update the control appearance
6098 void wxRichTextAction::UpdateAppearance(long caretPosition, bool sendUpdateEvent, wxArrayInt* optimizationLineCharPositions, wxArrayInt* optimizationLineYPositions)
6099 {
6100 if (m_ctrl)
6101 {
6102 m_ctrl->SetCaretPosition(caretPosition);
6103 if (!m_ctrl->IsFrozen())
6104 {
6105 m_ctrl->LayoutContent();
6106 m_ctrl->PositionCaret();
6107
6108 #if wxRICHTEXT_USE_OPTIMIZED_DRAWING
6109 // Find refresh rectangle if we are in a position to optimise refresh
6110 if (m_cmdId == wxRICHTEXT_INSERT && optimizationLineCharPositions && optimizationLineCharPositions->GetCount() > 0)
6111 {
6112 size_t i;
6113
6114 wxSize clientSize = m_ctrl->GetClientSize();
6115 wxPoint firstVisiblePt = m_ctrl->GetFirstVisiblePoint();
6116
6117 // Start/end positions
6118 int firstY = 0;
6119 int lastY = firstVisiblePt.y + clientSize.y;
6120
6121 bool foundStart = false;
6122 bool foundEnd = false;
6123
6124 // position offset - how many characters were inserted
6125 int positionOffset = GetRange().GetLength();
6126
6127 // find the first line which is being drawn at the same position as it was
6128 // before. Since we're talking about a simple insertion, we can assume
6129 // that the rest of the window does not need to be redrawn.
6130
6131 wxRichTextParagraph* para = m_buffer->GetParagraphAtPosition(GetPosition());
6132 wxRichTextObjectList::compatibility_iterator node = m_buffer->GetChildren().Find(para);
6133 while (node)
6134 {
6135 wxRichTextParagraph* child = (wxRichTextParagraph*) node->GetData();
6136 wxRichTextLineList::compatibility_iterator node2 = child->GetLines().GetFirst();
6137 while (node2)
6138 {
6139 wxRichTextLine* line = node2->GetData();
6140 wxPoint pt = line->GetAbsolutePosition();
6141 wxRichTextRange range = line->GetAbsoluteRange();
6142
6143 // we want to find the first line that is in the same position
6144 // as before. This will mean we're at the end of the changed text.
6145
6146 if (pt.y > lastY) // going past the end of the window, no more info
6147 {
6148 node2 = wxRichTextLineList::compatibility_iterator();
6149 node = wxRichTextObjectList::compatibility_iterator();
6150 }
6151 else
6152 {
6153 if (!foundStart)
6154 {
6155 firstY = pt.y - firstVisiblePt.y;
6156 foundStart = true;
6157 }
6158
6159 // search for this line being at the same position as before
6160 for (i = 0; i < optimizationLineCharPositions->GetCount(); i++)
6161 {
6162 if (((*optimizationLineCharPositions)[i] + positionOffset == range.GetStart()) &&
6163 ((*optimizationLineYPositions)[i] == pt.y))
6164 {
6165 // Stop, we're now the same as we were
6166 foundEnd = true;
6167 lastY = pt.y - firstVisiblePt.y;
6168
6169 node2 = wxRichTextLineList::compatibility_iterator();
6170 node = wxRichTextObjectList::compatibility_iterator();
6171
6172 break;
6173 }
6174 }
6175 }
6176
6177 if (node2)
6178 node2 = node2->GetNext();
6179 }
6180
6181 if (node)
6182 node = node->GetNext();
6183 }
6184
6185 if (!foundStart)
6186 firstY = firstVisiblePt.y;
6187 if (!foundEnd)
6188 lastY = firstVisiblePt.y + clientSize.y;
6189
6190 wxRect rect(firstVisiblePt.x, firstY, firstVisiblePt.x + clientSize.x, lastY - firstY);
6191 m_ctrl->RefreshRect(rect);
6192
6193 // TODO: we need to make sure that lines are only drawn if in the update region. The rect
6194 // passed to Draw is currently used in different ways (to pass the position the content should
6195 // be drawn at as well as the relevant region).
6196 }
6197 else
6198 #endif
6199 m_ctrl->Refresh(false);
6200
6201 if (sendUpdateEvent)
6202 m_ctrl->SendTextUpdatedEvent();
6203 }
6204 }
6205 }
6206
6207 /// Replace the buffer paragraphs with the new ones.
6208 void wxRichTextAction::ApplyParagraphs(const wxRichTextParagraphLayoutBox& fragment)
6209 {
6210 wxRichTextObjectList::compatibility_iterator node = fragment.GetChildren().GetFirst();
6211 while (node)
6212 {
6213 wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
6214 wxASSERT (para != NULL);
6215
6216 // We'll replace the existing paragraph by finding the paragraph at this position,
6217 // delete its node data, and setting a copy as the new node data.
6218 // TODO: make more efficient by simply swapping old and new paragraph objects.
6219
6220 wxRichTextParagraph* existingPara = m_buffer->GetParagraphAtPosition(para->GetRange().GetStart());
6221 if (existingPara)
6222 {
6223 wxRichTextObjectList::compatibility_iterator bufferParaNode = m_buffer->GetChildren().Find(existingPara);
6224 if (bufferParaNode)
6225 {
6226 wxRichTextParagraph* newPara = new wxRichTextParagraph(*para);
6227 newPara->SetParent(m_buffer);
6228
6229 bufferParaNode->SetData(newPara);
6230
6231 delete existingPara;
6232 }
6233 }
6234
6235 node = node->GetNext();
6236 }
6237 }
6238
6239
6240 /*!
6241 * wxRichTextRange
6242 * This stores beginning and end positions for a range of data.
6243 */
6244
6245 /// Limit this range to be within 'range'
6246 bool wxRichTextRange::LimitTo(const wxRichTextRange& range)
6247 {
6248 if (m_start < range.m_start)
6249 m_start = range.m_start;
6250
6251 if (m_end > range.m_end)
6252 m_end = range.m_end;
6253
6254 return true;
6255 }
6256
6257 /*!
6258 * wxRichTextImage implementation
6259 * This object represents an image.
6260 */
6261
6262 IMPLEMENT_DYNAMIC_CLASS(wxRichTextImage, wxRichTextObject)
6263
6264 wxRichTextImage::wxRichTextImage(const wxImage& image, wxRichTextObject* parent, wxTextAttrEx* charStyle):
6265 wxRichTextObject(parent)
6266 {
6267 m_image = image;
6268 if (charStyle)
6269 SetAttributes(*charStyle);
6270 }
6271
6272 wxRichTextImage::wxRichTextImage(const wxRichTextImageBlock& imageBlock, wxRichTextObject* parent, wxTextAttrEx* charStyle):
6273 wxRichTextObject(parent)
6274 {
6275 m_imageBlock = imageBlock;
6276 m_imageBlock.Load(m_image);
6277 if (charStyle)
6278 SetAttributes(*charStyle);
6279 }
6280
6281 /// Load wxImage from the block
6282 bool wxRichTextImage::LoadFromBlock()
6283 {
6284 m_imageBlock.Load(m_image);
6285 return m_imageBlock.Ok();
6286 }
6287
6288 /// Make block from the wxImage
6289 bool wxRichTextImage::MakeBlock()
6290 {
6291 if (m_imageBlock.GetImageType() == wxBITMAP_TYPE_ANY || m_imageBlock.GetImageType() == -1)
6292 m_imageBlock.SetImageType(wxBITMAP_TYPE_PNG);
6293
6294 m_imageBlock.MakeImageBlock(m_image, m_imageBlock.GetImageType());
6295 return m_imageBlock.Ok();
6296 }
6297
6298
6299 /// Draw the item
6300 bool wxRichTextImage::Draw(wxDC& dc, const wxRichTextRange& range, const wxRichTextRange& selectionRange, const wxRect& rect, int WXUNUSED(descent), int WXUNUSED(style))
6301 {
6302 if (!m_image.Ok() && m_imageBlock.Ok())
6303 LoadFromBlock();
6304
6305 if (!m_image.Ok())
6306 return false;
6307
6308 if (m_image.Ok() && !m_bitmap.Ok())
6309 m_bitmap = wxBitmap(m_image);
6310
6311 int y = rect.y + (rect.height - m_image.GetHeight());
6312
6313 if (m_bitmap.Ok())
6314 dc.DrawBitmap(m_bitmap, rect.x, y, true);
6315
6316 if (selectionRange.Contains(range.GetStart()))
6317 {
6318 dc.SetBrush(*wxBLACK_BRUSH);
6319 dc.SetPen(*wxBLACK_PEN);
6320 dc.SetLogicalFunction(wxINVERT);
6321 dc.DrawRectangle(rect);
6322 dc.SetLogicalFunction(wxCOPY);
6323 }
6324
6325 return true;
6326 }
6327
6328 /// Lay the item out
6329 bool wxRichTextImage::Layout(wxDC& WXUNUSED(dc), const wxRect& rect, int WXUNUSED(style))
6330 {
6331 if (!m_image.Ok())
6332 LoadFromBlock();
6333
6334 if (m_image.Ok())
6335 {
6336 SetCachedSize(wxSize(m_image.GetWidth(), m_image.GetHeight()));
6337 SetPosition(rect.GetPosition());
6338 }
6339
6340 return true;
6341 }
6342
6343 /// Get/set the object size for the given range. Returns false if the range
6344 /// is invalid for this object.
6345 bool wxRichTextImage::GetRangeSize(const wxRichTextRange& range, wxSize& size, int& WXUNUSED(descent), wxDC& WXUNUSED(dc), int WXUNUSED(flags), wxPoint WXUNUSED(position)) const
6346 {
6347 if (!range.IsWithin(GetRange()))
6348 return false;
6349
6350 if (!m_image.Ok())
6351 return false;
6352
6353 size.x = m_image.GetWidth();
6354 size.y = m_image.GetHeight();
6355
6356 return true;
6357 }
6358
6359 /// Copy
6360 void wxRichTextImage::Copy(const wxRichTextImage& obj)
6361 {
6362 wxRichTextObject::Copy(obj);
6363
6364 m_image = obj.m_image;
6365 m_imageBlock = obj.m_imageBlock;
6366 }
6367
6368 /*!
6369 * Utilities
6370 *
6371 */
6372
6373 /// Compare two attribute objects
6374 bool wxTextAttrEq(const wxTextAttrEx& attr1, const wxTextAttrEx& attr2)
6375 {
6376 return (attr1 == attr2);
6377 }
6378
6379 bool wxTextAttrEq(const wxTextAttrEx& attr1, const wxRichTextAttr& attr2)
6380 {
6381 return (
6382 attr1.GetTextColour() == attr2.GetTextColour() &&
6383 attr1.GetBackgroundColour() == attr2.GetBackgroundColour() &&
6384 attr1.GetFont().GetPointSize() == attr2.GetFontSize() &&
6385 attr1.GetFont().GetStyle() == attr2.GetFontStyle() &&
6386 attr1.GetFont().GetWeight() == attr2.GetFontWeight() &&
6387 attr1.GetFont().GetFaceName() == attr2.GetFontFaceName() &&
6388 attr1.GetFont().GetUnderlined() == attr2.GetFontUnderlined() &&
6389 attr1.GetTextEffects() == attr2.GetTextEffects() &&
6390 attr1.GetTextEffectFlags() == attr2.GetTextEffectFlags() &&
6391 attr1.GetAlignment() == attr2.GetAlignment() &&
6392 attr1.GetLeftIndent() == attr2.GetLeftIndent() &&
6393 attr1.GetRightIndent() == attr2.GetRightIndent() &&
6394 attr1.GetLeftSubIndent() == attr2.GetLeftSubIndent() &&
6395 wxRichTextTabsEq(attr1.GetTabs(), attr2.GetTabs()) &&
6396 attr1.GetLineSpacing() == attr2.GetLineSpacing() &&
6397 attr1.GetParagraphSpacingAfter() == attr2.GetParagraphSpacingAfter() &&
6398 attr1.GetParagraphSpacingBefore() == attr2.GetParagraphSpacingBefore() &&
6399 attr1.GetBulletStyle() == attr2.GetBulletStyle() &&
6400 attr1.GetBulletNumber() == attr2.GetBulletNumber() &&
6401 attr1.GetBulletText() == attr2.GetBulletText() &&
6402 attr1.GetBulletName() == attr2.GetBulletName() &&
6403 attr1.GetBulletFont() == attr2.GetBulletFont() &&
6404 attr1.GetOutlineLevel() == attr2.GetOutlineLevel() &&
6405 attr1.GetCharacterStyleName() == attr2.GetCharacterStyleName() &&
6406 attr1.GetParagraphStyleName() == attr2.GetParagraphStyleName() &&
6407 attr1.GetListStyleName() == attr2.GetListStyleName() &&
6408 attr1.HasPageBreak() == attr2.HasPageBreak());
6409 }
6410
6411 /// Compare two attribute objects, but take into account the flags
6412 /// specifying attributes of interest.
6413 bool wxTextAttrEqPartial(const wxTextAttrEx& attr1, const wxTextAttrEx& attr2, int flags)
6414 {
6415 if ((flags & wxTEXT_ATTR_TEXT_COLOUR) && attr1.GetTextColour() != attr2.GetTextColour())
6416 return false;
6417
6418 if ((flags & wxTEXT_ATTR_BACKGROUND_COLOUR) && attr1.GetBackgroundColour() != attr2.GetBackgroundColour())
6419 return false;
6420
6421 if ((flags & wxTEXT_ATTR_FONT_FACE) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
6422 attr1.GetFont().GetFaceName() != attr2.GetFont().GetFaceName())
6423 return false;
6424
6425 if ((flags & wxTEXT_ATTR_FONT_SIZE) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
6426 attr1.GetFont().GetPointSize() != attr2.GetFont().GetPointSize())
6427 return false;
6428
6429 if ((flags & wxTEXT_ATTR_FONT_WEIGHT) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
6430 attr1.GetFont().GetWeight() != attr2.GetFont().GetWeight())
6431 return false;
6432
6433 if ((flags & wxTEXT_ATTR_FONT_ITALIC) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
6434 attr1.GetFont().GetStyle() != attr2.GetFont().GetStyle())
6435 return false;
6436
6437 if ((flags & wxTEXT_ATTR_FONT_UNDERLINE) && attr1.GetFont().Ok() && attr2.GetFont().Ok() &&
6438 attr1.GetFont().GetUnderlined() != attr2.GetFont().GetUnderlined())
6439 return false;
6440
6441 if ((flags & wxTEXT_ATTR_ALIGNMENT) && attr1.GetAlignment() != attr2.GetAlignment())
6442 return false;
6443
6444 if ((flags & wxTEXT_ATTR_LEFT_INDENT) &&
6445 ((attr1.GetLeftIndent() != attr2.GetLeftIndent()) || (attr1.GetLeftSubIndent() != attr2.GetLeftSubIndent())))
6446 return false;
6447
6448 if ((flags & wxTEXT_ATTR_RIGHT_INDENT) &&
6449 (attr1.GetRightIndent() != attr2.GetRightIndent()))
6450 return false;
6451
6452 if ((flags & wxTEXT_ATTR_PARA_SPACING_AFTER) &&
6453 (attr1.GetParagraphSpacingAfter() != attr2.GetParagraphSpacingAfter()))
6454 return false;
6455
6456 if ((flags & wxTEXT_ATTR_PARA_SPACING_BEFORE) &&
6457 (attr1.GetParagraphSpacingBefore() != attr2.GetParagraphSpacingBefore()))
6458 return false;
6459
6460 if ((flags & wxTEXT_ATTR_LINE_SPACING) &&
6461 (attr1.GetLineSpacing() != attr2.GetLineSpacing()))
6462 return false;
6463
6464 if ((flags & wxTEXT_ATTR_CHARACTER_STYLE_NAME) &&
6465 (attr1.GetCharacterStyleName() != attr2.GetCharacterStyleName()))
6466 return false;
6467
6468 if ((flags & wxTEXT_ATTR_PARAGRAPH_STYLE_NAME) &&
6469 (attr1.GetParagraphStyleName() != attr2.GetParagraphStyleName()))
6470 return false;
6471
6472 if ((flags & wxTEXT_ATTR_LIST_STYLE_NAME) &&
6473 (attr1.GetListStyleName() != attr2.GetListStyleName()))
6474 return false;
6475
6476 if ((flags & wxTEXT_ATTR_BULLET_STYLE) &&
6477 (attr1.GetBulletStyle() != attr2.GetBulletStyle()))
6478 return false;
6479
6480 if ((flags & wxTEXT_ATTR_BULLET_NUMBER) &&
6481 (attr1.GetBulletNumber() != attr2.GetBulletNumber()))
6482 return false;
6483
6484 if ((flags & wxTEXT_ATTR_BULLET_TEXT) &&
6485 (attr1.GetBulletText() != attr2.GetBulletText()) &&
6486 (attr1.GetBulletFont() != attr2.GetBulletFont()))
6487 return false;
6488
6489 if ((flags & wxTEXT_ATTR_BULLET_NAME) &&
6490 (attr1.GetBulletName() != attr2.GetBulletName()))
6491 return false;
6492
6493 if ((flags & wxTEXT_ATTR_TABS) &&
6494 !wxRichTextTabsEq(attr1.GetTabs(), attr2.GetTabs()))
6495 return false;
6496
6497 if ((flags & wxTEXT_ATTR_PAGE_BREAK) &&
6498 (attr1.HasPageBreak() != attr2.HasPageBreak()))
6499 return false;
6500
6501 if (flags & wxTEXT_ATTR_EFFECTS)
6502 {
6503 if (attr1.HasTextEffects() != attr2.HasTextEffects())
6504 return false;
6505 if (!wxRichTextBitlistsEqPartial(attr1.GetTextEffects(), attr2.GetTextEffects(), attr2.GetTextEffectFlags()))
6506 return false;
6507 }
6508
6509 if ((flags & wxTEXT_ATTR_OUTLINE_LEVEL) &&
6510 (attr1.GetOutlineLevel() != attr2.GetOutlineLevel()))
6511 return false;
6512
6513 return true;
6514 }
6515
6516 bool wxTextAttrEqPartial(const wxTextAttrEx& attr1, const wxRichTextAttr& attr2, int flags)
6517 {
6518 if ((flags & wxTEXT_ATTR_TEXT_COLOUR) && attr1.GetTextColour() != attr2.GetTextColour())
6519 return false;
6520
6521 if ((flags & wxTEXT_ATTR_BACKGROUND_COLOUR) && attr1.GetBackgroundColour() != attr2.GetBackgroundColour())
6522 return false;
6523
6524 if ((flags & (wxTEXT_ATTR_FONT)) && !attr1.GetFont().Ok())
6525 return false;
6526
6527 if ((flags & wxTEXT_ATTR_FONT_FACE) && attr1.GetFont().Ok() &&
6528 attr1.GetFont().GetFaceName() != attr2.GetFontFaceName())
6529 return false;
6530
6531 if ((flags & wxTEXT_ATTR_FONT_SIZE) && attr1.GetFont().Ok() &&
6532 attr1.GetFont().GetPointSize() != attr2.GetFontSize())
6533 return false;
6534
6535 if ((flags & wxTEXT_ATTR_FONT_WEIGHT) && attr1.GetFont().Ok() &&
6536 attr1.GetFont().GetWeight() != attr2.GetFontWeight())
6537 return false;
6538
6539 if ((flags & wxTEXT_ATTR_FONT_ITALIC) && attr1.GetFont().Ok() &&
6540 attr1.GetFont().GetStyle() != attr2.GetFontStyle())
6541 return false;
6542
6543 if ((flags & wxTEXT_ATTR_FONT_UNDERLINE) && attr1.GetFont().Ok() &&
6544 attr1.GetFont().GetUnderlined() != attr2.GetFontUnderlined())
6545 return false;
6546
6547 if ((flags & wxTEXT_ATTR_ALIGNMENT) && attr1.GetAlignment() != attr2.GetAlignment())
6548 return false;
6549
6550 if ((flags & wxTEXT_ATTR_LEFT_INDENT) &&
6551 ((attr1.GetLeftIndent() != attr2.GetLeftIndent()) || (attr1.GetLeftSubIndent() != attr2.GetLeftSubIndent())))
6552 return false;
6553
6554 if ((flags & wxTEXT_ATTR_RIGHT_INDENT) &&
6555 (attr1.GetRightIndent() != attr2.GetRightIndent()))
6556 return false;
6557
6558 if ((flags & wxTEXT_ATTR_PARA_SPACING_AFTER) &&
6559 (attr1.GetParagraphSpacingAfter() != attr2.GetParagraphSpacingAfter()))
6560 return false;
6561
6562 if ((flags & wxTEXT_ATTR_PARA_SPACING_BEFORE) &&
6563 (attr1.GetParagraphSpacingBefore() != attr2.GetParagraphSpacingBefore()))
6564 return false;
6565
6566 if ((flags & wxTEXT_ATTR_LINE_SPACING) &&
6567 (attr1.GetLineSpacing() != attr2.GetLineSpacing()))
6568 return false;
6569
6570 if ((flags & wxTEXT_ATTR_CHARACTER_STYLE_NAME) &&
6571 (attr1.GetCharacterStyleName() != attr2.GetCharacterStyleName()))
6572 return false;
6573
6574 if ((flags & wxTEXT_ATTR_PARAGRAPH_STYLE_NAME) &&
6575 (attr1.GetParagraphStyleName() != attr2.GetParagraphStyleName()))
6576 return false;
6577
6578 if ((flags & wxTEXT_ATTR_LIST_STYLE_NAME) &&
6579 (attr1.GetListStyleName() != attr2.GetListStyleName()))
6580 return false;
6581
6582 if ((flags & wxTEXT_ATTR_BULLET_STYLE) &&
6583 (attr1.GetBulletStyle() != attr2.GetBulletStyle()))
6584 return false;
6585
6586 if ((flags & wxTEXT_ATTR_BULLET_NUMBER) &&
6587 (attr1.GetBulletNumber() != attr2.GetBulletNumber()))
6588 return false;
6589
6590 if ((flags & wxTEXT_ATTR_BULLET_TEXT) &&
6591 (attr1.GetBulletText() != attr2.GetBulletText()) &&
6592 (attr1.GetBulletFont() != attr2.GetBulletFont()))
6593 return false;
6594
6595 if ((flags & wxTEXT_ATTR_BULLET_NAME) &&
6596 (attr1.GetBulletName() != attr2.GetBulletName()))
6597 return false;
6598
6599 if ((flags & wxTEXT_ATTR_TABS) &&
6600 !wxRichTextTabsEq(attr1.GetTabs(), attr2.GetTabs()))
6601 return false;
6602
6603 if ((flags & wxTEXT_ATTR_PAGE_BREAK) &&
6604 (attr1.HasPageBreak() != attr2.HasPageBreak()))
6605 return false;
6606
6607 if (flags & wxTEXT_ATTR_EFFECTS)
6608 {
6609 if (attr1.HasTextEffects() != attr2.HasTextEffects())
6610 return false;
6611 if (!wxRichTextBitlistsEqPartial(attr1.GetTextEffects(), attr2.GetTextEffects(), attr2.GetTextEffectFlags()))
6612 return false;
6613 }
6614
6615 if ((flags & wxTEXT_ATTR_OUTLINE_LEVEL) &&
6616 (attr1.GetOutlineLevel() != attr2.GetOutlineLevel()))
6617 return false;
6618
6619 return true;
6620 }
6621
6622 /// Compare tabs
6623 bool wxRichTextTabsEq(const wxArrayInt& tabs1, const wxArrayInt& tabs2)
6624 {
6625 if (tabs1.GetCount() != tabs2.GetCount())
6626 return false;
6627
6628 size_t i;
6629 for (i = 0; i < tabs1.GetCount(); i++)
6630 {
6631 if (tabs1[i] != tabs2[i])
6632 return false;
6633 }
6634 return true;
6635 }
6636
6637 /// Apply one style to another
6638 bool wxRichTextApplyStyle(wxTextAttrEx& destStyle, const wxTextAttrEx& style)
6639 {
6640 // Whole font
6641 if (style.GetFont().Ok() && ((style.GetFlags() & (wxTEXT_ATTR_FONT)) == (wxTEXT_ATTR_FONT)))
6642 destStyle.SetFont(style.GetFont());
6643 else if (style.GetFont().Ok())
6644 {
6645 wxFont font = destStyle.GetFont();
6646
6647 if (style.GetFlags() & wxTEXT_ATTR_FONT_FACE)
6648 {
6649 destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_FACE);
6650 font.SetFaceName(style.GetFont().GetFaceName());
6651 }
6652
6653 if (style.GetFlags() & wxTEXT_ATTR_FONT_SIZE)
6654 {
6655 destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_SIZE);
6656 font.SetPointSize(style.GetFont().GetPointSize());
6657 }
6658
6659 if (style.GetFlags() & wxTEXT_ATTR_FONT_ITALIC)
6660 {
6661 destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_ITALIC);
6662 font.SetStyle(style.GetFont().GetStyle());
6663 }
6664
6665 if (style.GetFlags() & wxTEXT_ATTR_FONT_WEIGHT)
6666 {
6667 destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_WEIGHT);
6668 font.SetWeight(style.GetFont().GetWeight());
6669 }
6670
6671 if (style.GetFlags() & wxTEXT_ATTR_FONT_UNDERLINE)
6672 {
6673 destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_UNDERLINE);
6674 font.SetUnderlined(style.GetFont().GetUnderlined());
6675 }
6676
6677 if (font != destStyle.GetFont())
6678 {
6679 int oldFlags = destStyle.GetFlags();
6680
6681 destStyle.SetFont(font);
6682
6683 destStyle.SetFlags(oldFlags);
6684 }
6685 }
6686
6687 if ( style.GetTextColour().Ok() && style.HasTextColour())
6688 destStyle.SetTextColour(style.GetTextColour());
6689
6690 if ( style.GetBackgroundColour().Ok() && style.HasBackgroundColour())
6691 destStyle.SetBackgroundColour(style.GetBackgroundColour());
6692
6693 if (style.HasAlignment())
6694 destStyle.SetAlignment(style.GetAlignment());
6695
6696 if (style.HasTabs())
6697 destStyle.SetTabs(style.GetTabs());
6698
6699 if (style.HasLeftIndent())
6700 destStyle.SetLeftIndent(style.GetLeftIndent(), style.GetLeftSubIndent());
6701
6702 if (style.HasRightIndent())
6703 destStyle.SetRightIndent(style.GetRightIndent());
6704
6705 if (style.HasParagraphSpacingAfter())
6706 destStyle.SetParagraphSpacingAfter(style.GetParagraphSpacingAfter());
6707
6708 if (style.HasParagraphSpacingBefore())
6709 destStyle.SetParagraphSpacingBefore(style.GetParagraphSpacingBefore());
6710
6711 if (style.HasLineSpacing())
6712 destStyle.SetLineSpacing(style.GetLineSpacing());
6713
6714 if (style.HasCharacterStyleName())
6715 destStyle.SetCharacterStyleName(style.GetCharacterStyleName());
6716
6717 if (style.HasParagraphStyleName())
6718 destStyle.SetParagraphStyleName(style.GetParagraphStyleName());
6719
6720 if (style.HasListStyleName())
6721 destStyle.SetListStyleName(style.GetListStyleName());
6722
6723 if (style.HasBulletStyle())
6724 destStyle.SetBulletStyle(style.GetBulletStyle());
6725
6726 if (style.HasBulletText())
6727 {
6728 destStyle.SetBulletText(style.GetBulletText());
6729 destStyle.SetBulletFont(style.GetBulletFont());
6730 }
6731
6732 if (style.HasBulletName())
6733 destStyle.SetBulletName(style.GetBulletName());
6734
6735 if (style.HasBulletNumber())
6736 destStyle.SetBulletNumber(style.GetBulletNumber());
6737
6738 if (style.HasURL())
6739 destStyle.SetURL(style.GetURL());
6740
6741 if (style.HasPageBreak())
6742 destStyle.SetPageBreak();
6743
6744 if (style.HasTextEffects())
6745 {
6746 int destBits = destStyle.GetTextEffects();
6747 int destFlags = destStyle.GetTextEffectFlags();
6748
6749 int srcBits = style.GetTextEffects();
6750 int srcFlags = style.GetTextEffectFlags();
6751
6752 wxRichTextCombineBitlists(destBits, srcBits, destFlags, srcFlags);
6753
6754 destStyle.SetTextEffects(destBits);
6755 destStyle.SetTextEffectFlags(destFlags);
6756 }
6757
6758 if (style.HasOutlineLevel())
6759 destStyle.SetOutlineLevel(style.GetOutlineLevel());
6760
6761 return true;
6762 }
6763
6764 bool wxRichTextApplyStyle(wxRichTextAttr& destStyle, const wxTextAttrEx& style)
6765 {
6766 wxTextAttrEx destStyle2 = destStyle;
6767 wxRichTextApplyStyle(destStyle2, style);
6768 destStyle = destStyle2;
6769 return true;
6770 }
6771
6772 bool wxRichTextApplyStyle(wxRichTextAttr& destStyle, const wxRichTextAttr& style, wxRichTextAttr* compareWith)
6773 {
6774 wxTextAttrEx attr(destStyle);
6775 wxRichTextApplyStyle(attr, style, compareWith);
6776 destStyle = attr;
6777 return true;
6778 }
6779
6780 bool wxRichTextApplyStyle(wxTextAttrEx& destStyle, const wxRichTextAttr& style, wxRichTextAttr* compareWith)
6781 {
6782 // Whole font. Avoiding setting individual attributes if possible, since
6783 // it recreates the font each time.
6784 if (((style.GetFlags() & (wxTEXT_ATTR_FONT)) == (wxTEXT_ATTR_FONT)) && !compareWith)
6785 {
6786 destStyle.SetFont(wxFont(style.GetFontSize(), destStyle.GetFont().Ok() ? destStyle.GetFont().GetFamily() : wxDEFAULT,
6787 style.GetFontStyle(), style.GetFontWeight(), style.GetFontUnderlined(), style.GetFontFaceName()));
6788 }
6789 else if (style.GetFlags() & (wxTEXT_ATTR_FONT))
6790 {
6791 wxFont font = destStyle.GetFont();
6792
6793 if (style.GetFlags() & wxTEXT_ATTR_FONT_FACE)
6794 {
6795 if (compareWith && compareWith->HasFaceName() && compareWith->GetFontFaceName() == style.GetFontFaceName())
6796 {
6797 // The same as currently displayed, so don't set
6798 }
6799 else
6800 {
6801 destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_FACE);
6802 font.SetFaceName(style.GetFontFaceName());
6803 }
6804 }
6805
6806 if (style.GetFlags() & wxTEXT_ATTR_FONT_SIZE)
6807 {
6808 if (compareWith && compareWith->HasSize() && compareWith->GetFontSize() == style.GetFontSize())
6809 {
6810 // The same as currently displayed, so don't set
6811 }
6812 else
6813 {
6814 destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_SIZE);
6815 font.SetPointSize(style.GetFontSize());
6816 }
6817 }
6818
6819 if (style.GetFlags() & wxTEXT_ATTR_FONT_ITALIC)
6820 {
6821 if (compareWith && compareWith->HasItalic() && compareWith->GetFontStyle() == style.GetFontStyle())
6822 {
6823 // The same as currently displayed, so don't set
6824 }
6825 else
6826 {
6827 destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_ITALIC);
6828 font.SetStyle(style.GetFontStyle());
6829 }
6830 }
6831
6832 if (style.GetFlags() & wxTEXT_ATTR_FONT_WEIGHT)
6833 {
6834 if (compareWith && compareWith->HasWeight() && compareWith->GetFontWeight() == style.GetFontWeight())
6835 {
6836 // The same as currently displayed, so don't set
6837 }
6838 else
6839 {
6840 destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_WEIGHT);
6841 font.SetWeight(style.GetFontWeight());
6842 }
6843 }
6844
6845 if (style.GetFlags() & wxTEXT_ATTR_FONT_UNDERLINE)
6846 {
6847 if (compareWith && compareWith->HasUnderlined() && compareWith->GetFontUnderlined() == style.GetFontUnderlined())
6848 {
6849 // The same as currently displayed, so don't set
6850 }
6851 else
6852 {
6853 destStyle.SetFlags(destStyle.GetFlags() | wxTEXT_ATTR_FONT_UNDERLINE);
6854 font.SetUnderlined(style.GetFontUnderlined());
6855 }
6856 }
6857
6858 if (font != destStyle.GetFont())
6859 {
6860 int oldFlags = destStyle.GetFlags();
6861
6862 destStyle.SetFont(font);
6863
6864 destStyle.SetFlags(oldFlags);
6865 }
6866 }
6867
6868 if (style.GetTextColour().Ok() && style.HasTextColour())
6869 {
6870 if (!(compareWith && compareWith->HasTextColour() && compareWith->GetTextColour() == style.GetTextColour()))
6871 destStyle.SetTextColour(style.GetTextColour());
6872 }
6873
6874 if (style.GetBackgroundColour().Ok() && style.HasBackgroundColour())
6875 {
6876 if (!(compareWith && compareWith->HasBackgroundColour() && compareWith->GetBackgroundColour() == style.GetBackgroundColour()))
6877 destStyle.SetBackgroundColour(style.GetBackgroundColour());
6878 }
6879
6880 if (style.HasAlignment())
6881 {
6882 if (!(compareWith && compareWith->HasAlignment() && compareWith->GetAlignment() == style.GetAlignment()))
6883 destStyle.SetAlignment(style.GetAlignment());
6884 }
6885
6886 if (style.HasTabs())
6887 {
6888 if (!(compareWith && compareWith->HasTabs() && wxRichTextTabsEq(compareWith->GetTabs(), style.GetTabs())))
6889 destStyle.SetTabs(style.GetTabs());
6890 }
6891
6892 if (style.HasLeftIndent())
6893 {
6894 if (!(compareWith && compareWith->HasLeftIndent() && compareWith->GetLeftIndent() == style.GetLeftIndent()
6895 && compareWith->GetLeftSubIndent() == style.GetLeftSubIndent()))
6896 destStyle.SetLeftIndent(style.GetLeftIndent(), style.GetLeftSubIndent());
6897 }
6898
6899 if (style.HasRightIndent())
6900 {
6901 if (!(compareWith && compareWith->HasRightIndent() && compareWith->GetRightIndent() == style.GetRightIndent()))
6902 destStyle.SetRightIndent(style.GetRightIndent());
6903 }
6904
6905 if (style.HasParagraphSpacingAfter())
6906 {
6907 if (!(compareWith && compareWith->HasParagraphSpacingAfter() && compareWith->GetParagraphSpacingAfter() == style.GetParagraphSpacingAfter()))
6908 destStyle.SetParagraphSpacingAfter(style.GetParagraphSpacingAfter());
6909 }
6910
6911 if (style.HasParagraphSpacingBefore())
6912 {
6913 if (!(compareWith && compareWith->HasParagraphSpacingBefore() && compareWith->GetParagraphSpacingBefore() == style.GetParagraphSpacingBefore()))
6914 destStyle.SetParagraphSpacingBefore(style.GetParagraphSpacingBefore());
6915 }
6916
6917 if (style.HasLineSpacing())
6918 {
6919 if (!(compareWith && compareWith->HasLineSpacing() && compareWith->GetLineSpacing() == style.GetLineSpacing()))
6920 destStyle.SetLineSpacing(style.GetLineSpacing());
6921 }
6922
6923 if (style.HasCharacterStyleName())
6924 {
6925 if (!(compareWith && compareWith->HasCharacterStyleName() && compareWith->GetCharacterStyleName() == style.GetCharacterStyleName()))
6926 destStyle.SetCharacterStyleName(style.GetCharacterStyleName());
6927 }
6928
6929 if (style.HasParagraphStyleName())
6930 {
6931 if (!(compareWith && compareWith->HasParagraphStyleName() && compareWith->GetParagraphStyleName() == style.GetParagraphStyleName()))
6932 destStyle.SetParagraphStyleName(style.GetParagraphStyleName());
6933 }
6934
6935 if (style.HasListStyleName())
6936 {
6937 if (!(compareWith && compareWith->HasListStyleName() && compareWith->GetListStyleName() == style.GetListStyleName()))
6938 destStyle.SetListStyleName(style.GetListStyleName());
6939 }
6940
6941 if (style.HasBulletStyle())
6942 {
6943 if (!(compareWith && compareWith->HasBulletStyle() && compareWith->GetBulletStyle() == style.GetBulletStyle()))
6944 destStyle.SetBulletStyle(style.GetBulletStyle());
6945 }
6946
6947 if (style.HasBulletText())
6948 {
6949 if (!(compareWith && compareWith->HasBulletText() && compareWith->GetBulletText() == style.GetBulletText()))
6950 {
6951 destStyle.SetBulletText(style.GetBulletText());
6952 destStyle.SetBulletFont(style.GetBulletFont());
6953 }
6954 }
6955
6956 if (style.HasBulletNumber())
6957 {
6958 if (!(compareWith && compareWith->HasBulletNumber() && compareWith->GetBulletNumber() == style.GetBulletNumber()))
6959 destStyle.SetBulletNumber(style.GetBulletNumber());
6960 }
6961
6962 if (style.HasBulletName())
6963 {
6964 if (!(compareWith && compareWith->HasBulletName() && compareWith->GetBulletName() == style.GetBulletName()))
6965 destStyle.SetBulletName(style.GetBulletName());
6966 }
6967
6968 if (style.HasURL())
6969 {
6970 if (!(compareWith && compareWith->HasURL() && compareWith->GetURL() == style.GetURL()))
6971 destStyle.SetURL(style.GetURL());
6972 }
6973
6974 if (style.HasPageBreak())
6975 {
6976 if (!(compareWith && compareWith->HasPageBreak()))
6977 destStyle.SetPageBreak();
6978 }
6979
6980 if (style.HasTextEffects())
6981 {
6982 if (!(compareWith && compareWith->HasTextEffects() && compareWith->GetTextEffects() == style.GetTextEffects()))
6983 {
6984 int destBits = destStyle.GetTextEffects();
6985 int destFlags = destStyle.GetTextEffectFlags();
6986
6987 int srcBits = style.GetTextEffects();
6988 int srcFlags = style.GetTextEffectFlags();
6989
6990 wxRichTextCombineBitlists(destBits, srcBits, destFlags, srcFlags);
6991
6992 destStyle.SetTextEffects(destBits);
6993 destStyle.SetTextEffectFlags(destFlags);
6994 }
6995 }
6996
6997 if (style.HasOutlineLevel())
6998 {
6999 if (!(compareWith && compareWith->HasOutlineLevel() && compareWith->GetOutlineLevel() == style.GetOutlineLevel()))
7000 destStyle.SetOutlineLevel(style.GetOutlineLevel());
7001 }
7002
7003 return true;
7004 }
7005
7006 /// Combine two bitlists, specifying the bits of interest with separate flags.
7007 bool wxRichTextCombineBitlists(int& valueA, int valueB, int& flagsA, int flagsB)
7008 {
7009 // We want to apply B's bits to A, taking into account each's flags which indicate which bits
7010 // are to be taken into account. A zero in B's bits should reset that bit in A but only if B's flags
7011 // indicate it.
7012
7013 // First, reset the 0 bits from B. We make a mask so we're only dealing with B's zero
7014 // bits at this point, ignoring any 1 bits in B or 0 bits in B that are not relevant.
7015 int valueA2 = ~(~valueB & flagsB) & valueA;
7016
7017 // Now combine the 1 bits.
7018 int valueA3 = (valueB & flagsB) | valueA2;
7019
7020 valueA = valueA3;
7021 flagsA = (flagsA | flagsB);
7022
7023 return true;
7024 }
7025
7026 /// Compare two bitlists
7027 bool wxRichTextBitlistsEqPartial(int valueA, int valueB, int flags)
7028 {
7029 int relevantBitsA = valueA & flags;
7030 int relevantBitsB = valueB & flags;
7031 return (relevantBitsA != relevantBitsB);
7032 }
7033
7034 /// Split into paragraph and character styles
7035 bool wxRichTextSplitParaCharStyles(const wxTextAttrEx& style, wxTextAttrEx& parStyle, wxTextAttrEx& charStyle)
7036 {
7037 wxTextAttrEx defaultCharStyle1(style);
7038 wxTextAttrEx defaultParaStyle1(style);
7039 defaultCharStyle1.SetFlags(defaultCharStyle1.GetFlags()&wxTEXT_ATTR_CHARACTER);
7040 defaultParaStyle1.SetFlags(defaultParaStyle1.GetFlags()&wxTEXT_ATTR_PARAGRAPH);
7041
7042 wxRichTextApplyStyle(charStyle, defaultCharStyle1);
7043 wxRichTextApplyStyle(parStyle, defaultParaStyle1);
7044
7045 return true;
7046 }
7047
7048 void wxSetFontPreservingStyles(wxTextAttr& attr, const wxFont& font)
7049 {
7050 long flags = attr.GetFlags();
7051 attr.SetFont(font);
7052 attr.SetFlags(flags);
7053 }
7054
7055 /// Convert a decimal to Roman numerals
7056 wxString wxRichTextDecimalToRoman(long n)
7057 {
7058 static wxArrayInt decimalNumbers;
7059 static wxArrayString romanNumbers;
7060
7061 // Clean up arrays
7062 if (n == -1)
7063 {
7064 decimalNumbers.Clear();
7065 romanNumbers.Clear();
7066 return wxEmptyString;
7067 }
7068
7069 if (decimalNumbers.GetCount() == 0)
7070 {
7071 #define wxRichTextAddDecRom(n, r) decimalNumbers.Add(n); romanNumbers.Add(r);
7072
7073 wxRichTextAddDecRom(1000, wxT("M"));
7074 wxRichTextAddDecRom(900, wxT("CM"));
7075 wxRichTextAddDecRom(500, wxT("D"));
7076 wxRichTextAddDecRom(400, wxT("CD"));
7077 wxRichTextAddDecRom(100, wxT("C"));
7078 wxRichTextAddDecRom(90, wxT("XC"));
7079 wxRichTextAddDecRom(50, wxT("L"));
7080 wxRichTextAddDecRom(40, wxT("XL"));
7081 wxRichTextAddDecRom(10, wxT("X"));
7082 wxRichTextAddDecRom(9, wxT("IX"));
7083 wxRichTextAddDecRom(5, wxT("V"));
7084 wxRichTextAddDecRom(4, wxT("IV"));
7085 wxRichTextAddDecRom(1, wxT("I"));
7086 }
7087
7088 int i = 0;
7089 wxString roman;
7090
7091 while (n > 0 && i < 13)
7092 {
7093 if (n >= decimalNumbers[i])
7094 {
7095 n -= decimalNumbers[i];
7096 roman += romanNumbers[i];
7097 }
7098 else
7099 {
7100 i ++;
7101 }
7102 }
7103 if (roman.IsEmpty())
7104 roman = wxT("0");
7105 return roman;
7106 }
7107
7108 /*!
7109 * wxRichTextAttr stores attributes without a wxFont object, so is a much more
7110 * efficient way to query styles.
7111 */
7112
7113 // ctors
7114 wxRichTextAttr::wxRichTextAttr(const wxColour& colText,
7115 const wxColour& colBack,
7116 wxTextAttrAlignment alignment): m_textAlignment(alignment), m_colText(colText), m_colBack(colBack)
7117 {
7118 Init();
7119
7120 if (m_colText.Ok()) m_flags |= wxTEXT_ATTR_TEXT_COLOUR;
7121 if (m_colBack.Ok()) m_flags |= wxTEXT_ATTR_BACKGROUND_COLOUR;
7122 if (alignment != wxTEXT_ALIGNMENT_DEFAULT)
7123 m_flags |= wxTEXT_ATTR_ALIGNMENT;
7124 }
7125
7126 wxRichTextAttr::wxRichTextAttr(const wxTextAttrEx& attr)
7127 {
7128 Init();
7129
7130 (*this) = attr;
7131 }
7132
7133 wxRichTextAttr::wxRichTextAttr(const wxRichTextAttr& attr)
7134 {
7135 Copy(attr);
7136 }
7137
7138 // operations
7139 void wxRichTextAttr::Init()
7140 {
7141 m_textAlignment = wxTEXT_ALIGNMENT_DEFAULT;
7142 m_flags = 0;
7143 m_leftIndent = 0;
7144 m_leftSubIndent = 0;
7145 m_rightIndent = 0;
7146
7147 m_fontSize = 12;
7148 m_fontStyle = wxNORMAL;
7149 m_fontWeight = wxNORMAL;
7150 m_fontUnderlined = false;
7151
7152 m_paragraphSpacingAfter = 0;
7153 m_paragraphSpacingBefore = 0;
7154 m_lineSpacing = 0;
7155 m_bulletStyle = wxTEXT_ATTR_BULLET_STYLE_NONE;
7156 m_textEffects = wxTEXT_ATTR_EFFECT_NONE;
7157 m_textEffectFlags = wxTEXT_ATTR_EFFECT_NONE;
7158 m_outlineLevel = 0;
7159 m_bulletNumber = 0;
7160 }
7161
7162 // Copy
7163 void wxRichTextAttr::Copy(const wxRichTextAttr& attr)
7164 {
7165 m_colText = attr.m_colText;
7166 m_colBack = attr.m_colBack;
7167 m_textAlignment = attr.m_textAlignment;
7168 m_leftIndent = attr.m_leftIndent;
7169 m_leftSubIndent = attr.m_leftSubIndent;
7170 m_rightIndent = attr.m_rightIndent;
7171 m_tabs = attr.m_tabs;
7172 m_flags = attr.m_flags;
7173
7174 m_fontSize = attr.m_fontSize;
7175 m_fontStyle = attr.m_fontStyle;
7176 m_fontWeight = attr.m_fontWeight;
7177 m_fontUnderlined = attr.m_fontUnderlined;
7178 m_fontFaceName = attr.m_fontFaceName;
7179 m_textEffects = attr.m_textEffects;
7180 m_textEffectFlags = attr.m_textEffectFlags;
7181
7182 m_paragraphSpacingAfter = attr.m_paragraphSpacingAfter;
7183 m_paragraphSpacingBefore = attr.m_paragraphSpacingBefore;
7184 m_lineSpacing = attr.m_lineSpacing;
7185 m_characterStyleName = attr.m_characterStyleName;
7186 m_paragraphStyleName = attr.m_paragraphStyleName;
7187 m_listStyleName = attr.m_listStyleName;
7188 m_bulletStyle = attr.m_bulletStyle;
7189 m_bulletNumber = attr.m_bulletNumber;
7190 m_bulletText = attr.m_bulletText;
7191 m_bulletFont = attr.m_bulletFont;
7192 m_bulletName = attr.m_bulletName;
7193 m_outlineLevel = attr.m_outlineLevel;
7194
7195 m_urlTarget = attr.m_urlTarget;
7196 }
7197
7198 // operators
7199 void wxRichTextAttr::operator= (const wxRichTextAttr& attr)
7200 {
7201 Copy(attr);
7202 }
7203
7204 // operators
7205 void wxRichTextAttr::operator= (const wxTextAttrEx& attr)
7206 {
7207 m_flags = attr.GetFlags();
7208
7209 m_colText = attr.GetTextColour();
7210 m_colBack = attr.GetBackgroundColour();
7211 m_textAlignment = attr.GetAlignment();
7212 m_leftIndent = attr.GetLeftIndent();
7213 m_leftSubIndent = attr.GetLeftSubIndent();
7214 m_rightIndent = attr.GetRightIndent();
7215 m_tabs = attr.GetTabs();
7216 m_textEffects = attr.GetTextEffects();
7217 m_textEffectFlags = attr.GetTextEffectFlags();
7218
7219 m_paragraphSpacingAfter = attr.GetParagraphSpacingAfter();
7220 m_paragraphSpacingBefore = attr.GetParagraphSpacingBefore();
7221 m_lineSpacing = attr.GetLineSpacing();
7222 m_characterStyleName = attr.GetCharacterStyleName();
7223 m_paragraphStyleName = attr.GetParagraphStyleName();
7224 m_listStyleName = attr.GetListStyleName();
7225 m_bulletStyle = attr.GetBulletStyle();
7226 m_bulletNumber = attr.GetBulletNumber();
7227 m_bulletText = attr.GetBulletText();
7228 m_bulletName = attr.GetBulletName();
7229 m_bulletFont = attr.GetBulletFont();
7230 m_outlineLevel = attr.GetOutlineLevel();
7231
7232 m_urlTarget = attr.GetURL();
7233
7234 if (attr.GetFont().Ok())
7235 GetFontAttributes(attr.GetFont());
7236 }
7237
7238 // Making a wxTextAttrEx object.
7239 wxRichTextAttr::operator wxTextAttrEx () const
7240 {
7241 wxTextAttrEx attr;
7242 attr.SetTextColour(GetTextColour());
7243 attr.SetBackgroundColour(GetBackgroundColour());
7244 attr.SetAlignment(GetAlignment());
7245 attr.SetTabs(GetTabs());
7246 attr.SetLeftIndent(GetLeftIndent(), GetLeftSubIndent());
7247 attr.SetRightIndent(GetRightIndent());
7248 attr.SetFont(CreateFont());
7249
7250 attr.SetParagraphSpacingAfter(m_paragraphSpacingAfter);
7251 attr.SetParagraphSpacingBefore(m_paragraphSpacingBefore);
7252 attr.SetLineSpacing(m_lineSpacing);
7253 attr.SetBulletStyle(m_bulletStyle);
7254 attr.SetBulletNumber(m_bulletNumber);
7255 attr.SetBulletText(m_bulletText);
7256 attr.SetBulletName(m_bulletName);
7257 attr.SetBulletFont(m_bulletFont);
7258 attr.SetCharacterStyleName(m_characterStyleName);
7259 attr.SetParagraphStyleName(m_paragraphStyleName);
7260 attr.SetListStyleName(m_listStyleName);
7261 attr.SetTextEffects(m_textEffects);
7262 attr.SetTextEffectFlags(m_textEffectFlags);
7263 attr.SetOutlineLevel(m_outlineLevel);
7264
7265 attr.SetURL(m_urlTarget);
7266
7267 attr.SetFlags(GetFlags()); // Important: set after SetFont and others, since they set flags
7268 return attr;
7269 }
7270
7271 // Equality test
7272 bool wxRichTextAttr::operator== (const wxRichTextAttr& attr) const
7273 {
7274 return GetFlags() == attr.GetFlags() &&
7275
7276 GetTextColour() == attr.GetTextColour() &&
7277 GetBackgroundColour() == attr.GetBackgroundColour() &&
7278
7279 GetAlignment() == attr.GetAlignment() &&
7280 GetLeftIndent() == attr.GetLeftIndent() &&
7281 GetLeftSubIndent() == attr.GetLeftSubIndent() &&
7282 GetRightIndent() == attr.GetRightIndent() &&
7283 wxRichTextTabsEq(GetTabs(), attr.GetTabs()) &&
7284
7285 GetParagraphSpacingAfter() == attr.GetParagraphSpacingAfter() &&
7286 GetParagraphSpacingBefore() == attr.GetParagraphSpacingBefore() &&
7287 GetLineSpacing() == attr.GetLineSpacing() &&
7288 GetCharacterStyleName() == attr.GetCharacterStyleName() &&
7289 GetParagraphStyleName() == attr.GetParagraphStyleName() &&
7290 GetListStyleName() == attr.GetListStyleName() &&
7291
7292 GetBulletStyle() == attr.GetBulletStyle() &&
7293 GetBulletText() == attr.GetBulletText() &&
7294 GetBulletNumber() == attr.GetBulletNumber() &&
7295 GetBulletFont() == attr.GetBulletFont() &&
7296 GetBulletName() == attr.GetBulletName() &&
7297
7298 GetTextEffects() == attr.GetTextEffects() &&
7299 GetTextEffectFlags() == attr.GetTextEffectFlags() &&
7300
7301 GetOutlineLevel() == attr.GetOutlineLevel() &&
7302
7303 GetFontSize() == attr.GetFontSize() &&
7304 GetFontStyle() == attr.GetFontStyle() &&
7305 GetFontWeight() == attr.GetFontWeight() &&
7306 GetFontUnderlined() == attr.GetFontUnderlined() &&
7307 GetFontFaceName() == attr.GetFontFaceName() &&
7308
7309 GetURL() == attr.GetURL();
7310 }
7311
7312 // Create font from font attributes.
7313 wxFont wxRichTextAttr::CreateFont() const
7314 {
7315 wxFont font(m_fontSize, wxDEFAULT, m_fontStyle, m_fontWeight, m_fontUnderlined, m_fontFaceName);
7316 #ifdef __WXMAC__
7317 font.SetNoAntiAliasing(true);
7318 #endif
7319 return font;
7320 }
7321
7322 // Get attributes from font.
7323 bool wxRichTextAttr::GetFontAttributes(const wxFont& font)
7324 {
7325 if (!font.Ok())
7326 return false;
7327
7328 m_fontSize = font.GetPointSize();
7329 m_fontStyle = font.GetStyle();
7330 m_fontWeight = font.GetWeight();
7331 m_fontUnderlined = font.GetUnderlined();
7332 m_fontFaceName = font.GetFaceName();
7333
7334 return true;
7335 }
7336
7337 wxRichTextAttr wxRichTextAttr::Combine(const wxRichTextAttr& attr,
7338 const wxRichTextAttr& attrDef,
7339 const wxTextCtrlBase *text)
7340 {
7341 wxColour colFg = attr.GetTextColour();
7342 if ( !colFg.Ok() )
7343 {
7344 colFg = attrDef.GetTextColour();
7345
7346 if ( text && !colFg.Ok() )
7347 colFg = text->GetForegroundColour();
7348 }
7349
7350 wxColour colBg = attr.GetBackgroundColour();
7351 if ( !colBg.Ok() )
7352 {
7353 colBg = attrDef.GetBackgroundColour();
7354
7355 if ( text && !colBg.Ok() )
7356 colBg = text->GetBackgroundColour();
7357 }
7358
7359 wxRichTextAttr newAttr(colFg, colBg);
7360
7361 if (attr.HasWeight())
7362 newAttr.SetFontWeight(attr.GetFontWeight());
7363
7364 if (attr.HasSize())
7365 newAttr.SetFontSize(attr.GetFontSize());
7366
7367 if (attr.HasItalic())
7368 newAttr.SetFontStyle(attr.GetFontStyle());
7369
7370 if (attr.HasUnderlined())
7371 newAttr.SetFontUnderlined(attr.GetFontUnderlined());
7372
7373 if (attr.HasFaceName())
7374 newAttr.SetFontFaceName(attr.GetFontFaceName());
7375
7376 if (attr.HasAlignment())
7377 newAttr.SetAlignment(attr.GetAlignment());
7378 else if (attrDef.HasAlignment())
7379 newAttr.SetAlignment(attrDef.GetAlignment());
7380
7381 if (attr.HasTabs())
7382 newAttr.SetTabs(attr.GetTabs());
7383 else if (attrDef.HasTabs())
7384 newAttr.SetTabs(attrDef.GetTabs());
7385
7386 if (attr.HasLeftIndent())
7387 newAttr.SetLeftIndent(attr.GetLeftIndent(), attr.GetLeftSubIndent());
7388 else if (attrDef.HasLeftIndent())
7389 newAttr.SetLeftIndent(attrDef.GetLeftIndent(), attr.GetLeftSubIndent());
7390
7391 if (attr.HasRightIndent())
7392 newAttr.SetRightIndent(attr.GetRightIndent());
7393 else if (attrDef.HasRightIndent())
7394 newAttr.SetRightIndent(attrDef.GetRightIndent());
7395
7396 // NEW ATTRIBUTES
7397
7398 if (attr.HasParagraphSpacingAfter())
7399 newAttr.SetParagraphSpacingAfter(attr.GetParagraphSpacingAfter());
7400
7401 if (attr.HasParagraphSpacingBefore())
7402 newAttr.SetParagraphSpacingBefore(attr.GetParagraphSpacingBefore());
7403
7404 if (attr.HasLineSpacing())
7405 newAttr.SetLineSpacing(attr.GetLineSpacing());
7406
7407 if (attr.HasCharacterStyleName())
7408 newAttr.SetCharacterStyleName(attr.GetCharacterStyleName());
7409
7410 if (attr.HasParagraphStyleName())
7411 newAttr.SetParagraphStyleName(attr.GetParagraphStyleName());
7412
7413 if (attr.HasListStyleName())
7414 newAttr.SetListStyleName(attr.GetListStyleName());
7415
7416 if (attr.HasBulletStyle())
7417 newAttr.SetBulletStyle(attr.GetBulletStyle());
7418
7419 if (attr.HasBulletNumber())
7420 newAttr.SetBulletNumber(attr.GetBulletNumber());
7421
7422 if (attr.HasBulletName())
7423 newAttr.SetBulletName(attr.GetBulletName());
7424
7425 if (attr.HasBulletText())
7426 {
7427 newAttr.SetBulletText(attr.GetBulletText());
7428 newAttr.SetBulletFont(attr.GetBulletFont());
7429 }
7430
7431 if (attr.HasURL())
7432 newAttr.SetURL(attr.GetURL());
7433
7434 if (attr.HasPageBreak())
7435 newAttr.SetPageBreak();
7436
7437 if (attr.HasTextEffects())
7438 {
7439 newAttr.SetTextEffects(attr.GetTextEffects());
7440 newAttr.SetTextEffectFlags(attr.GetTextEffectFlags());
7441 }
7442
7443 if (attr.HasOutlineLevel())
7444 newAttr.SetOutlineLevel(attr.GetOutlineLevel());
7445
7446 return newAttr;
7447 }
7448
7449 /*!
7450 * wxTextAttrEx is an extended version of wxTextAttr with more paragraph attributes.
7451 */
7452
7453 wxTextAttrEx::wxTextAttrEx(const wxTextAttrEx& attr): wxTextAttr()
7454 {
7455 Copy(attr);
7456 }
7457
7458 // Initialise this object.
7459 void wxTextAttrEx::Init()
7460 {
7461 m_paragraphSpacingAfter = 0;
7462 m_paragraphSpacingBefore = 0;
7463 m_lineSpacing = 0;
7464 m_bulletStyle = wxTEXT_ATTR_BULLET_STYLE_NONE;
7465 m_textEffects = wxTEXT_ATTR_EFFECT_NONE;
7466 m_textEffectFlags = wxTEXT_ATTR_EFFECT_NONE;
7467 m_bulletNumber = 0;
7468 m_outlineLevel = 0;
7469 }
7470
7471 // Copy
7472 void wxTextAttrEx::Copy(const wxTextAttrEx& attr)
7473 {
7474 wxTextAttr::operator= (attr);
7475
7476 m_paragraphSpacingAfter = attr.m_paragraphSpacingAfter;
7477 m_paragraphSpacingBefore = attr.m_paragraphSpacingBefore;
7478 m_lineSpacing = attr.m_lineSpacing;
7479 m_characterStyleName = attr.m_characterStyleName;
7480 m_paragraphStyleName = attr.m_paragraphStyleName;
7481 m_listStyleName = attr.m_listStyleName;
7482 m_bulletStyle = attr.m_bulletStyle;
7483 m_bulletNumber = attr.m_bulletNumber;
7484 m_bulletText = attr.m_bulletText;
7485 m_bulletFont = attr.m_bulletFont;
7486 m_bulletName = attr.m_bulletName;
7487 m_urlTarget = attr.m_urlTarget;
7488 m_textEffects = attr.m_textEffects;
7489 m_textEffectFlags = attr.m_textEffectFlags;
7490 m_outlineLevel = attr.m_outlineLevel;
7491 }
7492
7493 // Assignment from a wxTextAttrEx object
7494 void wxTextAttrEx::operator= (const wxTextAttrEx& attr)
7495 {
7496 Copy(attr);
7497 }
7498
7499 // Assignment from a wxTextAttr object.
7500 void wxTextAttrEx::operator= (const wxTextAttr& attr)
7501 {
7502 wxTextAttr::operator= (attr);
7503 }
7504
7505 // Equality test
7506 bool wxTextAttrEx::operator== (const wxTextAttrEx& attr) const
7507 {
7508 return (
7509 GetFlags() == attr.GetFlags() &&
7510 GetTextColour() == attr.GetTextColour() &&
7511 GetBackgroundColour() == attr.GetBackgroundColour() &&
7512 GetFont() == attr.GetFont() &&
7513 GetTextEffects() == attr.GetTextEffects() &&
7514 GetTextEffectFlags() == attr.GetTextEffectFlags() &&
7515 GetAlignment() == attr.GetAlignment() &&
7516 GetLeftIndent() == attr.GetLeftIndent() &&
7517 GetRightIndent() == attr.GetRightIndent() &&
7518 GetLeftSubIndent() == attr.GetLeftSubIndent() &&
7519 wxRichTextTabsEq(GetTabs(), attr.GetTabs()) &&
7520 GetLineSpacing() == attr.GetLineSpacing() &&
7521 GetParagraphSpacingAfter() == attr.GetParagraphSpacingAfter() &&
7522 GetParagraphSpacingBefore() == attr.GetParagraphSpacingBefore() &&
7523 GetBulletStyle() == attr.GetBulletStyle() &&
7524 GetBulletNumber() == attr.GetBulletNumber() &&
7525 GetBulletText() == attr.GetBulletText() &&
7526 GetBulletName() == attr.GetBulletName() &&
7527 GetBulletFont() == attr.GetBulletFont() &&
7528 GetCharacterStyleName() == attr.GetCharacterStyleName() &&
7529 GetParagraphStyleName() == attr.GetParagraphStyleName() &&
7530 GetListStyleName() == attr.GetListStyleName() &&
7531 GetOutlineLevel() == attr.GetOutlineLevel() &&
7532 GetURL() == attr.GetURL());
7533 }
7534
7535 wxTextAttrEx wxTextAttrEx::CombineEx(const wxTextAttrEx& attr,
7536 const wxTextAttrEx& attrDef,
7537 const wxTextCtrlBase *text)
7538 {
7539 wxTextAttrEx newAttr;
7540
7541 // If attr specifies the complete font, just use that font, overriding all
7542 // default font attributes.
7543 if ((attr.GetFlags() & wxTEXT_ATTR_FONT) == wxTEXT_ATTR_FONT)
7544 newAttr.SetFont(attr.GetFont());
7545 else
7546 {
7547 // First find the basic, default font
7548 long flags = 0;
7549
7550 wxFont font;
7551 if (attrDef.HasFont())
7552 {
7553 flags = (attrDef.GetFlags() & wxTEXT_ATTR_FONT);
7554 font = attrDef.GetFont();
7555 }
7556 else
7557 {
7558 if (text)
7559 font = text->GetFont();
7560
7561 // We leave flags at 0 because no font attributes have been specified yet
7562 }
7563 if (!font.Ok())
7564 font = *wxNORMAL_FONT;
7565
7566 // Otherwise, if there are font attributes in attr, apply them
7567 if (attr.GetFlags() & wxTEXT_ATTR_FONT)
7568 {
7569 if (attr.HasSize())
7570 {
7571 flags |= wxTEXT_ATTR_FONT_SIZE;
7572 font.SetPointSize(attr.GetFont().GetPointSize());
7573 }
7574 if (attr.HasItalic())
7575 {
7576 flags |= wxTEXT_ATTR_FONT_ITALIC;;
7577 font.SetStyle(attr.GetFont().GetStyle());
7578 }
7579 if (attr.HasWeight())
7580 {
7581 flags |= wxTEXT_ATTR_FONT_WEIGHT;
7582 font.SetWeight(attr.GetFont().GetWeight());
7583 }
7584 if (attr.HasFaceName())
7585 {
7586 flags |= wxTEXT_ATTR_FONT_FACE;
7587 font.SetFaceName(attr.GetFont().GetFaceName());
7588 }
7589 if (attr.HasUnderlined())
7590 {
7591 flags |= wxTEXT_ATTR_FONT_UNDERLINE;
7592 font.SetUnderlined(attr.GetFont().GetUnderlined());
7593 }
7594 newAttr.SetFont(font);
7595 newAttr.SetFlags(newAttr.GetFlags()|flags);
7596 }
7597 }
7598
7599 // TODO: should really check we are specifying these in the flags,
7600 // before setting them, as per above; or we will set them willy-nilly.
7601 // However, we should also check whether this is the intention
7602 // as per wxTextAttr::Combine, i.e. always to have valid colours
7603 // in the style.
7604 wxColour colFg = attr.GetTextColour();
7605 if ( !colFg.Ok() )
7606 {
7607 colFg = attrDef.GetTextColour();
7608
7609 if ( text && !colFg.Ok() )
7610 colFg = text->GetForegroundColour();
7611 }
7612
7613 wxColour colBg = attr.GetBackgroundColour();
7614 if ( !colBg.Ok() )
7615 {
7616 colBg = attrDef.GetBackgroundColour();
7617
7618 if ( text && !colBg.Ok() )
7619 colBg = text->GetBackgroundColour();
7620 }
7621
7622 newAttr.SetTextColour(colFg);
7623 newAttr.SetBackgroundColour(colBg);
7624
7625 if (attr.HasAlignment())
7626 newAttr.SetAlignment(attr.GetAlignment());
7627 else if (attrDef.HasAlignment())
7628 newAttr.SetAlignment(attrDef.GetAlignment());
7629
7630 if (attr.HasTabs())
7631 newAttr.SetTabs(attr.GetTabs());
7632 else if (attrDef.HasTabs())
7633 newAttr.SetTabs(attrDef.GetTabs());
7634
7635 if (attr.HasLeftIndent())
7636 newAttr.SetLeftIndent(attr.GetLeftIndent(), attr.GetLeftSubIndent());
7637 else if (attrDef.HasLeftIndent())
7638 newAttr.SetLeftIndent(attrDef.GetLeftIndent(), attr.GetLeftSubIndent());
7639
7640 if (attr.HasRightIndent())
7641 newAttr.SetRightIndent(attr.GetRightIndent());
7642 else if (attrDef.HasRightIndent())
7643 newAttr.SetRightIndent(attrDef.GetRightIndent());
7644
7645 // NEW ATTRIBUTES
7646
7647 if (attr.HasParagraphSpacingAfter())
7648 newAttr.SetParagraphSpacingAfter(attr.GetParagraphSpacingAfter());
7649
7650 if (attr.HasParagraphSpacingBefore())
7651 newAttr.SetParagraphSpacingBefore(attr.GetParagraphSpacingBefore());
7652
7653 if (attr.HasLineSpacing())
7654 newAttr.SetLineSpacing(attr.GetLineSpacing());
7655
7656 if (attr.HasCharacterStyleName())
7657 newAttr.SetCharacterStyleName(attr.GetCharacterStyleName());
7658
7659 if (attr.HasParagraphStyleName())
7660 newAttr.SetParagraphStyleName(attr.GetParagraphStyleName());
7661
7662 if (attr.HasListStyleName())
7663 newAttr.SetListStyleName(attr.GetListStyleName());
7664
7665 if (attr.HasBulletStyle())
7666 newAttr.SetBulletStyle(attr.GetBulletStyle());
7667
7668 if (attr.HasBulletNumber())
7669 newAttr.SetBulletNumber(attr.GetBulletNumber());
7670
7671 if (attr.HasBulletName())
7672 newAttr.SetBulletName(attr.GetBulletName());
7673
7674 if (attr.HasBulletText())
7675 {
7676 newAttr.SetBulletText(attr.GetBulletText());
7677 newAttr.SetBulletFont(attr.GetBulletFont());
7678 }
7679
7680 if (attr.HasURL())
7681 newAttr.SetURL(attr.GetURL());
7682
7683 if (attr.HasTextEffects())
7684 {
7685 newAttr.SetTextEffects(attr.GetTextEffects());
7686 newAttr.SetTextEffectFlags(attr.GetTextEffectFlags());
7687 }
7688
7689 if (attr.HasOutlineLevel())
7690 newAttr.SetOutlineLevel(attr.GetOutlineLevel());
7691
7692 return newAttr;
7693 }
7694
7695
7696 /*!
7697 * wxRichTextFileHandler
7698 * Base class for file handlers
7699 */
7700
7701 IMPLEMENT_CLASS(wxRichTextFileHandler, wxObject)
7702
7703 #if wxUSE_STREAMS
7704 bool wxRichTextFileHandler::LoadFile(wxRichTextBuffer *buffer, const wxString& filename)
7705 {
7706 wxFFileInputStream stream(filename);
7707 if (stream.Ok())
7708 return LoadFile(buffer, stream);
7709
7710 return false;
7711 }
7712
7713 bool wxRichTextFileHandler::SaveFile(wxRichTextBuffer *buffer, const wxString& filename)
7714 {
7715 wxFFileOutputStream stream(filename);
7716 if (stream.Ok())
7717 return SaveFile(buffer, stream);
7718
7719 return false;
7720 }
7721 #endif // wxUSE_STREAMS
7722
7723 /// Can we handle this filename (if using files)? By default, checks the extension.
7724 bool wxRichTextFileHandler::CanHandle(const wxString& filename) const
7725 {
7726 wxString path, file, ext;
7727 wxSplitPath(filename, & path, & file, & ext);
7728
7729 return (ext.Lower() == GetExtension());
7730 }
7731
7732 /*!
7733 * wxRichTextTextHandler
7734 * Plain text handler
7735 */
7736
7737 IMPLEMENT_CLASS(wxRichTextPlainTextHandler, wxRichTextFileHandler)
7738
7739 #if wxUSE_STREAMS
7740 bool wxRichTextPlainTextHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream)
7741 {
7742 if (!stream.IsOk())
7743 return false;
7744
7745 wxString str;
7746 int lastCh = 0;
7747
7748 while (!stream.Eof())
7749 {
7750 int ch = stream.GetC();
7751
7752 if (!stream.Eof())
7753 {
7754 if (ch == 10 && lastCh != 13)
7755 str += wxT('\n');
7756
7757 if (ch > 0 && ch != 10)
7758 str += wxChar(ch);
7759
7760 lastCh = ch;
7761 }
7762 }
7763
7764 buffer->ResetAndClearCommands();
7765 buffer->Clear();
7766 buffer->AddParagraphs(str);
7767 buffer->UpdateRanges();
7768
7769 return true;
7770 }
7771
7772 bool wxRichTextPlainTextHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
7773 {
7774 if (!stream.IsOk())
7775 return false;
7776
7777 wxString text = buffer->GetText();
7778 wxCharBuffer buf = text.ToAscii();
7779
7780 stream.Write((const char*) buf, text.length());
7781 return true;
7782 }
7783 #endif // wxUSE_STREAMS
7784
7785 /*
7786 * Stores information about an image, in binary in-memory form
7787 */
7788
7789 wxRichTextImageBlock::wxRichTextImageBlock()
7790 {
7791 Init();
7792 }
7793
7794 wxRichTextImageBlock::wxRichTextImageBlock(const wxRichTextImageBlock& block):wxObject()
7795 {
7796 Init();
7797 Copy(block);
7798 }
7799
7800 wxRichTextImageBlock::~wxRichTextImageBlock()
7801 {
7802 if (m_data)
7803 {
7804 delete[] m_data;
7805 m_data = NULL;
7806 }
7807 }
7808
7809 void wxRichTextImageBlock::Init()
7810 {
7811 m_data = NULL;
7812 m_dataSize = 0;
7813 m_imageType = -1;
7814 }
7815
7816 void wxRichTextImageBlock::Clear()
7817 {
7818 delete[] m_data;
7819 m_data = NULL;
7820 m_dataSize = 0;
7821 m_imageType = -1;
7822 }
7823
7824
7825 // Load the original image into a memory block.
7826 // If the image is not a JPEG, we must convert it into a JPEG
7827 // to conserve space.
7828 // If it's not a JPEG we can make use of 'image', already scaled, so we don't have to
7829 // load the image a 2nd time.
7830
7831 bool wxRichTextImageBlock::MakeImageBlock(const wxString& filename, int imageType, wxImage& image, bool convertToJPEG)
7832 {
7833 m_imageType = imageType;
7834
7835 wxString filenameToRead(filename);
7836 bool removeFile = false;
7837
7838 if (imageType == -1)
7839 return false; // Could not determine image type
7840
7841 if ((imageType != wxBITMAP_TYPE_JPEG) && convertToJPEG)
7842 {
7843 wxString tempFile;
7844 bool success = wxGetTempFileName(_("image"), tempFile) ;
7845
7846 wxASSERT(success);
7847
7848 wxUnusedVar(success);
7849
7850 image.SaveFile(tempFile, wxBITMAP_TYPE_JPEG);
7851 filenameToRead = tempFile;
7852 removeFile = true;
7853
7854 m_imageType = wxBITMAP_TYPE_JPEG;
7855 }
7856 wxFile file;
7857 if (!file.Open(filenameToRead))
7858 return false;
7859
7860 m_dataSize = (size_t) file.Length();
7861 file.Close();
7862
7863 if (m_data)
7864 delete[] m_data;
7865 m_data = ReadBlock(filenameToRead, m_dataSize);
7866
7867 if (removeFile)
7868 wxRemoveFile(filenameToRead);
7869
7870 return (m_data != NULL);
7871 }
7872
7873 // Make an image block from the wxImage in the given
7874 // format.
7875 bool wxRichTextImageBlock::MakeImageBlock(wxImage& image, int imageType, int quality)
7876 {
7877 m_imageType = imageType;
7878 image.SetOption(wxT("quality"), quality);
7879
7880 if (imageType == -1)
7881 return false; // Could not determine image type
7882
7883 wxString tempFile;
7884 bool success = wxGetTempFileName(_("image"), tempFile) ;
7885
7886 wxASSERT(success);
7887 wxUnusedVar(success);
7888
7889 if (!image.SaveFile(tempFile, m_imageType))
7890 {
7891 if (wxFileExists(tempFile))
7892 wxRemoveFile(tempFile);
7893 return false;
7894 }
7895
7896 wxFile file;
7897 if (!file.Open(tempFile))
7898 return false;
7899
7900 m_dataSize = (size_t) file.Length();
7901 file.Close();
7902
7903 if (m_data)
7904 delete[] m_data;
7905 m_data = ReadBlock(tempFile, m_dataSize);
7906
7907 wxRemoveFile(tempFile);
7908
7909 return (m_data != NULL);
7910 }
7911
7912
7913 // Write to a file
7914 bool wxRichTextImageBlock::Write(const wxString& filename)
7915 {
7916 return WriteBlock(filename, m_data, m_dataSize);
7917 }
7918
7919 void wxRichTextImageBlock::Copy(const wxRichTextImageBlock& block)
7920 {
7921 m_imageType = block.m_imageType;
7922 if (m_data)
7923 {
7924 delete[] m_data;
7925 m_data = NULL;
7926 }
7927 m_dataSize = block.m_dataSize;
7928 if (m_dataSize == 0)
7929 return;
7930
7931 m_data = new unsigned char[m_dataSize];
7932 unsigned int i;
7933 for (i = 0; i < m_dataSize; i++)
7934 m_data[i] = block.m_data[i];
7935 }
7936
7937 //// Operators
7938 void wxRichTextImageBlock::operator=(const wxRichTextImageBlock& block)
7939 {
7940 Copy(block);
7941 }
7942
7943 // Load a wxImage from the block
7944 bool wxRichTextImageBlock::Load(wxImage& image)
7945 {
7946 if (!m_data)
7947 return false;
7948
7949 // Read in the image.
7950 #if wxUSE_STREAMS
7951 wxMemoryInputStream mstream(m_data, m_dataSize);
7952 bool success = image.LoadFile(mstream, GetImageType());
7953 #else
7954 wxString tempFile;
7955 bool success = wxGetTempFileName(_("image"), tempFile) ;
7956 wxASSERT(success);
7957
7958 if (!WriteBlock(tempFile, m_data, m_dataSize))
7959 {
7960 return false;
7961 }
7962 success = image.LoadFile(tempFile, GetImageType());
7963 wxRemoveFile(tempFile);
7964 #endif
7965
7966 return success;
7967 }
7968
7969 // Write data in hex to a stream
7970 bool wxRichTextImageBlock::WriteHex(wxOutputStream& stream)
7971 {
7972 wxString hex;
7973 int i;
7974 for (i = 0; i < (int) m_dataSize; i++)
7975 {
7976 hex = wxDecToHex(m_data[i]);
7977 wxCharBuffer buf = hex.ToAscii();
7978
7979 stream.Write((const char*) buf, hex.length());
7980 }
7981
7982 return true;
7983 }
7984
7985 // Read data in hex from a stream
7986 bool wxRichTextImageBlock::ReadHex(wxInputStream& stream, int length, int imageType)
7987 {
7988 int dataSize = length/2;
7989
7990 if (m_data)
7991 delete[] m_data;
7992
7993 wxString str(wxT(" "));
7994 m_data = new unsigned char[dataSize];
7995 int i;
7996 for (i = 0; i < dataSize; i ++)
7997 {
7998 str[0] = stream.GetC();
7999 str[1] = stream.GetC();
8000
8001 m_data[i] = (unsigned char)wxHexToDec(str);
8002 }
8003
8004 m_dataSize = dataSize;
8005 m_imageType = imageType;
8006
8007 return true;
8008 }
8009
8010 // Allocate and read from stream as a block of memory
8011 unsigned char* wxRichTextImageBlock::ReadBlock(wxInputStream& stream, size_t size)
8012 {
8013 unsigned char* block = new unsigned char[size];
8014 if (!block)
8015 return NULL;
8016
8017 stream.Read(block, size);
8018
8019 return block;
8020 }
8021
8022 unsigned char* wxRichTextImageBlock::ReadBlock(const wxString& filename, size_t size)
8023 {
8024 wxFileInputStream stream(filename);
8025 if (!stream.Ok())
8026 return NULL;
8027
8028 return ReadBlock(stream, size);
8029 }
8030
8031 // Write memory block to stream
8032 bool wxRichTextImageBlock::WriteBlock(wxOutputStream& stream, unsigned char* block, size_t size)
8033 {
8034 stream.Write((void*) block, size);
8035 return stream.IsOk();
8036
8037 }
8038
8039 // Write memory block to file
8040 bool wxRichTextImageBlock::WriteBlock(const wxString& filename, unsigned char* block, size_t size)
8041 {
8042 wxFileOutputStream outStream(filename);
8043 if (!outStream.Ok())
8044 return false;
8045
8046 return WriteBlock(outStream, block, size);
8047 }
8048
8049 // Gets the extension for the block's type
8050 wxString wxRichTextImageBlock::GetExtension() const
8051 {
8052 wxImageHandler* handler = wxImage::FindHandler(GetImageType());
8053 if (handler)
8054 return handler->GetExtension();
8055 else
8056 return wxEmptyString;
8057 }
8058
8059 #if wxUSE_DATAOBJ
8060
8061 /*!
8062 * The data object for a wxRichTextBuffer
8063 */
8064
8065 const wxChar *wxRichTextBufferDataObject::ms_richTextBufferFormatId = wxT("wxShape");
8066
8067 wxRichTextBufferDataObject::wxRichTextBufferDataObject(wxRichTextBuffer* richTextBuffer)
8068 {
8069 m_richTextBuffer = richTextBuffer;
8070
8071 // this string should uniquely identify our format, but is otherwise
8072 // arbitrary
8073 m_formatRichTextBuffer.SetId(GetRichTextBufferFormatId());
8074
8075 SetFormat(m_formatRichTextBuffer);
8076 }
8077
8078 wxRichTextBufferDataObject::~wxRichTextBufferDataObject()
8079 {
8080 delete m_richTextBuffer;
8081 }
8082
8083 // after a call to this function, the richTextBuffer is owned by the caller and it
8084 // is responsible for deleting it!
8085 wxRichTextBuffer* wxRichTextBufferDataObject::GetRichTextBuffer()
8086 {
8087 wxRichTextBuffer* richTextBuffer = m_richTextBuffer;
8088 m_richTextBuffer = NULL;
8089
8090 return richTextBuffer;
8091 }
8092
8093 wxDataFormat wxRichTextBufferDataObject::GetPreferredFormat(Direction WXUNUSED(dir)) const
8094 {
8095 return m_formatRichTextBuffer;
8096 }
8097
8098 size_t wxRichTextBufferDataObject::GetDataSize() const
8099 {
8100 if (!m_richTextBuffer)
8101 return 0;
8102
8103 wxString bufXML;
8104
8105 {
8106 wxStringOutputStream stream(& bufXML);
8107 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
8108 {
8109 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
8110 return 0;
8111 }
8112 }
8113
8114 #if wxUSE_UNICODE
8115 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
8116 return strlen(buffer) + 1;
8117 #else
8118 return bufXML.Length()+1;
8119 #endif
8120 }
8121
8122 bool wxRichTextBufferDataObject::GetDataHere(void *pBuf) const
8123 {
8124 if (!pBuf || !m_richTextBuffer)
8125 return false;
8126
8127 wxString bufXML;
8128
8129 {
8130 wxStringOutputStream stream(& bufXML);
8131 if (!m_richTextBuffer->SaveFile(stream, wxRICHTEXT_TYPE_XML))
8132 {
8133 wxLogError(wxT("Could not write the buffer to an XML stream.\nYou may have forgotten to add the XML file handler."));
8134 return 0;
8135 }
8136 }
8137
8138 #if wxUSE_UNICODE
8139 wxCharBuffer buffer = bufXML.mb_str(wxConvUTF8);
8140 size_t len = strlen(buffer);
8141 memcpy((char*) pBuf, (const char*) buffer, len);
8142 ((char*) pBuf)[len] = 0;
8143 #else
8144 size_t len = bufXML.Length();
8145 memcpy((char*) pBuf, (const char*) bufXML.c_str(), len);
8146 ((char*) pBuf)[len] = 0;
8147 #endif
8148
8149 return true;
8150 }
8151
8152 bool wxRichTextBufferDataObject::SetData(size_t WXUNUSED(len), const void *buf)
8153 {
8154 delete m_richTextBuffer;
8155 m_richTextBuffer = NULL;
8156
8157 wxString bufXML((const char*) buf, wxConvUTF8);
8158
8159 m_richTextBuffer = new wxRichTextBuffer;
8160
8161 wxStringInputStream stream(bufXML);
8162 if (!m_richTextBuffer->LoadFile(stream, wxRICHTEXT_TYPE_XML))
8163 {
8164 wxLogError(wxT("Could not read the buffer from an XML stream.\nYou may have forgotten to add the XML file handler."));
8165
8166 delete m_richTextBuffer;
8167 m_richTextBuffer = NULL;
8168
8169 return false;
8170 }
8171 return true;
8172 }
8173
8174 #endif
8175 // wxUSE_DATAOBJ
8176
8177 #endif
8178 // wxUSE_RICHTEXT