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