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