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