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