1 ///////////////////////////////////////////////////////////////////////////// 
   2 // Name:        src/univ/textctrl.cpp 
   4 // Author:      Vadim Zeitlin 
   8 // Copyright:   (c) 2000 SciTech Software, Inc. (www.scitechsoft.com) 
   9 // Licence:     wxWindows licence 
  10 ///////////////////////////////////////////////////////////////////////////// 
  15 +  1. update vert scrollbar when any line length changes for WrapLines() 
  16 +  2. cursor movement ("Hello,^" -> "^verse!" on Arrow Down)? 
  17      -> maybe save the x position and use it instead of current in handling 
  18         DOWN/UP actions (this would make up/down always return the cursor to 
  20    3. split file into chunks 
  21 +? 4. rewrite Replace() refresh logic to deal with wrapping lines 
  22 +? 5. cache info found by GetPartOfWrappedLine() - performance must be horrible 
  25    6. backspace refreshes too much (until end of line) 
  29    Optimisation hints from PureQuantify: 
  31   +1. wxStringTokenize is the slowest part of Replace 
  32    2. GetDC/ReleaseDC are very slow, avoid calling them several times 
  33   +3. GetCharHeight() should be cached too 
  34    4. wxClientDC construction/destruction in HitTestLine is horribly expensive 
  36    For line wrapping controls HitTest2 takes 50% of program time. The results 
  37    of GetRowsPerLine and GetPartOfWrappedLine *MUST* be cached. 
  39    Search for "OPT!" for things which must be optimized. 
  45    Everywhere in this file LINE refers to a logical line of text, and ROW to a 
  46    physical line of text on the display. They are the same unless WrapLines() 
  47    is true in which case a single LINE may correspond to multiple ROWs. 
  49    A text position is an unsigned int (which for reasons of compatibility is 
  50    still a long as wxTextPos) from 0 to GetLastPosition() inclusive. The positions 
  51    correspond to the gaps between the letters so the position 0 is just 
  52    before the first character and the last position is the one beyond the last 
  53    character. For an empty text control GetLastPosition() returns 0. 
  55    Lines and columns returned/accepted by XYToPosition() and PositionToXY() 
  56    start from 0. The y coordinate is a LINE, not a ROW. Columns correspond to 
  57    the characters, the first column of a line is the first character in it, 
  58    the last one is length(line text). For compatibility, again, lines and 
  59    columns are also longs. 
  61    When translating lines/column coordinates to/from positions, the line and 
  62    column give the character after the given position. Thus, GetLastPosition() 
  63    doesn't have any corresponding column. 
  65    An example of positions and lines/columns for a control without wrapping 
  66    containing the text "Hello, Universe!\nGoodbye" 
  69    pos:    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 
  70             H e l l o ,   U n i v e r s e !         line 0 
  71    col:     0 1 2 3 4 5 6 7 8 9 1 1 1 1 1 1 
  80    The same example for a control with line wrap assuming "Universe" is too 
  81    long to fit on the same line with "Hello,": 
  84             H e l l o ,                             line 0 (row 0) 
  88    pos:    6 7 8 9 0 1 2 3 4 5 6 
  89               U n i v e r s e !                     line 0 (row 1) 
  90    col:     6 7 8 9 1 1 1 1 1 1 
  93    (line 1 == row 2 same as above) 
  95    Note that there is still the same number of columns and positions and that 
  96    there is no (logical) position at the end of the first ROW. This position 
  97    is identified with the preceding one (which is not how Windows does it: it 
  98    identifies it with the next one, i.e. the first position of the next line, 
  99    but much more logical IMHO). 
 103    Search for "OPT" for possible optimizations 
 105    A possible global optimization would be to always store the coords in the 
 106    text in triplets (pos, col, line) and update them simultaneously instead of 
 107    recalculating col and line from pos each time it is needed. Currently we 
 108    only do it for the current position but we might also do it for the 
 109    selection start and end. 
 112 // ============================================================================ 
 114 // ============================================================================ 
 116 // ---------------------------------------------------------------------------- 
 118 // ---------------------------------------------------------------------------- 
 120 #include "wx/wxprec.h" 
 128 #include "wx/textctrl.h" 
 132     #include "wx/dcclient.h" 
 133     #include "wx/validate.h" 
 134     #include "wx/dataobj.h" 
 139 #include "wx/clipbrd.h" 
 141 #include "wx/textfile.h" 
 143 #include "wx/caret.h" 
 145 #include "wx/univ/inphand.h" 
 146 #include "wx/univ/renderer.h" 
 147 #include "wx/univ/colschem.h" 
 148 #include "wx/univ/theme.h" 
 150 #include "wx/cmdproc.h" 
 152 #if wxDEBUG_LEVEL >= 2 
 153     // turn extra wxTextCtrl-specific debugging on/off 
 156     // turn wxTextCtrl::Replace() debugging on (slows down code a *lot*!) 
 157     #define WXDEBUG_TEXT_REPLACE 
 158 #endif // wxDEBUG_LEVEL >= 2 
 160 // wxStringTokenize only needed for debug checks 
 161 #ifdef WXDEBUG_TEXT_REPLACE 
 162     #include "wx/tokenzr.h" 
 163 #endif // WXDEBUG_TEXT_REPLACE 
 165 // ---------------------------------------------------------------------------- 
 166 // wxStdTextCtrlInputHandler: this control handles only the mouse/kbd actions 
 167 // common to Win32 and GTK, platform-specific things are implemented elsewhere 
 168 // ---------------------------------------------------------------------------- 
 170 class WXDLLEXPORT wxStdTextCtrlInputHandler 
: public wxStdInputHandler
 
 173     wxStdTextCtrlInputHandler(wxInputHandler 
*inphand
); 
 175     virtual bool HandleKey(wxInputConsumer 
*consumer
, 
 176                            const wxKeyEvent
& event
, 
 178     virtual bool HandleMouse(wxInputConsumer 
*consumer
, 
 179                              const wxMouseEvent
& event
); 
 180     virtual bool HandleMouseMove(wxInputConsumer 
*consumer
, 
 181                                  const wxMouseEvent
& event
); 
 182     virtual bool HandleFocus(wxInputConsumer 
*consumer
, const wxFocusEvent
& event
); 
 185     // get the position of the mouse click 
 186     static wxTextPos 
HitTest(const wxTextCtrl 
*text
, const wxPoint
& pos
); 
 189     wxTextCtrl 
*m_winCapture
; 
 192 // ---------------------------------------------------------------------------- 
 194 // ---------------------------------------------------------------------------- 
 196 // exchange two positions so that from is always less than or equal to to 
 197 static inline void OrderPositions(wxTextPos
& from
, wxTextPos
& to
) 
 201         wxTextPos tmp 
= from
; 
 207 // ---------------------------------------------------------------------------- 
 209 // ---------------------------------------------------------------------------- 
 211 // names of text ctrl commands 
 212 #define wxTEXT_COMMAND_INSERT wxT("insert") 
 213 #define wxTEXT_COMMAND_REMOVE wxT("remove") 
 215 // the value which is never used for text position, even not -1 which is 
 216 // sometimes used for some special meaning 
 217 static const wxTextPos INVALID_POS_VALUE 
= wxInvalidTextCoord
; 
 219 // overlap between pages (when using PageUp/Dn) in lines 
 220 static const size_t PAGE_OVERLAP_IN_LINES 
= 1; 
 222 // ---------------------------------------------------------------------------- 
 223 // private data of wxTextCtrl 
 224 // ---------------------------------------------------------------------------- 
 226 // the data only used by single line text controls 
 227 struct wxTextSingleLineData
 
 229     // the position of the first visible pixel and the first visible column 
 231     wxTextCoord m_colStart
; 
 233     // and the last ones (m_posLastVisible is the width but m_colLastVisible 
 234     // is an absolute value) 
 235     wxCoord m_posLastVisible
; 
 236     wxTextCoord m_colLastVisible
; 
 239     wxTextSingleLineData() 
 244         m_colLastVisible 
= -1; 
 245         m_posLastVisible 
= -1; 
 250 // the data only used by multi line text controls 
 251 struct wxTextMultiLineData
 
 254     wxArrayString m_lines
; 
 256     // the current ranges of the scrollbars 
 260     // should we adjust the horz/vert scrollbar? 
 261     bool m_updateScrollbarX
, 
 264     // the max line length in pixels 
 267     // the index of the line which has the length of m_widthMax 
 268     wxTextCoord m_lineLongest
; 
 270     // the rect in which text appears: it is even less than m_rectText because 
 271     // only the last _complete_ line is shown, hence there is an unoccupied 
 272     // horizontal band at the bottom of it 
 273     wxRect m_rectTextReal
; 
 275     // the x-coordinate of the caret before we started moving it vertically: 
 276     // this is used to ensure that moving the caret up and then down will 
 277     // return it to the same position as if we always round it in one direction 
 278     // we would shift it in that direction 
 280     // when m_xCaret == -1, we don't have any remembered position 
 284     wxTextMultiLineData() 
 290         m_updateScrollbarY 
= false; 
 299 // the data only used by multi line text controls in line wrap mode 
 300 class wxWrappedLineData
 
 302     // these functions set all our values, so give them access to them 
 303 friend void wxTextCtrl::LayoutLine(wxTextCoord line
, 
 304                                     wxWrappedLineData
& lineData
) const; 
 305 friend void wxTextCtrl::LayoutLines(wxTextCoord
) const; 
 314     // get the start of any row (remember that accessing m_rowsStart doesn't work 
 315     // for the first one) 
 316     wxTextCoord 
GetRowStart(wxTextCoord row
) const 
 318         wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") ); 
 320         return row 
? m_rowsStart
[row 
- 1] : 0; 
 323     // get the length of the row (using the total line length which we don't 
 324     // have here but need to calculate the length of the last row, so it must 
 326     wxTextCoord 
GetRowLength(wxTextCoord row
, wxTextCoord lenLine
) const 
 328         wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") ); 
 330         // note that m_rowsStart[row] is the same as GetRowStart(row + 1) (but 
 331         // slightly more efficient) and lenLine is the same as the start of the 
 332         // first row of the next line 
 333         return ((size_t)row 
== m_rowsStart
.GetCount() ? lenLine 
: m_rowsStart
[row
]) 
 337     // return the width of the row in pixels 
 338     wxCoord 
GetRowWidth(wxTextCoord row
) const 
 340         wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") ); 
 342         return m_rowsWidth
[row
]; 
 345     // return the number of rows 
 346     size_t GetRowCount() const 
 348         wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") ); 
 350         return m_rowsStart
.GetCount() + 1; 
 353     // return the number of additional (i.e. after the first one) rows 
 354     size_t GetExtraRowCount() const 
 356         wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") ); 
 358         return m_rowsStart
.GetCount(); 
 361     // return the first row of this line 
 362     wxTextCoord 
GetFirstRow() const 
 364         wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") ); 
 369     // return the first row of the next line 
 370     wxTextCoord 
GetNextRow() const 
 372         wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") ); 
 374         return m_rowFirst 
+ m_rowsStart
.GetCount() + 1; 
 377     // this just provides direct access to m_rowsStart aerray for efficiency 
 378     wxTextCoord 
GetExtraRowStart(wxTextCoord row
) const 
 380         wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") ); 
 382         return m_rowsStart
[row
]; 
 385     // this code is unused any longer 
 387     // return true if the column is in the start of the last row (hence the row 
 388     // it is in is not wrapped) 
 389     bool IsLastRow(wxTextCoord colRowStart
) const 
 391         return colRowStart 
== GetRowStart(m_rowsStart
.GetCount()); 
 394     // return true if the column is the last column of the row starting in 
 396     bool IsLastColInRow(wxTextCoord colRowStart
, 
 397                         wxTextCoord colRowEnd
, 
 398                         wxTextCoord lenLine
) const 
 400         // find the row which starts with colRowStart 
 401         size_t nRows 
= GetRowCount(); 
 402         for ( size_t n 
= 0; n 
< nRows
; n
++ ) 
 404             if ( GetRowStart(n
) == colRowStart 
) 
 406                 wxTextCoord colNextRowStart 
= n 
== nRows 
- 1 
 408                                                 : GetRowStart(n 
+ 1); 
 410                 wxASSERT_MSG( colRowEnd 
< colNextRowStart
, 
 411                               wxT("this column is not in this row at all!") ); 
 413                 return colRowEnd 
== colNextRowStart 
- 1; 
 417         // caller got it wrong 
 418         wxFAIL_MSG( wxT("this column is not in the start of the row!") ); 
 424     // is this row the last one in its line? 
 425     bool IsLastRow(wxTextCoord row
) const 
 427         return (size_t)row 
== GetExtraRowCount(); 
 430     // the line is valid if it had been laid out correctly: note that just 
 431     // shiwting the line (because one of previous lines changed) doesn't make 
 433     bool IsValid() const { return !m_rowsWidth
.IsEmpty(); } 
 435     // invalidating line will relayout it 
 436     void Invalidate() { m_rowsWidth
.Empty(); } 
 439     // for each line we remember the starting columns of all its rows after the 
 440     // first one (which always starts at 0), i.e. if a line is wrapped twice 
 441     // (== takes 3 rows) its m_rowsStart[0] may be 10 and m_rowsStart[1] == 15 
 442     wxArrayLong m_rowsStart
; 
 444     // and the width of each row in pixels (this array starts from 0, as usual) 
 445     wxArrayInt m_rowsWidth
; 
 447     // and also its starting row (0 for the first line, first lines' 
 448     // m_rowsStart.GetCount() + 1 for the second &c): it is set to -1 initially 
 449     // and this means that the struct hadn't yet been initialized 
 450     wxTextCoord m_rowFirst
; 
 452     // the last modification "time"-stamp used by LayoutLines() 
 456 WX_DECLARE_OBJARRAY(wxWrappedLineData
, wxArrayWrappedLinesData
); 
 457 #include "wx/arrimpl.cpp" 
 458 WX_DEFINE_OBJARRAY(wxArrayWrappedLinesData
); 
 460 struct wxTextWrappedData 
: public wxTextMultiLineData
 
 462     // the width of the column to the right of the text rect used for the 
 463     // indicator mark display for the wrapped lines 
 466     // the data for each line 
 467     wxArrayWrappedLinesData m_linesData
; 
 469     // flag telling us to recalculate all starting rows starting from this line 
 470     // (if it is -1, we don't have to recalculate anything) - it is set when 
 471     // the number of the rows in the middle of the control changes 
 472     wxTextCoord m_rowFirstInvalid
; 
 474     // the current timestamp used by LayoutLines() 
 477     // invalidate starting rows of all lines (NOT rows!) after this one 
 478     void InvalidateLinesBelow(wxTextCoord line
) 
 480         if ( m_rowFirstInvalid 
== -1 || m_rowFirstInvalid 
> line 
) 
 482             m_rowFirstInvalid 
= line
; 
 486     // check if this line is valid: i.e. before the first invalid one 
 487     bool IsValidLine(wxTextCoord line
) const 
 489         return ((m_rowFirstInvalid 
== -1) || (line 
< m_rowFirstInvalid
)) && 
 490                     m_linesData
[line
].IsValid(); 
 497         m_rowFirstInvalid 
= -1; 
 502 // ---------------------------------------------------------------------------- 
 503 // private classes for undo/redo management 
 504 // ---------------------------------------------------------------------------- 
 507    We use custom versions of wxWidgets command processor to implement undo/redo 
 508    as we want to avoid storing the backpointer to wxTextCtrl in wxCommand 
 509    itself: this is a waste of memory as all commands in the given command 
 510    processor always have the same associated wxTextCtrl and so it makes sense 
 511    to store the backpointer there. 
 513    As for the rest of the implementation, it's fairly standard: we have 2 
 514    command classes corresponding to adding and removing text. 
 517 // a command corresponding to a wxTextCtrl action 
 518 class wxTextCtrlCommand 
: public wxCommand
 
 521     wxTextCtrlCommand(const wxString
& name
) : wxCommand(true, name
) { } 
 523     // we don't use these methods as they don't make sense for us as we need a 
 524     // wxTextCtrl to be applied 
 525     virtual bool Do() { wxFAIL_MSG(wxT("shouldn't be called")); return false; } 
 526     virtual bool Undo() { wxFAIL_MSG(wxT("shouldn't be called")); return false; } 
 528     // instead, our command processor uses these methods 
 529     virtual bool Do(wxTextCtrl 
*text
) = 0; 
 530     virtual bool Undo(wxTextCtrl 
*text
) = 0; 
 533 // insert text command 
 534 class wxTextCtrlInsertCommand 
: public wxTextCtrlCommand
 
 537     wxTextCtrlInsertCommand(const wxString
& textToInsert
) 
 538         : wxTextCtrlCommand(wxTEXT_COMMAND_INSERT
), m_text(textToInsert
) 
 543     // combine the 2 commands together 
 544     void Append(wxTextCtrlInsertCommand 
*other
); 
 546     virtual bool CanUndo() const; 
 547     virtual bool Do(wxTextCtrl 
*text
); 
 548     virtual bool Do() { return wxTextCtrlCommand::Do(); } 
 549     virtual bool Undo() { return wxTextCtrlCommand::Undo(); } 
 550     virtual bool Undo(wxTextCtrl 
*text
); 
 553     // the text we insert 
 556     // the position where we inserted the text 
 560 // remove text command 
 561 class wxTextCtrlRemoveCommand 
: public wxTextCtrlCommand
 
 564     wxTextCtrlRemoveCommand(wxTextPos from
, wxTextPos to
) 
 565         : wxTextCtrlCommand(wxTEXT_COMMAND_REMOVE
) 
 571     virtual bool CanUndo() const; 
 572     virtual bool Do(wxTextCtrl 
*text
); 
 573     virtual bool Do() { return wxTextCtrlCommand::Do(); } 
 574     virtual bool Undo() { return wxTextCtrlCommand::Undo(); } 
 575     virtual bool Undo(wxTextCtrl 
*text
); 
 578     // the range of text to delete 
 582     // the text which was deleted when this command was Do()ne 
 583     wxString m_textDeleted
; 
 586 // a command processor for a wxTextCtrl 
 587 class wxTextCtrlCommandProcessor 
: public wxCommandProcessor
 
 590     wxTextCtrlCommandProcessor(wxTextCtrl 
*text
) 
 592         m_compressInserts 
= false; 
 597     // override Store() to compress multiple wxTextCtrlInsertCommand into one 
 598     virtual void Store(wxCommand 
*command
); 
 600     // stop compressing insert commands when this is called 
 601     void StopCompressing() { m_compressInserts 
= false; } 
 604     wxTextCtrl 
*GetTextCtrl() const { return m_text
; } 
 605     bool IsCompressing() const { return m_compressInserts
; } 
 608     virtual bool DoCommand(wxCommand
& cmd
) 
 609         { return ((wxTextCtrlCommand 
&)cmd
).Do(m_text
); } 
 610     virtual bool UndoCommand(wxCommand
& cmd
) 
 611         { return ((wxTextCtrlCommand 
&)cmd
).Undo(m_text
); } 
 613     // check if this command is a wxTextCtrlInsertCommand and return it casted 
 614     // to the right type if it is or NULL otherwise 
 615     wxTextCtrlInsertCommand 
*IsInsertCommand(wxCommand 
*cmd
); 
 618     // the control we're associated with 
 621     // if the flag is true we're compressing subsequent insert commands into 
 622     // one so that the entire typing could be undone in one call to Undo() 
 623     bool m_compressInserts
; 
 626 // ============================================================================ 
 628 // ============================================================================ 
 630 BEGIN_EVENT_TABLE(wxTextCtrl
, wxTextCtrlBase
) 
 631     EVT_CHAR(wxTextCtrl::OnChar
) 
 633     EVT_SIZE(wxTextCtrl::OnSize
) 
 636 // ---------------------------------------------------------------------------- 
 638 // ---------------------------------------------------------------------------- 
 640 void wxTextCtrl::Init() 
 646     m_isModified 
= false; 
 658     // init the undo manager 
 659     m_cmdProcessor 
= new wxTextCtrlCommandProcessor(this); 
 665 bool wxTextCtrl::Create(wxWindow 
*parent
, 
 667                         const wxString
& value
, 
 671                         const wxValidator
& validator
, 
 672                         const wxString 
&name
) 
 674     if ( style 
& wxTE_MULTILINE 
) 
 676         // for compatibility with wxMSW we create the controls with vertical 
 677         // scrollbar always shown unless they have wxTE_RICH style (because 
 678         // Windows text controls always has vert scrollbar but richedit one 
 680         if ( !(style 
& wxTE_RICH
) ) 
 682             style 
|= wxALWAYS_SHOW_SB
; 
 685         // wrapping style: wxTE_DONTWRAP == wxHSCROLL so if it's _not_ given, 
 686         // we won't have horizontal scrollbar automatically, no need to do 
 689         // TODO: support wxTE_NO_VSCROLL (?) 
 691         // create data object for normal multiline or for controls with line 
 693         if ( style 
& wxHSCROLL 
) 
 695             m_data
.mdata 
= new wxTextMultiLineData
; 
 697         else // we must wrap lines if we don't have horizontal scrollbar 
 699             // NB: we can't rely on HasFlag(wxHSCROLL) as the flags can change 
 700             //     later and even wxWindow::Create() itself temporarily resets 
 701             //     wxHSCROLL in wxUniv, so remember that we have a wrapped data 
 702             //     and not just a multi line data in a separate variable 
 704             m_data
.wdata 
= new wxTextWrappedData
; 
 709         // this doesn't make sense for single line controls 
 712         // create data object for single line controls 
 713         m_data
.sdata 
= new wxTextSingleLineData
; 
 716 #if wxUSE_TWO_WINDOWS 
 717     if ((style 
& wxBORDER_MASK
) == 0) 
 718         style 
|= wxBORDER_SUNKEN
; 
 721     if ( !wxControl::Create(parent
, id
, pos
, size
, style
, 
 727     SetCursor(wxCURSOR_IBEAM
); 
 729     if ( style 
& wxTE_MULTILINE 
) 
 731         // we should always have at least one line in a multiline control 
 732         MData().m_lines
.Add(wxEmptyString
); 
 734         if ( !(style 
& wxHSCROLL
) ) 
 736             WData().m_linesData
.Add(new wxWrappedLineData
); 
 737             WData().InvalidateLinesBelow(0); 
 740         // we might support it but it's quite useless and other ports don't 
 742         wxASSERT_MSG( !(style 
& wxTE_PASSWORD
), 
 743                       wxT("wxTE_PASSWORD can't be used with multiline ctrls") ); 
 748     SetInitialSize(size
); 
 750     m_isEditable 
= !(style 
& wxTE_READONLY
); 
 753     InitInsertionPoint(); 
 755     // we can't show caret right now as we're not shown yet and so it would 
 756     // result in garbage on the screen - we'll do it after first OnPaint() 
 759     CreateInputHandler(wxINP_HANDLER_TEXTCTRL
); 
 761     wxSizeEvent 
sizeEvent(GetSize(), GetId()); 
 762     GetEventHandler()->ProcessEvent(sizeEvent
); 
 767 wxTextCtrl::~wxTextCtrl() 
 769     delete m_cmdProcessor
; 
 773         if ( IsSingleLine() ) 
 775         else if ( WrapLines() ) 
 782 // ---------------------------------------------------------------------------- 
 784 // ---------------------------------------------------------------------------- 
 786 void wxTextCtrl::DoSetValue(const wxString
& value
, int flags
) 
 788     if ( value 
!= GetValue() ) 
 790         EventsSuppressor 
noeventsIf(this, !(flags 
& SetValue_SendEvent
)); 
 792         Replace(0, GetLastPosition(), value
); 
 794         if ( IsSingleLine() ) 
 796             SetInsertionPoint(0); 
 799     else // nothing changed 
 801         // still send event for consistency 
 802         if ( flags 
& SetValue_SendEvent 
) 
 803             SendTextUpdatedEvent(); 
 807 const wxArrayString
& wxTextCtrl::GetLines() const 
 809     return MData().m_lines
; 
 812 size_t wxTextCtrl::GetLineCount() const 
 814     return MData().m_lines
.GetCount(); 
 817 wxString 
wxTextCtrl::DoGetValue() const 
 819     // for multiline controls we don't always store the total value but only 
 820     // recompute it when asked - and to invalidate it we just empty it in 
 822     if ( !IsSingleLine() && m_value
.empty() ) 
 824         // recalculate: note that we always do it for empty multilien control, 
 825         // but then it's so quick that it's not important 
 827         // the first line is special as there is no \n before it, so it's 
 829         const wxArrayString
& lines 
= GetLines(); 
 830         wxTextCtrl 
*self 
= wxConstCast(this, wxTextCtrl
); 
 831         self
->m_value 
<< lines
[0u]; 
 832         size_t count 
= lines
.GetCount(); 
 833         for ( size_t n 
= 1; n 
< count
; n
++ ) 
 835             self
->m_value 
<< wxT('\n') << lines
[n
]; 
 842 void wxTextCtrl::Clear() 
 844     SetValue(wxEmptyString
); 
 847 bool wxTextCtrl::ReplaceLine(wxTextCoord line
, 
 848                              const wxString
& text
) 
 852         // first, we have to relayout the line entirely 
 854         // OPT: we might try not to recalc the unchanged part of line 
 856         wxWrappedLineData
& lineData 
= WData().m_linesData
[line
]; 
 858         // if we had some number of rows before, use this number, otherwise 
 859         // just make sure that the test below (rowsNew != rowsOld) will be true 
 861         if ( lineData
.IsValid() ) 
 863             rowsOld 
= lineData
.GetExtraRowCount(); 
 865         else // line wasn't laid out yet 
 867             // assume it changed entirely as we can't do anything better 
 871         // now change the line 
 872         MData().m_lines
[line
] = text
; 
 874         // OPT: we choose to lay it our immediately instead of delaying it 
 875         //      until it is needed because it allows us to avoid invalidating 
 876         //      lines further down if the number of rows didn't chnage, but 
 877         //      maybe we can imporve this even further? 
 878         LayoutLine(line
, lineData
); 
 880         int rowsNew 
= lineData
.GetExtraRowCount(); 
 882         if ( rowsNew 
!= rowsOld 
) 
 884             // we have to update the line wrap marks as this is normally done 
 885             // by LayoutLines() which we bypassed by calling LayoutLine() 
 887             wxTextCoord rowFirst 
= lineData
.GetFirstRow(), 
 888                         rowCount 
= wxMax(rowsOld
, rowsNew
); 
 889             RefreshLineWrapMarks(rowFirst
, rowFirst 
+ rowCount
); 
 891             // next, if this is not the last line, as the number of rows in it 
 892             // changed, we need to shift all the lines below it 
 893             if ( (size_t)line 
< WData().m_linesData
.GetCount() ) 
 895                 // number of rows changed shifting all lines below 
 896                 WData().InvalidateLinesBelow(line 
+ 1); 
 899             // the number of rows changed 
 905         MData().m_lines
[line
] = text
; 
 908     // the number of rows didn't change 
 912 void wxTextCtrl::RemoveLine(wxTextCoord line
) 
 914     MData().m_lines
.RemoveAt(line
); 
 917         // we need to recalculate all the starting rows from this line, but we 
 918         // can avoid doing it if this line was never calculated: this means 
 919         // that we will recalculate all lines below it anyhow later if needed 
 920         if ( WData().IsValidLine(line
) ) 
 922             WData().InvalidateLinesBelow(line
); 
 925         WData().m_linesData
.RemoveAt(line
); 
 929 void wxTextCtrl::InsertLine(wxTextCoord line
, const wxString
& text
) 
 931     MData().m_lines
.Insert(text
, line
); 
 934         WData().m_linesData
.Insert(new wxWrappedLineData
, line
); 
 936         // invalidate everything below it 
 937         WData().InvalidateLinesBelow(line
); 
 941 void wxTextCtrl::Replace(wxTextPos from
, wxTextPos to
, const wxString
& text
) 
 943     wxTextCoord colStart
, colEnd
, 
 947          !PositionToXY(from
, &colStart
, &lineStart
) || 
 948          !PositionToXY(to
, &colEnd
, &lineEnd
) ) 
 950         wxFAIL_MSG(wxT("invalid range in wxTextCtrl::Replace")); 
 955 #ifdef WXDEBUG_TEXT_REPLACE 
 956     // a straighforward (but very inefficient) way of calculating what the new 
 958     wxString textTotal 
= GetValue(); 
 959     wxString 
textTotalNew(textTotal
, (size_t)from
); 
 960     textTotalNew 
+= text
; 
 961     if ( (size_t)to 
< textTotal
.length() ) 
 962         textTotalNew 
+= textTotal
.c_str() + (size_t)to
; 
 963 #endif // WXDEBUG_TEXT_REPLACE 
 965     // remember the old selection and reset it immediately: we must do it 
 966     // before calling Refresh(anything) as, at least under GTK, this leads to 
 967     // an _immediate_ repaint (under MSW it is delayed) and hence parts of 
 968     // text would be redrawn as selected if we didn't reset the selection 
 969     int selStartOld 
= m_selStart
, 
 970         selEndOld 
= m_selEnd
; 
 975     if ( IsSingleLine() ) 
 977         // replace the part of the text with the new value 
 978         wxString 
valueNew(m_value
, (size_t)from
); 
 980         // remember it for later use 
 981         wxCoord startNewText 
= GetTextWidth(valueNew
); 
 984         if ( (size_t)to 
< m_value
.length() ) 
 986             valueNew 
+= m_value
.c_str() + (size_t)to
; 
 989         // we usually refresh till the end of line except of the most common case 
 990         // when some text is appended to the end of the string in which case we 
 992         wxCoord widthNewText
; 
 994         if ( (size_t)from 
< m_value
.length() ) 
 996             // refresh till the end of line 
 999         else // text appended, not replaced 
1001             // refresh only the new text 
1002             widthNewText 
= GetTextWidth(text
); 
1007         // force SData().m_colLastVisible update 
1008         SData().m_colLastVisible 
= -1; 
1011         RefreshPixelRange(0, startNewText
, widthNewText
); 
1015         //OPT: special case for replacements inside single line? 
1018            Join all the lines in the replacement range into one string, then 
1019            replace a part of it with the new text and break it into lines again. 
1022         // (0) we want to know if this replacement changes the number of rows 
1023         //     as if it does we need to refresh everything below the changed 
1024         //     text (it will be shifted...) and we can avoid it if there is no 
1026         bool rowsNumberChanged 
= false; 
1029         const wxArrayString
& linesOld 
= GetLines(); 
1032         for ( line 
= lineStart
; line 
<= lineEnd
; line
++ ) 
1034             if ( line 
> lineStart 
) 
1036                 // from the previous line 
1037                 textOrig 
+= wxT('\n'); 
1040             textOrig 
+= linesOld
[line
]; 
1043         // we need to append the '\n' for the last line unless there is no 
1045         size_t countOld 
= linesOld
.GetCount(); 
1047         // (2) replace text in the combined string 
1049         // (2a) leave the part before replaced area unchanged 
1050         wxString 
textNew(textOrig
, colStart
); 
1052         // these values will be used to refresh the changed area below 
1053         wxCoord widthNewText
, 
1054                 startNewText 
= GetTextWidth(textNew
); 
1055         if ( (size_t)colStart 
== linesOld
[lineStart
].length() ) 
1057             // text appended, refresh just enough to show the new text 
1058             widthNewText 
= GetTextWidth(text
.BeforeFirst(wxT('\n'))); 
1060         else // text inserted, refresh till the end of line 
1065         // (2b) insert new text 
1068         // (2c) and append the end of the old text 
1070         // adjust for index shift: to is relative to colStart, not 0 
1071         size_t toRel 
= (size_t)((to 
- from
) + colStart
); 
1072         if ( toRel 
< textOrig
.length() ) 
1074             textNew 
+= textOrig
.c_str() + toRel
; 
1077         // (3) break it into lines 
1079         wxArrayString lines
; 
1080         const wxChar 
*curLineStart 
= textNew
.c_str(); 
1081         for ( const wxChar 
*p 
= textNew
.c_str(); ; p
++ ) 
1083             // end of line/text? 
1084             if ( !*p 
|| *p 
== wxT('\n') ) 
1086                 lines
.Add(wxString(curLineStart
, p
)); 
1090                 curLineStart 
= p 
+ 1; 
1094 #ifdef WXDEBUG_TEXT_REPLACE 
1095         // (3a) all empty tokens should be counted as replacing with "foo" and 
1096         //      with "foo\n" should have different effects 
1097         wxArrayString lines2 
= wxStringTokenize(textNew
, wxT("\n"), 
1098                                                 wxTOKEN_RET_EMPTY_ALL
); 
1100         if ( lines2
.IsEmpty() ) 
1102             lines2
.Add(wxEmptyString
); 
1105         wxASSERT_MSG( lines
.GetCount() == lines2
.GetCount(), 
1106                       wxT("Replace() broken") ); 
1107         for ( size_t n 
= 0; n 
< lines
.GetCount(); n
++ ) 
1109             wxASSERT_MSG( lines
[n
] == lines2
[n
], wxT("Replace() broken") ); 
1111 #endif // WXDEBUG_TEXT_REPLACE 
1113         // (3b) special case: if we replace everything till the end we need to 
1114         //      keep an empty line or the lines would disappear completely 
1115         //      (this also takes care of never leaving m_lines empty) 
1116         if ( ((size_t)lineEnd 
== countOld 
- 1) && lines
.IsEmpty() ) 
1118             lines
.Add(wxEmptyString
); 
1121         size_t nReplaceCount 
= lines
.GetCount(), 
1124         // (4) merge into the array 
1127         for ( line 
= lineStart
; line 
<= lineEnd
; line
++, nReplaceLine
++ ) 
1129             if ( nReplaceLine 
< nReplaceCount 
) 
1131                 // we have the replacement line for this one 
1132                 if ( ReplaceLine(line
, lines
[nReplaceLine
]) ) 
1134                     rowsNumberChanged 
= true; 
1137                 UpdateMaxWidth(line
); 
1139             else // no more replacement lines 
1141                 // (4b) delete all extra lines (note that we need to delete 
1142                 //      them backwards because indices shift while we do it) 
1143                 bool deletedLongestLine 
= false; 
1144                 for ( wxTextCoord lineDel 
= lineEnd
; lineDel 
>= line
; lineDel
-- ) 
1146                     if ( lineDel 
== MData().m_lineLongest 
) 
1148                         // we will need to recalc the max line width 
1149                         deletedLongestLine 
= true; 
1152                     RemoveLine(lineDel
); 
1155                 if ( deletedLongestLine 
) 
1160                 // even the line number changed 
1161                 rowsNumberChanged 
= true; 
1163                 // update line to exit the loop 
1168         // (4c) insert the new lines 
1169         if ( nReplaceLine 
< nReplaceCount 
) 
1171             // even the line number changed 
1172             rowsNumberChanged 
= true; 
1176                 InsertLine(++lineEnd
, lines
[nReplaceLine
++]); 
1178                 UpdateMaxWidth(lineEnd
); 
1180             while ( nReplaceLine 
< nReplaceCount 
); 
1183         // (5) now refresh the changed area 
1185         // update the (cached) last position first as refresh functions use it 
1186         m_posLast 
+= text
.length() - to 
+ from
; 
1188         // we may optimize refresh if the number of rows didn't change - but if 
1189         // it did we have to refresh everything below the part we chanegd as 
1190         // well as it might have moved 
1191         if ( !rowsNumberChanged 
) 
1193             // refresh the line we changed 
1196                 RefreshPixelRange(lineStart
++, startNewText
, widthNewText
); 
1200                 //OPT: we shouldn't refresh the unchanged part of the line in 
1201                 //     this case, but instead just refresh the tail of it - the 
1202                 //     trouble is that we don't know here where does this tail 
1206             // number of rows didn't change, refresh the updated rows and the 
1208             if ( lineStart 
<= lineEnd 
) 
1209                 RefreshLineRange(lineStart
, lineEnd
); 
1211         else // rows number did change 
1215                 // refresh only part of the first line 
1216                 RefreshPixelRange(lineStart
++, startNewText
, widthNewText
); 
1218             //else: we have to refresh everything as some part of the text 
1219             //      could be in the previous row before but moved to the next 
1220             //      one now (due to word wrap) 
1222             wxTextCoord lineEnd 
= GetLines().GetCount() - 1; 
1223             if ( lineStart 
<= lineEnd 
) 
1224                 RefreshLineRange(lineStart
, lineEnd
); 
1226             // refresh text rect left below 
1227             RefreshLineRange(lineEnd 
+ 1, 0); 
1229             // the vert scrollbar might [dis]appear 
1230             MData().m_updateScrollbarY 
= true; 
1233         // must recalculate it - will do later 
1237 #ifdef WXDEBUG_TEXT_REPLACE 
1238     // optimized code above should give the same result as straightforward 
1239     // computation in the beginning 
1240     wxASSERT_MSG( GetValue() == textTotalNew
, wxT("error in Replace()") ); 
1241 #endif // WXDEBUG_TEXT_REPLACE 
1243     // update the current position: note that we always put the cursor at the 
1244     // end of the replacement text 
1245     DoSetInsertionPoint(from 
+ text
.length()); 
1247     // and the selection: this is complicated by the fact that selection coords 
1248     // must be first updated to reflect change in text coords, i.e. if we had 
1249     // selection from 17 to 19 and we just removed this range, we don't have to 
1250     // refresh anything, so we can't just use ClearSelection() here 
1251     if ( selStartOld 
!= -1 ) 
1253         // refresh the parst of the selection outside the changed text (which 
1254         // we already refreshed) 
1255         if ( selStartOld 
< from 
) 
1256             RefreshTextRange(selStartOld
, from
); 
1257         if ( to 
< selEndOld 
) 
1258             RefreshTextRange(to
, selEndOld
); 
1262     // now call it to do the rest (not related to refreshing) 
1265     SendTextUpdatedEventIfAllowed(); 
1268 void wxTextCtrl::Remove(wxTextPos from
, wxTextPos to
) 
1270     // Replace() only works with correctly ordered arguments, so exchange them 
1272     OrderPositions(from
, to
); 
1274     Replace(from
, to
, wxEmptyString
); 
1277 void wxTextCtrl::WriteText(const wxString
& text
) 
1279     // replace the selection with the new text 
1282     Replace(m_curPos
, m_curPos
, text
); 
1285 void wxTextCtrl::AppendText(const wxString
& text
) 
1287     SetInsertionPointEnd(); 
1291 // ---------------------------------------------------------------------------- 
1293 // ---------------------------------------------------------------------------- 
1295 void wxTextCtrl::SetInsertionPoint(wxTextPos pos
) 
1297     wxCHECK_RET( pos 
>= 0 && pos 
<= GetLastPosition(), 
1298                  wxT("insertion point position out of range") ); 
1300     // don't do anything if it didn't change 
1301     if ( pos 
!= m_curPos 
) 
1303         DoSetInsertionPoint(pos
); 
1306     if ( !IsSingleLine() ) 
1308         // moving cursor should reset the stored abscissa (even if the cursor 
1309         // position didn't actually change!) 
1310         MData().m_xCaret 
= -1; 
1316 void wxTextCtrl::InitInsertionPoint() 
1318     // so far always put it in the beginning 
1319     DoSetInsertionPoint(0); 
1321     // this will also set the selection anchor correctly 
1325 void wxTextCtrl::MoveInsertionPoint(wxTextPos pos
) 
1327     wxASSERT_MSG( pos 
>= 0 && pos 
<= GetLastPosition(), 
1328                  wxT("DoSetInsertionPoint() can only be called with valid pos") ); 
1331     PositionToXY(m_curPos
, &m_curCol
, &m_curRow
); 
1334 void wxTextCtrl::DoSetInsertionPoint(wxTextPos pos
) 
1336     MoveInsertionPoint(pos
); 
1341 void wxTextCtrl::SetInsertionPointEnd() 
1343     SetInsertionPoint(GetLastPosition()); 
1346 wxTextPos 
wxTextCtrl::GetInsertionPoint() const 
1351 wxTextPos 
wxTextCtrl::GetLastPosition() const 
1354     if ( IsSingleLine() ) 
1356         pos 
= m_value
.length(); 
1362         size_t nLineCount 
= GetLineCount(); 
1363         for ( size_t nLine 
= 0; nLine 
< nLineCount
; nLine
++ ) 
1365             // +1 is because the positions at the end of this line and of the 
1366             // start of the next one are different 
1367             pos 
+= GetLines()[nLine
].length() + 1; 
1372             // the last position is at the end of the last line, not in the 
1373             // beginning of the next line after it 
1377         // more probable reason of this would be to forget to update m_posLast 
1378         wxASSERT_MSG( pos 
== m_posLast
, wxT("bug in GetLastPosition()") ); 
1379 #endif // WXDEBUG_TEXT 
1387 // ---------------------------------------------------------------------------- 
1389 // ---------------------------------------------------------------------------- 
1391 void wxTextCtrl::GetSelection(wxTextPos
* from
, wxTextPos
* to
) const 
1399 wxString 
wxTextCtrl::GetSelectionText() const 
1403     if ( HasSelection() ) 
1405         if ( IsSingleLine() ) 
1407             sel 
= m_value
.Mid(m_selStart
, m_selEnd 
- m_selStart
); 
1411             wxTextCoord colStart
, lineStart
, 
1413             PositionToXY(m_selStart
, &colStart
, &lineStart
); 
1414             PositionToXY(m_selEnd
, &colEnd
, &lineEnd
); 
1416             // as always, we need to check for the special case when the start 
1417             // and end line are the same 
1418             if ( lineEnd 
== lineStart 
) 
1420                 sel 
= GetLines()[lineStart
].Mid(colStart
, colEnd 
- colStart
); 
1422             else // sel on multiple lines 
1424                 // take the end of the first line 
1425                 sel 
= GetLines()[lineStart
].c_str() + colStart
; 
1428                 // all intermediate ones 
1429                 for ( wxTextCoord line 
= lineStart 
+ 1; line 
< lineEnd
; line
++ ) 
1431                     sel 
<< GetLines()[line
] << wxT('\n'); 
1434                 // and the start of the last one 
1435                 sel 
+= GetLines()[lineEnd
].Left(colEnd
); 
1443 void wxTextCtrl::SetSelection(wxTextPos from
, wxTextPos to
) 
1445     // selecting till -1 is the same as selecting to the end 
1448         // and selecting (-1, -1) range is the same as selecting everything, by 
1452         to 
= GetLastPosition(); 
1455     if ( from 
== -1 || to 
== from 
) 
1459     else // valid sel range 
1461         // remember the 'to' position as the current position, used to move the 
1462         // caret there later 
1463         wxTextPos toOrig 
= to
; 
1465         OrderPositions(from
, to
); 
1467         wxCHECK_RET( to 
<= GetLastPosition(), 
1468                      wxT("invalid range in wxTextCtrl::SetSelection") ); 
1470         if ( from 
!= m_selStart 
|| to 
!= m_selEnd 
) 
1472             // we need to use temp vars as RefreshTextRange() may call DoDraw() 
1473             // directly and so m_selStart/End must be reset by then 
1474             wxTextPos selStartOld 
= m_selStart
, 
1475                       selEndOld 
= m_selEnd
; 
1480             wxLogTrace(wxT("text"), wxT("Selection range is %ld-%ld"), 
1481                        m_selStart
, m_selEnd
); 
1483             // refresh only the part of text which became (un)selected if 
1485             if ( selStartOld 
== m_selStart 
) 
1487                 RefreshTextRange(selEndOld
, m_selEnd
); 
1489             else if ( selEndOld 
== m_selEnd 
) 
1491                 RefreshTextRange(m_selStart
, selStartOld
); 
1495                 // OPT: could check for other cases too but it is probably not 
1496                 //      worth it as the two above are the most common ones 
1497                 if ( selStartOld 
!= -1 ) 
1498                     RefreshTextRange(selStartOld
, selEndOld
); 
1499                 if ( m_selStart 
!= -1 ) 
1500                     RefreshTextRange(m_selStart
, m_selEnd
); 
1503             // we need to fully repaint the invalidated areas of the window 
1504             // before scrolling it (from DoSetInsertionPoint which is typically 
1505             // called after SetSelection()), otherwise they may stay unpainted 
1506             m_targetWindow
->Update(); 
1508         //else: nothing to do 
1510         // the insertion point is put at the location where the caret was moved 
1511         DoSetInsertionPoint(toOrig
); 
1515 void wxTextCtrl::ClearSelection() 
1517     if ( HasSelection() ) 
1519         // we need to use temp vars as RefreshTextRange() may call DoDraw() 
1520         // directly (see above as well) 
1521         wxTextPos selStart 
= m_selStart
, 
1524         // no selection any more 
1528         // refresh the old selection 
1529         RefreshTextRange(selStart
, selEnd
); 
1532     // the anchor should be moved even if there was no selection previously 
1533     m_selAnchor 
= m_curPos
; 
1536 void wxTextCtrl::RemoveSelection() 
1538     if ( !HasSelection() ) 
1541     Remove(m_selStart
, m_selEnd
); 
1544 bool wxTextCtrl::GetSelectedPartOfLine(wxTextCoord line
, 
1545                                        wxTextPos 
*start
, wxTextPos 
*end
) const 
1552     if ( !HasSelection() ) 
1554         // no selection at all, hence no selection in this line 
1558     wxTextCoord lineStart
, colStart
; 
1559     PositionToXY(m_selStart
, &colStart
, &lineStart
); 
1560     if ( lineStart 
> line 
) 
1562         // this line is entirely above the selection 
1566     wxTextCoord lineEnd
, colEnd
; 
1567     PositionToXY(m_selEnd
, &colEnd
, &lineEnd
); 
1568     if ( lineEnd 
< line 
) 
1570         // this line is entirely below the selection 
1574     if ( line 
== lineStart 
) 
1579             *end 
= lineEnd 
== lineStart 
? colEnd 
: GetLineLength(line
); 
1581     else if ( line 
== lineEnd 
) 
1584             *start 
= lineEnd 
== lineStart 
? colStart 
: 0; 
1588     else // the line is entirely inside the selection 
1593             *end 
= GetLineLength(line
); 
1599 // ---------------------------------------------------------------------------- 
1601 // ---------------------------------------------------------------------------- 
1603 bool wxTextCtrl::IsModified() const 
1605     return m_isModified
; 
1608 bool wxTextCtrl::IsEditable() const 
1610     // disabled control can never be edited 
1611     return m_isEditable 
&& IsEnabled(); 
1614 void wxTextCtrl::MarkDirty() 
1616     m_isModified 
= true; 
1619 void wxTextCtrl::DiscardEdits() 
1621     m_isModified 
= false; 
1624 void wxTextCtrl::SetEditable(bool editable
) 
1626     if ( editable 
!= m_isEditable 
) 
1628         m_isEditable 
= editable
; 
1630         // the caret (dis)appears 
1633         // the appearance of the control might have changed 
1638 // ---------------------------------------------------------------------------- 
1639 // col/lines <-> position correspondence 
1640 // ---------------------------------------------------------------------------- 
1643     A few remarks about this stuff: 
1645     o   The numbering of the text control columns/rows starts from 0. 
1646     o   Start of first line is position 0, its last position is line.length() 
1647     o   Start of the next line is the last position of the previous line + 1 
1650 int wxTextCtrl::GetLineLength(wxTextCoord line
) const 
1652     if ( IsSingleLine() ) 
1654         wxASSERT_MSG( line 
== 0, wxT("invalid GetLineLength() parameter") ); 
1656         return m_value
.length(); 
1660         wxCHECK_MSG( (size_t)line 
< GetLineCount(), -1, 
1661                      wxT("line index out of range") ); 
1663         return GetLines()[line
].length(); 
1667 wxString 
wxTextCtrl::GetLineText(wxTextCoord line
) const 
1669     if ( IsSingleLine() ) 
1671         wxASSERT_MSG( line 
== 0, wxT("invalid GetLineLength() parameter") ); 
1677         //this is called during DoGetBestSize 
1678         if (line 
== 0 && GetLineCount() == 0) return wxEmptyString 
; 
1680         wxCHECK_MSG( (size_t)line 
< GetLineCount(), wxEmptyString
, 
1681                      wxT("line index out of range") ); 
1683         return GetLines()[line
]; 
1687 int wxTextCtrl::GetNumberOfLines() const 
1689     // there is always 1 line, even if the text is empty 
1690     return IsSingleLine() ? 1 : GetLineCount(); 
1693 wxTextPos 
wxTextCtrl::XYToPosition(wxTextCoord x
, wxTextCoord y
) const 
1695     // note that this method should accept any values of x and y and return -1 
1696     // if they are out of range 
1697     if ( IsSingleLine() ) 
1699         return ( x 
> GetLastPosition() || y 
> 0 ) ? wxOutOfRangeTextCoord 
: x
; 
1703         if ( (size_t)y 
>= GetLineCount() ) 
1705             // this position is below the text 
1706             return GetLastPosition(); 
1710         for ( size_t nLine 
= 0; nLine 
< (size_t)y
; nLine
++ ) 
1712             // +1 is because the positions at the end of this line and of the 
1713             // start of the next one are different 
1714             pos 
+= GetLines()[nLine
].length() + 1; 
1717         // take into account also the position in line 
1718         if ( (size_t)x 
> GetLines()[y
].length() ) 
1720             // don't return position in the next line 
1721             x 
= GetLines()[y
].length(); 
1728 bool wxTextCtrl::PositionToXY(wxTextPos pos
, 
1729                               wxTextCoord 
*x
, wxTextCoord 
*y
) const 
1731     if ( IsSingleLine() ) 
1733         if ( (size_t)pos 
> m_value
.length() ) 
1745         wxTextPos posCur 
= 0; 
1746         size_t nLineCount 
= GetLineCount(); 
1747         for ( size_t nLine 
= 0; nLine 
< nLineCount
; nLine
++ ) 
1749             // +1 is because the start the start of the next line is one 
1750             // position after the end of this one 
1751             wxTextPos posNew 
= posCur 
+ GetLines()[nLine
].length() + 1; 
1754                 // we've found the line, now just calc the column 
1762                 wxASSERT_MSG( XYToPosition(pos 
- posCur
, nLine
) == pos
, 
1763                               wxT("XYToPosition() or PositionToXY() broken") ); 
1764 #endif // WXDEBUG_TEXT 
1768             else // go further down 
1774         // beyond the last line 
1779 wxTextCoord 
wxTextCtrl::GetRowsPerLine(wxTextCoord line
) const 
1781     // a normal line has one row 
1782     wxTextCoord numRows 
= 1; 
1786         // add the number of additional rows 
1787         numRows 
+= WData().m_linesData
[line
].GetExtraRowCount(); 
1793 wxTextCoord 
wxTextCtrl::GetRowCount() const 
1795     wxTextCoord count 
= GetLineCount(); 
1800         count 
= GetFirstRowOfLine(count 
- 1) + 
1801                     WData().m_linesData
[count 
- 1].GetRowCount(); 
1807 wxTextCoord 
wxTextCtrl::GetRowAfterLine(wxTextCoord line
) const 
1812     if ( !WData().IsValidLine(line
) ) 
1817     return WData().m_linesData
[line
].GetNextRow(); 
1820 wxTextCoord 
wxTextCtrl::GetFirstRowOfLine(wxTextCoord line
) const 
1825     if ( !WData().IsValidLine(line
) ) 
1830     return WData().m_linesData
[line
].GetFirstRow(); 
1833 bool wxTextCtrl::PositionToLogicalXY(wxTextPos pos
, 
1835                                      wxCoord 
*yOut
) const 
1837     wxTextCoord col
, line
; 
1839     // optimization for special (but common) case when we already have the col 
1841     if ( pos 
== m_curPos 
) 
1846     else // must really calculate col/line from pos 
1848         if ( !PositionToXY(pos
, &col
, &line
) ) 
1852     int hLine 
= GetLineHeight(); 
1854     wxString textLine 
= GetLineText(line
); 
1855     if ( IsSingleLine() || !WrapLines() ) 
1857         x 
= GetTextWidth(textLine
.Left(col
)); 
1860     else // difficult case: multline control with line wrap 
1862         y 
= GetFirstRowOfLine(line
); 
1864         wxTextCoord colRowStart
; 
1865         y 
+= GetRowInLine(line
, col
, &colRowStart
); 
1869         // x is the width of the text before this position in this row 
1870         x 
= GetTextWidth(textLine
.Mid(colRowStart
, col 
- colRowStart
)); 
1881 bool wxTextCtrl::PositionToDeviceXY(wxTextPos pos
, 
1883                                     wxCoord 
*yOut
) const 
1886     if ( !PositionToLogicalXY(pos
, &x
, &y
) ) 
1889     // finally translate the logical text rect coords into physical client 
1891     CalcScrolledPosition(m_rectText
.x 
+ x
, m_rectText
.y 
+ y
, xOut
, yOut
); 
1896 wxPoint 
wxTextCtrl::GetCaretPosition() const 
1898     wxCoord xCaret
, yCaret
; 
1899     if ( !PositionToDeviceXY(m_curPos
, &xCaret
, &yCaret
) ) 
1901         wxFAIL_MSG( wxT("Caret can't be beyond the text!") ); 
1904     return wxPoint(xCaret
, yCaret
); 
1907 // pos may be -1 to show the current position 
1908 void wxTextCtrl::ShowPosition(wxTextPos pos
) 
1910     bool showCaret 
= GetCaret() && GetCaret()->IsVisible(); 
1914     if ( IsSingleLine() ) 
1916         ShowHorzPosition(GetTextWidth(m_value
.Left(pos
))); 
1918     else if ( MData().m_scrollRangeX 
|| MData().m_scrollRangeY 
) // multiline with scrollbars 
1921         GetViewStart(&xStart
, &yStart
); 
1927         PositionToLogicalXY(pos
, &x
, &y
); 
1929         wxRect rectText 
= GetRealTextArea(); 
1931         // scroll the position vertically into view: if it is currently above 
1932         // it, make it the first one, otherwise the last one 
1933         if ( MData().m_scrollRangeY 
) 
1935             y 
/= GetLineHeight(); 
1941             else // we are currently in or below the view area 
1943                 // find the last row currently shown 
1948                     // to find the last row we need to use the generic HitTest 
1951                     // OPT this is a bit silly: we undo this in HitTest(), so 
1952                     //     it would be better to factor out the common 
1953                     //     functionality into a separate function (OTOH it 
1954                     //     won't probably save us that much) 
1955                     wxPoint 
pt(0, rectText
.height 
- 1); 
1956                     pt 
+= GetClientAreaOrigin(); 
1957                     pt 
+= m_rectText
.GetPosition(); 
1958                     HitTest(pt
, &col
, &yEnd
); 
1960                     // find the row inside the line 
1961                     yEnd 
= GetFirstRowOfLine(yEnd
) + GetRowInLine(yEnd
, col
); 
1965                     // finding the last line is easy if each line has exactly 
1967                     yEnd 
= yStart 
+ rectText
.height 
/ GetLineHeight(); 
1972                     // scroll down: the current item should appear at the 
1973                     // bottom of the view 
1974                     Scroll(0, y 
- (yEnd 
- yStart
)); 
1979         // scroll the position horizontally into view 
1981         // we follow what I believe to be Windows behaviour here, that is if 
1982         // the position is already entirely in the view we do nothing, but if 
1983         // we do have to scroll the window to bring it into view, we scroll it 
1984         // not just enough to show the position but slightly more so that this 
1985         // position is at 1/3 of the window width from the closest border to it 
1986         // (I'm not sure that Windows does exactly this but it looks like this) 
1987         if ( MData().m_scrollRangeX 
) 
1989             // unlike for the rows, xStart doesn't correspond to the starting 
1990             // column as they all have different widths, so we need to 
1991             // translate everything to pixels 
1993             // we want the text between x and x2 be entirely inside the view 
1994             // (i.e. the current character) 
1996             // make xStart the first visible pixel (and not position) 
1997             int wChar 
= GetAverageWidth(); 
2002                 // we want the position of this column be 1/3 to the right of 
2004                 x 
-= rectText
.width 
/ 3; 
2007                 Scroll(x 
/ wChar
, y
); 
2009             else // maybe we're beyond the right border of the view? 
2011                 wxTextCoord col
, row
; 
2012                 if ( PositionToXY(pos
, &col
, &row
) ) 
2014                     wxString lineText 
= GetLineText(row
); 
2015                     wxCoord x2 
= x 
+ GetTextWidth(lineText
[(size_t)col
]); 
2016                     if ( x2 
> xStart 
+ rectText
.width 
) 
2018                         // we want the position of this column be 1/3 to the 
2019                         // left of the right edge, i.e. 2/3 right of the left 
2021                         x2 
-= (2*rectText
.width
)/3; 
2024                         Scroll(x2 
/ wChar
, row
); 
2030     //else: multiline but no scrollbars, hence nothing to do 
2036 // ---------------------------------------------------------------------------- 
2038 // ---------------------------------------------------------------------------- 
2041     TODO: we could have (easy to do) vi-like options for word movement, i.e. 
2042           distinguish between inlusive/exclusive words and between words and 
2043           WORDS (in vim sense) and also, finally, make the set of characters 
2044           which make up a word configurable - currently we use the exclusive 
2045           WORDS only (coincidentally, this is what Windows edit control does) 
2047           For future references, here is what vim help says: 
2049           A word consists of a sequence of letters, digits and underscores, or 
2050           a sequence of other non-blank characters, separated with white space 
2051           (spaces, tabs, <EOL>).  This can be changed with the 'iskeyword' 
2054           A WORD consists of a sequence of non-blank characters, separated with 
2055           white space.  An empty line is also considered to be a word and a 
2059 static inline bool IsWordChar(wxChar ch
) 
2061     return !wxIsspace(ch
); 
2064 wxTextPos 
wxTextCtrl::GetWordStart() const 
2066     if ( m_curPos 
== -1 || m_curPos 
== 0 ) 
2069     if ( m_curCol 
== 0 ) 
2071         // go to the end of the previous line 
2072         return m_curPos 
- 1; 
2075     // it shouldn't be possible to learn where the word starts in the password 
2080     // start at the previous position 
2081     const wxChar 
*p0 
= GetLineText(m_curRow
).c_str(); 
2082     const wxChar 
*p 
= p0 
+ m_curCol 
- 1; 
2084     // find the end of the previous word 
2085     while ( (p 
> p0
) && !IsWordChar(*p
) ) 
2088     // now find the beginning of this word 
2089     while ( (p 
> p0
) && IsWordChar(*p
) ) 
2092     // we might have gone too far 
2093     if ( !IsWordChar(*p
) ) 
2096     return (m_curPos 
- m_curCol
) + p 
- p0
; 
2099 wxTextPos 
wxTextCtrl::GetWordEnd() const 
2101     if ( m_curPos 
== -1 ) 
2104     wxString line 
= GetLineText(m_curRow
); 
2105     if ( (size_t)m_curCol 
== line
.length() ) 
2107         // if we're on the last position in the line, go to the next one - if 
2109         wxTextPos pos 
= m_curPos
; 
2110         if ( pos 
< GetLastPosition() ) 
2116     // it shouldn't be possible to learn where the word ends in the password 
2119         return GetLastPosition(); 
2121     // start at the current position 
2122     const wxChar 
*p0 
= line
.c_str(); 
2123     const wxChar 
*p 
= p0 
+ m_curCol
; 
2125     // find the start of the next word 
2126     while ( *p 
&& !IsWordChar(*p
) ) 
2129     // now find the end of it 
2130     while ( *p 
&& IsWordChar(*p
) ) 
2133     // and find the start of the next word 
2134     while ( *p 
&& !IsWordChar(*p
) ) 
2137     return (m_curPos 
- m_curCol
) + p 
- p0
; 
2140 // ---------------------------------------------------------------------------- 
2142 // ---------------------------------------------------------------------------- 
2144 void wxTextCtrl::Copy() 
2147     if ( HasSelection() ) 
2149         wxClipboardLocker clipLock
; 
2151         // wxTextFile::Translate() is needed to transform all '\n' into "\r\n" 
2152         wxString text 
= wxTextFile::Translate(GetTextToShow(GetSelectionText())); 
2153         wxTextDataObject 
*data 
= new wxTextDataObject(text
); 
2154         wxTheClipboard
->SetData(data
); 
2156 #endif // wxUSE_CLIPBOARD 
2159 void wxTextCtrl::Cut() 
2164 bool wxTextCtrl::DoCut() 
2166     if ( !HasSelection() ) 
2176 void wxTextCtrl::Paste() 
2181 bool wxTextCtrl::DoPaste() 
2184     wxClipboardLocker clipLock
; 
2186     wxTextDataObject data
; 
2187     if ( wxTheClipboard
->IsSupported(data
.GetFormat()) 
2188             && wxTheClipboard
->GetData(data
) ) 
2190         // reverse transformation: '\r\n\" -> '\n' 
2191         wxString text 
= wxTextFile::Translate(data
.GetText(), 
2192                                               wxTextFileType_Unix
); 
2193         if ( !text
.empty() ) 
2200 #endif // wxUSE_CLIPBOARD 
2205 // ---------------------------------------------------------------------------- 
2207 // ---------------------------------------------------------------------------- 
2209 wxTextCtrlInsertCommand 
* 
2210 wxTextCtrlCommandProcessor::IsInsertCommand(wxCommand 
*command
) 
2212     return (wxTextCtrlInsertCommand 
*) 
2213             (command 
&& (command
->GetName() == wxTEXT_COMMAND_INSERT
) 
2217 void wxTextCtrlCommandProcessor::Store(wxCommand 
*command
) 
2219     wxTextCtrlInsertCommand 
*cmdIns 
= IsInsertCommand(command
); 
2222         if ( IsCompressing() ) 
2224             wxTextCtrlInsertCommand 
* 
2225                 cmdInsLast 
= IsInsertCommand(GetCurrentCommand()); 
2227             // it is possible that we don't have any last command at all if, 
2228             // for example, it was undone since the last Store(), so deal with 
2232                 cmdInsLast
->Append(cmdIns
); 
2236                 // don't need to call the base class version 
2241         // append the following insert commands to this one 
2242         m_compressInserts 
= true; 
2244         // let the base class version will do the job normally 
2246     else // not an insert command 
2248         // stop compressing insert commands - this won't work with the last 
2249         // command not being an insert one anyhow 
2252         // let the base class version will do the job normally 
2255     wxCommandProcessor::Store(command
); 
2258 void wxTextCtrlInsertCommand::Append(wxTextCtrlInsertCommand 
*other
) 
2260     m_text 
+= other
->m_text
; 
2263 bool wxTextCtrlInsertCommand::CanUndo() const 
2265     return m_from 
!= -1; 
2268 bool wxTextCtrlInsertCommand::Do(wxTextCtrl 
*text
) 
2270     // the text is going to be inserted at the current position, remember where 
2272     m_from 
= text
->GetInsertionPoint(); 
2274     // and now do insert it 
2275     text
->WriteText(m_text
); 
2280 bool wxTextCtrlInsertCommand::Undo(wxTextCtrl 
*text
) 
2282     wxCHECK_MSG( CanUndo(), false, wxT("impossible to undo insert cmd") ); 
2284     // remove the text from where we inserted it 
2285     text
->Remove(m_from
, m_from 
+ m_text
.length()); 
2290 bool wxTextCtrlRemoveCommand::CanUndo() const 
2292     // if we were executed, we should have the text we removed 
2293     return !m_textDeleted
.empty(); 
2296 bool wxTextCtrlRemoveCommand::Do(wxTextCtrl 
*text
) 
2298     text
->SetSelection(m_from
, m_to
); 
2299     m_textDeleted 
= text
->GetSelectionText(); 
2300     text
->RemoveSelection(); 
2305 bool wxTextCtrlRemoveCommand::Undo(wxTextCtrl 
*text
) 
2307     // it is possible that the text was deleted and that we can't restore text 
2308     // at the same position we removed it any more 
2309     wxTextPos posLast 
= text
->GetLastPosition(); 
2310     text
->SetInsertionPoint(m_from 
> posLast 
? posLast 
: m_from
); 
2311     text
->WriteText(m_textDeleted
); 
2316 void wxTextCtrl::Undo() 
2318     // the caller must check it 
2319     wxASSERT_MSG( CanUndo(), wxT("can't call Undo() if !CanUndo()") ); 
2321     m_cmdProcessor
->Undo(); 
2324 void wxTextCtrl::Redo() 
2326     // the caller must check it 
2327     wxASSERT_MSG( CanRedo(), wxT("can't call Undo() if !CanUndo()") ); 
2329     m_cmdProcessor
->Redo(); 
2332 bool wxTextCtrl::CanUndo() const 
2334     return IsEditable() && m_cmdProcessor
->CanUndo(); 
2337 bool wxTextCtrl::CanRedo() const 
2339     return IsEditable() && m_cmdProcessor
->CanRedo(); 
2342 // ---------------------------------------------------------------------------- 
2344 // ---------------------------------------------------------------------------- 
2346 wxSize 
wxTextCtrl::DoGetBestClientSize() const 
2348     // when we're called for the very first time from Create() we must 
2349     // calculate the font metrics here because we can't do it before calling 
2350     // Create() (there is no window yet and wxGTK crashes) but we need them 
2352     if ( m_heightLine 
== -1 ) 
2354         wxConstCast(this, wxTextCtrl
)->RecalcFontMetrics(); 
2358     GetTextExtent(GetTextToShow(GetLineText(0)), &w
, &h
); 
2360     int wChar 
= GetAverageWidth(), 
2361         hChar 
= GetLineHeight(); 
2363     int widthMin 
= wxMax(10*wChar
, 100); 
2369     if ( !IsSingleLine() ) 
2371         // let the control have a reasonable number of lines 
2372         int lines 
= GetNumberOfLines(); 
2375         else if ( lines 
> 10 ) 
2382     rectText
.height 
= h
; 
2383     wxRect rectTotal 
= GetRenderer()->GetTextTotalArea(this, rectText
); 
2384     return wxSize(rectTotal
.width
, rectTotal
.height
); 
2387 void wxTextCtrl::UpdateTextRect() 
2389     wxRect 
rectTotal(GetClientSize()); 
2390     wxCoord 
*extraSpace 
= WrapLines() ? &WData().m_widthMark 
: NULL
; 
2391     m_rectText 
= GetRenderer()->GetTextClientArea(this, rectTotal
, extraSpace
); 
2393     // code elsewhere is confused by negative rect size 
2394     if ( m_rectText
.width 
<= 0 ) 
2395         m_rectText
.width 
= 1; 
2396     if ( m_rectText
.height 
<= 0 ) 
2397         m_rectText
.height 
= 1; 
2399     if ( !IsSingleLine() ) 
2401         // invalidate it so that GetRealTextArea() will recalc it 
2402         MData().m_rectTextReal
.width 
= 0; 
2404         // only scroll this rect when the window is scrolled: note that we have 
2405         // to scroll not only the text but the line wrap marks too if we show 
2407         wxRect rectText 
= GetRealTextArea(); 
2408         if ( extraSpace 
&& *extraSpace 
) 
2410             rectText
.width 
+= *extraSpace
; 
2412         SetTargetRect(rectText
); 
2414         // relayout all lines 
2417             WData().m_rowFirstInvalid 
= 0; 
2419             // increase timestamp: this means that the lines which had been 
2420             // laid out before will be relaid out the next time LayoutLines() 
2421             // is called because their timestamp will be smaller than the 
2423             WData().m_timestamp
++; 
2427     UpdateLastVisible(); 
2430 void wxTextCtrl::UpdateLastVisible() 
2432     // this method is only used for horizontal "scrollbarless" scrolling which 
2433     // is used only with single line controls 
2434     if ( !IsSingleLine() ) 
2437     // use (efficient) HitTestLine to find the last visible character 
2438     wxString text 
= m_value
.Mid((size_t)SData().m_colStart 
/* to the end */); 
2440     switch ( HitTestLine(text
, m_rectText
.width
, &col
) ) 
2442         case wxTE_HT_BEYOND
: 
2443             // everything is visible 
2444             SData().m_ofsHorz 
= 0; 
2446             SData().m_colStart 
= 0; 
2447             SData().m_colLastVisible 
= text
.length(); 
2449             // calculate it below 
2450             SData().m_posLastVisible 
= -1; 
2454         case wxTE_HT_BEFORE: 
2458             wxFAIL_MSG(wxT("unexpected HitTestLine() return value")); 
2461         case wxTE_HT_ON_TEXT
: 
2464                 // the last entirely seen character is the previous one because 
2465                 // this one is only partly visible - unless the width of the 
2466                 // string is exactly the max width 
2467                 SData().m_posLastVisible 
= GetTextWidth(text
.Truncate(col 
+ 1)); 
2468                 if ( SData().m_posLastVisible 
> m_rectText
.width 
) 
2470                     // this character is not entirely visible, take the 
2475                     SData().m_posLastVisible 
= -1; 
2477                 //else: we can just see it 
2479                 SData().m_colLastVisible 
= col
; 
2484     // calculate the width of the text really shown 
2485     if ( SData().m_posLastVisible 
== -1 ) 
2487         SData().m_posLastVisible 
= GetTextWidth(text
.Truncate(SData().m_colLastVisible 
+ 1)); 
2490     // current value is relative the start of the string text which starts at 
2491     // SData().m_colStart, we need an absolute offset into string 
2492     SData().m_colLastVisible 
+= SData().m_colStart
; 
2494     wxLogTrace(wxT("text"), wxT("Last visible column/position is %d/%ld"), 
2495                (int) SData().m_colLastVisible
, (long) SData().m_posLastVisible
); 
2498 void wxTextCtrl::OnSize(wxSizeEvent
& event
) 
2502     if ( !IsSingleLine() ) 
2505         // update them immediately because if we are called for the first time, 
2506         // we need to create them in order for the base class version to 
2507         // position the scrollbars correctly - if we don't do it now, it won't 
2508         // happen at all if we don't get more size events 
2512         MData().m_updateScrollbarX 
= 
2513         MData().m_updateScrollbarY 
= true; 
2519 wxCoord 
wxTextCtrl::GetTotalWidth() const 
2522     CalcUnscrolledPosition(m_rectText
.width
, 0, &w
, NULL
); 
2526 wxCoord 
wxTextCtrl::GetTextWidth(const wxString
& text
) const 
2529     GetTextExtent(GetTextToShow(text
), &w
, NULL
); 
2533 wxRect 
wxTextCtrl::GetRealTextArea() const 
2535     // for single line text control it's just the same as text rect 
2536     if ( IsSingleLine() ) 
2539     // the real text area always holds an entire number of lines, so the only 
2540     // difference with the text area is a narrow strip along the bottom border 
2541     wxRect rectText 
= MData().m_rectTextReal
; 
2542     if ( !rectText
.width 
) 
2545         rectText 
= m_rectText
; 
2547         // when we're called for the very first time, the line height might not 
2548         // had been calculated yet, so do get it now 
2549         wxTextCtrl 
*self 
= wxConstCast(this, wxTextCtrl
); 
2550         self
->RecalcFontMetrics(); 
2552         int hLine 
= GetLineHeight(); 
2553         rectText
.height 
= (m_rectText
.height 
/ hLine
) * hLine
; 
2556         self
->MData().m_rectTextReal 
= rectText
; 
2562 wxTextCoord 
wxTextCtrl::GetRowInLine(wxTextCoord line
, 
2564                                      wxTextCoord 
*colRowStart
) const 
2566     wxASSERT_MSG( WrapLines(), wxT("shouldn't be called") ); 
2568     const wxWrappedLineData
& lineData 
= WData().m_linesData
[line
]; 
2570     if ( !WData().IsValidLine(line
) ) 
2573     // row is here counted a bit specially: 0 is the 2nd row of the line (1st 
2576            rowMax 
= lineData
.GetExtraRowCount(); 
2580         while ( (row 
< rowMax
) && (col 
>= lineData
.GetExtraRowStart(row
)) ) 
2583         // it's ok here that row is 1 greater than needed: like this, it is 
2584         // counted as a normal (and not extra) row 
2586     //else: only one row anyhow 
2590         // +1 because we need a real row number, not the extra row one 
2591         *colRowStart 
= lineData
.GetRowStart(row
); 
2593         // this can't happen, of course 
2594         wxASSERT_MSG( *colRowStart 
<= col
, wxT("GetRowInLine() is broken") ); 
2600 void wxTextCtrl::LayoutLine(wxTextCoord line
, wxWrappedLineData
& lineData
) const 
2602     // FIXME: this uses old GetPartOfWrappedLine() which is not used anywhere 
2603     //        else now and has rather awkward interface for our needs here 
2605     lineData
.m_rowsStart
.Empty(); 
2606     lineData
.m_rowsWidth
.Empty(); 
2608     const wxString
& text 
= GetLineText(line
); 
2610     size_t colRowStart 
= 0; 
2613         size_t lenRow 
= GetPartOfWrappedLine
 
2615                             text
.c_str() + colRowStart
, 
2619         // remember the start of this row (not for the first one as 
2620         // it's always 0) and its width 
2622             lineData
.m_rowsStart
.Add(colRowStart
); 
2623         lineData
.m_rowsWidth
.Add(widthRow
); 
2625         colRowStart 
+= lenRow
; 
2627     while ( colRowStart 
< text
.length() ); 
2629     // put the current timestamp on it 
2630     lineData
.m_timestamp 
= WData().m_timestamp
; 
2633 void wxTextCtrl::LayoutLines(wxTextCoord lineLast
) const 
2635     wxASSERT_MSG( WrapLines(), wxT("should only be used for line wrapping") ); 
2637     // if we were called, some line was dirty and if it was dirty we must have 
2638     // had m_rowFirstInvalid set to something too 
2639     wxTextCoord lineFirst 
= WData().m_rowFirstInvalid
; 
2640     wxASSERT_MSG( lineFirst 
!= -1, wxT("nothing to layout?") ); 
2642     wxTextCoord rowFirst
, rowCur
; 
2645         // start after the last known valid line 
2646         const wxWrappedLineData
& lineData 
= WData().m_linesData
[lineFirst 
- 1]; 
2647         rowFirst 
= lineData
.GetFirstRow() + lineData
.GetRowCount(); 
2649     else // no valid lines, start at row 0 
2655     for ( wxTextCoord line 
= lineFirst
; line 
<= lineLast
; line
++ ) 
2657         // set the starting row for this line 
2658         wxWrappedLineData
& lineData 
= WData().m_linesData
[line
]; 
2659         lineData
.m_rowFirst 
= rowCur
; 
2661         // had the line been already broken into rows? 
2663         // if so, compare its timestamp with the current one: if nothing has 
2664         // been changed, don't relayout it 
2665         if ( !lineData
.IsValid() || 
2666                 (lineData
.m_timestamp 
< WData().m_timestamp
) ) 
2668             // now do break it in rows 
2669             LayoutLine(line
, lineData
); 
2672         rowCur 
+= lineData
.GetRowCount(); 
2675     // we are now valid at least up to this line, but if it is the last one we 
2676     // just don't have any more invalid rows at all 
2677     if ( (size_t)lineLast 
== WData().m_linesData
.GetCount() -1 ) 
2682     wxTextCtrl 
*self 
= wxConstCast(this, wxTextCtrl
); 
2683     self
->WData().m_rowFirstInvalid 
= lineLast
; 
2685     // also refresh the line end indicators (FIXME shouldn't do it always!) 
2686     self
->RefreshLineWrapMarks(rowFirst
, rowCur
); 
2689 size_t wxTextCtrl::GetPartOfWrappedLine(const wxChar
* text
, 
2690                                         wxCoord 
*widthReal
) const 
2692     // this function is slow, it shouldn't be called unless really needed 
2693     wxASSERT_MSG( WrapLines(), wxT("shouldn't be called") ); 
2697     wxCoord wReal 
= wxDefaultCoord
; 
2698     switch ( HitTestLine(s
, m_rectText
.width
, &col
) ) 
2701         case wxTE_HT_BEFORE: 
2705             wxFAIL_MSG(wxT("unexpected HitTestLine() return value")); 
2708         case wxTE_HT_ON_TEXT
: 
2711                 // the last entirely seen character is the previous one because 
2712                 // this one is only partly visible - unless the width of the 
2713                 // string is exactly the max width 
2714                 wReal 
= GetTextWidth(s
.Truncate(col 
+ 1)); 
2715                 if ( wReal 
> m_rectText
.width 
) 
2717                     // this character is not entirely visible, take the 
2722                     wReal 
= wxDefaultCoord
; 
2724                 //else: we can just see it 
2726                 // wrap at any character or only at words boundaries? 
2727                 if ( !(GetWindowStyle() & wxTE_CHARWRAP
) ) 
2729                     // find the (last) not word char before this word 
2730                     wxTextCoord colWordStart
; 
2731                     for ( colWordStart 
= col
; 
2732                           colWordStart 
&& IsWordChar(s
[(size_t)colWordStart
]); 
2736                     if ( colWordStart 
> 0 ) 
2738                         if ( colWordStart 
!= col 
) 
2740                             // will have to recalc the real width 
2741                             wReal 
= wxDefaultCoord
; 
2746                     //else: only a single word, have to wrap it here 
2751         case wxTE_HT_BEYOND
: 
2755     // we return the number of characters, not the index of the last one 
2756     if ( (size_t)col 
< s
.length() ) 
2758         // but don't return more than this (empty) string has 
2764         if ( wReal 
== wxDefaultCoord 
) 
2766             // calc it if not done yet 
2767             wReal 
= GetTextWidth(s
.Truncate(col
)); 
2773     // VZ: old, horribly inefficient code which can still be used for checking 
2774     //     the result (in line, not word, wrap mode only) - to be removed later 
2776     wxTextCtrl 
*self 
= wxConstCast(this, wxTextCtrl
); 
2777     wxClientDC 
dc(self
); 
2778     dc
.SetFont(GetFont()); 
2779     self
->DoPrepareDC(dc
); 
2781     wxCoord widthMax 
= m_rectText
.width
; 
2783     // the text which we can keep in this ROW 
2786     for ( wOld 
= w 
= 0; *text 
&& (w 
<= widthMax
); ) 
2790         dc
.GetTextExtent(str
, &w
, NULL
); 
2795         // if we wrapped, the last letter was one too much 
2796         if ( str
.length() > 1 ) 
2799             str
.erase(str
.length() - 1, 1); 
2801         else // but always keep at least one letter in each row 
2803             // the real width then is the last value of w and not teh one 
2808     else // we didn't wrap 
2813     wxASSERT( col 
== str
.length() ); 
2817         wxASSERT( *widthReal 
== wOld 
); 
2822     //return str.length(); 
2828 // OPT: this function is called a lot - would be nice to optimize it but I 
2829 //      don't really know how yet 
2830 wxTextCtrlHitTestResult 
wxTextCtrl::HitTestLine(const wxString
& line
, 
2832                                                 wxTextCoord 
*colOut
) const 
2834     wxTextCtrlHitTestResult res 
= wxTE_HT_ON_TEXT
; 
2837     wxTextCtrl 
*self 
= wxConstCast(this, wxTextCtrl
); 
2838     wxClientDC 
dc(self
); 
2839     dc
.SetFont(GetFont()); 
2840     self
->DoPrepareDC(dc
); 
2843     dc
.GetTextExtent(line
, &width
, NULL
); 
2846         // clicking beyond the end of line is equivalent to clicking at 
2847         // the end of it, so return the last line column 
2848         col 
= line
.length(); 
2851             // unless the line is empty and so doesn't have any column at all - 
2852             // in this case return 0, what else can we do? 
2856         res 
= wxTE_HT_BEYOND
; 
2862         res 
= wxTE_HT_BEFORE
; 
2864     else // we're inside the line 
2866         // now calculate the column: first, approximate it with fixed-width 
2867         // value and then calculate the correct value iteratively: note that 
2868         // we use the first character of the line instead of (average) 
2869         // GetCharWidth(): it is common to have lines of dashes, for example, 
2870         // and this should give us much better approximation in such case 
2872         // OPT: maybe using (cache) m_widthAvg would be still faster? profile! 
2873         dc
.GetTextExtent(line
[0], &width
, NULL
); 
2880         else if ( (size_t)col 
> line
.length() ) 
2882             col 
= line
.length(); 
2885         // matchDir is the direction in which we should move to reach the 
2886         // character containing the given position 
2892         } matchDir 
= Match_None
; 
2895             // check that we didn't go beyond the line boundary 
2901             if ( (size_t)col 
> line
.length() ) 
2903                 col 
= line
.length(); 
2907             wxString 
strBefore(line
, (size_t)col
); 
2908             dc
.GetTextExtent(strBefore
, &width
, NULL
); 
2911                 if ( matchDir 
== Match_Right 
) 
2913                     // we were going to the right and, finally, moved beyond 
2914                     // the original position - stop on the previous one 
2920                 if ( matchDir 
== Match_None 
) 
2922                     // we just started iterating, now we know that we should 
2924                     matchDir 
= Match_Left
; 
2926                 //else: we are still to the right of the target, continue 
2930                 // invert the logic above 
2931                 if ( matchDir 
== Match_Left 
) 
2933                     // with the exception that we don't need to backtrack here 
2937                 if ( matchDir 
== Match_None 
) 
2940                     matchDir 
= Match_Right
; 
2944             // this is not supposed to happen 
2945             wxASSERT_MSG( matchDir
, wxT("logic error in wxTextCtrl::HitTest") ); 
2947             if ( matchDir 
== Match_Right 
) 
2954     // check that we calculated it correctly 
2956     if ( res 
== wxTE_HT_ON_TEXT 
) 
2959         wxString text 
= line
.Left(col
); 
2960         dc
.GetTextExtent(text
, &width1
, NULL
); 
2961         if ( (size_t)col 
< line
.length() ) 
2966             dc
.GetTextExtent(text
, &width2
, NULL
); 
2968             wxASSERT_MSG( (width1 
<= x
) && (x 
< width2
), 
2969                           wxT("incorrect HitTestLine() result") ); 
2971         else // we return last char 
2973             wxASSERT_MSG( x 
>= width1
, wxT("incorrect HitTestLine() result") ); 
2976 #endif // WXDEBUG_TEXT 
2984 wxTextCtrlHitTestResult 
wxTextCtrl::HitTest(const wxPoint
& pt
, long *pos
) const 
2987     wxTextCtrlHitTestResult rc 
= HitTest(pt
, &x
, &y
); 
2988     if ( rc 
!= wxTE_HT_UNKNOWN 
&& pos 
) 
2990         *pos 
= XYToPosition(x
, y
); 
2996 wxTextCtrlHitTestResult 
wxTextCtrl::HitTest(const wxPoint
& pos
, 
2997                                             wxTextCoord 
*colOut
, 
2998                                             wxTextCoord 
*rowOut
) const 
3000     return HitTest2(pos
.y
, pos
.x
, 0, rowOut
, colOut
, NULL
, NULL
); 
3003 wxTextCtrlHitTestResult 
wxTextCtrl::HitTestLogical(const wxPoint
& pos
, 
3004                                                    wxTextCoord 
*colOut
, 
3005                                                    wxTextCoord 
*rowOut
) const 
3007     return HitTest2(pos
.y
, pos
.x
, 0, rowOut
, colOut
, NULL
, NULL
, false); 
3010 wxTextCtrlHitTestResult 
wxTextCtrl::HitTest2(wxCoord y0
, 
3013                                              wxTextCoord 
*rowOut
, 
3014                                              wxTextCoord 
*colStart
, 
3015                                              wxTextCoord 
*colEnd
, 
3016                                              wxTextCoord 
*colRowStartOut
, 
3017                                              bool deviceCoords
) const 
3019     // is the point in the text area or to the right or below it? 
3020     wxTextCtrlHitTestResult res 
= wxTE_HT_ON_TEXT
; 
3022     // translate the window coords x0 and y0 into the client coords in the text 
3023     // area by adjusting for both the client and text area offsets (unless this 
3024     // was already done) 
3028         wxPoint pt 
= GetClientAreaOrigin() + m_rectText
.GetPosition(); 
3029         CalcUnscrolledPosition(x10 
- pt
.x
, y0 
- pt
.y
, &x1
, &y
); 
3037     // calculate the row (it is really a LINE, not a ROW) 
3040     // these vars are used only for WrapLines() case 
3041     wxTextCoord colRowStart 
= 0; 
3044     if ( colRowStartOut 
) 
3045         *colRowStartOut 
= 0; 
3047     int hLine 
= GetLineHeight(); 
3050         // and clicking before it is the same as clicking on the first one 
3053         res 
= wxTE_HT_BEFORE
; 
3057         wxTextCoord rowLast 
= GetNumberOfLines() - 1; 
3059         if ( IsSingleLine() || !WrapLines() ) 
3061             // in this case row calculation is simple as all lines have the 
3062             // same height and so row is the same as line 
3063             if ( row 
> rowLast 
) 
3065                 // clicking below the text is the same as clicking on the last 
3069                 res 
= wxTE_HT_BELOW
; 
3072         else // multline control with line wrap 
3074             // use binary search to find the line containing this row 
3075             const wxArrayWrappedLinesData
& linesData 
= WData().m_linesData
; 
3077                    hi 
= linesData
.GetCount(), 
3082                 const wxWrappedLineData
& lineData 
= linesData
[cur
]; 
3083                 if ( !WData().IsValidLine(cur
) ) 
3085                 wxTextCoord rowFirst 
= lineData
.GetFirstRow(); 
3087                 if ( row 
< rowFirst 
) 
3093                     // our row is after the first row of the cur line: 
3094                     // obviously, if cur is the last line, it contains this 
3095                     // row, otherwise we have to test that it is before the 
3096                     // first row of the next line 
3097                     bool found 
= cur 
== linesData
.GetCount() - 1; 
3100                         // if the row is beyond the end of text, adjust it to 
3101                         // be the last one and set res accordingly 
3102                         if ( (size_t)(row 
- rowFirst
) >= lineData
.GetRowCount() ) 
3104                             res 
= wxTE_HT_BELOW
; 
3106                             row 
= lineData
.GetRowCount() + rowFirst 
- 1; 
3109                     else // not the last row 
3111                         const wxWrappedLineData
& 
3112                                 lineNextData 
= linesData
[cur 
+ 1]; 
3113                         if ( !WData().IsValidLine(cur 
+ 1) ) 
3114                             LayoutLines(cur 
+ 1); 
3115                         found 
= row 
< lineNextData
.GetFirstRow(); 
3120                         colRowStart 
= lineData
.GetRowStart(row 
- rowFirst
); 
3121                         rowLen 
= lineData
.GetRowLength(row 
- rowFirst
, 
3122                                                        GetLines()[cur
].length()); 
3136     if ( res 
== wxTE_HT_ON_TEXT 
) 
3138         // now find the position in the line 
3139         wxString lineText 
= GetLineText(row
), 
3142         if ( colRowStart 
|| rowLen 
) 
3144             // look in this row only, not in whole line 
3145             rowText 
= lineText
.Mid(colRowStart
, rowLen
); 
3149             // just take the whole string 
3155             res 
= HitTestLine(GetTextToShow(rowText
), x1
, colStart
); 
3159                 if ( colRowStartOut 
) 
3161                     // give them the column offset in this ROW in pixels 
3162                     *colRowStartOut 
= colRowStart
; 
3165                 // take into account that the ROW doesn't start in the 
3166                 // beginning of the LINE 
3167                 *colStart 
+= colRowStart
; 
3172                 // the hit test result we return is for x1, so throw out 
3173                 // the result for x2 here 
3174                 int x2 
= x1 
+ x20 
- x10
; 
3175                 (void)HitTestLine(GetTextToShow(rowText
), x2
, colEnd
); 
3177                 *colEnd 
+= colRowStart
; 
3181     else // before/after vertical text span 
3185             // fill the column with the first/last position in the 
3186             // corresponding line 
3187             if ( res 
== wxTE_HT_BEFORE 
) 
3189             else // res == wxTE_HT_BELOW 
3190                 *colStart 
= GetLineText(GetNumberOfLines() - 1).length(); 
3196         // give them the row in text coords (as is) 
3203 bool wxTextCtrl::GetLineAndRow(wxTextCoord row
, 
3204                                wxTextCoord 
*lineOut
, 
3205                                wxTextCoord 
*rowInLineOut
) const 
3213     int nLines 
= GetNumberOfLines(); 
3216         const wxArrayWrappedLinesData
& linesData 
= WData().m_linesData
; 
3217         for ( line 
= 0; line 
< nLines
; line
++ ) 
3219             if ( !WData().IsValidLine(line
) ) 
3222             if ( row 
< linesData
[line
].GetNextRow() ) 
3224                 // we found the right line 
3225                 rowInLine 
= row 
- linesData
[line
].GetFirstRow(); 
3231         if ( line 
== nLines 
) 
3233             // the row is out of range 
3237     else // no line wrapping, everything is easy 
3239         if ( row 
>= nLines 
) 
3248         *rowInLineOut 
= rowInLine
; 
3253 // ---------------------------------------------------------------------------- 
3255 // ---------------------------------------------------------------------------- 
3258    wxTextCtrl has not one but two scrolling mechanisms: one is semi-automatic 
3259    scrolling in both horizontal and vertical direction implemented using 
3260    wxScrollHelper and the second one is manual scrolling implemented using 
3261    SData().m_ofsHorz and used by the single line controls without scroll bar. 
3263    The first version (the standard one) always scrolls by fixed amount which is 
3264    fine for vertical scrolling as all lines have the same height but is rather 
3265    ugly for horizontal scrolling if proportional font is used. This is why we 
3266    manually update and use SData().m_ofsHorz which contains the length of the string 
3267    which is hidden beyond the left border. An important property of text 
3268    controls using this kind of scrolling is that an entire number of characters 
3269    is always shown and that parts of characters never appear on display - 
3270    neither in the leftmost nor rightmost positions. 
3272    Once again, for multi line controls SData().m_ofsHorz is always 0 and scrolling is 
3273    done as usual for wxScrollWindow. 
3276 void wxTextCtrl::ShowHorzPosition(wxCoord pos
) 
3278     wxASSERT_MSG( IsSingleLine(), wxT("doesn't work for multiline") ); 
3280     // pos is the logical position to show 
3282     // SData().m_ofsHorz is the first logical position shown 
3283     if ( pos 
< SData().m_ofsHorz 
) 
3287         HitTestLine(m_value
, pos
, &col
); 
3292         wxCoord width 
= m_rectText
.width
; 
3295             // if we are called from the ctor, m_rectText is not initialized 
3296             // yet, so do it now 
3298             width 
= m_rectText
.width
; 
3301         // SData().m_ofsHorz + width is the last logical position shown 
3302         if ( pos 
> SData().m_ofsHorz 
+ width
) 
3306             HitTestLine(m_value
, pos 
- width
, &col
); 
3307             ScrollText(col 
+ 1); 
3312 // scroll the window horizontally so that the first visible character becomes 
3313 // the one at this position 
3314 void wxTextCtrl::ScrollText(wxTextCoord col
) 
3316     wxASSERT_MSG( IsSingleLine(), 
3317                   wxT("ScrollText() is for single line controls only") ); 
3319     // never scroll beyond the left border 
3323     // OPT: could only get the extent of the part of the string between col 
3324     //      and SData().m_colStart 
3325     wxCoord ofsHorz 
= GetTextWidth(GetLineText(0).Left(col
)); 
3327     if ( ofsHorz 
!= SData().m_ofsHorz 
) 
3329         // remember the last currently used pixel 
3330         int posLastVisible 
= SData().m_posLastVisible
; 
3331         if ( posLastVisible 
== -1 ) 
3333             // this may happen when we're called very early, during the 
3334             // controls construction 
3335             UpdateLastVisible(); 
3337             posLastVisible 
= SData().m_posLastVisible
; 
3340         // NB1: to scroll to the right, offset must be negative, hence the 
3341         //      order of operands 
3342         int dx 
= SData().m_ofsHorz 
- ofsHorz
; 
3344         // NB2: we call Refresh() below which results in a call to 
3345         //      DoDraw(), so we must update SData().m_ofsHorz before calling it 
3346         SData().m_ofsHorz 
= ofsHorz
; 
3347         SData().m_colStart 
= col
; 
3349         // after changing m_colStart, recalc the last visible position: we need 
3350         // to recalc the last visible position beore scrolling in order to make 
3351         // it appear exactly at the right edge of the text area after scrolling 
3352         UpdateLastVisible(); 
3357             // we want to force the update of it after scrolling 
3358             SData().m_colLastVisible 
= -1; 
3362         // scroll only the rectangle inside which there is the text 
3363         wxRect rect 
= m_rectText
; 
3364         rect
.width 
= posLastVisible
; 
3366         rect 
= ScrollNoRefresh(dx
, 0, &rect
); 
3369            we need to manually refresh the part which ScrollWindow() doesn't 
3370            refresh (with new API this means the part outside the rect returned 
3371            by ScrollNoRefresh): indeed, if we had this: 
3375            where '*' is text and 'o' is blank area at the end (too small to 
3376            hold the next char) then after scrolling by 2 positions to the left 
3381            where 'R' is the area refreshed by ScrollWindow() - but we still 
3382            need to refresh the 'o' at the end as it may be now big enough to 
3383            hold the new character shifted into view. 
3385            when we are scrolling to the right, we need to update this rect as 
3386            well because it might have contained something before but doesn't 
3387            contain anything any more 
3390         // we can combine both rectangles into one when scrolling to the left, 
3391         // but we need two separate Refreshes() otherwise 
3394             // refresh the uncovered part on the left 
3395             Refresh(true, &rect
); 
3397             // and now the area on the right 
3398             rect
.x 
= m_rectText
.x 
+ posLastVisible
; 
3399             rect
.width 
= m_rectText
.width 
- posLastVisible
; 
3401         else // scrolling to the left 
3403             // just extend the rect covering the uncovered area to the edge of 
3405             rect
.width 
+= m_rectText
.width 
- posLastVisible
; 
3408         Refresh(true, &rect
); 
3410         // I don't know exactly why is this needed here but without it we may 
3411         // scroll the window again (from the same method) before the previously 
3412         // invalidated area is repainted when typing *very* quickly - and this 
3413         // may lead to the display corruption 
3418 void wxTextCtrl::CalcUnscrolledPosition(int x
, int y
, int *xx
, int *yy
) const 
3420     if ( IsSingleLine() ) 
3422         // we don't use wxScrollHelper 
3424             *xx 
= x 
+ SData().m_ofsHorz
; 
3430         // let the base class do it 
3431         wxScrollHelper::CalcUnscrolledPosition(x
, y
, xx
, yy
); 
3435 void wxTextCtrl::CalcScrolledPosition(int x
, int y
, int *xx
, int *yy
) const 
3437     if ( IsSingleLine() ) 
3439         // we don't use wxScrollHelper 
3441             *xx 
= x 
- SData().m_ofsHorz
; 
3447         // let the base class do it 
3448         wxScrollHelper::CalcScrolledPosition(x
, y
, xx
, yy
); 
3452 void wxTextCtrl::DoPrepareDC(wxDC
& dc
) 
3454     // for single line controls we only have to deal with SData().m_ofsHorz and it's 
3455     // useless to call base class version as they don't use normal scrolling 
3456     if ( IsSingleLine() && SData().m_ofsHorz 
) 
3458         // adjust the DC origin if the text is shifted 
3459         wxPoint pt 
= dc
.GetDeviceOrigin(); 
3460         dc
.SetDeviceOrigin(pt
.x 
- SData().m_ofsHorz
, pt
.y
); 
3464         wxScrollHelper::DoPrepareDC(dc
); 
3468 void wxTextCtrl::UpdateMaxWidth(wxTextCoord line
) 
3472     // check if the max width changes after this line was modified 
3473     wxCoord widthMaxOld 
= MData().m_widthMax
, 
3475     GetTextExtent(GetLineText(line
), &width
, NULL
); 
3477     if ( line 
== MData().m_lineLongest 
) 
3479         // this line was the longest one, is it still? 
3480         if ( width 
> MData().m_widthMax 
) 
3482             MData().m_widthMax 
= width
; 
3484         else if ( width 
< MData().m_widthMax 
) 
3486             // we need to find the new longest line 
3489         //else: its length didn't change, nothing to do 
3491     else // it wasn't the longest line, but maybe it became it? 
3493         // GetMaxWidth() and not MData().m_widthMax as it might be not calculated yet 
3494         if ( width 
> GetMaxWidth() ) 
3496             MData().m_widthMax 
= width
; 
3497             MData().m_lineLongest 
= line
; 
3501     MData().m_updateScrollbarX 
= MData().m_widthMax 
!= widthMaxOld
; 
3504 void wxTextCtrl::RecalcFontMetrics() 
3506     m_heightLine 
= GetCharHeight(); 
3507     m_widthAvg 
= GetCharWidth(); 
3510 void wxTextCtrl::RecalcMaxWidth() 
3512     wxASSERT_MSG( !IsSingleLine(), wxT("only used for multiline") ); 
3514     MData().m_widthMax 
= -1; 
3515     (void)GetMaxWidth(); 
3518 wxCoord 
wxTextCtrl::GetMaxWidth() const 
3520     if ( MData().m_widthMax 
== -1 ) 
3524         // OPT: should we remember the widths of all the lines? 
3526         wxTextCtrl 
*self 
= wxConstCast(this, wxTextCtrl
); 
3527         wxClientDC 
dc(self
); 
3528         dc
.SetFont(GetFont()); 
3530         self
->MData().m_widthMax 
= 0; 
3532         size_t count 
= GetLineCount(); 
3533         for ( size_t n 
= 0; n 
< count
; n
++ ) 
3536             dc
.GetTextExtent(GetLines()[n
], &width
, NULL
); 
3537             if ( width 
> MData().m_widthMax 
) 
3539                 // remember the width and the line which has it 
3540                 self
->MData().m_widthMax 
= width
; 
3541                 self
->MData().m_lineLongest 
= n
; 
3546     wxASSERT_MSG( MData().m_widthMax 
!= -1, wxT("should have at least 1 line") ); 
3548     return MData().m_widthMax
; 
3551 void wxTextCtrl::UpdateScrollbars() 
3553     wxASSERT_MSG( !IsSingleLine(), wxT("only used for multiline") ); 
3555     wxSize size 
= GetRealTextArea().GetSize(); 
3557     // is our height enough to show all items? 
3558     wxTextCoord nRows 
= GetRowCount(); 
3559     wxCoord lineHeight 
= GetLineHeight(); 
3560     bool showScrollbarY 
= nRows
*lineHeight 
> size
.y
; 
3562     // is our width enough to show the longest line? 
3563     wxCoord charWidth
, maxWidth
; 
3564     bool showScrollbarX
; 
3567         charWidth 
= GetAverageWidth(); 
3568         maxWidth 
= GetMaxWidth(); 
3569         showScrollbarX 
= maxWidth 
> size
.x
; 
3571     else // never show the horz scrollbar 
3573         // just to suppress compiler warnings about using uninit vars below 
3574         charWidth 
= maxWidth 
= 0; 
3576         showScrollbarX 
= false; 
3579     // calc the scrollbars ranges 
3580     int scrollRangeX 
= showScrollbarX
 
3581                         ? (maxWidth 
+ 2*charWidth 
- 1) / charWidth
 
3583     int scrollRangeY 
= showScrollbarY 
? nRows 
: 0; 
3585     int scrollRangeXOld 
= MData().m_scrollRangeX
, 
3586         scrollRangeYOld 
= MData().m_scrollRangeY
; 
3587     if ( (scrollRangeY 
!= scrollRangeYOld
) || (scrollRangeX 
!= scrollRangeXOld
) ) 
3590         GetViewStart(&x
, &y
); 
3593         // we want to leave the scrollbars at the same position which means 
3594         // that x and y have to be adjusted as the number of positions may have 
3597         // the number of positions is calculated from knowing that last 
3598         // position = range - thumbSize and thumbSize == pageSize which is 
3599         // equal to the window width / pixelsPerLine 
3600         if ( scrollRangeXOld 
) 
3602             x 
*= scrollRangeX 
- m_rectText
.width 
/ charWidth
; 
3603             x 
/= scrollRangeXOld 
- m_rectText
.width 
/ charWidth
; 
3606         if ( scrollRangeYOld 
) 
3607             y 
*= scrollRangeY 
/ scrollRangeYOld
; 
3610         SetScrollbars(charWidth
, lineHeight
, 
3611                       scrollRangeX
, scrollRangeY
, 
3613                       true /* no refresh */); 
3615         if ( scrollRangeXOld 
) 
3617             const int w 
= m_rectText
.width 
/ charWidth
; 
3618             if ( w 
!= scrollRangeXOld 
) 
3620                 x 
*= scrollRangeX 
- w
; 
3621                 x 
/= scrollRangeXOld 
- w
; 
3626         MData().m_scrollRangeX 
= scrollRangeX
; 
3627         MData().m_scrollRangeY 
= scrollRangeY
; 
3629         // bring the current position in view 
3633     MData().m_updateScrollbarX 
= 
3634     MData().m_updateScrollbarY 
= false; 
3637 void wxTextCtrl::OnInternalIdle() 
3639     // notice that single line text control never has scrollbars 
3640     if ( !IsSingleLine() && 
3641             (MData().m_updateScrollbarX 
|| MData().m_updateScrollbarY
) ) 
3645     wxControl::OnInternalIdle(); 
3648 bool wxTextCtrl::SendAutoScrollEvents(wxScrollWinEvent
& event
) const 
3650     bool forward 
= event
.GetEventType() == wxEVT_SCROLLWIN_LINEDOWN
; 
3651     if ( event
.GetOrientation() == wxHORIZONTAL 
) 
3653         return forward 
? m_curCol 
<= GetLineLength(m_curRow
) : m_curCol 
> 0; 
3657         return forward 
? m_curRow 
< GetNumberOfLines() : m_curRow 
> 0; 
3661 // ---------------------------------------------------------------------------- 
3663 // ---------------------------------------------------------------------------- 
3665 void wxTextCtrl::RefreshSelection() 
3667     if ( HasSelection() ) 
3669         RefreshTextRange(m_selStart
, m_selEnd
); 
3673 void wxTextCtrl::RefreshLineRange(wxTextCoord lineFirst
, wxTextCoord lineLast
) 
3675     wxASSERT_MSG( lineFirst 
<= lineLast 
|| !lineLast
, 
3676                   wxT("no lines to refresh") ); 
3679     // rect.x is already 0 
3680     rect
.width 
= m_rectText
.width
; 
3681     wxCoord h 
= GetLineHeight(); 
3683     wxTextCoord rowFirst
; 
3684     if ( lineFirst 
< GetNumberOfLines() ) 
3686         rowFirst 
= GetFirstRowOfLine(lineFirst
); 
3688     else // lineFirst == GetNumberOfLines() 
3690         // lineFirst may be beyond the last line only if we refresh till 
3691         // the end, otherwise it's illegal 
3692         wxASSERT_MSG( lineFirst 
== GetNumberOfLines() && !lineLast
, 
3693                       wxT("invalid line range") ); 
3695         rowFirst 
= GetRowAfterLine(lineFirst 
- 1); 
3698     rect
.y 
= rowFirst
*h
; 
3702         // refresh till this line (inclusive) 
3703         wxTextCoord rowLast 
= GetRowAfterLine(lineLast
); 
3705         rect
.height 
= (rowLast 
- rowFirst 
+ 1)*h
; 
3707     else // lineLast == 0 means to refresh till the end 
3709         // FIXME: calc it exactly 
3710         rect
.height 
= 32000; 
3713     RefreshTextRect(rect
); 
3716 void wxTextCtrl::RefreshTextRange(wxTextPos start
, wxTextPos end
) 
3718     wxCHECK_RET( start 
!= -1 && end 
!= -1, 
3719                  wxT("invalid RefreshTextRange() arguments") ); 
3721     // accept arguments in any order as it is more conenient for the caller 
3722     OrderPositions(start
, end
); 
3724     // this is acceptable but we don't do anything in this case 
3728     wxTextPos colStart
, lineStart
; 
3729     if ( !PositionToXY(start
, &colStart
, &lineStart
) ) 
3731         // the range is entirely beyond the end of the text, nothing to do 
3735     wxTextCoord colEnd
, lineEnd
; 
3736     if ( !PositionToXY(end
, &colEnd
, &lineEnd
) ) 
3738         // the range spans beyond the end of text, refresh to the end 
3740         lineEnd 
= GetNumberOfLines() - 1; 
3743     // refresh all lines one by one 
3744     for ( wxTextCoord line 
= lineStart
; line 
<= lineEnd
; line
++ ) 
3746         // refresh the first line from the start of the range to the end, the 
3747         // intermediate ones entirely and the last one from the beginning to 
3748         // the end of the range 
3749         wxTextPos posStart 
= line 
== lineStart 
? colStart 
: 0; 
3751         if ( (line 
!= lineEnd
) || (colEnd 
== -1) ) 
3753             // intermediate line or the last one but we need to refresh it 
3754             // until the end anyhow - do it 
3755             posCount 
= wxString::npos
; 
3759             // refresh just the positions in between the start and the end one 
3760             posCount 
= colEnd 
- posStart
; 
3764             RefreshColRange(line
, posStart
, posCount
); 
3768 void wxTextCtrl::RefreshColRange(wxTextCoord line
, 
3772     wxString text 
= GetLineText(line
); 
3774     wxASSERT_MSG( (size_t)start 
<= text
.length() && count
, 
3775                   wxT("invalid RefreshColRange() parameter") ); 
3777     RefreshPixelRange(line
, 
3778                       GetTextWidth(text
.Left((size_t)start
)), 
3779                       GetTextWidth(text
.Mid((size_t)start
, (size_t)count
))); 
3782 // this method accepts "logical" coords in the sense that they are coordinates 
3783 // in a logical line but it can span several rows if we wrap lines and 
3784 // RefreshPixelRange() will then refresh several rows 
3785 void wxTextCtrl::RefreshPixelRange(wxTextCoord line
, 
3789     // we will use line text only in line wrap case 
3793         text 
= GetLineText(line
); 
3796     // special case: width == 0 means to refresh till the end of line 
3799         // refresh till the end of visible line 
3800         width 
= GetTotalWidth(); 
3804             // refresh till the end of text 
3805             wxCoord widthAll 
= GetTextWidth(text
); 
3807             // extend width to the end of ROW 
3808             width 
= widthAll 
- widthAll 
% width 
+ width
; 
3811         // no need to refresh beyond the end of line 
3814     //else: just refresh the specified part 
3816     wxCoord h 
= GetLineHeight(); 
3819     rect
.y 
= GetFirstRowOfLine(line
)*h
; 
3824         // (1) skip all rows which we don't touch at all 
3825         const wxWrappedLineData
& lineData 
= WData().m_linesData
[line
]; 
3826         if ( !WData().IsValidLine(line
) ) 
3829         wxCoord wLine 
= 0; // suppress compiler warning about uninit var 
3830         size_t rowLast 
= lineData
.GetRowCount(), 
3832         while ( (row 
< rowLast
) && 
3833                 (rect
.x 
> (wLine 
= lineData
.GetRowWidth(row
++))) ) 
3839         // (2) now refresh all lines except the last one: note that the first 
3840         //     line is refreshed from the given start to the end, all the next 
3842         while ( (row 
< rowLast
) && (width 
> wLine 
- rect
.x
) ) 
3844             rect
.width 
= GetTotalWidth() - rect
.x
; 
3845             RefreshTextRect(rect
); 
3847             width 
-= wLine 
- rect
.x
; 
3851             wLine 
= lineData
.GetRowWidth(row
++); 
3854         // (3) the code below will refresh the last line 
3859     RefreshTextRect(rect
); 
3862 void wxTextCtrl::RefreshTextRect(const wxRect
& rectClient
, bool textOnly
) 
3865     CalcScrolledPosition(rectClient
.x
, rectClient
.y
, &rect
.x
, &rect
.y
); 
3866     rect
.width 
= rectClient
.width
; 
3867     rect
.height 
= rectClient
.height
; 
3869     // account for the text area offset 
3870     rect
.Offset(m_rectText
.GetPosition()); 
3872     // don't refresh beyond the text area unless we're refreshing the line wrap 
3873     // marks in which case textOnly is false 
3876         if ( rect
.GetRight() > m_rectText
.GetRight() ) 
3878             rect
.SetRight(m_rectText
.GetRight()); 
3880             if ( rect
.width 
<= 0 ) 
3882                 // nothing to refresh 
3888     // check the bottom boundary always, even for the line wrap marks 
3889     if ( rect
.GetBottom() > m_rectText
.GetBottom() ) 
3891         rect
.SetBottom(m_rectText
.GetBottom()); 
3893         if ( rect
.height 
<= 0 ) 
3895             // nothing to refresh 
3900     // never refresh before the visible rect 
3901     if ( rect
.x 
< m_rectText
.x 
) 
3902         rect
.x 
= m_rectText
.x
; 
3904     if ( rect
.y 
< m_rectText
.y 
) 
3905         rect
.y 
= m_rectText
.y
; 
3907     wxLogTrace(wxT("text"), wxT("Refreshing (%d, %d)-(%d, %d)"), 
3908                rect
.x
, rect
.y
, rect
.x 
+ rect
.width
, rect
.y 
+ rect
.height
); 
3910     Refresh(true, &rect
); 
3913 void wxTextCtrl::RefreshLineWrapMarks(wxTextCoord rowFirst
, 
3914                                       wxTextCoord rowLast
) 
3916     if ( WData().m_widthMark 
) 
3919         rectMarks
.x 
= m_rectText
.width
; 
3920         rectMarks
.width 
= WData().m_widthMark
; 
3921         rectMarks
.y 
= rowFirst
*GetLineHeight(); 
3922         rectMarks
.height 
= (rowLast 
- rowFirst
)*GetLineHeight(); 
3924         RefreshTextRect(rectMarks
, false /* don't limit to text area */); 
3928 // ---------------------------------------------------------------------------- 
3930 // ---------------------------------------------------------------------------- 
3932 void wxTextCtrl::DoDrawBorder(wxDC
& dc
, const wxRect
& rect
) 
3934     m_renderer
->DrawTextBorder(dc
, GetBorder(), rect
, GetStateFlags()); 
3937 // ---------------------------------------------------------------------------- 
3938 // client area drawing 
3939 // ---------------------------------------------------------------------------- 
3942    Several remarks about wxTextCtrl redraw logic: 
3944    1. only the regions which must be updated are redrawn, this means that we 
3945       never Refresh() the entire window but use RefreshPixelRange() and 
3946       ScrollWindow() which only refresh small parts of it and iterate over the 
3947       update region in our DoDraw() 
3949    2. the text displayed on the screen is obtained using GetTextToShow(): it 
3950       should be used for all drawing/measuring 
3953 wxString 
wxTextCtrl::GetTextToShow(const wxString
& text
) const 
3957         textShown 
= wxString(wxT('*'), text
.length()); 
3964 void wxTextCtrl::DoDrawTextInRect(wxDC
& dc
, const wxRect
& rectUpdate
) 
3966     // debugging trick to see the update rect visually 
3968     static int s_countUpdates 
= -1; 
3969     if ( s_countUpdates 
!= -1 ) 
3971         wxWindowDC 
dc(this); 
3972         dc
.SetBrush(*(++s_countUpdates 
% 2 ? wxRED_BRUSH 
: wxGREEN_BRUSH
)); 
3973         dc
.SetPen(*wxTRANSPARENT_PEN
); 
3974         dc
.DrawRectangle(rectUpdate
); 
3976 #endif // WXDEBUG_TEXT 
3978     // calculate the range lineStart..lineEnd of lines to redraw 
3979     wxTextCoord lineStart
, lineEnd
; 
3980     if ( IsSingleLine() ) 
3987         wxPoint pt 
= rectUpdate
.GetPosition(); 
3988         (void)HitTest(pt
, NULL
, &lineStart
); 
3990         pt
.y 
+= rectUpdate
.height
; 
3991         (void)HitTest(pt
, NULL
, &lineEnd
); 
3994     // prepare for drawing 
3995     wxCoord hLine 
= GetLineHeight(); 
3997     // these vars will be used for hit testing of the current row 
3998     wxCoord y 
= rectUpdate
.y
; 
3999     const wxCoord x1 
= rectUpdate
.x
; 
4000     const wxCoord x2 
= rectUpdate
.x 
+ rectUpdate
.width
; 
4003     rectText
.height 
= hLine
; 
4004     wxCoord yClient 
= y 
- GetClientAreaOrigin().y
; 
4006     // we want to always start at the top of the line, otherwise if we redraw a 
4007     // rect whose top is in the middle of a line, we'd draw this line shifted 
4008     yClient 
-= (yClient 
- m_rectText
.y
) % hLine
; 
4010     if ( IsSingleLine() ) 
4012         rectText
.y 
= yClient
; 
4014     else // multiline, adjust for scrolling 
4016         CalcUnscrolledPosition(0, yClient
, NULL
, &rectText
.y
); 
4019     wxRenderer 
*renderer 
= GetRenderer(); 
4021     // do draw the invalidated parts of each line: note that we iterate here 
4022     // over ROWs, not over LINEs 
4023     for ( wxTextCoord line 
= lineStart
; 
4024           y 
< rectUpdate
.y 
+ rectUpdate
.height
; 
4026           rectText
.y 
+= hLine 
) 
4028         // calculate the update rect in text positions for this line 
4029         wxTextCoord colStart
, colEnd
, colRowStart
; 
4030         wxTextCtrlHitTestResult ht 
= HitTest2(y
, x1
, x2
, 
4031                                               &line
, &colStart
, &colEnd
, 
4034         if ( (ht 
== wxTE_HT_BEYOND
) || (ht 
== wxTE_HT_BELOW
) ) 
4036             wxASSERT_MSG( line 
<= lineEnd
, wxT("how did we get that far?") ); 
4038             if ( line 
== lineEnd 
) 
4040                 // we redrew everything 
4044             // the update rect is beyond the end of line, no need to redraw 
4045             // anything on this line - but continue with the remaining ones 
4049         // for single line controls we may additionally cut off everything 
4050         // which is to the right of the last visible position 
4051         if ( IsSingleLine() ) 
4053             // don't show the columns which are scrolled out to the left 
4054             if ( colStart 
< SData().m_colStart 
) 
4055                 colStart 
= SData().m_colStart
; 
4057             // colEnd may be less than colStart if colStart was changed by the 
4059             if ( colEnd 
< colStart 
) 
4062             // don't draw the chars beyond the rightmost one 
4063             if ( SData().m_colLastVisible 
== -1 ) 
4065                 // recalculate this rightmost column 
4066                 UpdateLastVisible(); 
4069             if ( colStart 
> SData().m_colLastVisible 
) 
4071                 // don't bother redrawing something that is beyond the last 
4076             if ( colEnd 
> SData().m_colLastVisible 
) 
4078                 colEnd 
= SData().m_colLastVisible
; 
4082         // extract the part of line we need to redraw 
4083         wxString textLine 
= GetTextToShow(GetLineText(line
)); 
4084         wxString text 
= textLine
.Mid(colStart
, colEnd 
- colStart 
+ 1); 
4086         // now deal with the selection: only do something if at least part of 
4087         // the line is selected 
4088         wxTextPos selStart
, selEnd
; 
4089         if ( GetSelectedPartOfLine(line
, &selStart
, &selEnd
) ) 
4091             // and if this part is (at least partly) in the current row 
4092             if ( (selStart 
<= colEnd
) && 
4093                     (selEnd 
>= wxMax(colStart
, colRowStart
)) ) 
4095                 // these values are relative to the start of the line while the 
4096                 // string passed to DrawTextLine() is only part of it, so 
4097                 // adjust the selection range accordingly 
4098                 selStart 
-= colStart
; 
4104                 if ( (size_t)selEnd 
>= text
.length() ) 
4105                     selEnd 
= text
.length(); 
4109                 // reset selStart and selEnd to avoid passing them to 
4110                 // DrawTextLine() below 
4116         // calculate the text coords on screen 
4117         wxASSERT_MSG( colStart 
>= colRowStart
, wxT("invalid string part") ); 
4118         wxCoord ofsStart 
= GetTextWidth( 
4119                                     textLine
.Mid(colRowStart
, 
4120                                                  colStart 
- colRowStart
)); 
4121         rectText
.x 
= m_rectText
.x 
+ ofsStart
; 
4122         rectText
.width 
= GetTextWidth(text
); 
4125         renderer
->DrawTextLine(dc
, text
, rectText
, selStart
, selEnd
, 
4127         wxLogTrace(wxT("text"), wxT("Line %ld: positions %ld-%ld redrawn."), 
4128                    line
, colStart
, colEnd
); 
4132 void wxTextCtrl::DoDrawLineWrapMarks(wxDC
& dc
, const wxRect
& rectUpdate
) 
4134     wxASSERT_MSG( WrapLines() && WData().m_widthMark
, 
4135                   wxT("shouldn't be called at all") ); 
4137     wxRenderer 
*renderer 
= GetRenderer(); 
4140     rectMark
.x 
= rectUpdate
.x
; 
4141     rectMark
.width 
= rectUpdate
.width
; 
4142     wxCoord yTop 
= GetClientAreaOrigin().y
; 
4143     CalcUnscrolledPosition(0, rectUpdate
.y 
- yTop
, NULL
, &rectMark
.y
); 
4144     wxCoord hLine 
= GetLineHeight(); 
4145     rectMark
.height 
= hLine
; 
4147     wxTextCoord line
, rowInLine
; 
4150     CalcUnscrolledPosition(0, rectUpdate
.GetBottom() - yTop
, NULL
, &yBottom
); 
4151     for ( ; rectMark
.y 
< yBottom
; rectMark
.y 
+= hLine 
) 
4153         if ( !GetLineAndRow(rectMark
.y 
/ hLine
, &line
, &rowInLine
) ) 
4155             // we went beyond the end of text 
4159         // is this row continued on the next one? 
4160         if ( !WData().m_linesData
[line
].IsLastRow(rowInLine
) ) 
4162             renderer
->DrawLineWrapMark(dc
, rectMark
); 
4167 void wxTextCtrl::DoDraw(wxControlRenderer 
*renderer
) 
4169     // hide the caret while we're redrawing the window and show it after we are 
4171     wxCaretSuspend 
cs(this); 
4174     wxDC
& dc 
= renderer
->GetDC(); 
4175     dc
.SetFont(GetFont()); 
4176     dc
.SetTextForeground(GetForegroundColour()); 
4178     // get the intersection of the update region with the text area: note that 
4179     // the update region is in window coords and text area is in the client 
4180     // ones, so it must be shifted before computing intersection 
4181     wxRegion rgnUpdate 
= GetUpdateRegion(); 
4183     wxRect rectTextArea 
= GetRealTextArea(); 
4184     wxPoint pt 
= GetClientAreaOrigin(); 
4185     wxRect rectTextAreaAdjusted 
= rectTextArea
; 
4186     rectTextAreaAdjusted
.x 
+= pt
.x
; 
4187     rectTextAreaAdjusted
.y 
+= pt
.y
; 
4188     rgnUpdate
.Intersect(rectTextAreaAdjusted
); 
4190     // even though the drawing is already clipped to the update region, we must 
4191     // explicitly clip it to the rect we will use as otherwise parts of letters 
4192     // might be drawn outside of it (if even a small part of a charater is 
4193     // inside, HitTest() will return its column and DrawText() can't draw only 
4194     // the part of the character, of course) 
4196     // FIXME: is this really a bug in wxMSW? 
4197     rectTextArea
.width
--; 
4199     dc
.DestroyClippingRegion(); 
4200     dc
.SetClippingRegion(rectTextArea
); 
4202     // adjust for scrolling 
4205     // and now refresh the invalidated parts of the window 
4206     wxRegionIterator 
iter(rgnUpdate
); 
4207     for ( ; iter
.HaveRects(); iter
++ ) 
4209         wxRect r 
= iter
.GetRect(); 
4211         // this is a workaround for wxGTK::wxRegion bug 
4213         if ( !r
.width 
|| !r
.height 
) 
4215             // ignore invalid rect 
4220         DoDrawTextInRect(dc
, r
); 
4223     // now redraw the line wrap marks (if we draw them) 
4224     if ( WrapLines() && WData().m_widthMark 
) 
4226         // this is the rect inside which line wrap marks are drawn 
4228         rectMarks
.x 
= rectTextAreaAdjusted
.GetRight() + 1; 
4229         rectMarks
.y 
= rectTextAreaAdjusted
.y
; 
4230         rectMarks
.width 
= WData().m_widthMark
; 
4231         rectMarks
.height 
= rectTextAreaAdjusted
.height
; 
4233         rgnUpdate 
= GetUpdateRegion(); 
4234         rgnUpdate
.Intersect(rectMarks
); 
4236         wxRect rectUpdate 
= rgnUpdate
.GetBox(); 
4237         if ( rectUpdate
.width 
&& rectUpdate
.height 
) 
4239             // the marks are outside previously set clipping region 
4240             dc
.DestroyClippingRegion(); 
4242             DoDrawLineWrapMarks(dc
, rectUpdate
); 
4246     // show caret first time only: we must show it after drawing the text or 
4247     // the display can be corrupted when it's hidden 
4248     if ( !m_hasCaret 
&& GetCaret() && (FindFocus() == this) ) 
4256 // ---------------------------------------------------------------------------- 
4258 // ---------------------------------------------------------------------------- 
4260 bool wxTextCtrl::SetFont(const wxFont
& font
) 
4262     if ( !wxControl::SetFont(font
) ) 
4265     // and refresh everything, of course 
4266     InitInsertionPoint(); 
4269     // update geometry parameters 
4271     RecalcFontMetrics(); 
4272     if ( !IsSingleLine() ) 
4278     // recreate it, in fact 
4286 bool wxTextCtrl::Enable(bool enable
) 
4288     if ( !wxTextCtrlBase::Enable(enable
) ) 
4291     if (FindFocus() == this && GetCaret() && 
4292         ((enable 
&& !GetCaret()->IsVisible()) || 
4293          (!enable 
&& GetCaret()->IsVisible()))) 
4299 void wxTextCtrl::CreateCaret() 
4305         // FIXME use renderer 
4306         caret 
= new wxCaret(this, 1, GetLineHeight()); 
4310         // read only controls don't have the caret 
4314     // SetCaret() will delete the old caret if any 
4318 void wxTextCtrl::ShowCaret(bool show
) 
4320     wxCaret 
*caret 
= GetCaret(); 
4323         // (re)position caret correctly 
4324         caret
->Move(GetCaretPosition()); 
4326         // and show it there 
4327         if ((show 
&& !caret
->IsVisible()) || 
4328             (!show 
&& caret
->IsVisible())) 
4333 // ---------------------------------------------------------------------------- 
4334 // vertical scrolling (multiline only) 
4335 // ---------------------------------------------------------------------------- 
4337 size_t wxTextCtrl::GetLinesPerPage() const 
4339     if ( IsSingleLine() ) 
4342     return GetRealTextArea().height 
/ GetLineHeight(); 
4345 wxTextPos 
wxTextCtrl::GetPositionAbove() 
4347     wxCHECK_MSG( !IsSingleLine(), INVALID_POS_VALUE
, 
4348                  wxT("can't move cursor vertically in a single line control") ); 
4350     // move the cursor up by one ROW not by one LINE: this means that 
4351     // we should really use HitTest() and not just go to the same 
4352     // position in the previous line 
4353     wxPoint pt 
= GetCaretPosition() - m_rectText
.GetPosition(); 
4354     if ( MData().m_xCaret 
== -1 ) 
4356         // remember the initial cursor abscissa 
4357         MData().m_xCaret 
= pt
.x
; 
4361         // use the remembered abscissa 
4362         pt
.x 
= MData().m_xCaret
; 
4365     CalcUnscrolledPosition(pt
.x
, pt
.y
, &pt
.x
, &pt
.y
); 
4366     pt
.y 
-= GetLineHeight(); 
4368     wxTextCoord col
, row
; 
4369     if ( HitTestLogical(pt
, &col
, &row
) == wxTE_HT_BEFORE 
) 
4371         // can't move further 
4372         return INVALID_POS_VALUE
; 
4375     return XYToPosition(col
, row
); 
4378 wxTextPos 
wxTextCtrl::GetPositionBelow() 
4380     wxCHECK_MSG( !IsSingleLine(), INVALID_POS_VALUE
, 
4381                  wxT("can't move cursor vertically in a single line control") ); 
4383     // see comments for wxACTION_TEXT_UP 
4384     wxPoint pt 
= GetCaretPosition() - m_rectText
.GetPosition(); 
4385     if ( MData().m_xCaret 
== -1 ) 
4387         // remember the initial cursor abscissa 
4388         MData().m_xCaret 
= pt
.x
; 
4392         // use the remembered abscissa 
4393         pt
.x 
= MData().m_xCaret
; 
4396     CalcUnscrolledPosition(pt
.x
, pt
.y
, &pt
.x
, &pt
.y
); 
4397     pt
.y 
+= GetLineHeight(); 
4399     wxTextCoord col
, row
; 
4400     if ( HitTestLogical(pt
, &col
, &row
) == wxTE_HT_BELOW 
) 
4402         // can't go further down 
4403         return INVALID_POS_VALUE
; 
4406     // note that wxTE_HT_BEYOND is ok: it happens when we go down 
4407     // from a longer line to a shorter one, for example (OTOH 
4408     // wxTE_HT_BEFORE can never happen) 
4409     return XYToPosition(col
, row
); 
4412 // ---------------------------------------------------------------------------- 
4414 // ---------------------------------------------------------------------------- 
4416 bool wxTextCtrl::PerformAction(const wxControlAction
& actionOrig
, 
4418                                const wxString
& strArg
) 
4420     // has the text changed as result of this action? 
4421     bool textChanged 
= false; 
4423     // the remembered cursor abscissa for multiline text controls is usually 
4424     // reset after each user action but for ones which do use it (UP and DOWN 
4425     // for example) we shouldn't do it - as indicated by this flag 
4426     bool rememberAbscissa 
= false; 
4428     // the command this action corresponds to or NULL if this action doesn't 
4429     // change text at all or can't be undone 
4430     wxTextCtrlCommand 
*command 
= NULL
; 
4435     if ( actionOrig
.StartsWith(wxACTION_TEXT_PREFIX_DEL
, &action
) ) 
4440     else if ( actionOrig
.StartsWith(wxACTION_TEXT_PREFIX_SEL
, &action
) ) 
4444     else // not selection nor delete action 
4446         action 
= actionOrig
; 
4449     // set newPos to -2 as it can't become equal to it in the assignments below 
4450     // (but it can become -1) 
4451     wxTextPos newPos 
= INVALID_POS_VALUE
; 
4453     if ( action 
== wxACTION_TEXT_HOME 
) 
4455         newPos 
= m_curPos 
- m_curCol
; 
4457     else if ( action 
== wxACTION_TEXT_END 
) 
4459         newPos 
= m_curPos 
+ GetLineLength(m_curRow
) - m_curCol
; 
4461     else if ( (action 
== wxACTION_TEXT_GOTO
) || 
4462               (action 
== wxACTION_TEXT_FIRST
) || 
4463               (action 
== wxACTION_TEXT_LAST
) ) 
4465         if ( action 
== wxACTION_TEXT_FIRST 
) 
4467         else if ( action 
== wxACTION_TEXT_LAST 
) 
4468             numArg 
= GetLastPosition(); 
4469         //else: numArg already contains the position 
4473     else if ( action 
== wxACTION_TEXT_UP 
) 
4475         if ( !IsSingleLine() ) 
4477             newPos 
= GetPositionAbove(); 
4479             if ( newPos 
!= INVALID_POS_VALUE 
) 
4481                 // remember where the cursor original had been 
4482                 rememberAbscissa 
= true; 
4486     else if ( action 
== wxACTION_TEXT_DOWN 
) 
4488         if ( !IsSingleLine() ) 
4490             newPos 
= GetPositionBelow(); 
4492             if ( newPos 
!= INVALID_POS_VALUE 
) 
4494                 // remember where the cursor original had been 
4495                 rememberAbscissa 
= true; 
4499     else if ( action 
== wxACTION_TEXT_LEFT 
) 
4501         newPos 
= m_curPos 
- 1; 
4503     else if ( action 
== wxACTION_TEXT_WORD_LEFT 
) 
4505         newPos 
= GetWordStart(); 
4507     else if ( action 
== wxACTION_TEXT_RIGHT 
) 
4509         newPos 
= m_curPos 
+ 1; 
4511     else if ( action 
== wxACTION_TEXT_WORD_RIGHT 
) 
4513         newPos 
= GetWordEnd(); 
4515     else if ( action 
== wxACTION_TEXT_INSERT 
) 
4517         if ( IsEditable() && !strArg
.empty() ) 
4519             // inserting text can be undone 
4520             command 
= new wxTextCtrlInsertCommand(strArg
); 
4525     else if ( (action 
== wxACTION_TEXT_PAGE_UP
) || 
4526               (action 
== wxACTION_TEXT_PAGE_DOWN
) ) 
4528         if ( !IsSingleLine() ) 
4530             size_t count 
= GetLinesPerPage(); 
4531             if ( count 
> PAGE_OVERLAP_IN_LINES 
) 
4533                 // pages should overlap slightly to allow the reader to keep 
4534                 // orientation in the text 
4535                 count 
-= PAGE_OVERLAP_IN_LINES
; 
4538             // remember where the cursor original had been 
4539             rememberAbscissa 
= true; 
4541             bool goUp 
= action 
== wxACTION_TEXT_PAGE_UP
; 
4542             for ( size_t line 
= 0; line 
< count
; line
++ ) 
4544                 wxTextPos pos 
= goUp 
? GetPositionAbove() : GetPositionBelow(); 
4545                 if ( pos 
== INVALID_POS_VALUE 
) 
4547                     // can't move further 
4551                 MoveInsertionPoint(pos
); 
4555             // we implement the Unix scrolling model here: cursor will always 
4556             // be on the first line after Page Down and on the last one after 
4559             // Windows programs usually keep the cursor line offset constant 
4560             // but do we really need it? 
4564                 // find the line such that when it is the first one, the 
4565                 // current position is in the last line 
4567                 for ( size_t line 
= 0; line 
< count
; line
++ ) 
4569                     pos 
= GetPositionAbove(); 
4570                     if ( pos 
== INVALID_POS_VALUE 
) 
4573                     MoveInsertionPoint(pos
); 
4576                 MoveInsertionPoint(newPos
); 
4578                 PositionToLogicalXY(pos
, NULL
, &y
); 
4580             else // scrolled down 
4582                 PositionToLogicalXY(newPos
, NULL
, &y
); 
4585             // scroll vertically only 
4586             Scroll(wxDefaultCoord
, y
); 
4589     else if ( action 
== wxACTION_TEXT_SEL_WORD 
) 
4591         SetSelection(GetWordStart(), GetWordEnd()); 
4593     else if ( action 
== wxACTION_TEXT_ANCHOR_SEL 
) 
4597     else if ( action 
== wxACTION_TEXT_EXTEND_SEL 
) 
4599         SetSelection(m_selAnchor
, numArg
); 
4601     else if ( action 
== wxACTION_TEXT_COPY 
) 
4605     else if ( action 
== wxACTION_TEXT_CUT 
) 
4610     else if ( action 
== wxACTION_TEXT_PASTE 
) 
4615     else if ( action 
== wxACTION_TEXT_UNDO 
) 
4620     else if ( action 
== wxACTION_TEXT_REDO 
) 
4627         return wxControl::PerformAction(action
, numArg
, strArg
); 
4630     if ( newPos 
!= INVALID_POS_VALUE 
) 
4632         // bring the new position into the range 
4636         wxTextPos posLast 
= GetLastPosition(); 
4637         if ( newPos 
> posLast 
) 
4642             // if we have the selection, remove just it 
4644             if ( HasSelection() ) 
4651                 // otherwise delete everything between current position and 
4653                 if ( m_curPos 
!= newPos 
) 
4658                 else // nothing to delete 
4660                     // prevent test below from working 
4661                     from 
= INVALID_POS_VALUE
; 
4663                     // and this is just to silent the compiler warning 
4668             if ( from 
!= INVALID_POS_VALUE 
) 
4670                 command 
= new wxTextCtrlRemoveCommand(from
, to
); 
4673         else // cursor movement command 
4676             DoSetInsertionPoint(newPos
); 
4680                 SetSelection(m_selAnchor
, m_curPos
); 
4682             else // simple movement 
4684                 // clear the existing selection 
4689         if ( !rememberAbscissa 
&& !IsSingleLine() ) 
4691             MData().m_xCaret 
= -1; 
4697         // execute and remember it to be able to undo it later 
4698         m_cmdProcessor
->Submit(command
); 
4700         // undoable commands always change text 
4703     else // no undoable command 
4705         // m_cmdProcessor->StopCompressing() 
4710         wxASSERT_MSG( IsEditable(), wxT("non editable control changed?") ); 
4712         wxCommandEvent 
event(wxEVT_TEXT
, GetId()); 
4713         InitCommandEvent(event
); 
4714         GetEventHandler()->ProcessEvent(event
); 
4716         // as the text changed... 
4717         m_isModified 
= true; 
4723 void wxTextCtrl::OnChar(wxKeyEvent
& event
) 
4725     // only process the key events from "simple keys" here 
4726     if ( !event
.HasModifiers() ) 
4728         int keycode 
= event
.GetKeyCode(); 
4730         wxChar unicode 
= event
.GetUnicodeKey(); 
4732         if ( keycode 
== WXK_RETURN 
) 
4734             if ( IsSingleLine() || (GetWindowStyle() & wxTE_PROCESS_ENTER
) ) 
4736                 wxCommandEvent 
event(wxEVT_TEXT_ENTER
, GetId()); 
4737                 InitCommandEvent(event
); 
4738                 event
.SetString(GetValue()); 
4739                 GetEventHandler()->ProcessEvent(event
); 
4741             else // interpret <Enter> normally: insert new line 
4743                 PerformAction(wxACTION_TEXT_INSERT
, -1, wxT('\n')); 
4746         else if ( keycode 
< 255 && isprint(keycode
) ) 
4748             PerformAction(wxACTION_TEXT_INSERT
, -1, (wxChar
)keycode
); 
4750             // skip event.Skip() below 
4754         else if (unicode 
> 0) 
4756             PerformAction(wxACTION_TEXT_INSERT
, -1, unicode
); 
4762 #if wxDEBUG_LEVEL >= 2 
4763     // Ctrl-R refreshes the control in debug mode 
4764     else if ( event
.ControlDown() && event
.GetKeyCode() == 'r' ) 
4766 #endif // wxDEBUG_LEVEL >= 2 
4772 wxInputHandler 
*wxTextCtrl::GetStdInputHandler(wxInputHandler 
*handlerDef
) 
4774     static wxStdTextCtrlInputHandler 
s_handler(handlerDef
); 
4779 // ---------------------------------------------------------------------------- 
4780 // wxStdTextCtrlInputHandler 
4781 // ---------------------------------------------------------------------------- 
4783 wxStdTextCtrlInputHandler::wxStdTextCtrlInputHandler(wxInputHandler 
*inphand
) 
4784                          : wxStdInputHandler(inphand
) 
4786     m_winCapture 
= NULL
; 
4790 wxTextPos 
wxStdTextCtrlInputHandler::HitTest(const wxTextCtrl 
*text
, 
4793     wxTextCoord col
, row
; 
4794     wxTextCtrlHitTestResult ht 
= text
->HitTest(pt
, &col
, &row
); 
4796     wxTextPos pos 
= text
->XYToPosition(col
, row
); 
4798     // if the point is after the last column we must adjust the position to be 
4799     // the last position in the line (unless it is already the last) 
4800     if ( (ht 
== wxTE_HT_BEYOND
) && (pos 
< text
->GetLastPosition()) ) 
4808 bool wxStdTextCtrlInputHandler::HandleKey(wxInputConsumer 
*consumer
, 
4809                                           const wxKeyEvent
& event
, 
4812     // we're only interested in key presses 
4816     int keycode 
= event
.GetKeyCode(); 
4818     wxControlAction action
; 
4820     bool ctrlDown 
= event
.ControlDown(), 
4821          shiftDown 
= event
.ShiftDown(); 
4824         action 
= wxACTION_TEXT_PREFIX_SEL
; 
4827     // the only key combination with Alt we recognize is Alt-Bksp for undo, so 
4828     // treat it first separately 
4829     if ( event
.AltDown() ) 
4831         if ( keycode 
== WXK_BACK 
&& !ctrlDown 
&& !shiftDown 
) 
4832             action 
= wxACTION_TEXT_UNDO
; 
4834     else switch ( keycode 
) 
4838             action 
<< (ctrlDown 
? wxACTION_TEXT_FIRST
 
4839                                 : wxACTION_TEXT_HOME
); 
4843             action 
<< (ctrlDown 
? wxACTION_TEXT_LAST
 
4844                                 : wxACTION_TEXT_END
); 
4849                 action 
<< wxACTION_TEXT_UP
; 
4854                 action 
<< wxACTION_TEXT_DOWN
; 
4858             action 
<< (ctrlDown 
? wxACTION_TEXT_WORD_LEFT
 
4859                                 : wxACTION_TEXT_LEFT
); 
4863             action 
<< (ctrlDown 
? wxACTION_TEXT_WORD_RIGHT
 
4864                                 : wxACTION_TEXT_RIGHT
); 
4868             // we don't map Ctrl-PgUp/Dn to anything special - what should it 
4869             // to? for now, it's the same as without control 
4870             action 
<< wxACTION_TEXT_PAGE_DOWN
; 
4874             action 
<< wxACTION_TEXT_PAGE_UP
; 
4880                 action 
<< wxACTION_TEXT_PREFIX_DEL 
<< wxACTION_TEXT_RIGHT
; 
4885                 action 
<< wxACTION_TEXT_PREFIX_DEL 
<< wxACTION_TEXT_LEFT
; 
4890             // reset the action as it could be already set to one of the 
4892             action 
= wxACTION_NONE
; 
4899                         action 
= wxACTION_TEXT_REDO
; 
4903                         action 
= wxACTION_TEXT_COPY
; 
4907                         action 
= wxACTION_TEXT_PASTE
; 
4911                         action 
= wxACTION_TEXT_CUT
; 
4915                         action 
= wxACTION_TEXT_UNDO
; 
4921     if ( (action 
!= wxACTION_NONE
) && (action 
!= wxACTION_TEXT_PREFIX_SEL
) ) 
4923         consumer
->PerformAction(action
, -1, str
); 
4928     return wxStdInputHandler::HandleKey(consumer
, event
, pressed
); 
4931 bool wxStdTextCtrlInputHandler::HandleMouse(wxInputConsumer 
*consumer
, 
4932                                             const wxMouseEvent
& event
) 
4934     if ( event
.LeftDown() ) 
4936         wxASSERT_MSG( !m_winCapture
, wxT("left button going down twice?") ); 
4938         wxTextCtrl 
*text 
= wxStaticCast(consumer
->GetInputWindow(), wxTextCtrl
); 
4940         m_winCapture 
= text
; 
4941         m_winCapture
->CaptureMouse(); 
4945         wxTextPos pos 
= HitTest(text
, event
.GetPosition()); 
4948             text
->PerformAction(wxACTION_TEXT_ANCHOR_SEL
, pos
); 
4951     else if ( event
.LeftDClick() ) 
4953         // select the word the cursor is on 
4954         consumer
->PerformAction(wxACTION_TEXT_SEL_WORD
); 
4956     else if ( event
.LeftUp() ) 
4960             m_winCapture
->ShowCaret(); 
4962             m_winCapture
->ReleaseMouse(); 
4963             m_winCapture 
= NULL
; 
4967     return wxStdInputHandler::HandleMouse(consumer
, event
); 
4970 bool wxStdTextCtrlInputHandler::HandleMouseMove(wxInputConsumer 
*consumer
, 
4971                                                 const wxMouseEvent
& event
) 
4976         wxTextCtrl 
*text 
= wxStaticCast(m_winCapture
, wxTextCtrl
); 
4977         wxTextPos pos 
= HitTest(text
, event
.GetPosition()); 
4980             text
->PerformAction(wxACTION_TEXT_EXTEND_SEL
, pos
); 
4984     return wxStdInputHandler::HandleMouseMove(consumer
, event
); 
4988 wxStdTextCtrlInputHandler::HandleFocus(wxInputConsumer 
*consumer
, 
4989                                        const wxFocusEvent
& event
) 
4991     wxTextCtrl 
*text 
= wxStaticCast(consumer
->GetInputWindow(), wxTextCtrl
); 
4993     // the selection appearance changes depending on whether we have the focus 
4994     text
->RefreshSelection(); 
4996     if (event
.GetEventType() == wxEVT_SET_FOCUS
) 
4998         if (text
->GetCaret() && !text
->GetCaret()->IsVisible()) 
5003         if (text
->GetCaret() && text
->GetCaret()->IsVisible()) 
5007     // never refresh entirely 
5011 #endif // wxUSE_TEXTCTRL