1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/univ/textctrl.cpp
3 // Purpose:     wxTextCtrl
4 // Author:      Vadim Zeitlin
5 // Modified by:
6 // Created:     15.09.00
7 // Copyright:   (c) 2000 SciTech Software, Inc. (www.scitechsoft.com)
8 // Licence:     wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10 
11 /*
12    TODO
13 
14 +  1. update vert scrollbar when any line length changes for WrapLines()
15 +  2. cursor movement ("Hello,^" -> "^verse!" on Arrow Down)?
16      -> maybe save the x position and use it instead of current in handling
17         DOWN/UP actions (this would make up/down always return the cursor to
18         the same location)?
19    3. split file into chunks
20 +? 4. rewrite Replace() refresh logic to deal with wrapping lines
21 +? 5. cache info found by GetPartOfWrappedLine() - performance must be horrible
22       with lots of text
23 
24    6. backspace refreshes too much (until end of line)
25  */
26 
27 /*
28    Optimisation hints from PureQuantify:
29 
30   +1. wxStringTokenize is the slowest part of Replace
31    2. GetDC/ReleaseDC are very slow, avoid calling them several times
32   +3. GetCharHeight() should be cached too
33    4. wxClientDC construction/destruction in HitTestLine is horribly expensive
34 
35    For line wrapping controls HitTest2 takes 50% of program time. The results
36    of GetRowsPerLine and GetPartOfWrappedLine *MUST* be cached.
37 
38    Search for "OPT!" for things which must be optimized.
39  */
40 
41 /*
42    Some terminology:
43 
44    Everywhere in this file LINE refers to a logical line of text, and ROW to a
45    physical line of text on the display. They are the same unless WrapLines()
46    is true in which case a single LINE may correspond to multiple ROWs.
47 
48    A text position is an unsigned int (which for reasons of compatibility is
49    still a long as wxTextPos) from 0 to GetLastPosition() inclusive. The positions
50    correspond to the gaps between the letters so the position 0 is just
51    before the first character and the last position is the one beyond the last
52    character. For an empty text control GetLastPosition() returns 0.
53 
54    Lines and columns returned/accepted by XYToPosition() and PositionToXY()
55    start from 0. The y coordinate is a LINE, not a ROW. Columns correspond to
56    the characters, the first column of a line is the first character in it,
57    the last one is length(line text). For compatibility, again, lines and
58    columns are also longs.
59 
60    When translating lines/column coordinates to/from positions, the line and
61    column give the character after the given position. Thus, GetLastPosition()
62    doesn't have any corresponding column.
63 
64    An example of positions and lines/columns for a control without wrapping
65    containing the text "Hello, Universe!\nGoodbye"
66 
67                                1 1 1 1 1 1 1
68    pos:    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
69             H e l l o ,   U n i v e r s e !         line 0
70    col:     0 1 2 3 4 5 6 7 8 9 1 1 1 1 1 1
71                                 0 1 2 3 4 5
72 
73    pos:    1 1 1 2 2 2 2 2
74            7 8 9 0 1 2 3 4
75             G o o d b y e                           line 1
76    col:     0 1 2 3 4 5 6
77 
78 
79    The same example for a control with line wrap assuming "Universe" is too
80    long to fit on the same line with "Hello,":
81 
82    pos:    0 1 2 3 4 5
83             H e l l o ,                             line 0 (row 0)
84    col:     0 1 2 3 4 5
85 
86                    1 1 1 1 1 1 1
87    pos:    6 7 8 9 0 1 2 3 4 5 6
88               U n i v e r s e !                     line 0 (row 1)
89    col:     6 7 8 9 1 1 1 1 1 1
90                     0 1 2 3 4 5
91 
92    (line 1 == row 2 same as above)
93 
94    Note that there is still the same number of columns and positions and that
95    there is no (logical) position at the end of the first ROW. This position
96    is identified with the preceding one (which is not how Windows does it: it
97    identifies it with the next one, i.e. the first position of the next line,
98    but much more logical IMHO).
99  */
100 
101 /*
102    Search for "OPT" for possible optimizations
103 
104    A possible global optimization would be to always store the coords in the
105    text in triplets (pos, col, line) and update them simultaneously instead of
106    recalculating col and line from pos each time it is needed. Currently we
107    only do it for the current position but we might also do it for the
108    selection start and end.
109  */
110 
111 // ============================================================================
112 // declarations
113 // ============================================================================
114 
115 // ----------------------------------------------------------------------------
116 // headers
117 // ----------------------------------------------------------------------------
118 
119 #include "wx/wxprec.h"
120 
121 
122 #if wxUSE_TEXTCTRL
123 
124 #include "wx/textctrl.h"
125 
126 #ifndef WX_PRECOMP
127     #include "wx/log.h"
128     #include "wx/dcclient.h"
129     #include "wx/validate.h"
130     #include "wx/dataobj.h"
131 #endif
132 
133 #include <ctype.h>
134 
135 #include "wx/clipbrd.h"
136 
137 #include "wx/textfile.h"
138 
139 #include "wx/caret.h"
140 
141 #include "wx/univ/inphand.h"
142 #include "wx/univ/renderer.h"
143 #include "wx/univ/colschem.h"
144 #include "wx/univ/theme.h"
145 
146 #include "wx/cmdproc.h"
147 
148 #if wxDEBUG_LEVEL >= 2
149     // turn extra wxTextCtrl-specific debugging on/off
150     #define WXDEBUG_TEXT
151 
152     // turn wxTextCtrl::Replace() debugging on (slows down code a *lot*!)
153     #define WXDEBUG_TEXT_REPLACE
154 #endif // wxDEBUG_LEVEL >= 2
155 
156 // wxStringTokenize only needed for debug checks
157 #ifdef WXDEBUG_TEXT_REPLACE
158     #include "wx/tokenzr.h"
159 #endif // WXDEBUG_TEXT_REPLACE
160 
161 // ----------------------------------------------------------------------------
162 // wxStdTextCtrlInputHandler: this control handles only the mouse/kbd actions
163 // common to Win32 and GTK, platform-specific things are implemented elsewhere
164 // ----------------------------------------------------------------------------
165 
166 class WXDLLEXPORT wxStdTextCtrlInputHandler : public wxStdInputHandler
167 {
168 public:
169     wxStdTextCtrlInputHandler(wxInputHandler *inphand);
170 
171     virtual bool HandleKey(wxInputConsumer *consumer,
172                            const wxKeyEvent& event,
173                            bool pressed);
174     virtual bool HandleMouse(wxInputConsumer *consumer,
175                              const wxMouseEvent& event);
176     virtual bool HandleMouseMove(wxInputConsumer *consumer,
177                                  const wxMouseEvent& event);
178     virtual bool HandleFocus(wxInputConsumer *consumer, const wxFocusEvent& event);
179 
180 protected:
181     // get the position of the mouse click
182     static wxTextPos HitTest(const wxTextCtrl *text, const wxPoint& pos);
183 
184     // capture data
185     wxTextCtrl *m_winCapture;
186 };
187 
188 // ----------------------------------------------------------------------------
189 // private functions
190 // ----------------------------------------------------------------------------
191 
192 // exchange two positions so that from is always less than or equal to to
OrderPositions(wxTextPos & from,wxTextPos & to)193 static inline void OrderPositions(wxTextPos& from, wxTextPos& to)
194 {
195     if ( from > to )
196     {
197         wxTextPos tmp = from;
198         from = to;
199         to = tmp;
200     }
201 }
202 
203 // ----------------------------------------------------------------------------
204 // constants
205 // ----------------------------------------------------------------------------
206 
207 // names of text ctrl commands
208 #define wxTEXT_COMMAND_INSERT wxT("insert")
209 #define wxTEXT_COMMAND_REMOVE wxT("remove")
210 
211 // the value which is never used for text position, even not -1 which is
212 // sometimes used for some special meaning
213 static const wxTextPos INVALID_POS_VALUE = wxInvalidTextCoord;
214 
215 // overlap between pages (when using PageUp/Dn) in lines
216 static const size_t PAGE_OVERLAP_IN_LINES = 1;
217 
218 // ----------------------------------------------------------------------------
219 // private data of wxTextCtrl
220 // ----------------------------------------------------------------------------
221 
222 // the data only used by single line text controls
223 struct wxTextSingleLineData
224 {
225     // the position of the first visible pixel and the first visible column
226     wxCoord m_ofsHorz;
227     wxTextCoord m_colStart;
228 
229     // and the last ones (m_posLastVisible is the width but m_colLastVisible
230     // is an absolute value)
231     wxCoord m_posLastVisible;
232     wxTextCoord m_colLastVisible;
233 
234     // def ctor
wxTextSingleLineDatawxTextSingleLineData235     wxTextSingleLineData()
236     {
237         m_colStart = 0;
238         m_ofsHorz = 0;
239 
240         m_colLastVisible = -1;
241         m_posLastVisible = -1;
242     }
243 
244 };
245 
246 // the data only used by multi line text controls
247 struct wxTextMultiLineData
248 {
249     // the lines of text
250     wxArrayString m_lines;
251 
252     // the current ranges of the scrollbars
253     int m_scrollRangeX,
254         m_scrollRangeY;
255 
256     // should we adjust the horz/vert scrollbar?
257     bool m_updateScrollbarX,
258          m_updateScrollbarY;
259 
260     // the max line length in pixels
261     wxCoord m_widthMax;
262 
263     // the index of the line which has the length of m_widthMax
264     wxTextCoord m_lineLongest;
265 
266     // the rect in which text appears: it is even less than m_rectText because
267     // only the last _complete_ line is shown, hence there is an unoccupied
268     // horizontal band at the bottom of it
269     wxRect m_rectTextReal;
270 
271     // the x-coordinate of the caret before we started moving it vertically:
272     // this is used to ensure that moving the caret up and then down will
273     // return it to the same position as if we always round it in one direction
274     // we would shift it in that direction
275     //
276     // when m_xCaret == -1, we don't have any remembered position
277     wxCoord m_xCaret;
278 
279     // the def ctor
wxTextMultiLineDatawxTextMultiLineData280     wxTextMultiLineData()
281     {
282         m_scrollRangeX =
283         m_scrollRangeY = 0;
284 
285         m_updateScrollbarX =
286         m_updateScrollbarY = false;
287 
288         m_widthMax = -1;
289         m_lineLongest = 0;
290 
291         m_xCaret = -1;
292     }
293 };
294 
295 // the data only used by multi line text controls in line wrap mode
296 class wxWrappedLineData
297 {
298     // these functions set all our values, so give them access to them
299 friend void wxTextCtrl::LayoutLine(wxTextCoord line,
300                                     wxWrappedLineData& lineData) const;
301 friend void wxTextCtrl::LayoutLines(wxTextCoord) const;
302 
303 public:
304     // def ctor
wxWrappedLineData()305     wxWrappedLineData()
306     {
307         m_rowFirst = -1;
308     }
309 
310     // get the start of any row (remember that accessing m_rowsStart doesn't work
311     // for the first one)
GetRowStart(wxTextCoord row) const312     wxTextCoord GetRowStart(wxTextCoord row) const
313     {
314         wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
315 
316         return row ? m_rowsStart[row - 1] : 0;
317     }
318 
319     // get the length of the row (using the total line length which we don't
320     // have here but need to calculate the length of the last row, so it must
321     // be given to us)
GetRowLength(wxTextCoord row,wxTextCoord lenLine) const322     wxTextCoord GetRowLength(wxTextCoord row, wxTextCoord lenLine) const
323     {
324         wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
325 
326         // note that m_rowsStart[row] is the same as GetRowStart(row + 1) (but
327         // slightly more efficient) and lenLine is the same as the start of the
328         // first row of the next line
329         return ((size_t)row == m_rowsStart.GetCount() ? lenLine : m_rowsStart[row])
330                     - GetRowStart(row);
331     }
332 
333     // return the width of the row in pixels
GetRowWidth(wxTextCoord row) const334     wxCoord GetRowWidth(wxTextCoord row) const
335     {
336         wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
337 
338         return m_rowsWidth[row];
339     }
340 
341     // return the number of rows
GetRowCount() const342     size_t GetRowCount() const
343     {
344         wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
345 
346         return m_rowsStart.GetCount() + 1;
347     }
348 
349     // return the number of additional (i.e. after the first one) rows
GetExtraRowCount() const350     size_t GetExtraRowCount() const
351     {
352         wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
353 
354         return m_rowsStart.GetCount();
355     }
356 
357     // return the first row of this line
GetFirstRow() const358     wxTextCoord GetFirstRow() const
359     {
360         wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
361 
362         return m_rowFirst;
363     }
364 
365     // return the first row of the next line
GetNextRow() const366     wxTextCoord GetNextRow() const
367     {
368         wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
369 
370         return m_rowFirst + m_rowsStart.GetCount() + 1;
371     }
372 
373     // this just provides direct access to m_rowsStart aerray for efficiency
GetExtraRowStart(wxTextCoord row) const374     wxTextCoord GetExtraRowStart(wxTextCoord row) const
375     {
376         wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
377 
378         return m_rowsStart[row];
379     }
380 
381     // this code is unused any longer
382 #if 0
383     // return true if the column is in the start of the last row (hence the row
384     // it is in is not wrapped)
385     bool IsLastRow(wxTextCoord colRowStart) const
386     {
387         return colRowStart == GetRowStart(m_rowsStart.GetCount());
388     }
389 
390     // return true if the column is the last column of the row starting in
391     // colRowStart
392     bool IsLastColInRow(wxTextCoord colRowStart,
393                         wxTextCoord colRowEnd,
394                         wxTextCoord lenLine) const
395     {
396         // find the row which starts with colRowStart
397         size_t nRows = GetRowCount();
398         for ( size_t n = 0; n < nRows; n++ )
399         {
400             if ( GetRowStart(n) == colRowStart )
401             {
402                 wxTextCoord colNextRowStart = n == nRows - 1
403                                                 ? lenLine
404                                                 : GetRowStart(n + 1);
405 
406                 wxASSERT_MSG( colRowEnd < colNextRowStart,
407                               wxT("this column is not in this row at all!") );
408 
409                 return colRowEnd == colNextRowStart - 1;
410             }
411         }
412 
413         // caller got it wrong
414         wxFAIL_MSG( wxT("this column is not in the start of the row!") );
415 
416         return false;
417     }
418 #endif // 0
419 
420     // is this row the last one in its line?
IsLastRow(wxTextCoord row) const421     bool IsLastRow(wxTextCoord row) const
422     {
423         return (size_t)row == GetExtraRowCount();
424     }
425 
426     // the line is valid if it had been laid out correctly: note that just
427     // shiwting the line (because one of previous lines changed) doesn't make
428     // it invalid
IsValid() const429     bool IsValid() const { return !m_rowsWidth.IsEmpty(); }
430 
431     // invalidating line will relayout it
Invalidate()432     void Invalidate() { m_rowsWidth.Empty(); }
433 
434 private:
435     // for each line we remember the starting columns of all its rows after the
436     // first one (which always starts at 0), i.e. if a line is wrapped twice
437     // (== takes 3 rows) its m_rowsStart[0] may be 10 and m_rowsStart[1] == 15
438     wxArrayLong m_rowsStart;
439 
440     // and the width of each row in pixels (this array starts from 0, as usual)
441     wxArrayInt m_rowsWidth;
442 
443     // and also its starting row (0 for the first line, first lines'
444     // m_rowsStart.GetCount() + 1 for the second &c): it is set to -1 initially
445     // and this means that the struct hadn't yet been initialized
446     wxTextCoord m_rowFirst;
447 
448     // the last modification "time"-stamp used by LayoutLines()
449     size_t m_timestamp;
450 };
451 
452 WX_DECLARE_OBJARRAY(wxWrappedLineData, wxArrayWrappedLinesData);
453 #include "wx/arrimpl.cpp"
454 WX_DEFINE_OBJARRAY(wxArrayWrappedLinesData);
455 
456 struct wxTextWrappedData : public wxTextMultiLineData
457 {
458     // the width of the column to the right of the text rect used for the
459     // indicator mark display for the wrapped lines
460     wxCoord m_widthMark;
461 
462     // the data for each line
463     wxArrayWrappedLinesData m_linesData;
464 
465     // flag telling us to recalculate all starting rows starting from this line
466     // (if it is -1, we don't have to recalculate anything) - it is set when
467     // the number of the rows in the middle of the control changes
468     wxTextCoord m_rowFirstInvalid;
469 
470     // the current timestamp used by LayoutLines()
471     size_t m_timestamp;
472 
473     // invalidate starting rows of all lines (NOT rows!) after this one
InvalidateLinesBelowwxTextWrappedData474     void InvalidateLinesBelow(wxTextCoord line)
475     {
476         if ( m_rowFirstInvalid == -1 || m_rowFirstInvalid > line )
477         {
478             m_rowFirstInvalid = line;
479         }
480     }
481 
482     // check if this line is valid: i.e. before the first invalid one
IsValidLinewxTextWrappedData483     bool IsValidLine(wxTextCoord line) const
484     {
485         return ((m_rowFirstInvalid == -1) || (line < m_rowFirstInvalid)) &&
486                     m_linesData[line].IsValid();
487     }
488 
489     // def ctor
wxTextWrappedDatawxTextWrappedData490     wxTextWrappedData()
491     {
492         m_widthMark = 0;
493         m_rowFirstInvalid = -1;
494         m_timestamp = 0;
495     }
496 };
497 
498 // ----------------------------------------------------------------------------
499 // private classes for undo/redo management
500 // ----------------------------------------------------------------------------
501 
502 /*
503    We use custom versions of wxWidgets command processor to implement undo/redo
504    as we want to avoid storing the backpointer to wxTextCtrl in wxCommand
505    itself: this is a waste of memory as all commands in the given command
506    processor always have the same associated wxTextCtrl and so it makes sense
507    to store the backpointer there.
508 
509    As for the rest of the implementation, it's fairly standard: we have 2
510    command classes corresponding to adding and removing text.
511  */
512 
513 // a command corresponding to a wxTextCtrl action
514 class wxTextCtrlCommand : public wxCommand
515 {
516 public:
wxTextCtrlCommand(const wxString & name)517     wxTextCtrlCommand(const wxString& name) : wxCommand(true, name) { }
518 
519     // we don't use these methods as they don't make sense for us as we need a
520     // wxTextCtrl to be applied
Do()521     virtual bool Do() { wxFAIL_MSG(wxT("shouldn't be called")); return false; }
Undo()522     virtual bool Undo() { wxFAIL_MSG(wxT("shouldn't be called")); return false; }
523 
524     // instead, our command processor uses these methods
525     virtual bool Do(wxTextCtrl *text) = 0;
526     virtual bool Undo(wxTextCtrl *text) = 0;
527 };
528 
529 // insert text command
530 class wxTextCtrlInsertCommand : public wxTextCtrlCommand
531 {
532 public:
wxTextCtrlInsertCommand(const wxString & textToInsert)533     wxTextCtrlInsertCommand(const wxString& textToInsert)
534         : wxTextCtrlCommand(wxTEXT_COMMAND_INSERT), m_text(textToInsert)
535     {
536         m_from = -1;
537     }
538 
539     // combine the 2 commands together
540     void Append(wxTextCtrlInsertCommand *other);
541 
542     virtual bool CanUndo() const;
543     virtual bool Do(wxTextCtrl *text);
Do()544     virtual bool Do() { return wxTextCtrlCommand::Do(); }
Undo()545     virtual bool Undo() { return wxTextCtrlCommand::Undo(); }
546     virtual bool Undo(wxTextCtrl *text);
547 
548 private:
549     // the text we insert
550     wxString m_text;
551 
552     // the position where we inserted the text
553     wxTextPos m_from;
554 };
555 
556 // remove text command
557 class wxTextCtrlRemoveCommand : public wxTextCtrlCommand
558 {
559 public:
wxTextCtrlRemoveCommand(wxTextPos from,wxTextPos to)560     wxTextCtrlRemoveCommand(wxTextPos from, wxTextPos to)
561         : wxTextCtrlCommand(wxTEXT_COMMAND_REMOVE)
562     {
563         m_from = from;
564         m_to = to;
565     }
566 
567     virtual bool CanUndo() const;
568     virtual bool Do(wxTextCtrl *text);
Do()569     virtual bool Do() { return wxTextCtrlCommand::Do(); }
Undo()570     virtual bool Undo() { return wxTextCtrlCommand::Undo(); }
571     virtual bool Undo(wxTextCtrl *text);
572 
573 private:
574     // the range of text to delete
575     wxTextPos m_from,
576          m_to;
577 
578     // the text which was deleted when this command was Do()ne
579     wxString m_textDeleted;
580 };
581 
582 // a command processor for a wxTextCtrl
583 class wxTextCtrlCommandProcessor : public wxCommandProcessor
584 {
585 public:
wxTextCtrlCommandProcessor(wxTextCtrl * text)586     wxTextCtrlCommandProcessor(wxTextCtrl *text)
587     {
588         m_compressInserts = false;
589 
590         m_text = text;
591     }
592 
593     // override Store() to compress multiple wxTextCtrlInsertCommand into one
594     virtual void Store(wxCommand *command);
595 
596     // stop compressing insert commands when this is called
StopCompressing()597     void StopCompressing() { m_compressInserts = false; }
598 
599     // accessors
GetTextCtrl() const600     wxTextCtrl *GetTextCtrl() const { return m_text; }
IsCompressing() const601     bool IsCompressing() const { return m_compressInserts; }
602 
603 protected:
DoCommand(wxCommand & cmd)604     virtual bool DoCommand(wxCommand& cmd)
605         { return ((wxTextCtrlCommand &)cmd).Do(m_text); }
UndoCommand(wxCommand & cmd)606     virtual bool UndoCommand(wxCommand& cmd)
607         { return ((wxTextCtrlCommand &)cmd).Undo(m_text); }
608 
609     // check if this command is a wxTextCtrlInsertCommand and return it casted
610     // to the right type if it is or NULL otherwise
611     wxTextCtrlInsertCommand *IsInsertCommand(wxCommand *cmd);
612 
613 private:
614     // the control we're associated with
615     wxTextCtrl *m_text;
616 
617     // if the flag is true we're compressing subsequent insert commands into
618     // one so that the entire typing could be undone in one call to Undo()
619     bool m_compressInserts;
620 };
621 
622 // ============================================================================
623 // implementation
624 // ============================================================================
625 
wxBEGIN_EVENT_TABLE(wxTextCtrl,wxTextCtrlBase)626 wxBEGIN_EVENT_TABLE(wxTextCtrl, wxTextCtrlBase)
627     EVT_CHAR(wxTextCtrl::OnChar)
628 
629     EVT_SIZE(wxTextCtrl::OnSize)
630 wxEND_EVENT_TABLE()
631 
632 // ----------------------------------------------------------------------------
633 // creation
634 // ----------------------------------------------------------------------------
635 
636 void wxTextCtrl::Init()
637 {
638     m_selAnchor =
639     m_selStart =
640     m_selEnd = -1;
641 
642     m_isModified = false;
643     m_isEditable = true;
644     m_wrapLines = false;
645 
646     m_posLast =
647     m_curPos =
648     m_curCol =
649     m_curRow = 0;
650 
651     // if m_maxLength is zero means there is no restriction of max length
652     m_maxLength = 0;
653 
654     m_heightLine =
655     m_widthAvg = -1;
656 
657     // init the undo manager
658     m_cmdProcessor = new wxTextCtrlCommandProcessor(this);
659 
660     // no data yet
661     m_data.data = NULL;
662 }
663 
Create(wxWindow * parent,wxWindowID id,const wxString & value,const wxPoint & pos,const wxSize & size,long style,const wxValidator & validator,const wxString & name)664 bool wxTextCtrl::Create(wxWindow *parent,
665                         wxWindowID id,
666                         const wxString& value,
667                         const wxPoint& pos,
668                         const wxSize& size,
669                         long style,
670                         const wxValidator& validator,
671                         const wxString &name)
672 {
673     if ( style & wxTE_MULTILINE )
674     {
675         // for compatibility with wxMSW we create the controls with vertical
676         // scrollbar always shown unless they have wxTE_RICH style (because
677         // Windows text controls always has vert scrollbar but richedit one
678         // doesn't)
679         if ( !(style & wxTE_RICH) )
680         {
681             style |= wxALWAYS_SHOW_SB;
682         }
683 
684         // wrapping style: wxTE_DONTWRAP == wxHSCROLL so if it's _not_ given,
685         // we won't have horizontal scrollbar automatically, no need to do
686         // anything
687 
688         // TODO: support wxTE_NO_VSCROLL (?)
689 
690         // create data object for normal multiline or for controls with line
691         // wrap as needed
692         if ( style & wxHSCROLL )
693         {
694             m_data.mdata = new wxTextMultiLineData;
695         }
696         else // we must wrap lines if we don't have horizontal scrollbar
697         {
698             // NB: we can't rely on HasFlag(wxHSCROLL) as the flags can change
699             //     later and even wxWindow::Create() itself temporarily resets
700             //     wxHSCROLL in wxUniv, so remember that we have a wrapped data
701             //     and not just a multi line data in a separate variable
702             m_wrapLines = true;
703             m_data.wdata = new wxTextWrappedData;
704         }
705     }
706     else
707     {
708         // this doesn't make sense for single line controls
709         style &= ~wxHSCROLL;
710 
711         // create data object for single line controls
712         m_data.sdata = new wxTextSingleLineData;
713     }
714 
715 #if wxUSE_TWO_WINDOWS
716     if ((style & wxBORDER_MASK) == 0)
717         style |= wxBORDER_SUNKEN;
718 #endif
719 
720     if ( !wxControl::Create(parent, id, pos, size, style,
721                             validator, name) )
722     {
723         return false;
724     }
725 
726     SetCursor(wxCURSOR_IBEAM);
727 
728     if ( style & wxTE_MULTILINE )
729     {
730         // we should always have at least one line in a multiline control
731         MData().m_lines.Add(wxEmptyString);
732 
733         if ( !(style & wxHSCROLL) )
734         {
735             WData().m_linesData.Add(new wxWrappedLineData);
736             WData().InvalidateLinesBelow(0);
737         }
738 
739         // we might support it but it's quite useless and other ports don't
740         // support it anyhow
741         wxASSERT_MSG( !(style & wxTE_PASSWORD),
742                       wxT("wxTE_PASSWORD can't be used with multiline ctrls") );
743     }
744 
745     RecalcFontMetrics();
746     ChangeValue(value);
747     SetInitialSize(size);
748 
749     m_isEditable = !(style & wxTE_READONLY);
750 
751     CreateCaret();
752     InitInsertionPoint();
753 
754     // we can't show caret right now as we're not shown yet and so it would
755     // result in garbage on the screen - we'll do it after first OnPaint()
756     m_hasCaret = false;
757 
758     CreateInputHandler(wxINP_HANDLER_TEXTCTRL);
759 
760     wxSizeEvent sizeEvent(GetSize(), GetId());
761     GetEventHandler()->ProcessEvent(sizeEvent);
762 
763     return true;
764 }
765 
~wxTextCtrl()766 wxTextCtrl::~wxTextCtrl()
767 {
768     delete m_cmdProcessor;
769 
770     if ( m_data.data )
771     {
772         if ( IsSingleLine() )
773             delete m_data.sdata;
774         else if ( WrapLines() )
775             delete m_data.wdata;
776         else
777             delete m_data.mdata;
778     }
779 }
780 
781 // ----------------------------------------------------------------------------
782 // set/get the value
783 // ----------------------------------------------------------------------------
784 
DoSetValue(const wxString & value,int flags)785 void wxTextCtrl::DoSetValue(const wxString& value, int flags)
786 {
787     if ( value != GetValue() )
788     {
789         EventsSuppressor noeventsIf(this, !(flags & SetValue_SendEvent));
790 
791         Replace(0, GetLastPosition(), value);
792 
793         if ( IsSingleLine() )
794         {
795             SetInsertionPoint(0);
796         }
797     }
798     else // nothing changed
799     {
800         // still send event for consistency
801         if ( flags & SetValue_SendEvent )
802             SendTextUpdatedEvent();
803     }
804 }
805 
GetLines() const806 const wxArrayString& wxTextCtrl::GetLines() const
807 {
808     return MData().m_lines;
809 }
810 
GetLineCount() const811 size_t wxTextCtrl::GetLineCount() const
812 {
813     return MData().m_lines.GetCount();
814 }
815 
DoGetValue() const816 wxString wxTextCtrl::DoGetValue() const
817 {
818     // for multiline controls we don't always store the total value but only
819     // recompute it when asked - and to invalidate it we just empty it in
820     // Replace()
821     if ( !IsSingleLine() && m_value.empty() )
822     {
823         // recalculate: note that we always do it for empty multilien control,
824         // but then it's so quick that it's not important
825 
826         // the first line is special as there is no \n before it, so it's
827         // outside the loop
828         const wxArrayString& lines = GetLines();
829         wxTextCtrl *self = wxConstCast(this, wxTextCtrl);
830         self->m_value << lines[0u];
831         size_t count = lines.GetCount();
832         for ( size_t n = 1; n < count; n++ )
833         {
834             self->m_value << wxT('\n') << lines[n];
835         }
836     }
837 
838     return m_value;
839 }
840 
Clear()841 void wxTextCtrl::Clear()
842 {
843     SetValue(wxEmptyString);
844 }
845 
ReplaceLine(wxTextCoord line,const wxString & text)846 bool wxTextCtrl::ReplaceLine(wxTextCoord line,
847                              const wxString& text)
848 {
849     if ( WrapLines() )
850     {
851         // first, we have to relayout the line entirely
852         //
853         // OPT: we might try not to recalc the unchanged part of line
854 
855         wxWrappedLineData& lineData = WData().m_linesData[line];
856 
857         // if we had some number of rows before, use this number, otherwise
858         // just make sure that the test below (rowsNew != rowsOld) will be true
859         int rowsOld;
860         if ( lineData.IsValid() )
861         {
862             rowsOld = lineData.GetExtraRowCount();
863         }
864         else // line wasn't laid out yet
865         {
866             // assume it changed entirely as we can't do anything better
867             rowsOld = -1;
868         }
869 
870         // now change the line
871         MData().m_lines[line] = text;
872 
873         // OPT: we choose to lay it out immediately instead of delaying it
874         //      until it is needed because it allows us to avoid invalidating
875         //      lines further down if the number of rows didn't change, but
876         //      maybe we can improve this even further?
877         LayoutLine(line, lineData);
878 
879         int rowsNew = lineData.GetExtraRowCount();
880 
881         if ( rowsNew != rowsOld )
882         {
883             // we have to update the line wrap marks as this is normally done
884             // by LayoutLines() which we bypassed by calling LayoutLine()
885             // directly
886             wxTextCoord rowFirst = lineData.GetFirstRow(),
887                         rowCount = wxMax(rowsOld, rowsNew);
888             RefreshLineWrapMarks(rowFirst, rowFirst + rowCount);
889 
890             // next, if this is not the last line, as the number of rows in it
891             // changed, we need to shift all the lines below it
892             if ( (size_t)line < WData().m_linesData.GetCount() )
893             {
894                 // number of rows changed shifting all lines below
895                 WData().InvalidateLinesBelow(line + 1);
896             }
897 
898             // the number of rows changed
899             return true;
900         }
901     }
902     else // no line wrap
903     {
904         MData().m_lines[line] = text;
905     }
906 
907     // the number of rows didn't change
908     return false;
909 }
910 
RemoveLine(wxTextCoord line)911 void wxTextCtrl::RemoveLine(wxTextCoord line)
912 {
913     MData().m_lines.RemoveAt(line);
914     if ( WrapLines() )
915     {
916         // we need to recalculate all the starting rows from this line, but we
917         // can avoid doing it if this line was never calculated: this means
918         // that we will recalculate all lines below it anyhow later if needed
919         if ( WData().IsValidLine(line) )
920         {
921             WData().InvalidateLinesBelow(line);
922         }
923 
924         WData().m_linesData.RemoveAt(line);
925     }
926 }
927 
InsertLine(wxTextCoord line,const wxString & text)928 void wxTextCtrl::InsertLine(wxTextCoord line, const wxString& text)
929 {
930     MData().m_lines.Insert(text, line);
931     if ( WrapLines() )
932     {
933         WData().m_linesData.Insert(new wxWrappedLineData, line);
934 
935         // invalidate everything below it
936         WData().InvalidateLinesBelow(line);
937     }
938 }
939 
Replace(wxTextPos from,wxTextPos to,const wxString & text)940 void wxTextCtrl::Replace(wxTextPos from, wxTextPos to, const wxString& text)
941 {
942     wxTextCoord colStart, colEnd,
943                 lineStart, lineEnd;
944     // as convention, ( src/gtk/textctrl.cpp:1411, src/msw/textctrl.cpp:759,
945     // test/controls/textentrytest.cpp:171 )
946     // if `to` equal -1, it means go to the last position
947     if ( to == -1 )
948         to = GetLastPosition();
949 
950     if ( (from > to) ||
951          !PositionToXY(from, &colStart, &lineStart) ||
952          !PositionToXY(to, &colEnd, &lineEnd) )
953     {
954         wxFAIL_MSG(wxT("invalid range in wxTextCtrl::Replace"));
955 
956         return;
957     }
958 
959 #ifdef WXDEBUG_TEXT_REPLACE
960     // a straighforward (but very inefficient) way of calculating what the new
961     // value should be
962     wxString textTotal = GetValue();
963     wxString textTotalNew(textTotal, (size_t)from);
964     textTotalNew += text;
965     if ( (size_t)to < textTotal.length() )
966         textTotalNew += textTotal.c_str() + (size_t)to;
967 #endif // WXDEBUG_TEXT_REPLACE
968 
969     // remember the old selection and reset it immediately: we must do it
970     // before calling Refresh(anything) as, at least under GTK, this leads to
971     // an _immediate_ repaint (under MSW it is delayed) and hence parts of
972     // text would be redrawn as selected if we didn't reset the selection
973     int selStartOld = m_selStart,
974         selEndOld = m_selEnd;
975 
976     // set selection range for GetSelection and GetInsertionPoint call
977     // if give a range but the text length that give doesn't equal the range
978     // it mean clear the text in the range and set the text after `from`
979     if ( (to - from) != (wxTextPos)text.Len() )
980     {
981         m_selStart = from;
982         m_selEnd = from + text.Len();
983     }
984 
985     if ( IsSingleLine() )
986     {
987         // replace the part of the text with the new value
988         wxString valueNew(m_value, (size_t)from);
989 
990         // remember it for later use
991         wxCoord startNewText = GetTextWidth(valueNew);
992 
993         valueNew += text;
994         if ( (size_t)to < m_value.length() )
995         {
996             valueNew += m_value.c_str() + (size_t)to;
997         }
998 
999         // we usually refresh till the end of line except of the most common case
1000         // when some text is appended to the end of the string in which case we
1001         // refresh just it
1002         wxCoord widthNewText;
1003 
1004         if ( (size_t)from < m_value.length() )
1005         {
1006             // refresh till the end of line
1007             widthNewText = 0;
1008         }
1009         else // text appended, not replaced
1010         {
1011             // refresh only the new text
1012             widthNewText = GetTextWidth(text);
1013         }
1014 
1015         m_value = valueNew;
1016 
1017         // force SData().m_colLastVisible update
1018         SData().m_colLastVisible = -1;
1019 
1020         // repaint
1021         RefreshPixelRange(0, startNewText, widthNewText);
1022     }
1023     else // multiline
1024     {
1025         //OPT: special case for replacements inside single line?
1026 
1027         /*
1028            Join all the lines in the replacement range into one string, then
1029            replace a part of it with the new text and break it into lines again.
1030         */
1031 
1032         // (0) we want to know if this replacement changes the number of rows
1033         //     as if it does we need to refresh everything below the changed
1034         //     text (it will be shifted...) and we can avoid it if there is no
1035         //     row relayout
1036         bool rowsNumberChanged = false;
1037 
1038         // (1) join lines
1039         const wxArrayString& linesOld = GetLines();
1040         wxString textOrig;
1041         wxTextCoord line;
1042         for ( line = lineStart; line <= lineEnd; line++ )
1043         {
1044             if ( line > lineStart )
1045             {
1046                 // from the previous line
1047                 textOrig += wxT('\n');
1048             }
1049 
1050             textOrig += linesOld[line];
1051         }
1052 
1053         // we need to append the '\n' for the last line unless there is no
1054         // following line
1055         size_t countOld = linesOld.GetCount();
1056 
1057         // (2) replace text in the combined string
1058 
1059         // (2a) leave the part before replaced area unchanged
1060         wxString textNew(textOrig, colStart);
1061 
1062         // these values will be used to refresh the changed area below
1063         wxCoord widthNewText,
1064                 startNewText = GetTextWidth(textNew);
1065         if ( (size_t)colStart == linesOld[lineStart].length() )
1066         {
1067             // text appended, refresh just enough to show the new text
1068             widthNewText = GetTextWidth(text.BeforeFirst(wxT('\n')));
1069         }
1070         else // text inserted, refresh till the end of line
1071         {
1072             widthNewText = 0;
1073         }
1074 
1075         // (2b) insert new text
1076         textNew += text;
1077 
1078         // (2c) and append the end of the old text
1079 
1080         // adjust for index shift: to is relative to colStart, not 0
1081         size_t toRel = (size_t)((to - from) + colStart);
1082         if ( toRel < textOrig.length() )
1083         {
1084             textNew += textOrig.c_str() + toRel;
1085         }
1086 
1087         // (3) break it into lines
1088 
1089         wxArrayString lines;
1090         const wxChar *curLineStart = textNew.c_str();
1091         for ( const wxChar *p = textNew.c_str(); ; p++ )
1092         {
1093             // end of line/text?
1094             if ( !*p || *p == wxT('\n') )
1095             {
1096                 lines.Add(wxString(curLineStart, p));
1097                 if ( !*p )
1098                     break;
1099 
1100                 curLineStart = p + 1;
1101             }
1102         }
1103 
1104 #ifdef WXDEBUG_TEXT_REPLACE
1105         // (3a) all empty tokens should be counted as replacing with "foo" and
1106         //      with "foo\n" should have different effects
1107         wxArrayString lines2 = wxStringTokenize(textNew, wxT("\n"),
1108                                                 wxTOKEN_RET_EMPTY_ALL);
1109 
1110         if ( lines2.IsEmpty() )
1111         {
1112             lines2.Add(wxEmptyString);
1113         }
1114 
1115         wxASSERT_MSG( lines.GetCount() == lines2.GetCount(),
1116                       wxT("Replace() broken") );
1117         for ( size_t n = 0; n < lines.GetCount(); n++ )
1118         {
1119             wxASSERT_MSG( lines[n] == lines2[n], wxT("Replace() broken") );
1120         }
1121 #endif // WXDEBUG_TEXT_REPLACE
1122 
1123         // (3b) special case: if we replace everything till the end we need to
1124         //      keep an empty line or the lines would disappear completely
1125         //      (this also takes care of never leaving m_lines empty)
1126         if ( ((size_t)lineEnd == countOld - 1) && lines.IsEmpty() )
1127         {
1128             lines.Add(wxEmptyString);
1129         }
1130 
1131         size_t nReplaceCount = lines.GetCount(),
1132                nReplaceLine = 0;
1133 
1134         // (4) merge into the array
1135 
1136         // (4a) replace
1137         for ( line = lineStart; line <= lineEnd; line++, nReplaceLine++ )
1138         {
1139             if ( nReplaceLine < nReplaceCount )
1140             {
1141                 // we have the replacement line for this one
1142                 if ( ReplaceLine(line, lines[nReplaceLine]) )
1143                 {
1144                     rowsNumberChanged = true;
1145                 }
1146 
1147                 UpdateMaxWidth(line);
1148             }
1149             else // no more replacement lines
1150             {
1151                 // (4b) delete all extra lines (note that we need to delete
1152                 //      them backwards because indices shift while we do it)
1153                 bool deletedLongestLine = false;
1154                 for ( wxTextCoord lineDel = lineEnd; lineDel >= line; lineDel-- )
1155                 {
1156                     if ( lineDel == MData().m_lineLongest )
1157                     {
1158                         // we will need to recalc the max line width
1159                         deletedLongestLine = true;
1160                     }
1161 
1162                     RemoveLine(lineDel);
1163                 }
1164 
1165                 if ( deletedLongestLine )
1166                 {
1167                     RecalcMaxWidth();
1168                 }
1169 
1170                 // even the line number changed
1171                 rowsNumberChanged = true;
1172 
1173                 // update line to exit the loop
1174                 line = lineEnd + 1;
1175             }
1176         }
1177 
1178         // (4c) insert the new lines
1179         if ( nReplaceLine < nReplaceCount )
1180         {
1181             // even the line number changed
1182             rowsNumberChanged = true;
1183 
1184             do
1185             {
1186                 InsertLine(++lineEnd, lines[nReplaceLine++]);
1187 
1188                 UpdateMaxWidth(lineEnd);
1189             }
1190             while ( nReplaceLine < nReplaceCount );
1191         }
1192 
1193         // (5) now refresh the changed area
1194 
1195         // update the (cached) last position first as refresh functions use it
1196         m_posLast += text.length() - to + from;
1197 
1198         // we may optimize refresh if the number of rows didn't change - but if
1199         // it did we have to refresh everything below the part we chanegd as
1200         // well as it might have moved
1201         if ( !rowsNumberChanged )
1202         {
1203             // refresh the line we changed
1204             if ( !WrapLines() )
1205             {
1206                 RefreshPixelRange(lineStart++, startNewText, widthNewText);
1207             }
1208             else
1209             {
1210                 //OPT: we shouldn't refresh the unchanged part of the line in
1211                 //     this case, but instead just refresh the tail of it - the
1212                 //     trouble is that we don't know here where does this tail
1213                 //     start
1214             }
1215 
1216             // number of rows didn't change, refresh the updated rows and the
1217             // last one
1218             if ( lineStart <= lineEnd )
1219                 RefreshLineRange(lineStart, lineEnd);
1220         }
1221         else // rows number did change
1222         {
1223             if ( !WrapLines() )
1224             {
1225                 // refresh only part of the first line
1226                 RefreshPixelRange(lineStart++, startNewText, widthNewText);
1227             }
1228             //else: we have to refresh everything as some part of the text
1229             //      could be in the previous row before but moved to the next
1230             //      one now (due to word wrap)
1231 
1232             wxTextCoord lineEnd = GetLines().GetCount() - 1;
1233             if ( lineStart <= lineEnd )
1234                 RefreshLineRange(lineStart, lineEnd);
1235 
1236             // refresh text rect left below
1237             RefreshLineRange(lineEnd + 1, 0);
1238 
1239             // the vert scrollbar might [dis]appear
1240             MData().m_updateScrollbarY = true;
1241         }
1242 
1243         // must recalculate it - will do later
1244         m_value.clear();
1245     }
1246 
1247 #ifdef WXDEBUG_TEXT_REPLACE
1248     // optimized code above should give the same result as straightforward
1249     // computation in the beginning
1250     wxASSERT_MSG( GetValue() == textTotalNew, wxT("error in Replace()") );
1251 #endif // WXDEBUG_TEXT_REPLACE
1252 
1253     // update the current position: note that we always put the cursor at the
1254     // end of the replacement text
1255     DoSetInsertionPoint(from + text.length());
1256 
1257     // and the selection: this is complicated by the fact that selection coords
1258     // must be first updated to reflect change in text coords, i.e. if we had
1259     // selection from 17 to 19 and we just removed this range, we don't have to
1260     // refresh anything, so we can't just use ClearSelection() here
1261     if ( selStartOld != -1 )
1262     {
1263         // refresh the parst of the selection outside the changed text (which
1264         // we already refreshed)
1265         if ( selStartOld < from )
1266             RefreshTextRange(selStartOld, from);
1267         if ( to < selEndOld )
1268             RefreshTextRange(to, selEndOld);
1269 
1270     }
1271 
1272     // now call it to do the rest (not related to refreshing)
1273     ClearSelection();
1274 
1275     SendTextUpdatedEventIfAllowed();
1276 }
1277 
Remove(wxTextPos from,wxTextPos to)1278 void wxTextCtrl::Remove(wxTextPos from, wxTextPos to)
1279 {
1280     // treat -1 as the last position
1281     if ( to == -1 )
1282         to = GetLastPosition();
1283 
1284     // Replace() only works with correctly ordered arguments, so exchange them
1285     // if necessary
1286     OrderPositions(from, to);
1287 
1288     Replace(from, to, wxEmptyString);
1289 }
1290 
WriteText(const wxString & text)1291 void wxTextCtrl::WriteText(const wxString& text)
1292 {
1293     // replace the selection with the new text
1294     RemoveSelection();
1295 
1296     Replace(m_curPos, m_curPos, text);
1297 }
1298 
AppendText(const wxString & text)1299 void wxTextCtrl::AppendText(const wxString& text)
1300 {
1301     SetInsertionPointEnd();
1302     WriteText(text);
1303 }
1304 
1305 // ----------------------------------------------------------------------------
1306 // current position
1307 // ----------------------------------------------------------------------------
1308 
SetInsertionPoint(wxTextPos pos)1309 void wxTextCtrl::SetInsertionPoint(wxTextPos pos)
1310 {
1311     if ( pos == -1 )
1312         pos = GetLastPosition();
1313 
1314     wxCHECK_RET( pos >= 0 && pos <= GetLastPosition(),
1315                  wxT("insertion point position out of range") );
1316 
1317     // don't do anything if it didn't change
1318     if ( pos != m_curPos )
1319     {
1320         DoSetInsertionPoint(pos);
1321     }
1322 
1323     if ( !IsSingleLine() )
1324     {
1325         // moving cursor should reset the stored abscissa (even if the cursor
1326         // position didn't actually change!)
1327         MData().m_xCaret = -1;
1328     }
1329 
1330     ClearSelection();
1331 }
1332 
InitInsertionPoint()1333 void wxTextCtrl::InitInsertionPoint()
1334 {
1335     // so far always put it in the beginning
1336     DoSetInsertionPoint(0);
1337 
1338     // this will also set the selection anchor correctly
1339     ClearSelection();
1340 }
1341 
MoveInsertionPoint(wxTextPos pos)1342 void wxTextCtrl::MoveInsertionPoint(wxTextPos pos)
1343 {
1344     wxASSERT_MSG( pos >= 0 && pos <= GetLastPosition(),
1345                  wxT("DoSetInsertionPoint() can only be called with valid pos") );
1346 
1347     m_curPos = pos;
1348     PositionToXY(m_curPos, &m_curCol, &m_curRow);
1349 }
1350 
DoSetInsertionPoint(wxTextPos pos)1351 void wxTextCtrl::DoSetInsertionPoint(wxTextPos pos)
1352 {
1353     MoveInsertionPoint(pos);
1354 
1355     ShowPosition(pos);
1356 }
1357 
SetInsertionPointEnd()1358 void wxTextCtrl::SetInsertionPointEnd()
1359 {
1360     SetInsertionPoint(GetLastPosition());
1361 }
1362 
GetInsertionPoint() const1363 wxTextPos wxTextCtrl::GetInsertionPoint() const
1364 {
1365     // if has selection, the insert point should be the lower number of selection,
1366     // else should be current cursor position
1367     long from;
1368     if ( HasSelection() )
1369         GetSelection(&from, NULL);
1370     else
1371         from = m_curPos;
1372     return from;
1373 }
1374 
GetLastPosition() const1375 wxTextPos wxTextCtrl::GetLastPosition() const
1376 {
1377     wxTextPos pos;
1378     if ( IsSingleLine() )
1379     {
1380         pos = m_value.length();
1381     }
1382     else // multiline
1383     {
1384 #ifdef WXDEBUG_TEXT
1385         pos = 0;
1386         size_t nLineCount = GetLineCount();
1387         for ( size_t nLine = 0; nLine < nLineCount; nLine++ )
1388         {
1389             // +1 is because the positions at the end of this line and of the
1390             // start of the next one are different
1391             pos += GetLines()[nLine].length() + 1;
1392         }
1393 
1394         if ( pos > 0 )
1395         {
1396             // the last position is at the end of the last line, not in the
1397             // beginning of the next line after it
1398             pos--;
1399         }
1400 
1401         // more probable reason of this would be to forget to update m_posLast
1402         wxASSERT_MSG( pos == m_posLast, wxT("bug in GetLastPosition()") );
1403 #endif // WXDEBUG_TEXT
1404 
1405         pos = m_posLast;
1406     }
1407 
1408     return pos;
1409 }
1410 
1411 // ----------------------------------------------------------------------------
1412 // selection
1413 // ----------------------------------------------------------------------------
1414 
GetSelection(wxTextPos * from,wxTextPos * to) const1415 void wxTextCtrl::GetSelection(wxTextPos* from, wxTextPos* to) const
1416 {
1417     if ( from )
1418         *from = m_selStart;
1419     if ( to )
1420         *to = m_selEnd;
1421 }
1422 
GetSelectionText() const1423 wxString wxTextCtrl::GetSelectionText() const
1424 {
1425     wxString sel;
1426 
1427     if ( HasSelection() )
1428     {
1429         if ( IsSingleLine() )
1430         {
1431             sel = m_value.Mid(m_selStart, m_selEnd - m_selStart);
1432         }
1433         else // multiline
1434         {
1435             wxTextCoord colStart, lineStart,
1436                         colEnd, lineEnd;
1437             PositionToXY(m_selStart, &colStart, &lineStart);
1438             PositionToXY(m_selEnd, &colEnd, &lineEnd);
1439 
1440             // as always, we need to check for the special case when the start
1441             // and end line are the same
1442             if ( lineEnd == lineStart )
1443             {
1444                 sel = GetLines()[lineStart].Mid(colStart, colEnd - colStart);
1445             }
1446             else // sel on multiple lines
1447             {
1448                 // take the end of the first line
1449                 sel = GetLines()[lineStart].c_str() + colStart;
1450                 sel += wxT('\n');
1451 
1452                 // all intermediate ones
1453                 for ( wxTextCoord line = lineStart + 1; line < lineEnd; line++ )
1454                 {
1455                     sel << GetLines()[line] << wxT('\n');
1456                 }
1457 
1458                 // and the start of the last one
1459                 sel += GetLines()[lineEnd].Left(colEnd);
1460             }
1461         }
1462     }
1463 
1464     return sel;
1465 }
1466 
SetSelection(wxTextPos from,wxTextPos to)1467 void wxTextCtrl::SetSelection(wxTextPos from, wxTextPos to)
1468 {
1469     // selecting till -1 is the same as selecting to the end
1470     if ( to == -1 )
1471     {
1472         // and selecting (-1, -1) range is the same as selecting everything, by
1473         // convention
1474         if ( from == -1 )
1475             from = 0;
1476         to = GetLastPosition();
1477     }
1478 
1479     if ( from == -1 || to == from )
1480     {
1481         ClearSelection();
1482     }
1483     else // valid sel range
1484     {
1485         // remember the 'to' position as the current position, used to move the
1486         // caret there later
1487         wxTextPos toOrig = to;
1488 
1489         OrderPositions(from, to);
1490 
1491         wxCHECK_RET( to <= GetLastPosition(),
1492                      wxT("invalid range in wxTextCtrl::SetSelection") );
1493 
1494         if ( from != m_selStart || to != m_selEnd )
1495         {
1496             // we need to use temp vars as RefreshTextRange() may call DoDraw()
1497             // directly and so m_selStart/End must be reset by then
1498             wxTextPos selStartOld = m_selStart,
1499                       selEndOld = m_selEnd;
1500 
1501             m_selStart = from;
1502             m_selEnd = to;
1503 
1504             wxLogTrace(wxT("text"), wxT("Selection range is %ld-%ld"),
1505                        m_selStart, m_selEnd);
1506 
1507             // refresh only the part of text which became (un)selected if
1508             // possible
1509             if ( selStartOld == m_selStart )
1510             {
1511                 RefreshTextRange(selEndOld, m_selEnd);
1512             }
1513             else if ( selEndOld == m_selEnd )
1514             {
1515                 RefreshTextRange(m_selStart, selStartOld);
1516             }
1517             else
1518             {
1519                 // OPT: could check for other cases too but it is probably not
1520                 //      worth it as the two above are the most common ones
1521                 if ( selStartOld != -1 )
1522                     RefreshTextRange(selStartOld, selEndOld);
1523                 if ( m_selStart != -1 )
1524                     RefreshTextRange(m_selStart, m_selEnd);
1525             }
1526 
1527             // we need to fully repaint the invalidated areas of the window
1528             // before scrolling it (from DoSetInsertionPoint which is typically
1529             // called after SetSelection()), otherwise they may stay unpainted
1530             m_targetWindow->Update();
1531         }
1532         //else: nothing to do
1533 
1534         // the insertion point is put at the location where the caret was moved
1535         DoSetInsertionPoint(toOrig);
1536     }
1537 }
1538 
ClearSelection()1539 void wxTextCtrl::ClearSelection()
1540 {
1541     if ( HasSelection() )
1542     {
1543         // we need to use temp vars as RefreshTextRange() may call DoDraw()
1544         // directly (see above as well)
1545         wxTextPos selStart = m_selStart,
1546                   selEnd = m_selEnd;
1547 
1548         // no selection any more
1549         m_selStart =
1550         m_selEnd = -1;
1551 
1552         // refresh the old selection
1553         RefreshTextRange(selStart, selEnd);
1554     }
1555 
1556     // the anchor should be moved even if there was no selection previously
1557     m_selAnchor = m_curPos;
1558 }
1559 
RemoveSelection()1560 void wxTextCtrl::RemoveSelection()
1561 {
1562     if ( !HasSelection() )
1563         return;
1564 
1565     Remove(m_selStart, m_selEnd);
1566 }
1567 
GetSelectedPartOfLine(wxTextCoord line,wxTextPos * start,wxTextPos * end) const1568 bool wxTextCtrl::GetSelectedPartOfLine(wxTextCoord line,
1569                                        wxTextPos *start, wxTextPos *end) const
1570 {
1571     if ( start )
1572         *start = -1;
1573     if ( end )
1574         *end = -1;
1575 
1576     if ( !HasSelection() )
1577     {
1578         // no selection at all, hence no selection in this line
1579         return false;
1580     }
1581 
1582     wxTextCoord lineStart, colStart;
1583     PositionToXY(m_selStart, &colStart, &lineStart);
1584     if ( lineStart > line )
1585     {
1586         // this line is entirely above the selection
1587         return false;
1588     }
1589 
1590     wxTextCoord lineEnd, colEnd;
1591     PositionToXY(m_selEnd, &colEnd, &lineEnd);
1592     if ( lineEnd < line )
1593     {
1594         // this line is entirely below the selection
1595         return false;
1596     }
1597 
1598     if ( line == lineStart )
1599     {
1600         if ( start )
1601             *start = colStart;
1602         if ( end )
1603             *end = lineEnd == lineStart ? colEnd : GetLineLength(line);
1604     }
1605     else if ( line == lineEnd )
1606     {
1607         if ( start )
1608             *start = lineEnd == lineStart ? colStart : 0;
1609         if ( end )
1610             *end = colEnd;
1611     }
1612     else // the line is entirely inside the selection
1613     {
1614         if ( start )
1615             *start = 0;
1616         if ( end )
1617             *end = GetLineLength(line);
1618     }
1619 
1620     return true;
1621 }
1622 
1623 // ----------------------------------------------------------------------------
1624 // flags
1625 // ----------------------------------------------------------------------------
1626 
IsModified() const1627 bool wxTextCtrl::IsModified() const
1628 {
1629     return m_isModified;
1630 }
1631 
IsEditable() const1632 bool wxTextCtrl::IsEditable() const
1633 {
1634     // disabled control can never be edited
1635     return m_isEditable && IsEnabled();
1636 }
1637 
MarkDirty()1638 void wxTextCtrl::MarkDirty()
1639 {
1640     m_isModified = true;
1641 }
1642 
DiscardEdits()1643 void wxTextCtrl::DiscardEdits()
1644 {
1645     m_isModified = false;
1646 }
1647 
SetEditable(bool editable)1648 void wxTextCtrl::SetEditable(bool editable)
1649 {
1650     if ( editable != m_isEditable )
1651     {
1652         m_isEditable = editable;
1653 
1654         // the caret (dis)appears
1655         CreateCaret();
1656 
1657         // the appearance of the control might have changed
1658         Refresh();
1659     }
1660 }
1661 
1662 // ----------------------------------------------------------------------------
1663 // col/lines <-> position correspondence
1664 // ----------------------------------------------------------------------------
1665 
1666 /*
1667     A few remarks about this stuff:
1668 
1669     o   The numbering of the text control columns/rows starts from 0.
1670     o   Start of first line is position 0, its last position is line.length()
1671     o   Start of the next line is the last position of the previous line + 1
1672  */
1673 
GetLineLength(wxTextCoord line) const1674 int wxTextCtrl::GetLineLength(wxTextCoord line) const
1675 {
1676     if ( IsSingleLine() )
1677     {
1678         wxASSERT_MSG( line == 0, wxT("invalid GetLineLength() parameter") );
1679 
1680         return m_value.length();
1681     }
1682     else // multiline
1683     {
1684         if ( line < 0 || (size_t)line >= GetLineCount() )
1685             return -1;
1686 
1687         return GetLines()[line].length();
1688     }
1689 }
1690 
GetLineText(wxTextCoord line) const1691 wxString wxTextCtrl::GetLineText(wxTextCoord line) const
1692 {
1693     if ( IsSingleLine() )
1694     {
1695         wxASSERT_MSG( line == 0, wxT("invalid GetLineLength() parameter") );
1696 
1697         return m_value;
1698     }
1699     else // multiline
1700     {
1701         //this is called during DoGetBestSize
1702         if (line == 0 && GetLineCount() == 0) return wxEmptyString ;
1703 
1704         wxCHECK_MSG( (size_t)line < GetLineCount(), wxEmptyString,
1705                      wxT("line index out of range") );
1706 
1707         return GetLines()[line];
1708     }
1709 }
1710 
GetNumberOfLines() const1711 int wxTextCtrl::GetNumberOfLines() const
1712 {
1713     // there is always 1 line, even if the text is empty
1714     return IsSingleLine() ? 1 : GetLineCount();
1715 }
1716 
XYToPosition(wxTextCoord x,wxTextCoord y) const1717 wxTextPos wxTextCtrl::XYToPosition(wxTextCoord x, wxTextCoord y) const
1718 {
1719     // note that this method should accept any values of x and y and return -1
1720     // if they are out of range
1721     if ( IsSingleLine() )
1722     {
1723         return ( x > GetLastPosition() || y > 0 ) ? wxOutOfRangeTextCoord : x;
1724     }
1725     else // multiline
1726     {
1727         if ( (size_t)y >= GetLineCount() )
1728         {
1729             // this position is below the text
1730             return GetLastPosition();
1731         }
1732 
1733         wxTextPos pos = 0;
1734         for ( size_t nLine = 0; nLine < (size_t)y; nLine++ )
1735         {
1736             // +1 is because the positions at the end of this line and of the
1737             // start of the next one are different
1738             pos += GetLines()[nLine].length() + 1;
1739         }
1740 
1741         // take into account also the position in line
1742         if ( (size_t)x > GetLines()[y].length() )
1743         {
1744             // don't return position in the next line
1745             x = GetLines()[y].length();
1746         }
1747 
1748         return pos + x;
1749     }
1750 }
1751 
PositionToXY(wxTextPos pos,wxTextCoord * x,wxTextCoord * y) const1752 bool wxTextCtrl::PositionToXY(wxTextPos pos,
1753                               wxTextCoord *x, wxTextCoord *y) const
1754 {
1755     if ( IsSingleLine() )
1756     {
1757         if ( (size_t)pos > m_value.length() )
1758             return false;
1759 
1760         if ( x )
1761             *x = pos;
1762         if ( y )
1763             *y = 0;
1764 
1765         return true;
1766     }
1767     else // multiline
1768     {
1769         wxTextPos posCur = 0;
1770         size_t nLineCount = GetLineCount();
1771         for ( size_t nLine = 0; nLine < nLineCount; nLine++ )
1772         {
1773             // +1 is because the start the start of the next line is one
1774             // position after the end of this one
1775             wxTextPos posNew = posCur + GetLines()[nLine].length() + 1;
1776             if ( posNew > pos )
1777             {
1778                 // we've found the line, now just calc the column
1779                 if ( x )
1780                     *x = pos - posCur;
1781 
1782                 if ( y )
1783                     *y = nLine;
1784 
1785 #ifdef WXDEBUG_TEXT
1786                 wxASSERT_MSG( XYToPosition(pos - posCur, nLine) == pos,
1787                               wxT("XYToPosition() or PositionToXY() broken") );
1788 #endif // WXDEBUG_TEXT
1789 
1790                 return true;
1791             }
1792             else // go further down
1793             {
1794                 posCur = posNew;
1795             }
1796         }
1797 
1798         // beyond the last line
1799         return false;
1800     }
1801 }
1802 
GetRowsPerLine(wxTextCoord line) const1803 wxTextCoord wxTextCtrl::GetRowsPerLine(wxTextCoord line) const
1804 {
1805     // a normal line has one row
1806     wxTextCoord numRows = 1;
1807 
1808     if ( WrapLines() )
1809     {
1810         // add the number of additional rows
1811         numRows += WData().m_linesData[line].GetExtraRowCount();
1812     }
1813 
1814     return numRows;
1815 }
1816 
GetRowCount() const1817 wxTextCoord wxTextCtrl::GetRowCount() const
1818 {
1819     wxTextCoord count = GetLineCount();
1820     if (count == 0)
1821         return 0;
1822     if ( WrapLines() )
1823     {
1824         count = GetFirstRowOfLine(count - 1) +
1825                     WData().m_linesData[count - 1].GetRowCount();
1826     }
1827 
1828     return count;
1829 }
1830 
GetRowAfterLine(wxTextCoord line) const1831 wxTextCoord wxTextCtrl::GetRowAfterLine(wxTextCoord line) const
1832 {
1833     if ( !WrapLines() )
1834         return line + 1;
1835 
1836     if ( !WData().IsValidLine(line) )
1837     {
1838         LayoutLines(line);
1839     }
1840 
1841     return WData().m_linesData[line].GetNextRow();
1842 }
1843 
GetFirstRowOfLine(wxTextCoord line) const1844 wxTextCoord wxTextCtrl::GetFirstRowOfLine(wxTextCoord line) const
1845 {
1846     if ( !WrapLines() )
1847         return line;
1848 
1849     if ( !WData().IsValidLine(line) )
1850     {
1851         LayoutLines(line);
1852     }
1853 
1854     return WData().m_linesData[line].GetFirstRow();
1855 }
1856 
PositionToLogicalXY(wxTextPos pos,wxCoord * xOut,wxCoord * yOut) const1857 bool wxTextCtrl::PositionToLogicalXY(wxTextPos pos,
1858                                      wxCoord *xOut,
1859                                      wxCoord *yOut) const
1860 {
1861     wxTextCoord col, line;
1862 
1863     // optimization for special (but common) case when we already have the col
1864     // and line
1865     if ( pos == m_curPos )
1866     {
1867         col = m_curCol;
1868         line = m_curRow;
1869     }
1870     else // must really calculate col/line from pos
1871     {
1872         if ( !PositionToXY(pos, &col, &line) )
1873             return false;
1874     }
1875 
1876     int hLine = GetLineHeight();
1877     wxCoord x, y;
1878     wxString textLine = GetLineText(line);
1879     if ( IsSingleLine() || !WrapLines() )
1880     {
1881         x = GetTextWidth(textLine.Left(col));
1882         y = line*hLine;
1883     }
1884     else // difficult case: multline control with line wrap
1885     {
1886         y = GetFirstRowOfLine(line);
1887 
1888         wxTextCoord colRowStart;
1889         y += GetRowInLine(line, col, &colRowStart);
1890 
1891         y *= hLine;
1892 
1893         // x is the width of the text before this position in this row
1894         x = GetTextWidth(textLine.Mid(colRowStart, col - colRowStart));
1895     }
1896 
1897     if ( xOut )
1898         *xOut = x;
1899     if ( yOut )
1900         *yOut = y;
1901 
1902     return true;
1903 }
1904 
PositionToDeviceXY(wxTextPos pos,wxCoord * xOut,wxCoord * yOut) const1905 bool wxTextCtrl::PositionToDeviceXY(wxTextPos pos,
1906                                     wxCoord *xOut,
1907                                     wxCoord *yOut) const
1908 {
1909     wxCoord x, y;
1910     if ( !PositionToLogicalXY(pos, &x, &y) )
1911         return false;
1912 
1913     // finally translate the logical text rect coords into physical client
1914     // coords
1915     CalcScrolledPosition(m_rectText.x + x, m_rectText.y + y, xOut, yOut);
1916 
1917     return true;
1918 }
1919 
GetCaretPosition() const1920 wxPoint wxTextCtrl::GetCaretPosition() const
1921 {
1922     wxCoord xCaret, yCaret;
1923     if ( !PositionToDeviceXY(m_curPos, &xCaret, &yCaret) )
1924     {
1925         wxFAIL_MSG( wxT("Caret can't be beyond the text!") );
1926     }
1927 
1928     return wxPoint(xCaret, yCaret);
1929 }
1930 
1931 // pos may be -1 to show the current position
ShowPosition(wxTextPos pos)1932 void wxTextCtrl::ShowPosition(wxTextPos pos)
1933 {
1934     bool showCaret = GetCaret() && GetCaret()->IsVisible();
1935     if (showCaret)
1936         HideCaret();
1937 
1938     if ( IsSingleLine() )
1939     {
1940         ShowHorzPosition(GetTextWidth(m_value.Left(pos)));
1941     }
1942     else if ( MData().m_scrollRangeX || MData().m_scrollRangeY ) // multiline with scrollbars
1943     {
1944         int xStart, yStart;
1945         GetViewStart(&xStart, &yStart);
1946 
1947         if ( pos == -1 )
1948             pos = m_curPos;
1949 
1950         wxCoord x, y;
1951         PositionToLogicalXY(pos, &x, &y);
1952 
1953         wxRect rectText = GetRealTextArea();
1954 
1955         // scroll the position vertically into view: if it is currently above
1956         // it, make it the first one, otherwise the last one
1957         if ( MData().m_scrollRangeY )
1958         {
1959             y /= GetLineHeight();
1960 
1961             if ( y < yStart )
1962             {
1963                 Scroll(0, y);
1964             }
1965             else // we are currently in or below the view area
1966             {
1967                 // find the last row currently shown
1968                 wxTextCoord yEnd;
1969 
1970                 if ( WrapLines() )
1971                 {
1972                     // to find the last row we need to use the generic HitTest
1973                     wxTextCoord col;
1974 
1975                     // OPT this is a bit silly: we undo this in HitTest(), so
1976                     //     it would be better to factor out the common
1977                     //     functionality into a separate function (OTOH it
1978                     //     won't probably save us that much)
1979                     wxPoint pt(0, rectText.height - 1);
1980                     pt += GetClientAreaOrigin();
1981                     pt += m_rectText.GetPosition();
1982                     HitTest(pt, &col, &yEnd);
1983 
1984                     // find the row inside the line
1985                     yEnd = GetFirstRowOfLine(yEnd) + GetRowInLine(yEnd, col);
1986                 }
1987                 else
1988                 {
1989                     // finding the last line is easy if each line has exactly
1990                     // one row
1991                     yEnd = yStart + rectText.height / GetLineHeight();
1992                 }
1993 
1994                 if ( yEnd < y )
1995                 {
1996                     // scroll down: the current item should appear at the
1997                     // bottom of the view
1998                     Scroll(0, y - (yEnd - yStart));
1999                 }
2000             }
2001         }
2002 
2003         // scroll the position horizontally into view
2004         //
2005         // we follow what I believe to be Windows behaviour here, that is if
2006         // the position is already entirely in the view we do nothing, but if
2007         // we do have to scroll the window to bring it into view, we scroll it
2008         // not just enough to show the position but slightly more so that this
2009         // position is at 1/3 of the window width from the closest border to it
2010         // (I'm not sure that Windows does exactly this but it looks like this)
2011         if ( MData().m_scrollRangeX )
2012         {
2013             // unlike for the rows, xStart doesn't correspond to the starting
2014             // column as they all have different widths, so we need to
2015             // translate everything to pixels
2016 
2017             // we want the text between x and x2 be entirely inside the view
2018             // (i.e. the current character)
2019 
2020             // make xStart the first visible pixel (and not position)
2021             int wChar = GetAverageWidth();
2022             xStart *= wChar;
2023 
2024             if ( x < xStart )
2025             {
2026                 // we want the position of this column be 1/3 to the right of
2027                 // the left edge
2028                 x -= rectText.width / 3;
2029                 if ( x < 0 )
2030                     x = 0;
2031                 Scroll(x / wChar, y);
2032             }
2033             else // maybe we're beyond the right border of the view?
2034             {
2035                 wxTextCoord col, row;
2036                 if ( PositionToXY(pos, &col, &row) )
2037                 {
2038                     wxString lineText = GetLineText(row);
2039                     wxCoord x2 = x + GetTextWidth(lineText[(size_t)col]);
2040                     if ( x2 > xStart + rectText.width )
2041                     {
2042                         // we want the position of this column be 1/3 to the
2043                         // left of the right edge, i.e. 2/3 right of the left
2044                         // one
2045                         x2 -= (2*rectText.width)/3;
2046                         if ( x2 < 0 )
2047                             x2 = 0;
2048                         Scroll(x2 / wChar, row);
2049                     }
2050                 }
2051             }
2052         }
2053     }
2054     //else: multiline but no scrollbars, hence nothing to do
2055 
2056     if (showCaret)
2057         ShowCaret();
2058 }
2059 
2060 // ----------------------------------------------------------------------------
2061 // word stuff
2062 // ----------------------------------------------------------------------------
2063 
2064 /*
2065     TODO: we could have (easy to do) vi-like options for word movement, i.e.
2066           distinguish between inlusive/exclusive words and between words and
2067           WORDS (in vim sense) and also, finally, make the set of characters
2068           which make up a word configurable - currently we use the exclusive
2069           WORDS only (coincidentally, this is what Windows edit control does)
2070 
2071           For future references, here is what vim help says:
2072 
2073           A word consists of a sequence of letters, digits and underscores, or
2074           a sequence of other non-blank characters, separated with white space
2075           (spaces, tabs, <EOL>).  This can be changed with the 'iskeyword'
2076           option.
2077 
2078           A WORD consists of a sequence of non-blank characters, separated with
2079           white space.  An empty line is also considered to be a word and a
2080           WORD.
2081  */
2082 
IsWordChar(wxChar ch)2083 static inline bool IsWordChar(wxChar ch)
2084 {
2085     return !wxIsspace(ch);
2086 }
2087 
GetWordStart() const2088 wxTextPos wxTextCtrl::GetWordStart() const
2089 {
2090     if ( m_curPos == -1 || m_curPos == 0 )
2091         return 0;
2092 
2093     if ( m_curCol == 0 )
2094     {
2095         // go to the end of the previous line
2096         return m_curPos - 1;
2097     }
2098 
2099     // it shouldn't be possible to learn where the word starts in the password
2100     // text entry zone
2101     if ( IsPassword() )
2102         return 0;
2103 
2104     // start at the previous position
2105     const wxChar *p0 = GetLineText(m_curRow).c_str();
2106     const wxChar *p = p0 + m_curCol - 1;
2107 
2108     // find the end of the previous word
2109     while ( (p > p0) && !IsWordChar(*p) )
2110         p--;
2111 
2112     // now find the beginning of this word
2113     while ( (p > p0) && IsWordChar(*p) )
2114         p--;
2115 
2116     // we might have gone too far
2117     if ( !IsWordChar(*p) )
2118         p++;
2119 
2120     return (m_curPos - m_curCol) + p - p0;
2121 }
2122 
GetWordEnd() const2123 wxTextPos wxTextCtrl::GetWordEnd() const
2124 {
2125     if ( m_curPos == -1 )
2126         return 0;
2127 
2128     wxString line = GetLineText(m_curRow);
2129     if ( (size_t)m_curCol == line.length() )
2130     {
2131         // if we're on the last position in the line, go to the next one - if
2132         // it exists
2133         wxTextPos pos = m_curPos;
2134         if ( pos < GetLastPosition() )
2135             pos++;
2136 
2137         return pos;
2138     }
2139 
2140     // it shouldn't be possible to learn where the word ends in the password
2141     // text entry zone
2142     if ( IsPassword() )
2143         return GetLastPosition();
2144 
2145     // start at the current position
2146     const wxChar *p0 = line.c_str();
2147     const wxChar *p = p0 + m_curCol;
2148 
2149     // find the start of the next word
2150     while ( *p && !IsWordChar(*p) )
2151         p++;
2152 
2153     // now find the end of it
2154     while ( *p && IsWordChar(*p) )
2155         p++;
2156 
2157     // and find the start of the next word
2158     while ( *p && !IsWordChar(*p) )
2159         p++;
2160 
2161     return (m_curPos - m_curCol) + p - p0;
2162 }
2163 
2164 // ----------------------------------------------------------------------------
2165 // clipboard stuff
2166 // ----------------------------------------------------------------------------
2167 
Copy()2168 void wxTextCtrl::Copy()
2169 {
2170 #if wxUSE_CLIPBOARD
2171     if ( HasSelection() )
2172     {
2173         wxClipboardLocker clipLock;
2174 
2175         // wxTextFile::Translate() is needed to transform all '\n' into "\r\n"
2176         wxString text = wxTextFile::Translate(GetTextToShow(GetSelectionText()));
2177         wxTextDataObject *data = new wxTextDataObject(text);
2178         wxTheClipboard->SetData(data);
2179     }
2180 #endif // wxUSE_CLIPBOARD
2181 }
2182 
Cut()2183 void wxTextCtrl::Cut()
2184 {
2185     (void)DoCut();
2186 }
2187 
DoCut()2188 bool wxTextCtrl::DoCut()
2189 {
2190     if ( !HasSelection() )
2191         return false;
2192 
2193     Copy();
2194 
2195     RemoveSelection();
2196 
2197     return true;
2198 }
2199 
Paste()2200 void wxTextCtrl::Paste()
2201 {
2202     (void)DoPaste();
2203 }
2204 
DoPaste()2205 bool wxTextCtrl::DoPaste()
2206 {
2207 #if wxUSE_CLIPBOARD
2208     wxClipboardLocker clipLock;
2209 
2210     wxTextDataObject data;
2211     if ( wxTheClipboard->IsSupported(data.GetFormat())
2212             && wxTheClipboard->GetData(data) )
2213     {
2214         // reverse transformation: '\r\n\" -> '\n'
2215         wxString text = wxTextFile::Translate(data.GetText(),
2216                                               wxTextFileType_Unix);
2217         if ( !text.empty() )
2218         {
2219             WriteText(text);
2220 
2221             return true;
2222         }
2223     }
2224 #endif // wxUSE_CLIPBOARD
2225 
2226     return false;
2227 }
2228 
2229 // ----------------------------------------------------------------------------
2230 // Undo and redo
2231 // ----------------------------------------------------------------------------
2232 
2233 wxTextCtrlInsertCommand *
IsInsertCommand(wxCommand * command)2234 wxTextCtrlCommandProcessor::IsInsertCommand(wxCommand *command)
2235 {
2236     return (wxTextCtrlInsertCommand *)
2237             (command && (command->GetName() == wxTEXT_COMMAND_INSERT)
2238                 ? command : NULL);
2239 }
2240 
Store(wxCommand * command)2241 void wxTextCtrlCommandProcessor::Store(wxCommand *command)
2242 {
2243     wxTextCtrlInsertCommand *cmdIns = IsInsertCommand(command);
2244     if ( cmdIns )
2245     {
2246         if ( IsCompressing() )
2247         {
2248             wxTextCtrlInsertCommand *
2249                 cmdInsLast = IsInsertCommand(GetCurrentCommand());
2250 
2251             // it is possible that we don't have any last command at all if,
2252             // for example, it was undone since the last Store(), so deal with
2253             // this case too
2254             if ( cmdInsLast )
2255             {
2256                 cmdInsLast->Append(cmdIns);
2257 
2258                 delete cmdIns;
2259 
2260                 // don't need to call the base class version
2261                 return;
2262             }
2263         }
2264 
2265         // append the following insert commands to this one
2266         m_compressInserts = true;
2267 
2268         // let the base class version will do the job normally
2269     }
2270     else // not an insert command
2271     {
2272         // stop compressing insert commands - this won't work with the last
2273         // command not being an insert one anyhow
2274         StopCompressing();
2275 
2276         // let the base class version will do the job normally
2277     }
2278 
2279     wxCommandProcessor::Store(command);
2280 }
2281 
Append(wxTextCtrlInsertCommand * other)2282 void wxTextCtrlInsertCommand::Append(wxTextCtrlInsertCommand *other)
2283 {
2284     m_text += other->m_text;
2285 }
2286 
CanUndo() const2287 bool wxTextCtrlInsertCommand::CanUndo() const
2288 {
2289     return m_from != -1;
2290 }
2291 
Do(wxTextCtrl * text)2292 bool wxTextCtrlInsertCommand::Do(wxTextCtrl *text)
2293 {
2294     // the text is going to be inserted at the current position, remember where
2295     // exactly it is
2296     m_from = text->GetInsertionPoint();
2297 
2298     // and now do insert it
2299     text->WriteText(m_text);
2300 
2301     return true;
2302 }
2303 
Undo(wxTextCtrl * text)2304 bool wxTextCtrlInsertCommand::Undo(wxTextCtrl *text)
2305 {
2306     wxCHECK_MSG( CanUndo(), false, wxT("impossible to undo insert cmd") );
2307 
2308     // remove the text from where we inserted it
2309     text->Remove(m_from, m_from + m_text.length());
2310 
2311     return true;
2312 }
2313 
CanUndo() const2314 bool wxTextCtrlRemoveCommand::CanUndo() const
2315 {
2316     // if we were executed, we should have the text we removed
2317     return !m_textDeleted.empty();
2318 }
2319 
Do(wxTextCtrl * text)2320 bool wxTextCtrlRemoveCommand::Do(wxTextCtrl *text)
2321 {
2322     text->SetSelection(m_from, m_to);
2323     m_textDeleted = text->GetSelectionText();
2324     text->RemoveSelection();
2325 
2326     return true;
2327 }
2328 
Undo(wxTextCtrl * text)2329 bool wxTextCtrlRemoveCommand::Undo(wxTextCtrl *text)
2330 {
2331     // it is possible that the text was deleted and that we can't restore text
2332     // at the same position we removed it any more
2333     wxTextPos posLast = text->GetLastPosition();
2334     text->SetInsertionPoint(m_from > posLast ? posLast : m_from);
2335     text->WriteText(m_textDeleted);
2336 
2337     return true;
2338 }
2339 
Undo()2340 void wxTextCtrl::Undo()
2341 {
2342     // the caller must check it
2343     wxASSERT_MSG( CanUndo(), wxT("can't call Undo() if !CanUndo()") );
2344 
2345     m_cmdProcessor->Undo();
2346 }
2347 
Redo()2348 void wxTextCtrl::Redo()
2349 {
2350     // the caller must check it
2351     wxASSERT_MSG( CanRedo(), wxT("can't call Undo() if !CanUndo()") );
2352 
2353     m_cmdProcessor->Redo();
2354 }
2355 
CanUndo() const2356 bool wxTextCtrl::CanUndo() const
2357 {
2358     return IsEditable() && m_cmdProcessor->CanUndo();
2359 }
2360 
CanRedo() const2361 bool wxTextCtrl::CanRedo() const
2362 {
2363     return IsEditable() && m_cmdProcessor->CanRedo();
2364 }
2365 
2366 // ----------------------------------------------------------------------------
2367 // geometry
2368 // ----------------------------------------------------------------------------
2369 
DoGetBestClientSize() const2370 wxSize wxTextCtrl::DoGetBestClientSize() const
2371 {
2372     // when we're called for the very first time from Create() we must
2373     // calculate the font metrics here because we can't do it before calling
2374     // Create() (there is no window yet and wxGTK crashes) but we need them
2375     // here
2376     if ( m_heightLine == -1 )
2377     {
2378         wxConstCast(this, wxTextCtrl)->RecalcFontMetrics();
2379     }
2380 
2381     wxCoord w, h;
2382     GetTextExtent(GetTextToShow(GetLineText(0)), &w, &h);
2383 
2384     int wChar = GetAverageWidth(),
2385         hChar = GetLineHeight();
2386 
2387     int widthMin = wxMax(10*wChar, 100);
2388     if ( w < widthMin )
2389         w = widthMin;
2390     if ( h < hChar )
2391         h = hChar;
2392 
2393     if ( !IsSingleLine() )
2394     {
2395         // let the control have a reasonable number of lines
2396         int lines = GetNumberOfLines();
2397         if ( lines < 5 )
2398             lines = 5;
2399         else if ( lines > 10 )
2400             lines = 10;
2401         h *= lines;
2402     }
2403 
2404     wxRect rectText;
2405     rectText.width = w;
2406     rectText.height = h;
2407     wxRect rectTotal = GetRenderer()->GetTextTotalArea(this, rectText);
2408     return wxSize(rectTotal.width, rectTotal.height);
2409 }
2410 
UpdateTextRect()2411 void wxTextCtrl::UpdateTextRect()
2412 {
2413     wxRect rectTotal(GetClientSize());
2414     wxCoord *extraSpace = WrapLines() ? &WData().m_widthMark : NULL;
2415     m_rectText = GetRenderer()->GetTextClientArea(this, rectTotal, extraSpace);
2416 
2417     // code elsewhere is confused by negative rect size
2418     if ( m_rectText.width <= 0 )
2419         m_rectText.width = 1;
2420     if ( m_rectText.height <= 0 )
2421         m_rectText.height = 1;
2422 
2423     if ( !IsSingleLine() )
2424     {
2425         // invalidate it so that GetRealTextArea() will recalc it
2426         MData().m_rectTextReal.width = 0;
2427 
2428         // only scroll this rect when the window is scrolled: note that we have
2429         // to scroll not only the text but the line wrap marks too if we show
2430         // them
2431         wxRect rectText = GetRealTextArea();
2432         if ( extraSpace && *extraSpace )
2433         {
2434             rectText.width += *extraSpace;
2435         }
2436         SetTargetRect(rectText);
2437 
2438         // relayout all lines
2439         if ( WrapLines() )
2440         {
2441             WData().m_rowFirstInvalid = 0;
2442 
2443             // increase timestamp: this means that the lines which had been
2444             // laid out before will be relaid out the next time LayoutLines()
2445             // is called because their timestamp will be smaller than the
2446             // current one
2447             WData().m_timestamp++;
2448         }
2449     }
2450 
2451     UpdateLastVisible();
2452 }
2453 
UpdateLastVisible()2454 void wxTextCtrl::UpdateLastVisible()
2455 {
2456     // this method is only used for horizontal "scrollbarless" scrolling which
2457     // is used only with single line controls
2458     if ( !IsSingleLine() )
2459         return;
2460 
2461     // use (efficient) HitTestLine to find the last visible character
2462     wxString text = m_value.Mid((size_t)SData().m_colStart /* to the end */);
2463     wxTextCoord col;
2464     switch ( HitTestLine(text, m_rectText.width, &col) )
2465     {
2466         case wxTE_HT_BEYOND:
2467             // everything is visible
2468             SData().m_ofsHorz = 0;
2469 
2470             SData().m_colStart = 0;
2471             SData().m_colLastVisible = text.length();
2472 
2473             // calculate it below
2474             SData().m_posLastVisible = -1;
2475             break;
2476 
2477            /*
2478         case wxTE_HT_BEFORE:
2479         case wxTE_HT_BELOW:
2480             */
2481         default:
2482             wxFAIL_MSG(wxT("unexpected HitTestLine() return value"));
2483             // fall through
2484 
2485         case wxTE_HT_ON_TEXT:
2486             if ( col > 0 )
2487             {
2488                 // the last entirely seen character is the previous one because
2489                 // this one is only partly visible - unless the width of the
2490                 // string is exactly the max width
2491                 SData().m_posLastVisible = GetTextWidth(text.Truncate(col + 1));
2492                 if ( SData().m_posLastVisible > m_rectText.width )
2493                 {
2494                     // this character is not entirely visible, take the
2495                     // previous one
2496                     col--;
2497 
2498                     // recalc it
2499                     SData().m_posLastVisible = -1;
2500                 }
2501                 //else: we can just see it
2502 
2503                 SData().m_colLastVisible = col;
2504             }
2505             break;
2506     }
2507 
2508     // calculate the width of the text really shown
2509     if ( SData().m_posLastVisible == -1 )
2510     {
2511         SData().m_posLastVisible = GetTextWidth(text.Truncate(SData().m_colLastVisible + 1));
2512     }
2513 
2514     // current value is relative the start of the string text which starts at
2515     // SData().m_colStart, we need an absolute offset into string
2516     SData().m_colLastVisible += SData().m_colStart;
2517 
2518     wxLogTrace(wxT("text"), wxT("Last visible column/position is %d/%ld"),
2519                (int) SData().m_colLastVisible, (long) SData().m_posLastVisible);
2520 }
2521 
OnSize(wxSizeEvent & event)2522 void wxTextCtrl::OnSize(wxSizeEvent& event)
2523 {
2524     UpdateTextRect();
2525 
2526     if ( !IsSingleLine() )
2527     {
2528 #if 0
2529         // update them immediately because if we are called for the first time,
2530         // we need to create them in order for the base class version to
2531         // position the scrollbars correctly - if we don't do it now, it won't
2532         // happen at all if we don't get more size events
2533         UpdateScrollbars();
2534 #endif // 0
2535 
2536         MData().m_updateScrollbarX =
2537         MData().m_updateScrollbarY = true;
2538     }
2539 
2540     event.Skip();
2541 }
2542 
SetMaxLength(unsigned long len)2543 void wxTextCtrl::SetMaxLength(unsigned long len)
2544 {
2545     // if the existing value in the text control is too long,
2546     // then it is clipped to the newly imposed limit.
2547     if ( m_value.Length() > len )
2548     {
2549         // block the wxEVT_TEXT event temporaryly
2550         // otherwise Remove will generate a wxEVT_TEXT event
2551         // that not what we want
2552         ForwardEnableTextChangedEvents(false);
2553         Remove(len, -1);
2554         ForwardEnableTextChangedEvents(true);
2555     }
2556 
2557     m_maxLength = len;
2558 }
2559 
GetTotalWidth() const2560 wxCoord wxTextCtrl::GetTotalWidth() const
2561 {
2562     wxCoord w;
2563     CalcUnscrolledPosition(m_rectText.width, 0, &w, NULL);
2564     return w;
2565 }
2566 
GetTextWidth(const wxString & text) const2567 wxCoord wxTextCtrl::GetTextWidth(const wxString& text) const
2568 {
2569     wxCoord w;
2570     GetTextExtent(GetTextToShow(text), &w, NULL);
2571     return w;
2572 }
2573 
GetRealTextArea() const2574 wxRect wxTextCtrl::GetRealTextArea() const
2575 {
2576     // for single line text control it's just the same as text rect
2577     if ( IsSingleLine() )
2578         return m_rectText;
2579 
2580     // the real text area always holds an entire number of lines, so the only
2581     // difference with the text area is a narrow strip along the bottom border
2582     wxRect rectText = MData().m_rectTextReal;
2583     if ( !rectText.width )
2584     {
2585         // recalculate it
2586         rectText = m_rectText;
2587 
2588         // when we're called for the very first time, the line height might not
2589         // had been calculated yet, so do get it now
2590         wxTextCtrl *self = wxConstCast(this, wxTextCtrl);
2591         self->RecalcFontMetrics();
2592 
2593         int hLine = GetLineHeight();
2594         rectText.height = (m_rectText.height / hLine) * hLine;
2595 
2596         // cache the result
2597         self->MData().m_rectTextReal = rectText;
2598     }
2599 
2600     return rectText;
2601 }
2602 
GetRowInLine(wxTextCoord line,wxTextCoord col,wxTextCoord * colRowStart) const2603 wxTextCoord wxTextCtrl::GetRowInLine(wxTextCoord line,
2604                                      wxTextCoord col,
2605                                      wxTextCoord *colRowStart) const
2606 {
2607     wxASSERT_MSG( WrapLines(), wxT("shouldn't be called") );
2608 
2609     const wxWrappedLineData& lineData = WData().m_linesData[line];
2610 
2611     if ( !WData().IsValidLine(line) )
2612         LayoutLines(line);
2613 
2614     // row is here counted a bit specially: 0 is the 2nd row of the line (1st
2615     // extra row)
2616     size_t row = 0,
2617            rowMax = lineData.GetExtraRowCount();
2618     if ( rowMax )
2619     {
2620         row = 0;
2621         while ( (row < rowMax) && (col >= lineData.GetExtraRowStart(row)) )
2622             row++;
2623 
2624         // it's ok here that row is 1 greater than needed: like this, it is
2625         // counted as a normal (and not extra) row
2626     }
2627     //else: only one row anyhow
2628 
2629     if ( colRowStart )
2630     {
2631         // +1 because we need a real row number, not the extra row one
2632         *colRowStart = lineData.GetRowStart(row);
2633 
2634         // this can't happen, of course
2635         wxASSERT_MSG( *colRowStart <= col, wxT("GetRowInLine() is broken") );
2636     }
2637 
2638     return row;
2639 }
2640 
LayoutLine(wxTextCoord line,wxWrappedLineData & lineData) const2641 void wxTextCtrl::LayoutLine(wxTextCoord line, wxWrappedLineData& lineData) const
2642 {
2643     // FIXME: this uses old GetPartOfWrappedLine() which is not used anywhere
2644     //        else now and has rather awkward interface for our needs here
2645 
2646     lineData.m_rowsStart.Empty();
2647     lineData.m_rowsWidth.Empty();
2648 
2649     const wxString& text = GetLineText(line);
2650     wxCoord widthRow;
2651     size_t colRowStart = 0;
2652     do
2653     {
2654         size_t lenRow = GetPartOfWrappedLine
2655                         (
2656                             text.c_str() + colRowStart,
2657                             &widthRow
2658                         );
2659 
2660         // remember the start of this row (not for the first one as
2661         // it's always 0) and its width
2662         if ( colRowStart )
2663             lineData.m_rowsStart.Add(colRowStart);
2664         lineData.m_rowsWidth.Add(widthRow);
2665 
2666         colRowStart += lenRow;
2667     }
2668     while ( colRowStart < text.length() );
2669 
2670     // put the current timestamp on it
2671     lineData.m_timestamp = WData().m_timestamp;
2672 }
2673 
LayoutLines(wxTextCoord lineLast) const2674 void wxTextCtrl::LayoutLines(wxTextCoord lineLast) const
2675 {
2676     wxASSERT_MSG( WrapLines(), wxT("should only be used for line wrapping") );
2677 
2678     // if we were called, some line was dirty and if it was dirty we must have
2679     // had m_rowFirstInvalid set to something too
2680     wxTextCoord lineFirst = WData().m_rowFirstInvalid;
2681     wxASSERT_MSG( lineFirst != -1, wxT("nothing to layout?") );
2682 
2683     wxTextCoord rowFirst, rowCur;
2684     if ( lineFirst )
2685     {
2686         // start after the last known valid line
2687         const wxWrappedLineData& lineData = WData().m_linesData[lineFirst - 1];
2688         rowFirst = lineData.GetFirstRow() + lineData.GetRowCount();
2689     }
2690     else // no valid lines, start at row 0
2691     {
2692         rowFirst = 0;
2693     }
2694 
2695     rowCur = rowFirst;
2696     for ( wxTextCoord line = lineFirst; line <= lineLast; line++ )
2697     {
2698         // set the starting row for this line
2699         wxWrappedLineData& lineData = WData().m_linesData[line];
2700         lineData.m_rowFirst = rowCur;
2701 
2702         // had the line been already broken into rows?
2703         //
2704         // if so, compare its timestamp with the current one: if nothing has
2705         // been changed, don't relayout it
2706         if ( !lineData.IsValid() ||
2707                 (lineData.m_timestamp < WData().m_timestamp) )
2708         {
2709             // now do break it in rows
2710             LayoutLine(line, lineData);
2711         }
2712 
2713         rowCur += lineData.GetRowCount();
2714     }
2715 
2716     // we are now valid at least up to this line, but if it is the last one we
2717     // just don't have any more invalid rows at all
2718     if ( (size_t)lineLast == WData().m_linesData.GetCount() -1 )
2719     {
2720         lineLast = -1;
2721     }
2722 
2723     wxTextCtrl *self = wxConstCast(this, wxTextCtrl);
2724     self->WData().m_rowFirstInvalid = lineLast;
2725 
2726     // also refresh the line end indicators (FIXME shouldn't do it always!)
2727     self->RefreshLineWrapMarks(rowFirst, rowCur);
2728 }
2729 
GetPartOfWrappedLine(const wxChar * text,wxCoord * widthReal) const2730 size_t wxTextCtrl::GetPartOfWrappedLine(const wxChar* text,
2731                                         wxCoord *widthReal) const
2732 {
2733     // this function is slow, it shouldn't be called unless really needed
2734     wxASSERT_MSG( WrapLines(), wxT("shouldn't be called") );
2735 
2736     wxString s(text);
2737     wxTextCoord col;
2738     wxCoord wReal = wxDefaultCoord;
2739     switch ( HitTestLine(s, m_rectText.width, &col) )
2740     {
2741             /*
2742         case wxTE_HT_BEFORE:
2743         case wxTE_HT_BELOW:
2744             */
2745         default:
2746             wxFAIL_MSG(wxT("unexpected HitTestLine() return value"));
2747             // fall through
2748 
2749         case wxTE_HT_ON_TEXT:
2750             if ( col > 0 )
2751             {
2752                 // the last entirely seen character is the previous one because
2753                 // this one is only partly visible - unless the width of the
2754                 // string is exactly the max width
2755                 wReal = GetTextWidth(s.Truncate(col + 1));
2756                 if ( wReal > m_rectText.width )
2757                 {
2758                     // this character is not entirely visible, take the
2759                     // previous one
2760                     col--;
2761 
2762                     // recalc the width
2763                     wReal = wxDefaultCoord;
2764                 }
2765                 //else: we can just see it
2766 
2767                 // wrap at any character or only at words boundaries?
2768                 if ( !(GetWindowStyle() & wxTE_CHARWRAP) )
2769                 {
2770                     // find the (last) not word char before this word
2771                     wxTextCoord colWordStart;
2772                     for ( colWordStart = col;
2773                           colWordStart && IsWordChar(s[(size_t)colWordStart]);
2774                           colWordStart-- )
2775                         ;
2776 
2777                     if ( colWordStart > 0 )
2778                     {
2779                         if ( colWordStart != col )
2780                         {
2781                             // will have to recalc the real width
2782                             wReal = wxDefaultCoord;
2783 
2784                             col = colWordStart;
2785                         }
2786                     }
2787                     //else: only a single word, have to wrap it here
2788                 }
2789             }
2790             break;
2791 
2792         case wxTE_HT_BEYOND:
2793             break;
2794     }
2795 
2796     // we return the number of characters, not the index of the last one
2797     if ( (size_t)col < s.length() )
2798     {
2799         // but don't return more than this (empty) string has
2800         col++;
2801     }
2802 
2803     if ( widthReal )
2804     {
2805         if ( wReal == wxDefaultCoord )
2806         {
2807             // calc it if not done yet
2808             wReal = GetTextWidth(s.Truncate(col));
2809         }
2810 
2811         *widthReal = wReal;
2812     }
2813 
2814     // VZ: old, horribly inefficient code which can still be used for checking
2815     //     the result (in line, not word, wrap mode only) - to be removed later
2816 #if 0
2817     wxTextCtrl *self = wxConstCast(this, wxTextCtrl);
2818     wxClientDC dc(self);
2819     dc.SetFont(GetFont());
2820     self->DoPrepareDC(dc);
2821 
2822     wxCoord widthMax = m_rectText.width;
2823 
2824     // the text which we can keep in this ROW
2825     wxString str;
2826     wxCoord w, wOld;
2827     for ( wOld = w = 0; *text && (w <= widthMax); )
2828     {
2829         wOld = w;
2830         str += *text++;
2831         dc.GetTextExtent(str, &w, NULL);
2832     }
2833 
2834     if ( w > widthMax )
2835     {
2836         // if we wrapped, the last letter was one too much
2837         if ( str.length() > 1 )
2838         {
2839             // remove it
2840             str.erase(str.length() - 1, 1);
2841         }
2842         else // but always keep at least one letter in each row
2843         {
2844             // the real width then is the last value of w and not teh one
2845             // before last
2846             wOld = w;
2847         }
2848     }
2849     else // we didn't wrap
2850     {
2851         wOld = w;
2852     }
2853 
2854     wxASSERT( col == str.length() );
2855 
2856     if ( widthReal )
2857     {
2858         wxASSERT( *widthReal == wOld );
2859 
2860         *widthReal = wOld;
2861     }
2862 
2863     //return str.length();
2864 #endif
2865 
2866     return col;
2867 }
2868 
2869 // OPT: this function is called a lot - would be nice to optimize it but I
2870 //      don't really know how yet
HitTestLine(const wxString & line,wxCoord x,wxTextCoord * colOut) const2871 wxTextCtrlHitTestResult wxTextCtrl::HitTestLine(const wxString& line,
2872                                                 wxCoord x,
2873                                                 wxTextCoord *colOut) const
2874 {
2875     wxTextCtrlHitTestResult res = wxTE_HT_ON_TEXT;
2876 
2877     int col;
2878     wxTextCtrl *self = wxConstCast(this, wxTextCtrl);
2879     wxClientDC dc(self);
2880     dc.SetFont(GetFont());
2881     self->DoPrepareDC(dc);
2882 
2883     wxCoord width;
2884     dc.GetTextExtent(line, &width, NULL);
2885     if ( x >= width )
2886     {
2887         // clicking beyond the end of line is equivalent to clicking at
2888         // the end of it, so return the last line column
2889         col = line.length();
2890         if ( col )
2891         {
2892             // unless the line is empty and so doesn't have any column at all -
2893             // in this case return 0, what else can we do?
2894             col--;
2895         }
2896 
2897         res = wxTE_HT_BEYOND;
2898     }
2899     else if ( x < 0 )
2900     {
2901         col = 0;
2902 
2903         res = wxTE_HT_BEFORE;
2904     }
2905     else // we're inside the line
2906     {
2907         // now calculate the column: first, approximate it with fixed-width
2908         // value and then calculate the correct value iteratively: note that
2909         // we use the first character of the line instead of (average)
2910         // GetCharWidth(): it is common to have lines of dashes, for example,
2911         // and this should give us much better approximation in such case
2912         //
2913         // OPT: maybe using (cache) m_widthAvg would be still faster? profile!
2914         dc.GetTextExtent(line[0], &width, NULL);
2915 
2916         col = x / width;
2917         if ( col < 0 )
2918         {
2919             col = 0;
2920         }
2921         else if ( (size_t)col > line.length() )
2922         {
2923             col = line.length();
2924         }
2925 
2926         // matchDir is the direction in which we should move to reach the
2927         // character containing the given position
2928         enum
2929         {
2930             Match_Left  = -1,
2931             Match_None  = 0,
2932             Match_Right = 1
2933         } matchDir = Match_None;
2934         for ( ;; )
2935         {
2936             // check that we didn't go beyond the line boundary
2937             if ( col < 0 )
2938             {
2939                 col = 0;
2940                 break;
2941             }
2942             if ( (size_t)col > line.length() )
2943             {
2944                 col = line.length();
2945                 break;
2946             }
2947 
2948             wxString strBefore(line, (size_t)col);
2949             dc.GetTextExtent(strBefore, &width, NULL);
2950             if ( width > x )
2951             {
2952                 if ( matchDir == Match_Right )
2953                 {
2954                     // we were going to the right and, finally, moved beyond
2955                     // the original position - stop on the previous one
2956                     col--;
2957 
2958                     break;
2959                 }
2960 
2961                 if ( matchDir == Match_None )
2962                 {
2963                     // we just started iterating, now we know that we should
2964                     // move to the left
2965                     matchDir = Match_Left;
2966                 }
2967                 //else: we are still to the right of the target, continue
2968             }
2969             else // width < x
2970             {
2971                 // invert the logic above
2972                 if ( matchDir == Match_Left )
2973                 {
2974                     // with the exception that we don't need to backtrack here
2975                     break;
2976                 }
2977 
2978                 if ( matchDir == Match_None )
2979                 {
2980                     // go to the right
2981                     matchDir = Match_Right;
2982                 }
2983             }
2984 
2985             // this is not supposed to happen
2986             wxASSERT_MSG( matchDir, wxT("logic error in wxTextCtrl::HitTest") );
2987 
2988             if ( matchDir == Match_Right )
2989                 col++;
2990             else
2991                 col--;
2992         }
2993     }
2994 
2995     // check that we calculated it correctly
2996 #ifdef WXDEBUG_TEXT
2997     if ( res == wxTE_HT_ON_TEXT )
2998     {
2999         wxCoord width1;
3000         wxString text = line.Left(col);
3001         dc.GetTextExtent(text, &width1, NULL);
3002         if ( (size_t)col < line.length() )
3003         {
3004             wxCoord width2;
3005 
3006             text += line[col];
3007             dc.GetTextExtent(text, &width2, NULL);
3008 
3009             wxASSERT_MSG( (width1 <= x) && (x < width2),
3010                           wxT("incorrect HitTestLine() result") );
3011         }
3012         else // we return last char
3013         {
3014             wxASSERT_MSG( x >= width1, wxT("incorrect HitTestLine() result") );
3015         }
3016     }
3017 #endif // WXDEBUG_TEXT
3018 
3019     if ( colOut )
3020         *colOut = col;
3021 
3022     return res;
3023 }
3024 
HitTest(const wxPoint & pt,long * pos) const3025 wxTextCtrlHitTestResult wxTextCtrl::HitTest(const wxPoint& pt, long *pos) const
3026 {
3027     wxTextCoord x, y;
3028     wxTextCtrlHitTestResult rc = HitTest(pt, &x, &y);
3029     if ( rc != wxTE_HT_UNKNOWN && pos )
3030     {
3031         *pos = XYToPosition(x, y);
3032     }
3033 
3034     return rc;
3035 }
3036 
HitTest(const wxPoint & pos,wxTextCoord * colOut,wxTextCoord * rowOut) const3037 wxTextCtrlHitTestResult wxTextCtrl::HitTest(const wxPoint& pos,
3038                                             wxTextCoord *colOut,
3039                                             wxTextCoord *rowOut) const
3040 {
3041     return HitTest2(pos.y, pos.x, 0, rowOut, colOut, NULL, NULL);
3042 }
3043 
HitTestLogical(const wxPoint & pos,wxTextCoord * colOut,wxTextCoord * rowOut) const3044 wxTextCtrlHitTestResult wxTextCtrl::HitTestLogical(const wxPoint& pos,
3045                                                    wxTextCoord *colOut,
3046                                                    wxTextCoord *rowOut) const
3047 {
3048     return HitTest2(pos.y, pos.x, 0, rowOut, colOut, NULL, NULL, false);
3049 }
3050 
HitTest2(wxCoord y0,wxCoord x10,wxCoord x20,wxTextCoord * rowOut,wxTextCoord * colStart,wxTextCoord * colEnd,wxTextCoord * colRowStartOut,bool deviceCoords) const3051 wxTextCtrlHitTestResult wxTextCtrl::HitTest2(wxCoord y0,
3052                                              wxCoord x10,
3053                                              wxCoord x20,
3054                                              wxTextCoord *rowOut,
3055                                              wxTextCoord *colStart,
3056                                              wxTextCoord *colEnd,
3057                                              wxTextCoord *colRowStartOut,
3058                                              bool deviceCoords) const
3059 {
3060     // is the point in the text area or to the right or below it?
3061     wxTextCtrlHitTestResult res = wxTE_HT_ON_TEXT;
3062 
3063     // translate the window coords x0 and y0 into the client coords in the text
3064     // area by adjusting for both the client and text area offsets (unless this
3065     // was already done)
3066     int x1, y;
3067     if ( deviceCoords )
3068     {
3069         wxPoint pt = GetClientAreaOrigin() + m_rectText.GetPosition();
3070         CalcUnscrolledPosition(x10 - pt.x, y0 - pt.y, &x1, &y);
3071     }
3072     else
3073     {
3074         y = y0;
3075         x1 = x10;
3076     }
3077 
3078     // calculate the row (it is really a LINE, not a ROW)
3079     wxTextCoord row;
3080 
3081     // these vars are used only for WrapLines() case
3082     wxTextCoord colRowStart = 0;
3083     size_t rowLen = 0;
3084 
3085     if ( colRowStartOut )
3086         *colRowStartOut = 0;
3087 
3088     int hLine = GetLineHeight();
3089     if ( y < 0 )
3090     {
3091         // and clicking before it is the same as clicking on the first one
3092         row = 0;
3093 
3094         res = wxTE_HT_BEFORE;
3095     }
3096     else // y >= 0
3097     {
3098         wxTextCoord rowLast = GetNumberOfLines() - 1;
3099         row = y / hLine;
3100         if ( IsSingleLine() || !WrapLines() )
3101         {
3102             // in this case row calculation is simple as all lines have the
3103             // same height and so row is the same as line
3104             if ( row > rowLast )
3105             {
3106                 // clicking below the text is the same as clicking on the last
3107                 // line
3108                 row = rowLast;
3109 
3110                 res = wxTE_HT_BELOW;
3111             }
3112         }
3113         else // multline control with line wrap
3114         {
3115             // use binary search to find the line containing this row
3116             const wxArrayWrappedLinesData& linesData = WData().m_linesData;
3117             size_t lo = 0,
3118                    hi = linesData.GetCount(),
3119                    cur;
3120             while ( lo < hi )
3121             {
3122                 cur = (lo + hi)/2;
3123                 const wxWrappedLineData& lineData = linesData[cur];
3124                 if ( !WData().IsValidLine(cur) )
3125                     LayoutLines(cur);
3126                 wxTextCoord rowFirst = lineData.GetFirstRow();
3127 
3128                 if ( row < rowFirst )
3129                 {
3130                     hi = cur;
3131                 }
3132                 else
3133                 {
3134                     // our row is after the first row of the cur line:
3135                     // obviously, if cur is the last line, it contains this
3136                     // row, otherwise we have to test that it is before the
3137                     // first row of the next line
3138                     bool found = cur == linesData.GetCount() - 1;
3139                     if ( found )
3140                     {
3141                         // if the row is beyond the end of text, adjust it to
3142                         // be the last one and set res accordingly
3143                         if ( (size_t)(row - rowFirst) >= lineData.GetRowCount() )
3144                         {
3145                             res = wxTE_HT_BELOW;
3146 
3147                             row = lineData.GetRowCount() + rowFirst - 1;
3148                         }
3149                     }
3150                     else // not the last row
3151                     {
3152                         const wxWrappedLineData&
3153                                 lineNextData = linesData[cur + 1];
3154                         if ( !WData().IsValidLine(cur + 1) )
3155                             LayoutLines(cur + 1);
3156                         found = row < lineNextData.GetFirstRow();
3157                     }
3158 
3159                     if ( found )
3160                     {
3161                         colRowStart = lineData.GetRowStart(row - rowFirst);
3162                         rowLen = lineData.GetRowLength(row - rowFirst,
3163                                                        GetLines()[cur].length());
3164                         row = cur;
3165 
3166                         break;
3167                     }
3168                     else
3169                     {
3170                         lo = cur;
3171                     }
3172                 }
3173             }
3174         }
3175     }
3176 
3177     if ( res == wxTE_HT_ON_TEXT )
3178     {
3179         // now find the position in the line
3180         wxString lineText = GetLineText(row),
3181                  rowText;
3182 
3183         if ( colRowStart || rowLen )
3184         {
3185             // look in this row only, not in whole line
3186             rowText = lineText.Mid(colRowStart, rowLen);
3187         }
3188         else
3189         {
3190             // just take the whole string
3191             rowText = lineText;
3192         }
3193 
3194         if ( colStart )
3195         {
3196             res = HitTestLine(GetTextToShow(rowText), x1, colStart);
3197 
3198             if ( colRowStart )
3199             {
3200                 if ( colRowStartOut )
3201                 {
3202                     // give them the column offset in this ROW in pixels
3203                     *colRowStartOut = colRowStart;
3204                 }
3205 
3206                 // take into account that the ROW doesn't start in the
3207                 // beginning of the LINE
3208                 *colStart += colRowStart;
3209             }
3210 
3211             if ( colEnd )
3212             {
3213                 // the hit test result we return is for x1, so throw out
3214                 // the result for x2 here
3215                 int x2 = x1 + x20 - x10;
3216                 (void)HitTestLine(GetTextToShow(rowText), x2, colEnd);
3217 
3218                 *colEnd += colRowStart;
3219             }
3220         }
3221     }
3222     else // before/after vertical text span
3223     {
3224         if ( colStart )
3225         {
3226             // fill the column with the first/last position in the
3227             // corresponding line
3228             if ( res == wxTE_HT_BEFORE )
3229                 *colStart = 0;
3230             else // res == wxTE_HT_BELOW
3231                 *colStart = GetLineText(GetNumberOfLines() - 1).length();
3232         }
3233     }
3234 
3235     if ( rowOut )
3236     {
3237         // give them the row in text coords (as is)
3238         *rowOut = row;
3239     }
3240 
3241     return res;
3242 }
3243 
GetLineAndRow(wxTextCoord row,wxTextCoord * lineOut,wxTextCoord * rowInLineOut) const3244 bool wxTextCtrl::GetLineAndRow(wxTextCoord row,
3245                                wxTextCoord *lineOut,
3246                                wxTextCoord *rowInLineOut) const
3247 {
3248     wxTextCoord line,
3249                 rowInLine = 0;
3250 
3251     if ( row < 0 )
3252         return false;
3253 
3254     int nLines = GetNumberOfLines();
3255     if ( WrapLines() )
3256     {
3257         const wxArrayWrappedLinesData& linesData = WData().m_linesData;
3258         for ( line = 0; line < nLines; line++ )
3259         {
3260             if ( !WData().IsValidLine(line) )
3261                 LayoutLines(line);
3262 
3263             if ( row < linesData[line].GetNextRow() )
3264             {
3265                 // we found the right line
3266                 rowInLine = row - linesData[line].GetFirstRow();
3267 
3268                 break;
3269             }
3270         }
3271 
3272         if ( line == nLines )
3273         {
3274             // the row is out of range
3275             return false;
3276         }
3277     }
3278     else // no line wrapping, everything is easy
3279     {
3280         if ( row >= nLines )
3281             return false;
3282 
3283         line = row;
3284     }
3285 
3286     if ( lineOut )
3287         *lineOut = line;
3288     if ( rowInLineOut )
3289         *rowInLineOut = rowInLine;
3290 
3291     return true;
3292 }
3293 
3294 // ----------------------------------------------------------------------------
3295 // scrolling
3296 // ----------------------------------------------------------------------------
3297 
3298 /*
3299    wxTextCtrl has not one but two scrolling mechanisms: one is semi-automatic
3300    scrolling in both horizontal and vertical direction implemented using
3301    wxScrollHelper and the second one is manual scrolling implemented using
3302    SData().m_ofsHorz and used by the single line controls without scroll bar.
3303 
3304    The first version (the standard one) always scrolls by fixed amount which is
3305    fine for vertical scrolling as all lines have the same height but is rather
3306    ugly for horizontal scrolling if proportional font is used. This is why we
3307    manually update and use SData().m_ofsHorz which contains the length of the string
3308    which is hidden beyond the left border. An important property of text
3309    controls using this kind of scrolling is that an entire number of characters
3310    is always shown and that parts of characters never appear on display -
3311    neither in the leftmost nor rightmost positions.
3312 
3313    Once again, for multi line controls SData().m_ofsHorz is always 0 and scrolling is
3314    done as usual for wxScrollWindow.
3315  */
3316 
ShowHorzPosition(wxCoord pos)3317 void wxTextCtrl::ShowHorzPosition(wxCoord pos)
3318 {
3319     wxASSERT_MSG( IsSingleLine(), wxT("doesn't work for multiline") );
3320 
3321     // pos is the logical position to show
3322 
3323     // SData().m_ofsHorz is the first logical position shown
3324     if ( pos < SData().m_ofsHorz )
3325     {
3326         // scroll backwards
3327         wxTextCoord col;
3328         HitTestLine(m_value, pos, &col);
3329         ScrollText(col);
3330     }
3331     else
3332     {
3333         wxCoord width = m_rectText.width;
3334         if ( !width )
3335         {
3336             // if we are called from the ctor, m_rectText is not initialized
3337             // yet, so do it now
3338             UpdateTextRect();
3339             width = m_rectText.width;
3340         }
3341 
3342         // SData().m_ofsHorz + width is the last logical position shown
3343         if ( pos > SData().m_ofsHorz + width)
3344         {
3345             // scroll forward
3346             wxTextCoord col;
3347             HitTestLine(m_value, pos - width, &col);
3348             ScrollText(col + 1);
3349         }
3350     }
3351 }
3352 
3353 // scroll the window horizontally so that the first visible character becomes
3354 // the one at this position
ScrollText(wxTextCoord col)3355 void wxTextCtrl::ScrollText(wxTextCoord col)
3356 {
3357     wxASSERT_MSG( IsSingleLine(),
3358                   wxT("ScrollText() is for single line controls only") );
3359 
3360     // never scroll beyond the left border
3361     if ( col < 0 )
3362         col = 0;
3363 
3364     // OPT: could only get the extent of the part of the string between col
3365     //      and SData().m_colStart
3366     wxCoord ofsHorz = GetTextWidth(GetLineText(0).Left(col));
3367 
3368     if ( ofsHorz != SData().m_ofsHorz )
3369     {
3370         // remember the last currently used pixel
3371         int posLastVisible = SData().m_posLastVisible;
3372         if ( posLastVisible == -1 )
3373         {
3374             // this may happen when we're called very early, during the
3375             // controls construction
3376             UpdateLastVisible();
3377 
3378             posLastVisible = SData().m_posLastVisible;
3379         }
3380 
3381         // NB1: to scroll to the right, offset must be negative, hence the
3382         //      order of operands
3383         int dx = SData().m_ofsHorz - ofsHorz;
3384 
3385         // NB2: we call Refresh() below which results in a call to
3386         //      DoDraw(), so we must update SData().m_ofsHorz before calling it
3387         SData().m_ofsHorz = ofsHorz;
3388         SData().m_colStart = col;
3389 
3390         // after changing m_colStart, recalc the last visible position: we need
3391         // to recalc the last visible position beore scrolling in order to make
3392         // it appear exactly at the right edge of the text area after scrolling
3393         UpdateLastVisible();
3394 
3395 #if 0 // do we?
3396         if ( dx < 0 )
3397         {
3398             // we want to force the update of it after scrolling
3399             SData().m_colLastVisible = -1;
3400         }
3401 #endif
3402 
3403         // scroll only the rectangle inside which there is the text
3404         wxRect rect = m_rectText;
3405         rect.width = posLastVisible;
3406 
3407         rect = ScrollNoRefresh(dx, 0, &rect);
3408 
3409         /*
3410            we need to manually refresh the part which ScrollWindow() doesn't
3411            refresh (with new API this means the part outside the rect returned
3412            by ScrollNoRefresh): indeed, if we had this:
3413 
3414                                    ********o
3415 
3416            where '*' is text and 'o' is blank area at the end (too small to
3417            hold the next char) then after scrolling by 2 positions to the left
3418            we're going to have
3419 
3420                                    ******RRo
3421 
3422            where 'R' is the area refreshed by ScrollWindow() - but we still
3423            need to refresh the 'o' at the end as it may be now big enough to
3424            hold the new character shifted into view.
3425 
3426            when we are scrolling to the right, we need to update this rect as
3427            well because it might have contained something before but doesn't
3428            contain anything any more
3429          */
3430 
3431         // we can combine both rectangles into one when scrolling to the left,
3432         // but we need two separate Refreshes() otherwise
3433         if ( dx > 0 )
3434         {
3435             // refresh the uncovered part on the left
3436             Refresh(true, &rect);
3437 
3438             // and now the area on the right
3439             rect.x = m_rectText.x + posLastVisible;
3440             rect.width = m_rectText.width - posLastVisible;
3441         }
3442         else // scrolling to the left
3443         {
3444             // just extend the rect covering the uncovered area to the edge of
3445             // the text rect
3446             rect.width += m_rectText.width - posLastVisible;
3447         }
3448 
3449         Refresh(true, &rect);
3450 
3451         // I don't know exactly why is this needed here but without it we may
3452         // scroll the window again (from the same method) before the previously
3453         // invalidated area is repainted when typing *very* quickly - and this
3454         // may lead to the display corruption
3455         Update();
3456     }
3457 }
3458 
CalcUnscrolledPosition(int x,int y,int * xx,int * yy) const3459 void wxTextCtrl::CalcUnscrolledPosition(int x, int y, int *xx, int *yy) const
3460 {
3461     if ( IsSingleLine() )
3462     {
3463         // we don't use wxScrollHelper
3464         if ( xx )
3465             *xx = x + SData().m_ofsHorz;
3466         if ( yy )
3467             *yy = y;
3468     }
3469     else
3470     {
3471         // let the base class do it
3472         wxScrollHelper::CalcUnscrolledPosition(x, y, xx, yy);
3473     }
3474 }
3475 
CalcScrolledPosition(int x,int y,int * xx,int * yy) const3476 void wxTextCtrl::CalcScrolledPosition(int x, int y, int *xx, int *yy) const
3477 {
3478     if ( IsSingleLine() )
3479     {
3480         // we don't use wxScrollHelper
3481         if ( xx )
3482             *xx = x - SData().m_ofsHorz;
3483         if ( yy )
3484             *yy = y;
3485     }
3486     else
3487     {
3488         // let the base class do it
3489         wxScrollHelper::CalcScrolledPosition(x, y, xx, yy);
3490     }
3491 }
3492 
DoPrepareDC(wxDC & dc)3493 void wxTextCtrl::DoPrepareDC(wxDC& dc)
3494 {
3495     // for single line controls we only have to deal with SData().m_ofsHorz and it's
3496     // useless to call base class version as they don't use normal scrolling
3497     if ( IsSingleLine() && SData().m_ofsHorz )
3498     {
3499         // adjust the DC origin if the text is shifted
3500         wxPoint pt = dc.GetDeviceOrigin();
3501         dc.SetDeviceOrigin(pt.x - SData().m_ofsHorz, pt.y);
3502     }
3503     else
3504     {
3505         wxScrollHelper::DoPrepareDC(dc);
3506     }
3507 }
3508 
UpdateMaxWidth(wxTextCoord line)3509 void wxTextCtrl::UpdateMaxWidth(wxTextCoord line)
3510 {
3511     // OPT!
3512 
3513     // check if the max width changes after this line was modified
3514     wxCoord widthMaxOld = MData().m_widthMax,
3515             width;
3516     GetTextExtent(GetLineText(line), &width, NULL);
3517 
3518     if ( line == MData().m_lineLongest )
3519     {
3520         // this line was the longest one, is it still?
3521         if ( width > MData().m_widthMax )
3522         {
3523             MData().m_widthMax = width;
3524         }
3525         else if ( width < MData().m_widthMax )
3526         {
3527             // we need to find the new longest line
3528             RecalcMaxWidth();
3529         }
3530         //else: its length didn't change, nothing to do
3531     }
3532     else // it wasn't the longest line, but maybe it became it?
3533     {
3534         // GetMaxWidth() and not MData().m_widthMax as it might be not calculated yet
3535         if ( width > GetMaxWidth() )
3536         {
3537             MData().m_widthMax = width;
3538             MData().m_lineLongest = line;
3539         }
3540     }
3541 
3542     MData().m_updateScrollbarX = MData().m_widthMax != widthMaxOld;
3543 }
3544 
RecalcFontMetrics()3545 void wxTextCtrl::RecalcFontMetrics()
3546 {
3547     m_heightLine = GetCharHeight();
3548     m_widthAvg = GetCharWidth();
3549 }
3550 
RecalcMaxWidth()3551 void wxTextCtrl::RecalcMaxWidth()
3552 {
3553     wxASSERT_MSG( !IsSingleLine(), wxT("only used for multiline") );
3554 
3555     MData().m_widthMax = -1;
3556     (void)GetMaxWidth();
3557 }
3558 
GetMaxWidth() const3559 wxCoord wxTextCtrl::GetMaxWidth() const
3560 {
3561     if ( MData().m_widthMax == -1 )
3562     {
3563         // recalculate it
3564 
3565         // OPT: should we remember the widths of all the lines?
3566 
3567         wxTextCtrl *self = wxConstCast(this, wxTextCtrl);
3568         wxClientDC dc(self);
3569         dc.SetFont(GetFont());
3570 
3571         self->MData().m_widthMax = 0;
3572 
3573         size_t count = GetLineCount();
3574         for ( size_t n = 0; n < count; n++ )
3575         {
3576             wxCoord width;
3577             dc.GetTextExtent(GetLines()[n], &width, NULL);
3578             if ( width > MData().m_widthMax )
3579             {
3580                 // remember the width and the line which has it
3581                 self->MData().m_widthMax = width;
3582                 self->MData().m_lineLongest = n;
3583             }
3584         }
3585     }
3586 
3587     wxASSERT_MSG( MData().m_widthMax != -1, wxT("should have at least 1 line") );
3588 
3589     return MData().m_widthMax;
3590 }
3591 
UpdateScrollbars()3592 void wxTextCtrl::UpdateScrollbars()
3593 {
3594     wxASSERT_MSG( !IsSingleLine(), wxT("only used for multiline") );
3595 
3596     wxSize size = GetRealTextArea().GetSize();
3597 
3598     // is our height enough to show all items?
3599     wxTextCoord nRows = GetRowCount();
3600     wxCoord lineHeight = GetLineHeight();
3601     bool showScrollbarY = nRows*lineHeight > size.y;
3602 
3603     // is our width enough to show the longest line?
3604     wxCoord charWidth, maxWidth;
3605     bool showScrollbarX;
3606     if ( !WrapLines() )
3607     {
3608         charWidth = GetAverageWidth();
3609         maxWidth = GetMaxWidth();
3610         showScrollbarX = maxWidth > size.x;
3611     }
3612     else // never show the horz scrollbar
3613     {
3614         // just to suppress compiler warnings about using uninit vars below
3615         charWidth = maxWidth = 0;
3616 
3617         showScrollbarX = false;
3618     }
3619 
3620     // calc the scrollbars ranges
3621     int scrollRangeX = showScrollbarX
3622                         ? (maxWidth + 2*charWidth - 1) / charWidth
3623                         : 0;
3624     int scrollRangeY = showScrollbarY ? nRows : 0;
3625 
3626     int scrollRangeXOld = MData().m_scrollRangeX,
3627         scrollRangeYOld = MData().m_scrollRangeY;
3628     if ( (scrollRangeY != scrollRangeYOld) || (scrollRangeX != scrollRangeXOld) )
3629     {
3630         int x, y;
3631         GetViewStart(&x, &y);
3632 
3633 #if 0
3634         // we want to leave the scrollbars at the same position which means
3635         // that x and y have to be adjusted as the number of positions may have
3636         // changed
3637         //
3638         // the number of positions is calculated from knowing that last
3639         // position = range - thumbSize and thumbSize == pageSize which is
3640         // equal to the window width / pixelsPerLine
3641         if ( scrollRangeXOld )
3642         {
3643             x *= scrollRangeX - m_rectText.width / charWidth;
3644             x /= scrollRangeXOld - m_rectText.width / charWidth;
3645         }
3646 
3647         if ( scrollRangeYOld )
3648             y *= scrollRangeY / scrollRangeYOld;
3649 #endif // 0
3650 
3651         SetScrollbars(charWidth, lineHeight,
3652                       scrollRangeX, scrollRangeY,
3653                       x, y,
3654                       true /* no refresh */);
3655 
3656         if ( scrollRangeXOld )
3657         {
3658             const int w = m_rectText.width / charWidth;
3659             if ( w != scrollRangeXOld )
3660             {
3661                 x *= scrollRangeX - w;
3662                 x /= scrollRangeXOld - w;
3663             }
3664             Scroll(x, y);
3665         }
3666 
3667         MData().m_scrollRangeX = scrollRangeX;
3668         MData().m_scrollRangeY = scrollRangeY;
3669 
3670         // bring the current position in view
3671         ShowPosition(-1);
3672     }
3673 
3674     MData().m_updateScrollbarX =
3675     MData().m_updateScrollbarY = false;
3676 }
3677 
OnInternalIdle()3678 void wxTextCtrl::OnInternalIdle()
3679 {
3680     // notice that single line text control never has scrollbars
3681     if ( !IsSingleLine() &&
3682             (MData().m_updateScrollbarX || MData().m_updateScrollbarY) )
3683     {
3684         UpdateScrollbars();
3685     }
3686     wxControl::OnInternalIdle();
3687 }
3688 
SendAutoScrollEvents(wxScrollWinEvent & event) const3689 bool wxTextCtrl::SendAutoScrollEvents(wxScrollWinEvent& event) const
3690 {
3691     bool forward = event.GetEventType() == wxEVT_SCROLLWIN_LINEDOWN;
3692     if ( event.GetOrientation() == wxHORIZONTAL )
3693     {
3694         return forward ? m_curCol <= GetLineLength(m_curRow) : m_curCol > 0;
3695     }
3696     else // wxVERTICAL
3697     {
3698         return forward ? m_curRow < GetNumberOfLines() : m_curRow > 0;
3699     }
3700 }
3701 
3702 // ----------------------------------------------------------------------------
3703 // refresh
3704 // ----------------------------------------------------------------------------
3705 
RefreshSelection()3706 void wxTextCtrl::RefreshSelection()
3707 {
3708     if ( HasSelection() )
3709     {
3710         RefreshTextRange(m_selStart, m_selEnd);
3711     }
3712 }
3713 
RefreshLineRange(wxTextCoord lineFirst,wxTextCoord lineLast)3714 void wxTextCtrl::RefreshLineRange(wxTextCoord lineFirst, wxTextCoord lineLast)
3715 {
3716     wxASSERT_MSG( lineFirst <= lineLast || !lineLast,
3717                   wxT("no lines to refresh") );
3718 
3719     wxRect rect;
3720     // rect.x is already 0
3721     rect.width = m_rectText.width;
3722     wxCoord h = GetLineHeight();
3723 
3724     wxTextCoord rowFirst;
3725     if ( lineFirst < GetNumberOfLines() )
3726     {
3727         rowFirst = GetFirstRowOfLine(lineFirst);
3728     }
3729     else // lineFirst == GetNumberOfLines()
3730     {
3731         // lineFirst may be beyond the last line only if we refresh till
3732         // the end, otherwise it's illegal
3733         wxASSERT_MSG( lineFirst == GetNumberOfLines() && !lineLast,
3734                       wxT("invalid line range") );
3735 
3736         rowFirst = GetRowAfterLine(lineFirst - 1);
3737     }
3738 
3739     rect.y = rowFirst*h;
3740 
3741     if ( lineLast )
3742     {
3743         // refresh till this line (inclusive)
3744         wxTextCoord rowLast = GetRowAfterLine(lineLast);
3745 
3746         rect.height = (rowLast - rowFirst + 1)*h;
3747     }
3748     else // lineLast == 0 means to refresh till the end
3749     {
3750         // FIXME: calc it exactly
3751         rect.height = 32000;
3752     }
3753 
3754     RefreshTextRect(rect);
3755 }
3756 
RefreshTextRange(wxTextPos start,wxTextPos end)3757 void wxTextCtrl::RefreshTextRange(wxTextPos start, wxTextPos end)
3758 {
3759     wxCHECK_RET( start != -1 && end != -1,
3760                  wxT("invalid RefreshTextRange() arguments") );
3761 
3762     // accept arguments in any order as it is more conenient for the caller
3763     OrderPositions(start, end);
3764 
3765     // this is acceptable but we don't do anything in this case
3766     if ( start == end )
3767         return;
3768 
3769     wxTextPos colStart, lineStart;
3770     if ( !PositionToXY(start, &colStart, &lineStart) )
3771     {
3772         // the range is entirely beyond the end of the text, nothing to do
3773         return;
3774     }
3775 
3776     wxTextCoord colEnd, lineEnd;
3777     if ( !PositionToXY(end, &colEnd, &lineEnd) )
3778     {
3779         // the range spans beyond the end of text, refresh to the end
3780         colEnd = -1;
3781         lineEnd = GetNumberOfLines() - 1;
3782     }
3783 
3784     // refresh all lines one by one
3785     for ( wxTextCoord line = lineStart; line <= lineEnd; line++ )
3786     {
3787         // refresh the first line from the start of the range to the end, the
3788         // intermediate ones entirely and the last one from the beginning to
3789         // the end of the range
3790         wxTextPos posStart = line == lineStart ? colStart : 0;
3791         size_t posCount;
3792         if ( (line != lineEnd) || (colEnd == -1) )
3793         {
3794             // intermediate line or the last one but we need to refresh it
3795             // until the end anyhow - do it
3796             posCount = wxString::npos;
3797         }
3798         else // last line
3799         {
3800             // refresh just the positions in between the start and the end one
3801             posCount = colEnd - posStart;
3802         }
3803 
3804         if ( posCount )
3805             RefreshColRange(line, posStart, posCount);
3806     }
3807 }
3808 
RefreshColRange(wxTextCoord line,wxTextPos start,size_t count)3809 void wxTextCtrl::RefreshColRange(wxTextCoord line,
3810                                  wxTextPos start,
3811                                  size_t count)
3812 {
3813     wxString text = GetLineText(line);
3814 
3815     wxASSERT_MSG( (size_t)start <= text.length() && count,
3816                   wxT("invalid RefreshColRange() parameter") );
3817 
3818     RefreshPixelRange(line,
3819                       GetTextWidth(text.Left((size_t)start)),
3820                       GetTextWidth(text.Mid((size_t)start, (size_t)count)));
3821 }
3822 
3823 // this method accepts "logical" coords in the sense that they are coordinates
3824 // in a logical line but it can span several rows if we wrap lines and
3825 // RefreshPixelRange() will then refresh several rows
RefreshPixelRange(wxTextCoord line,wxCoord start,wxCoord width)3826 void wxTextCtrl::RefreshPixelRange(wxTextCoord line,
3827                                    wxCoord start,
3828                                    wxCoord width)
3829 {
3830     // we will use line text only in line wrap case
3831     wxString text;
3832     if ( WrapLines() )
3833     {
3834         text = GetLineText(line);
3835     }
3836 
3837     // special case: width == 0 means to refresh till the end of line
3838     if ( width == 0 )
3839     {
3840         // refresh till the end of visible line
3841         width = GetTotalWidth();
3842 
3843         if ( WrapLines() )
3844         {
3845             // refresh till the end of text
3846             wxCoord widthAll = GetTextWidth(text);
3847 
3848             // extend width to the end of ROW
3849             width = widthAll - widthAll % width + width;
3850         }
3851 
3852         // no need to refresh beyond the end of line
3853         width -= start;
3854     }
3855     //else: just refresh the specified part
3856 
3857     wxCoord h = GetLineHeight();
3858     wxRect rect;
3859     rect.x = start;
3860     rect.y = GetFirstRowOfLine(line)*h;
3861     rect.height = h;
3862 
3863     if ( WrapLines() )
3864     {
3865         // (1) skip all rows which we don't touch at all
3866         const wxWrappedLineData& lineData = WData().m_linesData[line];
3867         if ( !WData().IsValidLine(line) )
3868             LayoutLines(line);
3869 
3870         wxCoord wLine = 0; // suppress compiler warning about uninit var
3871         size_t rowLast = lineData.GetRowCount(),
3872                row = 0;
3873         while ( (row < rowLast) &&
3874                 (rect.x > (wLine = lineData.GetRowWidth(row++))) )
3875         {
3876             rect.x -= wLine;
3877             rect.y += h;
3878         }
3879 
3880         // (2) now refresh all lines except the last one: note that the first
3881         //     line is refreshed from the given start to the end, all the next
3882         //     ones - entirely
3883         while ( (row < rowLast) && (width > wLine - rect.x) )
3884         {
3885             rect.width = GetTotalWidth() - rect.x;
3886             RefreshTextRect(rect);
3887 
3888             width -= wLine - rect.x;
3889             rect.x = 0;
3890             rect.y += h;
3891 
3892             wLine = lineData.GetRowWidth(row++);
3893         }
3894 
3895         // (3) the code below will refresh the last line
3896     }
3897 
3898     rect.width = width;
3899 
3900     RefreshTextRect(rect);
3901 }
3902 
RefreshTextRect(const wxRect & rectClient,bool textOnly)3903 void wxTextCtrl::RefreshTextRect(const wxRect& rectClient, bool textOnly)
3904 {
3905     wxRect rect;
3906     CalcScrolledPosition(rectClient.x, rectClient.y, &rect.x, &rect.y);
3907     rect.width = rectClient.width;
3908     rect.height = rectClient.height;
3909 
3910     // account for the text area offset
3911     rect.Offset(m_rectText.GetPosition());
3912 
3913     // don't refresh beyond the text area unless we're refreshing the line wrap
3914     // marks in which case textOnly is false
3915     if ( textOnly )
3916     {
3917         if ( rect.GetRight() > m_rectText.GetRight() )
3918         {
3919             rect.SetRight(m_rectText.GetRight());
3920 
3921             if ( rect.width <= 0 )
3922             {
3923                 // nothing to refresh
3924                 return;
3925             }
3926         }
3927     }
3928 
3929     // check the bottom boundary always, even for the line wrap marks
3930     if ( rect.GetBottom() > m_rectText.GetBottom() )
3931     {
3932         rect.SetBottom(m_rectText.GetBottom());
3933 
3934         if ( rect.height <= 0 )
3935         {
3936             // nothing to refresh
3937             return;
3938         }
3939     }
3940 
3941     // never refresh before the visible rect
3942     if ( rect.x < m_rectText.x )
3943         rect.x = m_rectText.x;
3944 
3945     if ( rect.y < m_rectText.y )
3946         rect.y = m_rectText.y;
3947 
3948     wxLogTrace(wxT("text"), wxT("Refreshing (%d, %d)-(%d, %d)"),
3949                rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
3950 
3951     Refresh(true, &rect);
3952 }
3953 
RefreshLineWrapMarks(wxTextCoord rowFirst,wxTextCoord rowLast)3954 void wxTextCtrl::RefreshLineWrapMarks(wxTextCoord rowFirst,
3955                                       wxTextCoord rowLast)
3956 {
3957     if ( WData().m_widthMark )
3958     {
3959         wxRect rectMarks;
3960         rectMarks.x = m_rectText.width;
3961         rectMarks.width = WData().m_widthMark;
3962         rectMarks.y = rowFirst*GetLineHeight();
3963         rectMarks.height = (rowLast - rowFirst)*GetLineHeight();
3964 
3965         RefreshTextRect(rectMarks, false /* don't limit to text area */);
3966     }
3967 }
3968 
3969 // ----------------------------------------------------------------------------
3970 // border drawing
3971 // ----------------------------------------------------------------------------
3972 
DoDrawBorder(wxDC & dc,const wxRect & rect)3973 void wxTextCtrl::DoDrawBorder(wxDC& dc, const wxRect& rect)
3974 {
3975     m_renderer->DrawTextBorder(dc, GetBorder(), rect, GetStateFlags());
3976 }
3977 
3978 // ----------------------------------------------------------------------------
3979 // client area drawing
3980 // ----------------------------------------------------------------------------
3981 
3982 /*
3983    Several remarks about wxTextCtrl redraw logic:
3984 
3985    1. only the regions which must be updated are redrawn, this means that we
3986       never Refresh() the entire window but use RefreshPixelRange() and
3987       ScrollWindow() which only refresh small parts of it and iterate over the
3988       update region in our DoDraw()
3989 
3990    2. the text displayed on the screen is obtained using GetTextToShow(): it
3991       should be used for all drawing/measuring
3992  */
3993 
GetTextToShow(const wxString & text) const3994 wxString wxTextCtrl::GetTextToShow(const wxString& text) const
3995 {
3996     wxString textShown;
3997     if ( IsPassword() )
3998         textShown = wxString(wxT('*'), text.length());
3999     else
4000         textShown = text;
4001 
4002     return textShown;
4003 }
4004 
DoDrawTextInRect(wxDC & dc,const wxRect & rectUpdate)4005 void wxTextCtrl::DoDrawTextInRect(wxDC& dc, const wxRect& rectUpdate)
4006 {
4007     // debugging trick to see the update rect visually
4008 #ifdef WXDEBUG_TEXT
4009     static int s_countUpdates = -1;
4010     if ( s_countUpdates != -1 )
4011     {
4012         wxWindowDC dc(this);
4013         dc.SetBrush(*(++s_countUpdates % 2 ? wxRED_BRUSH : wxGREEN_BRUSH));
4014         dc.SetPen(*wxTRANSPARENT_PEN);
4015         dc.DrawRectangle(rectUpdate);
4016     }
4017 #endif // WXDEBUG_TEXT
4018 
4019     // calculate the range lineStart..lineEnd of lines to redraw
4020     wxTextCoord lineStart, lineEnd;
4021     if ( IsSingleLine() )
4022     {
4023         lineStart =
4024         lineEnd = 0;
4025     }
4026     else // multiline
4027     {
4028         wxPoint pt = rectUpdate.GetPosition();
4029         (void)HitTest(pt, NULL, &lineStart);
4030 
4031         pt.y += rectUpdate.height;
4032         (void)HitTest(pt, NULL, &lineEnd);
4033     }
4034 
4035     // prepare for drawing
4036     wxCoord hLine = GetLineHeight();
4037 
4038     // these vars will be used for hit testing of the current row
4039     wxCoord y = rectUpdate.y;
4040     const wxCoord x1 = rectUpdate.x;
4041     const wxCoord x2 = rectUpdate.x + rectUpdate.width;
4042 
4043     wxRect rectText;
4044     rectText.height = hLine;
4045     wxCoord yClient = y - GetClientAreaOrigin().y;
4046 
4047     // we want to always start at the top of the line, otherwise if we redraw a
4048     // rect whose top is in the middle of a line, we'd draw this line shifted
4049     yClient -= (yClient - m_rectText.y) % hLine;
4050 
4051     if ( IsSingleLine() )
4052     {
4053         rectText.y = yClient;
4054     }
4055     else // multiline, adjust for scrolling
4056     {
4057         CalcUnscrolledPosition(0, yClient, NULL, &rectText.y);
4058     }
4059 
4060     wxRenderer *renderer = GetRenderer();
4061 
4062     // do draw the invalidated parts of each line: note that we iterate here
4063     // over ROWs, not over LINEs
4064     for ( wxTextCoord line = lineStart;
4065           y < rectUpdate.y + rectUpdate.height;
4066           y += hLine,
4067           rectText.y += hLine )
4068     {
4069         // calculate the update rect in text positions for this line
4070         wxTextCoord colStart, colEnd, colRowStart;
4071         wxTextCtrlHitTestResult ht = HitTest2(y, x1, x2,
4072                                               &line, &colStart, &colEnd,
4073                                               &colRowStart);
4074 
4075         if ( (ht == wxTE_HT_BEYOND) || (ht == wxTE_HT_BELOW) )
4076         {
4077             wxASSERT_MSG( line <= lineEnd, wxT("how did we get that far?") );
4078 
4079             if ( line == lineEnd )
4080             {
4081                 // we redrew everything
4082                 break;
4083             }
4084 
4085             // the update rect is beyond the end of line, no need to redraw
4086             // anything on this line - but continue with the remaining ones
4087             continue;
4088         }
4089 
4090         // for single line controls we may additionally cut off everything
4091         // which is to the right of the last visible position
4092         if ( IsSingleLine() )
4093         {
4094             // don't show the columns which are scrolled out to the left
4095             if ( colStart < SData().m_colStart )
4096                 colStart = SData().m_colStart;
4097 
4098             // colEnd may be less than colStart if colStart was changed by the
4099             // assignment above
4100             if ( colEnd < colStart )
4101                 colEnd = colStart;
4102 
4103             // don't draw the chars beyond the rightmost one
4104             if ( SData().m_colLastVisible == -1 )
4105             {
4106                 // recalculate this rightmost column
4107                 UpdateLastVisible();
4108             }
4109 
4110             if ( colStart > SData().m_colLastVisible )
4111             {
4112                 // don't bother redrawing something that is beyond the last
4113                 // visible position
4114                 continue;
4115             }
4116 
4117             if ( colEnd > SData().m_colLastVisible )
4118             {
4119                 colEnd = SData().m_colLastVisible;
4120             }
4121         }
4122 
4123         // extract the part of line we need to redraw
4124         wxString textLine = GetTextToShow(GetLineText(line));
4125         wxString text = textLine.Mid(colStart, colEnd - colStart + 1);
4126 
4127         // now deal with the selection: only do something if at least part of
4128         // the line is selected
4129         wxTextPos selStart, selEnd;
4130         if ( GetSelectedPartOfLine(line, &selStart, &selEnd) )
4131         {
4132             // and if this part is (at least partly) in the current row
4133             if ( (selStart <= colEnd) &&
4134                     (selEnd >= wxMax(colStart, colRowStart)) )
4135             {
4136                 // these values are relative to the start of the line while the
4137                 // string passed to DrawTextLine() is only part of it, so
4138                 // adjust the selection range accordingly
4139                 selStart -= colStart;
4140                 selEnd -= colStart;
4141 
4142                 if ( selStart < 0 )
4143                     selStart = 0;
4144 
4145                 if ( (size_t)selEnd >= text.length() )
4146                     selEnd = text.length();
4147             }
4148             else
4149             {
4150                 // reset selStart and selEnd to avoid passing them to
4151                 // DrawTextLine() below
4152                 selStart =
4153                 selEnd = -1;
4154             }
4155         }
4156 
4157         // calculate the text coords on screen
4158         wxASSERT_MSG( colStart >= colRowStart, wxT("invalid string part") );
4159         wxCoord ofsStart = GetTextWidth(
4160                                     textLine.Mid(colRowStart,
4161                                                  colStart - colRowStart));
4162         rectText.x = m_rectText.x + ofsStart;
4163         rectText.width = GetTextWidth(text);
4164 
4165         // do draw the text
4166         renderer->DrawTextLine(dc, text, rectText, selStart, selEnd,
4167                                GetStateFlags());
4168         wxLogTrace(wxT("text"), wxT("Line %ld: positions %ld-%ld redrawn."),
4169                    line, colStart, colEnd);
4170     }
4171 }
4172 
DoDrawLineWrapMarks(wxDC & dc,const wxRect & rectUpdate)4173 void wxTextCtrl::DoDrawLineWrapMarks(wxDC& dc, const wxRect& rectUpdate)
4174 {
4175     wxASSERT_MSG( WrapLines() && WData().m_widthMark,
4176                   wxT("shouldn't be called at all") );
4177 
4178     wxRenderer *renderer = GetRenderer();
4179 
4180     wxRect rectMark;
4181     rectMark.x = rectUpdate.x;
4182     rectMark.width = rectUpdate.width;
4183     wxCoord yTop = GetClientAreaOrigin().y;
4184     CalcUnscrolledPosition(0, rectUpdate.y - yTop, NULL, &rectMark.y);
4185     wxCoord hLine = GetLineHeight();
4186     rectMark.height = hLine;
4187 
4188     wxTextCoord line, rowInLine;
4189 
4190     wxCoord yBottom;
4191     CalcUnscrolledPosition(0, rectUpdate.GetBottom() - yTop, NULL, &yBottom);
4192     for ( ; rectMark.y < yBottom; rectMark.y += hLine )
4193     {
4194         if ( !GetLineAndRow(rectMark.y / hLine, &line, &rowInLine) )
4195         {
4196             // we went beyond the end of text
4197             break;
4198         }
4199 
4200         // is this row continued on the next one?
4201         if ( !WData().m_linesData[line].IsLastRow(rowInLine) )
4202         {
4203             renderer->DrawLineWrapMark(dc, rectMark);
4204         }
4205     }
4206 }
4207 
DoDraw(wxControlRenderer * renderer)4208 void wxTextCtrl::DoDraw(wxControlRenderer *renderer)
4209 {
4210     // hide the caret while we're redrawing the window and show it after we are
4211     // done with it
4212     wxCaretSuspend cs(this);
4213 
4214     // prepare the DC
4215     wxDC& dc = renderer->GetDC();
4216     dc.SetFont(GetFont());
4217     dc.SetTextForeground(GetForegroundColour());
4218 
4219     // get the intersection of the update region with the text area: note that
4220     // the update region is in window coords and text area is in the client
4221     // ones, so it must be shifted before computing intersection
4222     wxRegion rgnUpdate = GetUpdateRegion();
4223 
4224     wxRect rectTextArea = GetRealTextArea();
4225     wxPoint pt = GetClientAreaOrigin();
4226     wxRect rectTextAreaAdjusted = rectTextArea;
4227     rectTextAreaAdjusted.x += pt.x;
4228     rectTextAreaAdjusted.y += pt.y;
4229     rgnUpdate.Intersect(rectTextAreaAdjusted);
4230 
4231     // even though the drawing is already clipped to the update region, we must
4232     // explicitly clip it to the rect we will use as otherwise parts of letters
4233     // might be drawn outside of it (if even a small part of a charater is
4234     // inside, HitTest() will return its column and DrawText() can't draw only
4235     // the part of the character, of course)
4236 #ifdef __WXMSW__
4237     // FIXME: is this really a bug in wxMSW?
4238     rectTextArea.width--;
4239 #endif // __WXMSW__
4240     dc.DestroyClippingRegion();
4241     dc.SetClippingRegion(rectTextArea);
4242 
4243     // adjust for scrolling
4244     DoPrepareDC(dc);
4245 
4246     // and now refresh the invalidated parts of the window
4247     wxRegionIterator iter(rgnUpdate);
4248     for ( ; iter.HaveRects(); iter++ )
4249     {
4250         wxRect r = iter.GetRect();
4251 
4252         // this is a workaround for wxGTK::wxRegion bug
4253 #ifdef __WXGTK__
4254         if ( !r.width || !r.height )
4255         {
4256             // ignore invalid rect
4257             continue;
4258         }
4259 #endif // __WXGTK__
4260 
4261         DoDrawTextInRect(dc, r);
4262     }
4263 
4264     // now redraw the line wrap marks (if we draw them)
4265     if ( WrapLines() && WData().m_widthMark )
4266     {
4267         // this is the rect inside which line wrap marks are drawn
4268         wxRect rectMarks;
4269         rectMarks.x = rectTextAreaAdjusted.GetRight() + 1;
4270         rectMarks.y = rectTextAreaAdjusted.y;
4271         rectMarks.width = WData().m_widthMark;
4272         rectMarks.height = rectTextAreaAdjusted.height;
4273 
4274         rgnUpdate = GetUpdateRegion();
4275         rgnUpdate.Intersect(rectMarks);
4276 
4277         wxRect rectUpdate = rgnUpdate.GetBox();
4278         if ( rectUpdate.width && rectUpdate.height )
4279         {
4280             // the marks are outside previously set clipping region
4281             dc.DestroyClippingRegion();
4282 
4283             DoDrawLineWrapMarks(dc, rectUpdate);
4284         }
4285     }
4286 
4287     // show caret first time only: we must show it after drawing the text or
4288     // the display can be corrupted when it's hidden
4289     if ( !m_hasCaret && GetCaret() && (FindFocus() == this) )
4290     {
4291         ShowCaret();
4292 
4293         m_hasCaret = true;
4294     }
4295 }
4296 
4297 // ----------------------------------------------------------------------------
4298 // caret
4299 // ----------------------------------------------------------------------------
4300 
SetFont(const wxFont & font)4301 bool wxTextCtrl::SetFont(const wxFont& font)
4302 {
4303     if ( !wxControl::SetFont(font) )
4304         return false;
4305 
4306     // and refresh everything, of course
4307     InitInsertionPoint();
4308     ClearSelection();
4309 
4310     // update geometry parameters
4311     UpdateTextRect();
4312     RecalcFontMetrics();
4313     if ( !IsSingleLine() )
4314     {
4315         UpdateScrollbars();
4316         RecalcMaxWidth();
4317     }
4318 
4319     // recreate it, in fact
4320     CreateCaret();
4321 
4322     Refresh();
4323 
4324     return true;
4325 }
4326 
Enable(bool enable)4327 bool wxTextCtrl::Enable(bool enable)
4328 {
4329     if ( !wxTextCtrlBase::Enable(enable) )
4330         return false;
4331 
4332     if (FindFocus() == this && GetCaret() &&
4333         ((enable && !GetCaret()->IsVisible()) ||
4334          (!enable && GetCaret()->IsVisible())))
4335         ShowCaret(enable);
4336 
4337     return true;
4338 }
4339 
CreateCaret()4340 void wxTextCtrl::CreateCaret()
4341 {
4342     wxCaret *caret;
4343 
4344     if ( IsEditable() )
4345     {
4346         // FIXME use renderer
4347         caret = new wxCaret(this, 1, GetLineHeight());
4348     }
4349     else
4350     {
4351         // read only controls don't have the caret
4352         caret = NULL;
4353     }
4354 
4355     // SetCaret() will delete the old caret if any
4356     SetCaret(caret);
4357 }
4358 
ShowCaret(bool show)4359 void wxTextCtrl::ShowCaret(bool show)
4360 {
4361     wxCaret *caret = GetCaret();
4362     if ( caret )
4363     {
4364         // (re)position caret correctly
4365         caret->Move(GetCaretPosition());
4366 
4367         // and show it there
4368         if ((show && !caret->IsVisible()) ||
4369             (!show && caret->IsVisible()))
4370             caret->Show(show);
4371     }
4372 }
4373 
4374 // ----------------------------------------------------------------------------
4375 // vertical scrolling (multiline only)
4376 // ----------------------------------------------------------------------------
4377 
GetLinesPerPage() const4378 size_t wxTextCtrl::GetLinesPerPage() const
4379 {
4380     if ( IsSingleLine() )
4381         return 1;
4382 
4383     return GetRealTextArea().height / GetLineHeight();
4384 }
4385 
GetPositionAbove()4386 wxTextPos wxTextCtrl::GetPositionAbove()
4387 {
4388     wxCHECK_MSG( !IsSingleLine(), INVALID_POS_VALUE,
4389                  wxT("can't move cursor vertically in a single line control") );
4390 
4391     // move the cursor up by one ROW not by one LINE: this means that
4392     // we should really use HitTest() and not just go to the same
4393     // position in the previous line
4394     wxPoint pt = GetCaretPosition() - m_rectText.GetPosition();
4395     if ( MData().m_xCaret == -1 )
4396     {
4397         // remember the initial cursor abscissa
4398         MData().m_xCaret = pt.x;
4399     }
4400     else
4401     {
4402         // use the remembered abscissa
4403         pt.x = MData().m_xCaret;
4404     }
4405 
4406     CalcUnscrolledPosition(pt.x, pt.y, &pt.x, &pt.y);
4407     pt.y -= GetLineHeight();
4408 
4409     wxTextCoord col, row;
4410     if ( HitTestLogical(pt, &col, &row) == wxTE_HT_BEFORE )
4411     {
4412         // can't move further
4413         return INVALID_POS_VALUE;
4414     }
4415 
4416     return XYToPosition(col, row);
4417 }
4418 
GetPositionBelow()4419 wxTextPos wxTextCtrl::GetPositionBelow()
4420 {
4421     wxCHECK_MSG( !IsSingleLine(), INVALID_POS_VALUE,
4422                  wxT("can't move cursor vertically in a single line control") );
4423 
4424     // see comments for wxACTION_TEXT_UP
4425     wxPoint pt = GetCaretPosition() - m_rectText.GetPosition();
4426     if ( MData().m_xCaret == -1 )
4427     {
4428         // remember the initial cursor abscissa
4429         MData().m_xCaret = pt.x;
4430     }
4431     else
4432     {
4433         // use the remembered abscissa
4434         pt.x = MData().m_xCaret;
4435     }
4436 
4437     CalcUnscrolledPosition(pt.x, pt.y, &pt.x, &pt.y);
4438     pt.y += GetLineHeight();
4439 
4440     wxTextCoord col, row;
4441     if ( HitTestLogical(pt, &col, &row) == wxTE_HT_BELOW )
4442     {
4443         // can't go further down
4444         return INVALID_POS_VALUE;
4445     }
4446 
4447     // note that wxTE_HT_BEYOND is ok: it happens when we go down
4448     // from a longer line to a shorter one, for example (OTOH
4449     // wxTE_HT_BEFORE can never happen)
4450     return XYToPosition(col, row);
4451 }
4452 
4453 // ----------------------------------------------------------------------------
4454 // input
4455 // ----------------------------------------------------------------------------
4456 
PerformAction(const wxControlAction & actionOrig,long numArg,const wxString & strArg)4457 bool wxTextCtrl::PerformAction(const wxControlAction& actionOrig,
4458                                long numArg,
4459                                const wxString& strArg)
4460 {
4461     // the remembered cursor abscissa for multiline text controls is usually
4462     // reset after each user action but for ones which do use it (UP and DOWN
4463     // for example) we shouldn't do it - as indicated by this flag
4464     bool rememberAbscissa = false;
4465 
4466     // the command this action corresponds to or NULL if this action doesn't
4467     // change text at all or can't be undone
4468     wxTextCtrlCommand *command = NULL;
4469 
4470     wxString action;
4471     bool del = false,
4472          sel = false;
4473     if ( actionOrig.StartsWith(wxACTION_TEXT_PREFIX_DEL, &action) )
4474     {
4475         if ( IsEditable() )
4476             del = true;
4477     }
4478     else if ( actionOrig.StartsWith(wxACTION_TEXT_PREFIX_SEL, &action) )
4479     {
4480         sel = true;
4481     }
4482     else // not selection nor delete action
4483     {
4484         action = actionOrig;
4485     }
4486 
4487     // set newPos to -2 as it can't become equal to it in the assignments below
4488     // (but it can become -1)
4489     wxTextPos newPos = INVALID_POS_VALUE;
4490 
4491     if ( action == wxACTION_TEXT_HOME )
4492     {
4493         newPos = m_curPos - m_curCol;
4494     }
4495     else if ( action == wxACTION_TEXT_END )
4496     {
4497         newPos = m_curPos + GetLineLength(m_curRow) - m_curCol;
4498     }
4499     else if ( (action == wxACTION_TEXT_GOTO) ||
4500               (action == wxACTION_TEXT_FIRST) ||
4501               (action == wxACTION_TEXT_LAST) )
4502     {
4503         if ( action == wxACTION_TEXT_FIRST )
4504             numArg = 0;
4505         else if ( action == wxACTION_TEXT_LAST )
4506             numArg = GetLastPosition();
4507         //else: numArg already contains the position
4508 
4509         newPos = numArg;
4510     }
4511     else if ( action == wxACTION_TEXT_UP )
4512     {
4513         if ( !IsSingleLine() )
4514         {
4515             newPos = GetPositionAbove();
4516 
4517             if ( newPos != INVALID_POS_VALUE )
4518             {
4519                 // remember where the cursor original had been
4520                 rememberAbscissa = true;
4521             }
4522         }
4523     }
4524     else if ( action == wxACTION_TEXT_DOWN )
4525     {
4526         if ( !IsSingleLine() )
4527         {
4528             newPos = GetPositionBelow();
4529 
4530             if ( newPos != INVALID_POS_VALUE )
4531             {
4532                 // remember where the cursor original had been
4533                 rememberAbscissa = true;
4534             }
4535         }
4536     }
4537     else if ( action == wxACTION_TEXT_LEFT )
4538     {
4539         newPos = m_curPos - 1;
4540     }
4541     else if ( action == wxACTION_TEXT_WORD_LEFT )
4542     {
4543         newPos = GetWordStart();
4544     }
4545     else if ( action == wxACTION_TEXT_RIGHT )
4546     {
4547         newPos = m_curPos + 1;
4548     }
4549     else if ( action == wxACTION_TEXT_WORD_RIGHT )
4550     {
4551         newPos = GetWordEnd();
4552     }
4553     else if ( action == wxACTION_TEXT_INSERT )
4554     {
4555         if ( IsEditable() && !strArg.empty() )
4556         {
4557             bool isTextClipped = m_maxLength && (m_value.length() + strArg.length() > m_maxLength);
4558             const wxString& acceptedString = isTextClipped ? strArg.Left(m_maxLength - m_value.length())
4559                                                         : strArg;
4560 
4561             if ( isTextClipped ) // text was clipped so emit wxEVT_TEXT_MAXLEN
4562             {
4563                 wxCommandEvent event(wxEVT_TEXT_MAXLEN, m_windowId);
4564                 InitCommandEvent(event);
4565                 GetEventHandler()->ProcessEvent(event);
4566             }
4567 
4568             if ( !acceptedString.empty() )
4569                 // inserting text can be undone
4570                 command = new wxTextCtrlInsertCommand(acceptedString);
4571         }
4572 
4573     }
4574     else if ( (action == wxACTION_TEXT_PAGE_UP) ||
4575               (action == wxACTION_TEXT_PAGE_DOWN) )
4576     {
4577         if ( !IsSingleLine() )
4578         {
4579             size_t count = GetLinesPerPage();
4580             if ( count > PAGE_OVERLAP_IN_LINES )
4581             {
4582                 // pages should overlap slightly to allow the reader to keep
4583                 // orientation in the text
4584                 count -= PAGE_OVERLAP_IN_LINES;
4585             }
4586 
4587             // remember where the cursor original had been
4588             rememberAbscissa = true;
4589 
4590             bool goUp = action == wxACTION_TEXT_PAGE_UP;
4591             for ( size_t line = 0; line < count; line++ )
4592             {
4593                 wxTextPos pos = goUp ? GetPositionAbove() : GetPositionBelow();
4594                 if ( pos == INVALID_POS_VALUE )
4595                 {
4596                     // can't move further
4597                     break;
4598                 }
4599 
4600                 MoveInsertionPoint(pos);
4601                 newPos = pos;
4602             }
4603 
4604             // we implement the Unix scrolling model here: cursor will always
4605             // be on the first line after Page Down and on the last one after
4606             // Page Up
4607             //
4608             // Windows programs usually keep the cursor line offset constant
4609             // but do we really need it?
4610             wxCoord y;
4611             if ( goUp )
4612             {
4613                 // find the line such that when it is the first one, the
4614                 // current position is in the last line
4615                 wxTextPos pos = 0;
4616                 for ( size_t line = 0; line < count; line++ )
4617                 {
4618                     pos = GetPositionAbove();
4619                     if ( pos == INVALID_POS_VALUE )
4620                         break;
4621 
4622                     MoveInsertionPoint(pos);
4623                 }
4624 
4625                 MoveInsertionPoint(newPos);
4626 
4627                 PositionToLogicalXY(pos, NULL, &y);
4628             }
4629             else // scrolled down
4630             {
4631                 PositionToLogicalXY(newPos, NULL, &y);
4632             }
4633 
4634             // scroll vertically only
4635             Scroll(wxDefaultCoord, y);
4636         }
4637     }
4638     else if ( action == wxACTION_TEXT_SEL_WORD )
4639     {
4640         SetSelection(GetWordStart(), GetWordEnd());
4641     }
4642     else if ( action == wxACTION_TEXT_ANCHOR_SEL )
4643     {
4644         newPos = numArg;
4645     }
4646     else if ( action == wxACTION_TEXT_EXTEND_SEL )
4647     {
4648         SetSelection(m_selAnchor, numArg);
4649     }
4650     else if ( action == wxACTION_TEXT_COPY )
4651     {
4652         Copy();
4653     }
4654     else if ( action == wxACTION_TEXT_CUT )
4655     {
4656         if ( IsEditable() )
4657             Cut();
4658     }
4659     else if ( action == wxACTION_TEXT_PASTE )
4660     {
4661         if ( IsEditable() )
4662             Paste();
4663     }
4664     else if ( action == wxACTION_TEXT_UNDO )
4665     {
4666         if ( CanUndo() )
4667             Undo();
4668     }
4669     else if ( action == wxACTION_TEXT_REDO )
4670     {
4671         if ( CanRedo() )
4672             Redo();
4673     }
4674     else
4675     {
4676         return wxControl::PerformAction(action, numArg, strArg);
4677     }
4678 
4679     if ( newPos != INVALID_POS_VALUE )
4680     {
4681         // bring the new position into the range
4682         if ( newPos < 0 )
4683             newPos = 0;
4684 
4685         wxTextPos posLast = GetLastPosition();
4686         if ( newPos > posLast )
4687             newPos = posLast;
4688 
4689         if ( del )
4690         {
4691             // if we have the selection, remove just it
4692             wxTextPos from, to;
4693             if ( HasSelection() )
4694             {
4695                 from = m_selStart;
4696                 to = m_selEnd;
4697             }
4698             else
4699             {
4700                 // otherwise delete everything between current position and
4701                 // the new one
4702                 if ( m_curPos != newPos )
4703                 {
4704                     from = m_curPos;
4705                     to = newPos;
4706                 }
4707                 else // nothing to delete
4708                 {
4709                     // prevent test below from working
4710                     from = INVALID_POS_VALUE;
4711 
4712                     // and this is just to silent the compiler warning
4713                     to = 0;
4714                 }
4715             }
4716 
4717             if ( from != INVALID_POS_VALUE )
4718             {
4719                 command = new wxTextCtrlRemoveCommand(from, to);
4720             }
4721         }
4722         else // cursor movement command
4723         {
4724             // just go there
4725             DoSetInsertionPoint(newPos);
4726 
4727             if ( sel )
4728             {
4729                 SetSelection(m_selAnchor, m_curPos);
4730             }
4731             else // simple movement
4732             {
4733                 // clear the existing selection
4734                 ClearSelection();
4735             }
4736         }
4737 
4738         if ( !rememberAbscissa && !IsSingleLine() )
4739         {
4740             MData().m_xCaret = -1;
4741         }
4742     }
4743 
4744     // Submit call will insert text and generate wxEVT_TEXT event.
4745     if ( command )
4746     {
4747         // execute and remember it to be able to undo it later
4748         m_cmdProcessor->Submit(command);
4749         m_isModified = true;
4750     }
4751 
4752     return true;
4753 }
4754 
OnChar(wxKeyEvent & event)4755 void wxTextCtrl::OnChar(wxKeyEvent& event)
4756 {
4757     // only process the key events from "simple keys" here
4758     if ( !event.HasModifiers() )
4759     {
4760         int keycode = event.GetKeyCode();
4761 #if wxUSE_UNICODE
4762         wxChar unicode = event.GetUnicodeKey();
4763 #endif
4764         if ( keycode == WXK_RETURN )
4765         {
4766             if ( IsSingleLine() || (GetWindowStyle() & wxTE_PROCESS_ENTER) )
4767             {
4768                 wxCommandEvent event(wxEVT_TEXT_ENTER, GetId());
4769                 InitCommandEvent(event);
4770                 event.SetString(GetValue());
4771                 GetEventHandler()->ProcessEvent(event);
4772             }
4773             else // interpret <Enter> normally: insert new line
4774             {
4775                 PerformAction(wxACTION_TEXT_INSERT, -1, wxT('\n'));
4776             }
4777         }
4778         else if ( keycode < 255 && isprint(keycode) )
4779         {
4780             PerformAction(wxACTION_TEXT_INSERT, -1, (wxChar)keycode);
4781 
4782             // skip event.Skip() below
4783             return;
4784         }
4785 #if wxUSE_UNICODE
4786         else if (unicode > 0)
4787         {
4788             PerformAction(wxACTION_TEXT_INSERT, -1, unicode);
4789 
4790             return;
4791         }
4792 #endif
4793     }
4794 #if wxDEBUG_LEVEL >= 2
4795     // Ctrl-R refreshes the control in debug mode
4796     else if ( event.ControlDown() && event.GetKeyCode() == 'r' )
4797         Refresh();
4798 #endif // wxDEBUG_LEVEL >= 2
4799 
4800     event.Skip();
4801 }
4802 
4803 /* static */
GetStdInputHandler(wxInputHandler * handlerDef)4804 wxInputHandler *wxTextCtrl::GetStdInputHandler(wxInputHandler *handlerDef)
4805 {
4806     static wxStdTextCtrlInputHandler s_handler(handlerDef);
4807 
4808     return &s_handler;
4809 }
4810 
4811 // ----------------------------------------------------------------------------
4812 // wxStdTextCtrlInputHandler
4813 // ----------------------------------------------------------------------------
4814 
wxStdTextCtrlInputHandler(wxInputHandler * inphand)4815 wxStdTextCtrlInputHandler::wxStdTextCtrlInputHandler(wxInputHandler *inphand)
4816                          : wxStdInputHandler(inphand)
4817 {
4818     m_winCapture = NULL;
4819 }
4820 
4821 /* static */
HitTest(const wxTextCtrl * text,const wxPoint & pt)4822 wxTextPos wxStdTextCtrlInputHandler::HitTest(const wxTextCtrl *text,
4823                                              const wxPoint& pt)
4824 {
4825     wxTextCoord col, row;
4826     wxTextCtrlHitTestResult ht = text->HitTest(pt, &col, &row);
4827 
4828     wxTextPos pos = text->XYToPosition(col, row);
4829 
4830     // if the point is after the last column we must adjust the position to be
4831     // the last position in the line (unless it is already the last)
4832     if ( (ht == wxTE_HT_BEYOND) && (pos < text->GetLastPosition()) )
4833     {
4834         pos++;
4835     }
4836 
4837     return pos;
4838 }
4839 
HandleKey(wxInputConsumer * consumer,const wxKeyEvent & event,bool pressed)4840 bool wxStdTextCtrlInputHandler::HandleKey(wxInputConsumer *consumer,
4841                                           const wxKeyEvent& event,
4842                                           bool pressed)
4843 {
4844     // we're only interested in key presses
4845     if ( !pressed )
4846         return false;
4847 
4848     int keycode = event.GetKeyCode();
4849 
4850     wxControlAction action;
4851     wxString str;
4852     bool ctrlDown = event.ControlDown(),
4853          shiftDown = event.ShiftDown();
4854     if ( shiftDown )
4855     {
4856         action = wxACTION_TEXT_PREFIX_SEL;
4857     }
4858 
4859     // the only key combination with Alt we recognize is Alt-Bksp for undo, so
4860     // treat it first separately
4861     if ( event.AltDown() )
4862     {
4863         if ( keycode == WXK_BACK && !ctrlDown && !shiftDown )
4864             action = wxACTION_TEXT_UNDO;
4865     }
4866     else switch ( keycode )
4867     {
4868         // cursor movement
4869         case WXK_HOME:
4870             action << (ctrlDown ? wxACTION_TEXT_FIRST
4871                                 : wxACTION_TEXT_HOME);
4872             break;
4873 
4874         case WXK_END:
4875             action << (ctrlDown ? wxACTION_TEXT_LAST
4876                                 : wxACTION_TEXT_END);
4877             break;
4878 
4879         case WXK_UP:
4880             if ( !ctrlDown )
4881                 action << wxACTION_TEXT_UP;
4882             break;
4883 
4884         case WXK_DOWN:
4885             if ( !ctrlDown )
4886                 action << wxACTION_TEXT_DOWN;
4887             break;
4888 
4889         case WXK_LEFT:
4890             action << (ctrlDown ? wxACTION_TEXT_WORD_LEFT
4891                                 : wxACTION_TEXT_LEFT);
4892             break;
4893 
4894         case WXK_RIGHT:
4895             action << (ctrlDown ? wxACTION_TEXT_WORD_RIGHT
4896                                 : wxACTION_TEXT_RIGHT);
4897             break;
4898 
4899         case WXK_PAGEDOWN:
4900             // we don't map Ctrl-PgUp/Dn to anything special - what should it
4901             // to? for now, it's the same as without control
4902             action << wxACTION_TEXT_PAGE_DOWN;
4903             break;
4904 
4905         case WXK_PAGEUP:
4906             action << wxACTION_TEXT_PAGE_UP;
4907             break;
4908 
4909         // delete
4910         case WXK_DELETE:
4911             if ( !ctrlDown )
4912                 action << wxACTION_TEXT_PREFIX_DEL << wxACTION_TEXT_RIGHT;
4913             break;
4914 
4915         case WXK_BACK:
4916             if ( !ctrlDown )
4917                 action << wxACTION_TEXT_PREFIX_DEL << wxACTION_TEXT_LEFT;
4918             break;
4919 
4920         // something else
4921         default:
4922             // reset the action as it could be already set to one of the
4923             // prefixes
4924             action = wxACTION_NONE;
4925 
4926             if ( ctrlDown )
4927             {
4928                 switch ( keycode )
4929                 {
4930                     case 'A':
4931                         action = wxACTION_TEXT_REDO;
4932                         break;
4933 
4934                     case 'C':
4935                         action = wxACTION_TEXT_COPY;
4936                         break;
4937 
4938                     case 'V':
4939                         action = wxACTION_TEXT_PASTE;
4940                         break;
4941 
4942                     case 'X':
4943                         action = wxACTION_TEXT_CUT;
4944                         break;
4945 
4946                     case 'Z':
4947                         action = wxACTION_TEXT_UNDO;
4948                         break;
4949                 }
4950             }
4951     }
4952 
4953     if ( (action != wxACTION_NONE) && (action != wxACTION_TEXT_PREFIX_SEL) )
4954     {
4955         consumer->PerformAction(action, -1, str);
4956 
4957         // the key down of WXK_UP/DOWN and WXK_PAGEUP/DOWN
4958         // must generate a wxEVT_TEXT event. For the controls
4959         // that use wxTextCtrl, such as wxSpinCtrl
4960         if ( (action != wxACTION_TEXT_UP) &
4961              (action != wxACTION_TEXT_DOWN) &
4962              (action != wxACTION_TEXT_PAGE_DOWN) &
4963              (action != wxACTION_TEXT_PAGE_UP) )
4964             return true;
4965     }
4966 
4967     return wxStdInputHandler::HandleKey(consumer, event, pressed);
4968 }
4969 
HandleMouse(wxInputConsumer * consumer,const wxMouseEvent & event)4970 bool wxStdTextCtrlInputHandler::HandleMouse(wxInputConsumer *consumer,
4971                                             const wxMouseEvent& event)
4972 {
4973     if ( event.LeftDown() )
4974     {
4975         wxASSERT_MSG( !m_winCapture, wxT("left button going down twice?") );
4976 
4977         wxTextCtrl *text = wxStaticCast(consumer->GetInputWindow(), wxTextCtrl);
4978 
4979         m_winCapture = text;
4980         m_winCapture->CaptureMouse();
4981 
4982         text->HideCaret();
4983 
4984         wxTextPos pos = HitTest(text, event.GetPosition());
4985         if ( pos != -1 )
4986         {
4987             text->PerformAction(wxACTION_TEXT_ANCHOR_SEL, pos);
4988         }
4989     }
4990     else if ( event.LeftDClick() )
4991     {
4992         // select the word the cursor is on
4993         consumer->PerformAction(wxACTION_TEXT_SEL_WORD);
4994     }
4995     else if ( event.LeftUp() )
4996     {
4997         if ( m_winCapture )
4998         {
4999             m_winCapture->ShowCaret();
5000 
5001             m_winCapture->ReleaseMouse();
5002             m_winCapture = NULL;
5003         }
5004     }
5005 
5006     return wxStdInputHandler::HandleMouse(consumer, event);
5007 }
5008 
HandleMouseMove(wxInputConsumer * consumer,const wxMouseEvent & event)5009 bool wxStdTextCtrlInputHandler::HandleMouseMove(wxInputConsumer *consumer,
5010                                                 const wxMouseEvent& event)
5011 {
5012     if ( m_winCapture )
5013     {
5014         // track it
5015         wxTextCtrl *text = wxStaticCast(m_winCapture, wxTextCtrl);
5016         wxTextPos pos = HitTest(text, event.GetPosition());
5017         if ( pos != -1 )
5018         {
5019             text->PerformAction(wxACTION_TEXT_EXTEND_SEL, pos);
5020         }
5021     }
5022 
5023     return wxStdInputHandler::HandleMouseMove(consumer, event);
5024 }
5025 
5026 bool
HandleFocus(wxInputConsumer * consumer,const wxFocusEvent & event)5027 wxStdTextCtrlInputHandler::HandleFocus(wxInputConsumer *consumer,
5028                                        const wxFocusEvent& event)
5029 {
5030     wxTextCtrl *text = wxStaticCast(consumer->GetInputWindow(), wxTextCtrl);
5031 
5032     // the selection appearance changes depending on whether we have the focus
5033     text->RefreshSelection();
5034 
5035     if (event.GetEventType() == wxEVT_SET_FOCUS)
5036     {
5037         if (text->GetCaret() && !text->GetCaret()->IsVisible())
5038             text->ShowCaret();
5039     }
5040     else
5041     {
5042         if (text->GetCaret() && text->GetCaret()->IsVisible())
5043             text->HideCaret();
5044     }
5045 
5046     // never refresh entirely
5047     return false;
5048 }
5049 
5050 #endif // wxUSE_TEXTCTRL
5051