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