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