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