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