1 /////////////////////////////////////////////////////////////////////////////
2 // Name: univ/textctrl.cpp
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 2000 SciTech Software, Inc. (www.scitechsoft.com)
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
15 + 1. update vert scrollbar when any line length changes for WrapLines()
16 + 2. cursor movement ("Hello,^" -> "^verse!" on Arrow Down)?
17 -> maybe save the x position and use it instead of current in handling
18 DOWN/UP actions (this would make up/down always return the cursor to
20 3. split file into chunks
21 +? 4. rewrite Replace() refresh logic to deal with wrapping lines
22 +? 5. cache info found by GetPartOfWrappedLine() - performance must be horrible
25 6. backspace refreshes too much (until end of line)
29 Optimisation hints from PureQuantify:
31 +1. wxStringTokenize is the slowest part of Replace
32 2. GetDC/ReleaseDC are very slow, avoid calling them several times
33 +3. GetCharHeight() should be cached too
34 4. wxClientDC construction/destruction in HitTestLine is horribly expensive
36 For line wrapping controls HitTest2 takes 50% of program time. The results
37 of GetRowsPerLine and GetPartOfWrappedLine *MUST* be cached.
39 Search for "OPT!" for things which must be optimized.
45 Everywhere in this file LINE refers to a logical line of text, and ROW to a
46 physical line of text on the display. They are the same unless WrapLines()
47 is TRUE in which case a single LINE may correspond to multiple ROWs.
49 A text position is an unsigned int (which for reasons of compatibility is
50 still a long) from 0 to GetLastPosition() inclusive. The positions
51 correspond to the gaps between the letters so the position 0 is just
52 before the first character and the last position is the one beyond the last
53 character. For an empty text control GetLastPosition() returns 0.
55 Lines and columns returned/accepted by XYToPosition() and PositionToXY()
56 start from 0. The y coordinate is a LINE, not a ROW. Columns correspond to
57 the characters, the first column of a line is the first character in it,
58 the last one is length(line text). For compatibility, again, lines and
59 columns are also longs.
61 When translating lines/column coordinates to/from positions, the line and
62 column give the character after the given position. Thus, GetLastPosition()
63 doesn't have any corresponding column.
65 An example of positions and lines/columns for a control without wrapping
66 containing the text "Hello, Universe!\nGoodbye"
69 pos: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
70 H e l l o , U n i v e r s e ! line 0
71 col: 0 1 2 3 4 5 6 7 8 9 1 1 1 1 1 1
80 The same example for a control with line wrap assuming "Universe" is too
81 long to fit on the same line with "Hello,":
84 H e l l o , line 0 (row 0)
88 pos: 6 7 8 9 0 1 2 3 4 5 6
89 U n i v e r s e ! line 0 (row 1)
90 col: 6 7 8 9 1 1 1 1 1 1
93 (line 1 == row 2 same as above)
95 Note that there is still the same number of columns and positions and that
96 there is no (logical) position at the end of the first ROW. This position
97 is identified with the preceding one (which is not how Windows does it: it
98 identifies it with the next one, i.e. the first position of the next line,
99 but much more logical IMHO).
103 Search for "OPT" for possible optimizations
105 A possible global optimization would be to always store the coords in the
106 text in triplets (pos, col, line) and update them simultaneously instead of
107 recalculating col and line from pos each time it is needed. Currently we
108 only do it for the current position but we might also do it for the
109 selection start and end.
112 // ============================================================================
114 // ============================================================================
116 // ----------------------------------------------------------------------------
118 // ----------------------------------------------------------------------------
121 #pragma implementation "univtextctrl.h"
124 #include "wx/wxprec.h"
135 #include "wx/dcclient.h"
136 #include "wx/validate.h"
137 #include "wx/textctrl.h"
140 #include "wx/clipbrd.h"
142 #include "wx/textfile.h"
144 #include "wx/caret.h"
146 #include "wx/univ/inphand.h"
147 #include "wx/univ/renderer.h"
148 #include "wx/univ/colschem.h"
149 #include "wx/univ/theme.h"
151 #include "wx/cmdproc.h"
154 #include "wx/dataobj.h"
157 // turn extra wxTextCtrl-specific debugging on/off
160 // turn wxTextCtrl::Replace() debugging on (slows down code a *lot*!)
161 #define WXDEBUG_TEXT_REPLACE
165 #undef WXDEBUG_TEXT_REPLACE
168 // wxStringTokenize only needed for debug checks
169 #ifdef WXDEBUG_TEXT_REPLACE
170 #include "wx/tokenzr.h"
171 #endif // WXDEBUG_TEXT_REPLACE
173 // ----------------------------------------------------------------------------
175 // ----------------------------------------------------------------------------
177 // exchange two positions so that from is always less than or equal to to
178 static inline void OrderPositions(wxTextPos
& from
, wxTextPos
& to
)
182 wxTextPos tmp
= from
;
188 // ----------------------------------------------------------------------------
190 // ----------------------------------------------------------------------------
192 // names of text ctrl commands
193 #define wxTEXT_COMMAND_INSERT _T("insert")
194 #define wxTEXT_COMMAND_REMOVE _T("remove")
196 // the value which is never used for text position, even not -1 which is
197 // sometimes used for some special meaning
198 static const wxTextPos INVALID_POS_VALUE
= -2;
200 // overlap between pages (when using PageUp/Dn) in lines
201 static const size_t PAGE_OVERLAP_IN_LINES
= 1;
203 // ----------------------------------------------------------------------------
204 // private data of wxTextCtrl
205 // ----------------------------------------------------------------------------
207 // the data only used by single line text controls
208 struct WXDLLEXPORT wxTextSingleLineData
210 // the position of the first visible pixel and the first visible column
212 wxTextCoord m_colStart
;
214 // and the last ones (m_posLastVisible is the width but m_colLastVisible
215 // is an absolute value)
216 wxCoord m_posLastVisible
;
217 wxTextCoord m_colLastVisible
;
220 wxTextSingleLineData()
225 m_colLastVisible
= -1;
226 m_posLastVisible
= -1;
231 // the data only used by multi line text controls
232 struct WXDLLEXPORT wxTextMultiLineData
235 wxArrayString m_lines
;
237 // the current ranges of the scrollbars
241 // should we adjust the horz/vert scrollbar?
242 bool m_updateScrollbarX
,
245 // the max line length in pixels
248 // the index of the line which has the length of m_widthMax
249 wxTextCoord m_lineLongest
;
251 // the rect in which text appears: it is even less than m_rectText because
252 // only the last _complete_ line is shown, hence there is an unoccupied
253 // horizontal band at the bottom of it
254 wxRect m_rectTextReal
;
256 // the x-coordinate of the caret before we started moving it vertically:
257 // this is used to ensure that moving the caret up and then down will
258 // return it to the same position as if we always round it in one direction
259 // we would shift it in that direction
261 // when m_xCaret == -1, we don't have any remembered position
265 wxTextMultiLineData()
271 m_updateScrollbarY
= FALSE
;
280 // the data only used by multi line text controls in line wrap mode
281 class WXDLLEXPORT wxWrappedLineData
283 // these functions set all our values, so give them access to them
284 friend void wxTextCtrl::LayoutLine(wxTextCoord line
,
285 wxWrappedLineData
& lineData
) const;
286 friend void wxTextCtrl::LayoutLines(wxTextCoord
) const;
295 // get the start of any row (remember that accessing m_rowsStart doesn't work
296 // for the first one)
297 wxTextCoord
GetRowStart(wxTextCoord row
) const
299 wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
301 return row
? m_rowsStart
[row
- 1] : 0;
304 // get the length of the row (using the total line length which we don't
305 // have here but need to calculate the length of the last row, so it must
307 wxTextCoord
GetRowLength(wxTextCoord row
, wxTextCoord lenLine
) const
309 wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
311 // note that m_rowsStart[row] is the same as GetRowStart(row + 1) (but
312 // slightly more efficient) and lenLine is the same as the start of the
313 // first row of the next line
314 return ((size_t)row
== m_rowsStart
.GetCount() ? lenLine
: m_rowsStart
[row
])
318 // return the width of the row in pixels
319 wxCoord
GetRowWidth(wxTextCoord row
) const
321 wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
323 return m_rowsWidth
[row
];
326 // return the number of rows
327 size_t GetRowCount() const
329 wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
331 return m_rowsStart
.GetCount() + 1;
334 // return the number of additional (i.e. after the first one) rows
335 size_t GetExtraRowCount() const
337 wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
339 return m_rowsStart
.GetCount();
342 // return the first row of this line
343 wxTextCoord
GetFirstRow() const
345 wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
350 // return the first row of the next line
351 wxTextCoord
GetNextRow() const
353 wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
355 return m_rowFirst
+ m_rowsStart
.GetCount() + 1;
358 // this just provides direct access to m_rowsStart aerray for efficiency
359 wxTextCoord
GetExtraRowStart(wxTextCoord row
) const
361 wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
363 return m_rowsStart
[row
];
366 // this code is unused any longer
368 // return TRUE if the column is in the start of the last row (hence the row
369 // it is in is not wrapped)
370 bool IsLastRow(wxTextCoord colRowStart
) const
372 return colRowStart
== GetRowStart(m_rowsStart
.GetCount());
375 // return TRUE if the column is the last column of the row starting in
377 bool IsLastColInRow(wxTextCoord colRowStart
,
378 wxTextCoord colRowEnd
,
379 wxTextCoord lenLine
) const
381 // find the row which starts with colRowStart
382 size_t nRows
= GetRowCount();
383 for ( size_t n
= 0; n
< nRows
; n
++ )
385 if ( GetRowStart(n
) == colRowStart
)
387 wxTextCoord colNextRowStart
= n
== nRows
- 1
389 : GetRowStart(n
+ 1);
391 wxASSERT_MSG( colRowEnd
< colNextRowStart
,
392 _T("this column is not in this row at all!") );
394 return colRowEnd
== colNextRowStart
- 1;
398 // caller got it wrong
399 wxFAIL_MSG( _T("this column is not in the start of the row!") );
405 // is this row the last one in its line?
406 bool IsLastRow(wxTextCoord row
) const
408 return (size_t)row
== GetExtraRowCount();
411 // the line is valid if it had been laid out correctly: note that just
412 // shiwting the line (because one of previous lines changed) doesn't make
414 bool IsValid() const { return !m_rowsWidth
.IsEmpty(); }
416 // invalidating line will relayout it
417 void Invalidate() { m_rowsWidth
.Empty(); }
420 // for each line we remember the starting columns of all its rows after the
421 // first one (which always starts at 0), i.e. if a line is wrapped twice
422 // (== takes 3 rows) its m_rowsStart[0] may be 10 and m_rowsStart[1] == 15
423 wxArrayLong m_rowsStart
;
425 // and the width of each row in pixels (this array starts from 0, as usual)
426 wxArrayInt m_rowsWidth
;
428 // and also its starting row (0 for the first line, first lines'
429 // m_rowsStart.GetCount() + 1 for the second &c): it is set to -1 initially
430 // and this means that the struct hadn't yet been initialized
431 wxTextCoord m_rowFirst
;
433 // the last modification "time"-stamp used by LayoutLines()
437 WX_DECLARE_OBJARRAY(wxWrappedLineData
, wxArrayWrappedLinesData
);
438 #include "wx/arrimpl.cpp"
439 WX_DEFINE_OBJARRAY(wxArrayWrappedLinesData
);
441 struct WXDLLEXPORT wxTextWrappedData
: public wxTextMultiLineData
443 // the width of the column to the right of the text rect used for the
444 // indicator mark display for the wrapped lines
447 // the data for each line
448 wxArrayWrappedLinesData m_linesData
;
450 // flag telling us to recalculate all starting rows starting from this line
451 // (if it is -1, we don't have to recalculate anything) - it is set when
452 // the number of the rows in the middle of the control changes
453 wxTextCoord m_rowFirstInvalid
;
455 // the current timestamp used by LayoutLines()
458 // invalidate starting rows of all lines (NOT rows!) after this one
459 void InvalidateLinesBelow(wxTextCoord line
)
461 if ( m_rowFirstInvalid
== -1 || m_rowFirstInvalid
> line
)
463 m_rowFirstInvalid
= line
;
467 // check if this line is valid: i.e. before the first invalid one
468 bool IsValidLine(wxTextCoord line
) const
470 return ((m_rowFirstInvalid
== -1) || (line
< m_rowFirstInvalid
)) &&
471 m_linesData
[line
].IsValid();
478 m_rowFirstInvalid
= -1;
483 // ----------------------------------------------------------------------------
484 // private classes for undo/redo management
485 // ----------------------------------------------------------------------------
488 We use custom versions of wxWindows command processor to implement undo/redo
489 as we want to avoid storing the backpointer to wxTextCtrl in wxCommand
490 itself: this is a waste of memory as all commands in the given command
491 processor always have the same associated wxTextCtrl and so it makes sense
492 to store the backpointer there.
494 As for the rest of the implementation, it's fairly standard: we have 2
495 command classes corresponding to adding and removing text.
498 // a command corresponding to a wxTextCtrl action
499 class wxTextCtrlCommand
: public wxCommand
502 wxTextCtrlCommand(const wxString
& name
) : wxCommand(TRUE
, name
) { }
504 // we don't use these methods as they don't make sense for us as we need a
505 // wxTextCtrl to be applied
506 virtual bool Do() { wxFAIL_MSG(_T("shouldn't be called")); return FALSE
; }
507 virtual bool Undo() { wxFAIL_MSG(_T("shouldn't be called")); return FALSE
; }
509 // instead, our command processor uses these methods
510 virtual bool Do(wxTextCtrl
*text
) = 0;
511 virtual bool Undo(wxTextCtrl
*text
) = 0;
514 // insert text command
515 class wxTextCtrlInsertCommand
: public wxTextCtrlCommand
518 wxTextCtrlInsertCommand(const wxString
& textToInsert
)
519 : wxTextCtrlCommand(wxTEXT_COMMAND_INSERT
), m_text(textToInsert
)
524 // combine the 2 commands together
525 void Append(wxTextCtrlInsertCommand
*other
);
527 virtual bool CanUndo() const;
528 virtual bool Do(wxTextCtrl
*text
);
529 virtual bool Undo(wxTextCtrl
*text
);
532 // the text we insert
535 // the position where we inserted the text
539 // remove text command
540 class wxTextCtrlRemoveCommand
: public wxTextCtrlCommand
543 wxTextCtrlRemoveCommand(wxTextPos from
, wxTextPos to
)
544 : wxTextCtrlCommand(wxTEXT_COMMAND_REMOVE
)
550 virtual bool CanUndo() const;
551 virtual bool Do(wxTextCtrl
*text
);
552 virtual bool Undo(wxTextCtrl
*text
);
555 // the range of text to delete
559 // the text which was deleted when this command was Do()ne
560 wxString m_textDeleted
;
563 // a command processor for a wxTextCtrl
564 class wxTextCtrlCommandProcessor
: public wxCommandProcessor
567 wxTextCtrlCommandProcessor(wxTextCtrl
*text
)
569 m_compressInserts
= FALSE
;
574 // override Store() to compress multiple wxTextCtrlInsertCommand into one
575 virtual void Store(wxCommand
*command
);
577 // stop compressing insert commands when this is called
578 void StopCompressing() { m_compressInserts
= FALSE
; }
581 wxTextCtrl
*GetTextCtrl() const { return m_text
; }
582 bool IsCompressing() const { return m_compressInserts
; }
585 virtual bool DoCommand(wxCommand
& cmd
)
586 { return ((wxTextCtrlCommand
&)cmd
).Do(m_text
); }
587 virtual bool UndoCommand(wxCommand
& cmd
)
588 { return ((wxTextCtrlCommand
&)cmd
).Undo(m_text
); }
590 // check if this command is a wxTextCtrlInsertCommand and return it casted
591 // to the right type if it is or NULL otherwise
592 wxTextCtrlInsertCommand
*IsInsertCommand(wxCommand
*cmd
);
595 // the control we're associated with
598 // if the flag is TRUE we're compressing subsequent insert commands into
599 // one so that the entire typing could be undone in one call to Undo()
600 bool m_compressInserts
;
603 // ============================================================================
605 // ============================================================================
607 BEGIN_EVENT_TABLE(wxTextCtrl
, wxControl
)
608 EVT_CHAR(wxTextCtrl::OnChar
)
610 EVT_SIZE(wxTextCtrl::OnSize
)
612 EVT_IDLE(wxTextCtrl::OnIdle
)
615 IMPLEMENT_DYNAMIC_CLASS(wxTextCtrl
, wxControl
)
617 // ----------------------------------------------------------------------------
619 // ----------------------------------------------------------------------------
621 void wxTextCtrl::Init()
627 m_isModified
= FALSE
;
638 // init wxScrollHelper
641 // init the undo manager
642 m_cmdProcessor
= new wxTextCtrlCommandProcessor(this);
648 bool wxTextCtrl::Create(wxWindow
*parent
,
650 const wxString
& value
,
654 const wxValidator
& validator
,
655 const wxString
&name
)
657 if ( style
& wxTE_MULTILINE
)
659 // for compatibility with wxMSW we create the controls with vertical
660 // scrollbar always shown unless they have wxTE_RICH style (because
661 // Windows text controls always has vert scrollbar but richedit one
663 if ( !(style
& wxTE_RICH
) )
665 style
|= wxALWAYS_SHOW_SB
;
668 // wxTE_WORDWRAP is 0 for now so we don't need the code below
670 if ( style
& wxTE_WORDWRAP
)
672 // wrapping words means wrapping, hence no horz scrollbar
677 // TODO: support wxTE_NO_VSCROLL (?)
679 // create data object for normal multiline or for controls with line
681 if ( style
& wxHSCROLL
)
682 m_data
.mdata
= new wxTextMultiLineData
;
684 m_data
.wdata
= new wxTextWrappedData
;
688 // this doesn't make sense for single line controls
691 // create data object for single line controls
692 m_data
.sdata
= new wxTextSingleLineData
;
695 #if wxUSE_TWO_WINDOWS
696 if ((style
& wxBORDER_MASK
) == 0)
697 style
|= wxBORDER_SUNKEN
;
700 if ( !wxControl::Create(parent
, id
, pos
, size
, style
,
706 SetCursor(wxCURSOR_IBEAM
);
708 if ( style
& wxTE_MULTILINE
)
710 // we should always have at least one line in a multiline control
711 MData().m_lines
.Add(wxEmptyString
);
713 if ( !(style
& wxHSCROLL
) )
715 WData().m_linesData
.Add(new wxWrappedLineData
);
716 WData().InvalidateLinesBelow(0);
719 // we might support it but it's quite useless and other ports don't
721 wxASSERT_MSG( !(style
& wxTE_PASSWORD
),
722 _T("wxTE_PASSWORD can't be used with multiline ctrls") );
729 m_isEditable
= !(style
& wxTE_READONLY
);
732 InitInsertionPoint();
734 // we can't show caret right now as we're not shown yet and so it would
735 // result in garbage on the screen - we'll do it after first OnPaint()
738 CreateInputHandler(wxINP_HANDLER_TEXTCTRL
);
743 wxTextCtrl::~wxTextCtrl()
745 delete m_cmdProcessor
;
749 if ( IsSingleLine() )
751 else if ( WrapLines() )
758 // ----------------------------------------------------------------------------
760 // ----------------------------------------------------------------------------
762 void wxTextCtrl::SetValue(const wxString
& value
)
764 if ( IsSingleLine() && (value
== GetValue()) )
770 Replace(0, GetLastPosition(), value
);
772 if ( IsSingleLine() )
774 SetInsertionPoint(0);
777 // TODO: should we generate the event or not, finally?
780 const wxArrayString
& wxTextCtrl::GetLines() const
782 return MData().m_lines
;
785 size_t wxTextCtrl::GetLineCount() const
787 return MData().m_lines
.GetCount();
790 wxString
wxTextCtrl::GetValue() const
792 // for multiline controls we don't always store the total value but only
793 // recompute it when asked - and to invalidate it we just empty it in
795 if ( !IsSingleLine() && m_value
.empty() )
797 // recalculate: note that we always do it for empty multilien control,
798 // but then it's so quick that it's not important
800 // the first line is special as there is no \n before it, so it's
802 const wxArrayString
& lines
= GetLines();
803 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
804 self
->m_value
<< lines
[0u];
805 size_t count
= lines
.GetCount();
806 for ( size_t n
= 1; n
< count
; n
++ )
808 self
->m_value
<< _T('\n') << lines
[n
];
815 void wxTextCtrl::Clear()
820 bool wxTextCtrl::ReplaceLine(wxTextCoord line
,
821 const wxString
& text
)
825 // first, we have to relayout the line entirely
827 // OPT: we might try not to recalc the unchanged part of line
829 wxWrappedLineData
& lineData
= WData().m_linesData
[line
];
831 // if we had some number of rows before, use this number, otherwise
832 // just make sure that the test below (rowsNew != rowsOld) will be true
834 if ( lineData
.IsValid() )
836 rowsOld
= lineData
.GetExtraRowCount();
838 else // line wasn't laid out yet
840 // assume it changed entirely as we can't do anything better
844 // now change the line
845 MData().m_lines
[line
] = text
;
847 // OPT: we choose to lay it our immediately instead of delaying it
848 // until it is needed because it allows us to avoid invalidating
849 // lines further down if the number of rows didn't chnage, but
850 // maybe we can imporve this even further?
851 LayoutLine(line
, lineData
);
853 int rowsNew
= lineData
.GetExtraRowCount();
855 if ( rowsNew
!= rowsOld
)
857 // we have to update the line wrap marks as this is normally done
858 // by LayoutLines() which we bypassed by calling LayoutLine()
860 wxTextCoord rowFirst
= lineData
.GetFirstRow(),
861 rowCount
= wxMax(rowsOld
, rowsNew
);
862 RefreshLineWrapMarks(rowFirst
, rowFirst
+ rowCount
);
864 // next, if this is not the last line, as the number of rows in it
865 // changed, we need to shift all the lines below it
866 if ( (size_t)line
< WData().m_linesData
.GetCount() )
868 // number of rows changed shifting all lines below
869 WData().InvalidateLinesBelow(line
+ 1);
872 // the number of rows changed
878 MData().m_lines
[line
] = text
;
881 // the number of rows didn't change
885 void wxTextCtrl::RemoveLine(wxTextCoord line
)
887 MData().m_lines
.RemoveAt(line
);
890 // we need to recalculate all the starting rows from this line, but we
891 // can avoid doing it if this line was never calculated: this means
892 // that we will recalculate all lines below it anyhow later if needed
893 if ( WData().IsValidLine(line
) )
895 WData().InvalidateLinesBelow(line
);
898 WData().m_linesData
.RemoveAt(line
);
902 void wxTextCtrl::InsertLine(wxTextCoord line
, const wxString
& text
)
904 MData().m_lines
.Insert(text
, line
);
907 WData().m_linesData
.Insert(new wxWrappedLineData
, line
);
909 // invalidate everything below it
910 WData().InvalidateLinesBelow(line
);
914 void wxTextCtrl::Replace(wxTextPos from
, wxTextPos to
, const wxString
& text
)
916 wxTextCoord colStart
, colEnd
,
920 !PositionToXY(from
, &colStart
, &lineStart
) ||
921 !PositionToXY(to
, &colEnd
, &lineEnd
) )
923 wxFAIL_MSG(_T("invalid range in wxTextCtrl::Replace"));
928 #ifdef WXDEBUG_TEXT_REPLACE
929 // a straighforward (but very inefficient) way of calculating what the new
931 wxString textTotal
= GetValue();
932 wxString
textTotalNew(textTotal
, (size_t)from
);
933 textTotalNew
+= text
;
934 if ( (size_t)to
< textTotal
.length() )
935 textTotalNew
+= textTotal
.c_str() + (size_t)to
;
936 #endif // WXDEBUG_TEXT_REPLACE
938 // remember the old selection and reset it immediately: we must do it
939 // before calling Refresh(anything) as, at least under GTK, this leads to
940 // an _immediate_ repaint (under MSW it is delayed) and hence parts of
941 // text would be redrawn as selected if we didn't reset the selection
942 int selStartOld
= m_selStart
,
943 selEndOld
= m_selEnd
;
948 if ( IsSingleLine() )
950 // replace the part of the text with the new value
951 wxString
valueNew(m_value
, (size_t)from
);
953 // remember it for later use
954 wxCoord startNewText
= GetTextWidth(valueNew
);
957 if ( (size_t)to
< m_value
.length() )
959 valueNew
+= m_value
.c_str() + (size_t)to
;
962 // we usually refresh till the end of line except of the most common case
963 // when some text is appended to the end of the string in which case we
965 wxCoord widthNewText
;
967 if ( (size_t)from
< m_value
.length() )
969 // refresh till the end of line
972 else // text appended, not replaced
974 // refresh only the new text
975 widthNewText
= GetTextWidth(text
);
980 // force SData().m_colLastVisible update
981 SData().m_colLastVisible
= -1;
984 RefreshPixelRange(0, startNewText
, widthNewText
);
988 //OPT: special case for replacements inside single line?
991 Join all the lines in the replacement range into one string, then
992 replace a part of it with the new text and break it into lines again.
995 // (0) we want to know if this replacement changes the number of rows
996 // as if it does we need to refresh everything below the changed
997 // text (it will be shifted...) and we can avoid it if there is no
999 bool rowsNumberChanged
= FALSE
;
1002 const wxArrayString
& linesOld
= GetLines();
1005 for ( line
= lineStart
; line
<= lineEnd
; line
++ )
1007 if ( line
> lineStart
)
1009 // from the previous line
1010 textOrig
+= _T('\n');
1013 textOrig
+= linesOld
[line
];
1016 // we need to append the '\n' for the last line unless there is no
1018 size_t countOld
= linesOld
.GetCount();
1020 // (2) replace text in the combined string
1022 // (2a) leave the part before replaced area unchanged
1023 wxString
textNew(textOrig
, colStart
);
1025 // these values will be used to refresh the changed area below
1026 wxCoord widthNewText
,
1027 startNewText
= GetTextWidth(textNew
);
1028 if ( (size_t)colStart
== linesOld
[lineStart
].length() )
1030 // text appended, refresh just enough to show the new text
1031 widthNewText
= GetTextWidth(text
.BeforeFirst(_T('\n')));
1033 else // text inserted, refresh till the end of line
1038 // (2b) insert new text
1041 // (2c) and append the end of the old text
1043 // adjust for index shift: to is relative to colStart, not 0
1044 size_t toRel
= (size_t)((to
- from
) + colStart
);
1045 if ( toRel
< textOrig
.length() )
1047 textNew
+= textOrig
.c_str() + toRel
;
1050 // (3) break it into lines
1052 wxArrayString lines
;
1053 const wxChar
*curLineStart
= textNew
.c_str();
1054 for ( const wxChar
*p
= textNew
.c_str(); ; p
++ )
1056 // end of line/text?
1057 if ( !*p
|| *p
== _T('\n') )
1059 lines
.Add(wxString(curLineStart
, p
));
1063 curLineStart
= p
+ 1;
1067 #ifdef WXDEBUG_TEXT_REPLACE
1068 // (3a) all empty tokens should be counted as replacing with "foo" and
1069 // with "foo\n" should have different effects
1070 wxArrayString lines2
= wxStringTokenize(textNew
, _T("\n"),
1071 wxTOKEN_RET_EMPTY_ALL
);
1073 if ( lines2
.IsEmpty() )
1075 lines2
.Add(wxEmptyString
);
1078 wxASSERT_MSG( lines
.GetCount() == lines2
.GetCount(),
1079 _T("Replace() broken") );
1080 for ( size_t n
= 0; n
< lines
.GetCount(); n
++ )
1082 wxASSERT_MSG( lines
[n
] == lines2
[n
], _T("Replace() broken") );
1084 #endif // WXDEBUG_TEXT_REPLACE
1086 // (3b) special case: if we replace everything till the end we need to
1087 // keep an empty line or the lines would disappear completely
1088 // (this also takes care of never leaving m_lines empty)
1089 if ( ((size_t)lineEnd
== countOld
- 1) && lines
.IsEmpty() )
1091 lines
.Add(wxEmptyString
);
1094 size_t nReplaceCount
= lines
.GetCount(),
1097 // (4) merge into the array
1100 for ( line
= lineStart
; line
<= lineEnd
; line
++, nReplaceLine
++ )
1102 if ( nReplaceLine
< nReplaceCount
)
1104 // we have the replacement line for this one
1105 if ( ReplaceLine(line
, lines
[nReplaceLine
]) )
1107 rowsNumberChanged
= TRUE
;
1110 UpdateMaxWidth(line
);
1112 else // no more replacement lines
1114 // (4b) delete all extra lines (note that we need to delete
1115 // them backwards because indices shift while we do it)
1116 bool deletedLongestLine
= FALSE
;
1117 for ( wxTextCoord lineDel
= lineEnd
; lineDel
>= line
; lineDel
-- )
1119 if ( lineDel
== MData().m_lineLongest
)
1121 // we will need to recalc the max line width
1122 deletedLongestLine
= TRUE
;
1125 RemoveLine(lineDel
);
1128 if ( deletedLongestLine
)
1133 // even the line number changed
1134 rowsNumberChanged
= TRUE
;
1136 // update line to exit the loop
1141 // (4c) insert the new lines
1142 if ( nReplaceLine
< nReplaceCount
)
1144 // even the line number changed
1145 rowsNumberChanged
= TRUE
;
1149 InsertLine(++lineEnd
, lines
[nReplaceLine
++]);
1151 UpdateMaxWidth(lineEnd
);
1153 while ( nReplaceLine
< nReplaceCount
);
1156 // (5) now refresh the changed area
1158 // update the (cached) last position first as refresh functions use it
1159 m_posLast
+= text
.length() - to
+ from
;
1161 // we may optimize refresh if the number of rows didn't change - but if
1162 // it did we have to refresh everything below the part we chanegd as
1163 // well as it might have moved
1164 if ( !rowsNumberChanged
)
1166 // refresh the line we changed
1169 RefreshPixelRange(lineStart
++, startNewText
, widthNewText
);
1173 //OPT: we shouldn't refresh the unchanged part of the line in
1174 // this case, but instead just refresh the tail of it - the
1175 // trouble is that we don't know here where does this tail
1179 // number of rows didn't change, refresh the updated rows and the
1181 if ( lineStart
<= lineEnd
)
1182 RefreshLineRange(lineStart
, lineEnd
);
1184 else // rows number did change
1188 // refresh only part of the first line
1189 RefreshPixelRange(lineStart
++, startNewText
, widthNewText
);
1191 //else: we have to refresh everything as some part of the text
1192 // could be in the previous row before but moved to the next
1193 // one now (due to word wrap)
1195 wxTextCoord lineEnd
= GetLines().GetCount() - 1;
1196 if ( lineStart
<= lineEnd
)
1197 RefreshLineRange(lineStart
, lineEnd
);
1199 // refresh text rect left below
1200 RefreshLineRange(lineEnd
+ 1, 0);
1202 // the vert scrollbar might [dis]appear
1203 MData().m_updateScrollbarY
= TRUE
;
1206 // must recalculate it - will do later
1210 #ifdef WXDEBUG_TEXT_REPLACE
1211 // optimized code above should give the same result as straightforward
1212 // computation in the beginning
1213 wxASSERT_MSG( GetValue() == textTotalNew
, _T("error in Replace()") );
1214 #endif // WXDEBUG_TEXT_REPLACE
1216 // update the current position: note that we always put the cursor at the
1217 // end of the replacement text
1218 DoSetInsertionPoint(from
+ text
.length());
1220 // and the selection: this is complicated by the fact that selection coords
1221 // must be first updated to reflect change in text coords, i.e. if we had
1222 // selection from 17 to 19 and we just removed this range, we don't have to
1223 // refresh anything, so we can't just use ClearSelection() here
1224 if ( selStartOld
!= -1 )
1226 // refresh the parst of the selection outside the changed text (which
1227 // we already refreshed)
1228 if ( selStartOld
< from
)
1229 RefreshTextRange(selStartOld
, from
);
1230 if ( to
< selEndOld
)
1231 RefreshTextRange(to
, selEndOld
);
1235 // now call it to do the rest (not related to refreshing)
1239 void wxTextCtrl::Remove(wxTextPos from
, wxTextPos to
)
1241 // Replace() only works with correctly ordered arguments, so exchange them
1243 OrderPositions(from
, to
);
1245 Replace(from
, to
, _T(""));
1248 void wxTextCtrl::WriteText(const wxString
& text
)
1250 // replace the selection with the new text
1253 Replace(m_curPos
, m_curPos
, text
);
1256 void wxTextCtrl::AppendText(const wxString
& text
)
1258 SetInsertionPointEnd();
1262 // ----------------------------------------------------------------------------
1264 // ----------------------------------------------------------------------------
1266 void wxTextCtrl::SetInsertionPoint(wxTextPos pos
)
1268 wxCHECK_RET( pos
>= 0 && pos
<= GetLastPosition(),
1269 _T("insertion point position out of range") );
1271 // don't do anything if it didn't change
1272 if ( pos
!= m_curPos
)
1274 DoSetInsertionPoint(pos
);
1277 if ( !IsSingleLine() )
1279 // moving cursor should reset the stored abscissa (even if the cursor
1280 // position didn't actually change!)
1281 MData().m_xCaret
= -1;
1287 void wxTextCtrl::InitInsertionPoint()
1289 // so far always put it in the beginning
1290 DoSetInsertionPoint(0);
1292 // this will also set the selection anchor correctly
1296 void wxTextCtrl::MoveInsertionPoint(wxTextPos pos
)
1298 wxASSERT_MSG( pos
>= 0 && pos
<= GetLastPosition(),
1299 _T("DoSetInsertionPoint() can only be called with valid pos") );
1302 PositionToXY(m_curPos
, &m_curCol
, &m_curRow
);
1305 void wxTextCtrl::DoSetInsertionPoint(wxTextPos pos
)
1307 MoveInsertionPoint(pos
);
1312 void wxTextCtrl::SetInsertionPointEnd()
1314 SetInsertionPoint(GetLastPosition());
1317 wxTextPos
wxTextCtrl::GetInsertionPoint() const
1322 wxTextPos
wxTextCtrl::GetLastPosition() const
1325 if ( IsSingleLine() )
1327 pos
= m_value
.length();
1333 size_t nLineCount
= GetLineCount();
1334 for ( size_t nLine
= 0; nLine
< nLineCount
; nLine
++ )
1336 // +1 is because the positions at the end of this line and of the
1337 // start of the next one are different
1338 pos
+= GetLines()[nLine
].length() + 1;
1343 // the last position is at the end of the last line, not in the
1344 // beginning of the next line after it
1348 // more probable reason of this would be to forget to update m_posLast
1349 wxASSERT_MSG( pos
== m_posLast
, _T("bug in GetLastPosition()") );
1350 #endif // WXDEBUG_TEXT
1358 // ----------------------------------------------------------------------------
1360 // ----------------------------------------------------------------------------
1362 void wxTextCtrl::GetSelection(wxTextPos
* from
, wxTextPos
* to
) const
1370 wxString
wxTextCtrl::GetSelectionText() const
1374 if ( HasSelection() )
1376 if ( IsSingleLine() )
1378 sel
= m_value
.Mid(m_selStart
, m_selEnd
- m_selStart
);
1382 wxTextCoord colStart
, lineStart
,
1384 PositionToXY(m_selStart
, &colStart
, &lineStart
);
1385 PositionToXY(m_selEnd
, &colEnd
, &lineEnd
);
1387 // as always, we need to check for the special case when the start
1388 // and end line are the same
1389 if ( lineEnd
== lineStart
)
1391 sel
= GetLines()[lineStart
].Mid(colStart
, colEnd
- colStart
);
1393 else // sel on multiple lines
1395 // take the end of the first line
1396 sel
= GetLines()[lineStart
].c_str() + colStart
;
1399 // all intermediate ones
1400 for ( wxTextCoord line
= lineStart
+ 1; line
< lineEnd
; line
++ )
1402 sel
<< GetLines()[line
] << _T('\n');
1405 // and the start of the last one
1406 sel
+= GetLines()[lineEnd
].Left(colEnd
);
1414 void wxTextCtrl::SetSelection(wxTextPos from
, wxTextPos to
)
1416 // selecting till -1 is the same as selecting to the end
1417 if ( to
== -1 && from
!= -1 )
1419 to
= GetLastPosition();
1422 if ( from
== -1 || to
== from
)
1426 else // valid sel range
1428 // remember the 'to' position as the current position, used to move the
1429 // caret there later
1430 wxTextPos toOrig
= to
;
1432 OrderPositions(from
, to
);
1434 wxCHECK_RET( to
<= GetLastPosition(),
1435 _T("invalid range in wxTextCtrl::SetSelection") );
1437 if ( from
!= m_selStart
|| to
!= m_selEnd
)
1439 // we need to use temp vars as RefreshTextRange() may call DoDraw()
1440 // directly and so m_selStart/End must be reset by then
1441 wxTextPos selStartOld
= m_selStart
,
1442 selEndOld
= m_selEnd
;
1447 wxLogTrace(_T("text"), _T("Selection range is %ld-%ld"),
1448 m_selStart
, m_selEnd
);
1450 // refresh only the part of text which became (un)selected if
1452 if ( selStartOld
== m_selStart
)
1454 RefreshTextRange(selEndOld
, m_selEnd
);
1456 else if ( selEndOld
== m_selEnd
)
1458 RefreshTextRange(m_selStart
, selStartOld
);
1462 // OPT: could check for other cases too but it is probably not
1463 // worth it as the two above are the most common ones
1464 if ( selStartOld
!= -1 )
1465 RefreshTextRange(selStartOld
, selEndOld
);
1466 if ( m_selStart
!= -1 )
1467 RefreshTextRange(m_selStart
, m_selEnd
);
1470 // we need to fully repaint the invalidated areas of the window
1471 // before scrolling it (from DoSetInsertionPoint which is typically
1472 // called after SetSelection()), otherwise they may stay unpainted
1473 m_targetWindow
->Update();
1475 //else: nothing to do
1477 // the insertion point is put at the location where the caret was moved
1478 DoSetInsertionPoint(toOrig
);
1482 void wxTextCtrl::ClearSelection()
1484 if ( HasSelection() )
1486 // we need to use temp vars as RefreshTextRange() may call DoDraw()
1487 // directly (see above as well)
1488 wxTextPos selStart
= m_selStart
,
1491 // no selection any more
1495 // refresh the old selection
1496 RefreshTextRange(selStart
, selEnd
);
1499 // the anchor should be moved even if there was no selection previously
1500 m_selAnchor
= m_curPos
;
1503 void wxTextCtrl::RemoveSelection()
1505 if ( !HasSelection() )
1508 Remove(m_selStart
, m_selEnd
);
1511 bool wxTextCtrl::GetSelectedPartOfLine(wxTextCoord line
,
1512 wxTextPos
*start
, wxTextPos
*end
) const
1519 if ( !HasSelection() )
1521 // no selection at all, hence no selection in this line
1525 wxTextCoord lineStart
, colStart
;
1526 PositionToXY(m_selStart
, &colStart
, &lineStart
);
1527 if ( lineStart
> line
)
1529 // this line is entirely above the selection
1533 wxTextCoord lineEnd
, colEnd
;
1534 PositionToXY(m_selEnd
, &colEnd
, &lineEnd
);
1535 if ( lineEnd
< line
)
1537 // this line is entirely below the selection
1541 if ( line
== lineStart
)
1546 *end
= lineEnd
== lineStart
? colEnd
: GetLineLength(line
);
1548 else if ( line
== lineEnd
)
1551 *start
= lineEnd
== lineStart
? colStart
: 0;
1555 else // the line is entirely inside the selection
1560 *end
= GetLineLength(line
);
1566 // ----------------------------------------------------------------------------
1568 // ----------------------------------------------------------------------------
1570 bool wxTextCtrl::IsModified() const
1572 return m_isModified
;
1575 bool wxTextCtrl::IsEditable() const
1577 // disabled control can never be edited
1578 return m_isEditable
&& IsEnabled();
1581 void wxTextCtrl::DiscardEdits()
1583 m_isModified
= FALSE
;
1586 void wxTextCtrl::SetEditable(bool editable
)
1588 if ( editable
!= m_isEditable
)
1590 m_isEditable
= editable
;
1592 // the caret (dis)appears
1595 // the appearance of the control might have changed
1600 // ----------------------------------------------------------------------------
1601 // col/lines <-> position correspondence
1602 // ----------------------------------------------------------------------------
1605 A few remarks about this stuff:
1607 o The numbering of the text control columns/rows starts from 0.
1608 o Start of first line is position 0, its last position is line.length()
1609 o Start of the next line is the last position of the previous line + 1
1612 int wxTextCtrl::GetLineLength(wxTextCoord line
) const
1614 if ( IsSingleLine() )
1616 wxASSERT_MSG( line
== 0, _T("invalid GetLineLength() parameter") );
1618 return m_value
.length();
1622 wxCHECK_MSG( (size_t)line
< GetLineCount(), -1,
1623 _T("line index out of range") );
1625 return GetLines()[line
].length();
1629 wxString
wxTextCtrl::GetLineText(wxTextCoord line
) const
1631 if ( IsSingleLine() )
1633 wxASSERT_MSG( line
== 0, _T("invalid GetLineLength() parameter") );
1639 wxCHECK_MSG( (size_t)line
< GetLineCount(), _T(""),
1640 _T("line index out of range") );
1642 return GetLines()[line
];
1646 int wxTextCtrl::GetNumberOfLines() const
1648 // there is always 1 line, even if the text is empty
1649 return IsSingleLine() ? 1 : GetLineCount();
1652 wxTextPos
wxTextCtrl::XYToPosition(wxTextCoord x
, wxTextCoord y
) const
1654 // note that this method should accept any values of x and y and return -1
1655 // if they are out of range
1656 if ( IsSingleLine() )
1658 return x
> GetLastPosition() || y
> 0 ? -1 : x
;
1662 if ( (size_t)y
>= GetLineCount() )
1664 // this position is below the text
1665 return GetLastPosition();
1669 for ( size_t nLine
= 0; nLine
< (size_t)y
; nLine
++ )
1671 // +1 is because the positions at the end of this line and of the
1672 // start of the next one are different
1673 pos
+= GetLines()[nLine
].length() + 1;
1676 // take into account also the position in line
1677 if ( (size_t)x
> GetLines()[y
].length() )
1679 // don't return position in the next line
1680 x
= GetLines()[y
].length();
1687 bool wxTextCtrl::PositionToXY(wxTextPos pos
,
1688 wxTextCoord
*x
, wxTextCoord
*y
) const
1690 if ( IsSingleLine() )
1692 if ( (size_t)pos
> m_value
.length() )
1704 wxTextPos posCur
= 0;
1705 size_t nLineCount
= GetLineCount();
1706 for ( size_t nLine
= 0; nLine
< nLineCount
; nLine
++ )
1708 // +1 is because the start the start of the next line is one
1709 // position after the end of this one
1710 wxTextPos posNew
= posCur
+ GetLines()[nLine
].length() + 1;
1713 // we've found the line, now just calc the column
1721 wxASSERT_MSG( XYToPosition(pos
- posCur
, nLine
) == pos
,
1722 _T("XYToPosition() or PositionToXY() broken") );
1723 #endif // WXDEBUG_TEXT
1727 else // go further down
1733 // beyond the last line
1738 wxTextCoord
wxTextCtrl::GetRowsPerLine(wxTextCoord line
) const
1740 // a normal line has one row
1741 wxTextCoord numRows
= 1;
1745 // add the number of additional rows
1746 numRows
+= WData().m_linesData
[line
].GetExtraRowCount();
1752 wxTextCoord
wxTextCtrl::GetRowCount() const
1754 wxTextCoord count
= GetLineCount();
1759 count
= GetFirstRowOfLine(count
- 1) +
1760 WData().m_linesData
[count
- 1].GetRowCount();
1766 wxTextCoord
wxTextCtrl::GetRowAfterLine(wxTextCoord line
) const
1771 if ( !WData().IsValidLine(line
) )
1776 return WData().m_linesData
[line
].GetNextRow();
1779 wxTextCoord
wxTextCtrl::GetFirstRowOfLine(wxTextCoord line
) const
1784 if ( !WData().IsValidLine(line
) )
1789 return WData().m_linesData
[line
].GetFirstRow();
1792 bool wxTextCtrl::PositionToLogicalXY(wxTextPos pos
,
1794 wxCoord
*yOut
) const
1796 wxTextCoord col
, line
;
1798 // optimization for special (but common) case when we already have the col
1800 if ( pos
== m_curPos
)
1805 else // must really calculate col/line from pos
1807 if ( !PositionToXY(pos
, &col
, &line
) )
1811 int hLine
= GetLineHeight();
1813 wxString textLine
= GetLineText(line
);
1814 if ( IsSingleLine() || !WrapLines() )
1816 x
= GetTextWidth(textLine
.Left(col
));
1819 else // difficult case: multline control with line wrap
1821 y
= GetFirstRowOfLine(line
);
1823 wxTextCoord colRowStart
;
1824 y
+= GetRowInLine(line
, col
, &colRowStart
);
1828 // x is the width of the text before this position in this row
1829 x
= GetTextWidth(textLine
.Mid(colRowStart
, col
- colRowStart
));
1840 bool wxTextCtrl::PositionToDeviceXY(wxTextPos pos
,
1842 wxCoord
*yOut
) const
1845 if ( !PositionToLogicalXY(pos
, &x
, &y
) )
1848 // finally translate the logical text rect coords into physical client
1850 CalcScrolledPosition(m_rectText
.x
+ x
, m_rectText
.y
+ y
, xOut
, yOut
);
1855 wxPoint
wxTextCtrl::GetCaretPosition() const
1857 wxCoord xCaret
, yCaret
;
1858 if ( !PositionToDeviceXY(m_curPos
, &xCaret
, &yCaret
) )
1860 wxFAIL_MSG( _T("Caret can't be beyond the text!") );
1863 return wxPoint(xCaret
, yCaret
);
1866 // pos may be -1 to show the current position
1867 void wxTextCtrl::ShowPosition(wxTextPos pos
)
1871 if ( IsSingleLine() )
1873 ShowHorzPosition(GetTextWidth(m_value
.Left(pos
)));
1875 else if ( MData().m_scrollRangeX
|| MData().m_scrollRangeY
) // multiline with scrollbars
1878 GetViewStart(&xStart
, &yStart
);
1884 PositionToLogicalXY(pos
, &x
, &y
);
1886 wxRect rectText
= GetRealTextArea();
1888 // scroll the position vertically into view: if it is currently above
1889 // it, make it the first one, otherwise the last one
1890 if ( MData().m_scrollRangeY
)
1892 y
/= GetLineHeight();
1898 else // we are currently in or below the view area
1900 // find the last row currently shown
1905 // to find the last row we need to use the generic HitTest
1908 // OPT this is a bit silly: we undo this in HitTest(), so
1909 // it would be better to factor out the common
1910 // functionality into a separate function (OTOH it
1911 // won't probably save us that much)
1912 wxPoint
pt(0, rectText
.height
- 1);
1913 pt
+= GetClientAreaOrigin();
1914 pt
+= m_rectText
.GetPosition();
1915 HitTest(pt
, &col
, &yEnd
);
1917 // find the row inside the line
1918 yEnd
= GetFirstRowOfLine(yEnd
) + GetRowInLine(yEnd
, col
);
1922 // finding the last line is easy if each line has exactly
1924 yEnd
= yStart
+ rectText
.height
/ GetLineHeight() - 1;
1929 // scroll down: the current item should appear at the
1930 // bottom of the view
1931 Scroll(0, y
- (yEnd
- yStart
));
1936 // scroll the position horizontally into view
1938 // we follow what I believe to be Windows behaviour here, that is if
1939 // the position is already entirely in the view we do nothing, but if
1940 // we do have to scroll the window to bring it into view, we scroll it
1941 // not just enough to show the position but slightly more so that this
1942 // position is at 1/3 of the window width from the closest border to it
1943 // (I'm not sure that Windows does exactly this but it looks like this)
1944 if ( MData().m_scrollRangeX
)
1946 // unlike for the rows, xStart doesn't correspond to the starting
1947 // column as they all have different widths, so we need to
1948 // translate everything to pixels
1950 // we want the text between x and x2 be entirely inside the view
1951 // (i.e. the current character)
1953 // make xStart the first visible pixel (and not position)
1954 int wChar
= GetAverageWidth();
1959 // we want the position of this column be 1/3 to the right of
1961 x
-= rectText
.width
/ 3;
1964 Scroll(x
/ wChar
, y
);
1966 else // maybe we're beyond the right border of the view?
1968 wxTextCoord col
, row
;
1969 if ( PositionToXY(pos
, &col
, &row
) )
1971 wxString lineText
= GetLineText(row
);
1972 wxCoord x2
= x
+ GetTextWidth(lineText
[(size_t)col
]);
1973 if ( x2
> xStart
+ rectText
.width
)
1975 // we want the position of this column be 1/3 to the
1976 // left of the right edge, i.e. 2/3 right of the left
1978 x2
-= (2*rectText
.width
)/3;
1981 Scroll(x2
/ wChar
, row
);
1987 //else: multiline but no scrollbars, hence nothing to do
1992 // ----------------------------------------------------------------------------
1994 // ----------------------------------------------------------------------------
1997 TODO: we could have (easy to do) vi-like options for word movement, i.e.
1998 distinguish between inlusive/exclusive words and between words and
1999 WORDS (in vim sense) and also, finally, make the set of characters
2000 which make up a word configurable - currently we use the exclusive
2001 WORDS only (coincidentally, this is what Windows edit control does)
2003 For future references, here is what vim help says:
2005 A word consists of a sequence of letters, digits and underscores, or
2006 a sequence of other non-blank characters, separated with white space
2007 (spaces, tabs, <EOL>). This can be changed with the 'iskeyword'
2010 A WORD consists of a sequence of non-blank characters, separated with
2011 white space. An empty line is also considered to be a word and a
2015 static inline bool IsWordChar(wxChar ch
)
2017 return !wxIsspace(ch
);
2020 wxTextPos
wxTextCtrl::GetWordStart() const
2022 if ( m_curPos
== -1 || m_curPos
== 0 )
2025 if ( m_curCol
== 0 )
2027 // go to the end of the previous line
2028 return m_curPos
- 1;
2031 // it shouldn't be possible to learn where the word starts in the password
2036 // start at the previous position
2037 const wxChar
*p0
= GetLineText(m_curRow
).c_str();
2038 const wxChar
*p
= p0
+ m_curCol
- 1;
2040 // find the end of the previous word
2041 while ( (p
> p0
) && !IsWordChar(*p
) )
2044 // now find the beginning of this word
2045 while ( (p
> p0
) && IsWordChar(*p
) )
2048 // we might have gone too far
2049 if ( !IsWordChar(*p
) )
2052 return (m_curPos
- m_curCol
) + p
- p0
;
2055 wxTextPos
wxTextCtrl::GetWordEnd() const
2057 if ( m_curPos
== -1 )
2060 wxString line
= GetLineText(m_curRow
);
2061 if ( (size_t)m_curCol
== line
.length() )
2063 // if we're on the last position in the line, go to the next one - if
2065 wxTextPos pos
= m_curPos
;
2066 if ( pos
< GetLastPosition() )
2072 // it shouldn't be possible to learn where the word ends in the password
2075 return GetLastPosition();
2077 // start at the current position
2078 const wxChar
*p0
= line
.c_str();
2079 const wxChar
*p
= p0
+ m_curCol
;
2081 // find the start of the next word
2082 while ( *p
&& !IsWordChar(*p
) )
2085 // now find the end of it
2086 while ( *p
&& IsWordChar(*p
) )
2089 // and find the start of the next word
2090 while ( *p
&& !IsWordChar(*p
) )
2093 return (m_curPos
- m_curCol
) + p
- p0
;
2096 // ----------------------------------------------------------------------------
2098 // ----------------------------------------------------------------------------
2100 void wxTextCtrl::Copy()
2103 if ( HasSelection() )
2105 wxClipboardLocker clipLock
;
2107 // wxTextFile::Translate() is needed to transform all '\n' into "\r\n"
2108 wxString text
= wxTextFile::Translate(GetTextToShow(GetSelectionText()));
2109 wxTextDataObject
*data
= new wxTextDataObject(text
);
2110 wxTheClipboard
->SetData(data
);
2112 #endif // wxUSE_CLIPBOARD
2115 void wxTextCtrl::Cut()
2120 bool wxTextCtrl::DoCut()
2122 if ( !HasSelection() )
2132 void wxTextCtrl::Paste()
2137 bool wxTextCtrl::DoPaste()
2140 wxClipboardLocker clipLock
;
2142 wxTextDataObject data
;
2143 if ( wxTheClipboard
->IsSupported(data
.GetFormat())
2144 && wxTheClipboard
->GetData(data
) )
2146 // reverse transformation: '\r\n\" -> '\n'
2147 wxString text
= wxTextFile::Translate(data
.GetText(),
2148 wxTextFileType_Unix
);
2149 if ( !text
.empty() )
2156 #endif // wxUSE_CLIPBOARD
2161 // ----------------------------------------------------------------------------
2163 // ----------------------------------------------------------------------------
2165 wxTextCtrlInsertCommand
*
2166 wxTextCtrlCommandProcessor::IsInsertCommand(wxCommand
*command
)
2168 return (wxTextCtrlInsertCommand
*)
2169 (command
&& (command
->GetName() == wxTEXT_COMMAND_INSERT
)
2173 void wxTextCtrlCommandProcessor::Store(wxCommand
*command
)
2175 wxTextCtrlInsertCommand
*cmdIns
= IsInsertCommand(command
);
2178 if ( IsCompressing() )
2180 wxTextCtrlInsertCommand
*
2181 cmdInsLast
= IsInsertCommand(GetCurrentCommand());
2183 // it is possible that we don't have any last command at all if,
2184 // for example, it was undone since the last Store(), so deal with
2188 cmdInsLast
->Append(cmdIns
);
2192 // don't need to call the base class version
2197 // append the following insert commands to this one
2198 m_compressInserts
= TRUE
;
2200 // let the base class version will do the job normally
2202 else // not an insert command
2204 // stop compressing insert commands - this won't work with the last
2205 // command not being an insert one anyhow
2208 // let the base class version will do the job normally
2211 wxCommandProcessor::Store(command
);
2214 void wxTextCtrlInsertCommand::Append(wxTextCtrlInsertCommand
*other
)
2216 m_text
+= other
->m_text
;
2219 bool wxTextCtrlInsertCommand::CanUndo() const
2221 return m_from
!= -1;
2224 bool wxTextCtrlInsertCommand::Do(wxTextCtrl
*text
)
2226 // the text is going to be inserted at the current position, remember where
2228 m_from
= text
->GetInsertionPoint();
2230 // and now do insert it
2231 text
->WriteText(m_text
);
2236 bool wxTextCtrlInsertCommand::Undo(wxTextCtrl
*text
)
2238 wxCHECK_MSG( CanUndo(), FALSE
, _T("impossible to undo insert cmd") );
2240 // remove the text from where we inserted it
2241 text
->Remove(m_from
, m_from
+ m_text
.length());
2246 bool wxTextCtrlRemoveCommand::CanUndo() const
2248 // if we were executed, we should have the text we removed
2249 return !m_textDeleted
.empty();
2252 bool wxTextCtrlRemoveCommand::Do(wxTextCtrl
*text
)
2254 text
->SetSelection(m_from
, m_to
);
2255 m_textDeleted
= text
->GetSelectionText();
2256 text
->RemoveSelection();
2261 bool wxTextCtrlRemoveCommand::Undo(wxTextCtrl
*text
)
2263 // it is possible that the text was deleted and that we can't restore text
2264 // at the same position we removed it any more
2265 wxTextPos posLast
= text
->GetLastPosition();
2266 text
->SetInsertionPoint(m_from
> posLast
? posLast
: m_from
);
2267 text
->WriteText(m_textDeleted
);
2272 void wxTextCtrl::Undo()
2274 // the caller must check it
2275 wxASSERT_MSG( CanUndo(), _T("can't call Undo() if !CanUndo()") );
2277 m_cmdProcessor
->Undo();
2280 void wxTextCtrl::Redo()
2282 // the caller must check it
2283 wxASSERT_MSG( CanRedo(), _T("can't call Undo() if !CanUndo()") );
2285 m_cmdProcessor
->Redo();
2288 bool wxTextCtrl::CanUndo() const
2290 return IsEditable() && m_cmdProcessor
->CanUndo();
2293 bool wxTextCtrl::CanRedo() const
2295 return IsEditable() && m_cmdProcessor
->CanRedo();
2298 // ----------------------------------------------------------------------------
2300 // ----------------------------------------------------------------------------
2302 wxSize
wxTextCtrl::DoGetBestClientSize() const
2304 // when we're called for the very first time from Create() we must
2305 // calculate the font metrics here because we can't do it before calling
2306 // Create() (there is no window yet and wxGTK crashes) but we need them
2308 if ( m_heightLine
== -1 )
2310 wxConstCast(this, wxTextCtrl
)->RecalcFontMetrics();
2314 GetTextExtent(GetTextToShow(GetLineText(0)), &w
, &h
);
2316 int wChar
= GetAverageWidth(),
2317 hChar
= GetLineHeight();
2319 int widthMin
= wxMax(10*wChar
, 100);
2325 if ( !IsSingleLine() )
2327 // let the control have a reasonable number of lines
2328 int lines
= GetNumberOfLines();
2331 else if ( lines
> 10 )
2338 rectText
.height
= h
;
2339 wxRect rectTotal
= GetRenderer()->GetTextTotalArea(this, rectText
);
2340 return wxSize(rectTotal
.width
, rectTotal
.height
);
2343 void wxTextCtrl::UpdateTextRect()
2345 wxRect
rectTotal(wxPoint(0, 0), GetClientSize());
2346 wxCoord
*extraSpace
= WrapLines() ? &WData().m_widthMark
: NULL
;
2347 m_rectText
= GetRenderer()->GetTextClientArea(this, rectTotal
, extraSpace
);
2349 // code elsewhere is confused by negative rect size
2350 if ( m_rectText
.width
<= 0 )
2351 m_rectText
.width
= 1;
2352 if ( m_rectText
.height
<= 0 )
2353 m_rectText
.height
= 1;
2355 if ( !IsSingleLine() )
2357 // invalidate it so that GetRealTextArea() will recalc it
2358 MData().m_rectTextReal
.width
= 0;
2360 // only scroll this rect when the window is scrolled: note that we have
2361 // to scroll not only the text but the line wrap marks too if we show
2363 wxRect rectText
= GetRealTextArea();
2364 if ( extraSpace
&& *extraSpace
)
2366 rectText
.width
+= *extraSpace
;
2368 SetTargetRect(rectText
);
2370 // relayout all lines
2373 WData().m_rowFirstInvalid
= 0;
2375 // increase timestamp: this means that the lines which had been
2376 // laid out before will be relayd out the next time LayoutLines()
2377 // is called because their timestamp will be smaller than the
2379 WData().m_timestamp
++;
2383 UpdateLastVisible();
2386 void wxTextCtrl::UpdateLastVisible()
2388 // this method is only used for horizontal "scrollbarless" scrolling which
2389 // is used only with single line controls
2390 if ( !IsSingleLine() )
2393 // use (efficient) HitTestLine to find the last visible character
2394 wxString text
= m_value
.Mid((size_t)SData().m_colStart
/* to the end */);
2396 switch ( HitTestLine(text
, m_rectText
.width
, &col
) )
2398 case wxTE_HT_BEYOND
:
2399 // everything is visible
2400 SData().m_colLastVisible
= text
.length();
2403 SData().m_posLastVisible
= -1;
2407 case wxTE_HT_BEFORE:
2411 wxFAIL_MSG(_T("unexpected HitTestLine() return value"));
2414 case wxTE_HT_ON_TEXT
:
2417 // the last entirely seen character is the previous one because
2418 // this one is only partly visible - unless the width of the
2419 // string is exactly the max width
2420 SData().m_posLastVisible
= GetTextWidth(text
.Truncate(col
+ 1));
2421 if ( SData().m_posLastVisible
> m_rectText
.width
)
2423 // this character is not entirely visible, take the
2428 SData().m_posLastVisible
= -1;
2430 //else: we can just see it
2432 SData().m_colLastVisible
= col
;
2437 // calculate the width of the text really shown
2438 if ( SData().m_posLastVisible
== -1 )
2440 SData().m_posLastVisible
= GetTextWidth(text
.Truncate(SData().m_colLastVisible
+ 1));
2443 // current value is relative the start of the string text which starts at
2444 // SData().m_colStart, we need an absolute offset into string
2445 SData().m_colLastVisible
+= SData().m_colStart
;
2447 wxLogTrace(_T("text"), _T("Last visible column/position is %d/%ld"),
2448 SData().m_colLastVisible
, SData().m_posLastVisible
);
2451 void wxTextCtrl::OnSize(wxSizeEvent
& event
)
2455 if ( !IsSingleLine() )
2458 // update them immediately because if we are called for the first time,
2459 // we need to create them in order for the base class version to
2460 // position the scrollbars correctly - if we don't do it now, it won't
2461 // happen at all if we don't get more size events
2465 MData().m_updateScrollbarX
=
2466 MData().m_updateScrollbarY
= TRUE
;
2472 wxCoord
wxTextCtrl::GetTotalWidth() const
2475 CalcUnscrolledPosition(m_rectText
.width
, 0, &w
, NULL
);
2479 wxCoord
wxTextCtrl::GetTextWidth(const wxString
& text
) const
2482 GetTextExtent(GetTextToShow(text
), &w
, NULL
);
2486 wxRect
wxTextCtrl::GetRealTextArea() const
2488 // for single line text control it's just the same as text rect
2489 if ( IsSingleLine() )
2492 // the real text area always holds an entire number of lines, so the only
2493 // difference with the text area is a narrow strip along the bottom border
2494 wxRect rectText
= MData().m_rectTextReal
;
2495 if ( !rectText
.width
)
2498 rectText
= m_rectText
;
2500 // when we're called for the very first time, the line height might not
2501 // had been calculated yet, so do get it now
2502 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
2503 self
->RecalcFontMetrics();
2505 int hLine
= GetLineHeight();
2506 rectText
.height
= (m_rectText
.height
/ hLine
) * hLine
;
2509 self
->MData().m_rectTextReal
= rectText
;
2515 wxTextCoord
wxTextCtrl::GetRowInLine(wxTextCoord line
,
2517 wxTextCoord
*colRowStart
) const
2519 wxASSERT_MSG( WrapLines(), _T("shouldn't be called") );
2521 const wxWrappedLineData
& lineData
= WData().m_linesData
[line
];
2523 if ( !WData().IsValidLine(line
) )
2526 // row is here counted a bit specially: 0 is the 2nd row of the line (1st
2529 rowMax
= lineData
.GetExtraRowCount();
2533 while ( (row
< rowMax
) && (col
>= lineData
.GetExtraRowStart(row
)) )
2536 // it's ok here that row is 1 greater than needed: like this, it is
2537 // counted as a normal (and not extra) row
2539 //else: only one row anyhow
2543 // +1 because we need a real row number, not the extra row one
2544 *colRowStart
= lineData
.GetRowStart(row
);
2546 // this can't happen, of course
2547 wxASSERT_MSG( *colRowStart
<= col
, _T("GetRowInLine() is broken") );
2553 void wxTextCtrl::LayoutLine(wxTextCoord line
, wxWrappedLineData
& lineData
) const
2555 // FIXME: this uses old GetPartOfWrappedLine() which is not used anywhere
2556 // else now and has rather awkward interface for our needs here
2558 lineData
.m_rowsStart
.Empty();
2559 lineData
.m_rowsWidth
.Empty();
2561 const wxString
& text
= GetLineText(line
);
2563 size_t colRowStart
= 0;
2566 size_t lenRow
= GetPartOfWrappedLine
2568 text
.c_str() + colRowStart
,
2572 // remember the start of this row (not for the first one as
2573 // it's always 0) and its width
2575 lineData
.m_rowsStart
.Add(colRowStart
);
2576 lineData
.m_rowsWidth
.Add(widthRow
);
2578 colRowStart
+= lenRow
;
2580 while ( colRowStart
< text
.length() );
2582 // put the current timestamp on it
2583 lineData
.m_timestamp
= WData().m_timestamp
;
2586 void wxTextCtrl::LayoutLines(wxTextCoord lineLast
) const
2588 wxASSERT_MSG( WrapLines(), _T("should only be used for line wrapping") );
2590 // if we were called, some line was dirty and if it was dirty we must have
2591 // had m_rowFirstInvalid set to something too
2592 wxTextCoord lineFirst
= WData().m_rowFirstInvalid
;
2593 wxASSERT_MSG( lineFirst
!= -1, _T("nothing to layout?") );
2595 wxTextCoord rowFirst
, rowCur
;
2598 // start after the last known valid line
2599 const wxWrappedLineData
& lineData
= WData().m_linesData
[lineFirst
- 1];
2600 rowFirst
= lineData
.GetFirstRow() + lineData
.GetRowCount();
2602 else // no valid lines, start at row 0
2608 for ( wxTextCoord line
= lineFirst
; line
<= lineLast
; line
++ )
2610 // set the starting row for this line
2611 wxWrappedLineData
& lineData
= WData().m_linesData
[line
];
2612 lineData
.m_rowFirst
= rowCur
;
2614 // had the line been already broken into rows?
2616 // if so, compare its timestamp with the current one: if nothing has
2617 // been changed, don't relayout it
2618 if ( !lineData
.IsValid() ||
2619 (lineData
.m_timestamp
< WData().m_timestamp
) )
2621 // now do break it in rows
2622 LayoutLine(line
, lineData
);
2625 rowCur
+= lineData
.GetRowCount();
2628 // we are now valid at least up to this line, but if it is the last one we
2629 // just don't have any more invalid rows at all
2630 if ( (size_t)lineLast
== WData().m_linesData
.GetCount() -1 )
2635 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
2636 self
->WData().m_rowFirstInvalid
= lineLast
;
2638 // also refresh the line end indicators (FIXME shouldn't do it always!)
2639 self
->RefreshLineWrapMarks(rowFirst
, rowCur
);
2642 size_t wxTextCtrl::GetPartOfWrappedLine(const wxChar
* text
,
2643 wxCoord
*widthReal
) const
2645 // this function is slow, it shouldn't be called unless really needed
2646 wxASSERT_MSG( WrapLines(), _T("shouldn't be called") );
2651 switch ( HitTestLine(s
, m_rectText
.width
, &col
) )
2654 case wxTE_HT_BEFORE:
2658 wxFAIL_MSG(_T("unexpected HitTestLine() return value"));
2661 case wxTE_HT_ON_TEXT
:
2664 // the last entirely seen character is the previous one because
2665 // this one is only partly visible - unless the width of the
2666 // string is exactly the max width
2667 wReal
= GetTextWidth(s
.Truncate(col
+ 1));
2668 if ( wReal
> m_rectText
.width
)
2670 // this character is not entirely visible, take the
2677 //else: we can just see it
2679 // wrap at any character or only at words boundaries?
2680 if ( !(GetWindowStyle() & wxTE_LINEWRAP
) )
2682 // find the (last) not word char before this word
2683 wxTextCoord colWordStart
;
2684 for ( colWordStart
= col
;
2685 colWordStart
&& IsWordChar(s
[(size_t)colWordStart
]);
2689 if ( colWordStart
> 0 )
2691 if ( colWordStart
!= col
)
2693 // will have to recalc the real width
2699 //else: only a single word, have to wrap it here
2704 case wxTE_HT_BEYOND
:
2708 // we return the number of characters, not the index of the last one
2709 if ( (size_t)col
< s
.length() )
2711 // but don't return more than this (empty) string has
2719 // calc it if not done yet
2720 wReal
= GetTextWidth(s
.Truncate(col
));
2726 // VZ: old, horribly inefficient code which can still be used for checking
2727 // the result (in line, not word, wrap mode only) - to be removed later
2729 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
2730 wxClientDC
dc(self
);
2731 dc
.SetFont(GetFont());
2732 self
->DoPrepareDC(dc
);
2734 wxCoord widthMax
= m_rectText
.width
;
2736 // the text which we can keep in this ROW
2739 for ( wOld
= w
= 0; *text
&& (w
<= widthMax
); )
2743 dc
.GetTextExtent(str
, &w
, NULL
);
2748 // if we wrapped, the last letter was one too much
2749 if ( str
.length() > 1 )
2752 str
.erase(str
.length() - 1, 1);
2754 else // but always keep at least one letter in each row
2756 // the real width then is the last value of w and not teh one
2761 else // we didn't wrap
2766 wxASSERT( col
== str
.length() );
2770 wxASSERT( *widthReal
== wOld
);
2775 //return str.length();
2781 // OPT: this function is called a lot - would be nice to optimize it but I
2782 // don't really know how yet
2783 wxTextCtrlHitTestResult
wxTextCtrl::HitTestLine(const wxString
& line
,
2785 wxTextCoord
*colOut
) const
2787 wxTextCtrlHitTestResult res
= wxTE_HT_ON_TEXT
;
2790 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
2791 wxClientDC
dc(self
);
2792 dc
.SetFont(GetFont());
2793 self
->DoPrepareDC(dc
);
2796 dc
.GetTextExtent(line
, &width
, NULL
);
2799 // clicking beyond the end of line is equivalent to clicking at
2800 // the end of it, so return the last line column
2801 col
= line
.length();
2804 // unless the line is empty and so doesn't have any column at all -
2805 // in this case return 0, what else can we do?
2809 res
= wxTE_HT_BEYOND
;
2815 res
= wxTE_HT_BEFORE
;
2817 else // we're inside the line
2819 // now calculate the column: first, approximate it with fixed-width
2820 // value and then calculate the correct value iteratively: note that
2821 // we use the first character of the line instead of (average)
2822 // GetCharWidth(): it is common to have lines of dashes, for example,
2823 // and this should give us much better approximation in such case
2825 // OPT: maybe using (cache) m_widthAvg would be still faster? profile!
2826 dc
.GetTextExtent(line
[0], &width
, NULL
);
2833 else if ( (size_t)col
> line
.length() )
2835 col
= line
.length();
2838 // matchDir is the direction in which we should move to reach the
2839 // character containing the given position
2845 } matchDir
= Match_None
;
2848 // check that we didn't go beyond the line boundary
2854 if ( (size_t)col
> line
.length() )
2856 col
= line
.length();
2860 wxString
strBefore(line
, (size_t)col
);
2861 dc
.GetTextExtent(strBefore
, &width
, NULL
);
2864 if ( matchDir
== Match_Right
)
2866 // we were going to the right and, finally, moved beyond
2867 // the original position - stop on the previous one
2873 if ( matchDir
== Match_None
)
2875 // we just started iterating, now we know that we should
2877 matchDir
= Match_Left
;
2879 //else: we are still to the right of the target, continue
2883 // invert the logic above
2884 if ( matchDir
== Match_Left
)
2886 // with the exception that we don't need to backtrack here
2890 if ( matchDir
== Match_None
)
2893 matchDir
= Match_Right
;
2897 // this is not supposed to happen
2898 wxASSERT_MSG( matchDir
, _T("logic error in wxTextCtrl::HitTest") );
2900 if ( matchDir
== Match_Right
)
2907 // check that we calculated it correctly
2909 if ( res
== wxTE_HT_ON_TEXT
)
2912 wxString text
= line
.Left(col
);
2913 dc
.GetTextExtent(text
, &width1
, NULL
);
2914 if ( (size_t)col
< line
.length() )
2919 dc
.GetTextExtent(text
, &width2
, NULL
);
2921 wxASSERT_MSG( (width1
<= x
) && (x
< width2
),
2922 _T("incorrect HitTestLine() result") );
2924 else // we return last char
2926 wxASSERT_MSG( x
>= width1
, _T("incorrect HitTestLine() result") );
2929 #endif // WXDEBUG_TEXT
2937 wxTextCtrlHitTestResult
wxTextCtrl::HitTest(const wxPoint
& pos
,
2938 wxTextCoord
*colOut
,
2939 wxTextCoord
*rowOut
) const
2941 return HitTest2(pos
.y
, pos
.x
, 0, rowOut
, colOut
, NULL
, NULL
);
2944 wxTextCtrlHitTestResult
wxTextCtrl::HitTestLogical(const wxPoint
& pos
,
2945 wxTextCoord
*colOut
,
2946 wxTextCoord
*rowOut
) const
2948 return HitTest2(pos
.y
, pos
.x
, 0, rowOut
, colOut
, NULL
, NULL
, FALSE
);
2951 wxTextCtrlHitTestResult
wxTextCtrl::HitTest2(wxCoord y0
,
2954 wxTextCoord
*rowOut
,
2955 wxTextCoord
*colStart
,
2956 wxTextCoord
*colEnd
,
2957 wxTextCoord
*colRowStartOut
,
2958 bool deviceCoords
) const
2960 // is the point in the text area or to the right or below it?
2961 wxTextCtrlHitTestResult res
= wxTE_HT_ON_TEXT
;
2963 // translate the window coords x0 and y0 into the client coords in the text
2964 // area by adjusting for both the client and text area offsets (unless this
2965 // was already done)
2969 wxPoint pt
= GetClientAreaOrigin() + m_rectText
.GetPosition();
2970 CalcUnscrolledPosition(x10
- pt
.x
, y0
- pt
.y
, &x1
, &y
);
2978 // calculate the row (it is really a LINE, not a ROW)
2981 // these vars are used only for WrapLines() case
2982 wxTextCoord colRowStart
= 0;
2985 if ( colRowStartOut
)
2986 *colRowStartOut
= 0;
2988 int hLine
= GetLineHeight();
2991 // and clicking before it is the same as clicking on the first one
2994 res
= wxTE_HT_BEFORE
;
2998 wxTextCoord rowLast
= GetNumberOfLines() - 1;
3000 if ( IsSingleLine() || !WrapLines() )
3002 // in this case row calculation is simple as all lines have the
3003 // same height and so row is the same as line
3004 if ( row
> rowLast
)
3006 // clicking below the text is the same as clicking on the last
3010 res
= wxTE_HT_BELOW
;
3013 else // multline control with line wrap
3015 // use binary search to find the line containing this row
3016 const wxArrayWrappedLinesData
& linesData
= WData().m_linesData
;
3018 hi
= linesData
.GetCount(),
3023 const wxWrappedLineData
& lineData
= linesData
[cur
];
3024 if ( !WData().IsValidLine(cur
) )
3026 wxTextCoord rowFirst
= lineData
.GetFirstRow();
3028 if ( row
< rowFirst
)
3034 // our row is after the first row of the cur line:
3035 // obviously, if cur is the last line, it contains this
3036 // row, otherwise we have to test that it is before the
3037 // first row of the next line
3038 bool found
= cur
== linesData
.GetCount() - 1;
3041 // if the row is beyond the end of text, adjust it to
3042 // be the last one and set res accordingly
3043 if ( (size_t)(row
- rowFirst
) >= lineData
.GetRowCount() )
3045 res
= wxTE_HT_BELOW
;
3047 row
= lineData
.GetRowCount() + rowFirst
- 1;
3050 else // not the last row
3052 const wxWrappedLineData
&
3053 lineNextData
= linesData
[cur
+ 1];
3054 if ( !WData().IsValidLine(cur
+ 1) )
3055 LayoutLines(cur
+ 1);
3056 found
= row
< lineNextData
.GetFirstRow();
3061 colRowStart
= lineData
.GetRowStart(row
- rowFirst
);
3062 rowLen
= lineData
.GetRowLength(row
- rowFirst
,
3063 GetLines()[cur
].length());
3077 if ( res
== wxTE_HT_ON_TEXT
)
3079 // now find the position in the line
3080 wxString lineText
= GetLineText(row
),
3083 if ( colRowStart
|| rowLen
)
3085 // look in this row only, not in whole line
3086 rowText
= lineText
.Mid(colRowStart
, rowLen
);
3090 // just take the whole string
3096 res
= HitTestLine(GetTextToShow(rowText
), x1
, colStart
);
3100 if ( colRowStartOut
)
3102 // give them the column offset in this ROW in pixels
3103 *colRowStartOut
= colRowStart
;
3106 // take into account that the ROW doesn't start in the
3107 // beginning of the LINE
3108 *colStart
+= colRowStart
;
3113 // the hit test result we return is for x1, so throw out
3114 // the result for x2 here
3115 int x2
= x1
+ x20
- x10
;
3116 (void)HitTestLine(GetTextToShow(rowText
), x2
, colEnd
);
3118 *colEnd
+= colRowStart
;
3122 else // before/after vertical text span
3126 // fill the column with the first/last position in the
3127 // corresponding line
3128 if ( res
== wxTE_HT_BEFORE
)
3130 else // res == wxTE_HT_BELOW
3131 *colStart
= GetLineText(GetNumberOfLines() - 1).length();
3137 // give them the row in text coords (as is)
3144 bool wxTextCtrl::GetLineAndRow(wxTextCoord row
,
3145 wxTextCoord
*lineOut
,
3146 wxTextCoord
*rowInLineOut
) const
3154 int nLines
= GetNumberOfLines();
3157 const wxArrayWrappedLinesData
& linesData
= WData().m_linesData
;
3158 for ( line
= 0; line
< nLines
; line
++ )
3160 if ( !WData().IsValidLine(line
) )
3163 if ( row
< linesData
[line
].GetNextRow() )
3165 // we found the right line
3166 rowInLine
= row
- linesData
[line
].GetFirstRow();
3172 if ( line
== nLines
)
3174 // the row is out of range
3178 else // no line wrapping, everything is easy
3180 if ( row
>= nLines
)
3189 *rowInLineOut
= rowInLine
;
3194 // ----------------------------------------------------------------------------
3196 // ----------------------------------------------------------------------------
3199 wxTextCtrl has not one but two scrolling mechanisms: one is semi-automatic
3200 scrolling in both horizontal and vertical direction implemented using
3201 wxScrollHelper and the second one is manual scrolling implemented using
3202 SData().m_ofsHorz and used by the single line controls without scroll bar.
3204 The first version (the standard one) always scrolls by fixed amount which is
3205 fine for vertical scrolling as all lines have the same height but is rather
3206 ugly for horizontal scrolling if proportional font is used. This is why we
3207 manually update and use SData().m_ofsHorz which contains the length of the string
3208 which is hidden beyond the left borde. An important property of text
3209 controls using this kind of scrolling is that an entire number of characters
3210 is always shown and that parts of characters never appear on display -
3211 neither in the leftmost nor rightmost positions.
3213 Once again, for multi line controls SData().m_ofsHorz is always 0 and scrolling is
3214 done as usual for wxScrollWindow.
3217 void wxTextCtrl::ShowHorzPosition(wxCoord pos
)
3219 wxASSERT_MSG( IsSingleLine(), _T("doesn't work for multiline") );
3221 // pos is the logical position to show
3223 // SData().m_ofsHorz is the fisrt logical position shown
3224 if ( pos
< SData().m_ofsHorz
)
3228 HitTestLine(m_value
, pos
, &col
);
3233 wxCoord width
= m_rectText
.width
;
3236 // if we are called from the ctor, m_rectText is not initialized
3237 // yet, so do it now
3239 width
= m_rectText
.width
;
3242 // SData().m_ofsHorz + width is the last logical position shown
3243 if ( pos
> SData().m_ofsHorz
+ width
)
3247 HitTestLine(m_value
, pos
- width
, &col
);
3248 ScrollText(col
+ 1);
3253 // scroll the window horizontally so that the first visible character becomes
3254 // the one at this position
3255 void wxTextCtrl::ScrollText(wxTextCoord col
)
3257 wxASSERT_MSG( IsSingleLine(),
3258 _T("ScrollText() is for single line controls only") );
3260 // never scroll beyond the left border
3264 // OPT: could only get the extent of the part of the string between col
3265 // and SData().m_colStart
3266 wxCoord ofsHorz
= GetTextWidth(GetLineText(0).Left(col
));
3268 if ( ofsHorz
!= SData().m_ofsHorz
)
3270 // remember the last currently used pixel
3271 int posLastVisible
= SData().m_posLastVisible
;
3272 if ( posLastVisible
== -1 )
3274 // this may happen when we're called very early, during the
3275 // controls construction
3276 UpdateLastVisible();
3278 posLastVisible
= SData().m_posLastVisible
;
3281 // NB1: to scroll to the right, offset must be negative, hence the
3282 // order of operands
3283 int dx
= SData().m_ofsHorz
- ofsHorz
;
3285 // NB2: we call Refresh() below which results in a call to
3286 // DoDraw(), so we must update SData().m_ofsHorz before calling it
3287 SData().m_ofsHorz
= ofsHorz
;
3288 SData().m_colStart
= col
;
3290 // after changing m_colStart, recalc the last visible position: we need
3291 // to recalc the last visible position beore scrolling in order to make
3292 // it appear exactly at the right edge of the text area after scrolling
3293 UpdateLastVisible();
3298 // we want to force the update of it after scrolling
3299 SData().m_colLastVisible
= -1;
3303 // scroll only the rectangle inside which there is the text
3304 wxRect rect
= m_rectText
;
3305 rect
.width
= posLastVisible
;
3307 rect
= ScrollNoRefresh(dx
, 0, &rect
);
3310 we need to manually refresh the part which ScrollWindow() doesn't
3311 refresh (with new API this means the part outside the rect returned
3312 by ScrollNoRefresh): indeed, if we had this:
3316 where '*' is text and 'o' is blank area at the end (too small to
3317 hold the next char) then after scrolling by 2 positions to the left
3322 where 'R' is the area refreshed by ScrollWindow() - but we still
3323 need to refresh the 'o' at the end as it may be now big enough to
3324 hold the new character shifted into view.
3326 when we are scrolling to the right, we need to update this rect as
3327 well because it might have contained something before but doesn't
3328 contain anything any more
3331 // we can combine both rectangles into one when scrolling to the left,
3332 // but we need two separate Refreshes() otherwise
3335 // refresh the uncovered part on the left
3336 Refresh(TRUE
, &rect
);
3338 // and now the area on the right
3339 rect
.x
= m_rectText
.x
+ posLastVisible
;
3340 rect
.width
= m_rectText
.width
- posLastVisible
;
3342 else // scrolling to the left
3344 // just extend the rect covering the uncovered area to the edge of
3346 rect
.width
+= m_rectText
.width
- posLastVisible
;
3349 Refresh(TRUE
, &rect
);
3351 // I don't know exactly why is this needed here but without it we may
3352 // scroll the window again (from the same method) before the previously
3353 // invalidated area is repainted when typing *very* quickly - and this
3354 // may lead to the display corruption
3359 void wxTextCtrl::CalcUnscrolledPosition(int x
, int y
, int *xx
, int *yy
) const
3361 if ( IsSingleLine() )
3363 // we don't use wxScrollHelper
3365 *xx
= x
+ SData().m_ofsHorz
;
3371 // let the base class do it
3372 wxScrollHelper::CalcUnscrolledPosition(x
, y
, xx
, yy
);
3376 void wxTextCtrl::CalcScrolledPosition(int x
, int y
, int *xx
, int *yy
) const
3378 if ( IsSingleLine() )
3380 // we don't use wxScrollHelper
3382 *xx
= x
- SData().m_ofsHorz
;
3388 // let the base class do it
3389 wxScrollHelper::CalcScrolledPosition(x
, y
, xx
, yy
);
3393 void wxTextCtrl::DoPrepareDC(wxDC
& dc
)
3395 // for single line controls we only have to deal with SData().m_ofsHorz and it's
3396 // useless to call base class version as they don't use normal scrolling
3397 if ( IsSingleLine() && SData().m_ofsHorz
)
3399 // adjust the DC origin if the text is shifted
3400 wxPoint pt
= dc
.GetDeviceOrigin();
3401 dc
.SetDeviceOrigin(pt
.x
- SData().m_ofsHorz
, pt
.y
);
3405 wxScrollHelper::DoPrepareDC(dc
);
3409 void wxTextCtrl::UpdateMaxWidth(wxTextCoord line
)
3413 // check if the max width changes after this line was modified
3414 wxCoord widthMaxOld
= MData().m_widthMax
,
3416 GetTextExtent(GetLineText(line
), &width
, NULL
);
3418 if ( line
== MData().m_lineLongest
)
3420 // this line was the longest one, is it still?
3421 if ( width
> MData().m_widthMax
)
3423 MData().m_widthMax
= width
;
3425 else if ( width
< MData().m_widthMax
)
3427 // we need to find the new longest line
3430 //else: its length didn't change, nothing to do
3432 else // it wasn't the longest line, but maybe it became it?
3434 // GetMaxWidth() and not MData().m_widthMax as it might be not calculated yet
3435 if ( width
> GetMaxWidth() )
3437 MData().m_widthMax
= width
;
3438 MData().m_lineLongest
= line
;
3442 MData().m_updateScrollbarX
= MData().m_widthMax
!= widthMaxOld
;
3445 void wxTextCtrl::RecalcFontMetrics()
3447 m_heightLine
= GetCharHeight();
3448 m_widthAvg
= GetCharWidth();
3451 void wxTextCtrl::RecalcMaxWidth()
3453 wxASSERT_MSG( !IsSingleLine(), _T("only used for multiline") );
3455 MData().m_widthMax
= -1;
3456 (void)GetMaxWidth();
3459 wxCoord
wxTextCtrl::GetMaxWidth() const
3461 if ( MData().m_widthMax
== -1 )
3465 // OPT: should we remember the widths of all the lines?
3467 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
3468 wxClientDC
dc(self
);
3469 dc
.SetFont(GetFont());
3471 self
->MData().m_widthMax
= 0;
3473 size_t count
= GetLineCount();
3474 for ( size_t n
= 0; n
< count
; n
++ )
3477 dc
.GetTextExtent(GetLines()[n
], &width
, NULL
);
3478 if ( width
> MData().m_widthMax
)
3480 // remember the width and the line which has it
3481 self
->MData().m_widthMax
= width
;
3482 self
->MData().m_lineLongest
= n
;
3487 wxASSERT_MSG( MData().m_widthMax
!= -1, _T("should have at least 1 line") );
3489 return MData().m_widthMax
;
3492 void wxTextCtrl::UpdateScrollbars()
3494 wxASSERT_MSG( !IsSingleLine(), _T("only used for multiline") );
3496 wxSize size
= GetRealTextArea().GetSize();
3498 // is our height enough to show all items?
3499 wxTextCoord nRows
= GetRowCount();
3500 wxCoord lineHeight
= GetLineHeight();
3501 bool showScrollbarY
= nRows
*lineHeight
> size
.y
;
3503 // is our width enough to show the longest line?
3504 wxCoord charWidth
, maxWidth
;
3505 bool showScrollbarX
;
3508 charWidth
= GetAverageWidth();
3509 maxWidth
= GetMaxWidth();
3510 showScrollbarX
= maxWidth
> size
.x
;
3512 else // never show the horz scrollbar
3514 // just to suppress compiler warnings about using uninit vars below
3515 charWidth
= maxWidth
= 0;
3517 showScrollbarX
= FALSE
;
3520 // calc the scrollbars ranges
3521 int scrollRangeX
= showScrollbarX
3522 ? (maxWidth
+ 2*charWidth
- 1) / charWidth
3524 int scrollRangeY
= showScrollbarY
? nRows
: 0;
3526 int scrollRangeXOld
= MData().m_scrollRangeX
,
3527 scrollRangeYOld
= MData().m_scrollRangeY
;
3528 if ( (scrollRangeY
!= scrollRangeYOld
) || (scrollRangeX
!= scrollRangeXOld
) )
3531 GetViewStart(&x
, &y
);
3534 // we want to leave the scrollbars at the same position which means
3535 // that x and y have to be adjusted as the number of positions may have
3538 // the number of positions is calculated from knowing that last
3539 // position = range - thumbSize and thumbSize == pageSize which is
3540 // equal to the window width / pixelsPerLine
3541 if ( scrollRangeXOld
)
3543 x
*= scrollRangeX
- m_rectText
.width
/ charWidth
;
3544 x
/= scrollRangeXOld
- m_rectText
.width
/ charWidth
;
3547 if ( scrollRangeYOld
)
3548 y
*= scrollRangeY
/ scrollRangeYOld
;
3551 SetScrollbars(charWidth
, lineHeight
,
3552 scrollRangeX
, scrollRangeY
,
3554 TRUE
/* no refresh */);
3556 if ( scrollRangeXOld
)
3558 x
*= scrollRangeX
- m_rectText
.width
/ charWidth
;
3559 x
/= scrollRangeXOld
- m_rectText
.width
/ charWidth
;
3563 MData().m_scrollRangeX
= scrollRangeX
;
3564 MData().m_scrollRangeY
= scrollRangeY
;
3566 // bring the current position in view
3570 MData().m_updateScrollbarX
=
3571 MData().m_updateScrollbarY
= FALSE
;
3574 void wxTextCtrl::OnIdle(wxIdleEvent
& event
)
3576 // notice that single line text control never has scrollbars
3577 if ( !IsSingleLine() &&
3578 (MData().m_updateScrollbarX
|| MData().m_updateScrollbarY
) )
3586 bool wxTextCtrl::SendAutoScrollEvents(wxScrollWinEvent
& event
) const
3588 bool forward
= event
.GetEventType() == wxEVT_SCROLLWIN_LINEDOWN
;
3589 if ( event
.GetOrientation() == wxHORIZONTAL
)
3591 return forward
? m_curCol
<= GetLineLength(m_curRow
) : m_curCol
> 0;
3595 return forward
? m_curRow
< GetNumberOfLines() : m_curRow
> 0;
3599 // ----------------------------------------------------------------------------
3601 // ----------------------------------------------------------------------------
3603 void wxTextCtrl::RefreshSelection()
3605 if ( HasSelection() )
3607 RefreshTextRange(m_selStart
, m_selEnd
);
3611 void wxTextCtrl::RefreshLineRange(wxTextCoord lineFirst
, wxTextCoord lineLast
)
3613 wxASSERT_MSG( lineFirst
<= lineLast
|| !lineLast
,
3614 _T("no lines to refresh") );
3617 // rect.x is already 0
3618 rect
.width
= m_rectText
.width
;
3619 wxCoord h
= GetLineHeight();
3621 wxTextCoord rowFirst
;
3622 if ( lineFirst
< GetNumberOfLines() )
3624 rowFirst
= GetFirstRowOfLine(lineFirst
);
3626 else // lineFirst == GetNumberOfLines()
3628 // lineFirst may be beyond the last line only if we refresh till
3629 // the end, otherwise it's illegal
3630 wxASSERT_MSG( lineFirst
== GetNumberOfLines() && !lineLast
,
3631 _T("invalid line range") );
3633 rowFirst
= GetRowAfterLine(lineFirst
- 1);
3636 rect
.y
= rowFirst
*h
;
3640 // refresh till this line (inclusive)
3641 wxTextCoord rowLast
= GetRowAfterLine(lineLast
);
3643 rect
.height
= (rowLast
- rowFirst
+ 1)*h
;
3645 else // lineLast == 0 means to refresh till the end
3647 // FIXME: calc it exactly
3648 rect
.height
= 32000;
3651 RefreshTextRect(rect
);
3654 void wxTextCtrl::RefreshTextRange(wxTextPos start
, wxTextPos end
)
3656 wxCHECK_RET( start
!= -1 && end
!= -1,
3657 _T("invalid RefreshTextRange() arguments") );
3659 // accept arguments in any order as it is more conenient for the caller
3660 OrderPositions(start
, end
);
3662 // this is acceptable but we don't do anything in this case
3666 wxTextPos colStart
, lineStart
;
3667 if ( !PositionToXY(start
, &colStart
, &lineStart
) )
3669 // the range is entirely beyond the end of the text, nothing to do
3673 wxTextCoord colEnd
, lineEnd
;
3674 if ( !PositionToXY(end
, &colEnd
, &lineEnd
) )
3676 // the range spans beyond the end of text, refresh to the end
3678 lineEnd
= GetNumberOfLines() - 1;
3681 // refresh all lines one by one
3682 for ( wxTextCoord line
= lineStart
; line
<= lineEnd
; line
++ )
3684 // refresh the first line from the start of the range to the end, the
3685 // intermediate ones entirely and the last one from the beginning to
3686 // the end of the range
3687 wxTextPos posStart
= line
== lineStart
? colStart
: 0;
3689 if ( (line
!= lineEnd
) || (colEnd
== -1) )
3691 // intermediate line or the last one but we need to refresh it
3692 // until the end anyhow - do it
3693 posCount
= wxSTRING_MAXLEN
;
3697 // refresh just the positions in between the start and the end one
3698 posCount
= colEnd
- posStart
;
3702 RefreshColRange(line
, posStart
, posCount
);
3706 void wxTextCtrl::RefreshColRange(wxTextCoord line
,
3710 wxString text
= GetLineText(line
);
3712 wxASSERT_MSG( (size_t)start
<= text
.length() && count
,
3713 _T("invalid RefreshColRange() parameter") );
3715 RefreshPixelRange(line
,
3716 GetTextWidth(text
.Left((size_t)start
)),
3717 GetTextWidth(text
.Mid((size_t)start
, (size_t)count
)));
3720 // this method accepts "logical" coords in the sense that they are coordinates
3721 // in a logical line but it can span several rows if we wrap lines and
3722 // RefreshPixelRange() will then refresh several rows
3723 void wxTextCtrl::RefreshPixelRange(wxTextCoord line
,
3727 // we will use line text only in line wrap case
3731 text
= GetLineText(line
);
3734 // special case: width == 0 means to refresh till the end of line
3737 // refresh till the end of visible line
3738 width
= GetTotalWidth();
3742 // refresh till the end of text
3743 wxCoord widthAll
= GetTextWidth(text
);
3745 // extend width to the end of ROW
3746 width
= widthAll
- widthAll
% width
+ width
;
3749 // no need to refresh beyond the end of line
3752 //else: just refresh the specified part
3754 wxCoord h
= GetLineHeight();
3757 rect
.y
= GetFirstRowOfLine(line
)*h
;
3762 // (1) skip all rows which we don't touch at all
3763 const wxWrappedLineData
& lineData
= WData().m_linesData
[line
];
3764 if ( !WData().IsValidLine(line
) )
3767 wxCoord wLine
= 0; // suppress compiler warning about uninit var
3768 size_t rowLast
= lineData
.GetRowCount(),
3770 while ( (row
< rowLast
) &&
3771 (rect
.x
> (wLine
= lineData
.GetRowWidth(row
++))) )
3777 // (2) now refresh all lines except the last one: note that the first
3778 // line is refreshed from the given start to the end, all the next
3780 while ( (row
< rowLast
) && (width
> wLine
- rect
.x
) )
3782 rect
.width
= GetTotalWidth() - rect
.x
;
3783 RefreshTextRect(rect
);
3785 width
-= wLine
- rect
.x
;
3789 wLine
= lineData
.GetRowWidth(row
++);
3792 // (3) the code below will refresh the last line
3797 RefreshTextRect(rect
);
3800 void wxTextCtrl::RefreshTextRect(const wxRect
& rectClient
, bool textOnly
)
3803 CalcScrolledPosition(rectClient
.x
, rectClient
.y
, &rect
.x
, &rect
.y
);
3804 rect
.width
= rectClient
.width
;
3805 rect
.height
= rectClient
.height
;
3807 // account for the text area offset
3808 rect
.Offset(m_rectText
.GetPosition());
3810 // don't refresh beyond the text area unless we're refreshing the line wrap
3811 // marks in which case textOnly is FALSE
3814 if ( rect
.GetRight() > m_rectText
.GetRight() )
3816 rect
.SetRight(m_rectText
.GetRight());
3818 if ( rect
.width
<= 0 )
3820 // nothing to refresh
3826 // check the bottom boundary always, even for the line wrap marks
3827 if ( rect
.GetBottom() > m_rectText
.GetBottom() )
3829 rect
.SetBottom(m_rectText
.GetBottom());
3831 if ( rect
.height
<= 0 )
3833 // nothing to refresh
3838 // never refresh before the visible rect
3839 if ( rect
.x
< m_rectText
.x
)
3840 rect
.x
= m_rectText
.x
;
3842 if ( rect
.y
< m_rectText
.y
)
3843 rect
.y
= m_rectText
.y
;
3845 wxLogTrace(_T("text"), _T("Refreshing (%d, %d)-(%d, %d)"),
3846 rect
.x
, rect
.y
, rect
.x
+ rect
.width
, rect
.y
+ rect
.height
);
3848 Refresh(TRUE
, &rect
);
3851 void wxTextCtrl::RefreshLineWrapMarks(wxTextCoord rowFirst
,
3852 wxTextCoord rowLast
)
3854 if ( WData().m_widthMark
)
3857 rectMarks
.x
= m_rectText
.width
;
3858 rectMarks
.width
= WData().m_widthMark
;
3859 rectMarks
.y
= rowFirst
*GetLineHeight();
3860 rectMarks
.height
= (rowLast
- rowFirst
)*GetLineHeight();
3862 RefreshTextRect(rectMarks
, FALSE
/* don't limit to text area */);
3866 // ----------------------------------------------------------------------------
3868 // ----------------------------------------------------------------------------
3870 void wxTextCtrl::DoDrawBorder(wxDC
& dc
, const wxRect
& rect
)
3872 m_renderer
->DrawTextBorder(dc
, GetBorder(), rect
, GetStateFlags());
3875 // ----------------------------------------------------------------------------
3876 // client area drawing
3877 // ----------------------------------------------------------------------------
3880 Several remarks about wxTextCtrl redraw logic:
3882 1. only the regions which must be updated are redrawn, this means that we
3883 never Refresh() the entire window but use RefreshPixelRange() and
3884 ScrollWindow() which only refresh small parts of it and iterate over the
3885 update region in our DoDraw()
3887 2. the text displayed on the screen is obtained using GetTextToShow(): it
3888 should be used for all drawing/measuring
3891 wxString
wxTextCtrl::GetTextToShow(const wxString
& text
) const
3895 textShown
= wxString(_T('*'), text
.length());
3902 void wxTextCtrl::DoDrawTextInRect(wxDC
& dc
, const wxRect
& rectUpdate
)
3904 // debugging trick to see the update rect visually
3906 static int s_countUpdates
= -1;
3907 if ( s_countUpdates
!= -1 )
3909 wxWindowDC
dc(this);
3910 dc
.SetBrush(*(++s_countUpdates
% 2 ? wxRED_BRUSH
: wxGREEN_BRUSH
));
3911 dc
.SetPen(*wxTRANSPARENT_PEN
);
3912 dc
.DrawRectangle(rectUpdate
);
3914 #endif // WXDEBUG_TEXT
3916 // calculate the range lineStart..lineEnd of lines to redraw
3917 wxTextCoord lineStart
, lineEnd
;
3918 if ( IsSingleLine() )
3925 wxPoint pt
= rectUpdate
.GetPosition();
3926 (void)HitTest(pt
, NULL
, &lineStart
);
3928 pt
.y
+= rectUpdate
.height
;
3929 (void)HitTest(pt
, NULL
, &lineEnd
);
3932 // prepare for drawing
3933 wxCoord hLine
= GetLineHeight();
3935 // these vars will be used for hit testing of the current row
3936 wxCoord y
= rectUpdate
.y
;
3937 const wxCoord x1
= rectUpdate
.x
;
3938 const wxCoord x2
= rectUpdate
.x
+ rectUpdate
.width
;
3941 rectText
.height
= hLine
;
3942 wxCoord yClient
= y
- GetClientAreaOrigin().y
;
3944 // we want to always start at the top of the line, otherwise if we redraw a
3945 // rect whose top is in the middle of a line, we'd draw this line shifted
3946 yClient
-= (yClient
- m_rectText
.y
) % hLine
;
3948 if ( IsSingleLine() )
3950 rectText
.y
= yClient
;
3952 else // multiline, adjust for scrolling
3954 CalcUnscrolledPosition(0, yClient
, NULL
, &rectText
.y
);
3957 wxRenderer
*renderer
= GetRenderer();
3959 // do draw the invalidated parts of each line: note that we iterate here
3960 // over ROWs, not over LINEs
3961 for ( wxTextCoord line
= lineStart
;
3962 y
< rectUpdate
.y
+ rectUpdate
.height
;
3964 rectText
.y
+= hLine
)
3966 // calculate the update rect in text positions for this line
3967 wxTextCoord colStart
, colEnd
, colRowStart
;
3968 wxTextCtrlHitTestResult ht
= HitTest2(y
, x1
, x2
,
3969 &line
, &colStart
, &colEnd
,
3972 if ( (ht
== wxTE_HT_BEYOND
) || (ht
== wxTE_HT_BELOW
) )
3974 wxASSERT_MSG( line
<= lineEnd
, _T("how did we get that far?") );
3976 if ( line
== lineEnd
)
3978 // we redrew everything
3982 // the update rect is beyond the end of line, no need to redraw
3983 // anything on this line - but continue with the remaining ones
3987 // for single line controls we may additionally cut off everything
3988 // which is to the right of the last visible position
3989 if ( IsSingleLine() )
3991 // don't show the columns which are scrolled out to the left
3992 if ( colStart
< SData().m_colStart
)
3993 colStart
= SData().m_colStart
;
3995 // colEnd may be less than colStart if colStart was changed by the
3997 if ( colEnd
< colStart
)
4000 // don't draw the chars beyond the rightmost one
4001 if ( SData().m_colLastVisible
== -1 )
4003 // recalculate this rightmost column
4004 UpdateLastVisible();
4007 if ( colStart
> SData().m_colLastVisible
)
4009 // don't bother redrawing something that is beyond the last
4014 if ( colEnd
> SData().m_colLastVisible
)
4016 colEnd
= SData().m_colLastVisible
;
4020 // extract the part of line we need to redraw
4021 wxString textLine
= GetTextToShow(GetLineText(line
));
4022 wxString text
= textLine
.Mid(colStart
, colEnd
- colStart
+ 1);
4024 // now deal with the selection: only do something if at least part of
4025 // the line is selected
4026 wxTextPos selStart
, selEnd
;
4027 if ( GetSelectedPartOfLine(line
, &selStart
, &selEnd
) )
4029 // and if this part is (at least partly) in the current row
4030 if ( (selStart
<= colEnd
) &&
4031 (selEnd
>= wxMax(colStart
, colRowStart
)) )
4033 // these values are relative to the start of the line while the
4034 // string passed to DrawTextLine() is only part of it, so
4035 // adjust the selection range accordingly
4036 selStart
-= colStart
;
4042 if ( (size_t)selEnd
>= text
.length() )
4043 selEnd
= text
.length();
4047 // reset selStart and selEnd to avoid passing them to
4048 // DrawTextLine() below
4054 // calculate the text coords on screen
4055 wxASSERT_MSG( colStart
>= colRowStart
, _T("invalid string part") );
4056 wxCoord ofsStart
= GetTextWidth(
4057 textLine
.Mid(colRowStart
,
4058 colStart
- colRowStart
));
4059 rectText
.x
= m_rectText
.x
+ ofsStart
;
4060 rectText
.width
= GetTextWidth(text
);
4063 renderer
->DrawTextLine(dc
, text
, rectText
, selStart
, selEnd
,
4065 wxLogTrace(_T("text"), _T("Line %ld: positions %ld-%ld redrawn."),
4066 line
, colStart
, colEnd
);
4070 void wxTextCtrl::DoDrawLineWrapMarks(wxDC
& dc
, const wxRect
& rectUpdate
)
4072 wxASSERT_MSG( WrapLines() && WData().m_widthMark
,
4073 _T("shouldn't be called at all") );
4075 wxRenderer
*renderer
= GetRenderer();
4078 rectMark
.x
= rectUpdate
.x
;
4079 rectMark
.width
= rectUpdate
.width
;
4080 wxCoord yTop
= GetClientAreaOrigin().y
;
4081 CalcUnscrolledPosition(0, rectUpdate
.y
- yTop
, NULL
, &rectMark
.y
);
4082 wxCoord hLine
= GetLineHeight();
4083 rectMark
.height
= hLine
;
4085 wxTextCoord line
, rowInLine
;
4088 CalcUnscrolledPosition(0, rectUpdate
.GetBottom() - yTop
, NULL
, &yBottom
);
4089 for ( ; rectMark
.y
< yBottom
; rectMark
.y
+= hLine
)
4091 if ( !GetLineAndRow(rectMark
.y
/ hLine
, &line
, &rowInLine
) )
4093 // we went beyond the end of text
4097 // is this row continued on the next one?
4098 if ( !WData().m_linesData
[line
].IsLastRow(rowInLine
) )
4100 renderer
->DrawLineWrapMark(dc
, rectMark
);
4105 void wxTextCtrl::DoDraw(wxControlRenderer
*renderer
)
4107 // hide the caret while we're redrawing the window and show it after we are
4109 wxCaretSuspend
cs(this);
4112 wxDC
& dc
= renderer
->GetDC();
4113 dc
.SetFont(GetFont());
4114 dc
.SetTextForeground(GetForegroundColour());
4116 // get the intersection of the update region with the text area: note that
4117 // the update region is in window coords and text area is in the client
4118 // ones, so it must be shifted before computing intersection
4119 wxRegion rgnUpdate
= GetUpdateRegion();
4121 wxRect rectTextArea
= GetRealTextArea();
4122 wxPoint pt
= GetClientAreaOrigin();
4123 wxRect rectTextAreaAdjusted
= rectTextArea
;
4124 rectTextAreaAdjusted
.x
+= pt
.x
;
4125 rectTextAreaAdjusted
.y
+= pt
.y
;
4126 rgnUpdate
.Intersect(rectTextAreaAdjusted
);
4128 // even though the drawing is already clipped to the update region, we must
4129 // explicitly clip it to the rect we will use as otherwise parts of letters
4130 // might be drawn outside of it (if even a small part of a charater is
4131 // inside, HitTest() will return its column and DrawText() can't draw only
4132 // the part of the character, of course)
4134 // FIXME: is this really a bug in wxMSW?
4135 rectTextArea
.width
--;
4137 dc
.SetClippingRegion(rectTextArea
);
4139 // adjust for scrolling
4142 // and now refresh the invalidated parts of the window
4143 wxRegionIterator
iter(rgnUpdate
);
4144 for ( ; iter
.HaveRects(); iter
++ )
4146 wxRect r
= iter
.GetRect();
4148 // this is a workaround for wxGTK::wxRegion bug
4150 if ( !r
.width
|| !r
.height
)
4152 // ignore invalid rect
4157 DoDrawTextInRect(dc
, r
);
4160 // now redraw the line wrap marks (if we draw them)
4161 if ( WrapLines() && WData().m_widthMark
)
4163 // this is the rect inside which line wrap marks are drawn
4165 rectMarks
.x
= rectTextAreaAdjusted
.GetRight() + 1;
4166 rectMarks
.y
= rectTextAreaAdjusted
.y
;
4167 rectMarks
.width
= WData().m_widthMark
;
4168 rectMarks
.height
= rectTextAreaAdjusted
.height
;
4170 rgnUpdate
= GetUpdateRegion();
4171 rgnUpdate
.Intersect(rectMarks
);
4173 wxRect rectUpdate
= rgnUpdate
.GetBox();
4174 if ( rectUpdate
.width
&& rectUpdate
.height
)
4176 // the marks are outside previously set clipping region
4177 dc
.DestroyClippingRegion();
4179 DoDrawLineWrapMarks(dc
, rectUpdate
);
4183 // show caret first time only: we must show it after drawing the text or
4184 // the display can be corrupted when it's hidden
4185 if ( !m_hasCaret
&& GetCaret() )
4193 // ----------------------------------------------------------------------------
4195 // ----------------------------------------------------------------------------
4197 bool wxTextCtrl::SetFont(const wxFont
& font
)
4199 if ( !wxControl::SetFont(font
) )
4202 // and refresh everything, of course
4203 InitInsertionPoint();
4206 // update geometry parameters
4208 RecalcFontMetrics();
4209 if ( !IsSingleLine() )
4215 // recreate it, in fact
4223 bool wxTextCtrl::Enable(bool enable
)
4225 if ( !wxTextCtrlBase::Enable(enable
) )
4233 void wxTextCtrl::CreateCaret()
4239 // FIXME use renderer
4240 caret
= new wxCaret(this, 1, GetLineHeight());
4242 caret
->SetBlinkTime(0);
4247 // read only controls don't have the caret
4248 caret
= (wxCaret
*)NULL
;
4251 // SetCaret() will delete the old caret if any
4255 void wxTextCtrl::ShowCaret(bool show
)
4257 wxCaret
*caret
= GetCaret();
4260 // (re)position caret correctly
4261 caret
->Move(GetCaretPosition());
4263 // and show it there
4268 // ----------------------------------------------------------------------------
4269 // vertical scrolling (multiline only)
4270 // ----------------------------------------------------------------------------
4272 size_t wxTextCtrl::GetLinesPerPage() const
4274 if ( IsSingleLine() )
4277 return GetRealTextArea().height
/ GetLineHeight();
4280 wxTextPos
wxTextCtrl::GetPositionAbove()
4282 wxCHECK_MSG( !IsSingleLine(), INVALID_POS_VALUE
,
4283 _T("can't move cursor vertically in a single line control") );
4285 // move the cursor up by one ROW not by one LINE: this means that
4286 // we should really use HitTest() and not just go to the same
4287 // position in the previous line
4288 wxPoint pt
= GetCaretPosition() - m_rectText
.GetPosition();
4289 if ( MData().m_xCaret
== -1 )
4291 // remember the initial cursor abscissa
4292 MData().m_xCaret
= pt
.x
;
4296 // use the remembered abscissa
4297 pt
.x
= MData().m_xCaret
;
4300 CalcUnscrolledPosition(pt
.x
, pt
.y
, &pt
.x
, &pt
.y
);
4301 pt
.y
-= GetLineHeight();
4303 wxTextCoord col
, row
;
4304 if ( HitTestLogical(pt
, &col
, &row
) == wxTE_HT_BEFORE
)
4306 // can't move further
4307 return INVALID_POS_VALUE
;
4310 return XYToPosition(col
, row
);
4313 wxTextPos
wxTextCtrl::GetPositionBelow()
4315 wxCHECK_MSG( !IsSingleLine(), INVALID_POS_VALUE
,
4316 _T("can't move cursor vertically in a single line control") );
4318 // see comments for wxACTION_TEXT_UP
4319 wxPoint pt
= GetCaretPosition() - m_rectText
.GetPosition();
4320 if ( MData().m_xCaret
== -1 )
4322 // remember the initial cursor abscissa
4323 MData().m_xCaret
= pt
.x
;
4327 // use the remembered abscissa
4328 pt
.x
= MData().m_xCaret
;
4331 CalcUnscrolledPosition(pt
.x
, pt
.y
, &pt
.x
, &pt
.y
);
4332 pt
.y
+= GetLineHeight();
4334 wxTextCoord col
, row
;
4335 if ( HitTestLogical(pt
, &col
, &row
) == wxTE_HT_BELOW
)
4337 // can't go further down
4338 return INVALID_POS_VALUE
;
4341 // note that wxTE_HT_BEYOND is ok: it happens when we go down
4342 // from a longer line to a shorter one, for example (OTOH
4343 // wxTE_HT_BEFORE can never happen)
4344 return XYToPosition(col
, row
);
4347 // ----------------------------------------------------------------------------
4349 // ----------------------------------------------------------------------------
4351 bool wxTextCtrl::PerformAction(const wxControlAction
& actionOrig
,
4353 const wxString
& strArg
)
4355 // has the text changed as result of this action?
4356 bool textChanged
= FALSE
;
4358 // the remembered cursor abscissa for multiline text controls is usually
4359 // reset after each user action but for ones which do use it (UP and DOWN
4360 // for example) we shouldn't do it - as indicated by this flag
4361 bool rememberAbscissa
= FALSE
;
4363 // the command this action corresponds to or NULL if this action doesn't
4364 // change text at all or can't be undone
4365 wxTextCtrlCommand
*command
= (wxTextCtrlCommand
*)NULL
;
4370 if ( actionOrig
.StartsWith(wxACTION_TEXT_PREFIX_DEL
, &action
) )
4375 else if ( actionOrig
.StartsWith(wxACTION_TEXT_PREFIX_SEL
, &action
) )
4379 else // not selection nor delete action
4381 action
= actionOrig
;
4384 // set newPos to -2 as it can't become equal to it in the assignments below
4385 // (but it can become -1)
4386 wxTextPos newPos
= INVALID_POS_VALUE
;
4388 if ( action
== wxACTION_TEXT_HOME
)
4390 newPos
= m_curPos
- m_curCol
;
4392 else if ( action
== wxACTION_TEXT_END
)
4394 newPos
= m_curPos
+ GetLineLength(m_curRow
) - m_curCol
;
4396 else if ( (action
== wxACTION_TEXT_GOTO
) ||
4397 (action
== wxACTION_TEXT_FIRST
) ||
4398 (action
== wxACTION_TEXT_LAST
) )
4400 if ( action
== wxACTION_TEXT_FIRST
)
4402 else if ( action
== wxACTION_TEXT_LAST
)
4403 numArg
= GetLastPosition();
4404 //else: numArg already contains the position
4408 else if ( action
== wxACTION_TEXT_UP
)
4410 if ( !IsSingleLine() )
4412 newPos
= GetPositionAbove();
4414 if ( newPos
!= INVALID_POS_VALUE
)
4416 // remember where the cursor original had been
4417 rememberAbscissa
= TRUE
;
4421 else if ( action
== wxACTION_TEXT_DOWN
)
4423 if ( !IsSingleLine() )
4425 newPos
= GetPositionBelow();
4427 if ( newPos
!= INVALID_POS_VALUE
)
4429 // remember where the cursor original had been
4430 rememberAbscissa
= TRUE
;
4434 else if ( action
== wxACTION_TEXT_LEFT
)
4436 newPos
= m_curPos
- 1;
4438 else if ( action
== wxACTION_TEXT_WORD_LEFT
)
4440 newPos
= GetWordStart();
4442 else if ( action
== wxACTION_TEXT_RIGHT
)
4444 newPos
= m_curPos
+ 1;
4446 else if ( action
== wxACTION_TEXT_WORD_RIGHT
)
4448 newPos
= GetWordEnd();
4450 else if ( action
== wxACTION_TEXT_INSERT
)
4452 if ( IsEditable() && !strArg
.empty() )
4454 // inserting text can be undone
4455 command
= new wxTextCtrlInsertCommand(strArg
);
4460 else if ( (action
== wxACTION_TEXT_PAGE_UP
) ||
4461 (action
== wxACTION_TEXT_PAGE_DOWN
) )
4463 if ( !IsSingleLine() )
4465 size_t count
= GetLinesPerPage();
4466 if ( count
> PAGE_OVERLAP_IN_LINES
)
4468 // pages should overlap slightly to allow the reader to keep
4469 // orientation in the text
4470 count
-= PAGE_OVERLAP_IN_LINES
;
4473 // remember where the cursor original had been
4474 rememberAbscissa
= TRUE
;
4476 bool goUp
= action
== wxACTION_TEXT_PAGE_UP
;
4477 for ( size_t line
= 0; line
< count
; line
++ )
4479 wxTextPos pos
= goUp
? GetPositionAbove() : GetPositionBelow();
4480 if ( pos
== INVALID_POS_VALUE
)
4482 // can't move further
4486 MoveInsertionPoint(pos
);
4490 // we implement the Unix scrolling model here: cursor will always
4491 // be on the first line after Page Down and on the last one after
4494 // Windows programs usually keep the cursor line offset constant
4495 // but do we really need it?
4499 // find the line such that when it is the first one, the
4500 // current position is in the last line
4502 for ( size_t line
= 0; line
< count
; line
++ )
4504 pos
= GetPositionAbove();
4505 if ( pos
== INVALID_POS_VALUE
)
4508 MoveInsertionPoint(pos
);
4511 MoveInsertionPoint(newPos
);
4513 PositionToLogicalXY(pos
, NULL
, &y
);
4515 else // scrolled down
4517 PositionToLogicalXY(newPos
, NULL
, &y
);
4520 // scroll vertically only
4524 else if ( action
== wxACTION_TEXT_SEL_WORD
)
4526 SetSelection(GetWordStart(), GetWordEnd());
4528 else if ( action
== wxACTION_TEXT_ANCHOR_SEL
)
4532 else if ( action
== wxACTION_TEXT_EXTEND_SEL
)
4534 SetSelection(m_selAnchor
, numArg
);
4536 else if ( action
== wxACTION_TEXT_COPY
)
4540 else if ( action
== wxACTION_TEXT_CUT
)
4545 else if ( action
== wxACTION_TEXT_PASTE
)
4550 else if ( action
== wxACTION_TEXT_UNDO
)
4555 else if ( action
== wxACTION_TEXT_REDO
)
4562 return wxControl::PerformAction(action
, numArg
, strArg
);
4565 if ( newPos
!= INVALID_POS_VALUE
)
4567 // bring the new position into the range
4571 wxTextPos posLast
= GetLastPosition();
4572 if ( newPos
> posLast
)
4577 // if we have the selection, remove just it
4579 if ( HasSelection() )
4586 // otherwise delete everything between current position and
4588 if ( m_curPos
!= newPos
)
4593 else // nothing to delete
4595 // prevent test below from working
4596 from
= INVALID_POS_VALUE
;
4598 // and this is just to silent the compiler warning
4603 if ( from
!= INVALID_POS_VALUE
)
4605 command
= new wxTextCtrlRemoveCommand(from
, to
);
4608 else // cursor movement command
4611 DoSetInsertionPoint(newPos
);
4615 SetSelection(m_selAnchor
, m_curPos
);
4617 else // simple movement
4619 // clear the existing selection
4624 if ( !rememberAbscissa
&& !IsSingleLine() )
4626 MData().m_xCaret
= -1;
4632 // execute and remember it to be able to undo it later
4633 m_cmdProcessor
->Submit(command
);
4635 // undoable commands always change text
4638 else // no undoable command
4640 // m_cmdProcessor->StopCompressing()
4645 wxASSERT_MSG( IsEditable(), _T("non editable control changed?") );
4647 wxCommandEvent
event(wxEVT_COMMAND_TEXT_UPDATED
, GetId());
4648 InitCommandEvent(event
);
4649 event
.SetString(GetValue());
4650 GetEventHandler()->ProcessEvent(event
);
4652 // as the text changed...
4653 m_isModified
= TRUE
;
4659 void wxTextCtrl::OnChar(wxKeyEvent
& event
)
4661 // only process the key events from "simple keys" here
4662 if ( !event
.HasModifiers() )
4664 int keycode
= event
.GetKeyCode();
4665 if ( keycode
== WXK_RETURN
)
4667 if ( IsSingleLine() || (GetWindowStyle() & wxTE_PROCESS_ENTER
) )
4669 wxCommandEvent
event(wxEVT_COMMAND_TEXT_ENTER
, GetId());
4670 InitCommandEvent(event
);
4671 event
.SetString(GetValue());
4672 GetEventHandler()->ProcessEvent(event
);
4674 else // interpret <Enter> normally: insert new line
4676 PerformAction(wxACTION_TEXT_INSERT
, -1, _T('\n'));
4679 else if ( keycode
< 255 && isprint(keycode
) )
4681 PerformAction(wxACTION_TEXT_INSERT
, -1, (wxChar
)keycode
);
4683 // skip event.Skip() below
4688 // Ctrl-R refreshes the control in debug mode
4689 else if ( event
.ControlDown() && event
.GetKeyCode() == 'r' )
4691 #endif // __WXDEBUG__
4696 // ----------------------------------------------------------------------------
4697 // wxStdTextCtrlInputHandler
4698 // ----------------------------------------------------------------------------
4700 wxStdTextCtrlInputHandler::wxStdTextCtrlInputHandler(wxInputHandler
*inphand
)
4701 : wxStdInputHandler(inphand
)
4703 m_winCapture
= (wxTextCtrl
*)NULL
;
4707 wxTextPos
wxStdTextCtrlInputHandler::HitTest(const wxTextCtrl
*text
,
4710 wxTextCoord col
, row
;
4711 wxTextCtrlHitTestResult ht
= text
->HitTest(pt
, &col
, &row
);
4713 wxTextPos pos
= text
->XYToPosition(col
, row
);
4715 // if the point is after the last column we must adjust the position to be
4716 // the last position in the line (unless it is already the last)
4717 if ( (ht
== wxTE_HT_BEYOND
) && (pos
< text
->GetLastPosition()) )
4725 bool wxStdTextCtrlInputHandler::HandleKey(wxInputConsumer
*consumer
,
4726 const wxKeyEvent
& event
,
4729 // we're only interested in key presses
4733 int keycode
= event
.GetKeyCode();
4735 wxControlAction action
;
4737 bool ctrlDown
= event
.ControlDown(),
4738 shiftDown
= event
.ShiftDown();
4741 action
= wxACTION_TEXT_PREFIX_SEL
;
4744 // the only key combination with Alt we recognize is Alt-Bksp for undo, so
4745 // treat it first separately
4746 if ( event
.AltDown() )
4748 if ( keycode
== WXK_BACK
&& !ctrlDown
&& !shiftDown
)
4749 action
= wxACTION_TEXT_UNDO
;
4751 else switch ( keycode
)
4755 action
<< (ctrlDown
? wxACTION_TEXT_FIRST
4756 : wxACTION_TEXT_HOME
);
4760 action
<< (ctrlDown
? wxACTION_TEXT_LAST
4761 : wxACTION_TEXT_END
);
4766 action
<< wxACTION_TEXT_UP
;
4771 action
<< wxACTION_TEXT_DOWN
;
4775 action
<< (ctrlDown
? wxACTION_TEXT_WORD_LEFT
4776 : wxACTION_TEXT_LEFT
);
4780 action
<< (ctrlDown
? wxACTION_TEXT_WORD_RIGHT
4781 : wxACTION_TEXT_RIGHT
);
4786 // we don't map Ctrl-PgUp/Dn to anything special - what should it
4787 // to? for now, it's the same as without control
4788 action
<< wxACTION_TEXT_PAGE_DOWN
;
4793 action
<< wxACTION_TEXT_PAGE_UP
;
4799 action
<< wxACTION_TEXT_PREFIX_DEL
<< wxACTION_TEXT_RIGHT
;
4804 action
<< wxACTION_TEXT_PREFIX_DEL
<< wxACTION_TEXT_LEFT
;
4809 // reset the action as it could be already set to one of the
4811 action
= wxACTION_NONE
;
4818 action
= wxACTION_TEXT_REDO
;
4822 action
= wxACTION_TEXT_COPY
;
4826 action
= wxACTION_TEXT_PASTE
;
4830 action
= wxACTION_TEXT_CUT
;
4834 action
= wxACTION_TEXT_UNDO
;
4840 if ( (action
!= wxACTION_NONE
) && (action
!= wxACTION_TEXT_PREFIX_SEL
) )
4842 consumer
->PerformAction(action
, -1, str
);
4847 return wxStdInputHandler::HandleKey(consumer
, event
, pressed
);
4850 bool wxStdTextCtrlInputHandler::HandleMouse(wxInputConsumer
*consumer
,
4851 const wxMouseEvent
& event
)
4853 if ( event
.LeftDown() )
4855 wxASSERT_MSG( !m_winCapture
, _T("left button going down twice?") );
4857 wxTextCtrl
*text
= wxStaticCast(consumer
->GetInputWindow(), wxTextCtrl
);
4859 m_winCapture
= text
;
4860 m_winCapture
->CaptureMouse();
4864 wxTextPos pos
= HitTest(text
, event
.GetPosition());
4867 text
->PerformAction(wxACTION_TEXT_ANCHOR_SEL
, pos
);
4870 else if ( event
.LeftDClick() )
4872 // select the word the cursor is on
4873 consumer
->PerformAction(wxACTION_TEXT_SEL_WORD
);
4875 else if ( event
.LeftUp() )
4879 m_winCapture
->ShowCaret();
4881 m_winCapture
->ReleaseMouse();
4882 m_winCapture
= (wxTextCtrl
*)NULL
;
4886 return wxStdInputHandler::HandleMouse(consumer
, event
);
4889 bool wxStdTextCtrlInputHandler::HandleMouseMove(wxInputConsumer
*consumer
,
4890 const wxMouseEvent
& event
)
4895 wxTextCtrl
*text
= wxStaticCast(m_winCapture
, wxTextCtrl
);
4896 wxTextPos pos
= HitTest(text
, event
.GetPosition());
4899 text
->PerformAction(wxACTION_TEXT_EXTEND_SEL
, pos
);
4903 return wxStdInputHandler::HandleMouseMove(consumer
, event
);
4906 bool wxStdTextCtrlInputHandler::HandleFocus(wxInputConsumer
*consumer
,
4907 const wxFocusEvent
& event
)
4909 wxTextCtrl
*text
= wxStaticCast(consumer
->GetInputWindow(), wxTextCtrl
);
4911 // the selection appearance changes depending on whether we have the focus
4912 text
->RefreshSelection();
4914 // never refresh entirely
4918 #endif // wxUSE_TEXTCTRL