1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/univ/textctrl.cpp
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 2000 SciTech Software, Inc. (www.scitechsoft.com)
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
15 + 1. update vert scrollbar when any line length changes for WrapLines()
16 + 2. cursor movement ("Hello,^" -> "^verse!" on Arrow Down)?
17 -> maybe save the x position and use it instead of current in handling
18 DOWN/UP actions (this would make up/down always return the cursor to
20 3. split file into chunks
21 +? 4. rewrite Replace() refresh logic to deal with wrapping lines
22 +? 5. cache info found by GetPartOfWrappedLine() - performance must be horrible
25 6. backspace refreshes too much (until end of line)
29 Optimisation hints from PureQuantify:
31 +1. wxStringTokenize is the slowest part of Replace
32 2. GetDC/ReleaseDC are very slow, avoid calling them several times
33 +3. GetCharHeight() should be cached too
34 4. wxClientDC construction/destruction in HitTestLine is horribly expensive
36 For line wrapping controls HitTest2 takes 50% of program time. The results
37 of GetRowsPerLine and GetPartOfWrappedLine *MUST* be cached.
39 Search for "OPT!" for things which must be optimized.
45 Everywhere in this file LINE refers to a logical line of text, and ROW to a
46 physical line of text on the display. They are the same unless WrapLines()
47 is true in which case a single LINE may correspond to multiple ROWs.
49 A text position is an unsigned int (which for reasons of compatibility is
50 still a long as wxTextPos) from 0 to GetLastPosition() inclusive. The positions
51 correspond to the gaps between the letters so the position 0 is just
52 before the first character and the last position is the one beyond the last
53 character. For an empty text control GetLastPosition() returns 0.
55 Lines and columns returned/accepted by XYToPosition() and PositionToXY()
56 start from 0. The y coordinate is a LINE, not a ROW. Columns correspond to
57 the characters, the first column of a line is the first character in it,
58 the last one is length(line text). For compatibility, again, lines and
59 columns are also longs.
61 When translating lines/column coordinates to/from positions, the line and
62 column give the character after the given position. Thus, GetLastPosition()
63 doesn't have any corresponding column.
65 An example of positions and lines/columns for a control without wrapping
66 containing the text "Hello, Universe!\nGoodbye"
69 pos: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
70 H e l l o , U n i v e r s e ! line 0
71 col: 0 1 2 3 4 5 6 7 8 9 1 1 1 1 1 1
80 The same example for a control with line wrap assuming "Universe" is too
81 long to fit on the same line with "Hello,":
84 H e l l o , line 0 (row 0)
88 pos: 6 7 8 9 0 1 2 3 4 5 6
89 U n i v e r s e ! line 0 (row 1)
90 col: 6 7 8 9 1 1 1 1 1 1
93 (line 1 == row 2 same as above)
95 Note that there is still the same number of columns and positions and that
96 there is no (logical) position at the end of the first ROW. This position
97 is identified with the preceding one (which is not how Windows does it: it
98 identifies it with the next one, i.e. the first position of the next line,
99 but much more logical IMHO).
103 Search for "OPT" for possible optimizations
105 A possible global optimization would be to always store the coords in the
106 text in triplets (pos, col, line) and update them simultaneously instead of
107 recalculating col and line from pos each time it is needed. Currently we
108 only do it for the current position but we might also do it for the
109 selection start and end.
112 // ============================================================================
114 // ============================================================================
116 // ----------------------------------------------------------------------------
118 // ----------------------------------------------------------------------------
120 #include "wx/wxprec.h"
128 #include "wx/textctrl.h"
132 #include "wx/dcclient.h"
133 #include "wx/validate.h"
134 #include "wx/dataobj.h"
139 #include "wx/clipbrd.h"
141 #include "wx/textfile.h"
143 #include "wx/caret.h"
145 #include "wx/univ/inphand.h"
146 #include "wx/univ/renderer.h"
147 #include "wx/univ/colschem.h"
148 #include "wx/univ/theme.h"
150 #include "wx/cmdproc.h"
152 // turn extra wxTextCtrl-specific debugging on/off
155 // turn wxTextCtrl::Replace() debugging on (slows down code a *lot*!)
156 #define WXDEBUG_TEXT_REPLACE
160 #undef WXDEBUG_TEXT_REPLACE
163 // wxStringTokenize only needed for debug checks
164 #ifdef WXDEBUG_TEXT_REPLACE
165 #include "wx/tokenzr.h"
166 #endif // WXDEBUG_TEXT_REPLACE
168 // ----------------------------------------------------------------------------
169 // wxStdTextCtrlInputHandler: this control handles only the mouse/kbd actions
170 // common to Win32 and GTK, platform-specific things are implemented elsewhere
171 // ----------------------------------------------------------------------------
173 class WXDLLEXPORT wxStdTextCtrlInputHandler
: public wxStdInputHandler
176 wxStdTextCtrlInputHandler(wxInputHandler
*inphand
);
178 virtual bool HandleKey(wxInputConsumer
*consumer
,
179 const wxKeyEvent
& event
,
181 virtual bool HandleMouse(wxInputConsumer
*consumer
,
182 const wxMouseEvent
& event
);
183 virtual bool HandleMouseMove(wxInputConsumer
*consumer
,
184 const wxMouseEvent
& event
);
185 virtual bool HandleFocus(wxInputConsumer
*consumer
, const wxFocusEvent
& event
);
188 // get the position of the mouse click
189 static wxTextPos
HitTest(const wxTextCtrl
*text
, const wxPoint
& pos
);
192 wxTextCtrl
*m_winCapture
;
195 // ----------------------------------------------------------------------------
197 // ----------------------------------------------------------------------------
199 // exchange two positions so that from is always less than or equal to to
200 static inline void OrderPositions(wxTextPos
& from
, wxTextPos
& to
)
204 wxTextPos tmp
= from
;
210 // ----------------------------------------------------------------------------
212 // ----------------------------------------------------------------------------
214 // names of text ctrl commands
215 #define wxTEXT_COMMAND_INSERT _T("insert")
216 #define wxTEXT_COMMAND_REMOVE _T("remove")
218 // the value which is never used for text position, even not -1 which is
219 // sometimes used for some special meaning
220 static const wxTextPos INVALID_POS_VALUE
= wxInvalidTextCoord
;
222 // overlap between pages (when using PageUp/Dn) in lines
223 static const size_t PAGE_OVERLAP_IN_LINES
= 1;
225 // ----------------------------------------------------------------------------
226 // private data of wxTextCtrl
227 // ----------------------------------------------------------------------------
229 // the data only used by single line text controls
230 struct wxTextSingleLineData
232 // the position of the first visible pixel and the first visible column
234 wxTextCoord m_colStart
;
236 // and the last ones (m_posLastVisible is the width but m_colLastVisible
237 // is an absolute value)
238 wxCoord m_posLastVisible
;
239 wxTextCoord m_colLastVisible
;
242 wxTextSingleLineData()
247 m_colLastVisible
= -1;
248 m_posLastVisible
= -1;
253 // the data only used by multi line text controls
254 struct wxTextMultiLineData
257 wxArrayString m_lines
;
259 // the current ranges of the scrollbars
263 // should we adjust the horz/vert scrollbar?
264 bool m_updateScrollbarX
,
267 // the max line length in pixels
270 // the index of the line which has the length of m_widthMax
271 wxTextCoord m_lineLongest
;
273 // the rect in which text appears: it is even less than m_rectText because
274 // only the last _complete_ line is shown, hence there is an unoccupied
275 // horizontal band at the bottom of it
276 wxRect m_rectTextReal
;
278 // the x-coordinate of the caret before we started moving it vertically:
279 // this is used to ensure that moving the caret up and then down will
280 // return it to the same position as if we always round it in one direction
281 // we would shift it in that direction
283 // when m_xCaret == -1, we don't have any remembered position
287 wxTextMultiLineData()
293 m_updateScrollbarY
= false;
302 // the data only used by multi line text controls in line wrap mode
303 class wxWrappedLineData
305 // these functions set all our values, so give them access to them
306 friend void wxTextCtrl::LayoutLine(wxTextCoord line
,
307 wxWrappedLineData
& lineData
) const;
308 friend void wxTextCtrl::LayoutLines(wxTextCoord
) const;
317 // get the start of any row (remember that accessing m_rowsStart doesn't work
318 // for the first one)
319 wxTextCoord
GetRowStart(wxTextCoord row
) const
321 wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
323 return row
? m_rowsStart
[row
- 1] : 0;
326 // get the length of the row (using the total line length which we don't
327 // have here but need to calculate the length of the last row, so it must
329 wxTextCoord
GetRowLength(wxTextCoord row
, wxTextCoord lenLine
) const
331 wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
333 // note that m_rowsStart[row] is the same as GetRowStart(row + 1) (but
334 // slightly more efficient) and lenLine is the same as the start of the
335 // first row of the next line
336 return ((size_t)row
== m_rowsStart
.GetCount() ? lenLine
: m_rowsStart
[row
])
340 // return the width of the row in pixels
341 wxCoord
GetRowWidth(wxTextCoord row
) const
343 wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
345 return m_rowsWidth
[row
];
348 // return the number of rows
349 size_t GetRowCount() const
351 wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
353 return m_rowsStart
.GetCount() + 1;
356 // return the number of additional (i.e. after the first one) rows
357 size_t GetExtraRowCount() const
359 wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
361 return m_rowsStart
.GetCount();
364 // return the first row of this line
365 wxTextCoord
GetFirstRow() const
367 wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
372 // return the first row of the next line
373 wxTextCoord
GetNextRow() const
375 wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
377 return m_rowFirst
+ m_rowsStart
.GetCount() + 1;
380 // this just provides direct access to m_rowsStart aerray for efficiency
381 wxTextCoord
GetExtraRowStart(wxTextCoord row
) const
383 wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
385 return m_rowsStart
[row
];
388 // this code is unused any longer
390 // return true if the column is in the start of the last row (hence the row
391 // it is in is not wrapped)
392 bool IsLastRow(wxTextCoord colRowStart
) const
394 return colRowStart
== GetRowStart(m_rowsStart
.GetCount());
397 // return true if the column is the last column of the row starting in
399 bool IsLastColInRow(wxTextCoord colRowStart
,
400 wxTextCoord colRowEnd
,
401 wxTextCoord lenLine
) const
403 // find the row which starts with colRowStart
404 size_t nRows
= GetRowCount();
405 for ( size_t n
= 0; n
< nRows
; n
++ )
407 if ( GetRowStart(n
) == colRowStart
)
409 wxTextCoord colNextRowStart
= n
== nRows
- 1
411 : GetRowStart(n
+ 1);
413 wxASSERT_MSG( colRowEnd
< colNextRowStart
,
414 _T("this column is not in this row at all!") );
416 return colRowEnd
== colNextRowStart
- 1;
420 // caller got it wrong
421 wxFAIL_MSG( _T("this column is not in the start of the row!") );
427 // is this row the last one in its line?
428 bool IsLastRow(wxTextCoord row
) const
430 return (size_t)row
== GetExtraRowCount();
433 // the line is valid if it had been laid out correctly: note that just
434 // shiwting the line (because one of previous lines changed) doesn't make
436 bool IsValid() const { return !m_rowsWidth
.IsEmpty(); }
438 // invalidating line will relayout it
439 void Invalidate() { m_rowsWidth
.Empty(); }
442 // for each line we remember the starting columns of all its rows after the
443 // first one (which always starts at 0), i.e. if a line is wrapped twice
444 // (== takes 3 rows) its m_rowsStart[0] may be 10 and m_rowsStart[1] == 15
445 wxArrayLong m_rowsStart
;
447 // and the width of each row in pixels (this array starts from 0, as usual)
448 wxArrayInt m_rowsWidth
;
450 // and also its starting row (0 for the first line, first lines'
451 // m_rowsStart.GetCount() + 1 for the second &c): it is set to -1 initially
452 // and this means that the struct hadn't yet been initialized
453 wxTextCoord m_rowFirst
;
455 // the last modification "time"-stamp used by LayoutLines()
459 WX_DECLARE_OBJARRAY(wxWrappedLineData
, wxArrayWrappedLinesData
);
460 #include "wx/arrimpl.cpp"
461 WX_DEFINE_OBJARRAY(wxArrayWrappedLinesData
);
463 struct wxTextWrappedData
: public wxTextMultiLineData
465 // the width of the column to the right of the text rect used for the
466 // indicator mark display for the wrapped lines
469 // the data for each line
470 wxArrayWrappedLinesData m_linesData
;
472 // flag telling us to recalculate all starting rows starting from this line
473 // (if it is -1, we don't have to recalculate anything) - it is set when
474 // the number of the rows in the middle of the control changes
475 wxTextCoord m_rowFirstInvalid
;
477 // the current timestamp used by LayoutLines()
480 // invalidate starting rows of all lines (NOT rows!) after this one
481 void InvalidateLinesBelow(wxTextCoord line
)
483 if ( m_rowFirstInvalid
== -1 || m_rowFirstInvalid
> line
)
485 m_rowFirstInvalid
= line
;
489 // check if this line is valid: i.e. before the first invalid one
490 bool IsValidLine(wxTextCoord line
) const
492 return ((m_rowFirstInvalid
== -1) || (line
< m_rowFirstInvalid
)) &&
493 m_linesData
[line
].IsValid();
500 m_rowFirstInvalid
= -1;
505 // ----------------------------------------------------------------------------
506 // private classes for undo/redo management
507 // ----------------------------------------------------------------------------
510 We use custom versions of wxWidgets command processor to implement undo/redo
511 as we want to avoid storing the backpointer to wxTextCtrl in wxCommand
512 itself: this is a waste of memory as all commands in the given command
513 processor always have the same associated wxTextCtrl and so it makes sense
514 to store the backpointer there.
516 As for the rest of the implementation, it's fairly standard: we have 2
517 command classes corresponding to adding and removing text.
520 // a command corresponding to a wxTextCtrl action
521 class wxTextCtrlCommand
: public wxCommand
524 wxTextCtrlCommand(const wxString
& name
) : wxCommand(true, name
) { }
526 // we don't use these methods as they don't make sense for us as we need a
527 // wxTextCtrl to be applied
528 virtual bool Do() { wxFAIL_MSG(_T("shouldn't be called")); return false; }
529 virtual bool Undo() { wxFAIL_MSG(_T("shouldn't be called")); return false; }
531 // instead, our command processor uses these methods
532 virtual bool Do(wxTextCtrl
*text
) = 0;
533 virtual bool Undo(wxTextCtrl
*text
) = 0;
536 // insert text command
537 class wxTextCtrlInsertCommand
: public wxTextCtrlCommand
540 wxTextCtrlInsertCommand(const wxString
& textToInsert
)
541 : wxTextCtrlCommand(wxTEXT_COMMAND_INSERT
), m_text(textToInsert
)
546 // combine the 2 commands together
547 void Append(wxTextCtrlInsertCommand
*other
);
549 virtual bool CanUndo() const;
550 virtual bool Do(wxTextCtrl
*text
);
551 virtual bool Do() { return wxTextCtrlCommand::Do(); }
552 virtual bool Undo() { return wxTextCtrlCommand::Undo(); }
553 virtual bool Undo(wxTextCtrl
*text
);
556 // the text we insert
559 // the position where we inserted the text
563 // remove text command
564 class wxTextCtrlRemoveCommand
: public wxTextCtrlCommand
567 wxTextCtrlRemoveCommand(wxTextPos from
, wxTextPos to
)
568 : wxTextCtrlCommand(wxTEXT_COMMAND_REMOVE
)
574 virtual bool CanUndo() const;
575 virtual bool Do(wxTextCtrl
*text
);
576 virtual bool Do() { return wxTextCtrlCommand::Do(); }
577 virtual bool Undo() { return wxTextCtrlCommand::Undo(); }
578 virtual bool Undo(wxTextCtrl
*text
);
581 // the range of text to delete
585 // the text which was deleted when this command was Do()ne
586 wxString m_textDeleted
;
589 // a command processor for a wxTextCtrl
590 class wxTextCtrlCommandProcessor
: public wxCommandProcessor
593 wxTextCtrlCommandProcessor(wxTextCtrl
*text
)
595 m_compressInserts
= false;
600 // override Store() to compress multiple wxTextCtrlInsertCommand into one
601 virtual void Store(wxCommand
*command
);
603 // stop compressing insert commands when this is called
604 void StopCompressing() { m_compressInserts
= false; }
607 wxTextCtrl
*GetTextCtrl() const { return m_text
; }
608 bool IsCompressing() const { return m_compressInserts
; }
611 virtual bool DoCommand(wxCommand
& cmd
)
612 { return ((wxTextCtrlCommand
&)cmd
).Do(m_text
); }
613 virtual bool UndoCommand(wxCommand
& cmd
)
614 { return ((wxTextCtrlCommand
&)cmd
).Undo(m_text
); }
616 // check if this command is a wxTextCtrlInsertCommand and return it casted
617 // to the right type if it is or NULL otherwise
618 wxTextCtrlInsertCommand
*IsInsertCommand(wxCommand
*cmd
);
621 // the control we're associated with
624 // if the flag is true we're compressing subsequent insert commands into
625 // one so that the entire typing could be undone in one call to Undo()
626 bool m_compressInserts
;
629 // ============================================================================
631 // ============================================================================
633 BEGIN_EVENT_TABLE(wxTextCtrl
, wxTextCtrlBase
)
634 EVT_CHAR(wxTextCtrl::OnChar
)
636 EVT_SIZE(wxTextCtrl::OnSize
)
639 IMPLEMENT_DYNAMIC_CLASS(wxTextCtrl
, wxTextCtrlBase
)
641 // ----------------------------------------------------------------------------
643 // ----------------------------------------------------------------------------
645 void wxTextCtrl::Init()
651 m_isModified
= false;
663 // init the undo manager
664 m_cmdProcessor
= new wxTextCtrlCommandProcessor(this);
670 bool wxTextCtrl::Create(wxWindow
*parent
,
672 const wxString
& value
,
676 const wxValidator
& validator
,
677 const wxString
&name
)
679 if ( style
& wxTE_MULTILINE
)
681 // for compatibility with wxMSW we create the controls with vertical
682 // scrollbar always shown unless they have wxTE_RICH style (because
683 // Windows text controls always has vert scrollbar but richedit one
685 if ( !(style
& wxTE_RICH
) )
687 style
|= wxALWAYS_SHOW_SB
;
690 // wrapping style: wxTE_DONTWRAP == wxHSCROLL so if it's _not_ given,
691 // we won't have horizontal scrollbar automatically, no need to do
694 // TODO: support wxTE_NO_VSCROLL (?)
696 // create data object for normal multiline or for controls with line
698 if ( style
& wxHSCROLL
)
700 m_data
.mdata
= new wxTextMultiLineData
;
702 else // we must wrap lines if we don't have horizontal scrollbar
704 // NB: we can't rely on HasFlag(wxHSCROLL) as the flags can change
705 // later and even wxWindow::Create() itself temporarily resets
706 // wxHSCROLL in wxUniv, so remember that we have a wrapped data
707 // and not just a multi line data in a separate variable
709 m_data
.wdata
= new wxTextWrappedData
;
714 // this doesn't make sense for single line controls
717 // create data object for single line controls
718 m_data
.sdata
= new wxTextSingleLineData
;
721 #if wxUSE_TWO_WINDOWS
722 if ((style
& wxBORDER_MASK
) == 0)
723 style
|= wxBORDER_SUNKEN
;
726 if ( !wxControl::Create(parent
, id
, pos
, size
, style
,
732 SetCursor(wxCURSOR_IBEAM
);
734 if ( style
& wxTE_MULTILINE
)
736 // we should always have at least one line in a multiline control
737 MData().m_lines
.Add(wxEmptyString
);
739 if ( !(style
& wxHSCROLL
) )
741 WData().m_linesData
.Add(new wxWrappedLineData
);
742 WData().InvalidateLinesBelow(0);
745 // we might support it but it's quite useless and other ports don't
747 wxASSERT_MSG( !(style
& wxTE_PASSWORD
),
748 _T("wxTE_PASSWORD can't be used with multiline ctrls") );
753 SetInitialSize(size
);
755 m_isEditable
= !(style
& wxTE_READONLY
);
758 InitInsertionPoint();
760 // we can't show caret right now as we're not shown yet and so it would
761 // result in garbage on the screen - we'll do it after first OnPaint()
764 CreateInputHandler(wxINP_HANDLER_TEXTCTRL
);
766 wxSizeEvent
sizeEvent(GetSize(), GetId());
767 GetEventHandler()->ProcessEvent(sizeEvent
);
772 wxTextCtrl::~wxTextCtrl()
774 delete m_cmdProcessor
;
778 if ( IsSingleLine() )
780 else if ( WrapLines() )
787 // ----------------------------------------------------------------------------
789 // ----------------------------------------------------------------------------
791 void wxTextCtrl::DoSetValue(const wxString
& value
, int flags
)
793 if ( value
!= GetValue() )
795 EventsSuppressor
noeventsIf(this, !(flags
& SetValue_SendEvent
));
797 Replace(0, GetLastPosition(), value
);
799 if ( IsSingleLine() )
801 SetInsertionPoint(0);
804 else // nothing changed
806 // still send event for consistency
807 if ( flags
& SetValue_SendEvent
)
808 SendTextUpdatedEvent();
812 const wxArrayString
& wxTextCtrl::GetLines() const
814 return MData().m_lines
;
817 size_t wxTextCtrl::GetLineCount() const
819 return MData().m_lines
.GetCount();
822 wxString
wxTextCtrl::GetValue() const
824 // for multiline controls we don't always store the total value but only
825 // recompute it when asked - and to invalidate it we just empty it in
827 if ( !IsSingleLine() && m_value
.empty() )
829 // recalculate: note that we always do it for empty multilien control,
830 // but then it's so quick that it's not important
832 // the first line is special as there is no \n before it, so it's
834 const wxArrayString
& lines
= GetLines();
835 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
836 self
->m_value
<< lines
[0u];
837 size_t count
= lines
.GetCount();
838 for ( size_t n
= 1; n
< count
; n
++ )
840 self
->m_value
<< _T('\n') << lines
[n
];
847 void wxTextCtrl::Clear()
849 SetValue(wxEmptyString
);
852 bool wxTextCtrl::ReplaceLine(wxTextCoord line
,
853 const wxString
& text
)
857 // first, we have to relayout the line entirely
859 // OPT: we might try not to recalc the unchanged part of line
861 wxWrappedLineData
& lineData
= WData().m_linesData
[line
];
863 // if we had some number of rows before, use this number, otherwise
864 // just make sure that the test below (rowsNew != rowsOld) will be true
866 if ( lineData
.IsValid() )
868 rowsOld
= lineData
.GetExtraRowCount();
870 else // line wasn't laid out yet
872 // assume it changed entirely as we can't do anything better
876 // now change the line
877 MData().m_lines
[line
] = text
;
879 // OPT: we choose to lay it our immediately instead of delaying it
880 // until it is needed because it allows us to avoid invalidating
881 // lines further down if the number of rows didn't chnage, but
882 // maybe we can imporve this even further?
883 LayoutLine(line
, lineData
);
885 int rowsNew
= lineData
.GetExtraRowCount();
887 if ( rowsNew
!= rowsOld
)
889 // we have to update the line wrap marks as this is normally done
890 // by LayoutLines() which we bypassed by calling LayoutLine()
892 wxTextCoord rowFirst
= lineData
.GetFirstRow(),
893 rowCount
= wxMax(rowsOld
, rowsNew
);
894 RefreshLineWrapMarks(rowFirst
, rowFirst
+ rowCount
);
896 // next, if this is not the last line, as the number of rows in it
897 // changed, we need to shift all the lines below it
898 if ( (size_t)line
< WData().m_linesData
.GetCount() )
900 // number of rows changed shifting all lines below
901 WData().InvalidateLinesBelow(line
+ 1);
904 // the number of rows changed
910 MData().m_lines
[line
] = text
;
913 // the number of rows didn't change
917 void wxTextCtrl::RemoveLine(wxTextCoord line
)
919 MData().m_lines
.RemoveAt(line
);
922 // we need to recalculate all the starting rows from this line, but we
923 // can avoid doing it if this line was never calculated: this means
924 // that we will recalculate all lines below it anyhow later if needed
925 if ( WData().IsValidLine(line
) )
927 WData().InvalidateLinesBelow(line
);
930 WData().m_linesData
.RemoveAt(line
);
934 void wxTextCtrl::InsertLine(wxTextCoord line
, const wxString
& text
)
936 MData().m_lines
.Insert(text
, line
);
939 WData().m_linesData
.Insert(new wxWrappedLineData
, line
);
941 // invalidate everything below it
942 WData().InvalidateLinesBelow(line
);
946 void wxTextCtrl::Replace(wxTextPos from
, wxTextPos to
, const wxString
& text
)
948 wxTextCoord colStart
, colEnd
,
952 !PositionToXY(from
, &colStart
, &lineStart
) ||
953 !PositionToXY(to
, &colEnd
, &lineEnd
) )
955 wxFAIL_MSG(_T("invalid range in wxTextCtrl::Replace"));
960 #ifdef WXDEBUG_TEXT_REPLACE
961 // a straighforward (but very inefficient) way of calculating what the new
963 wxString textTotal
= GetValue();
964 wxString
textTotalNew(textTotal
, (size_t)from
);
965 textTotalNew
+= text
;
966 if ( (size_t)to
< textTotal
.length() )
967 textTotalNew
+= textTotal
.c_str() + (size_t)to
;
968 #endif // WXDEBUG_TEXT_REPLACE
970 // remember the old selection and reset it immediately: we must do it
971 // before calling Refresh(anything) as, at least under GTK, this leads to
972 // an _immediate_ repaint (under MSW it is delayed) and hence parts of
973 // text would be redrawn as selected if we didn't reset the selection
974 int selStartOld
= m_selStart
,
975 selEndOld
= m_selEnd
;
980 if ( IsSingleLine() )
982 // replace the part of the text with the new value
983 wxString
valueNew(m_value
, (size_t)from
);
985 // remember it for later use
986 wxCoord startNewText
= GetTextWidth(valueNew
);
989 if ( (size_t)to
< m_value
.length() )
991 valueNew
+= m_value
.c_str() + (size_t)to
;
994 // we usually refresh till the end of line except of the most common case
995 // when some text is appended to the end of the string in which case we
997 wxCoord widthNewText
;
999 if ( (size_t)from
< m_value
.length() )
1001 // refresh till the end of line
1004 else // text appended, not replaced
1006 // refresh only the new text
1007 widthNewText
= GetTextWidth(text
);
1012 // force SData().m_colLastVisible update
1013 SData().m_colLastVisible
= -1;
1016 RefreshPixelRange(0, startNewText
, widthNewText
);
1020 //OPT: special case for replacements inside single line?
1023 Join all the lines in the replacement range into one string, then
1024 replace a part of it with the new text and break it into lines again.
1027 // (0) we want to know if this replacement changes the number of rows
1028 // as if it does we need to refresh everything below the changed
1029 // text (it will be shifted...) and we can avoid it if there is no
1031 bool rowsNumberChanged
= false;
1034 const wxArrayString
& linesOld
= GetLines();
1037 for ( line
= lineStart
; line
<= lineEnd
; line
++ )
1039 if ( line
> lineStart
)
1041 // from the previous line
1042 textOrig
+= _T('\n');
1045 textOrig
+= linesOld
[line
];
1048 // we need to append the '\n' for the last line unless there is no
1050 size_t countOld
= linesOld
.GetCount();
1052 // (2) replace text in the combined string
1054 // (2a) leave the part before replaced area unchanged
1055 wxString
textNew(textOrig
, colStart
);
1057 // these values will be used to refresh the changed area below
1058 wxCoord widthNewText
,
1059 startNewText
= GetTextWidth(textNew
);
1060 if ( (size_t)colStart
== linesOld
[lineStart
].length() )
1062 // text appended, refresh just enough to show the new text
1063 widthNewText
= GetTextWidth(text
.BeforeFirst(_T('\n')));
1065 else // text inserted, refresh till the end of line
1070 // (2b) insert new text
1073 // (2c) and append the end of the old text
1075 // adjust for index shift: to is relative to colStart, not 0
1076 size_t toRel
= (size_t)((to
- from
) + colStart
);
1077 if ( toRel
< textOrig
.length() )
1079 textNew
+= textOrig
.c_str() + toRel
;
1082 // (3) break it into lines
1084 wxArrayString lines
;
1085 const wxChar
*curLineStart
= textNew
.c_str();
1086 for ( const wxChar
*p
= textNew
.c_str(); ; p
++ )
1088 // end of line/text?
1089 if ( !*p
|| *p
== _T('\n') )
1091 lines
.Add(wxString(curLineStart
, p
));
1095 curLineStart
= p
+ 1;
1099 #ifdef WXDEBUG_TEXT_REPLACE
1100 // (3a) all empty tokens should be counted as replacing with "foo" and
1101 // with "foo\n" should have different effects
1102 wxArrayString lines2
= wxStringTokenize(textNew
, _T("\n"),
1103 wxTOKEN_RET_EMPTY_ALL
);
1105 if ( lines2
.IsEmpty() )
1107 lines2
.Add(wxEmptyString
);
1110 wxASSERT_MSG( lines
.GetCount() == lines2
.GetCount(),
1111 _T("Replace() broken") );
1112 for ( size_t n
= 0; n
< lines
.GetCount(); n
++ )
1114 wxASSERT_MSG( lines
[n
] == lines2
[n
], _T("Replace() broken") );
1116 #endif // WXDEBUG_TEXT_REPLACE
1118 // (3b) special case: if we replace everything till the end we need to
1119 // keep an empty line or the lines would disappear completely
1120 // (this also takes care of never leaving m_lines empty)
1121 if ( ((size_t)lineEnd
== countOld
- 1) && lines
.IsEmpty() )
1123 lines
.Add(wxEmptyString
);
1126 size_t nReplaceCount
= lines
.GetCount(),
1129 // (4) merge into the array
1132 for ( line
= lineStart
; line
<= lineEnd
; line
++, nReplaceLine
++ )
1134 if ( nReplaceLine
< nReplaceCount
)
1136 // we have the replacement line for this one
1137 if ( ReplaceLine(line
, lines
[nReplaceLine
]) )
1139 rowsNumberChanged
= true;
1142 UpdateMaxWidth(line
);
1144 else // no more replacement lines
1146 // (4b) delete all extra lines (note that we need to delete
1147 // them backwards because indices shift while we do it)
1148 bool deletedLongestLine
= false;
1149 for ( wxTextCoord lineDel
= lineEnd
; lineDel
>= line
; lineDel
-- )
1151 if ( lineDel
== MData().m_lineLongest
)
1153 // we will need to recalc the max line width
1154 deletedLongestLine
= true;
1157 RemoveLine(lineDel
);
1160 if ( deletedLongestLine
)
1165 // even the line number changed
1166 rowsNumberChanged
= true;
1168 // update line to exit the loop
1173 // (4c) insert the new lines
1174 if ( nReplaceLine
< nReplaceCount
)
1176 // even the line number changed
1177 rowsNumberChanged
= true;
1181 InsertLine(++lineEnd
, lines
[nReplaceLine
++]);
1183 UpdateMaxWidth(lineEnd
);
1185 while ( nReplaceLine
< nReplaceCount
);
1188 // (5) now refresh the changed area
1190 // update the (cached) last position first as refresh functions use it
1191 m_posLast
+= text
.length() - to
+ from
;
1193 // we may optimize refresh if the number of rows didn't change - but if
1194 // it did we have to refresh everything below the part we chanegd as
1195 // well as it might have moved
1196 if ( !rowsNumberChanged
)
1198 // refresh the line we changed
1201 RefreshPixelRange(lineStart
++, startNewText
, widthNewText
);
1205 //OPT: we shouldn't refresh the unchanged part of the line in
1206 // this case, but instead just refresh the tail of it - the
1207 // trouble is that we don't know here where does this tail
1211 // number of rows didn't change, refresh the updated rows and the
1213 if ( lineStart
<= lineEnd
)
1214 RefreshLineRange(lineStart
, lineEnd
);
1216 else // rows number did change
1220 // refresh only part of the first line
1221 RefreshPixelRange(lineStart
++, startNewText
, widthNewText
);
1223 //else: we have to refresh everything as some part of the text
1224 // could be in the previous row before but moved to the next
1225 // one now (due to word wrap)
1227 wxTextCoord lineEnd
= GetLines().GetCount() - 1;
1228 if ( lineStart
<= lineEnd
)
1229 RefreshLineRange(lineStart
, lineEnd
);
1231 // refresh text rect left below
1232 RefreshLineRange(lineEnd
+ 1, 0);
1234 // the vert scrollbar might [dis]appear
1235 MData().m_updateScrollbarY
= true;
1238 // must recalculate it - will do later
1242 #ifdef WXDEBUG_TEXT_REPLACE
1243 // optimized code above should give the same result as straightforward
1244 // computation in the beginning
1245 wxASSERT_MSG( GetValue() == textTotalNew
, _T("error in Replace()") );
1246 #endif // WXDEBUG_TEXT_REPLACE
1248 // update the current position: note that we always put the cursor at the
1249 // end of the replacement text
1250 DoSetInsertionPoint(from
+ text
.length());
1252 // and the selection: this is complicated by the fact that selection coords
1253 // must be first updated to reflect change in text coords, i.e. if we had
1254 // selection from 17 to 19 and we just removed this range, we don't have to
1255 // refresh anything, so we can't just use ClearSelection() here
1256 if ( selStartOld
!= -1 )
1258 // refresh the parst of the selection outside the changed text (which
1259 // we already refreshed)
1260 if ( selStartOld
< from
)
1261 RefreshTextRange(selStartOld
, from
);
1262 if ( to
< selEndOld
)
1263 RefreshTextRange(to
, selEndOld
);
1267 // now call it to do the rest (not related to refreshing)
1270 if ( EventsAllowed() )
1271 SendTextUpdatedEvent();
1274 void wxTextCtrl::Remove(wxTextPos from
, wxTextPos to
)
1276 // Replace() only works with correctly ordered arguments, so exchange them
1278 OrderPositions(from
, to
);
1280 Replace(from
, to
, wxEmptyString
);
1283 void wxTextCtrl::WriteText(const wxString
& text
)
1285 // replace the selection with the new text
1288 Replace(m_curPos
, m_curPos
, text
);
1291 void wxTextCtrl::AppendText(const wxString
& text
)
1293 SetInsertionPointEnd();
1297 // ----------------------------------------------------------------------------
1299 // ----------------------------------------------------------------------------
1301 void wxTextCtrl::SetInsertionPoint(wxTextPos pos
)
1303 wxCHECK_RET( pos
>= 0 && pos
<= GetLastPosition(),
1304 _T("insertion point position out of range") );
1306 // don't do anything if it didn't change
1307 if ( pos
!= m_curPos
)
1309 DoSetInsertionPoint(pos
);
1312 if ( !IsSingleLine() )
1314 // moving cursor should reset the stored abscissa (even if the cursor
1315 // position didn't actually change!)
1316 MData().m_xCaret
= -1;
1322 void wxTextCtrl::InitInsertionPoint()
1324 // so far always put it in the beginning
1325 DoSetInsertionPoint(0);
1327 // this will also set the selection anchor correctly
1331 void wxTextCtrl::MoveInsertionPoint(wxTextPos pos
)
1333 wxASSERT_MSG( pos
>= 0 && pos
<= GetLastPosition(),
1334 _T("DoSetInsertionPoint() can only be called with valid pos") );
1337 PositionToXY(m_curPos
, &m_curCol
, &m_curRow
);
1340 void wxTextCtrl::DoSetInsertionPoint(wxTextPos pos
)
1342 MoveInsertionPoint(pos
);
1347 void wxTextCtrl::SetInsertionPointEnd()
1349 SetInsertionPoint(GetLastPosition());
1352 wxTextPos
wxTextCtrl::GetInsertionPoint() const
1357 wxTextPos
wxTextCtrl::GetLastPosition() const
1360 if ( IsSingleLine() )
1362 pos
= m_value
.length();
1368 size_t nLineCount
= GetLineCount();
1369 for ( size_t nLine
= 0; nLine
< nLineCount
; nLine
++ )
1371 // +1 is because the positions at the end of this line and of the
1372 // start of the next one are different
1373 pos
+= GetLines()[nLine
].length() + 1;
1378 // the last position is at the end of the last line, not in the
1379 // beginning of the next line after it
1383 // more probable reason of this would be to forget to update m_posLast
1384 wxASSERT_MSG( pos
== m_posLast
, _T("bug in GetLastPosition()") );
1385 #endif // WXDEBUG_TEXT
1393 // ----------------------------------------------------------------------------
1395 // ----------------------------------------------------------------------------
1397 void wxTextCtrl::GetSelection(wxTextPos
* from
, wxTextPos
* to
) const
1405 wxString
wxTextCtrl::GetSelectionText() const
1409 if ( HasSelection() )
1411 if ( IsSingleLine() )
1413 sel
= m_value
.Mid(m_selStart
, m_selEnd
- m_selStart
);
1417 wxTextCoord colStart
, lineStart
,
1419 PositionToXY(m_selStart
, &colStart
, &lineStart
);
1420 PositionToXY(m_selEnd
, &colEnd
, &lineEnd
);
1422 // as always, we need to check for the special case when the start
1423 // and end line are the same
1424 if ( lineEnd
== lineStart
)
1426 sel
= GetLines()[lineStart
].Mid(colStart
, colEnd
- colStart
);
1428 else // sel on multiple lines
1430 // take the end of the first line
1431 sel
= GetLines()[lineStart
].c_str() + colStart
;
1434 // all intermediate ones
1435 for ( wxTextCoord line
= lineStart
+ 1; line
< lineEnd
; line
++ )
1437 sel
<< GetLines()[line
] << _T('\n');
1440 // and the start of the last one
1441 sel
+= GetLines()[lineEnd
].Left(colEnd
);
1449 void wxTextCtrl::SetSelection(wxTextPos from
, wxTextPos to
)
1451 // selecting till -1 is the same as selecting to the end
1454 // and selecting (-1, -1) range is the same as selecting everything, by
1458 to
= GetLastPosition();
1461 if ( from
== -1 || to
== from
)
1465 else // valid sel range
1467 // remember the 'to' position as the current position, used to move the
1468 // caret there later
1469 wxTextPos toOrig
= to
;
1471 OrderPositions(from
, to
);
1473 wxCHECK_RET( to
<= GetLastPosition(),
1474 _T("invalid range in wxTextCtrl::SetSelection") );
1476 if ( from
!= m_selStart
|| to
!= m_selEnd
)
1478 // we need to use temp vars as RefreshTextRange() may call DoDraw()
1479 // directly and so m_selStart/End must be reset by then
1480 wxTextPos selStartOld
= m_selStart
,
1481 selEndOld
= m_selEnd
;
1486 wxLogTrace(_T("text"), _T("Selection range is %ld-%ld"),
1487 m_selStart
, m_selEnd
);
1489 // refresh only the part of text which became (un)selected if
1491 if ( selStartOld
== m_selStart
)
1493 RefreshTextRange(selEndOld
, m_selEnd
);
1495 else if ( selEndOld
== m_selEnd
)
1497 RefreshTextRange(m_selStart
, selStartOld
);
1501 // OPT: could check for other cases too but it is probably not
1502 // worth it as the two above are the most common ones
1503 if ( selStartOld
!= -1 )
1504 RefreshTextRange(selStartOld
, selEndOld
);
1505 if ( m_selStart
!= -1 )
1506 RefreshTextRange(m_selStart
, m_selEnd
);
1509 // we need to fully repaint the invalidated areas of the window
1510 // before scrolling it (from DoSetInsertionPoint which is typically
1511 // called after SetSelection()), otherwise they may stay unpainted
1512 m_targetWindow
->Update();
1514 //else: nothing to do
1516 // the insertion point is put at the location where the caret was moved
1517 DoSetInsertionPoint(toOrig
);
1521 void wxTextCtrl::ClearSelection()
1523 if ( HasSelection() )
1525 // we need to use temp vars as RefreshTextRange() may call DoDraw()
1526 // directly (see above as well)
1527 wxTextPos selStart
= m_selStart
,
1530 // no selection any more
1534 // refresh the old selection
1535 RefreshTextRange(selStart
, selEnd
);
1538 // the anchor should be moved even if there was no selection previously
1539 m_selAnchor
= m_curPos
;
1542 void wxTextCtrl::RemoveSelection()
1544 if ( !HasSelection() )
1547 Remove(m_selStart
, m_selEnd
);
1550 bool wxTextCtrl::GetSelectedPartOfLine(wxTextCoord line
,
1551 wxTextPos
*start
, wxTextPos
*end
) const
1558 if ( !HasSelection() )
1560 // no selection at all, hence no selection in this line
1564 wxTextCoord lineStart
, colStart
;
1565 PositionToXY(m_selStart
, &colStart
, &lineStart
);
1566 if ( lineStart
> line
)
1568 // this line is entirely above the selection
1572 wxTextCoord lineEnd
, colEnd
;
1573 PositionToXY(m_selEnd
, &colEnd
, &lineEnd
);
1574 if ( lineEnd
< line
)
1576 // this line is entirely below the selection
1580 if ( line
== lineStart
)
1585 *end
= lineEnd
== lineStart
? colEnd
: GetLineLength(line
);
1587 else if ( line
== lineEnd
)
1590 *start
= lineEnd
== lineStart
? colStart
: 0;
1594 else // the line is entirely inside the selection
1599 *end
= GetLineLength(line
);
1605 // ----------------------------------------------------------------------------
1607 // ----------------------------------------------------------------------------
1609 bool wxTextCtrl::IsModified() const
1611 return m_isModified
;
1614 bool wxTextCtrl::IsEditable() const
1616 // disabled control can never be edited
1617 return m_isEditable
&& IsEnabled();
1620 void wxTextCtrl::MarkDirty()
1622 m_isModified
= true;
1625 void wxTextCtrl::DiscardEdits()
1627 m_isModified
= false;
1630 void wxTextCtrl::SetEditable(bool editable
)
1632 if ( editable
!= m_isEditable
)
1634 m_isEditable
= editable
;
1636 // the caret (dis)appears
1639 // the appearance of the control might have changed
1644 // ----------------------------------------------------------------------------
1645 // col/lines <-> position correspondence
1646 // ----------------------------------------------------------------------------
1649 A few remarks about this stuff:
1651 o The numbering of the text control columns/rows starts from 0.
1652 o Start of first line is position 0, its last position is line.length()
1653 o Start of the next line is the last position of the previous line + 1
1656 int wxTextCtrl::GetLineLength(wxTextCoord line
) const
1658 if ( IsSingleLine() )
1660 wxASSERT_MSG( line
== 0, _T("invalid GetLineLength() parameter") );
1662 return m_value
.length();
1666 wxCHECK_MSG( (size_t)line
< GetLineCount(), -1,
1667 _T("line index out of range") );
1669 return GetLines()[line
].length();
1673 wxString
wxTextCtrl::GetLineText(wxTextCoord line
) const
1675 if ( IsSingleLine() )
1677 wxASSERT_MSG( line
== 0, _T("invalid GetLineLength() parameter") );
1683 //this is called during DoGetBestSize
1684 if (line
== 0 && GetLineCount() == 0) return wxEmptyString
;
1686 wxCHECK_MSG( (size_t)line
< GetLineCount(), wxEmptyString
,
1687 _T("line index out of range") );
1689 return GetLines()[line
];
1693 int wxTextCtrl::GetNumberOfLines() const
1695 // there is always 1 line, even if the text is empty
1696 return IsSingleLine() ? 1 : GetLineCount();
1699 wxTextPos
wxTextCtrl::XYToPosition(wxTextCoord x
, wxTextCoord y
) const
1701 // note that this method should accept any values of x and y and return -1
1702 // if they are out of range
1703 if ( IsSingleLine() )
1705 return ( x
> GetLastPosition() || y
> 0 ) ? wxOutOfRangeTextCoord
: x
;
1709 if ( (size_t)y
>= GetLineCount() )
1711 // this position is below the text
1712 return GetLastPosition();
1716 for ( size_t nLine
= 0; nLine
< (size_t)y
; nLine
++ )
1718 // +1 is because the positions at the end of this line and of the
1719 // start of the next one are different
1720 pos
+= GetLines()[nLine
].length() + 1;
1723 // take into account also the position in line
1724 if ( (size_t)x
> GetLines()[y
].length() )
1726 // don't return position in the next line
1727 x
= GetLines()[y
].length();
1734 bool wxTextCtrl::PositionToXY(wxTextPos pos
,
1735 wxTextCoord
*x
, wxTextCoord
*y
) const
1737 if ( IsSingleLine() )
1739 if ( (size_t)pos
> m_value
.length() )
1751 wxTextPos posCur
= 0;
1752 size_t nLineCount
= GetLineCount();
1753 for ( size_t nLine
= 0; nLine
< nLineCount
; nLine
++ )
1755 // +1 is because the start the start of the next line is one
1756 // position after the end of this one
1757 wxTextPos posNew
= posCur
+ GetLines()[nLine
].length() + 1;
1760 // we've found the line, now just calc the column
1768 wxASSERT_MSG( XYToPosition(pos
- posCur
, nLine
) == pos
,
1769 _T("XYToPosition() or PositionToXY() broken") );
1770 #endif // WXDEBUG_TEXT
1774 else // go further down
1780 // beyond the last line
1785 wxTextCoord
wxTextCtrl::GetRowsPerLine(wxTextCoord line
) const
1787 // a normal line has one row
1788 wxTextCoord numRows
= 1;
1792 // add the number of additional rows
1793 numRows
+= WData().m_linesData
[line
].GetExtraRowCount();
1799 wxTextCoord
wxTextCtrl::GetRowCount() const
1801 wxTextCoord count
= GetLineCount();
1806 count
= GetFirstRowOfLine(count
- 1) +
1807 WData().m_linesData
[count
- 1].GetRowCount();
1813 wxTextCoord
wxTextCtrl::GetRowAfterLine(wxTextCoord line
) const
1818 if ( !WData().IsValidLine(line
) )
1823 return WData().m_linesData
[line
].GetNextRow();
1826 wxTextCoord
wxTextCtrl::GetFirstRowOfLine(wxTextCoord line
) const
1831 if ( !WData().IsValidLine(line
) )
1836 return WData().m_linesData
[line
].GetFirstRow();
1839 bool wxTextCtrl::PositionToLogicalXY(wxTextPos pos
,
1841 wxCoord
*yOut
) const
1843 wxTextCoord col
, line
;
1845 // optimization for special (but common) case when we already have the col
1847 if ( pos
== m_curPos
)
1852 else // must really calculate col/line from pos
1854 if ( !PositionToXY(pos
, &col
, &line
) )
1858 int hLine
= GetLineHeight();
1860 wxString textLine
= GetLineText(line
);
1861 if ( IsSingleLine() || !WrapLines() )
1863 x
= GetTextWidth(textLine
.Left(col
));
1866 else // difficult case: multline control with line wrap
1868 y
= GetFirstRowOfLine(line
);
1870 wxTextCoord colRowStart
;
1871 y
+= GetRowInLine(line
, col
, &colRowStart
);
1875 // x is the width of the text before this position in this row
1876 x
= GetTextWidth(textLine
.Mid(colRowStart
, col
- colRowStart
));
1887 bool wxTextCtrl::PositionToDeviceXY(wxTextPos pos
,
1889 wxCoord
*yOut
) const
1892 if ( !PositionToLogicalXY(pos
, &x
, &y
) )
1895 // finally translate the logical text rect coords into physical client
1897 CalcScrolledPosition(m_rectText
.x
+ x
, m_rectText
.y
+ y
, xOut
, yOut
);
1902 wxPoint
wxTextCtrl::GetCaretPosition() const
1904 wxCoord xCaret
, yCaret
;
1905 if ( !PositionToDeviceXY(m_curPos
, &xCaret
, &yCaret
) )
1907 wxFAIL_MSG( _T("Caret can't be beyond the text!") );
1910 return wxPoint(xCaret
, yCaret
);
1913 // pos may be -1 to show the current position
1914 void wxTextCtrl::ShowPosition(wxTextPos pos
)
1916 bool showCaret
= GetCaret() && GetCaret()->IsVisible();
1920 if ( IsSingleLine() )
1922 ShowHorzPosition(GetTextWidth(m_value
.Left(pos
)));
1924 else if ( MData().m_scrollRangeX
|| MData().m_scrollRangeY
) // multiline with scrollbars
1927 GetViewStart(&xStart
, &yStart
);
1933 PositionToLogicalXY(pos
, &x
, &y
);
1935 wxRect rectText
= GetRealTextArea();
1937 // scroll the position vertically into view: if it is currently above
1938 // it, make it the first one, otherwise the last one
1939 if ( MData().m_scrollRangeY
)
1941 y
/= GetLineHeight();
1947 else // we are currently in or below the view area
1949 // find the last row currently shown
1954 // to find the last row we need to use the generic HitTest
1957 // OPT this is a bit silly: we undo this in HitTest(), so
1958 // it would be better to factor out the common
1959 // functionality into a separate function (OTOH it
1960 // won't probably save us that much)
1961 wxPoint
pt(0, rectText
.height
- 1);
1962 pt
+= GetClientAreaOrigin();
1963 pt
+= m_rectText
.GetPosition();
1964 HitTest(pt
, &col
, &yEnd
);
1966 // find the row inside the line
1967 yEnd
= GetFirstRowOfLine(yEnd
) + GetRowInLine(yEnd
, col
);
1971 // finding the last line is easy if each line has exactly
1973 yEnd
= yStart
+ rectText
.height
/ GetLineHeight();
1978 // scroll down: the current item should appear at the
1979 // bottom of the view
1980 Scroll(0, y
- (yEnd
- yStart
));
1985 // scroll the position horizontally into view
1987 // we follow what I believe to be Windows behaviour here, that is if
1988 // the position is already entirely in the view we do nothing, but if
1989 // we do have to scroll the window to bring it into view, we scroll it
1990 // not just enough to show the position but slightly more so that this
1991 // position is at 1/3 of the window width from the closest border to it
1992 // (I'm not sure that Windows does exactly this but it looks like this)
1993 if ( MData().m_scrollRangeX
)
1995 // unlike for the rows, xStart doesn't correspond to the starting
1996 // column as they all have different widths, so we need to
1997 // translate everything to pixels
1999 // we want the text between x and x2 be entirely inside the view
2000 // (i.e. the current character)
2002 // make xStart the first visible pixel (and not position)
2003 int wChar
= GetAverageWidth();
2008 // we want the position of this column be 1/3 to the right of
2010 x
-= rectText
.width
/ 3;
2013 Scroll(x
/ wChar
, y
);
2015 else // maybe we're beyond the right border of the view?
2017 wxTextCoord col
, row
;
2018 if ( PositionToXY(pos
, &col
, &row
) )
2020 wxString lineText
= GetLineText(row
);
2021 wxCoord x2
= x
+ GetTextWidth(lineText
[(size_t)col
]);
2022 if ( x2
> xStart
+ rectText
.width
)
2024 // we want the position of this column be 1/3 to the
2025 // left of the right edge, i.e. 2/3 right of the left
2027 x2
-= (2*rectText
.width
)/3;
2030 Scroll(x2
/ wChar
, row
);
2036 //else: multiline but no scrollbars, hence nothing to do
2042 // ----------------------------------------------------------------------------
2044 // ----------------------------------------------------------------------------
2047 TODO: we could have (easy to do) vi-like options for word movement, i.e.
2048 distinguish between inlusive/exclusive words and between words and
2049 WORDS (in vim sense) and also, finally, make the set of characters
2050 which make up a word configurable - currently we use the exclusive
2051 WORDS only (coincidentally, this is what Windows edit control does)
2053 For future references, here is what vim help says:
2055 A word consists of a sequence of letters, digits and underscores, or
2056 a sequence of other non-blank characters, separated with white space
2057 (spaces, tabs, <EOL>). This can be changed with the 'iskeyword'
2060 A WORD consists of a sequence of non-blank characters, separated with
2061 white space. An empty line is also considered to be a word and a
2065 static inline bool IsWordChar(wxChar ch
)
2067 return !wxIsspace(ch
);
2070 wxTextPos
wxTextCtrl::GetWordStart() const
2072 if ( m_curPos
== -1 || m_curPos
== 0 )
2075 if ( m_curCol
== 0 )
2077 // go to the end of the previous line
2078 return m_curPos
- 1;
2081 // it shouldn't be possible to learn where the word starts in the password
2086 // start at the previous position
2087 const wxChar
*p0
= GetLineText(m_curRow
).c_str();
2088 const wxChar
*p
= p0
+ m_curCol
- 1;
2090 // find the end of the previous word
2091 while ( (p
> p0
) && !IsWordChar(*p
) )
2094 // now find the beginning of this word
2095 while ( (p
> p0
) && IsWordChar(*p
) )
2098 // we might have gone too far
2099 if ( !IsWordChar(*p
) )
2102 return (m_curPos
- m_curCol
) + p
- p0
;
2105 wxTextPos
wxTextCtrl::GetWordEnd() const
2107 if ( m_curPos
== -1 )
2110 wxString line
= GetLineText(m_curRow
);
2111 if ( (size_t)m_curCol
== line
.length() )
2113 // if we're on the last position in the line, go to the next one - if
2115 wxTextPos pos
= m_curPos
;
2116 if ( pos
< GetLastPosition() )
2122 // it shouldn't be possible to learn where the word ends in the password
2125 return GetLastPosition();
2127 // start at the current position
2128 const wxChar
*p0
= line
.c_str();
2129 const wxChar
*p
= p0
+ m_curCol
;
2131 // find the start of the next word
2132 while ( *p
&& !IsWordChar(*p
) )
2135 // now find the end of it
2136 while ( *p
&& IsWordChar(*p
) )
2139 // and find the start of the next word
2140 while ( *p
&& !IsWordChar(*p
) )
2143 return (m_curPos
- m_curCol
) + p
- p0
;
2146 // ----------------------------------------------------------------------------
2148 // ----------------------------------------------------------------------------
2150 void wxTextCtrl::Copy()
2153 if ( HasSelection() )
2155 wxClipboardLocker clipLock
;
2157 // wxTextFile::Translate() is needed to transform all '\n' into "\r\n"
2158 wxString text
= wxTextFile::Translate(GetTextToShow(GetSelectionText()));
2159 wxTextDataObject
*data
= new wxTextDataObject(text
);
2160 wxTheClipboard
->SetData(data
);
2162 #endif // wxUSE_CLIPBOARD
2165 void wxTextCtrl::Cut()
2170 bool wxTextCtrl::DoCut()
2172 if ( !HasSelection() )
2182 void wxTextCtrl::Paste()
2187 bool wxTextCtrl::DoPaste()
2190 wxClipboardLocker clipLock
;
2192 wxTextDataObject data
;
2193 if ( wxTheClipboard
->IsSupported(data
.GetFormat())
2194 && wxTheClipboard
->GetData(data
) )
2196 // reverse transformation: '\r\n\" -> '\n'
2197 wxString text
= wxTextFile::Translate(data
.GetText(),
2198 wxTextFileType_Unix
);
2199 if ( !text
.empty() )
2206 #endif // wxUSE_CLIPBOARD
2211 // ----------------------------------------------------------------------------
2213 // ----------------------------------------------------------------------------
2215 wxTextCtrlInsertCommand
*
2216 wxTextCtrlCommandProcessor::IsInsertCommand(wxCommand
*command
)
2218 return (wxTextCtrlInsertCommand
*)
2219 (command
&& (command
->GetName() == wxTEXT_COMMAND_INSERT
)
2223 void wxTextCtrlCommandProcessor::Store(wxCommand
*command
)
2225 wxTextCtrlInsertCommand
*cmdIns
= IsInsertCommand(command
);
2228 if ( IsCompressing() )
2230 wxTextCtrlInsertCommand
*
2231 cmdInsLast
= IsInsertCommand(GetCurrentCommand());
2233 // it is possible that we don't have any last command at all if,
2234 // for example, it was undone since the last Store(), so deal with
2238 cmdInsLast
->Append(cmdIns
);
2242 // don't need to call the base class version
2247 // append the following insert commands to this one
2248 m_compressInserts
= true;
2250 // let the base class version will do the job normally
2252 else // not an insert command
2254 // stop compressing insert commands - this won't work with the last
2255 // command not being an insert one anyhow
2258 // let the base class version will do the job normally
2261 wxCommandProcessor::Store(command
);
2264 void wxTextCtrlInsertCommand::Append(wxTextCtrlInsertCommand
*other
)
2266 m_text
+= other
->m_text
;
2269 bool wxTextCtrlInsertCommand::CanUndo() const
2271 return m_from
!= -1;
2274 bool wxTextCtrlInsertCommand::Do(wxTextCtrl
*text
)
2276 // the text is going to be inserted at the current position, remember where
2278 m_from
= text
->GetInsertionPoint();
2280 // and now do insert it
2281 text
->WriteText(m_text
);
2286 bool wxTextCtrlInsertCommand::Undo(wxTextCtrl
*text
)
2288 wxCHECK_MSG( CanUndo(), false, _T("impossible to undo insert cmd") );
2290 // remove the text from where we inserted it
2291 text
->Remove(m_from
, m_from
+ m_text
.length());
2296 bool wxTextCtrlRemoveCommand::CanUndo() const
2298 // if we were executed, we should have the text we removed
2299 return !m_textDeleted
.empty();
2302 bool wxTextCtrlRemoveCommand::Do(wxTextCtrl
*text
)
2304 text
->SetSelection(m_from
, m_to
);
2305 m_textDeleted
= text
->GetSelectionText();
2306 text
->RemoveSelection();
2311 bool wxTextCtrlRemoveCommand::Undo(wxTextCtrl
*text
)
2313 // it is possible that the text was deleted and that we can't restore text
2314 // at the same position we removed it any more
2315 wxTextPos posLast
= text
->GetLastPosition();
2316 text
->SetInsertionPoint(m_from
> posLast
? posLast
: m_from
);
2317 text
->WriteText(m_textDeleted
);
2322 void wxTextCtrl::Undo()
2324 // the caller must check it
2325 wxASSERT_MSG( CanUndo(), _T("can't call Undo() if !CanUndo()") );
2327 m_cmdProcessor
->Undo();
2330 void wxTextCtrl::Redo()
2332 // the caller must check it
2333 wxASSERT_MSG( CanRedo(), _T("can't call Undo() if !CanUndo()") );
2335 m_cmdProcessor
->Redo();
2338 bool wxTextCtrl::CanUndo() const
2340 return IsEditable() && m_cmdProcessor
->CanUndo();
2343 bool wxTextCtrl::CanRedo() const
2345 return IsEditable() && m_cmdProcessor
->CanRedo();
2348 // ----------------------------------------------------------------------------
2350 // ----------------------------------------------------------------------------
2352 wxSize
wxTextCtrl::DoGetBestClientSize() const
2354 // when we're called for the very first time from Create() we must
2355 // calculate the font metrics here because we can't do it before calling
2356 // Create() (there is no window yet and wxGTK crashes) but we need them
2358 if ( m_heightLine
== -1 )
2360 wxConstCast(this, wxTextCtrl
)->RecalcFontMetrics();
2364 GetTextExtent(GetTextToShow(GetLineText(0)), &w
, &h
);
2366 int wChar
= GetAverageWidth(),
2367 hChar
= GetLineHeight();
2369 int widthMin
= wxMax(10*wChar
, 100);
2375 if ( !IsSingleLine() )
2377 // let the control have a reasonable number of lines
2378 int lines
= GetNumberOfLines();
2381 else if ( lines
> 10 )
2388 rectText
.height
= h
;
2389 wxRect rectTotal
= GetRenderer()->GetTextTotalArea(this, rectText
);
2390 return wxSize(rectTotal
.width
, rectTotal
.height
);
2393 void wxTextCtrl::UpdateTextRect()
2395 wxRect
rectTotal(GetClientSize());
2396 wxCoord
*extraSpace
= WrapLines() ? &WData().m_widthMark
: NULL
;
2397 m_rectText
= GetRenderer()->GetTextClientArea(this, rectTotal
, extraSpace
);
2399 // code elsewhere is confused by negative rect size
2400 if ( m_rectText
.width
<= 0 )
2401 m_rectText
.width
= 1;
2402 if ( m_rectText
.height
<= 0 )
2403 m_rectText
.height
= 1;
2405 if ( !IsSingleLine() )
2407 // invalidate it so that GetRealTextArea() will recalc it
2408 MData().m_rectTextReal
.width
= 0;
2410 // only scroll this rect when the window is scrolled: note that we have
2411 // to scroll not only the text but the line wrap marks too if we show
2413 wxRect rectText
= GetRealTextArea();
2414 if ( extraSpace
&& *extraSpace
)
2416 rectText
.width
+= *extraSpace
;
2418 SetTargetRect(rectText
);
2420 // relayout all lines
2423 WData().m_rowFirstInvalid
= 0;
2425 // increase timestamp: this means that the lines which had been
2426 // laid out before will be relaid out the next time LayoutLines()
2427 // is called because their timestamp will be smaller than the
2429 WData().m_timestamp
++;
2433 UpdateLastVisible();
2436 void wxTextCtrl::UpdateLastVisible()
2438 // this method is only used for horizontal "scrollbarless" scrolling which
2439 // is used only with single line controls
2440 if ( !IsSingleLine() )
2443 // use (efficient) HitTestLine to find the last visible character
2444 wxString text
= m_value
.Mid((size_t)SData().m_colStart
/* to the end */);
2446 switch ( HitTestLine(text
, m_rectText
.width
, &col
) )
2448 case wxTE_HT_BEYOND
:
2449 // everything is visible
2450 SData().m_ofsHorz
= 0;
2452 SData().m_colStart
= 0;
2453 SData().m_colLastVisible
= text
.length();
2455 // calculate it below
2456 SData().m_posLastVisible
= -1;
2460 case wxTE_HT_BEFORE:
2464 wxFAIL_MSG(_T("unexpected HitTestLine() return value"));
2467 case wxTE_HT_ON_TEXT
:
2470 // the last entirely seen character is the previous one because
2471 // this one is only partly visible - unless the width of the
2472 // string is exactly the max width
2473 SData().m_posLastVisible
= GetTextWidth(text
.Truncate(col
+ 1));
2474 if ( SData().m_posLastVisible
> m_rectText
.width
)
2476 // this character is not entirely visible, take the
2481 SData().m_posLastVisible
= -1;
2483 //else: we can just see it
2485 SData().m_colLastVisible
= col
;
2490 // calculate the width of the text really shown
2491 if ( SData().m_posLastVisible
== -1 )
2493 SData().m_posLastVisible
= GetTextWidth(text
.Truncate(SData().m_colLastVisible
+ 1));
2496 // current value is relative the start of the string text which starts at
2497 // SData().m_colStart, we need an absolute offset into string
2498 SData().m_colLastVisible
+= SData().m_colStart
;
2500 wxLogTrace(_T("text"), _T("Last visible column/position is %d/%ld"),
2501 (int) SData().m_colLastVisible
, (long) SData().m_posLastVisible
);
2504 void wxTextCtrl::OnSize(wxSizeEvent
& event
)
2508 if ( !IsSingleLine() )
2511 // update them immediately because if we are called for the first time,
2512 // we need to create them in order for the base class version to
2513 // position the scrollbars correctly - if we don't do it now, it won't
2514 // happen at all if we don't get more size events
2518 MData().m_updateScrollbarX
=
2519 MData().m_updateScrollbarY
= true;
2525 wxCoord
wxTextCtrl::GetTotalWidth() const
2528 CalcUnscrolledPosition(m_rectText
.width
, 0, &w
, NULL
);
2532 wxCoord
wxTextCtrl::GetTextWidth(const wxString
& text
) const
2535 GetTextExtent(GetTextToShow(text
), &w
, NULL
);
2539 wxRect
wxTextCtrl::GetRealTextArea() const
2541 // for single line text control it's just the same as text rect
2542 if ( IsSingleLine() )
2545 // the real text area always holds an entire number of lines, so the only
2546 // difference with the text area is a narrow strip along the bottom border
2547 wxRect rectText
= MData().m_rectTextReal
;
2548 if ( !rectText
.width
)
2551 rectText
= m_rectText
;
2553 // when we're called for the very first time, the line height might not
2554 // had been calculated yet, so do get it now
2555 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
2556 self
->RecalcFontMetrics();
2558 int hLine
= GetLineHeight();
2559 rectText
.height
= (m_rectText
.height
/ hLine
) * hLine
;
2562 self
->MData().m_rectTextReal
= rectText
;
2568 wxTextCoord
wxTextCtrl::GetRowInLine(wxTextCoord line
,
2570 wxTextCoord
*colRowStart
) const
2572 wxASSERT_MSG( WrapLines(), _T("shouldn't be called") );
2574 const wxWrappedLineData
& lineData
= WData().m_linesData
[line
];
2576 if ( !WData().IsValidLine(line
) )
2579 // row is here counted a bit specially: 0 is the 2nd row of the line (1st
2582 rowMax
= lineData
.GetExtraRowCount();
2586 while ( (row
< rowMax
) && (col
>= lineData
.GetExtraRowStart(row
)) )
2589 // it's ok here that row is 1 greater than needed: like this, it is
2590 // counted as a normal (and not extra) row
2592 //else: only one row anyhow
2596 // +1 because we need a real row number, not the extra row one
2597 *colRowStart
= lineData
.GetRowStart(row
);
2599 // this can't happen, of course
2600 wxASSERT_MSG( *colRowStart
<= col
, _T("GetRowInLine() is broken") );
2606 void wxTextCtrl::LayoutLine(wxTextCoord line
, wxWrappedLineData
& lineData
) const
2608 // FIXME: this uses old GetPartOfWrappedLine() which is not used anywhere
2609 // else now and has rather awkward interface for our needs here
2611 lineData
.m_rowsStart
.Empty();
2612 lineData
.m_rowsWidth
.Empty();
2614 const wxString
& text
= GetLineText(line
);
2616 size_t colRowStart
= 0;
2619 size_t lenRow
= GetPartOfWrappedLine
2621 text
.c_str() + colRowStart
,
2625 // remember the start of this row (not for the first one as
2626 // it's always 0) and its width
2628 lineData
.m_rowsStart
.Add(colRowStart
);
2629 lineData
.m_rowsWidth
.Add(widthRow
);
2631 colRowStart
+= lenRow
;
2633 while ( colRowStart
< text
.length() );
2635 // put the current timestamp on it
2636 lineData
.m_timestamp
= WData().m_timestamp
;
2639 void wxTextCtrl::LayoutLines(wxTextCoord lineLast
) const
2641 wxASSERT_MSG( WrapLines(), _T("should only be used for line wrapping") );
2643 // if we were called, some line was dirty and if it was dirty we must have
2644 // had m_rowFirstInvalid set to something too
2645 wxTextCoord lineFirst
= WData().m_rowFirstInvalid
;
2646 wxASSERT_MSG( lineFirst
!= -1, _T("nothing to layout?") );
2648 wxTextCoord rowFirst
, rowCur
;
2651 // start after the last known valid line
2652 const wxWrappedLineData
& lineData
= WData().m_linesData
[lineFirst
- 1];
2653 rowFirst
= lineData
.GetFirstRow() + lineData
.GetRowCount();
2655 else // no valid lines, start at row 0
2661 for ( wxTextCoord line
= lineFirst
; line
<= lineLast
; line
++ )
2663 // set the starting row for this line
2664 wxWrappedLineData
& lineData
= WData().m_linesData
[line
];
2665 lineData
.m_rowFirst
= rowCur
;
2667 // had the line been already broken into rows?
2669 // if so, compare its timestamp with the current one: if nothing has
2670 // been changed, don't relayout it
2671 if ( !lineData
.IsValid() ||
2672 (lineData
.m_timestamp
< WData().m_timestamp
) )
2674 // now do break it in rows
2675 LayoutLine(line
, lineData
);
2678 rowCur
+= lineData
.GetRowCount();
2681 // we are now valid at least up to this line, but if it is the last one we
2682 // just don't have any more invalid rows at all
2683 if ( (size_t)lineLast
== WData().m_linesData
.GetCount() -1 )
2688 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
2689 self
->WData().m_rowFirstInvalid
= lineLast
;
2691 // also refresh the line end indicators (FIXME shouldn't do it always!)
2692 self
->RefreshLineWrapMarks(rowFirst
, rowCur
);
2695 size_t wxTextCtrl::GetPartOfWrappedLine(const wxChar
* text
,
2696 wxCoord
*widthReal
) const
2698 // this function is slow, it shouldn't be called unless really needed
2699 wxASSERT_MSG( WrapLines(), _T("shouldn't be called") );
2703 wxCoord wReal
= wxDefaultCoord
;
2704 switch ( HitTestLine(s
, m_rectText
.width
, &col
) )
2707 case wxTE_HT_BEFORE:
2711 wxFAIL_MSG(_T("unexpected HitTestLine() return value"));
2714 case wxTE_HT_ON_TEXT
:
2717 // the last entirely seen character is the previous one because
2718 // this one is only partly visible - unless the width of the
2719 // string is exactly the max width
2720 wReal
= GetTextWidth(s
.Truncate(col
+ 1));
2721 if ( wReal
> m_rectText
.width
)
2723 // this character is not entirely visible, take the
2728 wReal
= wxDefaultCoord
;
2730 //else: we can just see it
2732 // wrap at any character or only at words boundaries?
2733 if ( !(GetWindowStyle() & wxTE_CHARWRAP
) )
2735 // find the (last) not word char before this word
2736 wxTextCoord colWordStart
;
2737 for ( colWordStart
= col
;
2738 colWordStart
&& IsWordChar(s
[(size_t)colWordStart
]);
2742 if ( colWordStart
> 0 )
2744 if ( colWordStart
!= col
)
2746 // will have to recalc the real width
2747 wReal
= wxDefaultCoord
;
2752 //else: only a single word, have to wrap it here
2757 case wxTE_HT_BEYOND
:
2761 // we return the number of characters, not the index of the last one
2762 if ( (size_t)col
< s
.length() )
2764 // but don't return more than this (empty) string has
2770 if ( wReal
== wxDefaultCoord
)
2772 // calc it if not done yet
2773 wReal
= GetTextWidth(s
.Truncate(col
));
2779 // VZ: old, horribly inefficient code which can still be used for checking
2780 // the result (in line, not word, wrap mode only) - to be removed later
2782 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
2783 wxClientDC
dc(self
);
2784 dc
.SetFont(GetFont());
2785 self
->DoPrepareDC(dc
);
2787 wxCoord widthMax
= m_rectText
.width
;
2789 // the text which we can keep in this ROW
2792 for ( wOld
= w
= 0; *text
&& (w
<= widthMax
); )
2796 dc
.GetTextExtent(str
, &w
, NULL
);
2801 // if we wrapped, the last letter was one too much
2802 if ( str
.length() > 1 )
2805 str
.erase(str
.length() - 1, 1);
2807 else // but always keep at least one letter in each row
2809 // the real width then is the last value of w and not teh one
2814 else // we didn't wrap
2819 wxASSERT( col
== str
.length() );
2823 wxASSERT( *widthReal
== wOld
);
2828 //return str.length();
2834 // OPT: this function is called a lot - would be nice to optimize it but I
2835 // don't really know how yet
2836 wxTextCtrlHitTestResult
wxTextCtrl::HitTestLine(const wxString
& line
,
2838 wxTextCoord
*colOut
) const
2840 wxTextCtrlHitTestResult res
= wxTE_HT_ON_TEXT
;
2843 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
2844 wxClientDC
dc(self
);
2845 dc
.SetFont(GetFont());
2846 self
->DoPrepareDC(dc
);
2849 dc
.GetTextExtent(line
, &width
, NULL
);
2852 // clicking beyond the end of line is equivalent to clicking at
2853 // the end of it, so return the last line column
2854 col
= line
.length();
2857 // unless the line is empty and so doesn't have any column at all -
2858 // in this case return 0, what else can we do?
2862 res
= wxTE_HT_BEYOND
;
2868 res
= wxTE_HT_BEFORE
;
2870 else // we're inside the line
2872 // now calculate the column: first, approximate it with fixed-width
2873 // value and then calculate the correct value iteratively: note that
2874 // we use the first character of the line instead of (average)
2875 // GetCharWidth(): it is common to have lines of dashes, for example,
2876 // and this should give us much better approximation in such case
2878 // OPT: maybe using (cache) m_widthAvg would be still faster? profile!
2879 dc
.GetTextExtent(line
[0], &width
, NULL
);
2886 else if ( (size_t)col
> line
.length() )
2888 col
= line
.length();
2891 // matchDir is the direction in which we should move to reach the
2892 // character containing the given position
2898 } matchDir
= Match_None
;
2901 // check that we didn't go beyond the line boundary
2907 if ( (size_t)col
> line
.length() )
2909 col
= line
.length();
2913 wxString
strBefore(line
, (size_t)col
);
2914 dc
.GetTextExtent(strBefore
, &width
, NULL
);
2917 if ( matchDir
== Match_Right
)
2919 // we were going to the right and, finally, moved beyond
2920 // the original position - stop on the previous one
2926 if ( matchDir
== Match_None
)
2928 // we just started iterating, now we know that we should
2930 matchDir
= Match_Left
;
2932 //else: we are still to the right of the target, continue
2936 // invert the logic above
2937 if ( matchDir
== Match_Left
)
2939 // with the exception that we don't need to backtrack here
2943 if ( matchDir
== Match_None
)
2946 matchDir
= Match_Right
;
2950 // this is not supposed to happen
2951 wxASSERT_MSG( matchDir
, _T("logic error in wxTextCtrl::HitTest") );
2953 if ( matchDir
== Match_Right
)
2960 // check that we calculated it correctly
2962 if ( res
== wxTE_HT_ON_TEXT
)
2965 wxString text
= line
.Left(col
);
2966 dc
.GetTextExtent(text
, &width1
, NULL
);
2967 if ( (size_t)col
< line
.length() )
2972 dc
.GetTextExtent(text
, &width2
, NULL
);
2974 wxASSERT_MSG( (width1
<= x
) && (x
< width2
),
2975 _T("incorrect HitTestLine() result") );
2977 else // we return last char
2979 wxASSERT_MSG( x
>= width1
, _T("incorrect HitTestLine() result") );
2982 #endif // WXDEBUG_TEXT
2990 wxTextCtrlHitTestResult
wxTextCtrl::HitTest(const wxPoint
& pt
, long *pos
) const
2993 wxTextCtrlHitTestResult rc
= HitTest(pt
, &x
, &y
);
2994 if ( rc
!= wxTE_HT_UNKNOWN
&& pos
)
2996 *pos
= XYToPosition(x
, y
);
3002 wxTextCtrlHitTestResult
wxTextCtrl::HitTest(const wxPoint
& pos
,
3003 wxTextCoord
*colOut
,
3004 wxTextCoord
*rowOut
) const
3006 return HitTest2(pos
.y
, pos
.x
, 0, rowOut
, colOut
, NULL
, NULL
);
3009 wxTextCtrlHitTestResult
wxTextCtrl::HitTestLogical(const wxPoint
& pos
,
3010 wxTextCoord
*colOut
,
3011 wxTextCoord
*rowOut
) const
3013 return HitTest2(pos
.y
, pos
.x
, 0, rowOut
, colOut
, NULL
, NULL
, false);
3016 wxTextCtrlHitTestResult
wxTextCtrl::HitTest2(wxCoord y0
,
3019 wxTextCoord
*rowOut
,
3020 wxTextCoord
*colStart
,
3021 wxTextCoord
*colEnd
,
3022 wxTextCoord
*colRowStartOut
,
3023 bool deviceCoords
) const
3025 // is the point in the text area or to the right or below it?
3026 wxTextCtrlHitTestResult res
= wxTE_HT_ON_TEXT
;
3028 // translate the window coords x0 and y0 into the client coords in the text
3029 // area by adjusting for both the client and text area offsets (unless this
3030 // was already done)
3034 wxPoint pt
= GetClientAreaOrigin() + m_rectText
.GetPosition();
3035 CalcUnscrolledPosition(x10
- pt
.x
, y0
- pt
.y
, &x1
, &y
);
3043 // calculate the row (it is really a LINE, not a ROW)
3046 // these vars are used only for WrapLines() case
3047 wxTextCoord colRowStart
= 0;
3050 if ( colRowStartOut
)
3051 *colRowStartOut
= 0;
3053 int hLine
= GetLineHeight();
3056 // and clicking before it is the same as clicking on the first one
3059 res
= wxTE_HT_BEFORE
;
3063 wxTextCoord rowLast
= GetNumberOfLines() - 1;
3065 if ( IsSingleLine() || !WrapLines() )
3067 // in this case row calculation is simple as all lines have the
3068 // same height and so row is the same as line
3069 if ( row
> rowLast
)
3071 // clicking below the text is the same as clicking on the last
3075 res
= wxTE_HT_BELOW
;
3078 else // multline control with line wrap
3080 // use binary search to find the line containing this row
3081 const wxArrayWrappedLinesData
& linesData
= WData().m_linesData
;
3083 hi
= linesData
.GetCount(),
3088 const wxWrappedLineData
& lineData
= linesData
[cur
];
3089 if ( !WData().IsValidLine(cur
) )
3091 wxTextCoord rowFirst
= lineData
.GetFirstRow();
3093 if ( row
< rowFirst
)
3099 // our row is after the first row of the cur line:
3100 // obviously, if cur is the last line, it contains this
3101 // row, otherwise we have to test that it is before the
3102 // first row of the next line
3103 bool found
= cur
== linesData
.GetCount() - 1;
3106 // if the row is beyond the end of text, adjust it to
3107 // be the last one and set res accordingly
3108 if ( (size_t)(row
- rowFirst
) >= lineData
.GetRowCount() )
3110 res
= wxTE_HT_BELOW
;
3112 row
= lineData
.GetRowCount() + rowFirst
- 1;
3115 else // not the last row
3117 const wxWrappedLineData
&
3118 lineNextData
= linesData
[cur
+ 1];
3119 if ( !WData().IsValidLine(cur
+ 1) )
3120 LayoutLines(cur
+ 1);
3121 found
= row
< lineNextData
.GetFirstRow();
3126 colRowStart
= lineData
.GetRowStart(row
- rowFirst
);
3127 rowLen
= lineData
.GetRowLength(row
- rowFirst
,
3128 GetLines()[cur
].length());
3142 if ( res
== wxTE_HT_ON_TEXT
)
3144 // now find the position in the line
3145 wxString lineText
= GetLineText(row
),
3148 if ( colRowStart
|| rowLen
)
3150 // look in this row only, not in whole line
3151 rowText
= lineText
.Mid(colRowStart
, rowLen
);
3155 // just take the whole string
3161 res
= HitTestLine(GetTextToShow(rowText
), x1
, colStart
);
3165 if ( colRowStartOut
)
3167 // give them the column offset in this ROW in pixels
3168 *colRowStartOut
= colRowStart
;
3171 // take into account that the ROW doesn't start in the
3172 // beginning of the LINE
3173 *colStart
+= colRowStart
;
3178 // the hit test result we return is for x1, so throw out
3179 // the result for x2 here
3180 int x2
= x1
+ x20
- x10
;
3181 (void)HitTestLine(GetTextToShow(rowText
), x2
, colEnd
);
3183 *colEnd
+= colRowStart
;
3187 else // before/after vertical text span
3191 // fill the column with the first/last position in the
3192 // corresponding line
3193 if ( res
== wxTE_HT_BEFORE
)
3195 else // res == wxTE_HT_BELOW
3196 *colStart
= GetLineText(GetNumberOfLines() - 1).length();
3202 // give them the row in text coords (as is)
3209 bool wxTextCtrl::GetLineAndRow(wxTextCoord row
,
3210 wxTextCoord
*lineOut
,
3211 wxTextCoord
*rowInLineOut
) const
3219 int nLines
= GetNumberOfLines();
3222 const wxArrayWrappedLinesData
& linesData
= WData().m_linesData
;
3223 for ( line
= 0; line
< nLines
; line
++ )
3225 if ( !WData().IsValidLine(line
) )
3228 if ( row
< linesData
[line
].GetNextRow() )
3230 // we found the right line
3231 rowInLine
= row
- linesData
[line
].GetFirstRow();
3237 if ( line
== nLines
)
3239 // the row is out of range
3243 else // no line wrapping, everything is easy
3245 if ( row
>= nLines
)
3254 *rowInLineOut
= rowInLine
;
3259 // ----------------------------------------------------------------------------
3261 // ----------------------------------------------------------------------------
3264 wxTextCtrl has not one but two scrolling mechanisms: one is semi-automatic
3265 scrolling in both horizontal and vertical direction implemented using
3266 wxScrollHelper and the second one is manual scrolling implemented using
3267 SData().m_ofsHorz and used by the single line controls without scroll bar.
3269 The first version (the standard one) always scrolls by fixed amount which is
3270 fine for vertical scrolling as all lines have the same height but is rather
3271 ugly for horizontal scrolling if proportional font is used. This is why we
3272 manually update and use SData().m_ofsHorz which contains the length of the string
3273 which is hidden beyond the left border. An important property of text
3274 controls using this kind of scrolling is that an entire number of characters
3275 is always shown and that parts of characters never appear on display -
3276 neither in the leftmost nor rightmost positions.
3278 Once again, for multi line controls SData().m_ofsHorz is always 0 and scrolling is
3279 done as usual for wxScrollWindow.
3282 void wxTextCtrl::ShowHorzPosition(wxCoord pos
)
3284 wxASSERT_MSG( IsSingleLine(), _T("doesn't work for multiline") );
3286 // pos is the logical position to show
3288 // SData().m_ofsHorz is the first logical position shown
3289 if ( pos
< SData().m_ofsHorz
)
3293 HitTestLine(m_value
, pos
, &col
);
3298 wxCoord width
= m_rectText
.width
;
3301 // if we are called from the ctor, m_rectText is not initialized
3302 // yet, so do it now
3304 width
= m_rectText
.width
;
3307 // SData().m_ofsHorz + width is the last logical position shown
3308 if ( pos
> SData().m_ofsHorz
+ width
)
3312 HitTestLine(m_value
, pos
- width
, &col
);
3313 ScrollText(col
+ 1);
3318 // scroll the window horizontally so that the first visible character becomes
3319 // the one at this position
3320 void wxTextCtrl::ScrollText(wxTextCoord col
)
3322 wxASSERT_MSG( IsSingleLine(),
3323 _T("ScrollText() is for single line controls only") );
3325 // never scroll beyond the left border
3329 // OPT: could only get the extent of the part of the string between col
3330 // and SData().m_colStart
3331 wxCoord ofsHorz
= GetTextWidth(GetLineText(0).Left(col
));
3333 if ( ofsHorz
!= SData().m_ofsHorz
)
3335 // remember the last currently used pixel
3336 int posLastVisible
= SData().m_posLastVisible
;
3337 if ( posLastVisible
== -1 )
3339 // this may happen when we're called very early, during the
3340 // controls construction
3341 UpdateLastVisible();
3343 posLastVisible
= SData().m_posLastVisible
;
3346 // NB1: to scroll to the right, offset must be negative, hence the
3347 // order of operands
3348 int dx
= SData().m_ofsHorz
- ofsHorz
;
3350 // NB2: we call Refresh() below which results in a call to
3351 // DoDraw(), so we must update SData().m_ofsHorz before calling it
3352 SData().m_ofsHorz
= ofsHorz
;
3353 SData().m_colStart
= col
;
3355 // after changing m_colStart, recalc the last visible position: we need
3356 // to recalc the last visible position beore scrolling in order to make
3357 // it appear exactly at the right edge of the text area after scrolling
3358 UpdateLastVisible();
3363 // we want to force the update of it after scrolling
3364 SData().m_colLastVisible
= -1;
3368 // scroll only the rectangle inside which there is the text
3369 wxRect rect
= m_rectText
;
3370 rect
.width
= posLastVisible
;
3372 rect
= ScrollNoRefresh(dx
, 0, &rect
);
3375 we need to manually refresh the part which ScrollWindow() doesn't
3376 refresh (with new API this means the part outside the rect returned
3377 by ScrollNoRefresh): indeed, if we had this:
3381 where '*' is text and 'o' is blank area at the end (too small to
3382 hold the next char) then after scrolling by 2 positions to the left
3387 where 'R' is the area refreshed by ScrollWindow() - but we still
3388 need to refresh the 'o' at the end as it may be now big enough to
3389 hold the new character shifted into view.
3391 when we are scrolling to the right, we need to update this rect as
3392 well because it might have contained something before but doesn't
3393 contain anything any more
3396 // we can combine both rectangles into one when scrolling to the left,
3397 // but we need two separate Refreshes() otherwise
3400 // refresh the uncovered part on the left
3401 Refresh(true, &rect
);
3403 // and now the area on the right
3404 rect
.x
= m_rectText
.x
+ posLastVisible
;
3405 rect
.width
= m_rectText
.width
- posLastVisible
;
3407 else // scrolling to the left
3409 // just extend the rect covering the uncovered area to the edge of
3411 rect
.width
+= m_rectText
.width
- posLastVisible
;
3414 Refresh(true, &rect
);
3416 // I don't know exactly why is this needed here but without it we may
3417 // scroll the window again (from the same method) before the previously
3418 // invalidated area is repainted when typing *very* quickly - and this
3419 // may lead to the display corruption
3424 void wxTextCtrl::CalcUnscrolledPosition(int x
, int y
, int *xx
, int *yy
) const
3426 if ( IsSingleLine() )
3428 // we don't use wxScrollHelper
3430 *xx
= x
+ SData().m_ofsHorz
;
3436 // let the base class do it
3437 wxScrollHelper::CalcUnscrolledPosition(x
, y
, xx
, yy
);
3441 void wxTextCtrl::CalcScrolledPosition(int x
, int y
, int *xx
, int *yy
) const
3443 if ( IsSingleLine() )
3445 // we don't use wxScrollHelper
3447 *xx
= x
- SData().m_ofsHorz
;
3453 // let the base class do it
3454 wxScrollHelper::CalcScrolledPosition(x
, y
, xx
, yy
);
3458 void wxTextCtrl::DoPrepareDC(wxDC
& dc
)
3460 // for single line controls we only have to deal with SData().m_ofsHorz and it's
3461 // useless to call base class version as they don't use normal scrolling
3462 if ( IsSingleLine() && SData().m_ofsHorz
)
3464 // adjust the DC origin if the text is shifted
3465 wxPoint pt
= dc
.GetDeviceOrigin();
3466 dc
.SetDeviceOrigin(pt
.x
- SData().m_ofsHorz
, pt
.y
);
3470 wxScrollHelper::DoPrepareDC(dc
);
3474 void wxTextCtrl::UpdateMaxWidth(wxTextCoord line
)
3478 // check if the max width changes after this line was modified
3479 wxCoord widthMaxOld
= MData().m_widthMax
,
3481 GetTextExtent(GetLineText(line
), &width
, NULL
);
3483 if ( line
== MData().m_lineLongest
)
3485 // this line was the longest one, is it still?
3486 if ( width
> MData().m_widthMax
)
3488 MData().m_widthMax
= width
;
3490 else if ( width
< MData().m_widthMax
)
3492 // we need to find the new longest line
3495 //else: its length didn't change, nothing to do
3497 else // it wasn't the longest line, but maybe it became it?
3499 // GetMaxWidth() and not MData().m_widthMax as it might be not calculated yet
3500 if ( width
> GetMaxWidth() )
3502 MData().m_widthMax
= width
;
3503 MData().m_lineLongest
= line
;
3507 MData().m_updateScrollbarX
= MData().m_widthMax
!= widthMaxOld
;
3510 void wxTextCtrl::RecalcFontMetrics()
3512 m_heightLine
= GetCharHeight();
3513 m_widthAvg
= GetCharWidth();
3516 void wxTextCtrl::RecalcMaxWidth()
3518 wxASSERT_MSG( !IsSingleLine(), _T("only used for multiline") );
3520 MData().m_widthMax
= -1;
3521 (void)GetMaxWidth();
3524 wxCoord
wxTextCtrl::GetMaxWidth() const
3526 if ( MData().m_widthMax
== -1 )
3530 // OPT: should we remember the widths of all the lines?
3532 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
3533 wxClientDC
dc(self
);
3534 dc
.SetFont(GetFont());
3536 self
->MData().m_widthMax
= 0;
3538 size_t count
= GetLineCount();
3539 for ( size_t n
= 0; n
< count
; n
++ )
3542 dc
.GetTextExtent(GetLines()[n
], &width
, NULL
);
3543 if ( width
> MData().m_widthMax
)
3545 // remember the width and the line which has it
3546 self
->MData().m_widthMax
= width
;
3547 self
->MData().m_lineLongest
= n
;
3552 wxASSERT_MSG( MData().m_widthMax
!= -1, _T("should have at least 1 line") );
3554 return MData().m_widthMax
;
3557 void wxTextCtrl::UpdateScrollbars()
3559 wxASSERT_MSG( !IsSingleLine(), _T("only used for multiline") );
3561 wxSize size
= GetRealTextArea().GetSize();
3563 // is our height enough to show all items?
3564 wxTextCoord nRows
= GetRowCount();
3565 wxCoord lineHeight
= GetLineHeight();
3566 bool showScrollbarY
= nRows
*lineHeight
> size
.y
;
3568 // is our width enough to show the longest line?
3569 wxCoord charWidth
, maxWidth
;
3570 bool showScrollbarX
;
3573 charWidth
= GetAverageWidth();
3574 maxWidth
= GetMaxWidth();
3575 showScrollbarX
= maxWidth
> size
.x
;
3577 else // never show the horz scrollbar
3579 // just to suppress compiler warnings about using uninit vars below
3580 charWidth
= maxWidth
= 0;
3582 showScrollbarX
= false;
3585 // calc the scrollbars ranges
3586 int scrollRangeX
= showScrollbarX
3587 ? (maxWidth
+ 2*charWidth
- 1) / charWidth
3589 int scrollRangeY
= showScrollbarY
? nRows
: 0;
3591 int scrollRangeXOld
= MData().m_scrollRangeX
,
3592 scrollRangeYOld
= MData().m_scrollRangeY
;
3593 if ( (scrollRangeY
!= scrollRangeYOld
) || (scrollRangeX
!= scrollRangeXOld
) )
3596 GetViewStart(&x
, &y
);
3599 // we want to leave the scrollbars at the same position which means
3600 // that x and y have to be adjusted as the number of positions may have
3603 // the number of positions is calculated from knowing that last
3604 // position = range - thumbSize and thumbSize == pageSize which is
3605 // equal to the window width / pixelsPerLine
3606 if ( scrollRangeXOld
)
3608 x
*= scrollRangeX
- m_rectText
.width
/ charWidth
;
3609 x
/= scrollRangeXOld
- m_rectText
.width
/ charWidth
;
3612 if ( scrollRangeYOld
)
3613 y
*= scrollRangeY
/ scrollRangeYOld
;
3616 SetScrollbars(charWidth
, lineHeight
,
3617 scrollRangeX
, scrollRangeY
,
3619 true /* no refresh */);
3621 if ( scrollRangeXOld
)
3623 const int w
= m_rectText
.width
/ charWidth
;
3624 if ( w
!= scrollRangeXOld
)
3626 x
*= scrollRangeX
- w
;
3627 x
/= scrollRangeXOld
- w
;
3632 MData().m_scrollRangeX
= scrollRangeX
;
3633 MData().m_scrollRangeY
= scrollRangeY
;
3635 // bring the current position in view
3639 MData().m_updateScrollbarX
=
3640 MData().m_updateScrollbarY
= false;
3643 void wxTextCtrl::OnInternalIdle()
3645 // notice that single line text control never has scrollbars
3646 if ( !IsSingleLine() &&
3647 (MData().m_updateScrollbarX
|| MData().m_updateScrollbarY
) )
3651 wxControl::OnInternalIdle();
3654 bool wxTextCtrl::SendAutoScrollEvents(wxScrollWinEvent
& event
) const
3656 bool forward
= event
.GetEventType() == wxEVT_SCROLLWIN_LINEDOWN
;
3657 if ( event
.GetOrientation() == wxHORIZONTAL
)
3659 return forward
? m_curCol
<= GetLineLength(m_curRow
) : m_curCol
> 0;
3663 return forward
? m_curRow
< GetNumberOfLines() : m_curRow
> 0;
3667 // ----------------------------------------------------------------------------
3669 // ----------------------------------------------------------------------------
3671 void wxTextCtrl::RefreshSelection()
3673 if ( HasSelection() )
3675 RefreshTextRange(m_selStart
, m_selEnd
);
3679 void wxTextCtrl::RefreshLineRange(wxTextCoord lineFirst
, wxTextCoord lineLast
)
3681 wxASSERT_MSG( lineFirst
<= lineLast
|| !lineLast
,
3682 _T("no lines to refresh") );
3685 // rect.x is already 0
3686 rect
.width
= m_rectText
.width
;
3687 wxCoord h
= GetLineHeight();
3689 wxTextCoord rowFirst
;
3690 if ( lineFirst
< GetNumberOfLines() )
3692 rowFirst
= GetFirstRowOfLine(lineFirst
);
3694 else // lineFirst == GetNumberOfLines()
3696 // lineFirst may be beyond the last line only if we refresh till
3697 // the end, otherwise it's illegal
3698 wxASSERT_MSG( lineFirst
== GetNumberOfLines() && !lineLast
,
3699 _T("invalid line range") );
3701 rowFirst
= GetRowAfterLine(lineFirst
- 1);
3704 rect
.y
= rowFirst
*h
;
3708 // refresh till this line (inclusive)
3709 wxTextCoord rowLast
= GetRowAfterLine(lineLast
);
3711 rect
.height
= (rowLast
- rowFirst
+ 1)*h
;
3713 else // lineLast == 0 means to refresh till the end
3715 // FIXME: calc it exactly
3716 rect
.height
= 32000;
3719 RefreshTextRect(rect
);
3722 void wxTextCtrl::RefreshTextRange(wxTextPos start
, wxTextPos end
)
3724 wxCHECK_RET( start
!= -1 && end
!= -1,
3725 _T("invalid RefreshTextRange() arguments") );
3727 // accept arguments in any order as it is more conenient for the caller
3728 OrderPositions(start
, end
);
3730 // this is acceptable but we don't do anything in this case
3734 wxTextPos colStart
, lineStart
;
3735 if ( !PositionToXY(start
, &colStart
, &lineStart
) )
3737 // the range is entirely beyond the end of the text, nothing to do
3741 wxTextCoord colEnd
, lineEnd
;
3742 if ( !PositionToXY(end
, &colEnd
, &lineEnd
) )
3744 // the range spans beyond the end of text, refresh to the end
3746 lineEnd
= GetNumberOfLines() - 1;
3749 // refresh all lines one by one
3750 for ( wxTextCoord line
= lineStart
; line
<= lineEnd
; line
++ )
3752 // refresh the first line from the start of the range to the end, the
3753 // intermediate ones entirely and the last one from the beginning to
3754 // the end of the range
3755 wxTextPos posStart
= line
== lineStart
? colStart
: 0;
3757 if ( (line
!= lineEnd
) || (colEnd
== -1) )
3759 // intermediate line or the last one but we need to refresh it
3760 // until the end anyhow - do it
3761 posCount
= wxString::npos
;
3765 // refresh just the positions in between the start and the end one
3766 posCount
= colEnd
- posStart
;
3770 RefreshColRange(line
, posStart
, posCount
);
3774 void wxTextCtrl::RefreshColRange(wxTextCoord line
,
3778 wxString text
= GetLineText(line
);
3780 wxASSERT_MSG( (size_t)start
<= text
.length() && count
,
3781 _T("invalid RefreshColRange() parameter") );
3783 RefreshPixelRange(line
,
3784 GetTextWidth(text
.Left((size_t)start
)),
3785 GetTextWidth(text
.Mid((size_t)start
, (size_t)count
)));
3788 // this method accepts "logical" coords in the sense that they are coordinates
3789 // in a logical line but it can span several rows if we wrap lines and
3790 // RefreshPixelRange() will then refresh several rows
3791 void wxTextCtrl::RefreshPixelRange(wxTextCoord line
,
3795 // we will use line text only in line wrap case
3799 text
= GetLineText(line
);
3802 // special case: width == 0 means to refresh till the end of line
3805 // refresh till the end of visible line
3806 width
= GetTotalWidth();
3810 // refresh till the end of text
3811 wxCoord widthAll
= GetTextWidth(text
);
3813 // extend width to the end of ROW
3814 width
= widthAll
- widthAll
% width
+ width
;
3817 // no need to refresh beyond the end of line
3820 //else: just refresh the specified part
3822 wxCoord h
= GetLineHeight();
3825 rect
.y
= GetFirstRowOfLine(line
)*h
;
3830 // (1) skip all rows which we don't touch at all
3831 const wxWrappedLineData
& lineData
= WData().m_linesData
[line
];
3832 if ( !WData().IsValidLine(line
) )
3835 wxCoord wLine
= 0; // suppress compiler warning about uninit var
3836 size_t rowLast
= lineData
.GetRowCount(),
3838 while ( (row
< rowLast
) &&
3839 (rect
.x
> (wLine
= lineData
.GetRowWidth(row
++))) )
3845 // (2) now refresh all lines except the last one: note that the first
3846 // line is refreshed from the given start to the end, all the next
3848 while ( (row
< rowLast
) && (width
> wLine
- rect
.x
) )
3850 rect
.width
= GetTotalWidth() - rect
.x
;
3851 RefreshTextRect(rect
);
3853 width
-= wLine
- rect
.x
;
3857 wLine
= lineData
.GetRowWidth(row
++);
3860 // (3) the code below will refresh the last line
3865 RefreshTextRect(rect
);
3868 void wxTextCtrl::RefreshTextRect(const wxRect
& rectClient
, bool textOnly
)
3871 CalcScrolledPosition(rectClient
.x
, rectClient
.y
, &rect
.x
, &rect
.y
);
3872 rect
.width
= rectClient
.width
;
3873 rect
.height
= rectClient
.height
;
3875 // account for the text area offset
3876 rect
.Offset(m_rectText
.GetPosition());
3878 // don't refresh beyond the text area unless we're refreshing the line wrap
3879 // marks in which case textOnly is false
3882 if ( rect
.GetRight() > m_rectText
.GetRight() )
3884 rect
.SetRight(m_rectText
.GetRight());
3886 if ( rect
.width
<= 0 )
3888 // nothing to refresh
3894 // check the bottom boundary always, even for the line wrap marks
3895 if ( rect
.GetBottom() > m_rectText
.GetBottom() )
3897 rect
.SetBottom(m_rectText
.GetBottom());
3899 if ( rect
.height
<= 0 )
3901 // nothing to refresh
3906 // never refresh before the visible rect
3907 if ( rect
.x
< m_rectText
.x
)
3908 rect
.x
= m_rectText
.x
;
3910 if ( rect
.y
< m_rectText
.y
)
3911 rect
.y
= m_rectText
.y
;
3913 wxLogTrace(_T("text"), _T("Refreshing (%d, %d)-(%d, %d)"),
3914 rect
.x
, rect
.y
, rect
.x
+ rect
.width
, rect
.y
+ rect
.height
);
3916 Refresh(true, &rect
);
3919 void wxTextCtrl::RefreshLineWrapMarks(wxTextCoord rowFirst
,
3920 wxTextCoord rowLast
)
3922 if ( WData().m_widthMark
)
3925 rectMarks
.x
= m_rectText
.width
;
3926 rectMarks
.width
= WData().m_widthMark
;
3927 rectMarks
.y
= rowFirst
*GetLineHeight();
3928 rectMarks
.height
= (rowLast
- rowFirst
)*GetLineHeight();
3930 RefreshTextRect(rectMarks
, false /* don't limit to text area */);
3934 // ----------------------------------------------------------------------------
3936 // ----------------------------------------------------------------------------
3938 void wxTextCtrl::DoDrawBorder(wxDC
& dc
, const wxRect
& rect
)
3940 m_renderer
->DrawTextBorder(dc
, GetBorder(), rect
, GetStateFlags());
3943 // ----------------------------------------------------------------------------
3944 // client area drawing
3945 // ----------------------------------------------------------------------------
3948 Several remarks about wxTextCtrl redraw logic:
3950 1. only the regions which must be updated are redrawn, this means that we
3951 never Refresh() the entire window but use RefreshPixelRange() and
3952 ScrollWindow() which only refresh small parts of it and iterate over the
3953 update region in our DoDraw()
3955 2. the text displayed on the screen is obtained using GetTextToShow(): it
3956 should be used for all drawing/measuring
3959 wxString
wxTextCtrl::GetTextToShow(const wxString
& text
) const
3963 textShown
= wxString(_T('*'), text
.length());
3970 void wxTextCtrl::DoDrawTextInRect(wxDC
& dc
, const wxRect
& rectUpdate
)
3972 // debugging trick to see the update rect visually
3974 static int s_countUpdates
= -1;
3975 if ( s_countUpdates
!= -1 )
3977 wxWindowDC
dc(this);
3978 dc
.SetBrush(*(++s_countUpdates
% 2 ? wxRED_BRUSH
: wxGREEN_BRUSH
));
3979 dc
.SetPen(*wxTRANSPARENT_PEN
);
3980 dc
.DrawRectangle(rectUpdate
);
3982 #endif // WXDEBUG_TEXT
3984 // calculate the range lineStart..lineEnd of lines to redraw
3985 wxTextCoord lineStart
, lineEnd
;
3986 if ( IsSingleLine() )
3993 wxPoint pt
= rectUpdate
.GetPosition();
3994 (void)HitTest(pt
, NULL
, &lineStart
);
3996 pt
.y
+= rectUpdate
.height
;
3997 (void)HitTest(pt
, NULL
, &lineEnd
);
4000 // prepare for drawing
4001 wxCoord hLine
= GetLineHeight();
4003 // these vars will be used for hit testing of the current row
4004 wxCoord y
= rectUpdate
.y
;
4005 const wxCoord x1
= rectUpdate
.x
;
4006 const wxCoord x2
= rectUpdate
.x
+ rectUpdate
.width
;
4009 rectText
.height
= hLine
;
4010 wxCoord yClient
= y
- GetClientAreaOrigin().y
;
4012 // we want to always start at the top of the line, otherwise if we redraw a
4013 // rect whose top is in the middle of a line, we'd draw this line shifted
4014 yClient
-= (yClient
- m_rectText
.y
) % hLine
;
4016 if ( IsSingleLine() )
4018 rectText
.y
= yClient
;
4020 else // multiline, adjust for scrolling
4022 CalcUnscrolledPosition(0, yClient
, NULL
, &rectText
.y
);
4025 wxRenderer
*renderer
= GetRenderer();
4027 // do draw the invalidated parts of each line: note that we iterate here
4028 // over ROWs, not over LINEs
4029 for ( wxTextCoord line
= lineStart
;
4030 y
< rectUpdate
.y
+ rectUpdate
.height
;
4032 rectText
.y
+= hLine
)
4034 // calculate the update rect in text positions for this line
4035 wxTextCoord colStart
, colEnd
, colRowStart
;
4036 wxTextCtrlHitTestResult ht
= HitTest2(y
, x1
, x2
,
4037 &line
, &colStart
, &colEnd
,
4040 if ( (ht
== wxTE_HT_BEYOND
) || (ht
== wxTE_HT_BELOW
) )
4042 wxASSERT_MSG( line
<= lineEnd
, _T("how did we get that far?") );
4044 if ( line
== lineEnd
)
4046 // we redrew everything
4050 // the update rect is beyond the end of line, no need to redraw
4051 // anything on this line - but continue with the remaining ones
4055 // for single line controls we may additionally cut off everything
4056 // which is to the right of the last visible position
4057 if ( IsSingleLine() )
4059 // don't show the columns which are scrolled out to the left
4060 if ( colStart
< SData().m_colStart
)
4061 colStart
= SData().m_colStart
;
4063 // colEnd may be less than colStart if colStart was changed by the
4065 if ( colEnd
< colStart
)
4068 // don't draw the chars beyond the rightmost one
4069 if ( SData().m_colLastVisible
== -1 )
4071 // recalculate this rightmost column
4072 UpdateLastVisible();
4075 if ( colStart
> SData().m_colLastVisible
)
4077 // don't bother redrawing something that is beyond the last
4082 if ( colEnd
> SData().m_colLastVisible
)
4084 colEnd
= SData().m_colLastVisible
;
4088 // extract the part of line we need to redraw
4089 wxString textLine
= GetTextToShow(GetLineText(line
));
4090 wxString text
= textLine
.Mid(colStart
, colEnd
- colStart
+ 1);
4092 // now deal with the selection: only do something if at least part of
4093 // the line is selected
4094 wxTextPos selStart
, selEnd
;
4095 if ( GetSelectedPartOfLine(line
, &selStart
, &selEnd
) )
4097 // and if this part is (at least partly) in the current row
4098 if ( (selStart
<= colEnd
) &&
4099 (selEnd
>= wxMax(colStart
, colRowStart
)) )
4101 // these values are relative to the start of the line while the
4102 // string passed to DrawTextLine() is only part of it, so
4103 // adjust the selection range accordingly
4104 selStart
-= colStart
;
4110 if ( (size_t)selEnd
>= text
.length() )
4111 selEnd
= text
.length();
4115 // reset selStart and selEnd to avoid passing them to
4116 // DrawTextLine() below
4122 // calculate the text coords on screen
4123 wxASSERT_MSG( colStart
>= colRowStart
, _T("invalid string part") );
4124 wxCoord ofsStart
= GetTextWidth(
4125 textLine
.Mid(colRowStart
,
4126 colStart
- colRowStart
));
4127 rectText
.x
= m_rectText
.x
+ ofsStart
;
4128 rectText
.width
= GetTextWidth(text
);
4131 renderer
->DrawTextLine(dc
, text
, rectText
, selStart
, selEnd
,
4133 wxLogTrace(_T("text"), _T("Line %ld: positions %ld-%ld redrawn."),
4134 line
, colStart
, colEnd
);
4138 void wxTextCtrl::DoDrawLineWrapMarks(wxDC
& dc
, const wxRect
& rectUpdate
)
4140 wxASSERT_MSG( WrapLines() && WData().m_widthMark
,
4141 _T("shouldn't be called at all") );
4143 wxRenderer
*renderer
= GetRenderer();
4146 rectMark
.x
= rectUpdate
.x
;
4147 rectMark
.width
= rectUpdate
.width
;
4148 wxCoord yTop
= GetClientAreaOrigin().y
;
4149 CalcUnscrolledPosition(0, rectUpdate
.y
- yTop
, NULL
, &rectMark
.y
);
4150 wxCoord hLine
= GetLineHeight();
4151 rectMark
.height
= hLine
;
4153 wxTextCoord line
, rowInLine
;
4156 CalcUnscrolledPosition(0, rectUpdate
.GetBottom() - yTop
, NULL
, &yBottom
);
4157 for ( ; rectMark
.y
< yBottom
; rectMark
.y
+= hLine
)
4159 if ( !GetLineAndRow(rectMark
.y
/ hLine
, &line
, &rowInLine
) )
4161 // we went beyond the end of text
4165 // is this row continued on the next one?
4166 if ( !WData().m_linesData
[line
].IsLastRow(rowInLine
) )
4168 renderer
->DrawLineWrapMark(dc
, rectMark
);
4173 void wxTextCtrl::DoDraw(wxControlRenderer
*renderer
)
4175 // hide the caret while we're redrawing the window and show it after we are
4177 wxCaretSuspend
cs(this);
4180 wxDC
& dc
= renderer
->GetDC();
4181 dc
.SetFont(GetFont());
4182 dc
.SetTextForeground(GetForegroundColour());
4184 // get the intersection of the update region with the text area: note that
4185 // the update region is in window coords and text area is in the client
4186 // ones, so it must be shifted before computing intersection
4187 wxRegion rgnUpdate
= GetUpdateRegion();
4189 wxRect rectTextArea
= GetRealTextArea();
4190 wxPoint pt
= GetClientAreaOrigin();
4191 wxRect rectTextAreaAdjusted
= rectTextArea
;
4192 rectTextAreaAdjusted
.x
+= pt
.x
;
4193 rectTextAreaAdjusted
.y
+= pt
.y
;
4194 rgnUpdate
.Intersect(rectTextAreaAdjusted
);
4196 // even though the drawing is already clipped to the update region, we must
4197 // explicitly clip it to the rect we will use as otherwise parts of letters
4198 // might be drawn outside of it (if even a small part of a charater is
4199 // inside, HitTest() will return its column and DrawText() can't draw only
4200 // the part of the character, of course)
4202 // FIXME: is this really a bug in wxMSW?
4203 rectTextArea
.width
--;
4205 dc
.DestroyClippingRegion();
4206 dc
.SetClippingRegion(rectTextArea
);
4208 // adjust for scrolling
4211 // and now refresh the invalidated parts of the window
4212 wxRegionIterator
iter(rgnUpdate
);
4213 for ( ; iter
.HaveRects(); iter
++ )
4215 wxRect r
= iter
.GetRect();
4217 // this is a workaround for wxGTK::wxRegion bug
4219 if ( !r
.width
|| !r
.height
)
4221 // ignore invalid rect
4226 DoDrawTextInRect(dc
, r
);
4229 // now redraw the line wrap marks (if we draw them)
4230 if ( WrapLines() && WData().m_widthMark
)
4232 // this is the rect inside which line wrap marks are drawn
4234 rectMarks
.x
= rectTextAreaAdjusted
.GetRight() + 1;
4235 rectMarks
.y
= rectTextAreaAdjusted
.y
;
4236 rectMarks
.width
= WData().m_widthMark
;
4237 rectMarks
.height
= rectTextAreaAdjusted
.height
;
4239 rgnUpdate
= GetUpdateRegion();
4240 rgnUpdate
.Intersect(rectMarks
);
4242 wxRect rectUpdate
= rgnUpdate
.GetBox();
4243 if ( rectUpdate
.width
&& rectUpdate
.height
)
4245 // the marks are outside previously set clipping region
4246 dc
.DestroyClippingRegion();
4248 DoDrawLineWrapMarks(dc
, rectUpdate
);
4252 // show caret first time only: we must show it after drawing the text or
4253 // the display can be corrupted when it's hidden
4254 if ( !m_hasCaret
&& GetCaret() && (FindFocus() == this) )
4262 // ----------------------------------------------------------------------------
4264 // ----------------------------------------------------------------------------
4266 bool wxTextCtrl::SetFont(const wxFont
& font
)
4268 if ( !wxControl::SetFont(font
) )
4271 // and refresh everything, of course
4272 InitInsertionPoint();
4275 // update geometry parameters
4277 RecalcFontMetrics();
4278 if ( !IsSingleLine() )
4284 // recreate it, in fact
4292 bool wxTextCtrl::Enable(bool enable
)
4294 if ( !wxTextCtrlBase::Enable(enable
) )
4297 if (FindFocus() == this && GetCaret() &&
4298 ((enable
&& !GetCaret()->IsVisible()) ||
4299 (!enable
&& GetCaret()->IsVisible())))
4305 void wxTextCtrl::CreateCaret()
4311 // FIXME use renderer
4312 caret
= new wxCaret(this, 1, GetLineHeight());
4316 // read only controls don't have the caret
4320 // SetCaret() will delete the old caret if any
4324 void wxTextCtrl::ShowCaret(bool show
)
4326 wxCaret
*caret
= GetCaret();
4329 // (re)position caret correctly
4330 caret
->Move(GetCaretPosition());
4332 // and show it there
4333 if ((show
&& !caret
->IsVisible()) ||
4334 (!show
&& caret
->IsVisible()))
4339 // ----------------------------------------------------------------------------
4340 // vertical scrolling (multiline only)
4341 // ----------------------------------------------------------------------------
4343 size_t wxTextCtrl::GetLinesPerPage() const
4345 if ( IsSingleLine() )
4348 return GetRealTextArea().height
/ GetLineHeight();
4351 wxTextPos
wxTextCtrl::GetPositionAbove()
4353 wxCHECK_MSG( !IsSingleLine(), INVALID_POS_VALUE
,
4354 _T("can't move cursor vertically in a single line control") );
4356 // move the cursor up by one ROW not by one LINE: this means that
4357 // we should really use HitTest() and not just go to the same
4358 // position in the previous line
4359 wxPoint pt
= GetCaretPosition() - m_rectText
.GetPosition();
4360 if ( MData().m_xCaret
== -1 )
4362 // remember the initial cursor abscissa
4363 MData().m_xCaret
= pt
.x
;
4367 // use the remembered abscissa
4368 pt
.x
= MData().m_xCaret
;
4371 CalcUnscrolledPosition(pt
.x
, pt
.y
, &pt
.x
, &pt
.y
);
4372 pt
.y
-= GetLineHeight();
4374 wxTextCoord col
, row
;
4375 if ( HitTestLogical(pt
, &col
, &row
) == wxTE_HT_BEFORE
)
4377 // can't move further
4378 return INVALID_POS_VALUE
;
4381 return XYToPosition(col
, row
);
4384 wxTextPos
wxTextCtrl::GetPositionBelow()
4386 wxCHECK_MSG( !IsSingleLine(), INVALID_POS_VALUE
,
4387 _T("can't move cursor vertically in a single line control") );
4389 // see comments for wxACTION_TEXT_UP
4390 wxPoint pt
= GetCaretPosition() - m_rectText
.GetPosition();
4391 if ( MData().m_xCaret
== -1 )
4393 // remember the initial cursor abscissa
4394 MData().m_xCaret
= pt
.x
;
4398 // use the remembered abscissa
4399 pt
.x
= MData().m_xCaret
;
4402 CalcUnscrolledPosition(pt
.x
, pt
.y
, &pt
.x
, &pt
.y
);
4403 pt
.y
+= GetLineHeight();
4405 wxTextCoord col
, row
;
4406 if ( HitTestLogical(pt
, &col
, &row
) == wxTE_HT_BELOW
)
4408 // can't go further down
4409 return INVALID_POS_VALUE
;
4412 // note that wxTE_HT_BEYOND is ok: it happens when we go down
4413 // from a longer line to a shorter one, for example (OTOH
4414 // wxTE_HT_BEFORE can never happen)
4415 return XYToPosition(col
, row
);
4418 // ----------------------------------------------------------------------------
4420 // ----------------------------------------------------------------------------
4422 bool wxTextCtrl::PerformAction(const wxControlAction
& actionOrig
,
4424 const wxString
& strArg
)
4426 // has the text changed as result of this action?
4427 bool textChanged
= false;
4429 // the remembered cursor abscissa for multiline text controls is usually
4430 // reset after each user action but for ones which do use it (UP and DOWN
4431 // for example) we shouldn't do it - as indicated by this flag
4432 bool rememberAbscissa
= false;
4434 // the command this action corresponds to or NULL if this action doesn't
4435 // change text at all or can't be undone
4436 wxTextCtrlCommand
*command
= NULL
;
4441 if ( actionOrig
.StartsWith(wxACTION_TEXT_PREFIX_DEL
, &action
) )
4446 else if ( actionOrig
.StartsWith(wxACTION_TEXT_PREFIX_SEL
, &action
) )
4450 else // not selection nor delete action
4452 action
= actionOrig
;
4455 // set newPos to -2 as it can't become equal to it in the assignments below
4456 // (but it can become -1)
4457 wxTextPos newPos
= INVALID_POS_VALUE
;
4459 if ( action
== wxACTION_TEXT_HOME
)
4461 newPos
= m_curPos
- m_curCol
;
4463 else if ( action
== wxACTION_TEXT_END
)
4465 newPos
= m_curPos
+ GetLineLength(m_curRow
) - m_curCol
;
4467 else if ( (action
== wxACTION_TEXT_GOTO
) ||
4468 (action
== wxACTION_TEXT_FIRST
) ||
4469 (action
== wxACTION_TEXT_LAST
) )
4471 if ( action
== wxACTION_TEXT_FIRST
)
4473 else if ( action
== wxACTION_TEXT_LAST
)
4474 numArg
= GetLastPosition();
4475 //else: numArg already contains the position
4479 else if ( action
== wxACTION_TEXT_UP
)
4481 if ( !IsSingleLine() )
4483 newPos
= GetPositionAbove();
4485 if ( newPos
!= INVALID_POS_VALUE
)
4487 // remember where the cursor original had been
4488 rememberAbscissa
= true;
4492 else if ( action
== wxACTION_TEXT_DOWN
)
4494 if ( !IsSingleLine() )
4496 newPos
= GetPositionBelow();
4498 if ( newPos
!= INVALID_POS_VALUE
)
4500 // remember where the cursor original had been
4501 rememberAbscissa
= true;
4505 else if ( action
== wxACTION_TEXT_LEFT
)
4507 newPos
= m_curPos
- 1;
4509 else if ( action
== wxACTION_TEXT_WORD_LEFT
)
4511 newPos
= GetWordStart();
4513 else if ( action
== wxACTION_TEXT_RIGHT
)
4515 newPos
= m_curPos
+ 1;
4517 else if ( action
== wxACTION_TEXT_WORD_RIGHT
)
4519 newPos
= GetWordEnd();
4521 else if ( action
== wxACTION_TEXT_INSERT
)
4523 if ( IsEditable() && !strArg
.empty() )
4525 // inserting text can be undone
4526 command
= new wxTextCtrlInsertCommand(strArg
);
4531 else if ( (action
== wxACTION_TEXT_PAGE_UP
) ||
4532 (action
== wxACTION_TEXT_PAGE_DOWN
) )
4534 if ( !IsSingleLine() )
4536 size_t count
= GetLinesPerPage();
4537 if ( count
> PAGE_OVERLAP_IN_LINES
)
4539 // pages should overlap slightly to allow the reader to keep
4540 // orientation in the text
4541 count
-= PAGE_OVERLAP_IN_LINES
;
4544 // remember where the cursor original had been
4545 rememberAbscissa
= true;
4547 bool goUp
= action
== wxACTION_TEXT_PAGE_UP
;
4548 for ( size_t line
= 0; line
< count
; line
++ )
4550 wxTextPos pos
= goUp
? GetPositionAbove() : GetPositionBelow();
4551 if ( pos
== INVALID_POS_VALUE
)
4553 // can't move further
4557 MoveInsertionPoint(pos
);
4561 // we implement the Unix scrolling model here: cursor will always
4562 // be on the first line after Page Down and on the last one after
4565 // Windows programs usually keep the cursor line offset constant
4566 // but do we really need it?
4570 // find the line such that when it is the first one, the
4571 // current position is in the last line
4573 for ( size_t line
= 0; line
< count
; line
++ )
4575 pos
= GetPositionAbove();
4576 if ( pos
== INVALID_POS_VALUE
)
4579 MoveInsertionPoint(pos
);
4582 MoveInsertionPoint(newPos
);
4584 PositionToLogicalXY(pos
, NULL
, &y
);
4586 else // scrolled down
4588 PositionToLogicalXY(newPos
, NULL
, &y
);
4591 // scroll vertically only
4592 Scroll(wxDefaultCoord
, y
);
4595 else if ( action
== wxACTION_TEXT_SEL_WORD
)
4597 SetSelection(GetWordStart(), GetWordEnd());
4599 else if ( action
== wxACTION_TEXT_ANCHOR_SEL
)
4603 else if ( action
== wxACTION_TEXT_EXTEND_SEL
)
4605 SetSelection(m_selAnchor
, numArg
);
4607 else if ( action
== wxACTION_TEXT_COPY
)
4611 else if ( action
== wxACTION_TEXT_CUT
)
4616 else if ( action
== wxACTION_TEXT_PASTE
)
4621 else if ( action
== wxACTION_TEXT_UNDO
)
4626 else if ( action
== wxACTION_TEXT_REDO
)
4633 return wxControl::PerformAction(action
, numArg
, strArg
);
4636 if ( newPos
!= INVALID_POS_VALUE
)
4638 // bring the new position into the range
4642 wxTextPos posLast
= GetLastPosition();
4643 if ( newPos
> posLast
)
4648 // if we have the selection, remove just it
4650 if ( HasSelection() )
4657 // otherwise delete everything between current position and
4659 if ( m_curPos
!= newPos
)
4664 else // nothing to delete
4666 // prevent test below from working
4667 from
= INVALID_POS_VALUE
;
4669 // and this is just to silent the compiler warning
4674 if ( from
!= INVALID_POS_VALUE
)
4676 command
= new wxTextCtrlRemoveCommand(from
, to
);
4679 else // cursor movement command
4682 DoSetInsertionPoint(newPos
);
4686 SetSelection(m_selAnchor
, m_curPos
);
4688 else // simple movement
4690 // clear the existing selection
4695 if ( !rememberAbscissa
&& !IsSingleLine() )
4697 MData().m_xCaret
= -1;
4703 // execute and remember it to be able to undo it later
4704 m_cmdProcessor
->Submit(command
);
4706 // undoable commands always change text
4709 else // no undoable command
4711 // m_cmdProcessor->StopCompressing()
4716 wxASSERT_MSG( IsEditable(), _T("non editable control changed?") );
4718 wxCommandEvent
event(wxEVT_COMMAND_TEXT_UPDATED
, GetId());
4719 InitCommandEvent(event
);
4720 GetEventHandler()->ProcessEvent(event
);
4722 // as the text changed...
4723 m_isModified
= true;
4729 void wxTextCtrl::OnChar(wxKeyEvent
& event
)
4731 // only process the key events from "simple keys" here
4732 if ( !event
.HasModifiers() )
4734 int keycode
= event
.GetKeyCode();
4736 wxChar unicode
= event
.GetUnicodeKey();
4738 if ( keycode
== WXK_RETURN
)
4740 if ( IsSingleLine() || (GetWindowStyle() & wxTE_PROCESS_ENTER
) )
4742 wxCommandEvent
event(wxEVT_COMMAND_TEXT_ENTER
, GetId());
4743 InitCommandEvent(event
);
4744 event
.SetString(GetValue());
4745 GetEventHandler()->ProcessEvent(event
);
4747 else // interpret <Enter> normally: insert new line
4749 PerformAction(wxACTION_TEXT_INSERT
, -1, _T('\n'));
4752 else if ( keycode
< 255 && isprint(keycode
) )
4754 PerformAction(wxACTION_TEXT_INSERT
, -1, (wxChar
)keycode
);
4756 // skip event.Skip() below
4760 else if (unicode
> 0)
4762 PerformAction(wxACTION_TEXT_INSERT
, -1, unicode
);
4769 // Ctrl-R refreshes the control in debug mode
4770 else if ( event
.ControlDown() && event
.GetKeyCode() == 'r' )
4772 #endif // __WXDEBUG__
4778 wxInputHandler
*wxTextCtrl::GetStdInputHandler(wxInputHandler
*handlerDef
)
4780 static wxStdTextCtrlInputHandler
s_handler(handlerDef
);
4785 // ----------------------------------------------------------------------------
4786 // wxStdTextCtrlInputHandler
4787 // ----------------------------------------------------------------------------
4789 wxStdTextCtrlInputHandler::wxStdTextCtrlInputHandler(wxInputHandler
*inphand
)
4790 : wxStdInputHandler(inphand
)
4792 m_winCapture
= NULL
;
4796 wxTextPos
wxStdTextCtrlInputHandler::HitTest(const wxTextCtrl
*text
,
4799 wxTextCoord col
, row
;
4800 wxTextCtrlHitTestResult ht
= text
->HitTest(pt
, &col
, &row
);
4802 wxTextPos pos
= text
->XYToPosition(col
, row
);
4804 // if the point is after the last column we must adjust the position to be
4805 // the last position in the line (unless it is already the last)
4806 if ( (ht
== wxTE_HT_BEYOND
) && (pos
< text
->GetLastPosition()) )
4814 bool wxStdTextCtrlInputHandler::HandleKey(wxInputConsumer
*consumer
,
4815 const wxKeyEvent
& event
,
4818 // we're only interested in key presses
4822 int keycode
= event
.GetKeyCode();
4824 wxControlAction action
;
4826 bool ctrlDown
= event
.ControlDown(),
4827 shiftDown
= event
.ShiftDown();
4830 action
= wxACTION_TEXT_PREFIX_SEL
;
4833 // the only key combination with Alt we recognize is Alt-Bksp for undo, so
4834 // treat it first separately
4835 if ( event
.AltDown() )
4837 if ( keycode
== WXK_BACK
&& !ctrlDown
&& !shiftDown
)
4838 action
= wxACTION_TEXT_UNDO
;
4840 else switch ( keycode
)
4844 action
<< (ctrlDown
? wxACTION_TEXT_FIRST
4845 : wxACTION_TEXT_HOME
);
4849 action
<< (ctrlDown
? wxACTION_TEXT_LAST
4850 : wxACTION_TEXT_END
);
4855 action
<< wxACTION_TEXT_UP
;
4860 action
<< wxACTION_TEXT_DOWN
;
4864 action
<< (ctrlDown
? wxACTION_TEXT_WORD_LEFT
4865 : wxACTION_TEXT_LEFT
);
4869 action
<< (ctrlDown
? wxACTION_TEXT_WORD_RIGHT
4870 : wxACTION_TEXT_RIGHT
);
4874 // we don't map Ctrl-PgUp/Dn to anything special - what should it
4875 // to? for now, it's the same as without control
4876 action
<< wxACTION_TEXT_PAGE_DOWN
;
4880 action
<< wxACTION_TEXT_PAGE_UP
;
4886 action
<< wxACTION_TEXT_PREFIX_DEL
<< wxACTION_TEXT_RIGHT
;
4891 action
<< wxACTION_TEXT_PREFIX_DEL
<< wxACTION_TEXT_LEFT
;
4896 // reset the action as it could be already set to one of the
4898 action
= wxACTION_NONE
;
4905 action
= wxACTION_TEXT_REDO
;
4909 action
= wxACTION_TEXT_COPY
;
4913 action
= wxACTION_TEXT_PASTE
;
4917 action
= wxACTION_TEXT_CUT
;
4921 action
= wxACTION_TEXT_UNDO
;
4927 if ( (action
!= wxACTION_NONE
) && (action
!= wxACTION_TEXT_PREFIX_SEL
) )
4929 consumer
->PerformAction(action
, -1, str
);
4934 return wxStdInputHandler::HandleKey(consumer
, event
, pressed
);
4937 bool wxStdTextCtrlInputHandler::HandleMouse(wxInputConsumer
*consumer
,
4938 const wxMouseEvent
& event
)
4940 if ( event
.LeftDown() )
4942 wxASSERT_MSG( !m_winCapture
, _T("left button going down twice?") );
4944 wxTextCtrl
*text
= wxStaticCast(consumer
->GetInputWindow(), wxTextCtrl
);
4946 m_winCapture
= text
;
4947 m_winCapture
->CaptureMouse();
4951 wxTextPos pos
= HitTest(text
, event
.GetPosition());
4954 text
->PerformAction(wxACTION_TEXT_ANCHOR_SEL
, pos
);
4957 else if ( event
.LeftDClick() )
4959 // select the word the cursor is on
4960 consumer
->PerformAction(wxACTION_TEXT_SEL_WORD
);
4962 else if ( event
.LeftUp() )
4966 m_winCapture
->ShowCaret();
4968 m_winCapture
->ReleaseMouse();
4969 m_winCapture
= NULL
;
4973 return wxStdInputHandler::HandleMouse(consumer
, event
);
4976 bool wxStdTextCtrlInputHandler::HandleMouseMove(wxInputConsumer
*consumer
,
4977 const wxMouseEvent
& event
)
4982 wxTextCtrl
*text
= wxStaticCast(m_winCapture
, wxTextCtrl
);
4983 wxTextPos pos
= HitTest(text
, event
.GetPosition());
4986 text
->PerformAction(wxACTION_TEXT_EXTEND_SEL
, pos
);
4990 return wxStdInputHandler::HandleMouseMove(consumer
, event
);
4994 wxStdTextCtrlInputHandler::HandleFocus(wxInputConsumer
*consumer
,
4995 const wxFocusEvent
& event
)
4997 wxTextCtrl
*text
= wxStaticCast(consumer
->GetInputWindow(), wxTextCtrl
);
4999 // the selection appearance changes depending on whether we have the focus
5000 text
->RefreshSelection();
5002 if (event
.GetEventType() == wxEVT_SET_FOCUS
)
5004 if (text
->GetCaret() && !text
->GetCaret()->IsVisible())
5009 if (text
->GetCaret() && text
->GetCaret()->IsVisible())
5013 // never refresh entirely
5017 #endif // wxUSE_TEXTCTRL