]> git.saurik.com Git - wxWidgets.git/blob - src/richtext/richtextctrl.cpp
Don't use toolbook for formatting dialog until we have icons
[wxWidgets.git] / src / richtext / richtextctrl.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/richtext/richeditctrl.cpp
3 // Purpose: A rich edit control
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/richtextctrl.h"
22 #include "wx/richtext/richtextstyles.h"
23
24 #ifndef WX_PRECOMP
25 #include "wx/wx.h"
26 #include "wx/settings.h"
27 #endif
28
29 #include "wx/textfile.h"
30 #include "wx/ffile.h"
31 #include "wx/filename.h"
32 #include "wx/dcbuffer.h"
33 #include "wx/arrimpl.cpp"
34
35 // DLL options compatibility check:
36 #include "wx/app.h"
37 WX_CHECK_BUILD_OPTIONS("wxRichTextCtrl")
38
39 DEFINE_EVENT_TYPE(wxEVT_COMMAND_RICHTEXT_ITEM_SELECTED)
40 DEFINE_EVENT_TYPE(wxEVT_COMMAND_RICHTEXT_ITEM_DESELECTED)
41 DEFINE_EVENT_TYPE(wxEVT_COMMAND_RICHTEXT_LEFT_CLICK)
42 DEFINE_EVENT_TYPE(wxEVT_COMMAND_RICHTEXT_MIDDLE_CLICK)
43 DEFINE_EVENT_TYPE(wxEVT_COMMAND_RICHTEXT_RIGHT_CLICK)
44 DEFINE_EVENT_TYPE(wxEVT_COMMAND_RICHTEXT_LEFT_DCLICK)
45 DEFINE_EVENT_TYPE(wxEVT_COMMAND_RICHTEXT_RETURN)
46
47 IMPLEMENT_CLASS( wxRichTextCtrl, wxControl )
48
49 IMPLEMENT_CLASS( wxRichTextEvent, wxNotifyEvent )
50
51 BEGIN_EVENT_TABLE( wxRichTextCtrl, wxControl )
52 EVT_PAINT(wxRichTextCtrl::OnPaint)
53 EVT_ERASE_BACKGROUND(wxRichTextCtrl::OnEraseBackground)
54 EVT_IDLE(wxRichTextCtrl::OnIdle)
55 EVT_SCROLLWIN(wxRichTextCtrl::OnScroll)
56 EVT_LEFT_DOWN(wxRichTextCtrl::OnLeftClick)
57 EVT_MOTION(wxRichTextCtrl::OnMoveMouse)
58 EVT_LEFT_UP(wxRichTextCtrl::OnLeftUp)
59 EVT_RIGHT_DOWN(wxRichTextCtrl::OnRightClick)
60 EVT_MIDDLE_DOWN(wxRichTextCtrl::OnMiddleClick)
61 EVT_LEFT_DCLICK(wxRichTextCtrl::OnLeftDClick)
62 EVT_CHAR(wxRichTextCtrl::OnChar)
63 EVT_SIZE(wxRichTextCtrl::OnSize)
64 EVT_SET_FOCUS(wxRichTextCtrl::OnSetFocus)
65 EVT_KILL_FOCUS(wxRichTextCtrl::OnKillFocus)
66 EVT_CONTEXT_MENU(wxRichTextCtrl::OnContextMenu)
67
68 EVT_MENU(wxID_UNDO, wxRichTextCtrl::OnUndo)
69 EVT_UPDATE_UI(wxID_UNDO, wxRichTextCtrl::OnUpdateUndo)
70
71 EVT_MENU(wxID_REDO, wxRichTextCtrl::OnRedo)
72 EVT_UPDATE_UI(wxID_REDO, wxRichTextCtrl::OnUpdateRedo)
73
74 EVT_MENU(wxID_COPY, wxRichTextCtrl::OnCopy)
75 EVT_UPDATE_UI(wxID_COPY, wxRichTextCtrl::OnUpdateCopy)
76
77 EVT_MENU(wxID_PASTE, wxRichTextCtrl::OnPaste)
78 EVT_UPDATE_UI(wxID_PASTE, wxRichTextCtrl::OnUpdatePaste)
79
80 EVT_MENU(wxID_CUT, wxRichTextCtrl::OnCut)
81 EVT_UPDATE_UI(wxID_CUT, wxRichTextCtrl::OnUpdateCut)
82
83 EVT_MENU(wxID_CLEAR, wxRichTextCtrl::OnClear)
84 EVT_UPDATE_UI(wxID_CLEAR, wxRichTextCtrl::OnUpdateClear)
85
86 EVT_MENU(wxID_SELECTALL, wxRichTextCtrl::OnSelectAll)
87 EVT_UPDATE_UI(wxID_SELECTALL, wxRichTextCtrl::OnUpdateSelectAll)
88 END_EVENT_TABLE()
89
90 /*!
91 * wxRichTextCtrl
92 */
93
94 wxRichTextCtrl::wxRichTextCtrl()
95 : wxScrollHelper(this)
96 {
97 Init();
98 }
99
100 wxRichTextCtrl::wxRichTextCtrl(wxWindow* parent,
101 wxWindowID id,
102 const wxString& value,
103 const wxPoint& pos,
104 const wxSize& size,
105 long style)
106 : wxScrollHelper(this)
107 {
108 Init();
109 Create(parent, id, value, pos, size, style);
110 }
111
112 /// Creation
113 bool wxRichTextCtrl::Create( wxWindow* parent, wxWindowID id, const wxString& value, const wxPoint& pos, const wxSize& size, long style)
114 {
115 if (!wxTextCtrlBase::Create(parent, id, pos, size,
116 style|wxFULL_REPAINT_ON_RESIZE))
117 return false;
118
119 if (!GetFont().Ok())
120 {
121 SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
122 }
123
124 GetBuffer().SetRichTextCtrl(this);
125
126 wxTextAttrEx attributes;
127 attributes.SetFont(GetFont());
128 attributes.SetTextColour(*wxBLACK);
129 attributes.SetBackgroundColour(*wxWHITE);
130 attributes.SetAlignment(wxTEXT_ALIGNMENT_LEFT);
131 attributes.SetLineSpacing(10);
132 attributes.SetParagraphSpacingAfter(10);
133 attributes.SetParagraphSpacingBefore(0);
134 attributes.SetFlags(wxTEXT_ATTR_ALL);
135 SetBasicStyle(attributes);
136
137 // The default attributes will be merged with base attributes, so
138 // can be empty to begin with
139 wxTextAttrEx defaultAttributes;
140 SetDefaultStyle(defaultAttributes);
141
142 SetBackgroundColour(*wxWHITE);
143 SetBackgroundStyle(wxBG_STYLE_CUSTOM);
144
145 // Tell the sizers to use the given or best size
146 SetBestFittingSize(size);
147
148 #if wxRICHTEXT_BUFFERED_PAINTING
149 // Create a buffer
150 RecreateBuffer(size);
151 #endif
152
153 SetCursor(wxCursor(wxCURSOR_IBEAM));
154
155 if (!value.IsEmpty())
156 SetValue(value);
157
158 return true;
159 }
160
161 wxRichTextCtrl::~wxRichTextCtrl()
162 {
163 delete m_contextMenu;
164 }
165
166 /// Member initialisation
167 void wxRichTextCtrl::Init()
168 {
169 m_freezeCount = 0;
170 m_contextMenu = NULL;
171 m_caret = NULL;
172 m_caretPosition = -1;
173 m_selectionRange.SetRange(-2, -2);
174 m_selectionAnchor = -2;
175 m_editable = true;
176 m_caretAtLineStart = false;
177 m_dragging = false;
178 m_fullLayoutRequired = false;
179 m_fullLayoutTime = 0;
180 m_fullLayoutSavedPosition = 0;
181 m_delayedLayoutThreshold = wxRICHTEXT_DEFAULT_DELAYED_LAYOUT_THRESHOLD;
182 m_caretPositionForDefaultStyle = -2;
183 }
184
185 /// Call Freeze to prevent refresh
186 void wxRichTextCtrl::Freeze()
187 {
188 m_freezeCount ++;
189 }
190
191 /// Call Thaw to refresh
192 void wxRichTextCtrl::Thaw()
193 {
194 m_freezeCount --;
195
196 if (m_freezeCount == 0)
197 {
198 SetupScrollbars();
199 Refresh(false);
200 }
201 }
202
203 /// Clear all text
204 void wxRichTextCtrl::Clear()
205 {
206 m_buffer.Reset();
207 m_buffer.SetDirty(true);
208 m_caretPosition = -1;
209 m_caretPositionForDefaultStyle = -2;
210 m_caretAtLineStart = false;
211 m_selectionRange.SetRange(-2, -2);
212
213 SetScrollbars(0, 0, 0, 0, 0, 0);
214
215 if (m_freezeCount == 0)
216 {
217 SetupScrollbars();
218 Refresh(false);
219 }
220 SendTextUpdatedEvent();
221 }
222
223 /// Painting
224 void wxRichTextCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
225 {
226 if (GetCaret())
227 GetCaret()->Hide();
228
229 {
230 #if wxRICHTEXT_BUFFERED_PAINTING
231 wxBufferedPaintDC dc(this, m_bufferBitmap);
232 #else
233 wxPaintDC dc(this);
234 #endif
235 PrepareDC(dc);
236
237 if (m_freezeCount > 0)
238 return;
239
240 dc.SetFont(GetFont());
241
242 // Paint the background
243 PaintBackground(dc);
244
245 wxRect drawingArea(GetLogicalPoint(wxPoint(0, 0)), GetClientSize());
246 wxRect availableSpace(GetClientSize());
247 if (GetBuffer().GetDirty())
248 {
249 GetBuffer().Layout(dc, availableSpace, wxRICHTEXT_FIXED_WIDTH|wxRICHTEXT_VARIABLE_HEIGHT);
250 GetBuffer().SetDirty(false);
251 SetupScrollbars();
252 }
253
254 GetBuffer().Draw(dc, GetBuffer().GetRange(), GetInternalSelectionRange(), drawingArea, 0 /* descent */, 0 /* flags */);
255 }
256
257 if (GetCaret())
258 GetCaret()->Show();
259
260 PositionCaret();
261 }
262
263 // Empty implementation, to prevent flicker
264 void wxRichTextCtrl::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
265 {
266 }
267
268 void wxRichTextCtrl::OnSetFocus(wxFocusEvent& WXUNUSED(event))
269 {
270 wxCaret* caret = new wxCaret(this, wxRICHTEXT_DEFAULT_CARET_WIDTH, 16);
271 SetCaret(caret);
272 caret->Show();
273 PositionCaret();
274
275 if (!IsFrozen())
276 Refresh(false);
277 }
278
279 void wxRichTextCtrl::OnKillFocus(wxFocusEvent& WXUNUSED(event))
280 {
281 SetCaret(NULL);
282
283 if (!IsFrozen())
284 Refresh(false);
285 }
286
287 /// Left-click
288 void wxRichTextCtrl::OnLeftClick(wxMouseEvent& event)
289 {
290 SetFocus();
291
292 wxClientDC dc(this);
293 PrepareDC(dc);
294 dc.SetFont(GetFont());
295
296 long position = 0;
297 int hit = GetBuffer().HitTest(dc, event.GetLogicalPosition(dc), position);
298
299 if (hit != wxRICHTEXT_HITTEST_NONE)
300 {
301 m_dragStart = event.GetLogicalPosition(dc);
302 m_dragging = true;
303 CaptureMouse();
304
305 SelectNone();
306
307 bool caretAtLineStart = false;
308
309 if (hit & wxRICHTEXT_HITTEST_BEFORE)
310 {
311 // If we're at the start of a line (but not first in para)
312 // then we should keep the caret showing at the start of the line
313 // by showing the m_caretAtLineStart flag.
314 wxRichTextParagraph* para = GetBuffer().GetParagraphAtPosition(position);
315 wxRichTextLine* line = GetBuffer().GetLineAtPosition(position);
316
317 if (line && para && line->GetAbsoluteRange().GetStart() == position && para->GetRange().GetStart() != position)
318 caretAtLineStart = true;
319 position --;
320 }
321
322 MoveCaret(position, caretAtLineStart);
323 SetDefaultStyleToCursorStyle();
324 }
325
326 event.Skip();
327 }
328
329 /// Left-up
330 void wxRichTextCtrl::OnLeftUp(wxMouseEvent& WXUNUSED(event))
331 {
332 if (m_dragging)
333 {
334 m_dragging = false;
335 if (GetCapture() == this)
336 ReleaseMouse();
337 }
338 }
339
340 /// Left-click
341 void wxRichTextCtrl::OnMoveMouse(wxMouseEvent& event)
342 {
343 if (!event.Dragging())
344 {
345 event.Skip();
346 return;
347 }
348
349 wxClientDC dc(this);
350 PrepareDC(dc);
351 dc.SetFont(GetFont());
352
353 long position = 0;
354 wxPoint logicalPt = event.GetLogicalPosition(dc);
355 int hit = GetBuffer().HitTest(dc, logicalPt, position);
356
357 if (m_dragging && hit != wxRICHTEXT_HITTEST_NONE)
358 {
359 // TODO: test closeness
360
361 bool caretAtLineStart = false;
362
363 if (hit & wxRICHTEXT_HITTEST_BEFORE)
364 {
365 // If we're at the start of a line (but not first in para)
366 // then we should keep the caret showing at the start of the line
367 // by showing the m_caretAtLineStart flag.
368 wxRichTextParagraph* para = GetBuffer().GetParagraphAtPosition(position);
369 wxRichTextLine* line = GetBuffer().GetLineAtPosition(position);
370
371 if (line && para && line->GetAbsoluteRange().GetStart() == position && para->GetRange().GetStart() != position)
372 caretAtLineStart = true;
373 position --;
374 }
375
376 if (m_caretPosition != position)
377 {
378 bool extendSel = ExtendSelection(m_caretPosition, position, wxRICHTEXT_SHIFT_DOWN);
379
380 MoveCaret(position, caretAtLineStart);
381 SetDefaultStyleToCursorStyle();
382
383 if (extendSel)
384 Refresh(false);
385 }
386 }
387 }
388
389 /// Right-click
390 void wxRichTextCtrl::OnRightClick(wxMouseEvent& event)
391 {
392 SetFocus();
393 event.Skip();
394 }
395
396 /// Left-double-click
397 void wxRichTextCtrl::OnLeftDClick(wxMouseEvent& event)
398 {
399 SelectWord(GetCaretPosition()+1);
400 event.Skip();
401 }
402
403 /// Middle-click
404 void wxRichTextCtrl::OnMiddleClick(wxMouseEvent& event)
405 {
406 event.Skip();
407 }
408
409 /// Key press
410 void wxRichTextCtrl::OnChar(wxKeyEvent& event)
411 {
412 int flags = 0;
413 if (event.CmdDown())
414 flags |= wxRICHTEXT_CTRL_DOWN;
415 if (event.ShiftDown())
416 flags |= wxRICHTEXT_SHIFT_DOWN;
417 if (event.AltDown())
418 flags |= wxRICHTEXT_ALT_DOWN;
419
420 if (event.GetKeyCode() == WXK_LEFT ||
421 event.GetKeyCode() == WXK_RIGHT ||
422 event.GetKeyCode() == WXK_UP ||
423 event.GetKeyCode() == WXK_DOWN ||
424 event.GetKeyCode() == WXK_HOME ||
425 event.GetKeyCode() == WXK_PAGEUP ||
426 event.GetKeyCode() == WXK_PAGEDOWN ||
427 event.GetKeyCode() == WXK_END ||
428
429 event.GetKeyCode() == WXK_NUMPAD_LEFT ||
430 event.GetKeyCode() == WXK_NUMPAD_RIGHT ||
431 event.GetKeyCode() == WXK_NUMPAD_UP ||
432 event.GetKeyCode() == WXK_NUMPAD_DOWN ||
433 event.GetKeyCode() == WXK_NUMPAD_HOME ||
434 event.GetKeyCode() == WXK_NUMPAD_PAGEUP ||
435 event.GetKeyCode() == WXK_NUMPAD_PAGEDOWN ||
436 event.GetKeyCode() == WXK_NUMPAD_END)
437 {
438 KeyboardNavigate(event.GetKeyCode(), flags);
439 return;
440 }
441
442 // all the other keys modify the controls contents which shouldn't be
443 // possible if we're read-only
444 if ( !IsEditable() )
445 {
446 event.Skip();
447 return;
448 }
449
450 if (event.GetKeyCode() == WXK_RETURN)
451 {
452 BeginBatchUndo(_("Insert Text"));
453
454 long newPos = m_caretPosition;
455
456 DeleteSelectedContent(& newPos);
457
458 GetBuffer().InsertNewlineWithUndo(newPos+1, this, wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE);
459
460 wxRichTextEvent cmdEvent(
461 wxEVT_COMMAND_RICHTEXT_RETURN,
462 GetId());
463 cmdEvent.SetEventObject(this);
464 cmdEvent.SetFlags(flags);
465 GetEventHandler()->ProcessEvent(cmdEvent);
466
467 EndBatchUndo();
468 SetDefaultStyleToCursorStyle();
469
470 ScrollIntoView(m_caretPosition, WXK_RIGHT);
471 }
472 else if (event.GetKeyCode() == WXK_BACK)
473 {
474 BeginBatchUndo(_("Delete Text"));
475
476 // Submit range in character positions, which are greater than caret positions,
477 // so subtract 1 for deleted character and add 1 for conversion to character position.
478 if (m_caretPosition > -1 && !HasSelection())
479 {
480 GetBuffer().DeleteRangeWithUndo(wxRichTextRange(m_caretPosition, m_caretPosition),
481 m_caretPosition, // Current caret position
482 m_caretPosition-1, // New caret position
483 this);
484 }
485 else
486 DeleteSelectedContent();
487
488 EndBatchUndo();
489
490 // Shouldn't this be in Do()?
491 if (GetLastPosition() == -1)
492 {
493 GetBuffer().Reset();
494
495 m_caretPosition = -1;
496 PositionCaret();
497 SetDefaultStyleToCursorStyle();
498 }
499
500 ScrollIntoView(m_caretPosition, WXK_LEFT);
501 }
502 else if (event.GetKeyCode() == WXK_DELETE)
503 {
504 BeginBatchUndo(_("Delete Text"));
505
506 // Submit range in character positions, which are greater than caret positions,
507 if (m_caretPosition < GetBuffer().GetRange().GetEnd()+1 && !HasSelection())
508 {
509 GetBuffer().DeleteRangeWithUndo(wxRichTextRange(m_caretPosition+1, m_caretPosition+1),
510 m_caretPosition, // Current caret position
511 m_caretPosition+1, // New caret position
512 this);
513 }
514 else
515 DeleteSelectedContent();
516
517 EndBatchUndo();
518
519 // Shouldn't this be in Do()?
520 if (GetLastPosition() == -1)
521 {
522 GetBuffer().Reset();
523
524 m_caretPosition = -1;
525 PositionCaret();
526 SetDefaultStyleToCursorStyle();
527 }
528 }
529 else
530 {
531 long keycode = event.GetKeyCode();
532 switch ( keycode )
533 {
534 case WXK_ESCAPE:
535 // case WXK_SPACE:
536 case WXK_DELETE:
537 case WXK_START:
538 case WXK_LBUTTON:
539 case WXK_RBUTTON:
540 case WXK_CANCEL:
541 case WXK_MBUTTON:
542 case WXK_CLEAR:
543 case WXK_SHIFT:
544 case WXK_ALT:
545 case WXK_CONTROL:
546 case WXK_MENU:
547 case WXK_PAUSE:
548 case WXK_CAPITAL:
549 case WXK_END:
550 case WXK_HOME:
551 case WXK_LEFT:
552 case WXK_UP:
553 case WXK_RIGHT:
554 case WXK_DOWN:
555 case WXK_SELECT:
556 case WXK_PRINT:
557 case WXK_EXECUTE:
558 case WXK_SNAPSHOT:
559 case WXK_INSERT:
560 case WXK_HELP:
561 case WXK_NUMPAD0:
562 case WXK_NUMPAD1:
563 case WXK_NUMPAD2:
564 case WXK_NUMPAD3:
565 case WXK_NUMPAD4:
566 case WXK_NUMPAD5:
567 case WXK_NUMPAD6:
568 case WXK_NUMPAD7:
569 case WXK_NUMPAD8:
570 case WXK_NUMPAD9:
571 case WXK_MULTIPLY:
572 case WXK_ADD:
573 case WXK_SEPARATOR:
574 case WXK_SUBTRACT:
575 case WXK_DECIMAL:
576 case WXK_DIVIDE:
577 case WXK_F1:
578 case WXK_F2:
579 case WXK_F3:
580 case WXK_F4:
581 case WXK_F5:
582 case WXK_F6:
583 case WXK_F7:
584 case WXK_F8:
585 case WXK_F9:
586 case WXK_F10:
587 case WXK_F11:
588 case WXK_F12:
589 case WXK_F13:
590 case WXK_F14:
591 case WXK_F15:
592 case WXK_F16:
593 case WXK_F17:
594 case WXK_F18:
595 case WXK_F19:
596 case WXK_F20:
597 case WXK_F21:
598 case WXK_F22:
599 case WXK_F23:
600 case WXK_F24:
601 case WXK_NUMLOCK:
602 case WXK_SCROLL:
603 case WXK_PAGEUP:
604 case WXK_PAGEDOWN:
605 case WXK_NUMPAD_SPACE:
606 case WXK_NUMPAD_TAB:
607 case WXK_NUMPAD_ENTER:
608 case WXK_NUMPAD_F1:
609 case WXK_NUMPAD_F2:
610 case WXK_NUMPAD_F3:
611 case WXK_NUMPAD_F4:
612 case WXK_NUMPAD_HOME:
613 case WXK_NUMPAD_LEFT:
614 case WXK_NUMPAD_UP:
615 case WXK_NUMPAD_RIGHT:
616 case WXK_NUMPAD_DOWN:
617 case WXK_NUMPAD_PAGEUP:
618 case WXK_NUMPAD_PAGEDOWN:
619 case WXK_NUMPAD_END:
620 case WXK_NUMPAD_BEGIN:
621 case WXK_NUMPAD_INSERT:
622 case WXK_NUMPAD_DELETE:
623 case WXK_NUMPAD_EQUAL:
624 case WXK_NUMPAD_MULTIPLY:
625 case WXK_NUMPAD_ADD:
626 case WXK_NUMPAD_SEPARATOR:
627 case WXK_NUMPAD_SUBTRACT:
628 case WXK_NUMPAD_DECIMAL:
629 {
630 event.Skip();
631 return;
632 }
633
634 default:
635 {
636 if (event.CmdDown() || event.AltDown())
637 {
638 event.Skip();
639 return;
640 }
641
642 BeginBatchUndo(_("Insert Text"));
643
644 long newPos = m_caretPosition;
645 DeleteSelectedContent(& newPos);
646
647 wxString str = (wxChar) event.GetKeyCode();
648 GetBuffer().InsertTextWithUndo(newPos+1, str, this, wxRICHTEXT_INSERT_WITH_PREVIOUS_PARAGRAPH_STYLE);
649
650 EndBatchUndo();
651
652 SetDefaultStyleToCursorStyle();
653 ScrollIntoView(m_caretPosition, WXK_RIGHT);
654 }
655 }
656 }
657 }
658
659 /// Delete content if there is a selection, e.g. when pressing a key.
660 bool wxRichTextCtrl::DeleteSelectedContent(long* newPos)
661 {
662 if (HasSelection())
663 {
664 long pos = m_selectionRange.GetStart();
665 GetBuffer().DeleteRangeWithUndo(m_selectionRange,
666 m_caretPosition, // Current caret position
667 pos, // New caret position
668 this);
669 m_selectionRange.SetRange(-2, -2);
670
671 if (newPos)
672 *newPos = pos-1;
673 return true;
674 }
675 else
676 return false;
677 }
678
679 /// Keyboard navigation
680
681 /*
682
683 Left: left one character
684 Right: right one character
685 Up: up one line
686 Down: down one line
687 Ctrl-Left: left one word
688 Ctrl-Right: right one word
689 Ctrl-Up: previous paragraph start
690 Ctrl-Down: next start of paragraph
691 Home: start of line
692 End: end of line
693 Ctrl-Home: start of document
694 Ctrl-End: end of document
695 Page-Up: Up a screen
696 Page-Down: Down a screen
697
698 Maybe:
699
700 Ctrl-Alt-PgUp: Start of window
701 Ctrl-Alt-PgDn: End of window
702 F8: Start selection mode
703 Esc: End selection mode
704
705 Adding Shift does the above but starts/extends selection.
706
707
708 */
709
710 bool wxRichTextCtrl::KeyboardNavigate(int keyCode, int flags)
711 {
712 bool success = false;
713
714 if (keyCode == WXK_RIGHT || keyCode == WXK_NUMPAD_RIGHT)
715 {
716 if (flags & wxRICHTEXT_CTRL_DOWN)
717 success = WordRight(1, flags);
718 else
719 success = MoveRight(1, flags);
720 }
721 else if (keyCode == WXK_LEFT || keyCode == WXK_NUMPAD_LEFT)
722 {
723 if (flags & wxRICHTEXT_CTRL_DOWN)
724 success = WordLeft(1, flags);
725 else
726 success = MoveLeft(1, flags);
727 }
728 else if (keyCode == WXK_UP || keyCode == WXK_NUMPAD_UP)
729 {
730 if (flags & wxRICHTEXT_CTRL_DOWN)
731 success = MoveToParagraphStart(flags);
732 else
733 success = MoveUp(1, flags);
734 }
735 else if (keyCode == WXK_DOWN || keyCode == WXK_NUMPAD_DOWN)
736 {
737 if (flags & wxRICHTEXT_CTRL_DOWN)
738 success = MoveToParagraphEnd(flags);
739 else
740 success = MoveDown(1, flags);
741 }
742 else if (keyCode == WXK_PAGEUP || keyCode == WXK_NUMPAD_PAGEUP)
743 {
744 success = PageUp(1, flags);
745 }
746 else if (keyCode == WXK_PAGEDOWN || keyCode == WXK_NUMPAD_PAGEDOWN)
747 {
748 success = PageDown(1, flags);
749 }
750 else if (keyCode == WXK_HOME || keyCode == WXK_NUMPAD_HOME)
751 {
752 if (flags & wxRICHTEXT_CTRL_DOWN)
753 success = MoveHome(flags);
754 else
755 success = MoveToLineStart(flags);
756 }
757 else if (keyCode == WXK_END || keyCode == WXK_NUMPAD_END)
758 {
759 if (flags & wxRICHTEXT_CTRL_DOWN)
760 success = MoveEnd(flags);
761 else
762 success = MoveToLineEnd(flags);
763 }
764
765 if (success)
766 {
767 ScrollIntoView(m_caretPosition, keyCode);
768 SetDefaultStyleToCursorStyle();
769 }
770
771 return success;
772 }
773
774 /// Extend the selection. Selections are in caret positions.
775 bool wxRichTextCtrl::ExtendSelection(long oldPos, long newPos, int flags)
776 {
777 if (flags & wxRICHTEXT_SHIFT_DOWN)
778 {
779 // If not currently selecting, start selecting
780 if (m_selectionRange.GetStart() == -2)
781 {
782 m_selectionAnchor = oldPos;
783
784 if (oldPos > newPos)
785 m_selectionRange.SetRange(newPos+1, oldPos);
786 else
787 m_selectionRange.SetRange(oldPos+1, newPos);
788 }
789 else
790 {
791 // Always ensure that the selection range start is greater than
792 // the end.
793 if (newPos > m_selectionAnchor)
794 m_selectionRange.SetRange(m_selectionAnchor+1, newPos);
795 else
796 m_selectionRange.SetRange(newPos+1, m_selectionAnchor);
797 }
798
799 if (m_selectionRange.GetStart() > m_selectionRange.GetEnd())
800 {
801 wxLogDebug(wxT("Strange selection range"));
802 }
803
804 return true;
805 }
806 else
807 return false;
808 }
809
810 /// Scroll into view, returning true if we scrolled.
811 /// This takes a _caret_ position.
812 bool wxRichTextCtrl::ScrollIntoView(long position, int keyCode)
813 {
814 wxRichTextLine* line = GetVisibleLineForCaretPosition(position);
815
816 if (!line)
817 return false;
818
819 int ppuX, ppuY;
820 GetScrollPixelsPerUnit(& ppuX, & ppuY);
821
822 int startXUnits, startYUnits;
823 GetViewStart(& startXUnits, & startYUnits);
824 int startY = startYUnits * ppuY;
825
826 int sx = 0, sy = 0;
827 GetVirtualSize(& sx, & sy);
828 int sxUnits = 0;
829 int syUnits = 0;
830 if (ppuY != 0)
831 syUnits = sy/ppuY;
832
833 wxRect rect = line->GetRect();
834
835 bool scrolled = false;
836
837 wxSize clientSize = GetClientSize();
838
839 // Going down
840 if (keyCode == WXK_DOWN || keyCode == WXK_NUMPAD_DOWN ||
841 keyCode == WXK_RIGHT || keyCode == WXK_NUMPAD_DOWN ||
842 keyCode == WXK_END || keyCode == WXK_NUMPAD_END ||
843 keyCode == WXK_PAGEDOWN || keyCode == WXK_NUMPAD_PAGEDOWN)
844 {
845 if ((rect.y + rect.height) > (clientSize.y + startY))
846 {
847 // Make it scroll so this item is at the bottom
848 // of the window
849 int y = rect.y - (clientSize.y - rect.height);
850 int yUnits = (int) (0.5 + ((float) y)/(float) ppuY);
851
852 // If we're still off the screen, scroll another line down
853 if ((rect.y + rect.height) > (clientSize.y + (yUnits*ppuY)))
854 yUnits ++;
855
856 if (startYUnits != yUnits)
857 {
858 SetScrollbars(ppuX, ppuY, sxUnits, syUnits, 0, yUnits);
859 scrolled = true;
860 }
861 }
862 else if (rect.y < startY)
863 {
864 // Make it scroll so this item is at the top
865 // of the window
866 int y = rect.y ;
867 int yUnits = (int) (0.5 + ((float) y)/(float) ppuY);
868
869 if (startYUnits != yUnits)
870 {
871 SetScrollbars(ppuX, ppuY, sxUnits, syUnits, 0, yUnits);
872 scrolled = true;
873 }
874 }
875 }
876 // Going up
877 else if (keyCode == WXK_UP || keyCode == WXK_NUMPAD_UP ||
878 keyCode == WXK_LEFT || keyCode == WXK_NUMPAD_LEFT ||
879 keyCode == WXK_HOME || keyCode == WXK_NUMPAD_HOME ||
880 keyCode == WXK_PAGEUP || keyCode == WXK_NUMPAD_PAGEUP )
881 {
882 if (rect.y < startY)
883 {
884 // Make it scroll so this item is at the top
885 // of the window
886 int y = rect.y ;
887 int yUnits = (int) (0.5 + ((float) y)/(float) ppuY);
888
889 if (startYUnits != yUnits)
890 {
891 SetScrollbars(ppuX, ppuY, sxUnits, syUnits, 0, yUnits);
892 scrolled = true;
893 }
894 }
895 else if ((rect.y + rect.height) > (clientSize.y + startY))
896 {
897 // Make it scroll so this item is at the bottom
898 // of the window
899 int y = rect.y - (clientSize.y - rect.height);
900 int yUnits = (int) (0.5 + ((float) y)/(float) ppuY);
901
902 // If we're still off the screen, scroll another line down
903 if ((rect.y + rect.height) > (clientSize.y + (yUnits*ppuY)))
904 yUnits ++;
905
906 if (startYUnits != yUnits)
907 {
908 SetScrollbars(ppuX, ppuY, sxUnits, syUnits, 0, yUnits);
909 scrolled = true;
910 }
911 }
912 }
913 PositionCaret();
914
915 return scrolled;
916 }
917
918 /// Is the given position visible on the screen?
919 bool wxRichTextCtrl::IsPositionVisible(long pos) const
920 {
921 wxRichTextLine* line = GetVisibleLineForCaretPosition(pos-1);
922
923 if (!line)
924 return false;
925
926 int ppuX, ppuY;
927 GetScrollPixelsPerUnit(& ppuX, & ppuY);
928
929 int startX, startY;
930 GetViewStart(& startX, & startY);
931 startX = 0;
932 startY = startY * ppuY;
933
934 int sx = 0, sy = 0;
935 GetVirtualSize(& sx, & sy);
936 sx = 0;
937 if (ppuY != 0)
938 sy = sy/ppuY;
939
940 wxRect rect = line->GetRect();
941
942 wxSize clientSize = GetClientSize();
943
944 return !(((rect.y + rect.height) > (clientSize.y + startY)) || rect.y < startY);
945 }
946
947 void wxRichTextCtrl::SetCaretPosition(long position, bool showAtLineStart)
948 {
949 m_caretPosition = position;
950 m_caretAtLineStart = showAtLineStart;
951 }
952
953 /// Move caret one visual step forward: this may mean setting a flag
954 /// and keeping the same position if we're going from the end of one line
955 /// to the start of the next, which may be the exact same caret position.
956 void wxRichTextCtrl::MoveCaretForward(long oldPosition)
957 {
958 wxRichTextParagraph* para = GetBuffer().GetParagraphAtPosition(oldPosition);
959
960 // Only do the check if we're not at the end of the paragraph (where things work OK
961 // anyway)
962 if (para && (oldPosition != para->GetRange().GetEnd() - 1))
963 {
964 wxRichTextLine* line = GetBuffer().GetLineAtPosition(oldPosition);
965
966 if (line)
967 {
968 wxRichTextRange lineRange = line->GetAbsoluteRange();
969
970 // We're at the end of a line. See whether we need to
971 // stay at the same actual caret position but change visual
972 // position, or not.
973 if (oldPosition == lineRange.GetEnd())
974 {
975 if (m_caretAtLineStart)
976 {
977 // We're already at the start of the line, so actually move on now.
978 m_caretPosition = oldPosition + 1;
979 m_caretAtLineStart = false;
980 }
981 else
982 {
983 // We're showing at the end of the line, so keep to
984 // the same position but indicate that we're to show
985 // at the start of the next line.
986 m_caretPosition = oldPosition;
987 m_caretAtLineStart = true;
988 }
989 SetDefaultStyleToCursorStyle();
990 return;
991 }
992 }
993 }
994 m_caretPosition ++;
995 SetDefaultStyleToCursorStyle();
996 }
997
998 /// Move caret one visual step backward: this may mean setting a flag
999 /// and keeping the same position if we're going from the end of one line
1000 /// to the start of the next, which may be the exact same caret position.
1001 void wxRichTextCtrl::MoveCaretBack(long oldPosition)
1002 {
1003 wxRichTextParagraph* para = GetBuffer().GetParagraphAtPosition(oldPosition);
1004
1005 // Only do the check if we're not at the start of the paragraph (where things work OK
1006 // anyway)
1007 if (para && (oldPosition != para->GetRange().GetStart()))
1008 {
1009 wxRichTextLine* line = GetBuffer().GetLineAtPosition(oldPosition);
1010
1011 if (line)
1012 {
1013 wxRichTextRange lineRange = line->GetAbsoluteRange();
1014
1015 // We're at the start of a line. See whether we need to
1016 // stay at the same actual caret position but change visual
1017 // position, or not.
1018 if (oldPosition == lineRange.GetStart())
1019 {
1020 m_caretPosition = oldPosition-1;
1021 m_caretAtLineStart = true;
1022 return;
1023 }
1024 else if (oldPosition == lineRange.GetEnd())
1025 {
1026 if (m_caretAtLineStart)
1027 {
1028 // We're at the start of the line, so keep the same caret position
1029 // but clear the start-of-line flag.
1030 m_caretPosition = oldPosition;
1031 m_caretAtLineStart = false;
1032 }
1033 else
1034 {
1035 // We're showing at the end of the line, so go back
1036 // to the previous character position.
1037 m_caretPosition = oldPosition - 1;
1038 }
1039 SetDefaultStyleToCursorStyle();
1040 return;
1041 }
1042 }
1043 }
1044 m_caretPosition --;
1045 SetDefaultStyleToCursorStyle();
1046 }
1047
1048 /// Move right
1049 bool wxRichTextCtrl::MoveRight(int noPositions, int flags)
1050 {
1051 long endPos = GetBuffer().GetRange().GetEnd();
1052
1053 if (m_caretPosition + noPositions < endPos)
1054 {
1055 long oldPos = m_caretPosition;
1056 long newPos = m_caretPosition + noPositions;
1057
1058 bool extendSel = ExtendSelection(m_caretPosition, newPos, flags);
1059 if (!extendSel)
1060 SelectNone();
1061
1062 // Determine by looking at oldPos and m_caretPosition whether
1063 // we moved from the end of a line to the start of the next line, in which case
1064 // we want to adjust the caret position such that it is positioned at the
1065 // start of the next line, rather than jumping past the first character of the
1066 // line.
1067 if (noPositions == 1 && !extendSel)
1068 MoveCaretForward(oldPos);
1069 else
1070 SetCaretPosition(newPos);
1071
1072 PositionCaret();
1073 SetDefaultStyleToCursorStyle();
1074
1075 if (extendSel)
1076 Refresh(false);
1077 return true;
1078 }
1079 else
1080 return false;
1081 }
1082
1083 /// Move left
1084 bool wxRichTextCtrl::MoveLeft(int noPositions, int flags)
1085 {
1086 long startPos = -1;
1087
1088 if (m_caretPosition > startPos - noPositions + 1)
1089 {
1090 long oldPos = m_caretPosition;
1091 long newPos = m_caretPosition - noPositions;
1092 bool extendSel = ExtendSelection(m_caretPosition, newPos, flags);
1093 if (!extendSel)
1094 SelectNone();
1095
1096 if (noPositions == 1 && !extendSel)
1097 MoveCaretBack(oldPos);
1098 else
1099 SetCaretPosition(newPos);
1100
1101 PositionCaret();
1102 SetDefaultStyleToCursorStyle();
1103
1104 if (extendSel)
1105 Refresh(false);
1106 return true;
1107 }
1108 else
1109 return false;
1110 }
1111
1112 /// Move up
1113 bool wxRichTextCtrl::MoveUp(int noLines, int flags)
1114 {
1115 return MoveDown(- noLines, flags);
1116 }
1117
1118 /// Move up
1119 bool wxRichTextCtrl::MoveDown(int noLines, int flags)
1120 {
1121 if (!GetCaret())
1122 return false;
1123
1124 long lineNumber = GetBuffer().GetVisibleLineNumber(m_caretPosition, true, m_caretAtLineStart);
1125 wxPoint pt = GetCaret()->GetPosition();
1126 long newLine = lineNumber + noLines;
1127
1128 if (lineNumber != -1)
1129 {
1130 if (noLines > 0)
1131 {
1132 long lastLine = GetBuffer().GetVisibleLineNumber(GetBuffer().GetRange().GetEnd());
1133
1134 if (newLine > lastLine)
1135 return false;
1136 }
1137 else
1138 {
1139 if (newLine < 0)
1140 return false;
1141 }
1142 }
1143
1144 wxRichTextLine* lineObj = GetBuffer().GetLineForVisibleLineNumber(newLine);
1145 if (lineObj)
1146 {
1147 pt.y = lineObj->GetAbsolutePosition().y + 2;
1148 }
1149 else
1150 return false;
1151
1152 long newPos = 0;
1153 wxClientDC dc(this);
1154 PrepareDC(dc);
1155 dc.SetFont(GetFont());
1156
1157 int hitTest = GetBuffer().HitTest(dc, pt, newPos);
1158
1159 if (hitTest != wxRICHTEXT_HITTEST_NONE)
1160 {
1161 // If end of previous line, and hitTest is wxRICHTEXT_HITTEST_BEFORE,
1162 // we want to be at the end of the last line but with m_caretAtLineStart set to true,
1163 // so we view the caret at the start of the line.
1164 bool caretLineStart = false;
1165 if (hitTest == wxRICHTEXT_HITTEST_BEFORE)
1166 {
1167 wxRichTextLine* thisLine = GetBuffer().GetLineAtPosition(newPos-1);
1168 wxRichTextRange lineRange;
1169 if (thisLine)
1170 lineRange = thisLine->GetAbsoluteRange();
1171
1172 if (thisLine && (newPos-1) == lineRange.GetEnd())
1173 {
1174 newPos --;
1175 caretLineStart = true;
1176 }
1177 else
1178 {
1179 wxRichTextParagraph* para = GetBuffer().GetParagraphAtPosition(newPos);
1180 if (para && para->GetRange().GetStart() == newPos)
1181 newPos --;
1182 }
1183 }
1184
1185 long newSelEnd = newPos;
1186
1187 bool extendSel = ExtendSelection(m_caretPosition, newSelEnd, flags);
1188 if (!extendSel)
1189 SelectNone();
1190
1191 SetCaretPosition(newPos, caretLineStart);
1192 PositionCaret();
1193 SetDefaultStyleToCursorStyle();
1194
1195 if (extendSel)
1196 Refresh(false);
1197 return true;
1198 }
1199
1200 return false;
1201 }
1202
1203 /// Move to the end of the paragraph
1204 bool wxRichTextCtrl::MoveToParagraphEnd(int flags)
1205 {
1206 wxRichTextParagraph* para = GetBuffer().GetParagraphAtPosition(m_caretPosition, true);
1207 if (para)
1208 {
1209 long newPos = para->GetRange().GetEnd() - 1;
1210 bool extendSel = ExtendSelection(m_caretPosition, newPos, flags);
1211 if (!extendSel)
1212 SelectNone();
1213
1214 SetCaretPosition(newPos);
1215 PositionCaret();
1216 SetDefaultStyleToCursorStyle();
1217
1218 if (extendSel)
1219 Refresh(false);
1220 return true;
1221 }
1222
1223 return false;
1224 }
1225
1226 /// Move to the start of the paragraph
1227 bool wxRichTextCtrl::MoveToParagraphStart(int flags)
1228 {
1229 wxRichTextParagraph* para = GetBuffer().GetParagraphAtPosition(m_caretPosition, true);
1230 if (para)
1231 {
1232 long newPos = para->GetRange().GetStart() - 1;
1233 bool extendSel = ExtendSelection(m_caretPosition, newPos, flags);
1234 if (!extendSel)
1235 SelectNone();
1236
1237 SetCaretPosition(newPos);
1238 PositionCaret();
1239 SetDefaultStyleToCursorStyle();
1240
1241 if (extendSel)
1242 Refresh(false);
1243 return true;
1244 }
1245
1246 return false;
1247 }
1248
1249 /// Move to the end of the line
1250 bool wxRichTextCtrl::MoveToLineEnd(int flags)
1251 {
1252 wxRichTextLine* line = GetVisibleLineForCaretPosition(m_caretPosition);
1253
1254 if (line)
1255 {
1256 wxRichTextRange lineRange = line->GetAbsoluteRange();
1257 long newPos = lineRange.GetEnd();
1258 bool extendSel = ExtendSelection(m_caretPosition, newPos, flags);
1259 if (!extendSel)
1260 SelectNone();
1261
1262 SetCaretPosition(newPos);
1263 PositionCaret();
1264 SetDefaultStyleToCursorStyle();
1265
1266 if (extendSel)
1267 Refresh(false);
1268 return true;
1269 }
1270
1271 return false;
1272 }
1273
1274 /// Move to the start of the line
1275 bool wxRichTextCtrl::MoveToLineStart(int flags)
1276 {
1277 wxRichTextLine* line = GetVisibleLineForCaretPosition(m_caretPosition);
1278 if (line)
1279 {
1280 wxRichTextRange lineRange = line->GetAbsoluteRange();
1281 long newPos = lineRange.GetStart()-1;
1282
1283 bool extendSel = ExtendSelection(m_caretPosition, newPos, flags);
1284 if (!extendSel)
1285 SelectNone();
1286
1287 wxRichTextParagraph* para = GetBuffer().GetParagraphForLine(line);
1288
1289 SetCaretPosition(newPos, para->GetRange().GetStart() != lineRange.GetStart());
1290 PositionCaret();
1291 SetDefaultStyleToCursorStyle();
1292
1293 if (extendSel)
1294 Refresh(false);
1295 return true;
1296 }
1297
1298 return false;
1299 }
1300
1301 /// Move to the start of the buffer
1302 bool wxRichTextCtrl::MoveHome(int flags)
1303 {
1304 if (m_caretPosition != -1)
1305 {
1306 bool extendSel = ExtendSelection(m_caretPosition, -1, flags);
1307 if (!extendSel)
1308 SelectNone();
1309
1310 SetCaretPosition(-1);
1311 PositionCaret();
1312 SetDefaultStyleToCursorStyle();
1313
1314 if (extendSel)
1315 Refresh(false);
1316 return true;
1317 }
1318 else
1319 return false;
1320 }
1321
1322 /// Move to the end of the buffer
1323 bool wxRichTextCtrl::MoveEnd(int flags)
1324 {
1325 long endPos = GetBuffer().GetRange().GetEnd()-1;
1326
1327 if (m_caretPosition != endPos)
1328 {
1329 bool extendSel = ExtendSelection(m_caretPosition, endPos, flags);
1330 if (!extendSel)
1331 SelectNone();
1332
1333 SetCaretPosition(endPos);
1334 PositionCaret();
1335 SetDefaultStyleToCursorStyle();
1336
1337 if (extendSel)
1338 Refresh(false);
1339 return true;
1340 }
1341 else
1342 return false;
1343 }
1344
1345 /// Move noPages pages up
1346 bool wxRichTextCtrl::PageUp(int noPages, int flags)
1347 {
1348 return PageDown(- noPages, flags);
1349 }
1350
1351 /// Move noPages pages down
1352 bool wxRichTextCtrl::PageDown(int noPages, int flags)
1353 {
1354 // Calculate which line occurs noPages * screen height further down.
1355 wxRichTextLine* line = GetVisibleLineForCaretPosition(m_caretPosition);
1356 if (line)
1357 {
1358 wxSize clientSize = GetClientSize();
1359 int newY = line->GetAbsolutePosition().y + noPages*clientSize.y;
1360
1361 wxRichTextLine* newLine = GetBuffer().GetLineAtYPosition(newY);
1362 if (newLine)
1363 {
1364 wxRichTextRange lineRange = newLine->GetAbsoluteRange();
1365 long pos = lineRange.GetStart()-1;
1366 if (pos != m_caretPosition)
1367 {
1368 wxRichTextParagraph* para = GetBuffer().GetParagraphForLine(newLine);
1369
1370 bool extendSel = ExtendSelection(m_caretPosition, pos, flags);
1371 if (!extendSel)
1372 SelectNone();
1373
1374 SetCaretPosition(pos, para->GetRange().GetStart() != lineRange.GetStart());
1375 PositionCaret();
1376 SetDefaultStyleToCursorStyle();
1377
1378 if (extendSel)
1379 Refresh(false);
1380 return true;
1381 }
1382 }
1383 }
1384
1385 return false;
1386 }
1387
1388 // Finds the caret position for the next word
1389 long wxRichTextCtrl::FindNextWordPosition(int direction) const
1390 {
1391 long endPos = GetBuffer().GetRange().GetEnd();
1392
1393 if (direction > 0)
1394 {
1395 long i = m_caretPosition+1+direction; // +1 for conversion to character pos
1396
1397 // First skip current text to space
1398 while (i < endPos && i > -1)
1399 {
1400 // i is in character, not caret positions
1401 wxString text = GetBuffer().GetTextForRange(wxRichTextRange(i, i));
1402 if (text != wxT(" ") && !text.empty())
1403 i += direction;
1404 else
1405 {
1406 break;
1407 }
1408 }
1409 while (i < endPos && i > -1)
1410 {
1411 // i is in character, not caret positions
1412 wxString text = GetBuffer().GetTextForRange(wxRichTextRange(i, i));
1413 if (text.empty()) // End of paragraph, or maybe an image
1414 return wxMax(-1, i - 1);
1415 else if (text == wxT(" ") || text.empty())
1416 i += direction;
1417 else
1418 {
1419 // Convert to caret position
1420 return wxMax(-1, i - 1);
1421 }
1422 }
1423 if (i >= endPos)
1424 return endPos-1;
1425 return i-1;
1426 }
1427 else
1428 {
1429 long i = m_caretPosition;
1430
1431 // First skip white space
1432 while (i < endPos && i > -1)
1433 {
1434 // i is in character, not caret positions
1435 wxString text = GetBuffer().GetTextForRange(wxRichTextRange(i, i));
1436 if (text.empty()) // End of paragraph, or maybe an image
1437 break;
1438 else if (text == wxT(" ") || text.empty())
1439 i += direction;
1440 else
1441 break;
1442 }
1443 // Next skip current text to space
1444 while (i < endPos && i > -1)
1445 {
1446 // i is in character, not caret positions
1447 wxString text = GetBuffer().GetTextForRange(wxRichTextRange(i, i));
1448 if (text != wxT(" ") /* && !text.empty() */)
1449 i += direction;
1450 else
1451 {
1452 return i;
1453 }
1454 }
1455 if (i < -1)
1456 return -1;
1457 return i;
1458 }
1459 }
1460
1461 /// Move n words left
1462 bool wxRichTextCtrl::WordLeft(int WXUNUSED(n), int flags)
1463 {
1464 long pos = FindNextWordPosition(-1);
1465 if (pos != m_caretPosition)
1466 {
1467 wxRichTextParagraph* para = GetBuffer().GetParagraphAtPosition(pos, true);
1468
1469 bool extendSel = ExtendSelection(m_caretPosition, pos, flags);
1470 if (!extendSel)
1471 SelectNone();
1472
1473 SetCaretPosition(pos, para->GetRange().GetStart() != pos);
1474 PositionCaret();
1475 SetDefaultStyleToCursorStyle();
1476
1477 if (extendSel)
1478 Refresh(false);
1479 return true;
1480 }
1481
1482 return false;
1483 }
1484
1485 /// Move n words right
1486 bool wxRichTextCtrl::WordRight(int WXUNUSED(n), int flags)
1487 {
1488 long pos = FindNextWordPosition(1);
1489 if (pos != m_caretPosition)
1490 {
1491 wxRichTextParagraph* para = GetBuffer().GetParagraphAtPosition(pos, true);
1492
1493 bool extendSel = ExtendSelection(m_caretPosition, pos, flags);
1494 if (!extendSel)
1495 SelectNone();
1496
1497 SetCaretPosition(pos, para->GetRange().GetStart() != pos);
1498 PositionCaret();
1499 SetDefaultStyleToCursorStyle();
1500
1501 if (extendSel)
1502 Refresh(false);
1503 return true;
1504 }
1505
1506 return false;
1507 }
1508
1509 /// Sizing
1510 void wxRichTextCtrl::OnSize(wxSizeEvent& event)
1511 {
1512 // Only do sizing optimization for large buffers
1513 if (GetBuffer().GetRange().GetEnd() > m_delayedLayoutThreshold)
1514 {
1515 m_fullLayoutRequired = true;
1516 m_fullLayoutTime = wxGetLocalTimeMillis();
1517 m_fullLayoutSavedPosition = GetFirstVisiblePosition();
1518 LayoutContent(true /* onlyVisibleRect */);
1519 }
1520 else
1521 GetBuffer().Invalidate(wxRICHTEXT_ALL);
1522
1523 #if wxRICHTEXT_BUFFERED_PAINTING
1524 RecreateBuffer();
1525 #endif
1526
1527 event.Skip();
1528 }
1529
1530
1531 /// Idle-time processing
1532 void wxRichTextCtrl::OnIdle(wxIdleEvent& event)
1533 {
1534 const int layoutInterval = wxRICHTEXT_DEFAULT_LAYOUT_INTERVAL;
1535
1536 if (m_fullLayoutRequired && (wxGetLocalTimeMillis() > (m_fullLayoutTime + layoutInterval)))
1537 {
1538 m_fullLayoutRequired = false;
1539 m_fullLayoutTime = 0;
1540 GetBuffer().Invalidate(wxRICHTEXT_ALL);
1541 ShowPosition(m_fullLayoutSavedPosition);
1542 Refresh(false);
1543 }
1544
1545 if (m_caretPositionForDefaultStyle != -2)
1546 {
1547 // If the caret position has changed, no longer reflect the default style
1548 // in the UI.
1549 if (GetCaretPosition() != m_caretPositionForDefaultStyle)
1550 m_caretPositionForDefaultStyle = -2;
1551 }
1552
1553 event.Skip();
1554 }
1555
1556 /// Scrolling
1557 void wxRichTextCtrl::OnScroll(wxScrollWinEvent& event)
1558 {
1559 // Not used
1560 event.Skip();
1561 }
1562
1563 /// Set up scrollbars, e.g. after a resize
1564 void wxRichTextCtrl::SetupScrollbars(bool atTop)
1565 {
1566 if (m_freezeCount)
1567 return;
1568
1569 if (GetBuffer().IsEmpty())
1570 {
1571 SetScrollbars(0, 0, 0, 0, 0, 0);
1572 return;
1573 }
1574
1575 // TODO: reimplement scrolling so we scroll by line, not by fixed number
1576 // of pixels. See e.g. wxVScrolledWindow for ideas.
1577 int pixelsPerUnit = 5;
1578 wxSize clientSize = GetClientSize();
1579
1580 int maxHeight = GetBuffer().GetCachedSize().y;
1581
1582 // Round up so we have at least maxHeight pixels
1583 int unitsY = (int) (((float)maxHeight/(float)pixelsPerUnit) + 0.5);
1584
1585 int startX = 0, startY = 0;
1586 if (!atTop)
1587 GetViewStart(& startX, & startY);
1588
1589 int maxPositionX = 0; // wxMax(sz.x - clientSize.x, 0);
1590 int maxPositionY = (int) ((((float)(wxMax((unitsY*pixelsPerUnit) - clientSize.y, 0)))/((float)pixelsPerUnit)) + 0.5);
1591
1592 // Move to previous scroll position if
1593 // possible
1594 SetScrollbars(0, pixelsPerUnit,
1595 0, unitsY,
1596 wxMin(maxPositionX, startX), wxMin(maxPositionY, startY));
1597 }
1598
1599 /// Paint the background
1600 void wxRichTextCtrl::PaintBackground(wxDC& dc)
1601 {
1602 wxColour backgroundColour = GetBackgroundColour();
1603 if (!backgroundColour.Ok())
1604 backgroundColour = wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE);
1605
1606 // Clear the background
1607 dc.SetBrush(wxBrush(backgroundColour));
1608 dc.SetPen(*wxTRANSPARENT_PEN);
1609 wxRect windowRect(GetClientSize());
1610 windowRect.x -= 2; windowRect.y -= 2;
1611 windowRect.width += 4; windowRect.height += 4;
1612
1613 // We need to shift the rectangle to take into account
1614 // scrolling. Converting device to logical coordinates.
1615 CalcUnscrolledPosition(windowRect.x, windowRect.y, & windowRect.x, & windowRect.y);
1616 dc.DrawRectangle(windowRect);
1617 }
1618
1619 #if wxRICHTEXT_BUFFERED_PAINTING
1620 /// Recreate buffer bitmap if necessary
1621 bool wxRichTextCtrl::RecreateBuffer(const wxSize& size)
1622 {
1623 wxSize sz = size;
1624 if (sz == wxDefaultSize)
1625 sz = GetClientSize();
1626
1627 if (sz.x < 1 || sz.y < 1)
1628 return false;
1629
1630 if (!m_bufferBitmap.Ok() || m_bufferBitmap.GetWidth() < sz.x || m_bufferBitmap.GetHeight() < sz.y)
1631 m_bufferBitmap = wxBitmap(sz.x, sz.y);
1632 return m_bufferBitmap.Ok();
1633 }
1634 #endif
1635
1636 // ----------------------------------------------------------------------------
1637 // file IO functions
1638 // ----------------------------------------------------------------------------
1639
1640 bool wxRichTextCtrl::DoLoadFile(const wxString& filename, int fileType)
1641 {
1642 bool success = GetBuffer().LoadFile(filename, fileType);
1643 if (success)
1644 m_filename = filename;
1645
1646 DiscardEdits();
1647 SetInsertionPoint(0);
1648 LayoutContent();
1649 PositionCaret();
1650 SetupScrollbars(true);
1651 Refresh(false);
1652 SendTextUpdatedEvent();
1653
1654 if (success)
1655 return true;
1656 else
1657 {
1658 wxLogError(_("File couldn't be loaded."));
1659
1660 return false;
1661 }
1662 }
1663
1664 bool wxRichTextCtrl::DoSaveFile(const wxString& filename, int fileType)
1665 {
1666 if (GetBuffer().SaveFile(filename, fileType))
1667 {
1668 m_filename = filename;
1669
1670 DiscardEdits();
1671
1672 return true;
1673 }
1674
1675 wxLogError(_("The text couldn't be saved."));
1676
1677 return false;
1678 }
1679
1680 // ----------------------------------------------------------------------------
1681 // wxRichTextCtrl specific functionality
1682 // ----------------------------------------------------------------------------
1683
1684 /// Add a new paragraph of text to the end of the buffer
1685 wxRichTextRange wxRichTextCtrl::AddParagraph(const wxString& text)
1686 {
1687 return GetBuffer().AddParagraph(text);
1688 }
1689
1690 /// Add an image
1691 wxRichTextRange wxRichTextCtrl::AddImage(const wxImage& image)
1692 {
1693 return GetBuffer().AddImage(image);
1694 }
1695
1696 // ----------------------------------------------------------------------------
1697 // selection and ranges
1698 // ----------------------------------------------------------------------------
1699
1700 void wxRichTextCtrl::SelectAll()
1701 {
1702 SetSelection(0, GetLastPosition()+1);
1703 m_selectionAnchor = -1;
1704 }
1705
1706 /// Select none
1707 void wxRichTextCtrl::SelectNone()
1708 {
1709 if (!(GetSelectionRange() == wxRichTextRange(-2, -2)))
1710 SetSelection(-2, -2);
1711 m_selectionAnchor = -2;
1712 }
1713
1714 static bool wxIsWordDelimiter(const wxString& text)
1715 {
1716 return !text.IsEmpty() && !wxIsalnum(text[0]);
1717 }
1718
1719 /// Select the word at the given character position
1720 bool wxRichTextCtrl::SelectWord(long position)
1721 {
1722 if (position < 0 || position > GetBuffer().GetRange().GetEnd())
1723 return false;
1724
1725 wxRichTextParagraph* para = GetBuffer().GetParagraphAtPosition(position);
1726 if (!para)
1727 return false;
1728
1729 long positionStart = position;
1730 long positionEnd = position;
1731
1732 for (positionStart = position; positionStart >= para->GetRange().GetStart(); positionStart --)
1733 {
1734 wxString text = GetBuffer().GetTextForRange(wxRichTextRange(positionStart, positionStart));
1735 if (wxIsWordDelimiter(text))
1736 {
1737 positionStart ++;
1738 break;
1739 }
1740 }
1741 if (positionStart < para->GetRange().GetStart())
1742 positionStart = para->GetRange().GetStart();
1743
1744 for (positionEnd = position; positionEnd < para->GetRange().GetEnd(); positionEnd ++)
1745 {
1746 wxString text = GetBuffer().GetTextForRange(wxRichTextRange(positionEnd, positionEnd));
1747 if (wxIsWordDelimiter(text))
1748 {
1749 positionEnd --;
1750 break;
1751 }
1752 }
1753 if (positionEnd >= para->GetRange().GetEnd())
1754 positionEnd = para->GetRange().GetEnd();
1755
1756 SetSelection(positionStart, positionEnd+1);
1757
1758 if (positionStart >= 0)
1759 {
1760 MoveCaret(positionStart-1, true);
1761 SetDefaultStyleToCursorStyle();
1762 }
1763
1764 return true;
1765 }
1766
1767 wxString wxRichTextCtrl::GetStringSelection() const
1768 {
1769 long from, to;
1770 GetSelection(&from, &to);
1771
1772 return GetRange(from, to);
1773 }
1774
1775 // ----------------------------------------------------------------------------
1776 // hit testing
1777 // ----------------------------------------------------------------------------
1778
1779 wxTextCtrlHitTestResult
1780 wxRichTextCtrl::HitTest(const wxPoint& pt, wxTextCoord *x, wxTextCoord *y) const
1781 {
1782 // implement in terms of the other overload as the native ports typically
1783 // can get the position and not (x, y) pair directly (although wxUniv
1784 // directly gets x and y -- and so overrides this method as well)
1785 long pos;
1786 wxTextCtrlHitTestResult rc = HitTest(pt, &pos);
1787
1788 if ( rc != wxTE_HT_UNKNOWN )
1789 {
1790 PositionToXY(pos, x, y);
1791 }
1792
1793 return rc;
1794 }
1795
1796 wxTextCtrlHitTestResult
1797 wxRichTextCtrl::HitTest(const wxPoint& pt,
1798 long * pos) const
1799 {
1800 wxClientDC dc((wxRichTextCtrl*) this);
1801 ((wxRichTextCtrl*)this)->PrepareDC(dc);
1802
1803 // Buffer uses logical position (relative to start of buffer)
1804 // so convert
1805 wxPoint pt2 = GetLogicalPoint(pt);
1806
1807 int hit = ((wxRichTextCtrl*)this)->GetBuffer().HitTest(dc, pt2, *pos);
1808
1809 switch ( hit )
1810 {
1811 case wxRICHTEXT_HITTEST_BEFORE:
1812 return wxTE_HT_BEFORE;
1813
1814 case wxRICHTEXT_HITTEST_AFTER:
1815 return wxTE_HT_BEYOND;
1816
1817 case wxRICHTEXT_HITTEST_ON:
1818 return wxTE_HT_ON_TEXT;
1819 }
1820
1821 return wxTE_HT_UNKNOWN;
1822 }
1823
1824 // ----------------------------------------------------------------------------
1825 // set/get the controls text
1826 // ----------------------------------------------------------------------------
1827
1828 wxString wxRichTextCtrl::GetValue() const
1829 {
1830 return GetBuffer().GetText();
1831 }
1832
1833 wxString wxRichTextCtrl::GetRange(long from, long to) const
1834 {
1835 // Public API for range is different from internals
1836 return GetBuffer().GetTextForRange(wxRichTextRange(from, to-1));
1837 }
1838
1839 void wxRichTextCtrl::DoSetValue(const wxString& value, int flags)
1840 {
1841 Clear();
1842
1843 // if the text is long enough, it's faster to just set it instead of first
1844 // comparing it with the old one (chances are that it will be different
1845 // anyhow, this comparison is there to avoid flicker for small single-line
1846 // edit controls mostly)
1847 if ( (value.length() > 0x400) || (value != GetValue()) )
1848 {
1849 DoWriteText(value);
1850
1851 // for compatibility, don't move the cursor when doing SetValue()
1852 SetInsertionPoint(0);
1853 }
1854 else // same text
1855 {
1856 if ( flags & SetValue_SendEvent )
1857 {
1858 // still send an event for consistency
1859 SendTextUpdatedEvent();
1860 }
1861 }
1862
1863 // we should reset the modified flag even if the value didn't really change
1864
1865 // mark the control as being not dirty - we changed its text, not the
1866 // user
1867 DiscardEdits();
1868 }
1869
1870 void wxRichTextCtrl::WriteText(const wxString& value)
1871 {
1872 DoWriteText(value);
1873 }
1874
1875 void wxRichTextCtrl::DoWriteText(const wxString& value, int flags)
1876 {
1877 wxString valueUnix = wxTextFile::Translate(value, wxTextFileType_Unix);
1878
1879 GetBuffer().InsertTextWithUndo(m_caretPosition+1, valueUnix, this);
1880
1881 if ( flags & SetValue_SendEvent )
1882 SendTextUpdatedEvent();
1883 }
1884
1885 void wxRichTextCtrl::AppendText(const wxString& text)
1886 {
1887 SetInsertionPointEnd();
1888
1889 WriteText(text);
1890 }
1891
1892 /// Write an image at the current insertion point
1893 bool wxRichTextCtrl::WriteImage(const wxImage& image, int bitmapType)
1894 {
1895 wxRichTextImageBlock imageBlock;
1896
1897 wxImage image2 = image;
1898 if (imageBlock.MakeImageBlock(image2, bitmapType))
1899 return WriteImage(imageBlock);
1900
1901 return false;
1902 }
1903
1904 bool wxRichTextCtrl::WriteImage(const wxString& filename, int bitmapType)
1905 {
1906 wxRichTextImageBlock imageBlock;
1907
1908 wxImage image;
1909 if (imageBlock.MakeImageBlock(filename, bitmapType, image, false))
1910 return WriteImage(imageBlock);
1911
1912 return false;
1913 }
1914
1915 bool wxRichTextCtrl::WriteImage(const wxRichTextImageBlock& imageBlock)
1916 {
1917 return GetBuffer().InsertImageWithUndo(m_caretPosition+1, imageBlock, this);
1918 }
1919
1920 bool wxRichTextCtrl::WriteImage(const wxBitmap& bitmap, int bitmapType)
1921 {
1922 if (bitmap.Ok())
1923 {
1924 wxRichTextImageBlock imageBlock;
1925
1926 wxImage image = bitmap.ConvertToImage();
1927 if (image.Ok() && imageBlock.MakeImageBlock(image, bitmapType))
1928 return WriteImage(imageBlock);
1929 }
1930
1931 return false;
1932 }
1933
1934 /// Insert a newline (actually paragraph) at the current insertion point.
1935 bool wxRichTextCtrl::Newline()
1936 {
1937 return GetBuffer().InsertNewlineWithUndo(m_caretPosition+1, this);
1938 }
1939
1940
1941 // ----------------------------------------------------------------------------
1942 // Clipboard operations
1943 // ----------------------------------------------------------------------------
1944
1945 void wxRichTextCtrl::Copy()
1946 {
1947 if (CanCopy())
1948 {
1949 wxRichTextRange range = GetInternalSelectionRange();
1950 GetBuffer().CopyToClipboard(range);
1951 }
1952 }
1953
1954 void wxRichTextCtrl::Cut()
1955 {
1956 if (CanCut())
1957 {
1958 wxRichTextRange range = GetInternalSelectionRange();
1959 GetBuffer().CopyToClipboard(range);
1960
1961 DeleteSelectedContent();
1962 LayoutContent();
1963 Refresh(false);
1964 }
1965 }
1966
1967 void wxRichTextCtrl::Paste()
1968 {
1969 if (CanPaste())
1970 {
1971 BeginBatchUndo(_("Paste"));
1972
1973 long newPos = m_caretPosition;
1974 DeleteSelectedContent(& newPos);
1975
1976 GetBuffer().PasteFromClipboard(newPos);
1977
1978 EndBatchUndo();
1979 }
1980 }
1981
1982 void wxRichTextCtrl::DeleteSelection()
1983 {
1984 if (CanDeleteSelection())
1985 {
1986 DeleteSelectedContent();
1987 }
1988 }
1989
1990 bool wxRichTextCtrl::HasSelection() const
1991 {
1992 return m_selectionRange.GetStart() != -2 && m_selectionRange.GetEnd() != -2;
1993 }
1994
1995 bool wxRichTextCtrl::CanCopy() const
1996 {
1997 // Can copy if there's a selection
1998 return HasSelection();
1999 }
2000
2001 bool wxRichTextCtrl::CanCut() const
2002 {
2003 return HasSelection() && IsEditable();
2004 }
2005
2006 bool wxRichTextCtrl::CanPaste() const
2007 {
2008 if ( !IsEditable() )
2009 return false;
2010
2011 return GetBuffer().CanPasteFromClipboard();
2012 }
2013
2014 bool wxRichTextCtrl::CanDeleteSelection() const
2015 {
2016 return HasSelection() && IsEditable();
2017 }
2018
2019
2020 // ----------------------------------------------------------------------------
2021 // Accessors
2022 // ----------------------------------------------------------------------------
2023
2024 void wxRichTextCtrl::SetEditable(bool editable)
2025 {
2026 m_editable = editable;
2027 }
2028
2029 void wxRichTextCtrl::SetInsertionPoint(long pos)
2030 {
2031 SelectNone();
2032
2033 m_caretPosition = pos - 1;
2034 }
2035
2036 void wxRichTextCtrl::SetInsertionPointEnd()
2037 {
2038 long pos = GetLastPosition();
2039 SetInsertionPoint(pos);
2040 }
2041
2042 long wxRichTextCtrl::GetInsertionPoint() const
2043 {
2044 return m_caretPosition+1;
2045 }
2046
2047 wxTextPos wxRichTextCtrl::GetLastPosition() const
2048 {
2049 return GetBuffer().GetRange().GetEnd();
2050 }
2051
2052 // If the return values from and to are the same, there is no
2053 // selection.
2054 void wxRichTextCtrl::GetSelection(long* from, long* to) const
2055 {
2056 *from = m_selectionRange.GetStart();
2057 *to = m_selectionRange.GetEnd();
2058 if ((*to) != -1 && (*to) != -2)
2059 (*to) ++;
2060 }
2061
2062 bool wxRichTextCtrl::IsEditable() const
2063 {
2064 return m_editable;
2065 }
2066
2067 // ----------------------------------------------------------------------------
2068 // selection
2069 // ----------------------------------------------------------------------------
2070
2071 void wxRichTextCtrl::SetSelection(long from, long to)
2072 {
2073 // if from and to are both -1, it means (in wxWidgets) that all text should
2074 // be selected.
2075 if ( (from == -1) && (to == -1) )
2076 {
2077 from = 0;
2078 to = GetLastPosition()+1;
2079 }
2080
2081 DoSetSelection(from, to);
2082 }
2083
2084 void wxRichTextCtrl::DoSetSelection(long from, long to, bool WXUNUSED(scrollCaret))
2085 {
2086 m_selectionAnchor = from;
2087 m_selectionRange.SetRange(from, to-1);
2088
2089 Refresh(false);
2090 PositionCaret();
2091 }
2092
2093 // ----------------------------------------------------------------------------
2094 // Editing
2095 // ----------------------------------------------------------------------------
2096
2097 void wxRichTextCtrl::Replace(long WXUNUSED(from), long WXUNUSED(to),
2098 const wxString& value)
2099 {
2100 BeginBatchUndo(_("Replace"));
2101
2102 DeleteSelectedContent();
2103
2104 DoWriteText(value, SetValue_SelectionOnly);
2105
2106 EndBatchUndo();
2107 }
2108
2109 void wxRichTextCtrl::Remove(long from, long to)
2110 {
2111 SelectNone();
2112
2113 GetBuffer().DeleteRangeWithUndo(wxRichTextRange(from, to),
2114 m_caretPosition, // Current caret position
2115 from, // New caret position
2116 this);
2117
2118 LayoutContent();
2119 if (!IsFrozen())
2120 Refresh(false);
2121 }
2122
2123 bool wxRichTextCtrl::IsModified() const
2124 {
2125 return m_buffer.IsModified();
2126 }
2127
2128 void wxRichTextCtrl::MarkDirty()
2129 {
2130 m_buffer.Modify(true);
2131 }
2132
2133 void wxRichTextCtrl::DiscardEdits()
2134 {
2135 m_caretPositionForDefaultStyle = -2;
2136 m_buffer.Modify(false);
2137 m_buffer.GetCommandProcessor()->ClearCommands();
2138 }
2139
2140 int wxRichTextCtrl::GetNumberOfLines() const
2141 {
2142 return GetBuffer().GetParagraphCount();
2143 }
2144
2145 // ----------------------------------------------------------------------------
2146 // Positions <-> coords
2147 // ----------------------------------------------------------------------------
2148
2149 long wxRichTextCtrl::XYToPosition(long x, long y) const
2150 {
2151 return GetBuffer().XYToPosition(x, y);
2152 }
2153
2154 bool wxRichTextCtrl::PositionToXY(long pos, long *x, long *y) const
2155 {
2156 return GetBuffer().PositionToXY(pos, x, y);
2157 }
2158
2159 // ----------------------------------------------------------------------------
2160 //
2161 // ----------------------------------------------------------------------------
2162
2163 void wxRichTextCtrl::ShowPosition(long pos)
2164 {
2165 if (!IsPositionVisible(pos))
2166 ScrollIntoView(pos-1, WXK_DOWN);
2167 }
2168
2169 int wxRichTextCtrl::GetLineLength(long lineNo) const
2170 {
2171 return GetBuffer().GetParagraphLength(lineNo);
2172 }
2173
2174 wxString wxRichTextCtrl::GetLineText(long lineNo) const
2175 {
2176 return GetBuffer().GetParagraphText(lineNo);
2177 }
2178
2179 // ----------------------------------------------------------------------------
2180 // Undo/redo
2181 // ----------------------------------------------------------------------------
2182
2183 void wxRichTextCtrl::Undo()
2184 {
2185 if (CanUndo())
2186 {
2187 GetCommandProcessor()->Undo();
2188 }
2189 }
2190
2191 void wxRichTextCtrl::Redo()
2192 {
2193 if (CanRedo())
2194 {
2195 GetCommandProcessor()->Redo();
2196 }
2197 }
2198
2199 bool wxRichTextCtrl::CanUndo() const
2200 {
2201 return GetCommandProcessor()->CanUndo();
2202 }
2203
2204 bool wxRichTextCtrl::CanRedo() const
2205 {
2206 return GetCommandProcessor()->CanRedo();
2207 }
2208
2209 // ----------------------------------------------------------------------------
2210 // implementation details
2211 // ----------------------------------------------------------------------------
2212
2213 void wxRichTextCtrl::Command(wxCommandEvent& event)
2214 {
2215 SetValue(event.GetString());
2216 GetEventHandler()->ProcessEvent(event);
2217 }
2218
2219 void wxRichTextCtrl::OnDropFiles(wxDropFilesEvent& event)
2220 {
2221 // By default, load the first file into the text window.
2222 if (event.GetNumberOfFiles() > 0)
2223 {
2224 LoadFile(event.GetFiles()[0]);
2225 }
2226 }
2227
2228 wxSize wxRichTextCtrl::DoGetBestSize() const
2229 {
2230 return wxSize(10, 10);
2231 }
2232
2233 // ----------------------------------------------------------------------------
2234 // standard handlers for standard edit menu events
2235 // ----------------------------------------------------------------------------
2236
2237 void wxRichTextCtrl::OnCut(wxCommandEvent& WXUNUSED(event))
2238 {
2239 Cut();
2240 }
2241
2242 void wxRichTextCtrl::OnClear(wxCommandEvent& WXUNUSED(event))
2243 {
2244 DeleteSelection();
2245 }
2246
2247 void wxRichTextCtrl::OnCopy(wxCommandEvent& WXUNUSED(event))
2248 {
2249 Copy();
2250 }
2251
2252 void wxRichTextCtrl::OnPaste(wxCommandEvent& WXUNUSED(event))
2253 {
2254 Paste();
2255 }
2256
2257 void wxRichTextCtrl::OnUndo(wxCommandEvent& WXUNUSED(event))
2258 {
2259 Undo();
2260 }
2261
2262 void wxRichTextCtrl::OnRedo(wxCommandEvent& WXUNUSED(event))
2263 {
2264 Redo();
2265 }
2266
2267 void wxRichTextCtrl::OnUpdateCut(wxUpdateUIEvent& event)
2268 {
2269 event.Enable( CanCut() );
2270 }
2271
2272 void wxRichTextCtrl::OnUpdateCopy(wxUpdateUIEvent& event)
2273 {
2274 event.Enable( CanCopy() );
2275 }
2276
2277 void wxRichTextCtrl::OnUpdateClear(wxUpdateUIEvent& event)
2278 {
2279 event.Enable( CanDeleteSelection() );
2280 }
2281
2282 void wxRichTextCtrl::OnUpdatePaste(wxUpdateUIEvent& event)
2283 {
2284 event.Enable( CanPaste() );
2285 }
2286
2287 void wxRichTextCtrl::OnUpdateUndo(wxUpdateUIEvent& event)
2288 {
2289 event.Enable( CanUndo() );
2290 event.SetText( GetCommandProcessor()->GetUndoMenuLabel() );
2291 }
2292
2293 void wxRichTextCtrl::OnUpdateRedo(wxUpdateUIEvent& event)
2294 {
2295 event.Enable( CanRedo() );
2296 event.SetText( GetCommandProcessor()->GetRedoMenuLabel() );
2297 }
2298
2299 void wxRichTextCtrl::OnSelectAll(wxCommandEvent& WXUNUSED(event))
2300 {
2301 SelectAll();
2302 }
2303
2304 void wxRichTextCtrl::OnUpdateSelectAll(wxUpdateUIEvent& event)
2305 {
2306 event.Enable(GetLastPosition() > 0);
2307 }
2308
2309 void wxRichTextCtrl::OnContextMenu(wxContextMenuEvent& WXUNUSED(event))
2310 {
2311 if (!m_contextMenu)
2312 {
2313 m_contextMenu = new wxMenu;
2314 m_contextMenu->Append(wxID_UNDO, _("&Undo"));
2315 m_contextMenu->Append(wxID_REDO, _("&Redo"));
2316 m_contextMenu->AppendSeparator();
2317 m_contextMenu->Append(wxID_CUT, _("Cu&t"));
2318 m_contextMenu->Append(wxID_COPY, _("&Copy"));
2319 m_contextMenu->Append(wxID_PASTE, _("&Paste"));
2320 m_contextMenu->Append(wxID_CLEAR, _("&Delete"));
2321 m_contextMenu->AppendSeparator();
2322 m_contextMenu->Append(wxID_SELECTALL, _("Select &All"));
2323 }
2324 PopupMenu(m_contextMenu);
2325 return;
2326 }
2327
2328 bool wxRichTextCtrl::SetStyle(long start, long end, const wxTextAttrEx& style)
2329 {
2330 return GetBuffer().SetStyle(wxRichTextRange(start, end-1), style);
2331 }
2332
2333 bool wxRichTextCtrl::SetStyle(long start, long end, const wxTextAttr& style)
2334 {
2335 return GetBuffer().SetStyle(wxRichTextRange(start, end-1), wxTextAttrEx(style));
2336 }
2337
2338 bool wxRichTextCtrl::SetStyle(const wxRichTextRange& range, const wxRichTextAttr& style)
2339 {
2340 return GetBuffer().SetStyle(range.ToInternal(), style);
2341 }
2342
2343 // extended style setting operation with flags including:
2344 // wxRICHTEXT_SETSTYLE_WITH_UNDO, wxRICHTEXT_SETSTYLE_OPTIMIZE, wxRICHTEXT_SETSTYLE_PARAGRAPHS_ONLY.
2345 // see richtextbuffer.h for more details.
2346 bool wxRichTextCtrl::SetStyleEx(long start, long end, const wxTextAttrEx& style, int flags)
2347 {
2348 return GetBuffer().SetStyle(wxRichTextRange(start, end-1), style, flags);
2349 }
2350
2351 bool wxRichTextCtrl::SetStyleEx(const wxRichTextRange& range, const wxTextAttrEx& style, int flags)
2352 {
2353 return GetBuffer().SetStyle(range.ToInternal(), style, flags);
2354 }
2355
2356 bool wxRichTextCtrl::SetStyleEx(const wxRichTextRange& range, const wxRichTextAttr& style, int flags)
2357 {
2358 return GetBuffer().SetStyle(range.ToInternal(), style, flags);
2359 }
2360
2361 bool wxRichTextCtrl::SetDefaultStyle(const wxTextAttrEx& style)
2362 {
2363 return GetBuffer().SetDefaultStyle(style);
2364 }
2365
2366 bool wxRichTextCtrl::SetDefaultStyle(const wxTextAttr& style)
2367 {
2368 return GetBuffer().SetDefaultStyle(wxTextAttrEx(style));
2369 }
2370
2371 const wxTextAttrEx& wxRichTextCtrl::GetDefaultStyleEx() const
2372 {
2373 return GetBuffer().GetDefaultStyle();
2374 }
2375
2376 const wxTextAttr& wxRichTextCtrl::GetDefaultStyle() const
2377 {
2378 return GetBuffer().GetDefaultStyle();
2379 }
2380
2381 bool wxRichTextCtrl::GetStyle(long position, wxTextAttr& style)
2382 {
2383 wxTextAttrEx attr(style);
2384 if (GetBuffer().GetStyle(position, attr))
2385 {
2386 style = attr;
2387 return true;
2388 }
2389 else
2390 return false;
2391 }
2392
2393 bool wxRichTextCtrl::GetStyle(long position, wxTextAttrEx& style)
2394 {
2395 return GetBuffer().GetStyle(position, style);
2396 }
2397
2398 bool wxRichTextCtrl::GetStyle(long position, wxRichTextAttr& style)
2399 {
2400 return GetBuffer().GetStyle(position, style);
2401 }
2402
2403 /// Get the content (uncombined) attributes for this position.
2404
2405 bool wxRichTextCtrl::GetUncombinedStyle(long position, wxTextAttr& style)
2406 {
2407 wxTextAttrEx attr(style);
2408 if (GetBuffer().GetUncombinedStyle(position, attr))
2409 {
2410 style = attr;
2411 return true;
2412 }
2413 else
2414 return false;
2415 }
2416
2417 bool wxRichTextCtrl::GetUncombinedStyle(long position, wxTextAttrEx& style)
2418 {
2419 return GetBuffer().GetUncombinedStyle(position, style);
2420 }
2421
2422 bool wxRichTextCtrl::GetUncombinedStyle(long position, wxRichTextAttr& style)
2423 {
2424 return GetBuffer().GetUncombinedStyle(position, style);
2425 }
2426
2427 /// Set font, and also the buffer attributes
2428 bool wxRichTextCtrl::SetFont(const wxFont& font)
2429 {
2430 wxControl::SetFont(font);
2431
2432 wxTextAttrEx attr = GetBuffer().GetAttributes();
2433 attr.SetFont(font);
2434 GetBuffer().SetBasicStyle(attr);
2435
2436 GetBuffer().Invalidate(wxRICHTEXT_ALL);
2437 Refresh(false);
2438
2439 return true;
2440 }
2441
2442 /// Transform logical to physical
2443 wxPoint wxRichTextCtrl::GetPhysicalPoint(const wxPoint& ptLogical) const
2444 {
2445 wxPoint pt;
2446 CalcScrolledPosition(ptLogical.x, ptLogical.y, & pt.x, & pt.y);
2447
2448 return pt;
2449 }
2450
2451 /// Transform physical to logical
2452 wxPoint wxRichTextCtrl::GetLogicalPoint(const wxPoint& ptPhysical) const
2453 {
2454 wxPoint pt;
2455 CalcUnscrolledPosition(ptPhysical.x, ptPhysical.y, & pt.x, & pt.y);
2456
2457 return pt;
2458 }
2459
2460 /// Position the caret
2461 void wxRichTextCtrl::PositionCaret()
2462 {
2463 if (!GetCaret())
2464 return;
2465
2466 //wxLogDebug(wxT("PositionCaret"));
2467
2468 wxRect caretRect;
2469 if (GetCaretPositionForIndex(GetCaretPosition(), caretRect))
2470 {
2471 wxPoint originalPt = caretRect.GetPosition();
2472 wxPoint pt = GetPhysicalPoint(originalPt);
2473 if (GetCaret()->GetPosition() != pt)
2474 {
2475 GetCaret()->Move(pt);
2476 GetCaret()->SetSize(caretRect.GetSize());
2477 }
2478 }
2479 }
2480
2481 /// Get the caret height and position for the given character position
2482 bool wxRichTextCtrl::GetCaretPositionForIndex(long position, wxRect& rect)
2483 {
2484 wxClientDC dc(this);
2485 dc.SetFont(GetFont());
2486
2487 PrepareDC(dc);
2488
2489 wxPoint pt;
2490 int height = 0;
2491
2492 if (GetBuffer().FindPosition(dc, position, pt, & height, m_caretAtLineStart))
2493 {
2494 rect = wxRect(pt, wxSize(wxRICHTEXT_DEFAULT_CARET_WIDTH, height));
2495 return true;
2496 }
2497
2498 return false;
2499 }
2500
2501 /// Gets the line for the visible caret position. If the caret is
2502 /// shown at the very end of the line, it means the next character is actually
2503 /// on the following line. So let's get the line we're expecting to find
2504 /// if this is the case.
2505 wxRichTextLine* wxRichTextCtrl::GetVisibleLineForCaretPosition(long caretPosition) const
2506 {
2507 wxRichTextLine* line = GetBuffer().GetLineAtPosition(caretPosition, true);
2508 wxRichTextParagraph* para = GetBuffer().GetParagraphAtPosition(caretPosition, true);
2509 if (line)
2510 {
2511 wxRichTextRange lineRange = line->GetAbsoluteRange();
2512 if (caretPosition == lineRange.GetStart()-1 &&
2513 (para->GetRange().GetStart() != lineRange.GetStart()))
2514 {
2515 if (!m_caretAtLineStart)
2516 line = GetBuffer().GetLineAtPosition(caretPosition-1, true);
2517 }
2518 }
2519 return line;
2520 }
2521
2522
2523 /// Move the caret to the given character position
2524 bool wxRichTextCtrl::MoveCaret(long pos, bool showAtLineStart)
2525 {
2526 if (GetBuffer().GetDirty())
2527 LayoutContent();
2528
2529 if (pos <= GetBuffer().GetRange().GetEnd())
2530 {
2531 SetCaretPosition(pos, showAtLineStart);
2532
2533 PositionCaret();
2534
2535 return true;
2536 }
2537 else
2538 return false;
2539 }
2540
2541 /// Layout the buffer: which we must do before certain operations, such as
2542 /// setting the caret position.
2543 bool wxRichTextCtrl::LayoutContent(bool onlyVisibleRect)
2544 {
2545 if (GetBuffer().GetDirty() || onlyVisibleRect)
2546 {
2547 wxRect availableSpace(GetClientSize());
2548 if (availableSpace.width == 0)
2549 availableSpace.width = 10;
2550 if (availableSpace.height == 0)
2551 availableSpace.height = 10;
2552
2553 int flags = wxRICHTEXT_FIXED_WIDTH|wxRICHTEXT_VARIABLE_HEIGHT;
2554 if (onlyVisibleRect)
2555 {
2556 flags |= wxRICHTEXT_LAYOUT_SPECIFIED_RECT;
2557 availableSpace.SetPosition(GetLogicalPoint(wxPoint(0, 0)));
2558 }
2559
2560 wxClientDC dc(this);
2561 dc.SetFont(GetFont());
2562
2563 PrepareDC(dc);
2564
2565 GetBuffer().Defragment();
2566 GetBuffer().UpdateRanges(); // If items were deleted, ranges need recalculation
2567 GetBuffer().Layout(dc, availableSpace, flags);
2568 GetBuffer().SetDirty(false);
2569
2570 if (!IsFrozen())
2571 SetupScrollbars();
2572 }
2573
2574 return true;
2575 }
2576
2577 /// Is all of the selection bold?
2578 bool wxRichTextCtrl::IsSelectionBold()
2579 {
2580 if (HasSelection())
2581 {
2582 wxRichTextAttr attr;
2583 wxRichTextRange range = GetInternalSelectionRange();
2584 attr.SetFlags(wxTEXT_ATTR_FONT_WEIGHT);
2585 attr.SetFontWeight(wxBOLD);
2586
2587 return HasCharacterAttributes(range, attr);
2588 }
2589 else
2590 {
2591 // If no selection, then we need to combine current style with default style
2592 // to see what the effect would be if we started typing.
2593 wxRichTextAttr attr;
2594 attr.SetFlags(wxTEXT_ATTR_FONT_WEIGHT);
2595
2596 long pos = GetAdjustedCaretPosition(GetCaretPosition());
2597 if (GetStyle(pos, attr))
2598 {
2599 if (IsDefaultStyleShowing())
2600 wxRichTextApplyStyle(attr, GetDefaultStyleEx());
2601 return attr.GetFontWeight() == wxBOLD;
2602 }
2603 }
2604 return false;
2605 }
2606
2607 /// Is all of the selection italics?
2608 bool wxRichTextCtrl::IsSelectionItalics()
2609 {
2610 if (HasSelection())
2611 {
2612 wxRichTextRange range = GetInternalSelectionRange();
2613 wxRichTextAttr attr;
2614 attr.SetFlags(wxTEXT_ATTR_FONT_ITALIC);
2615 attr.SetFontStyle(wxITALIC);
2616
2617 return HasCharacterAttributes(range, attr);
2618 }
2619 else
2620 {
2621 // If no selection, then we need to combine current style with default style
2622 // to see what the effect would be if we started typing.
2623 wxRichTextAttr attr;
2624 attr.SetFlags(wxTEXT_ATTR_FONT_ITALIC);
2625
2626 long pos = GetAdjustedCaretPosition(GetCaretPosition());
2627 if (GetStyle(pos, attr))
2628 {
2629 if (IsDefaultStyleShowing())
2630 wxRichTextApplyStyle(attr, GetDefaultStyleEx());
2631 return attr.GetFontStyle() == wxITALIC;
2632 }
2633 }
2634 return false;
2635 }
2636
2637 /// Is all of the selection underlined?
2638 bool wxRichTextCtrl::IsSelectionUnderlined()
2639 {
2640 if (HasSelection())
2641 {
2642 wxRichTextRange range = GetInternalSelectionRange();
2643 wxRichTextAttr attr;
2644 attr.SetFlags(wxTEXT_ATTR_FONT_UNDERLINE);
2645 attr.SetFontUnderlined(true);
2646
2647 return HasCharacterAttributes(range, attr);
2648 }
2649 else
2650 {
2651 // If no selection, then we need to combine current style with default style
2652 // to see what the effect would be if we started typing.
2653 wxRichTextAttr attr;
2654 attr.SetFlags(wxTEXT_ATTR_FONT_UNDERLINE);
2655 long pos = GetAdjustedCaretPosition(GetCaretPosition());
2656
2657 if (GetStyle(pos, attr))
2658 {
2659 if (IsDefaultStyleShowing())
2660 wxRichTextApplyStyle(attr, GetDefaultStyleEx());
2661 return attr.GetFontUnderlined();
2662 }
2663 }
2664 return false;
2665 }
2666
2667 /// Apply bold to the selection
2668 bool wxRichTextCtrl::ApplyBoldToSelection()
2669 {
2670 wxRichTextAttr attr;
2671 attr.SetFlags(wxTEXT_ATTR_FONT_WEIGHT);
2672 attr.SetFontWeight(IsSelectionBold() ? wxNORMAL : wxBOLD);
2673
2674 if (HasSelection())
2675 return SetStyleEx(GetSelectionRange(), attr, wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_OPTIMIZE|wxRICHTEXT_SETSTYLE_CHARACTERS_ONLY);
2676 else
2677 SetAndShowDefaultStyle(attr);
2678 return true;
2679 }
2680
2681 /// Apply italic to the selection
2682 bool wxRichTextCtrl::ApplyItalicToSelection()
2683 {
2684 wxRichTextAttr attr;
2685 attr.SetFlags(wxTEXT_ATTR_FONT_ITALIC);
2686 attr.SetFontStyle(IsSelectionItalics() ? wxNORMAL : wxITALIC);
2687
2688 if (HasSelection())
2689 return SetStyleEx(GetSelectionRange(), attr, wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_OPTIMIZE|wxRICHTEXT_SETSTYLE_CHARACTERS_ONLY);
2690 else
2691 SetAndShowDefaultStyle(attr);
2692 return true;
2693 }
2694
2695 /// Apply underline to the selection
2696 bool wxRichTextCtrl::ApplyUnderlineToSelection()
2697 {
2698 wxRichTextAttr attr;
2699 attr.SetFlags(wxTEXT_ATTR_FONT_UNDERLINE);
2700 attr.SetFontUnderlined(!IsSelectionUnderlined());
2701
2702 if (HasSelection())
2703 return SetStyleEx(GetSelectionRange(), attr, wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_OPTIMIZE|wxRICHTEXT_SETSTYLE_CHARACTERS_ONLY);
2704 else
2705 SetAndShowDefaultStyle(attr);
2706 return true;
2707 }
2708
2709 /// Is all of the selection aligned according to the specified flag?
2710 bool wxRichTextCtrl::IsSelectionAligned(wxTextAttrAlignment alignment)
2711 {
2712 wxRichTextRange range;
2713 if (HasSelection())
2714 range = GetInternalSelectionRange();
2715 else
2716 range = wxRichTextRange(GetCaretPosition()+1, GetCaretPosition()+1);
2717
2718 wxRichTextAttr attr;
2719 attr.SetAlignment(alignment);
2720
2721 return HasParagraphAttributes(range, attr);
2722 }
2723
2724 /// Apply alignment to the selection
2725 bool wxRichTextCtrl::ApplyAlignmentToSelection(wxTextAttrAlignment alignment)
2726 {
2727 wxRichTextAttr attr;
2728 attr.SetAlignment(alignment);
2729 if (HasSelection())
2730 return SetStyle(GetSelectionRange(), attr);
2731 else
2732 {
2733 wxRichTextParagraph* para = GetBuffer().GetParagraphAtPosition(GetCaretPosition()+1);
2734 if (para)
2735 return SetStyleEx(para->GetRange().FromInternal(), attr, wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_OPTIMIZE|wxRICHTEXT_SETSTYLE_PARAGRAPHS_ONLY);
2736 }
2737 return true;
2738 }
2739
2740 /// Apply a named style to the selection
2741 void wxRichTextCtrl::ApplyStyle(wxRichTextStyleDefinition* def)
2742 {
2743 // Flags are defined within each definition, so only certain
2744 // attributes are applied.
2745 wxRichTextAttr attr(def->GetStyle());
2746
2747 int flags = wxRICHTEXT_SETSTYLE_WITH_UNDO|wxRICHTEXT_SETSTYLE_OPTIMIZE;
2748
2749 // Make sure the attr has the style name
2750 if (def->IsKindOf(CLASSINFO(wxRichTextParagraphStyleDefinition)))
2751 {
2752 attr.SetParagraphStyleName(def->GetName());
2753
2754 // If applying a paragraph style, we only want the paragraph nodes to adopt these
2755 // attributes, and not the leaf nodes. This will allow the context (e.g. text)
2756 // to change its style independently.
2757 flags |= wxRICHTEXT_SETSTYLE_PARAGRAPHS_ONLY;
2758 }
2759 else
2760 attr.SetCharacterStyleName(def->GetName());
2761
2762 if (HasSelection())
2763 SetStyleEx(GetSelectionRange(), attr, flags);
2764 else
2765 SetAndShowDefaultStyle(attr);
2766 }
2767
2768 /// Apply the style sheet to the buffer, for example if the styles have changed.
2769 bool wxRichTextCtrl::ApplyStyleSheet(wxRichTextStyleSheet* styleSheet)
2770 {
2771 if (!styleSheet)
2772 styleSheet = GetBuffer().GetStyleSheet();
2773 if (!styleSheet)
2774 return false;
2775
2776 if (GetBuffer().ApplyStyleSheet(styleSheet))
2777 {
2778 GetBuffer().Invalidate(wxRICHTEXT_ALL);
2779 Refresh(false);
2780 return true;
2781 }
2782 else
2783 return false;
2784 }
2785
2786 /// Sets the default style to the style under the cursor
2787 bool wxRichTextCtrl::SetDefaultStyleToCursorStyle()
2788 {
2789 wxTextAttrEx attr;
2790 attr.SetFlags(wxTEXT_ATTR_CHARACTER);
2791
2792 // If at the start of a paragraph, use the next position.
2793 long pos = GetAdjustedCaretPosition(GetCaretPosition());
2794
2795 #if wxRICHTEXT_USE_DYNAMIC_STYLES
2796 if (GetUncombinedStyle(pos, attr))
2797 #else
2798 if (GetStyle(pos, attr))
2799 #endif
2800 {
2801 SetDefaultStyle(attr);
2802 return true;
2803 }
2804
2805 return false;
2806 }
2807
2808 /// Returns the first visible position in the current view
2809 long wxRichTextCtrl::GetFirstVisiblePosition() const
2810 {
2811 wxRichTextLine* line = GetBuffer().GetLineAtYPosition(GetLogicalPoint(wxPoint(0, 0)).y);
2812 if (line)
2813 return line->GetAbsoluteRange().GetStart();
2814 else
2815 return 0;
2816 }
2817
2818 /// The adjusted caret position is the character position adjusted to take
2819 /// into account whether we're at the start of a paragraph, in which case
2820 /// style information should be taken from the next position, not current one.
2821 long wxRichTextCtrl::GetAdjustedCaretPosition(long caretPos) const
2822 {
2823 wxRichTextParagraph* para = GetBuffer().GetParagraphAtPosition(caretPos+1);
2824
2825 if (para && (caretPos+1 == para->GetRange().GetStart()))
2826 caretPos ++;
2827 return caretPos;
2828 }
2829
2830 /// Get/set the selection range in character positions. -1, -1 means no selection.
2831 /// The range is in API convention, i.e. a single character selection is denoted
2832 /// by (n, n+1)
2833 wxRichTextRange wxRichTextCtrl::GetSelectionRange() const
2834 {
2835 wxRichTextRange range = GetInternalSelectionRange();
2836 if (range != wxRichTextRange(-2,-2) && range != wxRichTextRange(-1,-1))
2837 range.SetEnd(range.GetEnd() + 1);
2838 return range;
2839 }
2840
2841 void wxRichTextCtrl::SetSelectionRange(const wxRichTextRange& range)
2842 {
2843 wxRichTextRange range1(range);
2844 if (range1 != wxRichTextRange(-2,-2) && range1 != wxRichTextRange(-1,-1) )
2845 range1.SetEnd(range1.GetEnd() - 1);
2846
2847 wxASSERT( range1.GetStart() > range1.GetEnd() );
2848
2849 SetInternalSelectionRange(range1);
2850 }
2851
2852 #endif
2853 // wxUSE_RICHTEXT
2854