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