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