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 // turn extra wxTextCtrl-specific debugging on/off 
 155 // turn wxTextCtrl::Replace() debugging on (slows down code a *lot*!) 
 156 #define WXDEBUG_TEXT_REPLACE 
 160     #undef WXDEBUG_TEXT_REPLACE 
 163 // wxStringTokenize only needed for debug checks 
 164 #ifdef WXDEBUG_TEXT_REPLACE 
 165     #include "wx/tokenzr.h" 
 166 #endif // WXDEBUG_TEXT_REPLACE 
 168 // ---------------------------------------------------------------------------- 
 169 // wxStdTextCtrlInputHandler: this control handles only the mouse/kbd actions 
 170 // common to Win32 and GTK, platform-specific things are implemented elsewhere 
 171 // ---------------------------------------------------------------------------- 
 173 class WXDLLEXPORT wxStdTextCtrlInputHandler 
: public wxStdInputHandler
 
 176     wxStdTextCtrlInputHandler(wxInputHandler 
*inphand
); 
 178     virtual bool HandleKey(wxInputConsumer 
*consumer
, 
 179                            const wxKeyEvent
& event
, 
 181     virtual bool HandleMouse(wxInputConsumer 
*consumer
, 
 182                              const wxMouseEvent
& event
); 
 183     virtual bool HandleMouseMove(wxInputConsumer 
*consumer
, 
 184                                  const wxMouseEvent
& event
); 
 185     virtual bool HandleFocus(wxInputConsumer 
*consumer
, const wxFocusEvent
& event
); 
 188     // get the position of the mouse click 
 189     static wxTextPos 
HitTest(const wxTextCtrl 
*text
, const wxPoint
& pos
); 
 192     wxTextCtrl 
*m_winCapture
; 
 195 // ---------------------------------------------------------------------------- 
 197 // ---------------------------------------------------------------------------- 
 199 // exchange two positions so that from is always less than or equal to to 
 200 static inline void OrderPositions(wxTextPos
& from
, wxTextPos
& to
) 
 204         wxTextPos tmp 
= from
; 
 210 // ---------------------------------------------------------------------------- 
 212 // ---------------------------------------------------------------------------- 
 214 // names of text ctrl commands 
 215 #define wxTEXT_COMMAND_INSERT _T("insert") 
 216 #define wxTEXT_COMMAND_REMOVE _T("remove") 
 218 // the value which is never used for text position, even not -1 which is 
 219 // sometimes used for some special meaning 
 220 static const wxTextPos INVALID_POS_VALUE 
= wxInvalidTextCoord
; 
 222 // overlap between pages (when using PageUp/Dn) in lines 
 223 static const size_t PAGE_OVERLAP_IN_LINES 
= 1; 
 225 // ---------------------------------------------------------------------------- 
 226 // private data of wxTextCtrl 
 227 // ---------------------------------------------------------------------------- 
 229 // the data only used by single line text controls 
 230 struct wxTextSingleLineData
 
 232     // the position of the first visible pixel and the first visible column 
 234     wxTextCoord m_colStart
; 
 236     // and the last ones (m_posLastVisible is the width but m_colLastVisible 
 237     // is an absolute value) 
 238     wxCoord m_posLastVisible
; 
 239     wxTextCoord m_colLastVisible
; 
 242     wxTextSingleLineData() 
 247         m_colLastVisible 
= -1; 
 248         m_posLastVisible 
= -1; 
 253 // the data only used by multi line text controls 
 254 struct wxTextMultiLineData
 
 257     wxArrayString m_lines
; 
 259     // the current ranges of the scrollbars 
 263     // should we adjust the horz/vert scrollbar? 
 264     bool m_updateScrollbarX
, 
 267     // the max line length in pixels 
 270     // the index of the line which has the length of m_widthMax 
 271     wxTextCoord m_lineLongest
; 
 273     // the rect in which text appears: it is even less than m_rectText because 
 274     // only the last _complete_ line is shown, hence there is an unoccupied 
 275     // horizontal band at the bottom of it 
 276     wxRect m_rectTextReal
; 
 278     // the x-coordinate of the caret before we started moving it vertically: 
 279     // this is used to ensure that moving the caret up and then down will 
 280     // return it to the same position as if we always round it in one direction 
 281     // we would shift it in that direction 
 283     // when m_xCaret == -1, we don't have any remembered position 
 287     wxTextMultiLineData() 
 293         m_updateScrollbarY 
= false; 
 302 // the data only used by multi line text controls in line wrap mode 
 303 class wxWrappedLineData
 
 305     // these functions set all our values, so give them access to them 
 306 friend void wxTextCtrl::LayoutLine(wxTextCoord line
, 
 307                                     wxWrappedLineData
& lineData
) const; 
 308 friend void wxTextCtrl::LayoutLines(wxTextCoord
) const; 
 317     // get the start of any row (remember that accessing m_rowsStart doesn't work 
 318     // for the first one) 
 319     wxTextCoord 
GetRowStart(wxTextCoord row
) const 
 321         wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") ); 
 323         return row 
? m_rowsStart
[row 
- 1] : 0; 
 326     // get the length of the row (using the total line length which we don't 
 327     // have here but need to calculate the length of the last row, so it must 
 329     wxTextCoord 
GetRowLength(wxTextCoord row
, wxTextCoord lenLine
) const 
 331         wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") ); 
 333         // note that m_rowsStart[row] is the same as GetRowStart(row + 1) (but 
 334         // slightly more efficient) and lenLine is the same as the start of the 
 335         // first row of the next line 
 336         return ((size_t)row 
== m_rowsStart
.GetCount() ? lenLine 
: m_rowsStart
[row
]) 
 340     // return the width of the row in pixels 
 341     wxCoord 
GetRowWidth(wxTextCoord row
) const 
 343         wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") ); 
 345         return m_rowsWidth
[row
]; 
 348     // return the number of rows 
 349     size_t GetRowCount() const 
 351         wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") ); 
 353         return m_rowsStart
.GetCount() + 1; 
 356     // return the number of additional (i.e. after the first one) rows 
 357     size_t GetExtraRowCount() const 
 359         wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") ); 
 361         return m_rowsStart
.GetCount(); 
 364     // return the first row of this line 
 365     wxTextCoord 
GetFirstRow() const 
 367         wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") ); 
 372     // return the first row of the next line 
 373     wxTextCoord 
GetNextRow() const 
 375         wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") ); 
 377         return m_rowFirst 
+ m_rowsStart
.GetCount() + 1; 
 380     // this just provides direct access to m_rowsStart aerray for efficiency 
 381     wxTextCoord 
GetExtraRowStart(wxTextCoord row
) const 
 383         wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") ); 
 385         return m_rowsStart
[row
]; 
 388     // this code is unused any longer 
 390     // return true if the column is in the start of the last row (hence the row 
 391     // it is in is not wrapped) 
 392     bool IsLastRow(wxTextCoord colRowStart
) const 
 394         return colRowStart 
== GetRowStart(m_rowsStart
.GetCount()); 
 397     // return true if the column is the last column of the row starting in 
 399     bool IsLastColInRow(wxTextCoord colRowStart
, 
 400                         wxTextCoord colRowEnd
, 
 401                         wxTextCoord lenLine
) const 
 403         // find the row which starts with colRowStart 
 404         size_t nRows 
= GetRowCount(); 
 405         for ( size_t n 
= 0; n 
< nRows
; n
++ ) 
 407             if ( GetRowStart(n
) == colRowStart 
) 
 409                 wxTextCoord colNextRowStart 
= n 
== nRows 
- 1 
 411                                                 : GetRowStart(n 
+ 1); 
 413                 wxASSERT_MSG( colRowEnd 
< colNextRowStart
, 
 414                               _T("this column is not in this row at all!") ); 
 416                 return colRowEnd 
== colNextRowStart 
- 1; 
 420         // caller got it wrong 
 421         wxFAIL_MSG( _T("this column is not in the start of the row!") ); 
 427     // is this row the last one in its line? 
 428     bool IsLastRow(wxTextCoord row
) const 
 430         return (size_t)row 
== GetExtraRowCount(); 
 433     // the line is valid if it had been laid out correctly: note that just 
 434     // shiwting the line (because one of previous lines changed) doesn't make 
 436     bool IsValid() const { return !m_rowsWidth
.IsEmpty(); } 
 438     // invalidating line will relayout it 
 439     void Invalidate() { m_rowsWidth
.Empty(); } 
 442     // for each line we remember the starting columns of all its rows after the 
 443     // first one (which always starts at 0), i.e. if a line is wrapped twice 
 444     // (== takes 3 rows) its m_rowsStart[0] may be 10 and m_rowsStart[1] == 15 
 445     wxArrayLong m_rowsStart
; 
 447     // and the width of each row in pixels (this array starts from 0, as usual) 
 448     wxArrayInt m_rowsWidth
; 
 450     // and also its starting row (0 for the first line, first lines' 
 451     // m_rowsStart.GetCount() + 1 for the second &c): it is set to -1 initially 
 452     // and this means that the struct hadn't yet been initialized 
 453     wxTextCoord m_rowFirst
; 
 455     // the last modification "time"-stamp used by LayoutLines() 
 459 WX_DECLARE_OBJARRAY(wxWrappedLineData
, wxArrayWrappedLinesData
); 
 460 #include "wx/arrimpl.cpp" 
 461 WX_DEFINE_OBJARRAY(wxArrayWrappedLinesData
); 
 463 struct wxTextWrappedData 
: public wxTextMultiLineData
 
 465     // the width of the column to the right of the text rect used for the 
 466     // indicator mark display for the wrapped lines 
 469     // the data for each line 
 470     wxArrayWrappedLinesData m_linesData
; 
 472     // flag telling us to recalculate all starting rows starting from this line 
 473     // (if it is -1, we don't have to recalculate anything) - it is set when 
 474     // the number of the rows in the middle of the control changes 
 475     wxTextCoord m_rowFirstInvalid
; 
 477     // the current timestamp used by LayoutLines() 
 480     // invalidate starting rows of all lines (NOT rows!) after this one 
 481     void InvalidateLinesBelow(wxTextCoord line
) 
 483         if ( m_rowFirstInvalid 
== -1 || m_rowFirstInvalid 
> line 
) 
 485             m_rowFirstInvalid 
= line
; 
 489     // check if this line is valid: i.e. before the first invalid one 
 490     bool IsValidLine(wxTextCoord line
) const 
 492         return ((m_rowFirstInvalid 
== -1) || (line 
< m_rowFirstInvalid
)) && 
 493                     m_linesData
[line
].IsValid(); 
 500         m_rowFirstInvalid 
= -1; 
 505 // ---------------------------------------------------------------------------- 
 506 // private classes for undo/redo management 
 507 // ---------------------------------------------------------------------------- 
 510    We use custom versions of wxWidgets command processor to implement undo/redo 
 511    as we want to avoid storing the backpointer to wxTextCtrl in wxCommand 
 512    itself: this is a waste of memory as all commands in the given command 
 513    processor always have the same associated wxTextCtrl and so it makes sense 
 514    to store the backpointer there. 
 516    As for the rest of the implementation, it's fairly standard: we have 2 
 517    command classes corresponding to adding and removing text. 
 520 // a command corresponding to a wxTextCtrl action 
 521 class wxTextCtrlCommand 
: public wxCommand
 
 524     wxTextCtrlCommand(const wxString
& name
) : wxCommand(true, name
) { } 
 526     // we don't use these methods as they don't make sense for us as we need a 
 527     // wxTextCtrl to be applied 
 528     virtual bool Do() { wxFAIL_MSG(_T("shouldn't be called")); return false; } 
 529     virtual bool Undo() { wxFAIL_MSG(_T("shouldn't be called")); return false; } 
 531     // instead, our command processor uses these methods 
 532     virtual bool Do(wxTextCtrl 
*text
) = 0; 
 533     virtual bool Undo(wxTextCtrl 
*text
) = 0; 
 536 // insert text command 
 537 class wxTextCtrlInsertCommand 
: public wxTextCtrlCommand
 
 540     wxTextCtrlInsertCommand(const wxString
& textToInsert
) 
 541         : wxTextCtrlCommand(wxTEXT_COMMAND_INSERT
), m_text(textToInsert
) 
 546     // combine the 2 commands together 
 547     void Append(wxTextCtrlInsertCommand 
*other
); 
 549     virtual bool CanUndo() const; 
 550     virtual bool Do(wxTextCtrl 
*text
); 
 551     virtual bool Do() { return wxTextCtrlCommand::Do(); } 
 552     virtual bool Undo() { return wxTextCtrlCommand::Undo(); } 
 553     virtual bool Undo(wxTextCtrl 
*text
); 
 556     // the text we insert 
 559     // the position where we inserted the text 
 563 // remove text command 
 564 class wxTextCtrlRemoveCommand 
: public wxTextCtrlCommand
 
 567     wxTextCtrlRemoveCommand(wxTextPos from
, wxTextPos to
) 
 568         : wxTextCtrlCommand(wxTEXT_COMMAND_REMOVE
) 
 574     virtual bool CanUndo() const; 
 575     virtual bool Do(wxTextCtrl 
*text
); 
 576     virtual bool Do() { return wxTextCtrlCommand::Do(); } 
 577     virtual bool Undo() { return wxTextCtrlCommand::Undo(); } 
 578     virtual bool Undo(wxTextCtrl 
*text
); 
 581     // the range of text to delete 
 585     // the text which was deleted when this command was Do()ne 
 586     wxString m_textDeleted
; 
 589 // a command processor for a wxTextCtrl 
 590 class wxTextCtrlCommandProcessor 
: public wxCommandProcessor
 
 593     wxTextCtrlCommandProcessor(wxTextCtrl 
*text
) 
 595         m_compressInserts 
= false; 
 600     // override Store() to compress multiple wxTextCtrlInsertCommand into one 
 601     virtual void Store(wxCommand 
*command
); 
 603     // stop compressing insert commands when this is called 
 604     void StopCompressing() { m_compressInserts 
= false; } 
 607     wxTextCtrl 
*GetTextCtrl() const { return m_text
; } 
 608     bool IsCompressing() const { return m_compressInserts
; } 
 611     virtual bool DoCommand(wxCommand
& cmd
) 
 612         { return ((wxTextCtrlCommand 
&)cmd
).Do(m_text
); } 
 613     virtual bool UndoCommand(wxCommand
& cmd
) 
 614         { return ((wxTextCtrlCommand 
&)cmd
).Undo(m_text
); } 
 616     // check if this command is a wxTextCtrlInsertCommand and return it casted 
 617     // to the right type if it is or NULL otherwise 
 618     wxTextCtrlInsertCommand 
*IsInsertCommand(wxCommand 
*cmd
); 
 621     // the control we're associated with 
 624     // if the flag is true we're compressing subsequent insert commands into 
 625     // one so that the entire typing could be undone in one call to Undo() 
 626     bool m_compressInserts
; 
 629 // ============================================================================ 
 631 // ============================================================================ 
 633 BEGIN_EVENT_TABLE(wxTextCtrl
, wxTextCtrlBase
) 
 634     EVT_CHAR(wxTextCtrl::OnChar
) 
 636     EVT_SIZE(wxTextCtrl::OnSize
) 
 639 IMPLEMENT_DYNAMIC_CLASS(wxTextCtrl
, wxTextCtrlBase
) 
 641 // ---------------------------------------------------------------------------- 
 643 // ---------------------------------------------------------------------------- 
 645 void wxTextCtrl::Init() 
 651     m_isModified 
= false; 
 663     // init the undo manager 
 664     m_cmdProcessor 
= new wxTextCtrlCommandProcessor(this); 
 670 bool wxTextCtrl::Create(wxWindow 
*parent
, 
 672                         const wxString
& value
, 
 676                         const wxValidator
& validator
, 
 677                         const wxString 
&name
) 
 679     if ( style 
& wxTE_MULTILINE 
) 
 681         // for compatibility with wxMSW we create the controls with vertical 
 682         // scrollbar always shown unless they have wxTE_RICH style (because 
 683         // Windows text controls always has vert scrollbar but richedit one 
 685         if ( !(style 
& wxTE_RICH
) ) 
 687             style 
|= wxALWAYS_SHOW_SB
; 
 690         // wrapping style: wxTE_DONTWRAP == wxHSCROLL so if it's _not_ given, 
 691         // we won't have horizontal scrollbar automatically, no need to do 
 694         // TODO: support wxTE_NO_VSCROLL (?) 
 696         // create data object for normal multiline or for controls with line 
 698         if ( style 
& wxHSCROLL 
) 
 700             m_data
.mdata 
= new wxTextMultiLineData
; 
 702         else // we must wrap lines if we don't have horizontal scrollbar 
 704             // NB: we can't rely on HasFlag(wxHSCROLL) as the flags can change 
 705             //     later and even wxWindow::Create() itself temporarily resets 
 706             //     wxHSCROLL in wxUniv, so remember that we have a wrapped data 
 707             //     and not just a multi line data in a separate variable 
 709             m_data
.wdata 
= new wxTextWrappedData
; 
 714         // this doesn't make sense for single line controls 
 717         // create data object for single line controls 
 718         m_data
.sdata 
= new wxTextSingleLineData
; 
 721 #if wxUSE_TWO_WINDOWS 
 722     if ((style 
& wxBORDER_MASK
) == 0) 
 723         style 
|= wxBORDER_SUNKEN
; 
 726     if ( !wxControl::Create(parent
, id
, pos
, size
, style
, 
 732     SetCursor(wxCURSOR_IBEAM
); 
 734     if ( style 
& wxTE_MULTILINE 
) 
 736         // we should always have at least one line in a multiline control 
 737         MData().m_lines
.Add(wxEmptyString
); 
 739         if ( !(style 
& wxHSCROLL
) ) 
 741             WData().m_linesData
.Add(new wxWrappedLineData
); 
 742             WData().InvalidateLinesBelow(0); 
 745         // we might support it but it's quite useless and other ports don't 
 747         wxASSERT_MSG( !(style 
& wxTE_PASSWORD
), 
 748                       _T("wxTE_PASSWORD can't be used with multiline ctrls") ); 
 753     SetInitialSize(size
); 
 755     m_isEditable 
= !(style 
& wxTE_READONLY
); 
 758     InitInsertionPoint(); 
 760     // we can't show caret right now as we're not shown yet and so it would 
 761     // result in garbage on the screen - we'll do it after first OnPaint() 
 764     CreateInputHandler(wxINP_HANDLER_TEXTCTRL
); 
 766     wxSizeEvent 
sizeEvent(GetSize(), GetId()); 
 767     GetEventHandler()->ProcessEvent(sizeEvent
); 
 772 wxTextCtrl::~wxTextCtrl() 
 774     delete m_cmdProcessor
; 
 778         if ( IsSingleLine() ) 
 780         else if ( WrapLines() ) 
 787 // ---------------------------------------------------------------------------- 
 789 // ---------------------------------------------------------------------------- 
 791 void wxTextCtrl::DoSetValue(const wxString
& value
, int flags
) 
 793     if ( IsSingleLine() && (value 
== GetValue()) ) 
 799     Replace(0, GetLastPosition(), value
); 
 801     if ( IsSingleLine() ) 
 803         SetInsertionPoint(0); 
 806     if ( flags 
& SetValue_SendEvent 
) 
 807         SendTextUpdatedEvent(); 
 810 const wxArrayString
& wxTextCtrl::GetLines() const 
 812     return MData().m_lines
; 
 815 size_t wxTextCtrl::GetLineCount() const 
 817     return MData().m_lines
.GetCount(); 
 820 wxString 
wxTextCtrl::GetValue() const 
 822     // for multiline controls we don't always store the total value but only 
 823     // recompute it when asked - and to invalidate it we just empty it in 
 825     if ( !IsSingleLine() && m_value
.empty() ) 
 827         // recalculate: note that we always do it for empty multilien control, 
 828         // but then it's so quick that it's not important 
 830         // the first line is special as there is no \n before it, so it's 
 832         const wxArrayString
& lines 
= GetLines(); 
 833         wxTextCtrl 
*self 
= wxConstCast(this, wxTextCtrl
); 
 834         self
->m_value 
<< lines
[0u]; 
 835         size_t count 
= lines
.GetCount(); 
 836         for ( size_t n 
= 1; n 
< count
; n
++ ) 
 838             self
->m_value 
<< _T('\n') << lines
[n
]; 
 845 void wxTextCtrl::Clear() 
 847     SetValue(wxEmptyString
); 
 850 bool wxTextCtrl::ReplaceLine(wxTextCoord line
, 
 851                              const wxString
& text
) 
 855         // first, we have to relayout the line entirely 
 857         // OPT: we might try not to recalc the unchanged part of line 
 859         wxWrappedLineData
& lineData 
= WData().m_linesData
[line
]; 
 861         // if we had some number of rows before, use this number, otherwise 
 862         // just make sure that the test below (rowsNew != rowsOld) will be true 
 864         if ( lineData
.IsValid() ) 
 866             rowsOld 
= lineData
.GetExtraRowCount(); 
 868         else // line wasn't laid out yet 
 870             // assume it changed entirely as we can't do anything better 
 874         // now change the line 
 875         MData().m_lines
[line
] = text
; 
 877         // OPT: we choose to lay it our immediately instead of delaying it 
 878         //      until it is needed because it allows us to avoid invalidating 
 879         //      lines further down if the number of rows didn't chnage, but 
 880         //      maybe we can imporve this even further? 
 881         LayoutLine(line
, lineData
); 
 883         int rowsNew 
= lineData
.GetExtraRowCount(); 
 885         if ( rowsNew 
!= rowsOld 
) 
 887             // we have to update the line wrap marks as this is normally done 
 888             // by LayoutLines() which we bypassed by calling LayoutLine() 
 890             wxTextCoord rowFirst 
= lineData
.GetFirstRow(), 
 891                         rowCount 
= wxMax(rowsOld
, rowsNew
); 
 892             RefreshLineWrapMarks(rowFirst
, rowFirst 
+ rowCount
); 
 894             // next, if this is not the last line, as the number of rows in it 
 895             // changed, we need to shift all the lines below it 
 896             if ( (size_t)line 
< WData().m_linesData
.GetCount() ) 
 898                 // number of rows changed shifting all lines below 
 899                 WData().InvalidateLinesBelow(line 
+ 1); 
 902             // the number of rows changed 
 908         MData().m_lines
[line
] = text
; 
 911     // the number of rows didn't change 
 915 void wxTextCtrl::RemoveLine(wxTextCoord line
) 
 917     MData().m_lines
.RemoveAt(line
); 
 920         // we need to recalculate all the starting rows from this line, but we 
 921         // can avoid doing it if this line was never calculated: this means 
 922         // that we will recalculate all lines below it anyhow later if needed 
 923         if ( WData().IsValidLine(line
) ) 
 925             WData().InvalidateLinesBelow(line
); 
 928         WData().m_linesData
.RemoveAt(line
); 
 932 void wxTextCtrl::InsertLine(wxTextCoord line
, const wxString
& text
) 
 934     MData().m_lines
.Insert(text
, line
); 
 937         WData().m_linesData
.Insert(new wxWrappedLineData
, line
); 
 939         // invalidate everything below it 
 940         WData().InvalidateLinesBelow(line
); 
 944 void wxTextCtrl::Replace(wxTextPos from
, wxTextPos to
, const wxString
& text
) 
 946     wxTextCoord colStart
, colEnd
, 
 950          !PositionToXY(from
, &colStart
, &lineStart
) || 
 951          !PositionToXY(to
, &colEnd
, &lineEnd
) ) 
 953         wxFAIL_MSG(_T("invalid range in wxTextCtrl::Replace")); 
 958 #ifdef WXDEBUG_TEXT_REPLACE 
 959     // a straighforward (but very inefficient) way of calculating what the new 
 961     wxString textTotal 
= GetValue(); 
 962     wxString 
textTotalNew(textTotal
, (size_t)from
); 
 963     textTotalNew 
+= text
; 
 964     if ( (size_t)to 
< textTotal
.length() ) 
 965         textTotalNew 
+= textTotal
.c_str() + (size_t)to
; 
 966 #endif // WXDEBUG_TEXT_REPLACE 
 968     // remember the old selection and reset it immediately: we must do it 
 969     // before calling Refresh(anything) as, at least under GTK, this leads to 
 970     // an _immediate_ repaint (under MSW it is delayed) and hence parts of 
 971     // text would be redrawn as selected if we didn't reset the selection 
 972     int selStartOld 
= m_selStart
, 
 973         selEndOld 
= m_selEnd
; 
 978     if ( IsSingleLine() ) 
 980         // replace the part of the text with the new value 
 981         wxString 
valueNew(m_value
, (size_t)from
); 
 983         // remember it for later use 
 984         wxCoord startNewText 
= GetTextWidth(valueNew
); 
 987         if ( (size_t)to 
< m_value
.length() ) 
 989             valueNew 
+= m_value
.c_str() + (size_t)to
; 
 992         // we usually refresh till the end of line except of the most common case 
 993         // when some text is appended to the end of the string in which case we 
 995         wxCoord widthNewText
; 
 997         if ( (size_t)from 
< m_value
.length() ) 
 999             // refresh till the end of line 
1002         else // text appended, not replaced 
1004             // refresh only the new text 
1005             widthNewText 
= GetTextWidth(text
); 
1010         // force SData().m_colLastVisible update 
1011         SData().m_colLastVisible 
= -1; 
1014         RefreshPixelRange(0, startNewText
, widthNewText
); 
1018         //OPT: special case for replacements inside single line? 
1021            Join all the lines in the replacement range into one string, then 
1022            replace a part of it with the new text and break it into lines again. 
1025         // (0) we want to know if this replacement changes the number of rows 
1026         //     as if it does we need to refresh everything below the changed 
1027         //     text (it will be shifted...) and we can avoid it if there is no 
1029         bool rowsNumberChanged 
= false; 
1032         const wxArrayString
& linesOld 
= GetLines(); 
1035         for ( line 
= lineStart
; line 
<= lineEnd
; line
++ ) 
1037             if ( line 
> lineStart 
) 
1039                 // from the previous line 
1040                 textOrig 
+= _T('\n'); 
1043             textOrig 
+= linesOld
[line
]; 
1046         // we need to append the '\n' for the last line unless there is no 
1048         size_t countOld 
= linesOld
.GetCount(); 
1050         // (2) replace text in the combined string 
1052         // (2a) leave the part before replaced area unchanged 
1053         wxString 
textNew(textOrig
, colStart
); 
1055         // these values will be used to refresh the changed area below 
1056         wxCoord widthNewText
, 
1057                 startNewText 
= GetTextWidth(textNew
); 
1058         if ( (size_t)colStart 
== linesOld
[lineStart
].length() ) 
1060             // text appended, refresh just enough to show the new text 
1061             widthNewText 
= GetTextWidth(text
.BeforeFirst(_T('\n'))); 
1063         else // text inserted, refresh till the end of line 
1068         // (2b) insert new text 
1071         // (2c) and append the end of the old text 
1073         // adjust for index shift: to is relative to colStart, not 0 
1074         size_t toRel 
= (size_t)((to 
- from
) + colStart
); 
1075         if ( toRel 
< textOrig
.length() ) 
1077             textNew 
+= textOrig
.c_str() + toRel
; 
1080         // (3) break it into lines 
1082         wxArrayString lines
; 
1083         const wxChar 
*curLineStart 
= textNew
.c_str(); 
1084         for ( const wxChar 
*p 
= textNew
.c_str(); ; p
++ ) 
1086             // end of line/text? 
1087             if ( !*p 
|| *p 
== _T('\n') ) 
1089                 lines
.Add(wxString(curLineStart
, p
)); 
1093                 curLineStart 
= p 
+ 1; 
1097 #ifdef WXDEBUG_TEXT_REPLACE 
1098         // (3a) all empty tokens should be counted as replacing with "foo" and 
1099         //      with "foo\n" should have different effects 
1100         wxArrayString lines2 
= wxStringTokenize(textNew
, _T("\n"), 
1101                                                 wxTOKEN_RET_EMPTY_ALL
); 
1103         if ( lines2
.IsEmpty() ) 
1105             lines2
.Add(wxEmptyString
); 
1108         wxASSERT_MSG( lines
.GetCount() == lines2
.GetCount(), 
1109                       _T("Replace() broken") ); 
1110         for ( size_t n 
= 0; n 
< lines
.GetCount(); n
++ ) 
1112             wxASSERT_MSG( lines
[n
] == lines2
[n
], _T("Replace() broken") ); 
1114 #endif // WXDEBUG_TEXT_REPLACE 
1116         // (3b) special case: if we replace everything till the end we need to 
1117         //      keep an empty line or the lines would disappear completely 
1118         //      (this also takes care of never leaving m_lines empty) 
1119         if ( ((size_t)lineEnd 
== countOld 
- 1) && lines
.IsEmpty() ) 
1121             lines
.Add(wxEmptyString
); 
1124         size_t nReplaceCount 
= lines
.GetCount(), 
1127         // (4) merge into the array 
1130         for ( line 
= lineStart
; line 
<= lineEnd
; line
++, nReplaceLine
++ ) 
1132             if ( nReplaceLine 
< nReplaceCount 
) 
1134                 // we have the replacement line for this one 
1135                 if ( ReplaceLine(line
, lines
[nReplaceLine
]) ) 
1137                     rowsNumberChanged 
= true; 
1140                 UpdateMaxWidth(line
); 
1142             else // no more replacement lines 
1144                 // (4b) delete all extra lines (note that we need to delete 
1145                 //      them backwards because indices shift while we do it) 
1146                 bool deletedLongestLine 
= false; 
1147                 for ( wxTextCoord lineDel 
= lineEnd
; lineDel 
>= line
; lineDel
-- ) 
1149                     if ( lineDel 
== MData().m_lineLongest 
) 
1151                         // we will need to recalc the max line width 
1152                         deletedLongestLine 
= true; 
1155                     RemoveLine(lineDel
); 
1158                 if ( deletedLongestLine 
) 
1163                 // even the line number changed 
1164                 rowsNumberChanged 
= true; 
1166                 // update line to exit the loop 
1171         // (4c) insert the new lines 
1172         if ( nReplaceLine 
< nReplaceCount 
) 
1174             // even the line number changed 
1175             rowsNumberChanged 
= true; 
1179                 InsertLine(++lineEnd
, lines
[nReplaceLine
++]); 
1181                 UpdateMaxWidth(lineEnd
); 
1183             while ( nReplaceLine 
< nReplaceCount 
); 
1186         // (5) now refresh the changed area 
1188         // update the (cached) last position first as refresh functions use it 
1189         m_posLast 
+= text
.length() - to 
+ from
; 
1191         // we may optimize refresh if the number of rows didn't change - but if 
1192         // it did we have to refresh everything below the part we chanegd as 
1193         // well as it might have moved 
1194         if ( !rowsNumberChanged 
) 
1196             // refresh the line we changed 
1199                 RefreshPixelRange(lineStart
++, startNewText
, widthNewText
); 
1203                 //OPT: we shouldn't refresh the unchanged part of the line in 
1204                 //     this case, but instead just refresh the tail of it - the 
1205                 //     trouble is that we don't know here where does this tail 
1209             // number of rows didn't change, refresh the updated rows and the 
1211             if ( lineStart 
<= lineEnd 
) 
1212                 RefreshLineRange(lineStart
, lineEnd
); 
1214         else // rows number did change 
1218                 // refresh only part of the first line 
1219                 RefreshPixelRange(lineStart
++, startNewText
, widthNewText
); 
1221             //else: we have to refresh everything as some part of the text 
1222             //      could be in the previous row before but moved to the next 
1223             //      one now (due to word wrap) 
1225             wxTextCoord lineEnd 
= GetLines().GetCount() - 1; 
1226             if ( lineStart 
<= lineEnd 
) 
1227                 RefreshLineRange(lineStart
, lineEnd
); 
1229             // refresh text rect left below 
1230             RefreshLineRange(lineEnd 
+ 1, 0); 
1232             // the vert scrollbar might [dis]appear 
1233             MData().m_updateScrollbarY 
= true; 
1236         // must recalculate it - will do later 
1240 #ifdef WXDEBUG_TEXT_REPLACE 
1241     // optimized code above should give the same result as straightforward 
1242     // computation in the beginning 
1243     wxASSERT_MSG( GetValue() == textTotalNew
, _T("error in Replace()") ); 
1244 #endif // WXDEBUG_TEXT_REPLACE 
1246     // update the current position: note that we always put the cursor at the 
1247     // end of the replacement text 
1248     DoSetInsertionPoint(from 
+ text
.length()); 
1250     // and the selection: this is complicated by the fact that selection coords 
1251     // must be first updated to reflect change in text coords, i.e. if we had 
1252     // selection from 17 to 19 and we just removed this range, we don't have to 
1253     // refresh anything, so we can't just use ClearSelection() here 
1254     if ( selStartOld 
!= -1 ) 
1256         // refresh the parst of the selection outside the changed text (which 
1257         // we already refreshed) 
1258         if ( selStartOld 
< from 
) 
1259             RefreshTextRange(selStartOld
, from
); 
1260         if ( to 
< selEndOld 
) 
1261             RefreshTextRange(to
, selEndOld
); 
1265     // now call it to do the rest (not related to refreshing) 
1269 void wxTextCtrl::Remove(wxTextPos from
, wxTextPos to
) 
1271     // Replace() only works with correctly ordered arguments, so exchange them 
1273     OrderPositions(from
, to
); 
1275     Replace(from
, to
, wxEmptyString
); 
1278 void wxTextCtrl::WriteText(const wxString
& text
) 
1280     // replace the selection with the new text 
1283     Replace(m_curPos
, m_curPos
, text
); 
1286 void wxTextCtrl::AppendText(const wxString
& text
) 
1288     SetInsertionPointEnd(); 
1292 // ---------------------------------------------------------------------------- 
1294 // ---------------------------------------------------------------------------- 
1296 void wxTextCtrl::SetInsertionPoint(wxTextPos pos
) 
1298     wxCHECK_RET( pos 
>= 0 && pos 
<= GetLastPosition(), 
1299                  _T("insertion point position out of range") ); 
1301     // don't do anything if it didn't change 
1302     if ( pos 
!= m_curPos 
) 
1304         DoSetInsertionPoint(pos
); 
1307     if ( !IsSingleLine() ) 
1309         // moving cursor should reset the stored abscissa (even if the cursor 
1310         // position didn't actually change!) 
1311         MData().m_xCaret 
= -1; 
1317 void wxTextCtrl::InitInsertionPoint() 
1319     // so far always put it in the beginning 
1320     DoSetInsertionPoint(0); 
1322     // this will also set the selection anchor correctly 
1326 void wxTextCtrl::MoveInsertionPoint(wxTextPos pos
) 
1328     wxASSERT_MSG( pos 
>= 0 && pos 
<= GetLastPosition(), 
1329                  _T("DoSetInsertionPoint() can only be called with valid pos") ); 
1332     PositionToXY(m_curPos
, &m_curCol
, &m_curRow
); 
1335 void wxTextCtrl::DoSetInsertionPoint(wxTextPos pos
) 
1337     MoveInsertionPoint(pos
); 
1342 void wxTextCtrl::SetInsertionPointEnd() 
1344     SetInsertionPoint(GetLastPosition()); 
1347 wxTextPos 
wxTextCtrl::GetInsertionPoint() const 
1352 wxTextPos 
wxTextCtrl::GetLastPosition() const 
1355     if ( IsSingleLine() ) 
1357         pos 
= m_value
.length(); 
1363         size_t nLineCount 
= GetLineCount(); 
1364         for ( size_t nLine 
= 0; nLine 
< nLineCount
; nLine
++ ) 
1366             // +1 is because the positions at the end of this line and of the 
1367             // start of the next one are different 
1368             pos 
+= GetLines()[nLine
].length() + 1; 
1373             // the last position is at the end of the last line, not in the 
1374             // beginning of the next line after it 
1378         // more probable reason of this would be to forget to update m_posLast 
1379         wxASSERT_MSG( pos 
== m_posLast
, _T("bug in GetLastPosition()") ); 
1380 #endif // WXDEBUG_TEXT 
1388 // ---------------------------------------------------------------------------- 
1390 // ---------------------------------------------------------------------------- 
1392 void wxTextCtrl::GetSelection(wxTextPos
* from
, wxTextPos
* to
) const 
1400 wxString 
wxTextCtrl::GetSelectionText() const 
1404     if ( HasSelection() ) 
1406         if ( IsSingleLine() ) 
1408             sel 
= m_value
.Mid(m_selStart
, m_selEnd 
- m_selStart
); 
1412             wxTextCoord colStart
, lineStart
, 
1414             PositionToXY(m_selStart
, &colStart
, &lineStart
); 
1415             PositionToXY(m_selEnd
, &colEnd
, &lineEnd
); 
1417             // as always, we need to check for the special case when the start 
1418             // and end line are the same 
1419             if ( lineEnd 
== lineStart 
) 
1421                 sel 
= GetLines()[lineStart
].Mid(colStart
, colEnd 
- colStart
); 
1423             else // sel on multiple lines 
1425                 // take the end of the first line 
1426                 sel 
= GetLines()[lineStart
].c_str() + colStart
; 
1429                 // all intermediate ones 
1430                 for ( wxTextCoord line 
= lineStart 
+ 1; line 
< lineEnd
; line
++ ) 
1432                     sel 
<< GetLines()[line
] << _T('\n'); 
1435                 // and the start of the last one 
1436                 sel 
+= GetLines()[lineEnd
].Left(colEnd
); 
1444 void wxTextCtrl::SetSelection(wxTextPos from
, wxTextPos to
) 
1446     // selecting till -1 is the same as selecting to the end 
1449         // and selecting (-1, -1) range is the same as selecting everything, by 
1453         to 
= GetLastPosition(); 
1456     if ( from 
== -1 || to 
== from 
) 
1460     else // valid sel range 
1462         // remember the 'to' position as the current position, used to move the 
1463         // caret there later 
1464         wxTextPos toOrig 
= to
; 
1466         OrderPositions(from
, to
); 
1468         wxCHECK_RET( to 
<= GetLastPosition(), 
1469                      _T("invalid range in wxTextCtrl::SetSelection") ); 
1471         if ( from 
!= m_selStart 
|| to 
!= m_selEnd 
) 
1473             // we need to use temp vars as RefreshTextRange() may call DoDraw() 
1474             // directly and so m_selStart/End must be reset by then 
1475             wxTextPos selStartOld 
= m_selStart
, 
1476                       selEndOld 
= m_selEnd
; 
1481             wxLogTrace(_T("text"), _T("Selection range is %ld-%ld"), 
1482                        m_selStart
, m_selEnd
); 
1484             // refresh only the part of text which became (un)selected if 
1486             if ( selStartOld 
== m_selStart 
) 
1488                 RefreshTextRange(selEndOld
, m_selEnd
); 
1490             else if ( selEndOld 
== m_selEnd 
) 
1492                 RefreshTextRange(m_selStart
, selStartOld
); 
1496                 // OPT: could check for other cases too but it is probably not 
1497                 //      worth it as the two above are the most common ones 
1498                 if ( selStartOld 
!= -1 ) 
1499                     RefreshTextRange(selStartOld
, selEndOld
); 
1500                 if ( m_selStart 
!= -1 ) 
1501                     RefreshTextRange(m_selStart
, m_selEnd
); 
1504             // we need to fully repaint the invalidated areas of the window 
1505             // before scrolling it (from DoSetInsertionPoint which is typically 
1506             // called after SetSelection()), otherwise they may stay unpainted 
1507             m_targetWindow
->Update(); 
1509         //else: nothing to do 
1511         // the insertion point is put at the location where the caret was moved 
1512         DoSetInsertionPoint(toOrig
); 
1516 void wxTextCtrl::ClearSelection() 
1518     if ( HasSelection() ) 
1520         // we need to use temp vars as RefreshTextRange() may call DoDraw() 
1521         // directly (see above as well) 
1522         wxTextPos selStart 
= m_selStart
, 
1525         // no selection any more 
1529         // refresh the old selection 
1530         RefreshTextRange(selStart
, selEnd
); 
1533     // the anchor should be moved even if there was no selection previously 
1534     m_selAnchor 
= m_curPos
; 
1537 void wxTextCtrl::RemoveSelection() 
1539     if ( !HasSelection() ) 
1542     Remove(m_selStart
, m_selEnd
); 
1545 bool wxTextCtrl::GetSelectedPartOfLine(wxTextCoord line
, 
1546                                        wxTextPos 
*start
, wxTextPos 
*end
) const 
1553     if ( !HasSelection() ) 
1555         // no selection at all, hence no selection in this line 
1559     wxTextCoord lineStart
, colStart
; 
1560     PositionToXY(m_selStart
, &colStart
, &lineStart
); 
1561     if ( lineStart 
> line 
) 
1563         // this line is entirely above the selection 
1567     wxTextCoord lineEnd
, colEnd
; 
1568     PositionToXY(m_selEnd
, &colEnd
, &lineEnd
); 
1569     if ( lineEnd 
< line 
) 
1571         // this line is entirely below the selection 
1575     if ( line 
== lineStart 
) 
1580             *end 
= lineEnd 
== lineStart 
? colEnd 
: GetLineLength(line
); 
1582     else if ( line 
== lineEnd 
) 
1585             *start 
= lineEnd 
== lineStart 
? colStart 
: 0; 
1589     else // the line is entirely inside the selection 
1594             *end 
= GetLineLength(line
); 
1600 // ---------------------------------------------------------------------------- 
1602 // ---------------------------------------------------------------------------- 
1604 bool wxTextCtrl::IsModified() const 
1606     return m_isModified
; 
1609 bool wxTextCtrl::IsEditable() const 
1611     // disabled control can never be edited 
1612     return m_isEditable 
&& IsEnabled(); 
1615 void wxTextCtrl::MarkDirty() 
1617     m_isModified 
= true; 
1620 void wxTextCtrl::DiscardEdits() 
1622     m_isModified 
= false; 
1625 void wxTextCtrl::SetEditable(bool editable
) 
1627     if ( editable 
!= m_isEditable 
) 
1629         m_isEditable 
= editable
; 
1631         // the caret (dis)appears 
1634         // the appearance of the control might have changed 
1639 // ---------------------------------------------------------------------------- 
1640 // col/lines <-> position correspondence 
1641 // ---------------------------------------------------------------------------- 
1644     A few remarks about this stuff: 
1646     o   The numbering of the text control columns/rows starts from 0. 
1647     o   Start of first line is position 0, its last position is line.length() 
1648     o   Start of the next line is the last position of the previous line + 1 
1651 int wxTextCtrl::GetLineLength(wxTextCoord line
) const 
1653     if ( IsSingleLine() ) 
1655         wxASSERT_MSG( line 
== 0, _T("invalid GetLineLength() parameter") ); 
1657         return m_value
.length(); 
1661         wxCHECK_MSG( (size_t)line 
< GetLineCount(), -1, 
1662                      _T("line index out of range") ); 
1664         return GetLines()[line
].length(); 
1668 wxString 
wxTextCtrl::GetLineText(wxTextCoord line
) const 
1670     if ( IsSingleLine() ) 
1672         wxASSERT_MSG( line 
== 0, _T("invalid GetLineLength() parameter") ); 
1678         //this is called during DoGetBestSize 
1679         if (line 
== 0 && GetLineCount() == 0) return wxEmptyString 
; 
1681         wxCHECK_MSG( (size_t)line 
< GetLineCount(), wxEmptyString
, 
1682                      _T("line index out of range") ); 
1684         return GetLines()[line
]; 
1688 int wxTextCtrl::GetNumberOfLines() const 
1690     // there is always 1 line, even if the text is empty 
1691     return IsSingleLine() ? 1 : GetLineCount(); 
1694 wxTextPos 
wxTextCtrl::XYToPosition(wxTextCoord x
, wxTextCoord y
) const 
1696     // note that this method should accept any values of x and y and return -1 
1697     // if they are out of range 
1698     if ( IsSingleLine() ) 
1700         return ( x 
> GetLastPosition() || y 
> 0 ) ? wxOutOfRangeTextCoord 
: x
; 
1704         if ( (size_t)y 
>= GetLineCount() ) 
1706             // this position is below the text 
1707             return GetLastPosition(); 
1711         for ( size_t nLine 
= 0; nLine 
< (size_t)y
; nLine
++ ) 
1713             // +1 is because the positions at the end of this line and of the 
1714             // start of the next one are different 
1715             pos 
+= GetLines()[nLine
].length() + 1; 
1718         // take into account also the position in line 
1719         if ( (size_t)x 
> GetLines()[y
].length() ) 
1721             // don't return position in the next line 
1722             x 
= GetLines()[y
].length(); 
1729 bool wxTextCtrl::PositionToXY(wxTextPos pos
, 
1730                               wxTextCoord 
*x
, wxTextCoord 
*y
) const 
1732     if ( IsSingleLine() ) 
1734         if ( (size_t)pos 
> m_value
.length() ) 
1746         wxTextPos posCur 
= 0; 
1747         size_t nLineCount 
= GetLineCount(); 
1748         for ( size_t nLine 
= 0; nLine 
< nLineCount
; nLine
++ ) 
1750             // +1 is because the start the start of the next line is one 
1751             // position after the end of this one 
1752             wxTextPos posNew 
= posCur 
+ GetLines()[nLine
].length() + 1; 
1755                 // we've found the line, now just calc the column 
1763                 wxASSERT_MSG( XYToPosition(pos 
- posCur
, nLine
) == pos
, 
1764                               _T("XYToPosition() or PositionToXY() broken") ); 
1765 #endif // WXDEBUG_TEXT 
1769             else // go further down 
1775         // beyond the last line 
1780 wxTextCoord 
wxTextCtrl::GetRowsPerLine(wxTextCoord line
) const 
1782     // a normal line has one row 
1783     wxTextCoord numRows 
= 1; 
1787         // add the number of additional rows 
1788         numRows 
+= WData().m_linesData
[line
].GetExtraRowCount(); 
1794 wxTextCoord 
wxTextCtrl::GetRowCount() const 
1796     wxTextCoord count 
= GetLineCount(); 
1801         count 
= GetFirstRowOfLine(count 
- 1) + 
1802                     WData().m_linesData
[count 
- 1].GetRowCount(); 
1808 wxTextCoord 
wxTextCtrl::GetRowAfterLine(wxTextCoord line
) const 
1813     if ( !WData().IsValidLine(line
) ) 
1818     return WData().m_linesData
[line
].GetNextRow(); 
1821 wxTextCoord 
wxTextCtrl::GetFirstRowOfLine(wxTextCoord line
) const 
1826     if ( !WData().IsValidLine(line
) ) 
1831     return WData().m_linesData
[line
].GetFirstRow(); 
1834 bool wxTextCtrl::PositionToLogicalXY(wxTextPos pos
, 
1836                                      wxCoord 
*yOut
) const 
1838     wxTextCoord col
, line
; 
1840     // optimization for special (but common) case when we already have the col 
1842     if ( pos 
== m_curPos 
) 
1847     else // must really calculate col/line from pos 
1849         if ( !PositionToXY(pos
, &col
, &line
) ) 
1853     int hLine 
= GetLineHeight(); 
1855     wxString textLine 
= GetLineText(line
); 
1856     if ( IsSingleLine() || !WrapLines() ) 
1858         x 
= GetTextWidth(textLine
.Left(col
)); 
1861     else // difficult case: multline control with line wrap 
1863         y 
= GetFirstRowOfLine(line
); 
1865         wxTextCoord colRowStart
; 
1866         y 
+= GetRowInLine(line
, col
, &colRowStart
); 
1870         // x is the width of the text before this position in this row 
1871         x 
= GetTextWidth(textLine
.Mid(colRowStart
, col 
- colRowStart
)); 
1882 bool wxTextCtrl::PositionToDeviceXY(wxTextPos pos
, 
1884                                     wxCoord 
*yOut
) const 
1887     if ( !PositionToLogicalXY(pos
, &x
, &y
) ) 
1890     // finally translate the logical text rect coords into physical client 
1892     CalcScrolledPosition(m_rectText
.x 
+ x
, m_rectText
.y 
+ y
, xOut
, yOut
); 
1897 wxPoint 
wxTextCtrl::GetCaretPosition() const 
1899     wxCoord xCaret
, yCaret
; 
1900     if ( !PositionToDeviceXY(m_curPos
, &xCaret
, &yCaret
) ) 
1902         wxFAIL_MSG( _T("Caret can't be beyond the text!") ); 
1905     return wxPoint(xCaret
, yCaret
); 
1908 // pos may be -1 to show the current position 
1909 void wxTextCtrl::ShowPosition(wxTextPos pos
) 
1911     bool showCaret 
= GetCaret() && GetCaret()->IsVisible(); 
1915     if ( IsSingleLine() ) 
1917         ShowHorzPosition(GetTextWidth(m_value
.Left(pos
))); 
1919     else if ( MData().m_scrollRangeX 
|| MData().m_scrollRangeY 
) // multiline with scrollbars 
1922         GetViewStart(&xStart
, &yStart
); 
1928         PositionToLogicalXY(pos
, &x
, &y
); 
1930         wxRect rectText 
= GetRealTextArea(); 
1932         // scroll the position vertically into view: if it is currently above 
1933         // it, make it the first one, otherwise the last one 
1934         if ( MData().m_scrollRangeY 
) 
1936             y 
/= GetLineHeight(); 
1942             else // we are currently in or below the view area 
1944                 // find the last row currently shown 
1949                     // to find the last row we need to use the generic HitTest 
1952                     // OPT this is a bit silly: we undo this in HitTest(), so 
1953                     //     it would be better to factor out the common 
1954                     //     functionality into a separate function (OTOH it 
1955                     //     won't probably save us that much) 
1956                     wxPoint 
pt(0, rectText
.height 
- 1); 
1957                     pt 
+= GetClientAreaOrigin(); 
1958                     pt 
+= m_rectText
.GetPosition(); 
1959                     HitTest(pt
, &col
, &yEnd
); 
1961                     // find the row inside the line 
1962                     yEnd 
= GetFirstRowOfLine(yEnd
) + GetRowInLine(yEnd
, col
); 
1966                     // finding the last line is easy if each line has exactly 
1968                     yEnd 
= yStart 
+ rectText
.height 
/ GetLineHeight(); 
1973                     // scroll down: the current item should appear at the 
1974                     // bottom of the view 
1975                     Scroll(0, y 
- (yEnd 
- yStart
)); 
1980         // scroll the position horizontally into view 
1982         // we follow what I believe to be Windows behaviour here, that is if 
1983         // the position is already entirely in the view we do nothing, but if 
1984         // we do have to scroll the window to bring it into view, we scroll it 
1985         // not just enough to show the position but slightly more so that this 
1986         // position is at 1/3 of the window width from the closest border to it 
1987         // (I'm not sure that Windows does exactly this but it looks like this) 
1988         if ( MData().m_scrollRangeX 
) 
1990             // unlike for the rows, xStart doesn't correspond to the starting 
1991             // column as they all have different widths, so we need to 
1992             // translate everything to pixels 
1994             // we want the text between x and x2 be entirely inside the view 
1995             // (i.e. the current character) 
1997             // make xStart the first visible pixel (and not position) 
1998             int wChar 
= GetAverageWidth(); 
2003                 // we want the position of this column be 1/3 to the right of 
2005                 x 
-= rectText
.width 
/ 3; 
2008                 Scroll(x 
/ wChar
, y
); 
2010             else // maybe we're beyond the right border of the view? 
2012                 wxTextCoord col
, row
; 
2013                 if ( PositionToXY(pos
, &col
, &row
) ) 
2015                     wxString lineText 
= GetLineText(row
); 
2016                     wxCoord x2 
= x 
+ GetTextWidth(lineText
[(size_t)col
]); 
2017                     if ( x2 
> xStart 
+ rectText
.width 
) 
2019                         // we want the position of this column be 1/3 to the 
2020                         // left of the right edge, i.e. 2/3 right of the left 
2022                         x2 
-= (2*rectText
.width
)/3; 
2025                         Scroll(x2 
/ wChar
, row
); 
2031     //else: multiline but no scrollbars, hence nothing to do 
2037 // ---------------------------------------------------------------------------- 
2039 // ---------------------------------------------------------------------------- 
2042     TODO: we could have (easy to do) vi-like options for word movement, i.e. 
2043           distinguish between inlusive/exclusive words and between words and 
2044           WORDS (in vim sense) and also, finally, make the set of characters 
2045           which make up a word configurable - currently we use the exclusive 
2046           WORDS only (coincidentally, this is what Windows edit control does) 
2048           For future references, here is what vim help says: 
2050           A word consists of a sequence of letters, digits and underscores, or 
2051           a sequence of other non-blank characters, separated with white space 
2052           (spaces, tabs, <EOL>).  This can be changed with the 'iskeyword' 
2055           A WORD consists of a sequence of non-blank characters, separated with 
2056           white space.  An empty line is also considered to be a word and a 
2060 static inline bool IsWordChar(wxChar ch
) 
2062     return !wxIsspace(ch
); 
2065 wxTextPos 
wxTextCtrl::GetWordStart() const 
2067     if ( m_curPos 
== -1 || m_curPos 
== 0 ) 
2070     if ( m_curCol 
== 0 ) 
2072         // go to the end of the previous line 
2073         return m_curPos 
- 1; 
2076     // it shouldn't be possible to learn where the word starts in the password 
2081     // start at the previous position 
2082     const wxChar 
*p0 
= GetLineText(m_curRow
).c_str(); 
2083     const wxChar 
*p 
= p0 
+ m_curCol 
- 1; 
2085     // find the end of the previous word 
2086     while ( (p 
> p0
) && !IsWordChar(*p
) ) 
2089     // now find the beginning of this word 
2090     while ( (p 
> p0
) && IsWordChar(*p
) ) 
2093     // we might have gone too far 
2094     if ( !IsWordChar(*p
) ) 
2097     return (m_curPos 
- m_curCol
) + p 
- p0
; 
2100 wxTextPos 
wxTextCtrl::GetWordEnd() const 
2102     if ( m_curPos 
== -1 ) 
2105     wxString line 
= GetLineText(m_curRow
); 
2106     if ( (size_t)m_curCol 
== line
.length() ) 
2108         // if we're on the last position in the line, go to the next one - if 
2110         wxTextPos pos 
= m_curPos
; 
2111         if ( pos 
< GetLastPosition() ) 
2117     // it shouldn't be possible to learn where the word ends in the password 
2120         return GetLastPosition(); 
2122     // start at the current position 
2123     const wxChar 
*p0 
= line
.c_str(); 
2124     const wxChar 
*p 
= p0 
+ m_curCol
; 
2126     // find the start of the next word 
2127     while ( *p 
&& !IsWordChar(*p
) ) 
2130     // now find the end of it 
2131     while ( *p 
&& IsWordChar(*p
) ) 
2134     // and find the start of the next word 
2135     while ( *p 
&& !IsWordChar(*p
) ) 
2138     return (m_curPos 
- m_curCol
) + p 
- p0
; 
2141 // ---------------------------------------------------------------------------- 
2143 // ---------------------------------------------------------------------------- 
2145 void wxTextCtrl::Copy() 
2148     if ( HasSelection() ) 
2150         wxClipboardLocker clipLock
; 
2152         // wxTextFile::Translate() is needed to transform all '\n' into "\r\n" 
2153         wxString text 
= wxTextFile::Translate(GetTextToShow(GetSelectionText())); 
2154         wxTextDataObject 
*data 
= new wxTextDataObject(text
); 
2155         wxTheClipboard
->SetData(data
); 
2157 #endif // wxUSE_CLIPBOARD 
2160 void wxTextCtrl::Cut() 
2165 bool wxTextCtrl::DoCut() 
2167     if ( !HasSelection() ) 
2177 void wxTextCtrl::Paste() 
2182 bool wxTextCtrl::DoPaste() 
2185     wxClipboardLocker clipLock
; 
2187     wxTextDataObject data
; 
2188     if ( wxTheClipboard
->IsSupported(data
.GetFormat()) 
2189             && wxTheClipboard
->GetData(data
) ) 
2191         // reverse transformation: '\r\n\" -> '\n' 
2192         wxString text 
= wxTextFile::Translate(data
.GetText(), 
2193                                               wxTextFileType_Unix
); 
2194         if ( !text
.empty() ) 
2201 #endif // wxUSE_CLIPBOARD 
2206 // ---------------------------------------------------------------------------- 
2208 // ---------------------------------------------------------------------------- 
2210 wxTextCtrlInsertCommand 
* 
2211 wxTextCtrlCommandProcessor::IsInsertCommand(wxCommand 
*command
) 
2213     return (wxTextCtrlInsertCommand 
*) 
2214             (command 
&& (command
->GetName() == wxTEXT_COMMAND_INSERT
) 
2218 void wxTextCtrlCommandProcessor::Store(wxCommand 
*command
) 
2220     wxTextCtrlInsertCommand 
*cmdIns 
= IsInsertCommand(command
); 
2223         if ( IsCompressing() ) 
2225             wxTextCtrlInsertCommand 
* 
2226                 cmdInsLast 
= IsInsertCommand(GetCurrentCommand()); 
2228             // it is possible that we don't have any last command at all if, 
2229             // for example, it was undone since the last Store(), so deal with 
2233                 cmdInsLast
->Append(cmdIns
); 
2237                 // don't need to call the base class version 
2242         // append the following insert commands to this one 
2243         m_compressInserts 
= true; 
2245         // let the base class version will do the job normally 
2247     else // not an insert command 
2249         // stop compressing insert commands - this won't work with the last 
2250         // command not being an insert one anyhow 
2253         // let the base class version will do the job normally 
2256     wxCommandProcessor::Store(command
); 
2259 void wxTextCtrlInsertCommand::Append(wxTextCtrlInsertCommand 
*other
) 
2261     m_text 
+= other
->m_text
; 
2264 bool wxTextCtrlInsertCommand::CanUndo() const 
2266     return m_from 
!= -1; 
2269 bool wxTextCtrlInsertCommand::Do(wxTextCtrl 
*text
) 
2271     // the text is going to be inserted at the current position, remember where 
2273     m_from 
= text
->GetInsertionPoint(); 
2275     // and now do insert it 
2276     text
->WriteText(m_text
); 
2281 bool wxTextCtrlInsertCommand::Undo(wxTextCtrl 
*text
) 
2283     wxCHECK_MSG( CanUndo(), false, _T("impossible to undo insert cmd") ); 
2285     // remove the text from where we inserted it 
2286     text
->Remove(m_from
, m_from 
+ m_text
.length()); 
2291 bool wxTextCtrlRemoveCommand::CanUndo() const 
2293     // if we were executed, we should have the text we removed 
2294     return !m_textDeleted
.empty(); 
2297 bool wxTextCtrlRemoveCommand::Do(wxTextCtrl 
*text
) 
2299     text
->SetSelection(m_from
, m_to
); 
2300     m_textDeleted 
= text
->GetSelectionText(); 
2301     text
->RemoveSelection(); 
2306 bool wxTextCtrlRemoveCommand::Undo(wxTextCtrl 
*text
) 
2308     // it is possible that the text was deleted and that we can't restore text 
2309     // at the same position we removed it any more 
2310     wxTextPos posLast 
= text
->GetLastPosition(); 
2311     text
->SetInsertionPoint(m_from 
> posLast 
? posLast 
: m_from
); 
2312     text
->WriteText(m_textDeleted
); 
2317 void wxTextCtrl::Undo() 
2319     // the caller must check it 
2320     wxASSERT_MSG( CanUndo(), _T("can't call Undo() if !CanUndo()") ); 
2322     m_cmdProcessor
->Undo(); 
2325 void wxTextCtrl::Redo() 
2327     // the caller must check it 
2328     wxASSERT_MSG( CanRedo(), _T("can't call Undo() if !CanUndo()") ); 
2330     m_cmdProcessor
->Redo(); 
2333 bool wxTextCtrl::CanUndo() const 
2335     return IsEditable() && m_cmdProcessor
->CanUndo(); 
2338 bool wxTextCtrl::CanRedo() const 
2340     return IsEditable() && m_cmdProcessor
->CanRedo(); 
2343 // ---------------------------------------------------------------------------- 
2345 // ---------------------------------------------------------------------------- 
2347 wxSize 
wxTextCtrl::DoGetBestClientSize() const 
2349     // when we're called for the very first time from Create() we must 
2350     // calculate the font metrics here because we can't do it before calling 
2351     // Create() (there is no window yet and wxGTK crashes) but we need them 
2353     if ( m_heightLine 
== -1 ) 
2355         wxConstCast(this, wxTextCtrl
)->RecalcFontMetrics(); 
2359     GetTextExtent(GetTextToShow(GetLineText(0)), &w
, &h
); 
2361     int wChar 
= GetAverageWidth(), 
2362         hChar 
= GetLineHeight(); 
2364     int widthMin 
= wxMax(10*wChar
, 100); 
2370     if ( !IsSingleLine() ) 
2372         // let the control have a reasonable number of lines 
2373         int lines 
= GetNumberOfLines(); 
2376         else if ( lines 
> 10 ) 
2383     rectText
.height 
= h
; 
2384     wxRect rectTotal 
= GetRenderer()->GetTextTotalArea(this, rectText
); 
2385     return wxSize(rectTotal
.width
, rectTotal
.height
); 
2388 void wxTextCtrl::UpdateTextRect() 
2390     wxRect 
rectTotal(GetClientSize()); 
2391     wxCoord 
*extraSpace 
= WrapLines() ? &WData().m_widthMark 
: NULL
; 
2392     m_rectText 
= GetRenderer()->GetTextClientArea(this, rectTotal
, extraSpace
); 
2394     // code elsewhere is confused by negative rect size 
2395     if ( m_rectText
.width 
<= 0 ) 
2396         m_rectText
.width 
= 1; 
2397     if ( m_rectText
.height 
<= 0 ) 
2398         m_rectText
.height 
= 1; 
2400     if ( !IsSingleLine() ) 
2402         // invalidate it so that GetRealTextArea() will recalc it 
2403         MData().m_rectTextReal
.width 
= 0; 
2405         // only scroll this rect when the window is scrolled: note that we have 
2406         // to scroll not only the text but the line wrap marks too if we show 
2408         wxRect rectText 
= GetRealTextArea(); 
2409         if ( extraSpace 
&& *extraSpace 
) 
2411             rectText
.width 
+= *extraSpace
; 
2413         SetTargetRect(rectText
); 
2415         // relayout all lines 
2418             WData().m_rowFirstInvalid 
= 0; 
2420             // increase timestamp: this means that the lines which had been 
2421             // laid out before will be relaid out the next time LayoutLines() 
2422             // is called because their timestamp will be smaller than the 
2424             WData().m_timestamp
++; 
2428     UpdateLastVisible(); 
2431 void wxTextCtrl::UpdateLastVisible() 
2433     // this method is only used for horizontal "scrollbarless" scrolling which 
2434     // is used only with single line controls 
2435     if ( !IsSingleLine() ) 
2438     // use (efficient) HitTestLine to find the last visible character 
2439     wxString text 
= m_value
.Mid((size_t)SData().m_colStart 
/* to the end */); 
2441     switch ( HitTestLine(text
, m_rectText
.width
, &col
) ) 
2443         case wxTE_HT_BEYOND
: 
2444             // everything is visible 
2445             SData().m_ofsHorz 
= 0; 
2447             SData().m_colStart 
= 0; 
2448             SData().m_colLastVisible 
= text
.length(); 
2450             // calculate it below 
2451             SData().m_posLastVisible 
= -1; 
2455         case wxTE_HT_BEFORE: 
2459             wxFAIL_MSG(_T("unexpected HitTestLine() return value")); 
2462         case wxTE_HT_ON_TEXT
: 
2465                 // the last entirely seen character is the previous one because 
2466                 // this one is only partly visible - unless the width of the 
2467                 // string is exactly the max width 
2468                 SData().m_posLastVisible 
= GetTextWidth(text
.Truncate(col 
+ 1)); 
2469                 if ( SData().m_posLastVisible 
> m_rectText
.width 
) 
2471                     // this character is not entirely visible, take the 
2476                     SData().m_posLastVisible 
= -1; 
2478                 //else: we can just see it 
2480                 SData().m_colLastVisible 
= col
; 
2485     // calculate the width of the text really shown 
2486     if ( SData().m_posLastVisible 
== -1 ) 
2488         SData().m_posLastVisible 
= GetTextWidth(text
.Truncate(SData().m_colLastVisible 
+ 1)); 
2491     // current value is relative the start of the string text which starts at 
2492     // SData().m_colStart, we need an absolute offset into string 
2493     SData().m_colLastVisible 
+= SData().m_colStart
; 
2495     wxLogTrace(_T("text"), _T("Last visible column/position is %d/%ld"), 
2496                (int) SData().m_colLastVisible
, (long) SData().m_posLastVisible
); 
2499 void wxTextCtrl::OnSize(wxSizeEvent
& event
) 
2503     if ( !IsSingleLine() ) 
2506         // update them immediately because if we are called for the first time, 
2507         // we need to create them in order for the base class version to 
2508         // position the scrollbars correctly - if we don't do it now, it won't 
2509         // happen at all if we don't get more size events 
2513         MData().m_updateScrollbarX 
= 
2514         MData().m_updateScrollbarY 
= true; 
2520 wxCoord 
wxTextCtrl::GetTotalWidth() const 
2523     CalcUnscrolledPosition(m_rectText
.width
, 0, &w
, NULL
); 
2527 wxCoord 
wxTextCtrl::GetTextWidth(const wxString
& text
) const 
2530     GetTextExtent(GetTextToShow(text
), &w
, NULL
); 
2534 wxRect 
wxTextCtrl::GetRealTextArea() const 
2536     // for single line text control it's just the same as text rect 
2537     if ( IsSingleLine() ) 
2540     // the real text area always holds an entire number of lines, so the only 
2541     // difference with the text area is a narrow strip along the bottom border 
2542     wxRect rectText 
= MData().m_rectTextReal
; 
2543     if ( !rectText
.width 
) 
2546         rectText 
= m_rectText
; 
2548         // when we're called for the very first time, the line height might not 
2549         // had been calculated yet, so do get it now 
2550         wxTextCtrl 
*self 
= wxConstCast(this, wxTextCtrl
); 
2551         self
->RecalcFontMetrics(); 
2553         int hLine 
= GetLineHeight(); 
2554         rectText
.height 
= (m_rectText
.height 
/ hLine
) * hLine
; 
2557         self
->MData().m_rectTextReal 
= rectText
; 
2563 wxTextCoord 
wxTextCtrl::GetRowInLine(wxTextCoord line
, 
2565                                      wxTextCoord 
*colRowStart
) const 
2567     wxASSERT_MSG( WrapLines(), _T("shouldn't be called") ); 
2569     const wxWrappedLineData
& lineData 
= WData().m_linesData
[line
]; 
2571     if ( !WData().IsValidLine(line
) ) 
2574     // row is here counted a bit specially: 0 is the 2nd row of the line (1st 
2577            rowMax 
= lineData
.GetExtraRowCount(); 
2581         while ( (row 
< rowMax
) && (col 
>= lineData
.GetExtraRowStart(row
)) ) 
2584         // it's ok here that row is 1 greater than needed: like this, it is 
2585         // counted as a normal (and not extra) row 
2587     //else: only one row anyhow 
2591         // +1 because we need a real row number, not the extra row one 
2592         *colRowStart 
= lineData
.GetRowStart(row
); 
2594         // this can't happen, of course 
2595         wxASSERT_MSG( *colRowStart 
<= col
, _T("GetRowInLine() is broken") ); 
2601 void wxTextCtrl::LayoutLine(wxTextCoord line
, wxWrappedLineData
& lineData
) const 
2603     // FIXME: this uses old GetPartOfWrappedLine() which is not used anywhere 
2604     //        else now and has rather awkward interface for our needs here 
2606     lineData
.m_rowsStart
.Empty(); 
2607     lineData
.m_rowsWidth
.Empty(); 
2609     const wxString
& text 
= GetLineText(line
); 
2611     size_t colRowStart 
= 0; 
2614         size_t lenRow 
= GetPartOfWrappedLine
 
2616                             text
.c_str() + colRowStart
, 
2620         // remember the start of this row (not for the first one as 
2621         // it's always 0) and its width 
2623             lineData
.m_rowsStart
.Add(colRowStart
); 
2624         lineData
.m_rowsWidth
.Add(widthRow
); 
2626         colRowStart 
+= lenRow
; 
2628     while ( colRowStart 
< text
.length() ); 
2630     // put the current timestamp on it 
2631     lineData
.m_timestamp 
= WData().m_timestamp
; 
2634 void wxTextCtrl::LayoutLines(wxTextCoord lineLast
) const 
2636     wxASSERT_MSG( WrapLines(), _T("should only be used for line wrapping") ); 
2638     // if we were called, some line was dirty and if it was dirty we must have 
2639     // had m_rowFirstInvalid set to something too 
2640     wxTextCoord lineFirst 
= WData().m_rowFirstInvalid
; 
2641     wxASSERT_MSG( lineFirst 
!= -1, _T("nothing to layout?") ); 
2643     wxTextCoord rowFirst
, rowCur
; 
2646         // start after the last known valid line 
2647         const wxWrappedLineData
& lineData 
= WData().m_linesData
[lineFirst 
- 1]; 
2648         rowFirst 
= lineData
.GetFirstRow() + lineData
.GetRowCount(); 
2650     else // no valid lines, start at row 0 
2656     for ( wxTextCoord line 
= lineFirst
; line 
<= lineLast
; line
++ ) 
2658         // set the starting row for this line 
2659         wxWrappedLineData
& lineData 
= WData().m_linesData
[line
]; 
2660         lineData
.m_rowFirst 
= rowCur
; 
2662         // had the line been already broken into rows? 
2664         // if so, compare its timestamp with the current one: if nothing has 
2665         // been changed, don't relayout it 
2666         if ( !lineData
.IsValid() || 
2667                 (lineData
.m_timestamp 
< WData().m_timestamp
) ) 
2669             // now do break it in rows 
2670             LayoutLine(line
, lineData
); 
2673         rowCur 
+= lineData
.GetRowCount(); 
2676     // we are now valid at least up to this line, but if it is the last one we 
2677     // just don't have any more invalid rows at all 
2678     if ( (size_t)lineLast 
== WData().m_linesData
.GetCount() -1 ) 
2683     wxTextCtrl 
*self 
= wxConstCast(this, wxTextCtrl
); 
2684     self
->WData().m_rowFirstInvalid 
= lineLast
; 
2686     // also refresh the line end indicators (FIXME shouldn't do it always!) 
2687     self
->RefreshLineWrapMarks(rowFirst
, rowCur
); 
2690 size_t wxTextCtrl::GetPartOfWrappedLine(const wxChar
* text
, 
2691                                         wxCoord 
*widthReal
) const 
2693     // this function is slow, it shouldn't be called unless really needed 
2694     wxASSERT_MSG( WrapLines(), _T("shouldn't be called") ); 
2698     wxCoord wReal 
= wxDefaultCoord
; 
2699     switch ( HitTestLine(s
, m_rectText
.width
, &col
) ) 
2702         case wxTE_HT_BEFORE: 
2706             wxFAIL_MSG(_T("unexpected HitTestLine() return value")); 
2709         case wxTE_HT_ON_TEXT
: 
2712                 // the last entirely seen character is the previous one because 
2713                 // this one is only partly visible - unless the width of the 
2714                 // string is exactly the max width 
2715                 wReal 
= GetTextWidth(s
.Truncate(col 
+ 1)); 
2716                 if ( wReal 
> m_rectText
.width 
) 
2718                     // this character is not entirely visible, take the 
2723                     wReal 
= wxDefaultCoord
; 
2725                 //else: we can just see it 
2727                 // wrap at any character or only at words boundaries? 
2728                 if ( !(GetWindowStyle() & wxTE_CHARWRAP
) ) 
2730                     // find the (last) not word char before this word 
2731                     wxTextCoord colWordStart
; 
2732                     for ( colWordStart 
= col
; 
2733                           colWordStart 
&& IsWordChar(s
[(size_t)colWordStart
]); 
2737                     if ( colWordStart 
> 0 ) 
2739                         if ( colWordStart 
!= col 
) 
2741                             // will have to recalc the real width 
2742                             wReal 
= wxDefaultCoord
; 
2747                     //else: only a single word, have to wrap it here 
2752         case wxTE_HT_BEYOND
: 
2756     // we return the number of characters, not the index of the last one 
2757     if ( (size_t)col 
< s
.length() ) 
2759         // but don't return more than this (empty) string has 
2765         if ( wReal 
== wxDefaultCoord 
) 
2767             // calc it if not done yet 
2768             wReal 
= GetTextWidth(s
.Truncate(col
)); 
2774     // VZ: old, horribly inefficient code which can still be used for checking 
2775     //     the result (in line, not word, wrap mode only) - to be removed later 
2777     wxTextCtrl 
*self 
= wxConstCast(this, wxTextCtrl
); 
2778     wxClientDC 
dc(self
); 
2779     dc
.SetFont(GetFont()); 
2780     self
->DoPrepareDC(dc
); 
2782     wxCoord widthMax 
= m_rectText
.width
; 
2784     // the text which we can keep in this ROW 
2787     for ( wOld 
= w 
= 0; *text 
&& (w 
<= widthMax
); ) 
2791         dc
.GetTextExtent(str
, &w
, NULL
); 
2796         // if we wrapped, the last letter was one too much 
2797         if ( str
.length() > 1 ) 
2800             str
.erase(str
.length() - 1, 1); 
2802         else // but always keep at least one letter in each row 
2804             // the real width then is the last value of w and not teh one 
2809     else // we didn't wrap 
2814     wxASSERT( col 
== str
.length() ); 
2818         wxASSERT( *widthReal 
== wOld 
); 
2823     //return str.length(); 
2829 // OPT: this function is called a lot - would be nice to optimize it but I 
2830 //      don't really know how yet 
2831 wxTextCtrlHitTestResult 
wxTextCtrl::HitTestLine(const wxString
& line
, 
2833                                                 wxTextCoord 
*colOut
) const 
2835     wxTextCtrlHitTestResult res 
= wxTE_HT_ON_TEXT
; 
2838     wxTextCtrl 
*self 
= wxConstCast(this, wxTextCtrl
); 
2839     wxClientDC 
dc(self
); 
2840     dc
.SetFont(GetFont()); 
2841     self
->DoPrepareDC(dc
); 
2844     dc
.GetTextExtent(line
, &width
, NULL
); 
2847         // clicking beyond the end of line is equivalent to clicking at 
2848         // the end of it, so return the last line column 
2849         col 
= line
.length(); 
2852             // unless the line is empty and so doesn't have any column at all - 
2853             // in this case return 0, what else can we do? 
2857         res 
= wxTE_HT_BEYOND
; 
2863         res 
= wxTE_HT_BEFORE
; 
2865     else // we're inside the line 
2867         // now calculate the column: first, approximate it with fixed-width 
2868         // value and then calculate the correct value iteratively: note that 
2869         // we use the first character of the line instead of (average) 
2870         // GetCharWidth(): it is common to have lines of dashes, for example, 
2871         // and this should give us much better approximation in such case 
2873         // OPT: maybe using (cache) m_widthAvg would be still faster? profile! 
2874         dc
.GetTextExtent(line
[0], &width
, NULL
); 
2881         else if ( (size_t)col 
> line
.length() ) 
2883             col 
= line
.length(); 
2886         // matchDir is the direction in which we should move to reach the 
2887         // character containing the given position 
2893         } matchDir 
= Match_None
; 
2896             // check that we didn't go beyond the line boundary 
2902             if ( (size_t)col 
> line
.length() ) 
2904                 col 
= line
.length(); 
2908             wxString 
strBefore(line
, (size_t)col
); 
2909             dc
.GetTextExtent(strBefore
, &width
, NULL
); 
2912                 if ( matchDir 
== Match_Right 
) 
2914                     // we were going to the right and, finally, moved beyond 
2915                     // the original position - stop on the previous one 
2921                 if ( matchDir 
== Match_None 
) 
2923                     // we just started iterating, now we know that we should 
2925                     matchDir 
= Match_Left
; 
2927                 //else: we are still to the right of the target, continue 
2931                 // invert the logic above 
2932                 if ( matchDir 
== Match_Left 
) 
2934                     // with the exception that we don't need to backtrack here 
2938                 if ( matchDir 
== Match_None 
) 
2941                     matchDir 
= Match_Right
; 
2945             // this is not supposed to happen 
2946             wxASSERT_MSG( matchDir
, _T("logic error in wxTextCtrl::HitTest") ); 
2948             if ( matchDir 
== Match_Right 
) 
2955     // check that we calculated it correctly 
2957     if ( res 
== wxTE_HT_ON_TEXT 
) 
2960         wxString text 
= line
.Left(col
); 
2961         dc
.GetTextExtent(text
, &width1
, NULL
); 
2962         if ( (size_t)col 
< line
.length() ) 
2967             dc
.GetTextExtent(text
, &width2
, NULL
); 
2969             wxASSERT_MSG( (width1 
<= x
) && (x 
< width2
), 
2970                           _T("incorrect HitTestLine() result") ); 
2972         else // we return last char 
2974             wxASSERT_MSG( x 
>= width1
, _T("incorrect HitTestLine() result") ); 
2977 #endif // WXDEBUG_TEXT 
2985 wxTextCtrlHitTestResult 
wxTextCtrl::HitTest(const wxPoint
& pt
, long *pos
) const 
2988     wxTextCtrlHitTestResult rc 
= HitTest(pt
, &x
, &y
); 
2989     if ( rc 
!= wxTE_HT_UNKNOWN 
&& pos 
) 
2991         *pos 
= XYToPosition(x
, y
); 
2997 wxTextCtrlHitTestResult 
wxTextCtrl::HitTest(const wxPoint
& pos
, 
2998                                             wxTextCoord 
*colOut
, 
2999                                             wxTextCoord 
*rowOut
) const 
3001     return HitTest2(pos
.y
, pos
.x
, 0, rowOut
, colOut
, NULL
, NULL
); 
3004 wxTextCtrlHitTestResult 
wxTextCtrl::HitTestLogical(const wxPoint
& pos
, 
3005                                                    wxTextCoord 
*colOut
, 
3006                                                    wxTextCoord 
*rowOut
) const 
3008     return HitTest2(pos
.y
, pos
.x
, 0, rowOut
, colOut
, NULL
, NULL
, false); 
3011 wxTextCtrlHitTestResult 
wxTextCtrl::HitTest2(wxCoord y0
, 
3014                                              wxTextCoord 
*rowOut
, 
3015                                              wxTextCoord 
*colStart
, 
3016                                              wxTextCoord 
*colEnd
, 
3017                                              wxTextCoord 
*colRowStartOut
, 
3018                                              bool deviceCoords
) const 
3020     // is the point in the text area or to the right or below it? 
3021     wxTextCtrlHitTestResult res 
= wxTE_HT_ON_TEXT
; 
3023     // translate the window coords x0 and y0 into the client coords in the text 
3024     // area by adjusting for both the client and text area offsets (unless this 
3025     // was already done) 
3029         wxPoint pt 
= GetClientAreaOrigin() + m_rectText
.GetPosition(); 
3030         CalcUnscrolledPosition(x10 
- pt
.x
, y0 
- pt
.y
, &x1
, &y
); 
3038     // calculate the row (it is really a LINE, not a ROW) 
3041     // these vars are used only for WrapLines() case 
3042     wxTextCoord colRowStart 
= 0; 
3045     if ( colRowStartOut 
) 
3046         *colRowStartOut 
= 0; 
3048     int hLine 
= GetLineHeight(); 
3051         // and clicking before it is the same as clicking on the first one 
3054         res 
= wxTE_HT_BEFORE
; 
3058         wxTextCoord rowLast 
= GetNumberOfLines() - 1; 
3060         if ( IsSingleLine() || !WrapLines() ) 
3062             // in this case row calculation is simple as all lines have the 
3063             // same height and so row is the same as line 
3064             if ( row 
> rowLast 
) 
3066                 // clicking below the text is the same as clicking on the last 
3070                 res 
= wxTE_HT_BELOW
; 
3073         else // multline control with line wrap 
3075             // use binary search to find the line containing this row 
3076             const wxArrayWrappedLinesData
& linesData 
= WData().m_linesData
; 
3078                    hi 
= linesData
.GetCount(), 
3083                 const wxWrappedLineData
& lineData 
= linesData
[cur
]; 
3084                 if ( !WData().IsValidLine(cur
) ) 
3086                 wxTextCoord rowFirst 
= lineData
.GetFirstRow(); 
3088                 if ( row 
< rowFirst 
) 
3094                     // our row is after the first row of the cur line: 
3095                     // obviously, if cur is the last line, it contains this 
3096                     // row, otherwise we have to test that it is before the 
3097                     // first row of the next line 
3098                     bool found 
= cur 
== linesData
.GetCount() - 1; 
3101                         // if the row is beyond the end of text, adjust it to 
3102                         // be the last one and set res accordingly 
3103                         if ( (size_t)(row 
- rowFirst
) >= lineData
.GetRowCount() ) 
3105                             res 
= wxTE_HT_BELOW
; 
3107                             row 
= lineData
.GetRowCount() + rowFirst 
- 1; 
3110                     else // not the last row 
3112                         const wxWrappedLineData
& 
3113                                 lineNextData 
= linesData
[cur 
+ 1]; 
3114                         if ( !WData().IsValidLine(cur 
+ 1) ) 
3115                             LayoutLines(cur 
+ 1); 
3116                         found 
= row 
< lineNextData
.GetFirstRow(); 
3121                         colRowStart 
= lineData
.GetRowStart(row 
- rowFirst
); 
3122                         rowLen 
= lineData
.GetRowLength(row 
- rowFirst
, 
3123                                                        GetLines()[cur
].length()); 
3137     if ( res 
== wxTE_HT_ON_TEXT 
) 
3139         // now find the position in the line 
3140         wxString lineText 
= GetLineText(row
), 
3143         if ( colRowStart 
|| rowLen 
) 
3145             // look in this row only, not in whole line 
3146             rowText 
= lineText
.Mid(colRowStart
, rowLen
); 
3150             // just take the whole string 
3156             res 
= HitTestLine(GetTextToShow(rowText
), x1
, colStart
); 
3160                 if ( colRowStartOut 
) 
3162                     // give them the column offset in this ROW in pixels 
3163                     *colRowStartOut 
= colRowStart
; 
3166                 // take into account that the ROW doesn't start in the 
3167                 // beginning of the LINE 
3168                 *colStart 
+= colRowStart
; 
3173                 // the hit test result we return is for x1, so throw out 
3174                 // the result for x2 here 
3175                 int x2 
= x1 
+ x20 
- x10
; 
3176                 (void)HitTestLine(GetTextToShow(rowText
), x2
, colEnd
); 
3178                 *colEnd 
+= colRowStart
; 
3182     else // before/after vertical text span 
3186             // fill the column with the first/last position in the 
3187             // corresponding line 
3188             if ( res 
== wxTE_HT_BEFORE 
) 
3190             else // res == wxTE_HT_BELOW 
3191                 *colStart 
= GetLineText(GetNumberOfLines() - 1).length(); 
3197         // give them the row in text coords (as is) 
3204 bool wxTextCtrl::GetLineAndRow(wxTextCoord row
, 
3205                                wxTextCoord 
*lineOut
, 
3206                                wxTextCoord 
*rowInLineOut
) const 
3214     int nLines 
= GetNumberOfLines(); 
3217         const wxArrayWrappedLinesData
& linesData 
= WData().m_linesData
; 
3218         for ( line 
= 0; line 
< nLines
; line
++ ) 
3220             if ( !WData().IsValidLine(line
) ) 
3223             if ( row 
< linesData
[line
].GetNextRow() ) 
3225                 // we found the right line 
3226                 rowInLine 
= row 
- linesData
[line
].GetFirstRow(); 
3232         if ( line 
== nLines 
) 
3234             // the row is out of range 
3238     else // no line wrapping, everything is easy 
3240         if ( row 
>= nLines 
) 
3249         *rowInLineOut 
= rowInLine
; 
3254 // ---------------------------------------------------------------------------- 
3256 // ---------------------------------------------------------------------------- 
3259    wxTextCtrl has not one but two scrolling mechanisms: one is semi-automatic 
3260    scrolling in both horizontal and vertical direction implemented using 
3261    wxScrollHelper and the second one is manual scrolling implemented using 
3262    SData().m_ofsHorz and used by the single line controls without scroll bar. 
3264    The first version (the standard one) always scrolls by fixed amount which is 
3265    fine for vertical scrolling as all lines have the same height but is rather 
3266    ugly for horizontal scrolling if proportional font is used. This is why we 
3267    manually update and use SData().m_ofsHorz which contains the length of the string 
3268    which is hidden beyond the left border. An important property of text 
3269    controls using this kind of scrolling is that an entire number of characters 
3270    is always shown and that parts of characters never appear on display - 
3271    neither in the leftmost nor rightmost positions. 
3273    Once again, for multi line controls SData().m_ofsHorz is always 0 and scrolling is 
3274    done as usual for wxScrollWindow. 
3277 void wxTextCtrl::ShowHorzPosition(wxCoord pos
) 
3279     wxASSERT_MSG( IsSingleLine(), _T("doesn't work for multiline") ); 
3281     // pos is the logical position to show 
3283     // SData().m_ofsHorz is the first logical position shown 
3284     if ( pos 
< SData().m_ofsHorz 
) 
3288         HitTestLine(m_value
, pos
, &col
); 
3293         wxCoord width 
= m_rectText
.width
; 
3296             // if we are called from the ctor, m_rectText is not initialized 
3297             // yet, so do it now 
3299             width 
= m_rectText
.width
; 
3302         // SData().m_ofsHorz + width is the last logical position shown 
3303         if ( pos 
> SData().m_ofsHorz 
+ width
) 
3307             HitTestLine(m_value
, pos 
- width
, &col
); 
3308             ScrollText(col 
+ 1); 
3313 // scroll the window horizontally so that the first visible character becomes 
3314 // the one at this position 
3315 void wxTextCtrl::ScrollText(wxTextCoord col
) 
3317     wxASSERT_MSG( IsSingleLine(), 
3318                   _T("ScrollText() is for single line controls only") ); 
3320     // never scroll beyond the left border 
3324     // OPT: could only get the extent of the part of the string between col 
3325     //      and SData().m_colStart 
3326     wxCoord ofsHorz 
= GetTextWidth(GetLineText(0).Left(col
)); 
3328     if ( ofsHorz 
!= SData().m_ofsHorz 
) 
3330         // remember the last currently used pixel 
3331         int posLastVisible 
= SData().m_posLastVisible
; 
3332         if ( posLastVisible 
== -1 ) 
3334             // this may happen when we're called very early, during the 
3335             // controls construction 
3336             UpdateLastVisible(); 
3338             posLastVisible 
= SData().m_posLastVisible
; 
3341         // NB1: to scroll to the right, offset must be negative, hence the 
3342         //      order of operands 
3343         int dx 
= SData().m_ofsHorz 
- ofsHorz
; 
3345         // NB2: we call Refresh() below which results in a call to 
3346         //      DoDraw(), so we must update SData().m_ofsHorz before calling it 
3347         SData().m_ofsHorz 
= ofsHorz
; 
3348         SData().m_colStart 
= col
; 
3350         // after changing m_colStart, recalc the last visible position: we need 
3351         // to recalc the last visible position beore scrolling in order to make 
3352         // it appear exactly at the right edge of the text area after scrolling 
3353         UpdateLastVisible(); 
3358             // we want to force the update of it after scrolling 
3359             SData().m_colLastVisible 
= -1; 
3363         // scroll only the rectangle inside which there is the text 
3364         wxRect rect 
= m_rectText
; 
3365         rect
.width 
= posLastVisible
; 
3367         rect 
= ScrollNoRefresh(dx
, 0, &rect
); 
3370            we need to manually refresh the part which ScrollWindow() doesn't 
3371            refresh (with new API this means the part outside the rect returned 
3372            by ScrollNoRefresh): indeed, if we had this: 
3376            where '*' is text and 'o' is blank area at the end (too small to 
3377            hold the next char) then after scrolling by 2 positions to the left 
3382            where 'R' is the area refreshed by ScrollWindow() - but we still 
3383            need to refresh the 'o' at the end as it may be now big enough to 
3384            hold the new character shifted into view. 
3386            when we are scrolling to the right, we need to update this rect as 
3387            well because it might have contained something before but doesn't 
3388            contain anything any more 
3391         // we can combine both rectangles into one when scrolling to the left, 
3392         // but we need two separate Refreshes() otherwise 
3395             // refresh the uncovered part on the left 
3396             Refresh(true, &rect
); 
3398             // and now the area on the right 
3399             rect
.x 
= m_rectText
.x 
+ posLastVisible
; 
3400             rect
.width 
= m_rectText
.width 
- posLastVisible
; 
3402         else // scrolling to the left 
3404             // just extend the rect covering the uncovered area to the edge of 
3406             rect
.width 
+= m_rectText
.width 
- posLastVisible
; 
3409         Refresh(true, &rect
); 
3411         // I don't know exactly why is this needed here but without it we may 
3412         // scroll the window again (from the same method) before the previously 
3413         // invalidated area is repainted when typing *very* quickly - and this 
3414         // may lead to the display corruption 
3419 void wxTextCtrl::CalcUnscrolledPosition(int x
, int y
, int *xx
, int *yy
) const 
3421     if ( IsSingleLine() ) 
3423         // we don't use wxScrollHelper 
3425             *xx 
= x 
+ SData().m_ofsHorz
; 
3431         // let the base class do it 
3432         wxScrollHelper::CalcUnscrolledPosition(x
, y
, xx
, yy
); 
3436 void wxTextCtrl::CalcScrolledPosition(int x
, int y
, int *xx
, int *yy
) const 
3438     if ( IsSingleLine() ) 
3440         // we don't use wxScrollHelper 
3442             *xx 
= x 
- SData().m_ofsHorz
; 
3448         // let the base class do it 
3449         wxScrollHelper::CalcScrolledPosition(x
, y
, xx
, yy
); 
3453 void wxTextCtrl::DoPrepareDC(wxDC
& dc
) 
3455     // for single line controls we only have to deal with SData().m_ofsHorz and it's 
3456     // useless to call base class version as they don't use normal scrolling 
3457     if ( IsSingleLine() && SData().m_ofsHorz 
) 
3459         // adjust the DC origin if the text is shifted 
3460         wxPoint pt 
= dc
.GetDeviceOrigin(); 
3461         dc
.SetDeviceOrigin(pt
.x 
- SData().m_ofsHorz
, pt
.y
); 
3465         wxScrollHelper::DoPrepareDC(dc
); 
3469 void wxTextCtrl::UpdateMaxWidth(wxTextCoord line
) 
3473     // check if the max width changes after this line was modified 
3474     wxCoord widthMaxOld 
= MData().m_widthMax
, 
3476     GetTextExtent(GetLineText(line
), &width
, NULL
); 
3478     if ( line 
== MData().m_lineLongest 
) 
3480         // this line was the longest one, is it still? 
3481         if ( width 
> MData().m_widthMax 
) 
3483             MData().m_widthMax 
= width
; 
3485         else if ( width 
< MData().m_widthMax 
) 
3487             // we need to find the new longest line 
3490         //else: its length didn't change, nothing to do 
3492     else // it wasn't the longest line, but maybe it became it? 
3494         // GetMaxWidth() and not MData().m_widthMax as it might be not calculated yet 
3495         if ( width 
> GetMaxWidth() ) 
3497             MData().m_widthMax 
= width
; 
3498             MData().m_lineLongest 
= line
; 
3502     MData().m_updateScrollbarX 
= MData().m_widthMax 
!= widthMaxOld
; 
3505 void wxTextCtrl::RecalcFontMetrics() 
3507     m_heightLine 
= GetCharHeight(); 
3508     m_widthAvg 
= GetCharWidth(); 
3511 void wxTextCtrl::RecalcMaxWidth() 
3513     wxASSERT_MSG( !IsSingleLine(), _T("only used for multiline") ); 
3515     MData().m_widthMax 
= -1; 
3516     (void)GetMaxWidth(); 
3519 wxCoord 
wxTextCtrl::GetMaxWidth() const 
3521     if ( MData().m_widthMax 
== -1 ) 
3525         // OPT: should we remember the widths of all the lines? 
3527         wxTextCtrl 
*self 
= wxConstCast(this, wxTextCtrl
); 
3528         wxClientDC 
dc(self
); 
3529         dc
.SetFont(GetFont()); 
3531         self
->MData().m_widthMax 
= 0; 
3533         size_t count 
= GetLineCount(); 
3534         for ( size_t n 
= 0; n 
< count
; n
++ ) 
3537             dc
.GetTextExtent(GetLines()[n
], &width
, NULL
); 
3538             if ( width 
> MData().m_widthMax 
) 
3540                 // remember the width and the line which has it 
3541                 self
->MData().m_widthMax 
= width
; 
3542                 self
->MData().m_lineLongest 
= n
; 
3547     wxASSERT_MSG( MData().m_widthMax 
!= -1, _T("should have at least 1 line") ); 
3549     return MData().m_widthMax
; 
3552 void wxTextCtrl::UpdateScrollbars() 
3554     wxASSERT_MSG( !IsSingleLine(), _T("only used for multiline") ); 
3556     wxSize size 
= GetRealTextArea().GetSize(); 
3558     // is our height enough to show all items? 
3559     wxTextCoord nRows 
= GetRowCount(); 
3560     wxCoord lineHeight 
= GetLineHeight(); 
3561     bool showScrollbarY 
= nRows
*lineHeight 
> size
.y
; 
3563     // is our width enough to show the longest line? 
3564     wxCoord charWidth
, maxWidth
; 
3565     bool showScrollbarX
; 
3568         charWidth 
= GetAverageWidth(); 
3569         maxWidth 
= GetMaxWidth(); 
3570         showScrollbarX 
= maxWidth 
> size
.x
; 
3572     else // never show the horz scrollbar 
3574         // just to suppress compiler warnings about using uninit vars below 
3575         charWidth 
= maxWidth 
= 0; 
3577         showScrollbarX 
= false; 
3580     // calc the scrollbars ranges 
3581     int scrollRangeX 
= showScrollbarX
 
3582                         ? (maxWidth 
+ 2*charWidth 
- 1) / charWidth
 
3584     int scrollRangeY 
= showScrollbarY 
? nRows 
: 0; 
3586     int scrollRangeXOld 
= MData().m_scrollRangeX
, 
3587         scrollRangeYOld 
= MData().m_scrollRangeY
; 
3588     if ( (scrollRangeY 
!= scrollRangeYOld
) || (scrollRangeX 
!= scrollRangeXOld
) ) 
3591         GetViewStart(&x
, &y
); 
3594         // we want to leave the scrollbars at the same position which means 
3595         // that x and y have to be adjusted as the number of positions may have 
3598         // the number of positions is calculated from knowing that last 
3599         // position = range - thumbSize and thumbSize == pageSize which is 
3600         // equal to the window width / pixelsPerLine 
3601         if ( scrollRangeXOld 
) 
3603             x 
*= scrollRangeX 
- m_rectText
.width 
/ charWidth
; 
3604             x 
/= scrollRangeXOld 
- m_rectText
.width 
/ charWidth
; 
3607         if ( scrollRangeYOld 
) 
3608             y 
*= scrollRangeY 
/ scrollRangeYOld
; 
3611         SetScrollbars(charWidth
, lineHeight
, 
3612                       scrollRangeX
, scrollRangeY
, 
3614                       true /* no refresh */); 
3616         if ( scrollRangeXOld 
) 
3618             const int w 
= m_rectText
.width 
/ charWidth
; 
3619             if ( w 
!= scrollRangeXOld 
) 
3621                 x 
*= scrollRangeX 
- w
; 
3622                 x 
/= scrollRangeXOld 
- w
; 
3627         MData().m_scrollRangeX 
= scrollRangeX
; 
3628         MData().m_scrollRangeY 
= scrollRangeY
; 
3630         // bring the current position in view 
3634     MData().m_updateScrollbarX 
= 
3635     MData().m_updateScrollbarY 
= false; 
3638 void wxTextCtrl::OnInternalIdle() 
3640     // notice that single line text control never has scrollbars 
3641     if ( !IsSingleLine() && 
3642             (MData().m_updateScrollbarX 
|| MData().m_updateScrollbarY
) ) 
3646     wxControl::OnInternalIdle(); 
3649 bool wxTextCtrl::SendAutoScrollEvents(wxScrollWinEvent
& event
) const 
3651     bool forward 
= event
.GetEventType() == wxEVT_SCROLLWIN_LINEDOWN
; 
3652     if ( event
.GetOrientation() == wxHORIZONTAL 
) 
3654         return forward 
? m_curCol 
<= GetLineLength(m_curRow
) : m_curCol 
> 0; 
3658         return forward 
? m_curRow 
< GetNumberOfLines() : m_curRow 
> 0; 
3662 // ---------------------------------------------------------------------------- 
3664 // ---------------------------------------------------------------------------- 
3666 void wxTextCtrl::RefreshSelection() 
3668     if ( HasSelection() ) 
3670         RefreshTextRange(m_selStart
, m_selEnd
); 
3674 void wxTextCtrl::RefreshLineRange(wxTextCoord lineFirst
, wxTextCoord lineLast
) 
3676     wxASSERT_MSG( lineFirst 
<= lineLast 
|| !lineLast
, 
3677                   _T("no lines to refresh") ); 
3680     // rect.x is already 0 
3681     rect
.width 
= m_rectText
.width
; 
3682     wxCoord h 
= GetLineHeight(); 
3684     wxTextCoord rowFirst
; 
3685     if ( lineFirst 
< GetNumberOfLines() ) 
3687         rowFirst 
= GetFirstRowOfLine(lineFirst
); 
3689     else // lineFirst == GetNumberOfLines() 
3691         // lineFirst may be beyond the last line only if we refresh till 
3692         // the end, otherwise it's illegal 
3693         wxASSERT_MSG( lineFirst 
== GetNumberOfLines() && !lineLast
, 
3694                       _T("invalid line range") ); 
3696         rowFirst 
= GetRowAfterLine(lineFirst 
- 1); 
3699     rect
.y 
= rowFirst
*h
; 
3703         // refresh till this line (inclusive) 
3704         wxTextCoord rowLast 
= GetRowAfterLine(lineLast
); 
3706         rect
.height 
= (rowLast 
- rowFirst 
+ 1)*h
; 
3708     else // lineLast == 0 means to refresh till the end 
3710         // FIXME: calc it exactly 
3711         rect
.height 
= 32000; 
3714     RefreshTextRect(rect
); 
3717 void wxTextCtrl::RefreshTextRange(wxTextPos start
, wxTextPos end
) 
3719     wxCHECK_RET( start 
!= -1 && end 
!= -1, 
3720                  _T("invalid RefreshTextRange() arguments") ); 
3722     // accept arguments in any order as it is more conenient for the caller 
3723     OrderPositions(start
, end
); 
3725     // this is acceptable but we don't do anything in this case 
3729     wxTextPos colStart
, lineStart
; 
3730     if ( !PositionToXY(start
, &colStart
, &lineStart
) ) 
3732         // the range is entirely beyond the end of the text, nothing to do 
3736     wxTextCoord colEnd
, lineEnd
; 
3737     if ( !PositionToXY(end
, &colEnd
, &lineEnd
) ) 
3739         // the range spans beyond the end of text, refresh to the end 
3741         lineEnd 
= GetNumberOfLines() - 1; 
3744     // refresh all lines one by one 
3745     for ( wxTextCoord line 
= lineStart
; line 
<= lineEnd
; line
++ ) 
3747         // refresh the first line from the start of the range to the end, the 
3748         // intermediate ones entirely and the last one from the beginning to 
3749         // the end of the range 
3750         wxTextPos posStart 
= line 
== lineStart 
? colStart 
: 0; 
3752         if ( (line 
!= lineEnd
) || (colEnd 
== -1) ) 
3754             // intermediate line or the last one but we need to refresh it 
3755             // until the end anyhow - do it 
3756             posCount 
= wxString::npos
; 
3760             // refresh just the positions in between the start and the end one 
3761             posCount 
= colEnd 
- posStart
; 
3765             RefreshColRange(line
, posStart
, posCount
); 
3769 void wxTextCtrl::RefreshColRange(wxTextCoord line
, 
3773     wxString text 
= GetLineText(line
); 
3775     wxASSERT_MSG( (size_t)start 
<= text
.length() && count
, 
3776                   _T("invalid RefreshColRange() parameter") ); 
3778     RefreshPixelRange(line
, 
3779                       GetTextWidth(text
.Left((size_t)start
)), 
3780                       GetTextWidth(text
.Mid((size_t)start
, (size_t)count
))); 
3783 // this method accepts "logical" coords in the sense that they are coordinates 
3784 // in a logical line but it can span several rows if we wrap lines and 
3785 // RefreshPixelRange() will then refresh several rows 
3786 void wxTextCtrl::RefreshPixelRange(wxTextCoord line
, 
3790     // we will use line text only in line wrap case 
3794         text 
= GetLineText(line
); 
3797     // special case: width == 0 means to refresh till the end of line 
3800         // refresh till the end of visible line 
3801         width 
= GetTotalWidth(); 
3805             // refresh till the end of text 
3806             wxCoord widthAll 
= GetTextWidth(text
); 
3808             // extend width to the end of ROW 
3809             width 
= widthAll 
- widthAll 
% width 
+ width
; 
3812         // no need to refresh beyond the end of line 
3815     //else: just refresh the specified part 
3817     wxCoord h 
= GetLineHeight(); 
3820     rect
.y 
= GetFirstRowOfLine(line
)*h
; 
3825         // (1) skip all rows which we don't touch at all 
3826         const wxWrappedLineData
& lineData 
= WData().m_linesData
[line
]; 
3827         if ( !WData().IsValidLine(line
) ) 
3830         wxCoord wLine 
= 0; // suppress compiler warning about uninit var 
3831         size_t rowLast 
= lineData
.GetRowCount(), 
3833         while ( (row 
< rowLast
) && 
3834                 (rect
.x 
> (wLine 
= lineData
.GetRowWidth(row
++))) ) 
3840         // (2) now refresh all lines except the last one: note that the first 
3841         //     line is refreshed from the given start to the end, all the next 
3843         while ( (row 
< rowLast
) && (width 
> wLine 
- rect
.x
) ) 
3845             rect
.width 
= GetTotalWidth() - rect
.x
; 
3846             RefreshTextRect(rect
); 
3848             width 
-= wLine 
- rect
.x
; 
3852             wLine 
= lineData
.GetRowWidth(row
++); 
3855         // (3) the code below will refresh the last line 
3860     RefreshTextRect(rect
); 
3863 void wxTextCtrl::RefreshTextRect(const wxRect
& rectClient
, bool textOnly
) 
3866     CalcScrolledPosition(rectClient
.x
, rectClient
.y
, &rect
.x
, &rect
.y
); 
3867     rect
.width 
= rectClient
.width
; 
3868     rect
.height 
= rectClient
.height
; 
3870     // account for the text area offset 
3871     rect
.Offset(m_rectText
.GetPosition()); 
3873     // don't refresh beyond the text area unless we're refreshing the line wrap 
3874     // marks in which case textOnly is false 
3877         if ( rect
.GetRight() > m_rectText
.GetRight() ) 
3879             rect
.SetRight(m_rectText
.GetRight()); 
3881             if ( rect
.width 
<= 0 ) 
3883                 // nothing to refresh 
3889     // check the bottom boundary always, even for the line wrap marks 
3890     if ( rect
.GetBottom() > m_rectText
.GetBottom() ) 
3892         rect
.SetBottom(m_rectText
.GetBottom()); 
3894         if ( rect
.height 
<= 0 ) 
3896             // nothing to refresh 
3901     // never refresh before the visible rect 
3902     if ( rect
.x 
< m_rectText
.x 
) 
3903         rect
.x 
= m_rectText
.x
; 
3905     if ( rect
.y 
< m_rectText
.y 
) 
3906         rect
.y 
= m_rectText
.y
; 
3908     wxLogTrace(_T("text"), _T("Refreshing (%d, %d)-(%d, %d)"), 
3909                rect
.x
, rect
.y
, rect
.x 
+ rect
.width
, rect
.y 
+ rect
.height
); 
3911     Refresh(true, &rect
); 
3914 void wxTextCtrl::RefreshLineWrapMarks(wxTextCoord rowFirst
, 
3915                                       wxTextCoord rowLast
) 
3917     if ( WData().m_widthMark 
) 
3920         rectMarks
.x 
= m_rectText
.width
; 
3921         rectMarks
.width 
= WData().m_widthMark
; 
3922         rectMarks
.y 
= rowFirst
*GetLineHeight(); 
3923         rectMarks
.height 
= (rowLast 
- rowFirst
)*GetLineHeight(); 
3925         RefreshTextRect(rectMarks
, false /* don't limit to text area */); 
3929 // ---------------------------------------------------------------------------- 
3931 // ---------------------------------------------------------------------------- 
3933 void wxTextCtrl::DoDrawBorder(wxDC
& dc
, const wxRect
& rect
) 
3935     m_renderer
->DrawTextBorder(dc
, GetBorder(), rect
, GetStateFlags()); 
3938 // ---------------------------------------------------------------------------- 
3939 // client area drawing 
3940 // ---------------------------------------------------------------------------- 
3943    Several remarks about wxTextCtrl redraw logic: 
3945    1. only the regions which must be updated are redrawn, this means that we 
3946       never Refresh() the entire window but use RefreshPixelRange() and 
3947       ScrollWindow() which only refresh small parts of it and iterate over the 
3948       update region in our DoDraw() 
3950    2. the text displayed on the screen is obtained using GetTextToShow(): it 
3951       should be used for all drawing/measuring 
3954 wxString 
wxTextCtrl::GetTextToShow(const wxString
& text
) const 
3958         textShown 
= wxString(_T('*'), text
.length()); 
3965 void wxTextCtrl::DoDrawTextInRect(wxDC
& dc
, const wxRect
& rectUpdate
) 
3967     // debugging trick to see the update rect visually 
3969     static int s_countUpdates 
= -1; 
3970     if ( s_countUpdates 
!= -1 ) 
3972         wxWindowDC 
dc(this); 
3973         dc
.SetBrush(*(++s_countUpdates 
% 2 ? wxRED_BRUSH 
: wxGREEN_BRUSH
)); 
3974         dc
.SetPen(*wxTRANSPARENT_PEN
); 
3975         dc
.DrawRectangle(rectUpdate
); 
3977 #endif // WXDEBUG_TEXT 
3979     // calculate the range lineStart..lineEnd of lines to redraw 
3980     wxTextCoord lineStart
, lineEnd
; 
3981     if ( IsSingleLine() ) 
3988         wxPoint pt 
= rectUpdate
.GetPosition(); 
3989         (void)HitTest(pt
, NULL
, &lineStart
); 
3991         pt
.y 
+= rectUpdate
.height
; 
3992         (void)HitTest(pt
, NULL
, &lineEnd
); 
3995     // prepare for drawing 
3996     wxCoord hLine 
= GetLineHeight(); 
3998     // these vars will be used for hit testing of the current row 
3999     wxCoord y 
= rectUpdate
.y
; 
4000     const wxCoord x1 
= rectUpdate
.x
; 
4001     const wxCoord x2 
= rectUpdate
.x 
+ rectUpdate
.width
; 
4004     rectText
.height 
= hLine
; 
4005     wxCoord yClient 
= y 
- GetClientAreaOrigin().y
; 
4007     // we want to always start at the top of the line, otherwise if we redraw a 
4008     // rect whose top is in the middle of a line, we'd draw this line shifted 
4009     yClient 
-= (yClient 
- m_rectText
.y
) % hLine
; 
4011     if ( IsSingleLine() ) 
4013         rectText
.y 
= yClient
; 
4015     else // multiline, adjust for scrolling 
4017         CalcUnscrolledPosition(0, yClient
, NULL
, &rectText
.y
); 
4020     wxRenderer 
*renderer 
= GetRenderer(); 
4022     // do draw the invalidated parts of each line: note that we iterate here 
4023     // over ROWs, not over LINEs 
4024     for ( wxTextCoord line 
= lineStart
; 
4025           y 
< rectUpdate
.y 
+ rectUpdate
.height
; 
4027           rectText
.y 
+= hLine 
) 
4029         // calculate the update rect in text positions for this line 
4030         wxTextCoord colStart
, colEnd
, colRowStart
; 
4031         wxTextCtrlHitTestResult ht 
= HitTest2(y
, x1
, x2
, 
4032                                               &line
, &colStart
, &colEnd
, 
4035         if ( (ht 
== wxTE_HT_BEYOND
) || (ht 
== wxTE_HT_BELOW
) ) 
4037             wxASSERT_MSG( line 
<= lineEnd
, _T("how did we get that far?") ); 
4039             if ( line 
== lineEnd 
) 
4041                 // we redrew everything 
4045             // the update rect is beyond the end of line, no need to redraw 
4046             // anything on this line - but continue with the remaining ones 
4050         // for single line controls we may additionally cut off everything 
4051         // which is to the right of the last visible position 
4052         if ( IsSingleLine() ) 
4054             // don't show the columns which are scrolled out to the left 
4055             if ( colStart 
< SData().m_colStart 
) 
4056                 colStart 
= SData().m_colStart
; 
4058             // colEnd may be less than colStart if colStart was changed by the 
4060             if ( colEnd 
< colStart 
) 
4063             // don't draw the chars beyond the rightmost one 
4064             if ( SData().m_colLastVisible 
== -1 ) 
4066                 // recalculate this rightmost column 
4067                 UpdateLastVisible(); 
4070             if ( colStart 
> SData().m_colLastVisible 
) 
4072                 // don't bother redrawing something that is beyond the last 
4077             if ( colEnd 
> SData().m_colLastVisible 
) 
4079                 colEnd 
= SData().m_colLastVisible
; 
4083         // extract the part of line we need to redraw 
4084         wxString textLine 
= GetTextToShow(GetLineText(line
)); 
4085         wxString text 
= textLine
.Mid(colStart
, colEnd 
- colStart 
+ 1); 
4087         // now deal with the selection: only do something if at least part of 
4088         // the line is selected 
4089         wxTextPos selStart
, selEnd
; 
4090         if ( GetSelectedPartOfLine(line
, &selStart
, &selEnd
) ) 
4092             // and if this part is (at least partly) in the current row 
4093             if ( (selStart 
<= colEnd
) && 
4094                     (selEnd 
>= wxMax(colStart
, colRowStart
)) ) 
4096                 // these values are relative to the start of the line while the 
4097                 // string passed to DrawTextLine() is only part of it, so 
4098                 // adjust the selection range accordingly 
4099                 selStart 
-= colStart
; 
4105                 if ( (size_t)selEnd 
>= text
.length() ) 
4106                     selEnd 
= text
.length(); 
4110                 // reset selStart and selEnd to avoid passing them to 
4111                 // DrawTextLine() below 
4117         // calculate the text coords on screen 
4118         wxASSERT_MSG( colStart 
>= colRowStart
, _T("invalid string part") ); 
4119         wxCoord ofsStart 
= GetTextWidth( 
4120                                     textLine
.Mid(colRowStart
, 
4121                                                  colStart 
- colRowStart
)); 
4122         rectText
.x 
= m_rectText
.x 
+ ofsStart
; 
4123         rectText
.width 
= GetTextWidth(text
); 
4126         renderer
->DrawTextLine(dc
, text
, rectText
, selStart
, selEnd
, 
4128         wxLogTrace(_T("text"), _T("Line %ld: positions %ld-%ld redrawn."), 
4129                    line
, colStart
, colEnd
); 
4133 void wxTextCtrl::DoDrawLineWrapMarks(wxDC
& dc
, const wxRect
& rectUpdate
) 
4135     wxASSERT_MSG( WrapLines() && WData().m_widthMark
, 
4136                   _T("shouldn't be called at all") ); 
4138     wxRenderer 
*renderer 
= GetRenderer(); 
4141     rectMark
.x 
= rectUpdate
.x
; 
4142     rectMark
.width 
= rectUpdate
.width
; 
4143     wxCoord yTop 
= GetClientAreaOrigin().y
; 
4144     CalcUnscrolledPosition(0, rectUpdate
.y 
- yTop
, NULL
, &rectMark
.y
); 
4145     wxCoord hLine 
= GetLineHeight(); 
4146     rectMark
.height 
= hLine
; 
4148     wxTextCoord line
, rowInLine
; 
4151     CalcUnscrolledPosition(0, rectUpdate
.GetBottom() - yTop
, NULL
, &yBottom
); 
4152     for ( ; rectMark
.y 
< yBottom
; rectMark
.y 
+= hLine 
) 
4154         if ( !GetLineAndRow(rectMark
.y 
/ hLine
, &line
, &rowInLine
) ) 
4156             // we went beyond the end of text 
4160         // is this row continued on the next one? 
4161         if ( !WData().m_linesData
[line
].IsLastRow(rowInLine
) ) 
4163             renderer
->DrawLineWrapMark(dc
, rectMark
); 
4168 void wxTextCtrl::DoDraw(wxControlRenderer 
*renderer
) 
4170     // hide the caret while we're redrawing the window and show it after we are 
4172     wxCaretSuspend 
cs(this); 
4175     wxDC
& dc 
= renderer
->GetDC(); 
4176     dc
.SetFont(GetFont()); 
4177     dc
.SetTextForeground(GetForegroundColour()); 
4179     // get the intersection of the update region with the text area: note that 
4180     // the update region is in window coords and text area is in the client 
4181     // ones, so it must be shifted before computing intersection 
4182     wxRegion rgnUpdate 
= GetUpdateRegion(); 
4184     wxRect rectTextArea 
= GetRealTextArea(); 
4185     wxPoint pt 
= GetClientAreaOrigin(); 
4186     wxRect rectTextAreaAdjusted 
= rectTextArea
; 
4187     rectTextAreaAdjusted
.x 
+= pt
.x
; 
4188     rectTextAreaAdjusted
.y 
+= pt
.y
; 
4189     rgnUpdate
.Intersect(rectTextAreaAdjusted
); 
4191     // even though the drawing is already clipped to the update region, we must 
4192     // explicitly clip it to the rect we will use as otherwise parts of letters 
4193     // might be drawn outside of it (if even a small part of a charater is 
4194     // inside, HitTest() will return its column and DrawText() can't draw only 
4195     // the part of the character, of course) 
4197     // FIXME: is this really a bug in wxMSW? 
4198     rectTextArea
.width
--; 
4200     dc
.DestroyClippingRegion(); 
4201     dc
.SetClippingRegion(rectTextArea
); 
4203     // adjust for scrolling 
4206     // and now refresh the invalidated parts of the window 
4207     wxRegionIterator 
iter(rgnUpdate
); 
4208     for ( ; iter
.HaveRects(); iter
++ ) 
4210         wxRect r 
= iter
.GetRect(); 
4212         // this is a workaround for wxGTK::wxRegion bug 
4214         if ( !r
.width 
|| !r
.height 
) 
4216             // ignore invalid rect 
4221         DoDrawTextInRect(dc
, r
); 
4224     // now redraw the line wrap marks (if we draw them) 
4225     if ( WrapLines() && WData().m_widthMark 
) 
4227         // this is the rect inside which line wrap marks are drawn 
4229         rectMarks
.x 
= rectTextAreaAdjusted
.GetRight() + 1; 
4230         rectMarks
.y 
= rectTextAreaAdjusted
.y
; 
4231         rectMarks
.width 
= WData().m_widthMark
; 
4232         rectMarks
.height 
= rectTextAreaAdjusted
.height
; 
4234         rgnUpdate 
= GetUpdateRegion(); 
4235         rgnUpdate
.Intersect(rectMarks
); 
4237         wxRect rectUpdate 
= rgnUpdate
.GetBox(); 
4238         if ( rectUpdate
.width 
&& rectUpdate
.height 
) 
4240             // the marks are outside previously set clipping region 
4241             dc
.DestroyClippingRegion(); 
4243             DoDrawLineWrapMarks(dc
, rectUpdate
); 
4247     // show caret first time only: we must show it after drawing the text or 
4248     // the display can be corrupted when it's hidden 
4249     if ( !m_hasCaret 
&& GetCaret() && (FindFocus() == this) ) 
4257 // ---------------------------------------------------------------------------- 
4259 // ---------------------------------------------------------------------------- 
4261 bool wxTextCtrl::SetFont(const wxFont
& font
) 
4263     if ( !wxControl::SetFont(font
) ) 
4266     // and refresh everything, of course 
4267     InitInsertionPoint(); 
4270     // update geometry parameters 
4272     RecalcFontMetrics(); 
4273     if ( !IsSingleLine() ) 
4279     // recreate it, in fact 
4287 bool wxTextCtrl::Enable(bool enable
) 
4289     if ( !wxTextCtrlBase::Enable(enable
) ) 
4292     if (FindFocus() == this && GetCaret() && 
4293         ((enable 
&& !GetCaret()->IsVisible()) || 
4294          (!enable 
&& GetCaret()->IsVisible()))) 
4300 void wxTextCtrl::CreateCaret() 
4306         // FIXME use renderer 
4307         caret 
= new wxCaret(this, 1, GetLineHeight()); 
4311         // read only controls don't have the caret 
4312         caret 
= (wxCaret 
*)NULL
; 
4315     // SetCaret() will delete the old caret if any 
4319 void wxTextCtrl::ShowCaret(bool show
) 
4321     wxCaret 
*caret 
= GetCaret(); 
4324         // (re)position caret correctly 
4325         caret
->Move(GetCaretPosition()); 
4327         // and show it there 
4328         if ((show 
&& !caret
->IsVisible()) || 
4329             (!show 
&& caret
->IsVisible())) 
4334 // ---------------------------------------------------------------------------- 
4335 // vertical scrolling (multiline only) 
4336 // ---------------------------------------------------------------------------- 
4338 size_t wxTextCtrl::GetLinesPerPage() const 
4340     if ( IsSingleLine() ) 
4343     return GetRealTextArea().height 
/ GetLineHeight(); 
4346 wxTextPos 
wxTextCtrl::GetPositionAbove() 
4348     wxCHECK_MSG( !IsSingleLine(), INVALID_POS_VALUE
, 
4349                  _T("can't move cursor vertically in a single line control") ); 
4351     // move the cursor up by one ROW not by one LINE: this means that 
4352     // we should really use HitTest() and not just go to the same 
4353     // position in the previous line 
4354     wxPoint pt 
= GetCaretPosition() - m_rectText
.GetPosition(); 
4355     if ( MData().m_xCaret 
== -1 ) 
4357         // remember the initial cursor abscissa 
4358         MData().m_xCaret 
= pt
.x
; 
4362         // use the remembered abscissa 
4363         pt
.x 
= MData().m_xCaret
; 
4366     CalcUnscrolledPosition(pt
.x
, pt
.y
, &pt
.x
, &pt
.y
); 
4367     pt
.y 
-= GetLineHeight(); 
4369     wxTextCoord col
, row
; 
4370     if ( HitTestLogical(pt
, &col
, &row
) == wxTE_HT_BEFORE 
) 
4372         // can't move further 
4373         return INVALID_POS_VALUE
; 
4376     return XYToPosition(col
, row
); 
4379 wxTextPos 
wxTextCtrl::GetPositionBelow() 
4381     wxCHECK_MSG( !IsSingleLine(), INVALID_POS_VALUE
, 
4382                  _T("can't move cursor vertically in a single line control") ); 
4384     // see comments for wxACTION_TEXT_UP 
4385     wxPoint pt 
= GetCaretPosition() - m_rectText
.GetPosition(); 
4386     if ( MData().m_xCaret 
== -1 ) 
4388         // remember the initial cursor abscissa 
4389         MData().m_xCaret 
= pt
.x
; 
4393         // use the remembered abscissa 
4394         pt
.x 
= MData().m_xCaret
; 
4397     CalcUnscrolledPosition(pt
.x
, pt
.y
, &pt
.x
, &pt
.y
); 
4398     pt
.y 
+= GetLineHeight(); 
4400     wxTextCoord col
, row
; 
4401     if ( HitTestLogical(pt
, &col
, &row
) == wxTE_HT_BELOW 
) 
4403         // can't go further down 
4404         return INVALID_POS_VALUE
; 
4407     // note that wxTE_HT_BEYOND is ok: it happens when we go down 
4408     // from a longer line to a shorter one, for example (OTOH 
4409     // wxTE_HT_BEFORE can never happen) 
4410     return XYToPosition(col
, row
); 
4413 // ---------------------------------------------------------------------------- 
4415 // ---------------------------------------------------------------------------- 
4417 bool wxTextCtrl::PerformAction(const wxControlAction
& actionOrig
, 
4419                                const wxString
& strArg
) 
4421     // has the text changed as result of this action? 
4422     bool textChanged 
= false; 
4424     // the remembered cursor abscissa for multiline text controls is usually 
4425     // reset after each user action but for ones which do use it (UP and DOWN 
4426     // for example) we shouldn't do it - as indicated by this flag 
4427     bool rememberAbscissa 
= false; 
4429     // the command this action corresponds to or NULL if this action doesn't 
4430     // change text at all or can't be undone 
4431     wxTextCtrlCommand 
*command 
= (wxTextCtrlCommand 
*)NULL
; 
4436     if ( actionOrig
.StartsWith(wxACTION_TEXT_PREFIX_DEL
, &action
) ) 
4441     else if ( actionOrig
.StartsWith(wxACTION_TEXT_PREFIX_SEL
, &action
) ) 
4445     else // not selection nor delete action 
4447         action 
= actionOrig
; 
4450     // set newPos to -2 as it can't become equal to it in the assignments below 
4451     // (but it can become -1) 
4452     wxTextPos newPos 
= INVALID_POS_VALUE
; 
4454     if ( action 
== wxACTION_TEXT_HOME 
) 
4456         newPos 
= m_curPos 
- m_curCol
; 
4458     else if ( action 
== wxACTION_TEXT_END 
) 
4460         newPos 
= m_curPos 
+ GetLineLength(m_curRow
) - m_curCol
; 
4462     else if ( (action 
== wxACTION_TEXT_GOTO
) || 
4463               (action 
== wxACTION_TEXT_FIRST
) || 
4464               (action 
== wxACTION_TEXT_LAST
) ) 
4466         if ( action 
== wxACTION_TEXT_FIRST 
) 
4468         else if ( action 
== wxACTION_TEXT_LAST 
) 
4469             numArg 
= GetLastPosition(); 
4470         //else: numArg already contains the position 
4474     else if ( action 
== wxACTION_TEXT_UP 
) 
4476         if ( !IsSingleLine() ) 
4478             newPos 
= GetPositionAbove(); 
4480             if ( newPos 
!= INVALID_POS_VALUE 
) 
4482                 // remember where the cursor original had been 
4483                 rememberAbscissa 
= true; 
4487     else if ( action 
== wxACTION_TEXT_DOWN 
) 
4489         if ( !IsSingleLine() ) 
4491             newPos 
= GetPositionBelow(); 
4493             if ( newPos 
!= INVALID_POS_VALUE 
) 
4495                 // remember where the cursor original had been 
4496                 rememberAbscissa 
= true; 
4500     else if ( action 
== wxACTION_TEXT_LEFT 
) 
4502         newPos 
= m_curPos 
- 1; 
4504     else if ( action 
== wxACTION_TEXT_WORD_LEFT 
) 
4506         newPos 
= GetWordStart(); 
4508     else if ( action 
== wxACTION_TEXT_RIGHT 
) 
4510         newPos 
= m_curPos 
+ 1; 
4512     else if ( action 
== wxACTION_TEXT_WORD_RIGHT 
) 
4514         newPos 
= GetWordEnd(); 
4516     else if ( action 
== wxACTION_TEXT_INSERT 
) 
4518         if ( IsEditable() && !strArg
.empty() ) 
4520             // inserting text can be undone 
4521             command 
= new wxTextCtrlInsertCommand(strArg
); 
4526     else if ( (action 
== wxACTION_TEXT_PAGE_UP
) || 
4527               (action 
== wxACTION_TEXT_PAGE_DOWN
) ) 
4529         if ( !IsSingleLine() ) 
4531             size_t count 
= GetLinesPerPage(); 
4532             if ( count 
> PAGE_OVERLAP_IN_LINES 
) 
4534                 // pages should overlap slightly to allow the reader to keep 
4535                 // orientation in the text 
4536                 count 
-= PAGE_OVERLAP_IN_LINES
; 
4539             // remember where the cursor original had been 
4540             rememberAbscissa 
= true; 
4542             bool goUp 
= action 
== wxACTION_TEXT_PAGE_UP
; 
4543             for ( size_t line 
= 0; line 
< count
; line
++ ) 
4545                 wxTextPos pos 
= goUp 
? GetPositionAbove() : GetPositionBelow(); 
4546                 if ( pos 
== INVALID_POS_VALUE 
) 
4548                     // can't move further 
4552                 MoveInsertionPoint(pos
); 
4556             // we implement the Unix scrolling model here: cursor will always 
4557             // be on the first line after Page Down and on the last one after 
4560             // Windows programs usually keep the cursor line offset constant 
4561             // but do we really need it? 
4565                 // find the line such that when it is the first one, the 
4566                 // current position is in the last line 
4568                 for ( size_t line 
= 0; line 
< count
; line
++ ) 
4570                     pos 
= GetPositionAbove(); 
4571                     if ( pos 
== INVALID_POS_VALUE 
) 
4574                     MoveInsertionPoint(pos
); 
4577                 MoveInsertionPoint(newPos
); 
4579                 PositionToLogicalXY(pos
, NULL
, &y
); 
4581             else // scrolled down 
4583                 PositionToLogicalXY(newPos
, NULL
, &y
); 
4586             // scroll vertically only 
4587             Scroll(wxDefaultCoord
, y
); 
4590     else if ( action 
== wxACTION_TEXT_SEL_WORD 
) 
4592         SetSelection(GetWordStart(), GetWordEnd()); 
4594     else if ( action 
== wxACTION_TEXT_ANCHOR_SEL 
) 
4598     else if ( action 
== wxACTION_TEXT_EXTEND_SEL 
) 
4600         SetSelection(m_selAnchor
, numArg
); 
4602     else if ( action 
== wxACTION_TEXT_COPY 
) 
4606     else if ( action 
== wxACTION_TEXT_CUT 
) 
4611     else if ( action 
== wxACTION_TEXT_PASTE 
) 
4616     else if ( action 
== wxACTION_TEXT_UNDO 
) 
4621     else if ( action 
== wxACTION_TEXT_REDO 
) 
4628         return wxControl::PerformAction(action
, numArg
, strArg
); 
4631     if ( newPos 
!= INVALID_POS_VALUE 
) 
4633         // bring the new position into the range 
4637         wxTextPos posLast 
= GetLastPosition(); 
4638         if ( newPos 
> posLast 
) 
4643             // if we have the selection, remove just it 
4645             if ( HasSelection() ) 
4652                 // otherwise delete everything between current position and 
4654                 if ( m_curPos 
!= newPos 
) 
4659                 else // nothing to delete 
4661                     // prevent test below from working 
4662                     from 
= INVALID_POS_VALUE
; 
4664                     // and this is just to silent the compiler warning 
4669             if ( from 
!= INVALID_POS_VALUE 
) 
4671                 command 
= new wxTextCtrlRemoveCommand(from
, to
); 
4674         else // cursor movement command 
4677             DoSetInsertionPoint(newPos
); 
4681                 SetSelection(m_selAnchor
, m_curPos
); 
4683             else // simple movement 
4685                 // clear the existing selection 
4690         if ( !rememberAbscissa 
&& !IsSingleLine() ) 
4692             MData().m_xCaret 
= -1; 
4698         // execute and remember it to be able to undo it later 
4699         m_cmdProcessor
->Submit(command
); 
4701         // undoable commands always change text 
4704     else // no undoable command 
4706         // m_cmdProcessor->StopCompressing() 
4711         wxASSERT_MSG( IsEditable(), _T("non editable control changed?") ); 
4713         wxCommandEvent 
event(wxEVT_COMMAND_TEXT_UPDATED
, GetId()); 
4714         InitCommandEvent(event
); 
4715         GetEventHandler()->ProcessEvent(event
); 
4717         // as the text changed... 
4718         m_isModified 
= true; 
4724 void wxTextCtrl::OnChar(wxKeyEvent
& event
) 
4726     // only process the key events from "simple keys" here 
4727     if ( !event
.HasModifiers() ) 
4729         int keycode 
= event
.GetKeyCode(); 
4731         wxChar unicode 
= event
.GetUnicodeKey(); 
4733         if ( keycode 
== WXK_RETURN 
) 
4735             if ( IsSingleLine() || (GetWindowStyle() & wxTE_PROCESS_ENTER
) ) 
4737                 wxCommandEvent 
event(wxEVT_COMMAND_TEXT_ENTER
, GetId()); 
4738                 InitCommandEvent(event
); 
4739                 event
.SetString(GetValue()); 
4740                 GetEventHandler()->ProcessEvent(event
); 
4742             else // interpret <Enter> normally: insert new line 
4744                 PerformAction(wxACTION_TEXT_INSERT
, -1, _T('\n')); 
4747         else if ( keycode 
< 255 && isprint(keycode
) ) 
4749             PerformAction(wxACTION_TEXT_INSERT
, -1, (wxChar
)keycode
); 
4751             // skip event.Skip() below 
4755         else if (unicode 
> 0) 
4757             PerformAction(wxACTION_TEXT_INSERT
, -1, unicode
); 
4764     // Ctrl-R refreshes the control in debug mode 
4765     else if ( event
.ControlDown() && event
.GetKeyCode() == 'r' ) 
4767 #endif // __WXDEBUG__ 
4773 wxInputHandler 
*wxTextCtrl::GetStdInputHandler(wxInputHandler 
*handlerDef
) 
4775     static wxStdTextCtrlInputHandler 
s_handler(handlerDef
); 
4780 // ---------------------------------------------------------------------------- 
4781 // wxStdTextCtrlInputHandler 
4782 // ---------------------------------------------------------------------------- 
4784 wxStdTextCtrlInputHandler::wxStdTextCtrlInputHandler(wxInputHandler 
*inphand
) 
4785                          : wxStdInputHandler(inphand
) 
4787     m_winCapture 
= (wxTextCtrl 
*)NULL
; 
4791 wxTextPos 
wxStdTextCtrlInputHandler::HitTest(const wxTextCtrl 
*text
, 
4794     wxTextCoord col
, row
; 
4795     wxTextCtrlHitTestResult ht 
= text
->HitTest(pt
, &col
, &row
); 
4797     wxTextPos pos 
= text
->XYToPosition(col
, row
); 
4799     // if the point is after the last column we must adjust the position to be 
4800     // the last position in the line (unless it is already the last) 
4801     if ( (ht 
== wxTE_HT_BEYOND
) && (pos 
< text
->GetLastPosition()) ) 
4809 bool wxStdTextCtrlInputHandler::HandleKey(wxInputConsumer 
*consumer
, 
4810                                           const wxKeyEvent
& event
, 
4813     // we're only interested in key presses 
4817     int keycode 
= event
.GetKeyCode(); 
4819     wxControlAction action
; 
4821     bool ctrlDown 
= event
.ControlDown(), 
4822          shiftDown 
= event
.ShiftDown(); 
4825         action 
= wxACTION_TEXT_PREFIX_SEL
; 
4828     // the only key combination with Alt we recognize is Alt-Bksp for undo, so 
4829     // treat it first separately 
4830     if ( event
.AltDown() ) 
4832         if ( keycode 
== WXK_BACK 
&& !ctrlDown 
&& !shiftDown 
) 
4833             action 
= wxACTION_TEXT_UNDO
; 
4835     else switch ( keycode 
) 
4839             action 
<< (ctrlDown 
? wxACTION_TEXT_FIRST
 
4840                                 : wxACTION_TEXT_HOME
); 
4844             action 
<< (ctrlDown 
? wxACTION_TEXT_LAST
 
4845                                 : wxACTION_TEXT_END
); 
4850                 action 
<< wxACTION_TEXT_UP
; 
4855                 action 
<< wxACTION_TEXT_DOWN
; 
4859             action 
<< (ctrlDown 
? wxACTION_TEXT_WORD_LEFT
 
4860                                 : wxACTION_TEXT_LEFT
); 
4864             action 
<< (ctrlDown 
? wxACTION_TEXT_WORD_RIGHT
 
4865                                 : wxACTION_TEXT_RIGHT
); 
4869             // we don't map Ctrl-PgUp/Dn to anything special - what should it 
4870             // to? for now, it's the same as without control 
4871             action 
<< wxACTION_TEXT_PAGE_DOWN
; 
4875             action 
<< wxACTION_TEXT_PAGE_UP
; 
4881                 action 
<< wxACTION_TEXT_PREFIX_DEL 
<< wxACTION_TEXT_RIGHT
; 
4886                 action 
<< wxACTION_TEXT_PREFIX_DEL 
<< wxACTION_TEXT_LEFT
; 
4891             // reset the action as it could be already set to one of the 
4893             action 
= wxACTION_NONE
; 
4900                         action 
= wxACTION_TEXT_REDO
; 
4904                         action 
= wxACTION_TEXT_COPY
; 
4908                         action 
= wxACTION_TEXT_PASTE
; 
4912                         action 
= wxACTION_TEXT_CUT
; 
4916                         action 
= wxACTION_TEXT_UNDO
; 
4922     if ( (action 
!= wxACTION_NONE
) && (action 
!= wxACTION_TEXT_PREFIX_SEL
) ) 
4924         consumer
->PerformAction(action
, -1, str
); 
4929     return wxStdInputHandler::HandleKey(consumer
, event
, pressed
); 
4932 bool wxStdTextCtrlInputHandler::HandleMouse(wxInputConsumer 
*consumer
, 
4933                                             const wxMouseEvent
& event
) 
4935     if ( event
.LeftDown() ) 
4937         wxASSERT_MSG( !m_winCapture
, _T("left button going down twice?") ); 
4939         wxTextCtrl 
*text 
= wxStaticCast(consumer
->GetInputWindow(), wxTextCtrl
); 
4941         m_winCapture 
= text
; 
4942         m_winCapture
->CaptureMouse(); 
4946         wxTextPos pos 
= HitTest(text
, event
.GetPosition()); 
4949             text
->PerformAction(wxACTION_TEXT_ANCHOR_SEL
, pos
); 
4952     else if ( event
.LeftDClick() ) 
4954         // select the word the cursor is on 
4955         consumer
->PerformAction(wxACTION_TEXT_SEL_WORD
); 
4957     else if ( event
.LeftUp() ) 
4961             m_winCapture
->ShowCaret(); 
4963             m_winCapture
->ReleaseMouse(); 
4964             m_winCapture 
= (wxTextCtrl 
*)NULL
; 
4968     return wxStdInputHandler::HandleMouse(consumer
, event
); 
4971 bool wxStdTextCtrlInputHandler::HandleMouseMove(wxInputConsumer 
*consumer
, 
4972                                                 const wxMouseEvent
& event
) 
4977         wxTextCtrl 
*text 
= wxStaticCast(m_winCapture
, wxTextCtrl
); 
4978         wxTextPos pos 
= HitTest(text
, event
.GetPosition()); 
4981             text
->PerformAction(wxACTION_TEXT_EXTEND_SEL
, pos
); 
4985     return wxStdInputHandler::HandleMouseMove(consumer
, event
); 
4989 wxStdTextCtrlInputHandler::HandleFocus(wxInputConsumer 
*consumer
, 
4990                                        const wxFocusEvent
& event
) 
4992     wxTextCtrl 
*text 
= wxStaticCast(consumer
->GetInputWindow(), wxTextCtrl
); 
4994     // the selection appearance changes depending on whether we have the focus 
4995     text
->RefreshSelection(); 
4997     if (event
.GetEventType() == wxEVT_SET_FOCUS
) 
4999         if (text
->GetCaret() && !text
->GetCaret()->IsVisible()) 
5004         if (text
->GetCaret() && text
->GetCaret()->IsVisible()) 
5008     // never refresh entirely 
5012 #endif // wxUSE_TEXTCTRL