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