1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/univ/textctrl.cpp
4 // Author: Vadim Zeitlin
7 // Copyright: (c) 2000 SciTech Software, Inc. (www.scitechsoft.com)
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
14 + 1. update vert scrollbar when any line length changes for WrapLines()
15 + 2. cursor movement ("Hello,^" -> "^verse!" on Arrow Down)?
16 -> maybe save the x position and use it instead of current in handling
17 DOWN/UP actions (this would make up/down always return the cursor to
19 3. split file into chunks
20 +? 4. rewrite Replace() refresh logic to deal with wrapping lines
21 +? 5. cache info found by GetPartOfWrappedLine() - performance must be horrible
24 6. backspace refreshes too much (until end of line)
28 Optimisation hints from PureQuantify:
30 +1. wxStringTokenize is the slowest part of Replace
31 2. GetDC/ReleaseDC are very slow, avoid calling them several times
32 +3. GetCharHeight() should be cached too
33 4. wxClientDC construction/destruction in HitTestLine is horribly expensive
35 For line wrapping controls HitTest2 takes 50% of program time. The results
36 of GetRowsPerLine and GetPartOfWrappedLine *MUST* be cached.
38 Search for "OPT!" for things which must be optimized.
44 Everywhere in this file LINE refers to a logical line of text, and ROW to a
45 physical line of text on the display. They are the same unless WrapLines()
46 is true in which case a single LINE may correspond to multiple ROWs.
48 A text position is an unsigned int (which for reasons of compatibility is
49 still a long as wxTextPos) from 0 to GetLastPosition() inclusive. The positions
50 correspond to the gaps between the letters so the position 0 is just
51 before the first character and the last position is the one beyond the last
52 character. For an empty text control GetLastPosition() returns 0.
54 Lines and columns returned/accepted by XYToPosition() and PositionToXY()
55 start from 0. The y coordinate is a LINE, not a ROW. Columns correspond to
56 the characters, the first column of a line is the first character in it,
57 the last one is length(line text). For compatibility, again, lines and
58 columns are also longs.
60 When translating lines/column coordinates to/from positions, the line and
61 column give the character after the given position. Thus, GetLastPosition()
62 doesn't have any corresponding column.
64 An example of positions and lines/columns for a control without wrapping
65 containing the text "Hello, Universe!\nGoodbye"
68 pos: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
69 H e l l o , U n i v e r s e ! line 0
70 col: 0 1 2 3 4 5 6 7 8 9 1 1 1 1 1 1
79 The same example for a control with line wrap assuming "Universe" is too
80 long to fit on the same line with "Hello,":
83 H e l l o , line 0 (row 0)
87 pos: 6 7 8 9 0 1 2 3 4 5 6
88 U n i v e r s e ! line 0 (row 1)
89 col: 6 7 8 9 1 1 1 1 1 1
92 (line 1 == row 2 same as above)
94 Note that there is still the same number of columns and positions and that
95 there is no (logical) position at the end of the first ROW. This position
96 is identified with the preceding one (which is not how Windows does it: it
97 identifies it with the next one, i.e. the first position of the next line,
98 but much more logical IMHO).
102 Search for "OPT" for possible optimizations
104 A possible global optimization would be to always store the coords in the
105 text in triplets (pos, col, line) and update them simultaneously instead of
106 recalculating col and line from pos each time it is needed. Currently we
107 only do it for the current position but we might also do it for the
108 selection start and end.
111 // ============================================================================
113 // ============================================================================
115 // ----------------------------------------------------------------------------
117 // ----------------------------------------------------------------------------
119 #include "wx/wxprec.h"
127 #include "wx/textctrl.h"
131 #include "wx/dcclient.h"
132 #include "wx/validate.h"
133 #include "wx/dataobj.h"
138 #include "wx/clipbrd.h"
140 #include "wx/textfile.h"
142 #include "wx/caret.h"
144 #include "wx/univ/inphand.h"
145 #include "wx/univ/renderer.h"
146 #include "wx/univ/colschem.h"
147 #include "wx/univ/theme.h"
149 #include "wx/cmdproc.h"
151 #if wxDEBUG_LEVEL >= 2
152 // turn extra wxTextCtrl-specific debugging on/off
155 // turn wxTextCtrl::Replace() debugging on (slows down code a *lot*!)
156 #define WXDEBUG_TEXT_REPLACE
157 #endif // wxDEBUG_LEVEL >= 2
159 // wxStringTokenize only needed for debug checks
160 #ifdef WXDEBUG_TEXT_REPLACE
161 #include "wx/tokenzr.h"
162 #endif // WXDEBUG_TEXT_REPLACE
164 // ----------------------------------------------------------------------------
165 // wxStdTextCtrlInputHandler: this control handles only the mouse/kbd actions
166 // common to Win32 and GTK, platform-specific things are implemented elsewhere
167 // ----------------------------------------------------------------------------
169 class WXDLLEXPORT wxStdTextCtrlInputHandler
: public wxStdInputHandler
172 wxStdTextCtrlInputHandler(wxInputHandler
*inphand
);
174 virtual bool HandleKey(wxInputConsumer
*consumer
,
175 const wxKeyEvent
& event
,
177 virtual bool HandleMouse(wxInputConsumer
*consumer
,
178 const wxMouseEvent
& event
);
179 virtual bool HandleMouseMove(wxInputConsumer
*consumer
,
180 const wxMouseEvent
& event
);
181 virtual bool HandleFocus(wxInputConsumer
*consumer
, const wxFocusEvent
& event
);
184 // get the position of the mouse click
185 static wxTextPos
HitTest(const wxTextCtrl
*text
, const wxPoint
& pos
);
188 wxTextCtrl
*m_winCapture
;
191 // ----------------------------------------------------------------------------
193 // ----------------------------------------------------------------------------
195 // exchange two positions so that from is always less than or equal to to
196 static inline void OrderPositions(wxTextPos
& from
, wxTextPos
& to
)
200 wxTextPos tmp
= from
;
206 // ----------------------------------------------------------------------------
208 // ----------------------------------------------------------------------------
210 // names of text ctrl commands
211 #define wxTEXT_COMMAND_INSERT wxT("insert")
212 #define wxTEXT_COMMAND_REMOVE wxT("remove")
214 // the value which is never used for text position, even not -1 which is
215 // sometimes used for some special meaning
216 static const wxTextPos INVALID_POS_VALUE
= wxInvalidTextCoord
;
218 // overlap between pages (when using PageUp/Dn) in lines
219 static const size_t PAGE_OVERLAP_IN_LINES
= 1;
221 // ----------------------------------------------------------------------------
222 // private data of wxTextCtrl
223 // ----------------------------------------------------------------------------
225 // the data only used by single line text controls
226 struct wxTextSingleLineData
228 // the position of the first visible pixel and the first visible column
230 wxTextCoord m_colStart
;
232 // and the last ones (m_posLastVisible is the width but m_colLastVisible
233 // is an absolute value)
234 wxCoord m_posLastVisible
;
235 wxTextCoord m_colLastVisible
;
238 wxTextSingleLineData()
243 m_colLastVisible
= -1;
244 m_posLastVisible
= -1;
249 // the data only used by multi line text controls
250 struct wxTextMultiLineData
253 wxArrayString m_lines
;
255 // the current ranges of the scrollbars
259 // should we adjust the horz/vert scrollbar?
260 bool m_updateScrollbarX
,
263 // the max line length in pixels
266 // the index of the line which has the length of m_widthMax
267 wxTextCoord m_lineLongest
;
269 // the rect in which text appears: it is even less than m_rectText because
270 // only the last _complete_ line is shown, hence there is an unoccupied
271 // horizontal band at the bottom of it
272 wxRect m_rectTextReal
;
274 // the x-coordinate of the caret before we started moving it vertically:
275 // this is used to ensure that moving the caret up and then down will
276 // return it to the same position as if we always round it in one direction
277 // we would shift it in that direction
279 // when m_xCaret == -1, we don't have any remembered position
283 wxTextMultiLineData()
289 m_updateScrollbarY
= false;
298 // the data only used by multi line text controls in line wrap mode
299 class wxWrappedLineData
301 // these functions set all our values, so give them access to them
302 friend void wxTextCtrl::LayoutLine(wxTextCoord line
,
303 wxWrappedLineData
& lineData
) const;
304 friend void wxTextCtrl::LayoutLines(wxTextCoord
) const;
313 // get the start of any row (remember that accessing m_rowsStart doesn't work
314 // for the first one)
315 wxTextCoord
GetRowStart(wxTextCoord row
) const
317 wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
319 return row
? m_rowsStart
[row
- 1] : 0;
322 // get the length of the row (using the total line length which we don't
323 // have here but need to calculate the length of the last row, so it must
325 wxTextCoord
GetRowLength(wxTextCoord row
, wxTextCoord lenLine
) const
327 wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
329 // note that m_rowsStart[row] is the same as GetRowStart(row + 1) (but
330 // slightly more efficient) and lenLine is the same as the start of the
331 // first row of the next line
332 return ((size_t)row
== m_rowsStart
.GetCount() ? lenLine
: m_rowsStart
[row
])
336 // return the width of the row in pixels
337 wxCoord
GetRowWidth(wxTextCoord row
) const
339 wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
341 return m_rowsWidth
[row
];
344 // return the number of rows
345 size_t GetRowCount() const
347 wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
349 return m_rowsStart
.GetCount() + 1;
352 // return the number of additional (i.e. after the first one) rows
353 size_t GetExtraRowCount() const
355 wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
357 return m_rowsStart
.GetCount();
360 // return the first row of this line
361 wxTextCoord
GetFirstRow() const
363 wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
368 // return the first row of the next line
369 wxTextCoord
GetNextRow() const
371 wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
373 return m_rowFirst
+ m_rowsStart
.GetCount() + 1;
376 // this just provides direct access to m_rowsStart aerray for efficiency
377 wxTextCoord
GetExtraRowStart(wxTextCoord row
) const
379 wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
381 return m_rowsStart
[row
];
384 // this code is unused any longer
386 // return true if the column is in the start of the last row (hence the row
387 // it is in is not wrapped)
388 bool IsLastRow(wxTextCoord colRowStart
) const
390 return colRowStart
== GetRowStart(m_rowsStart
.GetCount());
393 // return true if the column is the last column of the row starting in
395 bool IsLastColInRow(wxTextCoord colRowStart
,
396 wxTextCoord colRowEnd
,
397 wxTextCoord lenLine
) const
399 // find the row which starts with colRowStart
400 size_t nRows
= GetRowCount();
401 for ( size_t n
= 0; n
< nRows
; n
++ )
403 if ( GetRowStart(n
) == colRowStart
)
405 wxTextCoord colNextRowStart
= n
== nRows
- 1
407 : GetRowStart(n
+ 1);
409 wxASSERT_MSG( colRowEnd
< colNextRowStart
,
410 wxT("this column is not in this row at all!") );
412 return colRowEnd
== colNextRowStart
- 1;
416 // caller got it wrong
417 wxFAIL_MSG( wxT("this column is not in the start of the row!") );
423 // is this row the last one in its line?
424 bool IsLastRow(wxTextCoord row
) const
426 return (size_t)row
== GetExtraRowCount();
429 // the line is valid if it had been laid out correctly: note that just
430 // shiwting the line (because one of previous lines changed) doesn't make
432 bool IsValid() const { return !m_rowsWidth
.IsEmpty(); }
434 // invalidating line will relayout it
435 void Invalidate() { m_rowsWidth
.Empty(); }
438 // for each line we remember the starting columns of all its rows after the
439 // first one (which always starts at 0), i.e. if a line is wrapped twice
440 // (== takes 3 rows) its m_rowsStart[0] may be 10 and m_rowsStart[1] == 15
441 wxArrayLong m_rowsStart
;
443 // and the width of each row in pixels (this array starts from 0, as usual)
444 wxArrayInt m_rowsWidth
;
446 // and also its starting row (0 for the first line, first lines'
447 // m_rowsStart.GetCount() + 1 for the second &c): it is set to -1 initially
448 // and this means that the struct hadn't yet been initialized
449 wxTextCoord m_rowFirst
;
451 // the last modification "time"-stamp used by LayoutLines()
455 WX_DECLARE_OBJARRAY(wxWrappedLineData
, wxArrayWrappedLinesData
);
456 #include "wx/arrimpl.cpp"
457 WX_DEFINE_OBJARRAY(wxArrayWrappedLinesData
);
459 struct wxTextWrappedData
: public wxTextMultiLineData
461 // the width of the column to the right of the text rect used for the
462 // indicator mark display for the wrapped lines
465 // the data for each line
466 wxArrayWrappedLinesData m_linesData
;
468 // flag telling us to recalculate all starting rows starting from this line
469 // (if it is -1, we don't have to recalculate anything) - it is set when
470 // the number of the rows in the middle of the control changes
471 wxTextCoord m_rowFirstInvalid
;
473 // the current timestamp used by LayoutLines()
476 // invalidate starting rows of all lines (NOT rows!) after this one
477 void InvalidateLinesBelow(wxTextCoord line
)
479 if ( m_rowFirstInvalid
== -1 || m_rowFirstInvalid
> line
)
481 m_rowFirstInvalid
= line
;
485 // check if this line is valid: i.e. before the first invalid one
486 bool IsValidLine(wxTextCoord line
) const
488 return ((m_rowFirstInvalid
== -1) || (line
< m_rowFirstInvalid
)) &&
489 m_linesData
[line
].IsValid();
496 m_rowFirstInvalid
= -1;
501 // ----------------------------------------------------------------------------
502 // private classes for undo/redo management
503 // ----------------------------------------------------------------------------
506 We use custom versions of wxWidgets command processor to implement undo/redo
507 as we want to avoid storing the backpointer to wxTextCtrl in wxCommand
508 itself: this is a waste of memory as all commands in the given command
509 processor always have the same associated wxTextCtrl and so it makes sense
510 to store the backpointer there.
512 As for the rest of the implementation, it's fairly standard: we have 2
513 command classes corresponding to adding and removing text.
516 // a command corresponding to a wxTextCtrl action
517 class wxTextCtrlCommand
: public wxCommand
520 wxTextCtrlCommand(const wxString
& name
) : wxCommand(true, name
) { }
522 // we don't use these methods as they don't make sense for us as we need a
523 // wxTextCtrl to be applied
524 virtual bool Do() { wxFAIL_MSG(wxT("shouldn't be called")); return false; }
525 virtual bool Undo() { wxFAIL_MSG(wxT("shouldn't be called")); return false; }
527 // instead, our command processor uses these methods
528 virtual bool Do(wxTextCtrl
*text
) = 0;
529 virtual bool Undo(wxTextCtrl
*text
) = 0;
532 // insert text command
533 class wxTextCtrlInsertCommand
: public wxTextCtrlCommand
536 wxTextCtrlInsertCommand(const wxString
& textToInsert
)
537 : wxTextCtrlCommand(wxTEXT_COMMAND_INSERT
), m_text(textToInsert
)
542 // combine the 2 commands together
543 void Append(wxTextCtrlInsertCommand
*other
);
545 virtual bool CanUndo() const;
546 virtual bool Do(wxTextCtrl
*text
);
547 virtual bool Do() { return wxTextCtrlCommand::Do(); }
548 virtual bool Undo() { return wxTextCtrlCommand::Undo(); }
549 virtual bool Undo(wxTextCtrl
*text
);
552 // the text we insert
555 // the position where we inserted the text
559 // remove text command
560 class wxTextCtrlRemoveCommand
: public wxTextCtrlCommand
563 wxTextCtrlRemoveCommand(wxTextPos from
, wxTextPos to
)
564 : wxTextCtrlCommand(wxTEXT_COMMAND_REMOVE
)
570 virtual bool CanUndo() const;
571 virtual bool Do(wxTextCtrl
*text
);
572 virtual bool Do() { return wxTextCtrlCommand::Do(); }
573 virtual bool Undo() { return wxTextCtrlCommand::Undo(); }
574 virtual bool Undo(wxTextCtrl
*text
);
577 // the range of text to delete
581 // the text which was deleted when this command was Do()ne
582 wxString m_textDeleted
;
585 // a command processor for a wxTextCtrl
586 class wxTextCtrlCommandProcessor
: public wxCommandProcessor
589 wxTextCtrlCommandProcessor(wxTextCtrl
*text
)
591 m_compressInserts
= false;
596 // override Store() to compress multiple wxTextCtrlInsertCommand into one
597 virtual void Store(wxCommand
*command
);
599 // stop compressing insert commands when this is called
600 void StopCompressing() { m_compressInserts
= false; }
603 wxTextCtrl
*GetTextCtrl() const { return m_text
; }
604 bool IsCompressing() const { return m_compressInserts
; }
607 virtual bool DoCommand(wxCommand
& cmd
)
608 { return ((wxTextCtrlCommand
&)cmd
).Do(m_text
); }
609 virtual bool UndoCommand(wxCommand
& cmd
)
610 { return ((wxTextCtrlCommand
&)cmd
).Undo(m_text
); }
612 // check if this command is a wxTextCtrlInsertCommand and return it casted
613 // to the right type if it is or NULL otherwise
614 wxTextCtrlInsertCommand
*IsInsertCommand(wxCommand
*cmd
);
617 // the control we're associated with
620 // if the flag is true we're compressing subsequent insert commands into
621 // one so that the entire typing could be undone in one call to Undo()
622 bool m_compressInserts
;
625 // ============================================================================
627 // ============================================================================
629 BEGIN_EVENT_TABLE(wxTextCtrl
, wxTextCtrlBase
)
630 EVT_CHAR(wxTextCtrl::OnChar
)
632 EVT_SIZE(wxTextCtrl::OnSize
)
635 // ----------------------------------------------------------------------------
637 // ----------------------------------------------------------------------------
639 void wxTextCtrl::Init()
645 m_isModified
= false;
657 // init the undo manager
658 m_cmdProcessor
= new wxTextCtrlCommandProcessor(this);
664 bool wxTextCtrl::Create(wxWindow
*parent
,
666 const wxString
& value
,
670 const wxValidator
& validator
,
671 const wxString
&name
)
673 if ( style
& wxTE_MULTILINE
)
675 // for compatibility with wxMSW we create the controls with vertical
676 // scrollbar always shown unless they have wxTE_RICH style (because
677 // Windows text controls always has vert scrollbar but richedit one
679 if ( !(style
& wxTE_RICH
) )
681 style
|= wxALWAYS_SHOW_SB
;
684 // wrapping style: wxTE_DONTWRAP == wxHSCROLL so if it's _not_ given,
685 // we won't have horizontal scrollbar automatically, no need to do
688 // TODO: support wxTE_NO_VSCROLL (?)
690 // create data object for normal multiline or for controls with line
692 if ( style
& wxHSCROLL
)
694 m_data
.mdata
= new wxTextMultiLineData
;
696 else // we must wrap lines if we don't have horizontal scrollbar
698 // NB: we can't rely on HasFlag(wxHSCROLL) as the flags can change
699 // later and even wxWindow::Create() itself temporarily resets
700 // wxHSCROLL in wxUniv, so remember that we have a wrapped data
701 // and not just a multi line data in a separate variable
703 m_data
.wdata
= new wxTextWrappedData
;
708 // this doesn't make sense for single line controls
711 // create data object for single line controls
712 m_data
.sdata
= new wxTextSingleLineData
;
715 #if wxUSE_TWO_WINDOWS
716 if ((style
& wxBORDER_MASK
) == 0)
717 style
|= wxBORDER_SUNKEN
;
720 if ( !wxControl::Create(parent
, id
, pos
, size
, style
,
726 SetCursor(wxCURSOR_IBEAM
);
728 if ( style
& wxTE_MULTILINE
)
730 // we should always have at least one line in a multiline control
731 MData().m_lines
.Add(wxEmptyString
);
733 if ( !(style
& wxHSCROLL
) )
735 WData().m_linesData
.Add(new wxWrappedLineData
);
736 WData().InvalidateLinesBelow(0);
739 // we might support it but it's quite useless and other ports don't
741 wxASSERT_MSG( !(style
& wxTE_PASSWORD
),
742 wxT("wxTE_PASSWORD can't be used with multiline ctrls") );
747 SetInitialSize(size
);
749 m_isEditable
= !(style
& wxTE_READONLY
);
752 InitInsertionPoint();
754 // we can't show caret right now as we're not shown yet and so it would
755 // result in garbage on the screen - we'll do it after first OnPaint()
758 CreateInputHandler(wxINP_HANDLER_TEXTCTRL
);
760 wxSizeEvent
sizeEvent(GetSize(), GetId());
761 GetEventHandler()->ProcessEvent(sizeEvent
);
766 wxTextCtrl::~wxTextCtrl()
768 delete m_cmdProcessor
;
772 if ( IsSingleLine() )
774 else if ( WrapLines() )
781 // ----------------------------------------------------------------------------
783 // ----------------------------------------------------------------------------
785 void wxTextCtrl::DoSetValue(const wxString
& value
, int flags
)
787 if ( value
!= GetValue() )
789 EventsSuppressor
noeventsIf(this, !(flags
& SetValue_SendEvent
));
791 Replace(0, GetLastPosition(), value
);
793 if ( IsSingleLine() )
795 SetInsertionPoint(0);
798 else // nothing changed
800 // still send event for consistency
801 if ( flags
& SetValue_SendEvent
)
802 SendTextUpdatedEvent();
806 const wxArrayString
& wxTextCtrl::GetLines() const
808 return MData().m_lines
;
811 size_t wxTextCtrl::GetLineCount() const
813 return MData().m_lines
.GetCount();
816 wxString
wxTextCtrl::DoGetValue() const
818 // for multiline controls we don't always store the total value but only
819 // recompute it when asked - and to invalidate it we just empty it in
821 if ( !IsSingleLine() && m_value
.empty() )
823 // recalculate: note that we always do it for empty multilien control,
824 // but then it's so quick that it's not important
826 // the first line is special as there is no \n before it, so it's
828 const wxArrayString
& lines
= GetLines();
829 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
830 self
->m_value
<< lines
[0u];
831 size_t count
= lines
.GetCount();
832 for ( size_t n
= 1; n
< count
; n
++ )
834 self
->m_value
<< wxT('\n') << lines
[n
];
841 void wxTextCtrl::Clear()
843 SetValue(wxEmptyString
);
846 bool wxTextCtrl::ReplaceLine(wxTextCoord line
,
847 const wxString
& text
)
851 // first, we have to relayout the line entirely
853 // OPT: we might try not to recalc the unchanged part of line
855 wxWrappedLineData
& lineData
= WData().m_linesData
[line
];
857 // if we had some number of rows before, use this number, otherwise
858 // just make sure that the test below (rowsNew != rowsOld) will be true
860 if ( lineData
.IsValid() )
862 rowsOld
= lineData
.GetExtraRowCount();
864 else // line wasn't laid out yet
866 // assume it changed entirely as we can't do anything better
870 // now change the line
871 MData().m_lines
[line
] = text
;
873 // OPT: we choose to lay it our immediately instead of delaying it
874 // until it is needed because it allows us to avoid invalidating
875 // lines further down if the number of rows didn't chnage, but
876 // maybe we can imporve this even further?
877 LayoutLine(line
, lineData
);
879 int rowsNew
= lineData
.GetExtraRowCount();
881 if ( rowsNew
!= rowsOld
)
883 // we have to update the line wrap marks as this is normally done
884 // by LayoutLines() which we bypassed by calling LayoutLine()
886 wxTextCoord rowFirst
= lineData
.GetFirstRow(),
887 rowCount
= wxMax(rowsOld
, rowsNew
);
888 RefreshLineWrapMarks(rowFirst
, rowFirst
+ rowCount
);
890 // next, if this is not the last line, as the number of rows in it
891 // changed, we need to shift all the lines below it
892 if ( (size_t)line
< WData().m_linesData
.GetCount() )
894 // number of rows changed shifting all lines below
895 WData().InvalidateLinesBelow(line
+ 1);
898 // the number of rows changed
904 MData().m_lines
[line
] = text
;
907 // the number of rows didn't change
911 void wxTextCtrl::RemoveLine(wxTextCoord line
)
913 MData().m_lines
.RemoveAt(line
);
916 // we need to recalculate all the starting rows from this line, but we
917 // can avoid doing it if this line was never calculated: this means
918 // that we will recalculate all lines below it anyhow later if needed
919 if ( WData().IsValidLine(line
) )
921 WData().InvalidateLinesBelow(line
);
924 WData().m_linesData
.RemoveAt(line
);
928 void wxTextCtrl::InsertLine(wxTextCoord line
, const wxString
& text
)
930 MData().m_lines
.Insert(text
, line
);
933 WData().m_linesData
.Insert(new wxWrappedLineData
, line
);
935 // invalidate everything below it
936 WData().InvalidateLinesBelow(line
);
940 void wxTextCtrl::Replace(wxTextPos from
, wxTextPos to
, const wxString
& text
)
942 wxTextCoord colStart
, colEnd
,
946 !PositionToXY(from
, &colStart
, &lineStart
) ||
947 !PositionToXY(to
, &colEnd
, &lineEnd
) )
949 wxFAIL_MSG(wxT("invalid range in wxTextCtrl::Replace"));
954 #ifdef WXDEBUG_TEXT_REPLACE
955 // a straighforward (but very inefficient) way of calculating what the new
957 wxString textTotal
= GetValue();
958 wxString
textTotalNew(textTotal
, (size_t)from
);
959 textTotalNew
+= text
;
960 if ( (size_t)to
< textTotal
.length() )
961 textTotalNew
+= textTotal
.c_str() + (size_t)to
;
962 #endif // WXDEBUG_TEXT_REPLACE
964 // remember the old selection and reset it immediately: we must do it
965 // before calling Refresh(anything) as, at least under GTK, this leads to
966 // an _immediate_ repaint (under MSW it is delayed) and hence parts of
967 // text would be redrawn as selected if we didn't reset the selection
968 int selStartOld
= m_selStart
,
969 selEndOld
= m_selEnd
;
974 if ( IsSingleLine() )
976 // replace the part of the text with the new value
977 wxString
valueNew(m_value
, (size_t)from
);
979 // remember it for later use
980 wxCoord startNewText
= GetTextWidth(valueNew
);
983 if ( (size_t)to
< m_value
.length() )
985 valueNew
+= m_value
.c_str() + (size_t)to
;
988 // we usually refresh till the end of line except of the most common case
989 // when some text is appended to the end of the string in which case we
991 wxCoord widthNewText
;
993 if ( (size_t)from
< m_value
.length() )
995 // refresh till the end of line
998 else // text appended, not replaced
1000 // refresh only the new text
1001 widthNewText
= GetTextWidth(text
);
1006 // force SData().m_colLastVisible update
1007 SData().m_colLastVisible
= -1;
1010 RefreshPixelRange(0, startNewText
, widthNewText
);
1014 //OPT: special case for replacements inside single line?
1017 Join all the lines in the replacement range into one string, then
1018 replace a part of it with the new text and break it into lines again.
1021 // (0) we want to know if this replacement changes the number of rows
1022 // as if it does we need to refresh everything below the changed
1023 // text (it will be shifted...) and we can avoid it if there is no
1025 bool rowsNumberChanged
= false;
1028 const wxArrayString
& linesOld
= GetLines();
1031 for ( line
= lineStart
; line
<= lineEnd
; line
++ )
1033 if ( line
> lineStart
)
1035 // from the previous line
1036 textOrig
+= wxT('\n');
1039 textOrig
+= linesOld
[line
];
1042 // we need to append the '\n' for the last line unless there is no
1044 size_t countOld
= linesOld
.GetCount();
1046 // (2) replace text in the combined string
1048 // (2a) leave the part before replaced area unchanged
1049 wxString
textNew(textOrig
, colStart
);
1051 // these values will be used to refresh the changed area below
1052 wxCoord widthNewText
,
1053 startNewText
= GetTextWidth(textNew
);
1054 if ( (size_t)colStart
== linesOld
[lineStart
].length() )
1056 // text appended, refresh just enough to show the new text
1057 widthNewText
= GetTextWidth(text
.BeforeFirst(wxT('\n')));
1059 else // text inserted, refresh till the end of line
1064 // (2b) insert new text
1067 // (2c) and append the end of the old text
1069 // adjust for index shift: to is relative to colStart, not 0
1070 size_t toRel
= (size_t)((to
- from
) + colStart
);
1071 if ( toRel
< textOrig
.length() )
1073 textNew
+= textOrig
.c_str() + toRel
;
1076 // (3) break it into lines
1078 wxArrayString lines
;
1079 const wxChar
*curLineStart
= textNew
.c_str();
1080 for ( const wxChar
*p
= textNew
.c_str(); ; p
++ )
1082 // end of line/text?
1083 if ( !*p
|| *p
== wxT('\n') )
1085 lines
.Add(wxString(curLineStart
, p
));
1089 curLineStart
= p
+ 1;
1093 #ifdef WXDEBUG_TEXT_REPLACE
1094 // (3a) all empty tokens should be counted as replacing with "foo" and
1095 // with "foo\n" should have different effects
1096 wxArrayString lines2
= wxStringTokenize(textNew
, wxT("\n"),
1097 wxTOKEN_RET_EMPTY_ALL
);
1099 if ( lines2
.IsEmpty() )
1101 lines2
.Add(wxEmptyString
);
1104 wxASSERT_MSG( lines
.GetCount() == lines2
.GetCount(),
1105 wxT("Replace() broken") );
1106 for ( size_t n
= 0; n
< lines
.GetCount(); n
++ )
1108 wxASSERT_MSG( lines
[n
] == lines2
[n
], wxT("Replace() broken") );
1110 #endif // WXDEBUG_TEXT_REPLACE
1112 // (3b) special case: if we replace everything till the end we need to
1113 // keep an empty line or the lines would disappear completely
1114 // (this also takes care of never leaving m_lines empty)
1115 if ( ((size_t)lineEnd
== countOld
- 1) && lines
.IsEmpty() )
1117 lines
.Add(wxEmptyString
);
1120 size_t nReplaceCount
= lines
.GetCount(),
1123 // (4) merge into the array
1126 for ( line
= lineStart
; line
<= lineEnd
; line
++, nReplaceLine
++ )
1128 if ( nReplaceLine
< nReplaceCount
)
1130 // we have the replacement line for this one
1131 if ( ReplaceLine(line
, lines
[nReplaceLine
]) )
1133 rowsNumberChanged
= true;
1136 UpdateMaxWidth(line
);
1138 else // no more replacement lines
1140 // (4b) delete all extra lines (note that we need to delete
1141 // them backwards because indices shift while we do it)
1142 bool deletedLongestLine
= false;
1143 for ( wxTextCoord lineDel
= lineEnd
; lineDel
>= line
; lineDel
-- )
1145 if ( lineDel
== MData().m_lineLongest
)
1147 // we will need to recalc the max line width
1148 deletedLongestLine
= true;
1151 RemoveLine(lineDel
);
1154 if ( deletedLongestLine
)
1159 // even the line number changed
1160 rowsNumberChanged
= true;
1162 // update line to exit the loop
1167 // (4c) insert the new lines
1168 if ( nReplaceLine
< nReplaceCount
)
1170 // even the line number changed
1171 rowsNumberChanged
= true;
1175 InsertLine(++lineEnd
, lines
[nReplaceLine
++]);
1177 UpdateMaxWidth(lineEnd
);
1179 while ( nReplaceLine
< nReplaceCount
);
1182 // (5) now refresh the changed area
1184 // update the (cached) last position first as refresh functions use it
1185 m_posLast
+= text
.length() - to
+ from
;
1187 // we may optimize refresh if the number of rows didn't change - but if
1188 // it did we have to refresh everything below the part we chanegd as
1189 // well as it might have moved
1190 if ( !rowsNumberChanged
)
1192 // refresh the line we changed
1195 RefreshPixelRange(lineStart
++, startNewText
, widthNewText
);
1199 //OPT: we shouldn't refresh the unchanged part of the line in
1200 // this case, but instead just refresh the tail of it - the
1201 // trouble is that we don't know here where does this tail
1205 // number of rows didn't change, refresh the updated rows and the
1207 if ( lineStart
<= lineEnd
)
1208 RefreshLineRange(lineStart
, lineEnd
);
1210 else // rows number did change
1214 // refresh only part of the first line
1215 RefreshPixelRange(lineStart
++, startNewText
, widthNewText
);
1217 //else: we have to refresh everything as some part of the text
1218 // could be in the previous row before but moved to the next
1219 // one now (due to word wrap)
1221 wxTextCoord lineEnd
= GetLines().GetCount() - 1;
1222 if ( lineStart
<= lineEnd
)
1223 RefreshLineRange(lineStart
, lineEnd
);
1225 // refresh text rect left below
1226 RefreshLineRange(lineEnd
+ 1, 0);
1228 // the vert scrollbar might [dis]appear
1229 MData().m_updateScrollbarY
= true;
1232 // must recalculate it - will do later
1236 #ifdef WXDEBUG_TEXT_REPLACE
1237 // optimized code above should give the same result as straightforward
1238 // computation in the beginning
1239 wxASSERT_MSG( GetValue() == textTotalNew
, wxT("error in Replace()") );
1240 #endif // WXDEBUG_TEXT_REPLACE
1242 // update the current position: note that we always put the cursor at the
1243 // end of the replacement text
1244 DoSetInsertionPoint(from
+ text
.length());
1246 // and the selection: this is complicated by the fact that selection coords
1247 // must be first updated to reflect change in text coords, i.e. if we had
1248 // selection from 17 to 19 and we just removed this range, we don't have to
1249 // refresh anything, so we can't just use ClearSelection() here
1250 if ( selStartOld
!= -1 )
1252 // refresh the parst of the selection outside the changed text (which
1253 // we already refreshed)
1254 if ( selStartOld
< from
)
1255 RefreshTextRange(selStartOld
, from
);
1256 if ( to
< selEndOld
)
1257 RefreshTextRange(to
, selEndOld
);
1261 // now call it to do the rest (not related to refreshing)
1264 SendTextUpdatedEventIfAllowed();
1267 void wxTextCtrl::Remove(wxTextPos from
, wxTextPos to
)
1269 // Replace() only works with correctly ordered arguments, so exchange them
1271 OrderPositions(from
, to
);
1273 Replace(from
, to
, wxEmptyString
);
1276 void wxTextCtrl::WriteText(const wxString
& text
)
1278 // replace the selection with the new text
1281 Replace(m_curPos
, m_curPos
, text
);
1284 void wxTextCtrl::AppendText(const wxString
& text
)
1286 SetInsertionPointEnd();
1290 // ----------------------------------------------------------------------------
1292 // ----------------------------------------------------------------------------
1294 void wxTextCtrl::SetInsertionPoint(wxTextPos pos
)
1296 wxCHECK_RET( pos
>= 0 && pos
<= GetLastPosition(),
1297 wxT("insertion point position out of range") );
1299 // don't do anything if it didn't change
1300 if ( pos
!= m_curPos
)
1302 DoSetInsertionPoint(pos
);
1305 if ( !IsSingleLine() )
1307 // moving cursor should reset the stored abscissa (even if the cursor
1308 // position didn't actually change!)
1309 MData().m_xCaret
= -1;
1315 void wxTextCtrl::InitInsertionPoint()
1317 // so far always put it in the beginning
1318 DoSetInsertionPoint(0);
1320 // this will also set the selection anchor correctly
1324 void wxTextCtrl::MoveInsertionPoint(wxTextPos pos
)
1326 wxASSERT_MSG( pos
>= 0 && pos
<= GetLastPosition(),
1327 wxT("DoSetInsertionPoint() can only be called with valid pos") );
1330 PositionToXY(m_curPos
, &m_curCol
, &m_curRow
);
1333 void wxTextCtrl::DoSetInsertionPoint(wxTextPos pos
)
1335 MoveInsertionPoint(pos
);
1340 void wxTextCtrl::SetInsertionPointEnd()
1342 SetInsertionPoint(GetLastPosition());
1345 wxTextPos
wxTextCtrl::GetInsertionPoint() const
1350 wxTextPos
wxTextCtrl::GetLastPosition() const
1353 if ( IsSingleLine() )
1355 pos
= m_value
.length();
1361 size_t nLineCount
= GetLineCount();
1362 for ( size_t nLine
= 0; nLine
< nLineCount
; nLine
++ )
1364 // +1 is because the positions at the end of this line and of the
1365 // start of the next one are different
1366 pos
+= GetLines()[nLine
].length() + 1;
1371 // the last position is at the end of the last line, not in the
1372 // beginning of the next line after it
1376 // more probable reason of this would be to forget to update m_posLast
1377 wxASSERT_MSG( pos
== m_posLast
, wxT("bug in GetLastPosition()") );
1378 #endif // WXDEBUG_TEXT
1386 // ----------------------------------------------------------------------------
1388 // ----------------------------------------------------------------------------
1390 void wxTextCtrl::GetSelection(wxTextPos
* from
, wxTextPos
* to
) const
1398 wxString
wxTextCtrl::GetSelectionText() const
1402 if ( HasSelection() )
1404 if ( IsSingleLine() )
1406 sel
= m_value
.Mid(m_selStart
, m_selEnd
- m_selStart
);
1410 wxTextCoord colStart
, lineStart
,
1412 PositionToXY(m_selStart
, &colStart
, &lineStart
);
1413 PositionToXY(m_selEnd
, &colEnd
, &lineEnd
);
1415 // as always, we need to check for the special case when the start
1416 // and end line are the same
1417 if ( lineEnd
== lineStart
)
1419 sel
= GetLines()[lineStart
].Mid(colStart
, colEnd
- colStart
);
1421 else // sel on multiple lines
1423 // take the end of the first line
1424 sel
= GetLines()[lineStart
].c_str() + colStart
;
1427 // all intermediate ones
1428 for ( wxTextCoord line
= lineStart
+ 1; line
< lineEnd
; line
++ )
1430 sel
<< GetLines()[line
] << wxT('\n');
1433 // and the start of the last one
1434 sel
+= GetLines()[lineEnd
].Left(colEnd
);
1442 void wxTextCtrl::SetSelection(wxTextPos from
, wxTextPos to
)
1444 // selecting till -1 is the same as selecting to the end
1447 // and selecting (-1, -1) range is the same as selecting everything, by
1451 to
= GetLastPosition();
1454 if ( from
== -1 || to
== from
)
1458 else // valid sel range
1460 // remember the 'to' position as the current position, used to move the
1461 // caret there later
1462 wxTextPos toOrig
= to
;
1464 OrderPositions(from
, to
);
1466 wxCHECK_RET( to
<= GetLastPosition(),
1467 wxT("invalid range in wxTextCtrl::SetSelection") );
1469 if ( from
!= m_selStart
|| to
!= m_selEnd
)
1471 // we need to use temp vars as RefreshTextRange() may call DoDraw()
1472 // directly and so m_selStart/End must be reset by then
1473 wxTextPos selStartOld
= m_selStart
,
1474 selEndOld
= m_selEnd
;
1479 wxLogTrace(wxT("text"), wxT("Selection range is %ld-%ld"),
1480 m_selStart
, m_selEnd
);
1482 // refresh only the part of text which became (un)selected if
1484 if ( selStartOld
== m_selStart
)
1486 RefreshTextRange(selEndOld
, m_selEnd
);
1488 else if ( selEndOld
== m_selEnd
)
1490 RefreshTextRange(m_selStart
, selStartOld
);
1494 // OPT: could check for other cases too but it is probably not
1495 // worth it as the two above are the most common ones
1496 if ( selStartOld
!= -1 )
1497 RefreshTextRange(selStartOld
, selEndOld
);
1498 if ( m_selStart
!= -1 )
1499 RefreshTextRange(m_selStart
, m_selEnd
);
1502 // we need to fully repaint the invalidated areas of the window
1503 // before scrolling it (from DoSetInsertionPoint which is typically
1504 // called after SetSelection()), otherwise they may stay unpainted
1505 m_targetWindow
->Update();
1507 //else: nothing to do
1509 // the insertion point is put at the location where the caret was moved
1510 DoSetInsertionPoint(toOrig
);
1514 void wxTextCtrl::ClearSelection()
1516 if ( HasSelection() )
1518 // we need to use temp vars as RefreshTextRange() may call DoDraw()
1519 // directly (see above as well)
1520 wxTextPos selStart
= m_selStart
,
1523 // no selection any more
1527 // refresh the old selection
1528 RefreshTextRange(selStart
, selEnd
);
1531 // the anchor should be moved even if there was no selection previously
1532 m_selAnchor
= m_curPos
;
1535 void wxTextCtrl::RemoveSelection()
1537 if ( !HasSelection() )
1540 Remove(m_selStart
, m_selEnd
);
1543 bool wxTextCtrl::GetSelectedPartOfLine(wxTextCoord line
,
1544 wxTextPos
*start
, wxTextPos
*end
) const
1551 if ( !HasSelection() )
1553 // no selection at all, hence no selection in this line
1557 wxTextCoord lineStart
, colStart
;
1558 PositionToXY(m_selStart
, &colStart
, &lineStart
);
1559 if ( lineStart
> line
)
1561 // this line is entirely above the selection
1565 wxTextCoord lineEnd
, colEnd
;
1566 PositionToXY(m_selEnd
, &colEnd
, &lineEnd
);
1567 if ( lineEnd
< line
)
1569 // this line is entirely below the selection
1573 if ( line
== lineStart
)
1578 *end
= lineEnd
== lineStart
? colEnd
: GetLineLength(line
);
1580 else if ( line
== lineEnd
)
1583 *start
= lineEnd
== lineStart
? colStart
: 0;
1587 else // the line is entirely inside the selection
1592 *end
= GetLineLength(line
);
1598 // ----------------------------------------------------------------------------
1600 // ----------------------------------------------------------------------------
1602 bool wxTextCtrl::IsModified() const
1604 return m_isModified
;
1607 bool wxTextCtrl::IsEditable() const
1609 // disabled control can never be edited
1610 return m_isEditable
&& IsEnabled();
1613 void wxTextCtrl::MarkDirty()
1615 m_isModified
= true;
1618 void wxTextCtrl::DiscardEdits()
1620 m_isModified
= false;
1623 void wxTextCtrl::SetEditable(bool editable
)
1625 if ( editable
!= m_isEditable
)
1627 m_isEditable
= editable
;
1629 // the caret (dis)appears
1632 // the appearance of the control might have changed
1637 // ----------------------------------------------------------------------------
1638 // col/lines <-> position correspondence
1639 // ----------------------------------------------------------------------------
1642 A few remarks about this stuff:
1644 o The numbering of the text control columns/rows starts from 0.
1645 o Start of first line is position 0, its last position is line.length()
1646 o Start of the next line is the last position of the previous line + 1
1649 int wxTextCtrl::GetLineLength(wxTextCoord line
) const
1651 if ( IsSingleLine() )
1653 wxASSERT_MSG( line
== 0, wxT("invalid GetLineLength() parameter") );
1655 return m_value
.length();
1659 wxCHECK_MSG( (size_t)line
< GetLineCount(), -1,
1660 wxT("line index out of range") );
1662 return GetLines()[line
].length();
1666 wxString
wxTextCtrl::GetLineText(wxTextCoord line
) const
1668 if ( IsSingleLine() )
1670 wxASSERT_MSG( line
== 0, wxT("invalid GetLineLength() parameter") );
1676 //this is called during DoGetBestSize
1677 if (line
== 0 && GetLineCount() == 0) return wxEmptyString
;
1679 wxCHECK_MSG( (size_t)line
< GetLineCount(), wxEmptyString
,
1680 wxT("line index out of range") );
1682 return GetLines()[line
];
1686 int wxTextCtrl::GetNumberOfLines() const
1688 // there is always 1 line, even if the text is empty
1689 return IsSingleLine() ? 1 : GetLineCount();
1692 wxTextPos
wxTextCtrl::XYToPosition(wxTextCoord x
, wxTextCoord y
) const
1694 // note that this method should accept any values of x and y and return -1
1695 // if they are out of range
1696 if ( IsSingleLine() )
1698 return ( x
> GetLastPosition() || y
> 0 ) ? wxOutOfRangeTextCoord
: x
;
1702 if ( (size_t)y
>= GetLineCount() )
1704 // this position is below the text
1705 return GetLastPosition();
1709 for ( size_t nLine
= 0; nLine
< (size_t)y
; nLine
++ )
1711 // +1 is because the positions at the end of this line and of the
1712 // start of the next one are different
1713 pos
+= GetLines()[nLine
].length() + 1;
1716 // take into account also the position in line
1717 if ( (size_t)x
> GetLines()[y
].length() )
1719 // don't return position in the next line
1720 x
= GetLines()[y
].length();
1727 bool wxTextCtrl::PositionToXY(wxTextPos pos
,
1728 wxTextCoord
*x
, wxTextCoord
*y
) const
1730 if ( IsSingleLine() )
1732 if ( (size_t)pos
> m_value
.length() )
1744 wxTextPos posCur
= 0;
1745 size_t nLineCount
= GetLineCount();
1746 for ( size_t nLine
= 0; nLine
< nLineCount
; nLine
++ )
1748 // +1 is because the start the start of the next line is one
1749 // position after the end of this one
1750 wxTextPos posNew
= posCur
+ GetLines()[nLine
].length() + 1;
1753 // we've found the line, now just calc the column
1761 wxASSERT_MSG( XYToPosition(pos
- posCur
, nLine
) == pos
,
1762 wxT("XYToPosition() or PositionToXY() broken") );
1763 #endif // WXDEBUG_TEXT
1767 else // go further down
1773 // beyond the last line
1778 wxTextCoord
wxTextCtrl::GetRowsPerLine(wxTextCoord line
) const
1780 // a normal line has one row
1781 wxTextCoord numRows
= 1;
1785 // add the number of additional rows
1786 numRows
+= WData().m_linesData
[line
].GetExtraRowCount();
1792 wxTextCoord
wxTextCtrl::GetRowCount() const
1794 wxTextCoord count
= GetLineCount();
1799 count
= GetFirstRowOfLine(count
- 1) +
1800 WData().m_linesData
[count
- 1].GetRowCount();
1806 wxTextCoord
wxTextCtrl::GetRowAfterLine(wxTextCoord line
) const
1811 if ( !WData().IsValidLine(line
) )
1816 return WData().m_linesData
[line
].GetNextRow();
1819 wxTextCoord
wxTextCtrl::GetFirstRowOfLine(wxTextCoord line
) const
1824 if ( !WData().IsValidLine(line
) )
1829 return WData().m_linesData
[line
].GetFirstRow();
1832 bool wxTextCtrl::PositionToLogicalXY(wxTextPos pos
,
1834 wxCoord
*yOut
) const
1836 wxTextCoord col
, line
;
1838 // optimization for special (but common) case when we already have the col
1840 if ( pos
== m_curPos
)
1845 else // must really calculate col/line from pos
1847 if ( !PositionToXY(pos
, &col
, &line
) )
1851 int hLine
= GetLineHeight();
1853 wxString textLine
= GetLineText(line
);
1854 if ( IsSingleLine() || !WrapLines() )
1856 x
= GetTextWidth(textLine
.Left(col
));
1859 else // difficult case: multline control with line wrap
1861 y
= GetFirstRowOfLine(line
);
1863 wxTextCoord colRowStart
;
1864 y
+= GetRowInLine(line
, col
, &colRowStart
);
1868 // x is the width of the text before this position in this row
1869 x
= GetTextWidth(textLine
.Mid(colRowStart
, col
- colRowStart
));
1880 bool wxTextCtrl::PositionToDeviceXY(wxTextPos pos
,
1882 wxCoord
*yOut
) const
1885 if ( !PositionToLogicalXY(pos
, &x
, &y
) )
1888 // finally translate the logical text rect coords into physical client
1890 CalcScrolledPosition(m_rectText
.x
+ x
, m_rectText
.y
+ y
, xOut
, yOut
);
1895 wxPoint
wxTextCtrl::GetCaretPosition() const
1897 wxCoord xCaret
, yCaret
;
1898 if ( !PositionToDeviceXY(m_curPos
, &xCaret
, &yCaret
) )
1900 wxFAIL_MSG( wxT("Caret can't be beyond the text!") );
1903 return wxPoint(xCaret
, yCaret
);
1906 // pos may be -1 to show the current position
1907 void wxTextCtrl::ShowPosition(wxTextPos pos
)
1909 bool showCaret
= GetCaret() && GetCaret()->IsVisible();
1913 if ( IsSingleLine() )
1915 ShowHorzPosition(GetTextWidth(m_value
.Left(pos
)));
1917 else if ( MData().m_scrollRangeX
|| MData().m_scrollRangeY
) // multiline with scrollbars
1920 GetViewStart(&xStart
, &yStart
);
1926 PositionToLogicalXY(pos
, &x
, &y
);
1928 wxRect rectText
= GetRealTextArea();
1930 // scroll the position vertically into view: if it is currently above
1931 // it, make it the first one, otherwise the last one
1932 if ( MData().m_scrollRangeY
)
1934 y
/= GetLineHeight();
1940 else // we are currently in or below the view area
1942 // find the last row currently shown
1947 // to find the last row we need to use the generic HitTest
1950 // OPT this is a bit silly: we undo this in HitTest(), so
1951 // it would be better to factor out the common
1952 // functionality into a separate function (OTOH it
1953 // won't probably save us that much)
1954 wxPoint
pt(0, rectText
.height
- 1);
1955 pt
+= GetClientAreaOrigin();
1956 pt
+= m_rectText
.GetPosition();
1957 HitTest(pt
, &col
, &yEnd
);
1959 // find the row inside the line
1960 yEnd
= GetFirstRowOfLine(yEnd
) + GetRowInLine(yEnd
, col
);
1964 // finding the last line is easy if each line has exactly
1966 yEnd
= yStart
+ rectText
.height
/ GetLineHeight();
1971 // scroll down: the current item should appear at the
1972 // bottom of the view
1973 Scroll(0, y
- (yEnd
- yStart
));
1978 // scroll the position horizontally into view
1980 // we follow what I believe to be Windows behaviour here, that is if
1981 // the position is already entirely in the view we do nothing, but if
1982 // we do have to scroll the window to bring it into view, we scroll it
1983 // not just enough to show the position but slightly more so that this
1984 // position is at 1/3 of the window width from the closest border to it
1985 // (I'm not sure that Windows does exactly this but it looks like this)
1986 if ( MData().m_scrollRangeX
)
1988 // unlike for the rows, xStart doesn't correspond to the starting
1989 // column as they all have different widths, so we need to
1990 // translate everything to pixels
1992 // we want the text between x and x2 be entirely inside the view
1993 // (i.e. the current character)
1995 // make xStart the first visible pixel (and not position)
1996 int wChar
= GetAverageWidth();
2001 // we want the position of this column be 1/3 to the right of
2003 x
-= rectText
.width
/ 3;
2006 Scroll(x
/ wChar
, y
);
2008 else // maybe we're beyond the right border of the view?
2010 wxTextCoord col
, row
;
2011 if ( PositionToXY(pos
, &col
, &row
) )
2013 wxString lineText
= GetLineText(row
);
2014 wxCoord x2
= x
+ GetTextWidth(lineText
[(size_t)col
]);
2015 if ( x2
> xStart
+ rectText
.width
)
2017 // we want the position of this column be 1/3 to the
2018 // left of the right edge, i.e. 2/3 right of the left
2020 x2
-= (2*rectText
.width
)/3;
2023 Scroll(x2
/ wChar
, row
);
2029 //else: multiline but no scrollbars, hence nothing to do
2035 // ----------------------------------------------------------------------------
2037 // ----------------------------------------------------------------------------
2040 TODO: we could have (easy to do) vi-like options for word movement, i.e.
2041 distinguish between inlusive/exclusive words and between words and
2042 WORDS (in vim sense) and also, finally, make the set of characters
2043 which make up a word configurable - currently we use the exclusive
2044 WORDS only (coincidentally, this is what Windows edit control does)
2046 For future references, here is what vim help says:
2048 A word consists of a sequence of letters, digits and underscores, or
2049 a sequence of other non-blank characters, separated with white space
2050 (spaces, tabs, <EOL>). This can be changed with the 'iskeyword'
2053 A WORD consists of a sequence of non-blank characters, separated with
2054 white space. An empty line is also considered to be a word and a
2058 static inline bool IsWordChar(wxChar ch
)
2060 return !wxIsspace(ch
);
2063 wxTextPos
wxTextCtrl::GetWordStart() const
2065 if ( m_curPos
== -1 || m_curPos
== 0 )
2068 if ( m_curCol
== 0 )
2070 // go to the end of the previous line
2071 return m_curPos
- 1;
2074 // it shouldn't be possible to learn where the word starts in the password
2079 // start at the previous position
2080 const wxChar
*p0
= GetLineText(m_curRow
).c_str();
2081 const wxChar
*p
= p0
+ m_curCol
- 1;
2083 // find the end of the previous word
2084 while ( (p
> p0
) && !IsWordChar(*p
) )
2087 // now find the beginning of this word
2088 while ( (p
> p0
) && IsWordChar(*p
) )
2091 // we might have gone too far
2092 if ( !IsWordChar(*p
) )
2095 return (m_curPos
- m_curCol
) + p
- p0
;
2098 wxTextPos
wxTextCtrl::GetWordEnd() const
2100 if ( m_curPos
== -1 )
2103 wxString line
= GetLineText(m_curRow
);
2104 if ( (size_t)m_curCol
== line
.length() )
2106 // if we're on the last position in the line, go to the next one - if
2108 wxTextPos pos
= m_curPos
;
2109 if ( pos
< GetLastPosition() )
2115 // it shouldn't be possible to learn where the word ends in the password
2118 return GetLastPosition();
2120 // start at the current position
2121 const wxChar
*p0
= line
.c_str();
2122 const wxChar
*p
= p0
+ m_curCol
;
2124 // find the start of the next word
2125 while ( *p
&& !IsWordChar(*p
) )
2128 // now find the end of it
2129 while ( *p
&& IsWordChar(*p
) )
2132 // and find the start of the next word
2133 while ( *p
&& !IsWordChar(*p
) )
2136 return (m_curPos
- m_curCol
) + p
- p0
;
2139 // ----------------------------------------------------------------------------
2141 // ----------------------------------------------------------------------------
2143 void wxTextCtrl::Copy()
2146 if ( HasSelection() )
2148 wxClipboardLocker clipLock
;
2150 // wxTextFile::Translate() is needed to transform all '\n' into "\r\n"
2151 wxString text
= wxTextFile::Translate(GetTextToShow(GetSelectionText()));
2152 wxTextDataObject
*data
= new wxTextDataObject(text
);
2153 wxTheClipboard
->SetData(data
);
2155 #endif // wxUSE_CLIPBOARD
2158 void wxTextCtrl::Cut()
2163 bool wxTextCtrl::DoCut()
2165 if ( !HasSelection() )
2175 void wxTextCtrl::Paste()
2180 bool wxTextCtrl::DoPaste()
2183 wxClipboardLocker clipLock
;
2185 wxTextDataObject data
;
2186 if ( wxTheClipboard
->IsSupported(data
.GetFormat())
2187 && wxTheClipboard
->GetData(data
) )
2189 // reverse transformation: '\r\n\" -> '\n'
2190 wxString text
= wxTextFile::Translate(data
.GetText(),
2191 wxTextFileType_Unix
);
2192 if ( !text
.empty() )
2199 #endif // wxUSE_CLIPBOARD
2204 // ----------------------------------------------------------------------------
2206 // ----------------------------------------------------------------------------
2208 wxTextCtrlInsertCommand
*
2209 wxTextCtrlCommandProcessor::IsInsertCommand(wxCommand
*command
)
2211 return (wxTextCtrlInsertCommand
*)
2212 (command
&& (command
->GetName() == wxTEXT_COMMAND_INSERT
)
2216 void wxTextCtrlCommandProcessor::Store(wxCommand
*command
)
2218 wxTextCtrlInsertCommand
*cmdIns
= IsInsertCommand(command
);
2221 if ( IsCompressing() )
2223 wxTextCtrlInsertCommand
*
2224 cmdInsLast
= IsInsertCommand(GetCurrentCommand());
2226 // it is possible that we don't have any last command at all if,
2227 // for example, it was undone since the last Store(), so deal with
2231 cmdInsLast
->Append(cmdIns
);
2235 // don't need to call the base class version
2240 // append the following insert commands to this one
2241 m_compressInserts
= true;
2243 // let the base class version will do the job normally
2245 else // not an insert command
2247 // stop compressing insert commands - this won't work with the last
2248 // command not being an insert one anyhow
2251 // let the base class version will do the job normally
2254 wxCommandProcessor::Store(command
);
2257 void wxTextCtrlInsertCommand::Append(wxTextCtrlInsertCommand
*other
)
2259 m_text
+= other
->m_text
;
2262 bool wxTextCtrlInsertCommand::CanUndo() const
2264 return m_from
!= -1;
2267 bool wxTextCtrlInsertCommand::Do(wxTextCtrl
*text
)
2269 // the text is going to be inserted at the current position, remember where
2271 m_from
= text
->GetInsertionPoint();
2273 // and now do insert it
2274 text
->WriteText(m_text
);
2279 bool wxTextCtrlInsertCommand::Undo(wxTextCtrl
*text
)
2281 wxCHECK_MSG( CanUndo(), false, wxT("impossible to undo insert cmd") );
2283 // remove the text from where we inserted it
2284 text
->Remove(m_from
, m_from
+ m_text
.length());
2289 bool wxTextCtrlRemoveCommand::CanUndo() const
2291 // if we were executed, we should have the text we removed
2292 return !m_textDeleted
.empty();
2295 bool wxTextCtrlRemoveCommand::Do(wxTextCtrl
*text
)
2297 text
->SetSelection(m_from
, m_to
);
2298 m_textDeleted
= text
->GetSelectionText();
2299 text
->RemoveSelection();
2304 bool wxTextCtrlRemoveCommand::Undo(wxTextCtrl
*text
)
2306 // it is possible that the text was deleted and that we can't restore text
2307 // at the same position we removed it any more
2308 wxTextPos posLast
= text
->GetLastPosition();
2309 text
->SetInsertionPoint(m_from
> posLast
? posLast
: m_from
);
2310 text
->WriteText(m_textDeleted
);
2315 void wxTextCtrl::Undo()
2317 // the caller must check it
2318 wxASSERT_MSG( CanUndo(), wxT("can't call Undo() if !CanUndo()") );
2320 m_cmdProcessor
->Undo();
2323 void wxTextCtrl::Redo()
2325 // the caller must check it
2326 wxASSERT_MSG( CanRedo(), wxT("can't call Undo() if !CanUndo()") );
2328 m_cmdProcessor
->Redo();
2331 bool wxTextCtrl::CanUndo() const
2333 return IsEditable() && m_cmdProcessor
->CanUndo();
2336 bool wxTextCtrl::CanRedo() const
2338 return IsEditable() && m_cmdProcessor
->CanRedo();
2341 // ----------------------------------------------------------------------------
2343 // ----------------------------------------------------------------------------
2345 wxSize
wxTextCtrl::DoGetBestClientSize() const
2347 // when we're called for the very first time from Create() we must
2348 // calculate the font metrics here because we can't do it before calling
2349 // Create() (there is no window yet and wxGTK crashes) but we need them
2351 if ( m_heightLine
== -1 )
2353 wxConstCast(this, wxTextCtrl
)->RecalcFontMetrics();
2357 GetTextExtent(GetTextToShow(GetLineText(0)), &w
, &h
);
2359 int wChar
= GetAverageWidth(),
2360 hChar
= GetLineHeight();
2362 int widthMin
= wxMax(10*wChar
, 100);
2368 if ( !IsSingleLine() )
2370 // let the control have a reasonable number of lines
2371 int lines
= GetNumberOfLines();
2374 else if ( lines
> 10 )
2381 rectText
.height
= h
;
2382 wxRect rectTotal
= GetRenderer()->GetTextTotalArea(this, rectText
);
2383 return wxSize(rectTotal
.width
, rectTotal
.height
);
2386 void wxTextCtrl::UpdateTextRect()
2388 wxRect
rectTotal(GetClientSize());
2389 wxCoord
*extraSpace
= WrapLines() ? &WData().m_widthMark
: NULL
;
2390 m_rectText
= GetRenderer()->GetTextClientArea(this, rectTotal
, extraSpace
);
2392 // code elsewhere is confused by negative rect size
2393 if ( m_rectText
.width
<= 0 )
2394 m_rectText
.width
= 1;
2395 if ( m_rectText
.height
<= 0 )
2396 m_rectText
.height
= 1;
2398 if ( !IsSingleLine() )
2400 // invalidate it so that GetRealTextArea() will recalc it
2401 MData().m_rectTextReal
.width
= 0;
2403 // only scroll this rect when the window is scrolled: note that we have
2404 // to scroll not only the text but the line wrap marks too if we show
2406 wxRect rectText
= GetRealTextArea();
2407 if ( extraSpace
&& *extraSpace
)
2409 rectText
.width
+= *extraSpace
;
2411 SetTargetRect(rectText
);
2413 // relayout all lines
2416 WData().m_rowFirstInvalid
= 0;
2418 // increase timestamp: this means that the lines which had been
2419 // laid out before will be relaid out the next time LayoutLines()
2420 // is called because their timestamp will be smaller than the
2422 WData().m_timestamp
++;
2426 UpdateLastVisible();
2429 void wxTextCtrl::UpdateLastVisible()
2431 // this method is only used for horizontal "scrollbarless" scrolling which
2432 // is used only with single line controls
2433 if ( !IsSingleLine() )
2436 // use (efficient) HitTestLine to find the last visible character
2437 wxString text
= m_value
.Mid((size_t)SData().m_colStart
/* to the end */);
2439 switch ( HitTestLine(text
, m_rectText
.width
, &col
) )
2441 case wxTE_HT_BEYOND
:
2442 // everything is visible
2443 SData().m_ofsHorz
= 0;
2445 SData().m_colStart
= 0;
2446 SData().m_colLastVisible
= text
.length();
2448 // calculate it below
2449 SData().m_posLastVisible
= -1;
2453 case wxTE_HT_BEFORE:
2457 wxFAIL_MSG(wxT("unexpected HitTestLine() return value"));
2460 case wxTE_HT_ON_TEXT
:
2463 // the last entirely seen character is the previous one because
2464 // this one is only partly visible - unless the width of the
2465 // string is exactly the max width
2466 SData().m_posLastVisible
= GetTextWidth(text
.Truncate(col
+ 1));
2467 if ( SData().m_posLastVisible
> m_rectText
.width
)
2469 // this character is not entirely visible, take the
2474 SData().m_posLastVisible
= -1;
2476 //else: we can just see it
2478 SData().m_colLastVisible
= col
;
2483 // calculate the width of the text really shown
2484 if ( SData().m_posLastVisible
== -1 )
2486 SData().m_posLastVisible
= GetTextWidth(text
.Truncate(SData().m_colLastVisible
+ 1));
2489 // current value is relative the start of the string text which starts at
2490 // SData().m_colStart, we need an absolute offset into string
2491 SData().m_colLastVisible
+= SData().m_colStart
;
2493 wxLogTrace(wxT("text"), wxT("Last visible column/position is %d/%ld"),
2494 (int) SData().m_colLastVisible
, (long) SData().m_posLastVisible
);
2497 void wxTextCtrl::OnSize(wxSizeEvent
& event
)
2501 if ( !IsSingleLine() )
2504 // update them immediately because if we are called for the first time,
2505 // we need to create them in order for the base class version to
2506 // position the scrollbars correctly - if we don't do it now, it won't
2507 // happen at all if we don't get more size events
2511 MData().m_updateScrollbarX
=
2512 MData().m_updateScrollbarY
= true;
2518 wxCoord
wxTextCtrl::GetTotalWidth() const
2521 CalcUnscrolledPosition(m_rectText
.width
, 0, &w
, NULL
);
2525 wxCoord
wxTextCtrl::GetTextWidth(const wxString
& text
) const
2528 GetTextExtent(GetTextToShow(text
), &w
, NULL
);
2532 wxRect
wxTextCtrl::GetRealTextArea() const
2534 // for single line text control it's just the same as text rect
2535 if ( IsSingleLine() )
2538 // the real text area always holds an entire number of lines, so the only
2539 // difference with the text area is a narrow strip along the bottom border
2540 wxRect rectText
= MData().m_rectTextReal
;
2541 if ( !rectText
.width
)
2544 rectText
= m_rectText
;
2546 // when we're called for the very first time, the line height might not
2547 // had been calculated yet, so do get it now
2548 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
2549 self
->RecalcFontMetrics();
2551 int hLine
= GetLineHeight();
2552 rectText
.height
= (m_rectText
.height
/ hLine
) * hLine
;
2555 self
->MData().m_rectTextReal
= rectText
;
2561 wxTextCoord
wxTextCtrl::GetRowInLine(wxTextCoord line
,
2563 wxTextCoord
*colRowStart
) const
2565 wxASSERT_MSG( WrapLines(), wxT("shouldn't be called") );
2567 const wxWrappedLineData
& lineData
= WData().m_linesData
[line
];
2569 if ( !WData().IsValidLine(line
) )
2572 // row is here counted a bit specially: 0 is the 2nd row of the line (1st
2575 rowMax
= lineData
.GetExtraRowCount();
2579 while ( (row
< rowMax
) && (col
>= lineData
.GetExtraRowStart(row
)) )
2582 // it's ok here that row is 1 greater than needed: like this, it is
2583 // counted as a normal (and not extra) row
2585 //else: only one row anyhow
2589 // +1 because we need a real row number, not the extra row one
2590 *colRowStart
= lineData
.GetRowStart(row
);
2592 // this can't happen, of course
2593 wxASSERT_MSG( *colRowStart
<= col
, wxT("GetRowInLine() is broken") );
2599 void wxTextCtrl::LayoutLine(wxTextCoord line
, wxWrappedLineData
& lineData
) const
2601 // FIXME: this uses old GetPartOfWrappedLine() which is not used anywhere
2602 // else now and has rather awkward interface for our needs here
2604 lineData
.m_rowsStart
.Empty();
2605 lineData
.m_rowsWidth
.Empty();
2607 const wxString
& text
= GetLineText(line
);
2609 size_t colRowStart
= 0;
2612 size_t lenRow
= GetPartOfWrappedLine
2614 text
.c_str() + colRowStart
,
2618 // remember the start of this row (not for the first one as
2619 // it's always 0) and its width
2621 lineData
.m_rowsStart
.Add(colRowStart
);
2622 lineData
.m_rowsWidth
.Add(widthRow
);
2624 colRowStart
+= lenRow
;
2626 while ( colRowStart
< text
.length() );
2628 // put the current timestamp on it
2629 lineData
.m_timestamp
= WData().m_timestamp
;
2632 void wxTextCtrl::LayoutLines(wxTextCoord lineLast
) const
2634 wxASSERT_MSG( WrapLines(), wxT("should only be used for line wrapping") );
2636 // if we were called, some line was dirty and if it was dirty we must have
2637 // had m_rowFirstInvalid set to something too
2638 wxTextCoord lineFirst
= WData().m_rowFirstInvalid
;
2639 wxASSERT_MSG( lineFirst
!= -1, wxT("nothing to layout?") );
2641 wxTextCoord rowFirst
, rowCur
;
2644 // start after the last known valid line
2645 const wxWrappedLineData
& lineData
= WData().m_linesData
[lineFirst
- 1];
2646 rowFirst
= lineData
.GetFirstRow() + lineData
.GetRowCount();
2648 else // no valid lines, start at row 0
2654 for ( wxTextCoord line
= lineFirst
; line
<= lineLast
; line
++ )
2656 // set the starting row for this line
2657 wxWrappedLineData
& lineData
= WData().m_linesData
[line
];
2658 lineData
.m_rowFirst
= rowCur
;
2660 // had the line been already broken into rows?
2662 // if so, compare its timestamp with the current one: if nothing has
2663 // been changed, don't relayout it
2664 if ( !lineData
.IsValid() ||
2665 (lineData
.m_timestamp
< WData().m_timestamp
) )
2667 // now do break it in rows
2668 LayoutLine(line
, lineData
);
2671 rowCur
+= lineData
.GetRowCount();
2674 // we are now valid at least up to this line, but if it is the last one we
2675 // just don't have any more invalid rows at all
2676 if ( (size_t)lineLast
== WData().m_linesData
.GetCount() -1 )
2681 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
2682 self
->WData().m_rowFirstInvalid
= lineLast
;
2684 // also refresh the line end indicators (FIXME shouldn't do it always!)
2685 self
->RefreshLineWrapMarks(rowFirst
, rowCur
);
2688 size_t wxTextCtrl::GetPartOfWrappedLine(const wxChar
* text
,
2689 wxCoord
*widthReal
) const
2691 // this function is slow, it shouldn't be called unless really needed
2692 wxASSERT_MSG( WrapLines(), wxT("shouldn't be called") );
2696 wxCoord wReal
= wxDefaultCoord
;
2697 switch ( HitTestLine(s
, m_rectText
.width
, &col
) )
2700 case wxTE_HT_BEFORE:
2704 wxFAIL_MSG(wxT("unexpected HitTestLine() return value"));
2707 case wxTE_HT_ON_TEXT
:
2710 // the last entirely seen character is the previous one because
2711 // this one is only partly visible - unless the width of the
2712 // string is exactly the max width
2713 wReal
= GetTextWidth(s
.Truncate(col
+ 1));
2714 if ( wReal
> m_rectText
.width
)
2716 // this character is not entirely visible, take the
2721 wReal
= wxDefaultCoord
;
2723 //else: we can just see it
2725 // wrap at any character or only at words boundaries?
2726 if ( !(GetWindowStyle() & wxTE_CHARWRAP
) )
2728 // find the (last) not word char before this word
2729 wxTextCoord colWordStart
;
2730 for ( colWordStart
= col
;
2731 colWordStart
&& IsWordChar(s
[(size_t)colWordStart
]);
2735 if ( colWordStart
> 0 )
2737 if ( colWordStart
!= col
)
2739 // will have to recalc the real width
2740 wReal
= wxDefaultCoord
;
2745 //else: only a single word, have to wrap it here
2750 case wxTE_HT_BEYOND
:
2754 // we return the number of characters, not the index of the last one
2755 if ( (size_t)col
< s
.length() )
2757 // but don't return more than this (empty) string has
2763 if ( wReal
== wxDefaultCoord
)
2765 // calc it if not done yet
2766 wReal
= GetTextWidth(s
.Truncate(col
));
2772 // VZ: old, horribly inefficient code which can still be used for checking
2773 // the result (in line, not word, wrap mode only) - to be removed later
2775 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
2776 wxClientDC
dc(self
);
2777 dc
.SetFont(GetFont());
2778 self
->DoPrepareDC(dc
);
2780 wxCoord widthMax
= m_rectText
.width
;
2782 // the text which we can keep in this ROW
2785 for ( wOld
= w
= 0; *text
&& (w
<= widthMax
); )
2789 dc
.GetTextExtent(str
, &w
, NULL
);
2794 // if we wrapped, the last letter was one too much
2795 if ( str
.length() > 1 )
2798 str
.erase(str
.length() - 1, 1);
2800 else // but always keep at least one letter in each row
2802 // the real width then is the last value of w and not teh one
2807 else // we didn't wrap
2812 wxASSERT( col
== str
.length() );
2816 wxASSERT( *widthReal
== wOld
);
2821 //return str.length();
2827 // OPT: this function is called a lot - would be nice to optimize it but I
2828 // don't really know how yet
2829 wxTextCtrlHitTestResult
wxTextCtrl::HitTestLine(const wxString
& line
,
2831 wxTextCoord
*colOut
) const
2833 wxTextCtrlHitTestResult res
= wxTE_HT_ON_TEXT
;
2836 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
2837 wxClientDC
dc(self
);
2838 dc
.SetFont(GetFont());
2839 self
->DoPrepareDC(dc
);
2842 dc
.GetTextExtent(line
, &width
, NULL
);
2845 // clicking beyond the end of line is equivalent to clicking at
2846 // the end of it, so return the last line column
2847 col
= line
.length();
2850 // unless the line is empty and so doesn't have any column at all -
2851 // in this case return 0, what else can we do?
2855 res
= wxTE_HT_BEYOND
;
2861 res
= wxTE_HT_BEFORE
;
2863 else // we're inside the line
2865 // now calculate the column: first, approximate it with fixed-width
2866 // value and then calculate the correct value iteratively: note that
2867 // we use the first character of the line instead of (average)
2868 // GetCharWidth(): it is common to have lines of dashes, for example,
2869 // and this should give us much better approximation in such case
2871 // OPT: maybe using (cache) m_widthAvg would be still faster? profile!
2872 dc
.GetTextExtent(line
[0], &width
, NULL
);
2879 else if ( (size_t)col
> line
.length() )
2881 col
= line
.length();
2884 // matchDir is the direction in which we should move to reach the
2885 // character containing the given position
2891 } matchDir
= Match_None
;
2894 // check that we didn't go beyond the line boundary
2900 if ( (size_t)col
> line
.length() )
2902 col
= line
.length();
2906 wxString
strBefore(line
, (size_t)col
);
2907 dc
.GetTextExtent(strBefore
, &width
, NULL
);
2910 if ( matchDir
== Match_Right
)
2912 // we were going to the right and, finally, moved beyond
2913 // the original position - stop on the previous one
2919 if ( matchDir
== Match_None
)
2921 // we just started iterating, now we know that we should
2923 matchDir
= Match_Left
;
2925 //else: we are still to the right of the target, continue
2929 // invert the logic above
2930 if ( matchDir
== Match_Left
)
2932 // with the exception that we don't need to backtrack here
2936 if ( matchDir
== Match_None
)
2939 matchDir
= Match_Right
;
2943 // this is not supposed to happen
2944 wxASSERT_MSG( matchDir
, wxT("logic error in wxTextCtrl::HitTest") );
2946 if ( matchDir
== Match_Right
)
2953 // check that we calculated it correctly
2955 if ( res
== wxTE_HT_ON_TEXT
)
2958 wxString text
= line
.Left(col
);
2959 dc
.GetTextExtent(text
, &width1
, NULL
);
2960 if ( (size_t)col
< line
.length() )
2965 dc
.GetTextExtent(text
, &width2
, NULL
);
2967 wxASSERT_MSG( (width1
<= x
) && (x
< width2
),
2968 wxT("incorrect HitTestLine() result") );
2970 else // we return last char
2972 wxASSERT_MSG( x
>= width1
, wxT("incorrect HitTestLine() result") );
2975 #endif // WXDEBUG_TEXT
2983 wxTextCtrlHitTestResult
wxTextCtrl::HitTest(const wxPoint
& pt
, long *pos
) const
2986 wxTextCtrlHitTestResult rc
= HitTest(pt
, &x
, &y
);
2987 if ( rc
!= wxTE_HT_UNKNOWN
&& pos
)
2989 *pos
= XYToPosition(x
, y
);
2995 wxTextCtrlHitTestResult
wxTextCtrl::HitTest(const wxPoint
& pos
,
2996 wxTextCoord
*colOut
,
2997 wxTextCoord
*rowOut
) const
2999 return HitTest2(pos
.y
, pos
.x
, 0, rowOut
, colOut
, NULL
, NULL
);
3002 wxTextCtrlHitTestResult
wxTextCtrl::HitTestLogical(const wxPoint
& pos
,
3003 wxTextCoord
*colOut
,
3004 wxTextCoord
*rowOut
) const
3006 return HitTest2(pos
.y
, pos
.x
, 0, rowOut
, colOut
, NULL
, NULL
, false);
3009 wxTextCtrlHitTestResult
wxTextCtrl::HitTest2(wxCoord y0
,
3012 wxTextCoord
*rowOut
,
3013 wxTextCoord
*colStart
,
3014 wxTextCoord
*colEnd
,
3015 wxTextCoord
*colRowStartOut
,
3016 bool deviceCoords
) const
3018 // is the point in the text area or to the right or below it?
3019 wxTextCtrlHitTestResult res
= wxTE_HT_ON_TEXT
;
3021 // translate the window coords x0 and y0 into the client coords in the text
3022 // area by adjusting for both the client and text area offsets (unless this
3023 // was already done)
3027 wxPoint pt
= GetClientAreaOrigin() + m_rectText
.GetPosition();
3028 CalcUnscrolledPosition(x10
- pt
.x
, y0
- pt
.y
, &x1
, &y
);
3036 // calculate the row (it is really a LINE, not a ROW)
3039 // these vars are used only for WrapLines() case
3040 wxTextCoord colRowStart
= 0;
3043 if ( colRowStartOut
)
3044 *colRowStartOut
= 0;
3046 int hLine
= GetLineHeight();
3049 // and clicking before it is the same as clicking on the first one
3052 res
= wxTE_HT_BEFORE
;
3056 wxTextCoord rowLast
= GetNumberOfLines() - 1;
3058 if ( IsSingleLine() || !WrapLines() )
3060 // in this case row calculation is simple as all lines have the
3061 // same height and so row is the same as line
3062 if ( row
> rowLast
)
3064 // clicking below the text is the same as clicking on the last
3068 res
= wxTE_HT_BELOW
;
3071 else // multline control with line wrap
3073 // use binary search to find the line containing this row
3074 const wxArrayWrappedLinesData
& linesData
= WData().m_linesData
;
3076 hi
= linesData
.GetCount(),
3081 const wxWrappedLineData
& lineData
= linesData
[cur
];
3082 if ( !WData().IsValidLine(cur
) )
3084 wxTextCoord rowFirst
= lineData
.GetFirstRow();
3086 if ( row
< rowFirst
)
3092 // our row is after the first row of the cur line:
3093 // obviously, if cur is the last line, it contains this
3094 // row, otherwise we have to test that it is before the
3095 // first row of the next line
3096 bool found
= cur
== linesData
.GetCount() - 1;
3099 // if the row is beyond the end of text, adjust it to
3100 // be the last one and set res accordingly
3101 if ( (size_t)(row
- rowFirst
) >= lineData
.GetRowCount() )
3103 res
= wxTE_HT_BELOW
;
3105 row
= lineData
.GetRowCount() + rowFirst
- 1;
3108 else // not the last row
3110 const wxWrappedLineData
&
3111 lineNextData
= linesData
[cur
+ 1];
3112 if ( !WData().IsValidLine(cur
+ 1) )
3113 LayoutLines(cur
+ 1);
3114 found
= row
< lineNextData
.GetFirstRow();
3119 colRowStart
= lineData
.GetRowStart(row
- rowFirst
);
3120 rowLen
= lineData
.GetRowLength(row
- rowFirst
,
3121 GetLines()[cur
].length());
3135 if ( res
== wxTE_HT_ON_TEXT
)
3137 // now find the position in the line
3138 wxString lineText
= GetLineText(row
),
3141 if ( colRowStart
|| rowLen
)
3143 // look in this row only, not in whole line
3144 rowText
= lineText
.Mid(colRowStart
, rowLen
);
3148 // just take the whole string
3154 res
= HitTestLine(GetTextToShow(rowText
), x1
, colStart
);
3158 if ( colRowStartOut
)
3160 // give them the column offset in this ROW in pixels
3161 *colRowStartOut
= colRowStart
;
3164 // take into account that the ROW doesn't start in the
3165 // beginning of the LINE
3166 *colStart
+= colRowStart
;
3171 // the hit test result we return is for x1, so throw out
3172 // the result for x2 here
3173 int x2
= x1
+ x20
- x10
;
3174 (void)HitTestLine(GetTextToShow(rowText
), x2
, colEnd
);
3176 *colEnd
+= colRowStart
;
3180 else // before/after vertical text span
3184 // fill the column with the first/last position in the
3185 // corresponding line
3186 if ( res
== wxTE_HT_BEFORE
)
3188 else // res == wxTE_HT_BELOW
3189 *colStart
= GetLineText(GetNumberOfLines() - 1).length();
3195 // give them the row in text coords (as is)
3202 bool wxTextCtrl::GetLineAndRow(wxTextCoord row
,
3203 wxTextCoord
*lineOut
,
3204 wxTextCoord
*rowInLineOut
) const
3212 int nLines
= GetNumberOfLines();
3215 const wxArrayWrappedLinesData
& linesData
= WData().m_linesData
;
3216 for ( line
= 0; line
< nLines
; line
++ )
3218 if ( !WData().IsValidLine(line
) )
3221 if ( row
< linesData
[line
].GetNextRow() )
3223 // we found the right line
3224 rowInLine
= row
- linesData
[line
].GetFirstRow();
3230 if ( line
== nLines
)
3232 // the row is out of range
3236 else // no line wrapping, everything is easy
3238 if ( row
>= nLines
)
3247 *rowInLineOut
= rowInLine
;
3252 // ----------------------------------------------------------------------------
3254 // ----------------------------------------------------------------------------
3257 wxTextCtrl has not one but two scrolling mechanisms: one is semi-automatic
3258 scrolling in both horizontal and vertical direction implemented using
3259 wxScrollHelper and the second one is manual scrolling implemented using
3260 SData().m_ofsHorz and used by the single line controls without scroll bar.
3262 The first version (the standard one) always scrolls by fixed amount which is
3263 fine for vertical scrolling as all lines have the same height but is rather
3264 ugly for horizontal scrolling if proportional font is used. This is why we
3265 manually update and use SData().m_ofsHorz which contains the length of the string
3266 which is hidden beyond the left border. An important property of text
3267 controls using this kind of scrolling is that an entire number of characters
3268 is always shown and that parts of characters never appear on display -
3269 neither in the leftmost nor rightmost positions.
3271 Once again, for multi line controls SData().m_ofsHorz is always 0 and scrolling is
3272 done as usual for wxScrollWindow.
3275 void wxTextCtrl::ShowHorzPosition(wxCoord pos
)
3277 wxASSERT_MSG( IsSingleLine(), wxT("doesn't work for multiline") );
3279 // pos is the logical position to show
3281 // SData().m_ofsHorz is the first logical position shown
3282 if ( pos
< SData().m_ofsHorz
)
3286 HitTestLine(m_value
, pos
, &col
);
3291 wxCoord width
= m_rectText
.width
;
3294 // if we are called from the ctor, m_rectText is not initialized
3295 // yet, so do it now
3297 width
= m_rectText
.width
;
3300 // SData().m_ofsHorz + width is the last logical position shown
3301 if ( pos
> SData().m_ofsHorz
+ width
)
3305 HitTestLine(m_value
, pos
- width
, &col
);
3306 ScrollText(col
+ 1);
3311 // scroll the window horizontally so that the first visible character becomes
3312 // the one at this position
3313 void wxTextCtrl::ScrollText(wxTextCoord col
)
3315 wxASSERT_MSG( IsSingleLine(),
3316 wxT("ScrollText() is for single line controls only") );
3318 // never scroll beyond the left border
3322 // OPT: could only get the extent of the part of the string between col
3323 // and SData().m_colStart
3324 wxCoord ofsHorz
= GetTextWidth(GetLineText(0).Left(col
));
3326 if ( ofsHorz
!= SData().m_ofsHorz
)
3328 // remember the last currently used pixel
3329 int posLastVisible
= SData().m_posLastVisible
;
3330 if ( posLastVisible
== -1 )
3332 // this may happen when we're called very early, during the
3333 // controls construction
3334 UpdateLastVisible();
3336 posLastVisible
= SData().m_posLastVisible
;
3339 // NB1: to scroll to the right, offset must be negative, hence the
3340 // order of operands
3341 int dx
= SData().m_ofsHorz
- ofsHorz
;
3343 // NB2: we call Refresh() below which results in a call to
3344 // DoDraw(), so we must update SData().m_ofsHorz before calling it
3345 SData().m_ofsHorz
= ofsHorz
;
3346 SData().m_colStart
= col
;
3348 // after changing m_colStart, recalc the last visible position: we need
3349 // to recalc the last visible position beore scrolling in order to make
3350 // it appear exactly at the right edge of the text area after scrolling
3351 UpdateLastVisible();
3356 // we want to force the update of it after scrolling
3357 SData().m_colLastVisible
= -1;
3361 // scroll only the rectangle inside which there is the text
3362 wxRect rect
= m_rectText
;
3363 rect
.width
= posLastVisible
;
3365 rect
= ScrollNoRefresh(dx
, 0, &rect
);
3368 we need to manually refresh the part which ScrollWindow() doesn't
3369 refresh (with new API this means the part outside the rect returned
3370 by ScrollNoRefresh): indeed, if we had this:
3374 where '*' is text and 'o' is blank area at the end (too small to
3375 hold the next char) then after scrolling by 2 positions to the left
3380 where 'R' is the area refreshed by ScrollWindow() - but we still
3381 need to refresh the 'o' at the end as it may be now big enough to
3382 hold the new character shifted into view.
3384 when we are scrolling to the right, we need to update this rect as
3385 well because it might have contained something before but doesn't
3386 contain anything any more
3389 // we can combine both rectangles into one when scrolling to the left,
3390 // but we need two separate Refreshes() otherwise
3393 // refresh the uncovered part on the left
3394 Refresh(true, &rect
);
3396 // and now the area on the right
3397 rect
.x
= m_rectText
.x
+ posLastVisible
;
3398 rect
.width
= m_rectText
.width
- posLastVisible
;
3400 else // scrolling to the left
3402 // just extend the rect covering the uncovered area to the edge of
3404 rect
.width
+= m_rectText
.width
- posLastVisible
;
3407 Refresh(true, &rect
);
3409 // I don't know exactly why is this needed here but without it we may
3410 // scroll the window again (from the same method) before the previously
3411 // invalidated area is repainted when typing *very* quickly - and this
3412 // may lead to the display corruption
3417 void wxTextCtrl::CalcUnscrolledPosition(int x
, int y
, int *xx
, int *yy
) const
3419 if ( IsSingleLine() )
3421 // we don't use wxScrollHelper
3423 *xx
= x
+ SData().m_ofsHorz
;
3429 // let the base class do it
3430 wxScrollHelper::CalcUnscrolledPosition(x
, y
, xx
, yy
);
3434 void wxTextCtrl::CalcScrolledPosition(int x
, int y
, int *xx
, int *yy
) const
3436 if ( IsSingleLine() )
3438 // we don't use wxScrollHelper
3440 *xx
= x
- SData().m_ofsHorz
;
3446 // let the base class do it
3447 wxScrollHelper::CalcScrolledPosition(x
, y
, xx
, yy
);
3451 void wxTextCtrl::DoPrepareDC(wxDC
& dc
)
3453 // for single line controls we only have to deal with SData().m_ofsHorz and it's
3454 // useless to call base class version as they don't use normal scrolling
3455 if ( IsSingleLine() && SData().m_ofsHorz
)
3457 // adjust the DC origin if the text is shifted
3458 wxPoint pt
= dc
.GetDeviceOrigin();
3459 dc
.SetDeviceOrigin(pt
.x
- SData().m_ofsHorz
, pt
.y
);
3463 wxScrollHelper::DoPrepareDC(dc
);
3467 void wxTextCtrl::UpdateMaxWidth(wxTextCoord line
)
3471 // check if the max width changes after this line was modified
3472 wxCoord widthMaxOld
= MData().m_widthMax
,
3474 GetTextExtent(GetLineText(line
), &width
, NULL
);
3476 if ( line
== MData().m_lineLongest
)
3478 // this line was the longest one, is it still?
3479 if ( width
> MData().m_widthMax
)
3481 MData().m_widthMax
= width
;
3483 else if ( width
< MData().m_widthMax
)
3485 // we need to find the new longest line
3488 //else: its length didn't change, nothing to do
3490 else // it wasn't the longest line, but maybe it became it?
3492 // GetMaxWidth() and not MData().m_widthMax as it might be not calculated yet
3493 if ( width
> GetMaxWidth() )
3495 MData().m_widthMax
= width
;
3496 MData().m_lineLongest
= line
;
3500 MData().m_updateScrollbarX
= MData().m_widthMax
!= widthMaxOld
;
3503 void wxTextCtrl::RecalcFontMetrics()
3505 m_heightLine
= GetCharHeight();
3506 m_widthAvg
= GetCharWidth();
3509 void wxTextCtrl::RecalcMaxWidth()
3511 wxASSERT_MSG( !IsSingleLine(), wxT("only used for multiline") );
3513 MData().m_widthMax
= -1;
3514 (void)GetMaxWidth();
3517 wxCoord
wxTextCtrl::GetMaxWidth() const
3519 if ( MData().m_widthMax
== -1 )
3523 // OPT: should we remember the widths of all the lines?
3525 wxTextCtrl
*self
= wxConstCast(this, wxTextCtrl
);
3526 wxClientDC
dc(self
);
3527 dc
.SetFont(GetFont());
3529 self
->MData().m_widthMax
= 0;
3531 size_t count
= GetLineCount();
3532 for ( size_t n
= 0; n
< count
; n
++ )
3535 dc
.GetTextExtent(GetLines()[n
], &width
, NULL
);
3536 if ( width
> MData().m_widthMax
)
3538 // remember the width and the line which has it
3539 self
->MData().m_widthMax
= width
;
3540 self
->MData().m_lineLongest
= n
;
3545 wxASSERT_MSG( MData().m_widthMax
!= -1, wxT("should have at least 1 line") );
3547 return MData().m_widthMax
;
3550 void wxTextCtrl::UpdateScrollbars()
3552 wxASSERT_MSG( !IsSingleLine(), wxT("only used for multiline") );
3554 wxSize size
= GetRealTextArea().GetSize();
3556 // is our height enough to show all items?
3557 wxTextCoord nRows
= GetRowCount();
3558 wxCoord lineHeight
= GetLineHeight();
3559 bool showScrollbarY
= nRows
*lineHeight
> size
.y
;
3561 // is our width enough to show the longest line?
3562 wxCoord charWidth
, maxWidth
;
3563 bool showScrollbarX
;
3566 charWidth
= GetAverageWidth();
3567 maxWidth
= GetMaxWidth();
3568 showScrollbarX
= maxWidth
> size
.x
;
3570 else // never show the horz scrollbar
3572 // just to suppress compiler warnings about using uninit vars below
3573 charWidth
= maxWidth
= 0;
3575 showScrollbarX
= false;
3578 // calc the scrollbars ranges
3579 int scrollRangeX
= showScrollbarX
3580 ? (maxWidth
+ 2*charWidth
- 1) / charWidth
3582 int scrollRangeY
= showScrollbarY
? nRows
: 0;
3584 int scrollRangeXOld
= MData().m_scrollRangeX
,
3585 scrollRangeYOld
= MData().m_scrollRangeY
;
3586 if ( (scrollRangeY
!= scrollRangeYOld
) || (scrollRangeX
!= scrollRangeXOld
) )
3589 GetViewStart(&x
, &y
);
3592 // we want to leave the scrollbars at the same position which means
3593 // that x and y have to be adjusted as the number of positions may have
3596 // the number of positions is calculated from knowing that last
3597 // position = range - thumbSize and thumbSize == pageSize which is
3598 // equal to the window width / pixelsPerLine
3599 if ( scrollRangeXOld
)
3601 x
*= scrollRangeX
- m_rectText
.width
/ charWidth
;
3602 x
/= scrollRangeXOld
- m_rectText
.width
/ charWidth
;
3605 if ( scrollRangeYOld
)
3606 y
*= scrollRangeY
/ scrollRangeYOld
;
3609 SetScrollbars(charWidth
, lineHeight
,
3610 scrollRangeX
, scrollRangeY
,
3612 true /* no refresh */);
3614 if ( scrollRangeXOld
)
3616 const int w
= m_rectText
.width
/ charWidth
;
3617 if ( w
!= scrollRangeXOld
)
3619 x
*= scrollRangeX
- w
;
3620 x
/= scrollRangeXOld
- w
;
3625 MData().m_scrollRangeX
= scrollRangeX
;
3626 MData().m_scrollRangeY
= scrollRangeY
;
3628 // bring the current position in view
3632 MData().m_updateScrollbarX
=
3633 MData().m_updateScrollbarY
= false;
3636 void wxTextCtrl::OnInternalIdle()
3638 // notice that single line text control never has scrollbars
3639 if ( !IsSingleLine() &&
3640 (MData().m_updateScrollbarX
|| MData().m_updateScrollbarY
) )
3644 wxControl::OnInternalIdle();
3647 bool wxTextCtrl::SendAutoScrollEvents(wxScrollWinEvent
& event
) const
3649 bool forward
= event
.GetEventType() == wxEVT_SCROLLWIN_LINEDOWN
;
3650 if ( event
.GetOrientation() == wxHORIZONTAL
)
3652 return forward
? m_curCol
<= GetLineLength(m_curRow
) : m_curCol
> 0;
3656 return forward
? m_curRow
< GetNumberOfLines() : m_curRow
> 0;
3660 // ----------------------------------------------------------------------------
3662 // ----------------------------------------------------------------------------
3664 void wxTextCtrl::RefreshSelection()
3666 if ( HasSelection() )
3668 RefreshTextRange(m_selStart
, m_selEnd
);
3672 void wxTextCtrl::RefreshLineRange(wxTextCoord lineFirst
, wxTextCoord lineLast
)
3674 wxASSERT_MSG( lineFirst
<= lineLast
|| !lineLast
,
3675 wxT("no lines to refresh") );
3678 // rect.x is already 0
3679 rect
.width
= m_rectText
.width
;
3680 wxCoord h
= GetLineHeight();
3682 wxTextCoord rowFirst
;
3683 if ( lineFirst
< GetNumberOfLines() )
3685 rowFirst
= GetFirstRowOfLine(lineFirst
);
3687 else // lineFirst == GetNumberOfLines()
3689 // lineFirst may be beyond the last line only if we refresh till
3690 // the end, otherwise it's illegal
3691 wxASSERT_MSG( lineFirst
== GetNumberOfLines() && !lineLast
,
3692 wxT("invalid line range") );
3694 rowFirst
= GetRowAfterLine(lineFirst
- 1);
3697 rect
.y
= rowFirst
*h
;
3701 // refresh till this line (inclusive)
3702 wxTextCoord rowLast
= GetRowAfterLine(lineLast
);
3704 rect
.height
= (rowLast
- rowFirst
+ 1)*h
;
3706 else // lineLast == 0 means to refresh till the end
3708 // FIXME: calc it exactly
3709 rect
.height
= 32000;
3712 RefreshTextRect(rect
);
3715 void wxTextCtrl::RefreshTextRange(wxTextPos start
, wxTextPos end
)
3717 wxCHECK_RET( start
!= -1 && end
!= -1,
3718 wxT("invalid RefreshTextRange() arguments") );
3720 // accept arguments in any order as it is more conenient for the caller
3721 OrderPositions(start
, end
);
3723 // this is acceptable but we don't do anything in this case
3727 wxTextPos colStart
, lineStart
;
3728 if ( !PositionToXY(start
, &colStart
, &lineStart
) )
3730 // the range is entirely beyond the end of the text, nothing to do
3734 wxTextCoord colEnd
, lineEnd
;
3735 if ( !PositionToXY(end
, &colEnd
, &lineEnd
) )
3737 // the range spans beyond the end of text, refresh to the end
3739 lineEnd
= GetNumberOfLines() - 1;
3742 // refresh all lines one by one
3743 for ( wxTextCoord line
= lineStart
; line
<= lineEnd
; line
++ )
3745 // refresh the first line from the start of the range to the end, the
3746 // intermediate ones entirely and the last one from the beginning to
3747 // the end of the range
3748 wxTextPos posStart
= line
== lineStart
? colStart
: 0;
3750 if ( (line
!= lineEnd
) || (colEnd
== -1) )
3752 // intermediate line or the last one but we need to refresh it
3753 // until the end anyhow - do it
3754 posCount
= wxString::npos
;
3758 // refresh just the positions in between the start and the end one
3759 posCount
= colEnd
- posStart
;
3763 RefreshColRange(line
, posStart
, posCount
);
3767 void wxTextCtrl::RefreshColRange(wxTextCoord line
,
3771 wxString text
= GetLineText(line
);
3773 wxASSERT_MSG( (size_t)start
<= text
.length() && count
,
3774 wxT("invalid RefreshColRange() parameter") );
3776 RefreshPixelRange(line
,
3777 GetTextWidth(text
.Left((size_t)start
)),
3778 GetTextWidth(text
.Mid((size_t)start
, (size_t)count
)));
3781 // this method accepts "logical" coords in the sense that they are coordinates
3782 // in a logical line but it can span several rows if we wrap lines and
3783 // RefreshPixelRange() will then refresh several rows
3784 void wxTextCtrl::RefreshPixelRange(wxTextCoord line
,
3788 // we will use line text only in line wrap case
3792 text
= GetLineText(line
);
3795 // special case: width == 0 means to refresh till the end of line
3798 // refresh till the end of visible line
3799 width
= GetTotalWidth();
3803 // refresh till the end of text
3804 wxCoord widthAll
= GetTextWidth(text
);
3806 // extend width to the end of ROW
3807 width
= widthAll
- widthAll
% width
+ width
;
3810 // no need to refresh beyond the end of line
3813 //else: just refresh the specified part
3815 wxCoord h
= GetLineHeight();
3818 rect
.y
= GetFirstRowOfLine(line
)*h
;
3823 // (1) skip all rows which we don't touch at all
3824 const wxWrappedLineData
& lineData
= WData().m_linesData
[line
];
3825 if ( !WData().IsValidLine(line
) )
3828 wxCoord wLine
= 0; // suppress compiler warning about uninit var
3829 size_t rowLast
= lineData
.GetRowCount(),
3831 while ( (row
< rowLast
) &&
3832 (rect
.x
> (wLine
= lineData
.GetRowWidth(row
++))) )
3838 // (2) now refresh all lines except the last one: note that the first
3839 // line is refreshed from the given start to the end, all the next
3841 while ( (row
< rowLast
) && (width
> wLine
- rect
.x
) )
3843 rect
.width
= GetTotalWidth() - rect
.x
;
3844 RefreshTextRect(rect
);
3846 width
-= wLine
- rect
.x
;
3850 wLine
= lineData
.GetRowWidth(row
++);
3853 // (3) the code below will refresh the last line
3858 RefreshTextRect(rect
);
3861 void wxTextCtrl::RefreshTextRect(const wxRect
& rectClient
, bool textOnly
)
3864 CalcScrolledPosition(rectClient
.x
, rectClient
.y
, &rect
.x
, &rect
.y
);
3865 rect
.width
= rectClient
.width
;
3866 rect
.height
= rectClient
.height
;
3868 // account for the text area offset
3869 rect
.Offset(m_rectText
.GetPosition());
3871 // don't refresh beyond the text area unless we're refreshing the line wrap
3872 // marks in which case textOnly is false
3875 if ( rect
.GetRight() > m_rectText
.GetRight() )
3877 rect
.SetRight(m_rectText
.GetRight());
3879 if ( rect
.width
<= 0 )
3881 // nothing to refresh
3887 // check the bottom boundary always, even for the line wrap marks
3888 if ( rect
.GetBottom() > m_rectText
.GetBottom() )
3890 rect
.SetBottom(m_rectText
.GetBottom());
3892 if ( rect
.height
<= 0 )
3894 // nothing to refresh
3899 // never refresh before the visible rect
3900 if ( rect
.x
< m_rectText
.x
)
3901 rect
.x
= m_rectText
.x
;
3903 if ( rect
.y
< m_rectText
.y
)
3904 rect
.y
= m_rectText
.y
;
3906 wxLogTrace(wxT("text"), wxT("Refreshing (%d, %d)-(%d, %d)"),
3907 rect
.x
, rect
.y
, rect
.x
+ rect
.width
, rect
.y
+ rect
.height
);
3909 Refresh(true, &rect
);
3912 void wxTextCtrl::RefreshLineWrapMarks(wxTextCoord rowFirst
,
3913 wxTextCoord rowLast
)
3915 if ( WData().m_widthMark
)
3918 rectMarks
.x
= m_rectText
.width
;
3919 rectMarks
.width
= WData().m_widthMark
;
3920 rectMarks
.y
= rowFirst
*GetLineHeight();
3921 rectMarks
.height
= (rowLast
- rowFirst
)*GetLineHeight();
3923 RefreshTextRect(rectMarks
, false /* don't limit to text area */);
3927 // ----------------------------------------------------------------------------
3929 // ----------------------------------------------------------------------------
3931 void wxTextCtrl::DoDrawBorder(wxDC
& dc
, const wxRect
& rect
)
3933 m_renderer
->DrawTextBorder(dc
, GetBorder(), rect
, GetStateFlags());
3936 // ----------------------------------------------------------------------------
3937 // client area drawing
3938 // ----------------------------------------------------------------------------
3941 Several remarks about wxTextCtrl redraw logic:
3943 1. only the regions which must be updated are redrawn, this means that we
3944 never Refresh() the entire window but use RefreshPixelRange() and
3945 ScrollWindow() which only refresh small parts of it and iterate over the
3946 update region in our DoDraw()
3948 2. the text displayed on the screen is obtained using GetTextToShow(): it
3949 should be used for all drawing/measuring
3952 wxString
wxTextCtrl::GetTextToShow(const wxString
& text
) const
3956 textShown
= wxString(wxT('*'), text
.length());
3963 void wxTextCtrl::DoDrawTextInRect(wxDC
& dc
, const wxRect
& rectUpdate
)
3965 // debugging trick to see the update rect visually
3967 static int s_countUpdates
= -1;
3968 if ( s_countUpdates
!= -1 )
3970 wxWindowDC
dc(this);
3971 dc
.SetBrush(*(++s_countUpdates
% 2 ? wxRED_BRUSH
: wxGREEN_BRUSH
));
3972 dc
.SetPen(*wxTRANSPARENT_PEN
);
3973 dc
.DrawRectangle(rectUpdate
);
3975 #endif // WXDEBUG_TEXT
3977 // calculate the range lineStart..lineEnd of lines to redraw
3978 wxTextCoord lineStart
, lineEnd
;
3979 if ( IsSingleLine() )
3986 wxPoint pt
= rectUpdate
.GetPosition();
3987 (void)HitTest(pt
, NULL
, &lineStart
);
3989 pt
.y
+= rectUpdate
.height
;
3990 (void)HitTest(pt
, NULL
, &lineEnd
);
3993 // prepare for drawing
3994 wxCoord hLine
= GetLineHeight();
3996 // these vars will be used for hit testing of the current row
3997 wxCoord y
= rectUpdate
.y
;
3998 const wxCoord x1
= rectUpdate
.x
;
3999 const wxCoord x2
= rectUpdate
.x
+ rectUpdate
.width
;
4002 rectText
.height
= hLine
;
4003 wxCoord yClient
= y
- GetClientAreaOrigin().y
;
4005 // we want to always start at the top of the line, otherwise if we redraw a
4006 // rect whose top is in the middle of a line, we'd draw this line shifted
4007 yClient
-= (yClient
- m_rectText
.y
) % hLine
;
4009 if ( IsSingleLine() )
4011 rectText
.y
= yClient
;
4013 else // multiline, adjust for scrolling
4015 CalcUnscrolledPosition(0, yClient
, NULL
, &rectText
.y
);
4018 wxRenderer
*renderer
= GetRenderer();
4020 // do draw the invalidated parts of each line: note that we iterate here
4021 // over ROWs, not over LINEs
4022 for ( wxTextCoord line
= lineStart
;
4023 y
< rectUpdate
.y
+ rectUpdate
.height
;
4025 rectText
.y
+= hLine
)
4027 // calculate the update rect in text positions for this line
4028 wxTextCoord colStart
, colEnd
, colRowStart
;
4029 wxTextCtrlHitTestResult ht
= HitTest2(y
, x1
, x2
,
4030 &line
, &colStart
, &colEnd
,
4033 if ( (ht
== wxTE_HT_BEYOND
) || (ht
== wxTE_HT_BELOW
) )
4035 wxASSERT_MSG( line
<= lineEnd
, wxT("how did we get that far?") );
4037 if ( line
== lineEnd
)
4039 // we redrew everything
4043 // the update rect is beyond the end of line, no need to redraw
4044 // anything on this line - but continue with the remaining ones
4048 // for single line controls we may additionally cut off everything
4049 // which is to the right of the last visible position
4050 if ( IsSingleLine() )
4052 // don't show the columns which are scrolled out to the left
4053 if ( colStart
< SData().m_colStart
)
4054 colStart
= SData().m_colStart
;
4056 // colEnd may be less than colStart if colStart was changed by the
4058 if ( colEnd
< colStart
)
4061 // don't draw the chars beyond the rightmost one
4062 if ( SData().m_colLastVisible
== -1 )
4064 // recalculate this rightmost column
4065 UpdateLastVisible();
4068 if ( colStart
> SData().m_colLastVisible
)
4070 // don't bother redrawing something that is beyond the last
4075 if ( colEnd
> SData().m_colLastVisible
)
4077 colEnd
= SData().m_colLastVisible
;
4081 // extract the part of line we need to redraw
4082 wxString textLine
= GetTextToShow(GetLineText(line
));
4083 wxString text
= textLine
.Mid(colStart
, colEnd
- colStart
+ 1);
4085 // now deal with the selection: only do something if at least part of
4086 // the line is selected
4087 wxTextPos selStart
, selEnd
;
4088 if ( GetSelectedPartOfLine(line
, &selStart
, &selEnd
) )
4090 // and if this part is (at least partly) in the current row
4091 if ( (selStart
<= colEnd
) &&
4092 (selEnd
>= wxMax(colStart
, colRowStart
)) )
4094 // these values are relative to the start of the line while the
4095 // string passed to DrawTextLine() is only part of it, so
4096 // adjust the selection range accordingly
4097 selStart
-= colStart
;
4103 if ( (size_t)selEnd
>= text
.length() )
4104 selEnd
= text
.length();
4108 // reset selStart and selEnd to avoid passing them to
4109 // DrawTextLine() below
4115 // calculate the text coords on screen
4116 wxASSERT_MSG( colStart
>= colRowStart
, wxT("invalid string part") );
4117 wxCoord ofsStart
= GetTextWidth(
4118 textLine
.Mid(colRowStart
,
4119 colStart
- colRowStart
));
4120 rectText
.x
= m_rectText
.x
+ ofsStart
;
4121 rectText
.width
= GetTextWidth(text
);
4124 renderer
->DrawTextLine(dc
, text
, rectText
, selStart
, selEnd
,
4126 wxLogTrace(wxT("text"), wxT("Line %ld: positions %ld-%ld redrawn."),
4127 line
, colStart
, colEnd
);
4131 void wxTextCtrl::DoDrawLineWrapMarks(wxDC
& dc
, const wxRect
& rectUpdate
)
4133 wxASSERT_MSG( WrapLines() && WData().m_widthMark
,
4134 wxT("shouldn't be called at all") );
4136 wxRenderer
*renderer
= GetRenderer();
4139 rectMark
.x
= rectUpdate
.x
;
4140 rectMark
.width
= rectUpdate
.width
;
4141 wxCoord yTop
= GetClientAreaOrigin().y
;
4142 CalcUnscrolledPosition(0, rectUpdate
.y
- yTop
, NULL
, &rectMark
.y
);
4143 wxCoord hLine
= GetLineHeight();
4144 rectMark
.height
= hLine
;
4146 wxTextCoord line
, rowInLine
;
4149 CalcUnscrolledPosition(0, rectUpdate
.GetBottom() - yTop
, NULL
, &yBottom
);
4150 for ( ; rectMark
.y
< yBottom
; rectMark
.y
+= hLine
)
4152 if ( !GetLineAndRow(rectMark
.y
/ hLine
, &line
, &rowInLine
) )
4154 // we went beyond the end of text
4158 // is this row continued on the next one?
4159 if ( !WData().m_linesData
[line
].IsLastRow(rowInLine
) )
4161 renderer
->DrawLineWrapMark(dc
, rectMark
);
4166 void wxTextCtrl::DoDraw(wxControlRenderer
*renderer
)
4168 // hide the caret while we're redrawing the window and show it after we are
4170 wxCaretSuspend
cs(this);
4173 wxDC
& dc
= renderer
->GetDC();
4174 dc
.SetFont(GetFont());
4175 dc
.SetTextForeground(GetForegroundColour());
4177 // get the intersection of the update region with the text area: note that
4178 // the update region is in window coords and text area is in the client
4179 // ones, so it must be shifted before computing intersection
4180 wxRegion rgnUpdate
= GetUpdateRegion();
4182 wxRect rectTextArea
= GetRealTextArea();
4183 wxPoint pt
= GetClientAreaOrigin();
4184 wxRect rectTextAreaAdjusted
= rectTextArea
;
4185 rectTextAreaAdjusted
.x
+= pt
.x
;
4186 rectTextAreaAdjusted
.y
+= pt
.y
;
4187 rgnUpdate
.Intersect(rectTextAreaAdjusted
);
4189 // even though the drawing is already clipped to the update region, we must
4190 // explicitly clip it to the rect we will use as otherwise parts of letters
4191 // might be drawn outside of it (if even a small part of a charater is
4192 // inside, HitTest() will return its column and DrawText() can't draw only
4193 // the part of the character, of course)
4195 // FIXME: is this really a bug in wxMSW?
4196 rectTextArea
.width
--;
4198 dc
.DestroyClippingRegion();
4199 dc
.SetClippingRegion(rectTextArea
);
4201 // adjust for scrolling
4204 // and now refresh the invalidated parts of the window
4205 wxRegionIterator
iter(rgnUpdate
);
4206 for ( ; iter
.HaveRects(); iter
++ )
4208 wxRect r
= iter
.GetRect();
4210 // this is a workaround for wxGTK::wxRegion bug
4212 if ( !r
.width
|| !r
.height
)
4214 // ignore invalid rect
4219 DoDrawTextInRect(dc
, r
);
4222 // now redraw the line wrap marks (if we draw them)
4223 if ( WrapLines() && WData().m_widthMark
)
4225 // this is the rect inside which line wrap marks are drawn
4227 rectMarks
.x
= rectTextAreaAdjusted
.GetRight() + 1;
4228 rectMarks
.y
= rectTextAreaAdjusted
.y
;
4229 rectMarks
.width
= WData().m_widthMark
;
4230 rectMarks
.height
= rectTextAreaAdjusted
.height
;
4232 rgnUpdate
= GetUpdateRegion();
4233 rgnUpdate
.Intersect(rectMarks
);
4235 wxRect rectUpdate
= rgnUpdate
.GetBox();
4236 if ( rectUpdate
.width
&& rectUpdate
.height
)
4238 // the marks are outside previously set clipping region
4239 dc
.DestroyClippingRegion();
4241 DoDrawLineWrapMarks(dc
, rectUpdate
);
4245 // show caret first time only: we must show it after drawing the text or
4246 // the display can be corrupted when it's hidden
4247 if ( !m_hasCaret
&& GetCaret() && (FindFocus() == this) )
4255 // ----------------------------------------------------------------------------
4257 // ----------------------------------------------------------------------------
4259 bool wxTextCtrl::SetFont(const wxFont
& font
)
4261 if ( !wxControl::SetFont(font
) )
4264 // and refresh everything, of course
4265 InitInsertionPoint();
4268 // update geometry parameters
4270 RecalcFontMetrics();
4271 if ( !IsSingleLine() )
4277 // recreate it, in fact
4285 bool wxTextCtrl::Enable(bool enable
)
4287 if ( !wxTextCtrlBase::Enable(enable
) )
4290 if (FindFocus() == this && GetCaret() &&
4291 ((enable
&& !GetCaret()->IsVisible()) ||
4292 (!enable
&& GetCaret()->IsVisible())))
4298 void wxTextCtrl::CreateCaret()
4304 // FIXME use renderer
4305 caret
= new wxCaret(this, 1, GetLineHeight());
4309 // read only controls don't have the caret
4313 // SetCaret() will delete the old caret if any
4317 void wxTextCtrl::ShowCaret(bool show
)
4319 wxCaret
*caret
= GetCaret();
4322 // (re)position caret correctly
4323 caret
->Move(GetCaretPosition());
4325 // and show it there
4326 if ((show
&& !caret
->IsVisible()) ||
4327 (!show
&& caret
->IsVisible()))
4332 // ----------------------------------------------------------------------------
4333 // vertical scrolling (multiline only)
4334 // ----------------------------------------------------------------------------
4336 size_t wxTextCtrl::GetLinesPerPage() const
4338 if ( IsSingleLine() )
4341 return GetRealTextArea().height
/ GetLineHeight();
4344 wxTextPos
wxTextCtrl::GetPositionAbove()
4346 wxCHECK_MSG( !IsSingleLine(), INVALID_POS_VALUE
,
4347 wxT("can't move cursor vertically in a single line control") );
4349 // move the cursor up by one ROW not by one LINE: this means that
4350 // we should really use HitTest() and not just go to the same
4351 // position in the previous line
4352 wxPoint pt
= GetCaretPosition() - m_rectText
.GetPosition();
4353 if ( MData().m_xCaret
== -1 )
4355 // remember the initial cursor abscissa
4356 MData().m_xCaret
= pt
.x
;
4360 // use the remembered abscissa
4361 pt
.x
= MData().m_xCaret
;
4364 CalcUnscrolledPosition(pt
.x
, pt
.y
, &pt
.x
, &pt
.y
);
4365 pt
.y
-= GetLineHeight();
4367 wxTextCoord col
, row
;
4368 if ( HitTestLogical(pt
, &col
, &row
) == wxTE_HT_BEFORE
)
4370 // can't move further
4371 return INVALID_POS_VALUE
;
4374 return XYToPosition(col
, row
);
4377 wxTextPos
wxTextCtrl::GetPositionBelow()
4379 wxCHECK_MSG( !IsSingleLine(), INVALID_POS_VALUE
,
4380 wxT("can't move cursor vertically in a single line control") );
4382 // see comments for wxACTION_TEXT_UP
4383 wxPoint pt
= GetCaretPosition() - m_rectText
.GetPosition();
4384 if ( MData().m_xCaret
== -1 )
4386 // remember the initial cursor abscissa
4387 MData().m_xCaret
= pt
.x
;
4391 // use the remembered abscissa
4392 pt
.x
= MData().m_xCaret
;
4395 CalcUnscrolledPosition(pt
.x
, pt
.y
, &pt
.x
, &pt
.y
);
4396 pt
.y
+= GetLineHeight();
4398 wxTextCoord col
, row
;
4399 if ( HitTestLogical(pt
, &col
, &row
) == wxTE_HT_BELOW
)
4401 // can't go further down
4402 return INVALID_POS_VALUE
;
4405 // note that wxTE_HT_BEYOND is ok: it happens when we go down
4406 // from a longer line to a shorter one, for example (OTOH
4407 // wxTE_HT_BEFORE can never happen)
4408 return XYToPosition(col
, row
);
4411 // ----------------------------------------------------------------------------
4413 // ----------------------------------------------------------------------------
4415 bool wxTextCtrl::PerformAction(const wxControlAction
& actionOrig
,
4417 const wxString
& strArg
)
4419 // has the text changed as result of this action?
4420 bool textChanged
= false;
4422 // the remembered cursor abscissa for multiline text controls is usually
4423 // reset after each user action but for ones which do use it (UP and DOWN
4424 // for example) we shouldn't do it - as indicated by this flag
4425 bool rememberAbscissa
= false;
4427 // the command this action corresponds to or NULL if this action doesn't
4428 // change text at all or can't be undone
4429 wxTextCtrlCommand
*command
= NULL
;
4434 if ( actionOrig
.StartsWith(wxACTION_TEXT_PREFIX_DEL
, &action
) )
4439 else if ( actionOrig
.StartsWith(wxACTION_TEXT_PREFIX_SEL
, &action
) )
4443 else // not selection nor delete action
4445 action
= actionOrig
;
4448 // set newPos to -2 as it can't become equal to it in the assignments below
4449 // (but it can become -1)
4450 wxTextPos newPos
= INVALID_POS_VALUE
;
4452 if ( action
== wxACTION_TEXT_HOME
)
4454 newPos
= m_curPos
- m_curCol
;
4456 else if ( action
== wxACTION_TEXT_END
)
4458 newPos
= m_curPos
+ GetLineLength(m_curRow
) - m_curCol
;
4460 else if ( (action
== wxACTION_TEXT_GOTO
) ||
4461 (action
== wxACTION_TEXT_FIRST
) ||
4462 (action
== wxACTION_TEXT_LAST
) )
4464 if ( action
== wxACTION_TEXT_FIRST
)
4466 else if ( action
== wxACTION_TEXT_LAST
)
4467 numArg
= GetLastPosition();
4468 //else: numArg already contains the position
4472 else if ( action
== wxACTION_TEXT_UP
)
4474 if ( !IsSingleLine() )
4476 newPos
= GetPositionAbove();
4478 if ( newPos
!= INVALID_POS_VALUE
)
4480 // remember where the cursor original had been
4481 rememberAbscissa
= true;
4485 else if ( action
== wxACTION_TEXT_DOWN
)
4487 if ( !IsSingleLine() )
4489 newPos
= GetPositionBelow();
4491 if ( newPos
!= INVALID_POS_VALUE
)
4493 // remember where the cursor original had been
4494 rememberAbscissa
= true;
4498 else if ( action
== wxACTION_TEXT_LEFT
)
4500 newPos
= m_curPos
- 1;
4502 else if ( action
== wxACTION_TEXT_WORD_LEFT
)
4504 newPos
= GetWordStart();
4506 else if ( action
== wxACTION_TEXT_RIGHT
)
4508 newPos
= m_curPos
+ 1;
4510 else if ( action
== wxACTION_TEXT_WORD_RIGHT
)
4512 newPos
= GetWordEnd();
4514 else if ( action
== wxACTION_TEXT_INSERT
)
4516 if ( IsEditable() && !strArg
.empty() )
4518 // inserting text can be undone
4519 command
= new wxTextCtrlInsertCommand(strArg
);
4524 else if ( (action
== wxACTION_TEXT_PAGE_UP
) ||
4525 (action
== wxACTION_TEXT_PAGE_DOWN
) )
4527 if ( !IsSingleLine() )
4529 size_t count
= GetLinesPerPage();
4530 if ( count
> PAGE_OVERLAP_IN_LINES
)
4532 // pages should overlap slightly to allow the reader to keep
4533 // orientation in the text
4534 count
-= PAGE_OVERLAP_IN_LINES
;
4537 // remember where the cursor original had been
4538 rememberAbscissa
= true;
4540 bool goUp
= action
== wxACTION_TEXT_PAGE_UP
;
4541 for ( size_t line
= 0; line
< count
; line
++ )
4543 wxTextPos pos
= goUp
? GetPositionAbove() : GetPositionBelow();
4544 if ( pos
== INVALID_POS_VALUE
)
4546 // can't move further
4550 MoveInsertionPoint(pos
);
4554 // we implement the Unix scrolling model here: cursor will always
4555 // be on the first line after Page Down and on the last one after
4558 // Windows programs usually keep the cursor line offset constant
4559 // but do we really need it?
4563 // find the line such that when it is the first one, the
4564 // current position is in the last line
4566 for ( size_t line
= 0; line
< count
; line
++ )
4568 pos
= GetPositionAbove();
4569 if ( pos
== INVALID_POS_VALUE
)
4572 MoveInsertionPoint(pos
);
4575 MoveInsertionPoint(newPos
);
4577 PositionToLogicalXY(pos
, NULL
, &y
);
4579 else // scrolled down
4581 PositionToLogicalXY(newPos
, NULL
, &y
);
4584 // scroll vertically only
4585 Scroll(wxDefaultCoord
, y
);
4588 else if ( action
== wxACTION_TEXT_SEL_WORD
)
4590 SetSelection(GetWordStart(), GetWordEnd());
4592 else if ( action
== wxACTION_TEXT_ANCHOR_SEL
)
4596 else if ( action
== wxACTION_TEXT_EXTEND_SEL
)
4598 SetSelection(m_selAnchor
, numArg
);
4600 else if ( action
== wxACTION_TEXT_COPY
)
4604 else if ( action
== wxACTION_TEXT_CUT
)
4609 else if ( action
== wxACTION_TEXT_PASTE
)
4614 else if ( action
== wxACTION_TEXT_UNDO
)
4619 else if ( action
== wxACTION_TEXT_REDO
)
4626 return wxControl::PerformAction(action
, numArg
, strArg
);
4629 if ( newPos
!= INVALID_POS_VALUE
)
4631 // bring the new position into the range
4635 wxTextPos posLast
= GetLastPosition();
4636 if ( newPos
> posLast
)
4641 // if we have the selection, remove just it
4643 if ( HasSelection() )
4650 // otherwise delete everything between current position and
4652 if ( m_curPos
!= newPos
)
4657 else // nothing to delete
4659 // prevent test below from working
4660 from
= INVALID_POS_VALUE
;
4662 // and this is just to silent the compiler warning
4667 if ( from
!= INVALID_POS_VALUE
)
4669 command
= new wxTextCtrlRemoveCommand(from
, to
);
4672 else // cursor movement command
4675 DoSetInsertionPoint(newPos
);
4679 SetSelection(m_selAnchor
, m_curPos
);
4681 else // simple movement
4683 // clear the existing selection
4688 if ( !rememberAbscissa
&& !IsSingleLine() )
4690 MData().m_xCaret
= -1;
4696 // execute and remember it to be able to undo it later
4697 m_cmdProcessor
->Submit(command
);
4699 // undoable commands always change text
4702 else // no undoable command
4704 // m_cmdProcessor->StopCompressing()
4709 wxASSERT_MSG( IsEditable(), wxT("non editable control changed?") );
4711 wxCommandEvent
event(wxEVT_TEXT
, GetId());
4712 InitCommandEvent(event
);
4713 GetEventHandler()->ProcessEvent(event
);
4715 // as the text changed...
4716 m_isModified
= true;
4722 void wxTextCtrl::OnChar(wxKeyEvent
& event
)
4724 // only process the key events from "simple keys" here
4725 if ( !event
.HasModifiers() )
4727 int keycode
= event
.GetKeyCode();
4729 wxChar unicode
= event
.GetUnicodeKey();
4731 if ( keycode
== WXK_RETURN
)
4733 if ( IsSingleLine() || (GetWindowStyle() & wxTE_PROCESS_ENTER
) )
4735 wxCommandEvent
event(wxEVT_TEXT_ENTER
, GetId());
4736 InitCommandEvent(event
);
4737 event
.SetString(GetValue());
4738 GetEventHandler()->ProcessEvent(event
);
4740 else // interpret <Enter> normally: insert new line
4742 PerformAction(wxACTION_TEXT_INSERT
, -1, wxT('\n'));
4745 else if ( keycode
< 255 && isprint(keycode
) )
4747 PerformAction(wxACTION_TEXT_INSERT
, -1, (wxChar
)keycode
);
4749 // skip event.Skip() below
4753 else if (unicode
> 0)
4755 PerformAction(wxACTION_TEXT_INSERT
, -1, unicode
);
4761 #if wxDEBUG_LEVEL >= 2
4762 // Ctrl-R refreshes the control in debug mode
4763 else if ( event
.ControlDown() && event
.GetKeyCode() == 'r' )
4765 #endif // wxDEBUG_LEVEL >= 2
4771 wxInputHandler
*wxTextCtrl::GetStdInputHandler(wxInputHandler
*handlerDef
)
4773 static wxStdTextCtrlInputHandler
s_handler(handlerDef
);
4778 // ----------------------------------------------------------------------------
4779 // wxStdTextCtrlInputHandler
4780 // ----------------------------------------------------------------------------
4782 wxStdTextCtrlInputHandler::wxStdTextCtrlInputHandler(wxInputHandler
*inphand
)
4783 : wxStdInputHandler(inphand
)
4785 m_winCapture
= NULL
;
4789 wxTextPos
wxStdTextCtrlInputHandler::HitTest(const wxTextCtrl
*text
,
4792 wxTextCoord col
, row
;
4793 wxTextCtrlHitTestResult ht
= text
->HitTest(pt
, &col
, &row
);
4795 wxTextPos pos
= text
->XYToPosition(col
, row
);
4797 // if the point is after the last column we must adjust the position to be
4798 // the last position in the line (unless it is already the last)
4799 if ( (ht
== wxTE_HT_BEYOND
) && (pos
< text
->GetLastPosition()) )
4807 bool wxStdTextCtrlInputHandler::HandleKey(wxInputConsumer
*consumer
,
4808 const wxKeyEvent
& event
,
4811 // we're only interested in key presses
4815 int keycode
= event
.GetKeyCode();
4817 wxControlAction action
;
4819 bool ctrlDown
= event
.ControlDown(),
4820 shiftDown
= event
.ShiftDown();
4823 action
= wxACTION_TEXT_PREFIX_SEL
;
4826 // the only key combination with Alt we recognize is Alt-Bksp for undo, so
4827 // treat it first separately
4828 if ( event
.AltDown() )
4830 if ( keycode
== WXK_BACK
&& !ctrlDown
&& !shiftDown
)
4831 action
= wxACTION_TEXT_UNDO
;
4833 else switch ( keycode
)
4837 action
<< (ctrlDown
? wxACTION_TEXT_FIRST
4838 : wxACTION_TEXT_HOME
);
4842 action
<< (ctrlDown
? wxACTION_TEXT_LAST
4843 : wxACTION_TEXT_END
);
4848 action
<< wxACTION_TEXT_UP
;
4853 action
<< wxACTION_TEXT_DOWN
;
4857 action
<< (ctrlDown
? wxACTION_TEXT_WORD_LEFT
4858 : wxACTION_TEXT_LEFT
);
4862 action
<< (ctrlDown
? wxACTION_TEXT_WORD_RIGHT
4863 : wxACTION_TEXT_RIGHT
);
4867 // we don't map Ctrl-PgUp/Dn to anything special - what should it
4868 // to? for now, it's the same as without control
4869 action
<< wxACTION_TEXT_PAGE_DOWN
;
4873 action
<< wxACTION_TEXT_PAGE_UP
;
4879 action
<< wxACTION_TEXT_PREFIX_DEL
<< wxACTION_TEXT_RIGHT
;
4884 action
<< wxACTION_TEXT_PREFIX_DEL
<< wxACTION_TEXT_LEFT
;
4889 // reset the action as it could be already set to one of the
4891 action
= wxACTION_NONE
;
4898 action
= wxACTION_TEXT_REDO
;
4902 action
= wxACTION_TEXT_COPY
;
4906 action
= wxACTION_TEXT_PASTE
;
4910 action
= wxACTION_TEXT_CUT
;
4914 action
= wxACTION_TEXT_UNDO
;
4920 if ( (action
!= wxACTION_NONE
) && (action
!= wxACTION_TEXT_PREFIX_SEL
) )
4922 consumer
->PerformAction(action
, -1, str
);
4927 return wxStdInputHandler::HandleKey(consumer
, event
, pressed
);
4930 bool wxStdTextCtrlInputHandler::HandleMouse(wxInputConsumer
*consumer
,
4931 const wxMouseEvent
& event
)
4933 if ( event
.LeftDown() )
4935 wxASSERT_MSG( !m_winCapture
, wxT("left button going down twice?") );
4937 wxTextCtrl
*text
= wxStaticCast(consumer
->GetInputWindow(), wxTextCtrl
);
4939 m_winCapture
= text
;
4940 m_winCapture
->CaptureMouse();
4944 wxTextPos pos
= HitTest(text
, event
.GetPosition());
4947 text
->PerformAction(wxACTION_TEXT_ANCHOR_SEL
, pos
);
4950 else if ( event
.LeftDClick() )
4952 // select the word the cursor is on
4953 consumer
->PerformAction(wxACTION_TEXT_SEL_WORD
);
4955 else if ( event
.LeftUp() )
4959 m_winCapture
->ShowCaret();
4961 m_winCapture
->ReleaseMouse();
4962 m_winCapture
= NULL
;
4966 return wxStdInputHandler::HandleMouse(consumer
, event
);
4969 bool wxStdTextCtrlInputHandler::HandleMouseMove(wxInputConsumer
*consumer
,
4970 const wxMouseEvent
& event
)
4975 wxTextCtrl
*text
= wxStaticCast(m_winCapture
, wxTextCtrl
);
4976 wxTextPos pos
= HitTest(text
, event
.GetPosition());
4979 text
->PerformAction(wxACTION_TEXT_EXTEND_SEL
, pos
);
4983 return wxStdInputHandler::HandleMouseMove(consumer
, event
);
4987 wxStdTextCtrlInputHandler::HandleFocus(wxInputConsumer
*consumer
,
4988 const wxFocusEvent
& event
)
4990 wxTextCtrl
*text
= wxStaticCast(consumer
->GetInputWindow(), wxTextCtrl
);
4992 // the selection appearance changes depending on whether we have the focus
4993 text
->RefreshSelection();
4995 if (event
.GetEventType() == wxEVT_SET_FOCUS
)
4997 if (text
->GetCaret() && !text
->GetCaret()->IsVisible())
5002 if (text
->GetCaret() && text
->GetCaret()->IsVisible())
5006 // never refresh entirely
5010 #endif // wxUSE_TEXTCTRL