PageRenderTime 62ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/ExtLibs/wxWidgets/src/univ/textctrl.cpp

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