/ExtLibs/wxWidgets/src/univ/textctrl.cpp
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
Large files files are truncated, but you can click here to view the full file
- /////////////////////////////////////////////////////////////////////////////
- // Name: src/univ/textctrl.cpp
- // Purpose: wxTextCtrl
- // Author: Vadim Zeitlin
- // Modified by:
- // Created: 15.09.00
- // Copyright: (c) 2000 SciTech Software, Inc. (www.scitechsoft.com)
- // Licence: wxWindows licence
- /////////////////////////////////////////////////////////////////////////////
- /*
- TODO
- + 1. update vert scrollbar when any line length changes for WrapLines()
- + 2. cursor movement ("Hello,^" -> "^verse!" on Arrow Down)?
- -> maybe save the x position and use it instead of current in handling
- DOWN/UP actions (this would make up/down always return the cursor to
- the same location)?
- 3. split file into chunks
- +? 4. rewrite Replace() refresh logic to deal with wrapping lines
- +? 5. cache info found by GetPartOfWrappedLine() - performance must be horrible
- with lots of text
- 6. backspace refreshes too much (until end of line)
- */
- /*
- Optimisation hints from PureQuantify:
- +1. wxStringTokenize is the slowest part of Replace
- 2. GetDC/ReleaseDC are very slow, avoid calling them several times
- +3. GetCharHeight() should be cached too
- 4. wxClientDC construction/destruction in HitTestLine is horribly expensive
- For line wrapping controls HitTest2 takes 50% of program time. The results
- of GetRowsPerLine and GetPartOfWrappedLine *MUST* be cached.
- Search for "OPT!" for things which must be optimized.
- */
- /*
- Some terminology:
- Everywhere in this file LINE refers to a logical line of text, and ROW to a
- physical line of text on the display. They are the same unless WrapLines()
- is true in which case a single LINE may correspond to multiple ROWs.
- A text position is an unsigned int (which for reasons of compatibility is
- still a long as wxTextPos) from 0 to GetLastPosition() inclusive. The positions
- correspond to the gaps between the letters so the position 0 is just
- before the first character and the last position is the one beyond the last
- character. For an empty text control GetLastPosition() returns 0.
- Lines and columns returned/accepted by XYToPosition() and PositionToXY()
- start from 0. The y coordinate is a LINE, not a ROW. Columns correspond to
- the characters, the first column of a line is the first character in it,
- the last one is length(line text). For compatibility, again, lines and
- columns are also longs.
- When translating lines/column coordinates to/from positions, the line and
- column give the character after the given position. Thus, GetLastPosition()
- doesn't have any corresponding column.
- An example of positions and lines/columns for a control without wrapping
- containing the text "Hello, Universe!\nGoodbye"
- 1 1 1 1 1 1 1
- pos: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
- H e l l o , U n i v e r s e ! line 0
- col: 0 1 2 3 4 5 6 7 8 9 1 1 1 1 1 1
- 0 1 2 3 4 5
- pos: 1 1 1 2 2 2 2 2
- 7 8 9 0 1 2 3 4
- G o o d b y e line 1
- col: 0 1 2 3 4 5 6
- The same example for a control with line wrap assuming "Universe" is too
- long to fit on the same line with "Hello,":
- pos: 0 1 2 3 4 5
- H e l l o , line 0 (row 0)
- col: 0 1 2 3 4 5
- 1 1 1 1 1 1 1
- pos: 6 7 8 9 0 1 2 3 4 5 6
- U n i v e r s e ! line 0 (row 1)
- col: 6 7 8 9 1 1 1 1 1 1
- 0 1 2 3 4 5
- (line 1 == row 2 same as above)
- Note that there is still the same number of columns and positions and that
- there is no (logical) position at the end of the first ROW. This position
- is identified with the preceding one (which is not how Windows does it: it
- identifies it with the next one, i.e. the first position of the next line,
- but much more logical IMHO).
- */
- /*
- Search for "OPT" for possible optimizations
- A possible global optimization would be to always store the coords in the
- text in triplets (pos, col, line) and update them simultaneously instead of
- recalculating col and line from pos each time it is needed. Currently we
- only do it for the current position but we might also do it for the
- selection start and end.
- */
- // ============================================================================
- // declarations
- // ============================================================================
- // ----------------------------------------------------------------------------
- // headers
- // ----------------------------------------------------------------------------
- #include "wx/wxprec.h"
- #ifdef __BORLANDC__
- #pragma hdrstop
- #endif
- #if wxUSE_TEXTCTRL
- #include "wx/textctrl.h"
- #ifndef WX_PRECOMP
- #include "wx/log.h"
- #include "wx/dcclient.h"
- #include "wx/validate.h"
- #include "wx/dataobj.h"
- #endif
- #include <ctype.h>
- #include "wx/clipbrd.h"
- #include "wx/textfile.h"
- #include "wx/caret.h"
- #include "wx/univ/inphand.h"
- #include "wx/univ/renderer.h"
- #include "wx/univ/colschem.h"
- #include "wx/univ/theme.h"
- #include "wx/cmdproc.h"
- #if wxDEBUG_LEVEL >= 2
- // turn extra wxTextCtrl-specific debugging on/off
- #define WXDEBUG_TEXT
- // turn wxTextCtrl::Replace() debugging on (slows down code a *lot*!)
- #define WXDEBUG_TEXT_REPLACE
- #endif // wxDEBUG_LEVEL >= 2
- // wxStringTokenize only needed for debug checks
- #ifdef WXDEBUG_TEXT_REPLACE
- #include "wx/tokenzr.h"
- #endif // WXDEBUG_TEXT_REPLACE
- // ----------------------------------------------------------------------------
- // wxStdTextCtrlInputHandler: this control handles only the mouse/kbd actions
- // common to Win32 and GTK, platform-specific things are implemented elsewhere
- // ----------------------------------------------------------------------------
- class WXDLLEXPORT wxStdTextCtrlInputHandler : public wxStdInputHandler
- {
- public:
- wxStdTextCtrlInputHandler(wxInputHandler *inphand);
- virtual bool HandleKey(wxInputConsumer *consumer,
- const wxKeyEvent& event,
- bool pressed);
- virtual bool HandleMouse(wxInputConsumer *consumer,
- const wxMouseEvent& event);
- virtual bool HandleMouseMove(wxInputConsumer *consumer,
- const wxMouseEvent& event);
- virtual bool HandleFocus(wxInputConsumer *consumer, const wxFocusEvent& event);
- protected:
- // get the position of the mouse click
- static wxTextPos HitTest(const wxTextCtrl *text, const wxPoint& pos);
- // capture data
- wxTextCtrl *m_winCapture;
- };
- // ----------------------------------------------------------------------------
- // private functions
- // ----------------------------------------------------------------------------
- // exchange two positions so that from is always less than or equal to to
- static inline void OrderPositions(wxTextPos& from, wxTextPos& to)
- {
- if ( from > to )
- {
- wxTextPos tmp = from;
- from = to;
- to = tmp;
- }
- }
- // ----------------------------------------------------------------------------
- // constants
- // ----------------------------------------------------------------------------
- // names of text ctrl commands
- #define wxTEXT_COMMAND_INSERT wxT("insert")
- #define wxTEXT_COMMAND_REMOVE wxT("remove")
- // the value which is never used for text position, even not -1 which is
- // sometimes used for some special meaning
- static const wxTextPos INVALID_POS_VALUE = wxInvalidTextCoord;
- // overlap between pages (when using PageUp/Dn) in lines
- static const size_t PAGE_OVERLAP_IN_LINES = 1;
- // ----------------------------------------------------------------------------
- // private data of wxTextCtrl
- // ----------------------------------------------------------------------------
- // the data only used by single line text controls
- struct wxTextSingleLineData
- {
- // the position of the first visible pixel and the first visible column
- wxCoord m_ofsHorz;
- wxTextCoord m_colStart;
- // and the last ones (m_posLastVisible is the width but m_colLastVisible
- // is an absolute value)
- wxCoord m_posLastVisible;
- wxTextCoord m_colLastVisible;
- // def ctor
- wxTextSingleLineData()
- {
- m_colStart = 0;
- m_ofsHorz = 0;
- m_colLastVisible = -1;
- m_posLastVisible = -1;
- }
- };
- // the data only used by multi line text controls
- struct wxTextMultiLineData
- {
- // the lines of text
- wxArrayString m_lines;
- // the current ranges of the scrollbars
- int m_scrollRangeX,
- m_scrollRangeY;
- // should we adjust the horz/vert scrollbar?
- bool m_updateScrollbarX,
- m_updateScrollbarY;
- // the max line length in pixels
- wxCoord m_widthMax;
- // the index of the line which has the length of m_widthMax
- wxTextCoord m_lineLongest;
- // the rect in which text appears: it is even less than m_rectText because
- // only the last _complete_ line is shown, hence there is an unoccupied
- // horizontal band at the bottom of it
- wxRect m_rectTextReal;
- // the x-coordinate of the caret before we started moving it vertically:
- // this is used to ensure that moving the caret up and then down will
- // return it to the same position as if we always round it in one direction
- // we would shift it in that direction
- //
- // when m_xCaret == -1, we don't have any remembered position
- wxCoord m_xCaret;
- // the def ctor
- wxTextMultiLineData()
- {
- m_scrollRangeX =
- m_scrollRangeY = 0;
- m_updateScrollbarX =
- m_updateScrollbarY = false;
- m_widthMax = -1;
- m_lineLongest = 0;
- m_xCaret = -1;
- }
- };
- // the data only used by multi line text controls in line wrap mode
- class wxWrappedLineData
- {
- // these functions set all our values, so give them access to them
- friend void wxTextCtrl::LayoutLine(wxTextCoord line,
- wxWrappedLineData& lineData) const;
- friend void wxTextCtrl::LayoutLines(wxTextCoord) const;
- public:
- // def ctor
- wxWrappedLineData()
- {
- m_rowFirst = -1;
- }
- // get the start of any row (remember that accessing m_rowsStart doesn't work
- // for the first one)
- wxTextCoord GetRowStart(wxTextCoord row) const
- {
- wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
- return row ? m_rowsStart[row - 1] : 0;
- }
- // get the length of the row (using the total line length which we don't
- // have here but need to calculate the length of the last row, so it must
- // be given to us)
- wxTextCoord GetRowLength(wxTextCoord row, wxTextCoord lenLine) const
- {
- wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
- // note that m_rowsStart[row] is the same as GetRowStart(row + 1) (but
- // slightly more efficient) and lenLine is the same as the start of the
- // first row of the next line
- return ((size_t)row == m_rowsStart.GetCount() ? lenLine : m_rowsStart[row])
- - GetRowStart(row);
- }
- // return the width of the row in pixels
- wxCoord GetRowWidth(wxTextCoord row) const
- {
- wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
- return m_rowsWidth[row];
- }
- // return the number of rows
- size_t GetRowCount() const
- {
- wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
- return m_rowsStart.GetCount() + 1;
- }
- // return the number of additional (i.e. after the first one) rows
- size_t GetExtraRowCount() const
- {
- wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
- return m_rowsStart.GetCount();
- }
- // return the first row of this line
- wxTextCoord GetFirstRow() const
- {
- wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
- return m_rowFirst;
- }
- // return the first row of the next line
- wxTextCoord GetNextRow() const
- {
- wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
- return m_rowFirst + m_rowsStart.GetCount() + 1;
- }
- // this just provides direct access to m_rowsStart aerray for efficiency
- wxTextCoord GetExtraRowStart(wxTextCoord row) const
- {
- wxASSERT_MSG( IsValid(), wxT("this line hadn't been laid out") );
- return m_rowsStart[row];
- }
- // this code is unused any longer
- #if 0
- // return true if the column is in the start of the last row (hence the row
- // it is in is not wrapped)
- bool IsLastRow(wxTextCoord colRowStart) const
- {
- return colRowStart == GetRowStart(m_rowsStart.GetCount());
- }
- // return true if the column is the last column of the row starting in
- // colRowStart
- bool IsLastColInRow(wxTextCoord colRowStart,
- wxTextCoord colRowEnd,
- wxTextCoord lenLine) const
- {
- // find the row which starts with colRowStart
- size_t nRows = GetRowCount();
- for ( size_t n = 0; n < nRows; n++ )
- {
- if ( GetRowStart(n) == colRowStart )
- {
- wxTextCoord colNextRowStart = n == nRows - 1
- ? lenLine
- : GetRowStart(n + 1);
- wxASSERT_MSG( colRowEnd < colNextRowStart,
- wxT("this column is not in this row at all!") );
- return colRowEnd == colNextRowStart - 1;
- }
- }
- // caller got it wrong
- wxFAIL_MSG( wxT("this column is not in the start of the row!") );
- return false;
- }
- #endif // 0
- // is this row the last one in its line?
- bool IsLastRow(wxTextCoord row) const
- {
- return (size_t)row == GetExtraRowCount();
- }
- // the line is valid if it had been laid out correctly: note that just
- // shiwting the line (because one of previous lines changed) doesn't make
- // it invalid
- bool IsValid() const { return !m_rowsWidth.IsEmpty(); }
- // invalidating line will relayout it
- void Invalidate() { m_rowsWidth.Empty(); }
- private:
- // for each line we remember the starting columns of all its rows after the
- // first one (which always starts at 0), i.e. if a line is wrapped twice
- // (== takes 3 rows) its m_rowsStart[0] may be 10 and m_rowsStart[1] == 15
- wxArrayLong m_rowsStart;
- // and the width of each row in pixels (this array starts from 0, as usual)
- wxArrayInt m_rowsWidth;
- // and also its starting row (0 for the first line, first lines'
- // m_rowsStart.GetCount() + 1 for the second &c): it is set to -1 initially
- // and this means that the struct hadn't yet been initialized
- wxTextCoord m_rowFirst;
- // the last modification "time"-stamp used by LayoutLines()
- size_t m_timestamp;
- };
- WX_DECLARE_OBJARRAY(wxWrappedLineData, wxArrayWrappedLinesData);
- #include "wx/arrimpl.cpp"
- WX_DEFINE_OBJARRAY(wxArrayWrappedLinesData);
- struct wxTextWrappedData : public wxTextMultiLineData
- {
- // the width of the column to the right of the text rect used for the
- // indicator mark display for the wrapped lines
- wxCoord m_widthMark;
- // the data for each line
- wxArrayWrappedLinesData m_linesData;
- // flag telling us to recalculate all starting rows starting from this line
- // (if it is -1, we don't have to recalculate anything) - it is set when
- // the number of the rows in the middle of the control changes
- wxTextCoord m_rowFirstInvalid;
- // the current timestamp used by LayoutLines()
- size_t m_timestamp;
- // invalidate starting rows of all lines (NOT rows!) after this one
- void InvalidateLinesBelow(wxTextCoord line)
- {
- if ( m_rowFirstInvalid == -1 || m_rowFirstInvalid > line )
- {
- m_rowFirstInvalid = line;
- }
- }
- // check if this line is valid: i.e. before the first invalid one
- bool IsValidLine(wxTextCoord line) const
- {
- return ((m_rowFirstInvalid == -1) || (line < m_rowFirstInvalid)) &&
- m_linesData[line].IsValid();
- }
- // def ctor
- wxTextWrappedData()
- {
- m_widthMark = 0;
- m_rowFirstInvalid = -1;
- m_timestamp = 0;
- }
- };
- // ----------------------------------------------------------------------------
- // private classes for undo/redo management
- // ----------------------------------------------------------------------------
- /*
- We use custom versions of wxWidgets command processor to implement undo/redo
- as we want to avoid storing the backpointer to wxTextCtrl in wxCommand
- itself: this is a waste of memory as all commands in the given command
- processor always have the same associated wxTextCtrl and so it makes sense
- to store the backpointer there.
- As for the rest of the implementation, it's fairly standard: we have 2
- command classes corresponding to adding and removing text.
- */
- // a command corresponding to a wxTextCtrl action
- class wxTextCtrlCommand : public wxCommand
- {
- public:
- wxTextCtrlCommand(const wxString& name) : wxCommand(true, name) { }
- // we don't use these methods as they don't make sense for us as we need a
- // wxTextCtrl to be applied
- virtual bool Do() { wxFAIL_MSG(wxT("shouldn't be called")); return false; }
- virtual bool Undo() { wxFAIL_MSG(wxT("shouldn't be called")); return false; }
- // instead, our command processor uses these methods
- virtual bool Do(wxTextCtrl *text) = 0;
- virtual bool Undo(wxTextCtrl *text) = 0;
- };
- // insert text command
- class wxTextCtrlInsertCommand : public wxTextCtrlCommand
- {
- public:
- wxTextCtrlInsertCommand(const wxString& textToInsert)
- : wxTextCtrlCommand(wxTEXT_COMMAND_INSERT), m_text(textToInsert)
- {
- m_from = -1;
- }
- // combine the 2 commands together
- void Append(wxTextCtrlInsertCommand *other);
- virtual bool CanUndo() const;
- virtual bool Do(wxTextCtrl *text);
- virtual bool Do() { return wxTextCtrlCommand::Do(); }
- virtual bool Undo() { return wxTextCtrlCommand::Undo(); }
- virtual bool Undo(wxTextCtrl *text);
- private:
- // the text we insert
- wxString m_text;
- // the position where we inserted the text
- wxTextPos m_from;
- };
- // remove text command
- class wxTextCtrlRemoveCommand : public wxTextCtrlCommand
- {
- public:
- wxTextCtrlRemoveCommand(wxTextPos from, wxTextPos to)
- : wxTextCtrlCommand(wxTEXT_COMMAND_REMOVE)
- {
- m_from = from;
- m_to = to;
- }
- virtual bool CanUndo() const;
- virtual bool Do(wxTextCtrl *text);
- virtual bool Do() { return wxTextCtrlCommand::Do(); }
- virtual bool Undo() { return wxTextCtrlCommand::Undo(); }
- virtual bool Undo(wxTextCtrl *text);
- private:
- // the range of text to delete
- wxTextPos m_from,
- m_to;
- // the text which was deleted when this command was Do()ne
- wxString m_textDeleted;
- };
- // a command processor for a wxTextCtrl
- class wxTextCtrlCommandProcessor : public wxCommandProcessor
- {
- public:
- wxTextCtrlCommandProcessor(wxTextCtrl *text)
- {
- m_compressInserts = false;
- m_text = text;
- }
- // override Store() to compress multiple wxTextCtrlInsertCommand into one
- virtual void Store(wxCommand *command);
- // stop compressing insert commands when this is called
- void StopCompressing() { m_compressInserts = false; }
- // accessors
- wxTextCtrl *GetTextCtrl() const { return m_text; }
- bool IsCompressing() const { return m_compressInserts; }
- protected:
- virtual bool DoCommand(wxCommand& cmd)
- { return ((wxTextCtrlCommand &)cmd).Do(m_text); }
- virtual bool UndoCommand(wxCommand& cmd)
- { return ((wxTextCtrlCommand &)cmd).Undo(m_text); }
- // check if this command is a wxTextCtrlInsertCommand and return it casted
- // to the right type if it is or NULL otherwise
- wxTextCtrlInsertCommand *IsInsertCommand(wxCommand *cmd);
- private:
- // the control we're associated with
- wxTextCtrl *m_text;
- // if the flag is true we're compressing subsequent insert commands into
- // one so that the entire typing could be undone in one call to Undo()
- bool m_compressInserts;
- };
- // ============================================================================
- // implementation
- // ============================================================================
- BEGIN_EVENT_TABLE(wxTextCtrl, wxTextCtrlBase)
- EVT_CHAR(wxTextCtrl::OnChar)
- EVT_SIZE(wxTextCtrl::OnSize)
- END_EVENT_TABLE()
- // ----------------------------------------------------------------------------
- // creation
- // ----------------------------------------------------------------------------
- void wxTextCtrl::Init()
- {
- m_selAnchor =
- m_selStart =
- m_selEnd = -1;
- m_isModified = false;
- m_isEditable = true;
- m_wrapLines = false;
- m_posLast =
- m_curPos =
- m_curCol =
- m_curRow = 0;
- m_heightLine =
- m_widthAvg = -1;
- // init the undo manager
- m_cmdProcessor = new wxTextCtrlCommandProcessor(this);
- // no data yet
- m_data.data = NULL;
- }
- bool wxTextCtrl::Create(wxWindow *parent,
- wxWindowID id,
- const wxString& value,
- const wxPoint& pos,
- const wxSize& size,
- long style,
- const wxValidator& validator,
- const wxString &name)
- {
- if ( style & wxTE_MULTILINE )
- {
- // for compatibility with wxMSW we create the controls with vertical
- // scrollbar always shown unless they have wxTE_RICH style (because
- // Windows text controls always has vert scrollbar but richedit one
- // doesn't)
- if ( !(style & wxTE_RICH) )
- {
- style |= wxALWAYS_SHOW_SB;
- }
- // wrapping style: wxTE_DONTWRAP == wxHSCROLL so if it's _not_ given,
- // we won't have horizontal scrollbar automatically, no need to do
- // anything
- // TODO: support wxTE_NO_VSCROLL (?)
- // create data object for normal multiline or for controls with line
- // wrap as needed
- if ( style & wxHSCROLL )
- {
- m_data.mdata = new wxTextMultiLineData;
- }
- else // we must wrap lines if we don't have horizontal scrollbar
- {
- // NB: we can't rely on HasFlag(wxHSCROLL) as the flags can change
- // later and even wxWindow::Create() itself temporarily resets
- // wxHSCROLL in wxUniv, so remember that we have a wrapped data
- // and not just a multi line data in a separate variable
- m_wrapLines = true;
- m_data.wdata = new wxTextWrappedData;
- }
- }
- else
- {
- // this doesn't make sense for single line controls
- style &= ~wxHSCROLL;
- // create data object for single line controls
- m_data.sdata = new wxTextSingleLineData;
- }
- #if wxUSE_TWO_WINDOWS
- if ((style & wxBORDER_MASK) == 0)
- style |= wxBORDER_SUNKEN;
- #endif
- if ( !wxControl::Create(parent, id, pos, size, style,
- validator, name) )
- {
- return false;
- }
- SetCursor(wxCURSOR_IBEAM);
- if ( style & wxTE_MULTILINE )
- {
- // we should always have at least one line in a multiline control
- MData().m_lines.Add(wxEmptyString);
- if ( !(style & wxHSCROLL) )
- {
- WData().m_linesData.Add(new wxWrappedLineData);
- WData().InvalidateLinesBelow(0);
- }
- // we might support it but it's quite useless and other ports don't
- // support it anyhow
- wxASSERT_MSG( !(style & wxTE_PASSWORD),
- wxT("wxTE_PASSWORD can't be used with multiline ctrls") );
- }
- RecalcFontMetrics();
- SetValue(value);
- SetInitialSize(size);
- m_isEditable = !(style & wxTE_READONLY);
- CreateCaret();
- InitInsertionPoint();
- // we can't show caret right now as we're not shown yet and so it would
- // result in garbage on the screen - we'll do it after first OnPaint()
- m_hasCaret = false;
- CreateInputHandler(wxINP_HANDLER_TEXTCTRL);
- wxSizeEvent sizeEvent(GetSize(), GetId());
- GetEventHandler()->ProcessEvent(sizeEvent);
- return true;
- }
- wxTextCtrl::~wxTextCtrl()
- {
- delete m_cmdProcessor;
- if ( m_data.data )
- {
- if ( IsSingleLine() )
- delete m_data.sdata;
- else if ( WrapLines() )
- delete m_data.wdata;
- else
- delete m_data.mdata;
- }
- }
- // ----------------------------------------------------------------------------
- // set/get the value
- // ----------------------------------------------------------------------------
- void wxTextCtrl::DoSetValue(const wxString& value, int flags)
- {
- if ( value != GetValue() )
- {
- EventsSuppressor noeventsIf(this, !(flags & SetValue_SendEvent));
- Replace(0, GetLastPosition(), value);
- if ( IsSingleLine() )
- {
- SetInsertionPoint(0);
- }
- }
- else // nothing changed
- {
- // still send event for consistency
- if ( flags & SetValue_SendEvent )
- SendTextUpdatedEvent();
- }
- }
- const wxArrayString& wxTextCtrl::GetLines() const
- {
- return MData().m_lines;
- }
- size_t wxTextCtrl::GetLineCount() const
- {
- return MData().m_lines.GetCount();
- }
- wxString wxTextCtrl::DoGetValue() const
- {
- // for multiline controls we don't always store the total value but only
- // recompute it when asked - and to invalidate it we just empty it in
- // Replace()
- if ( !IsSingleLine() && m_value.empty() )
- {
- // recalculate: note that we always do it for empty multilien control,
- // but then it's so quick that it's not important
- // the first line is special as there is no \n before it, so it's
- // outside the loop
- const wxArrayString& lines = GetLines();
- wxTextCtrl *self = wxConstCast(this, wxTextCtrl);
- self->m_value << lines[0u];
- size_t count = lines.GetCount();
- for ( size_t n = 1; n < count; n++ )
- {
- self->m_value << wxT('\n') << lines[n];
- }
- }
- return m_value;
- }
- void wxTextCtrl::Clear()
- {
- SetValue(wxEmptyString);
- }
- bool wxTextCtrl::ReplaceLine(wxTextCoord line,
- const wxString& text)
- {
- if ( WrapLines() )
- {
- // first, we have to relayout the line entirely
- //
- // OPT: we might try not to recalc the unchanged part of line
- wxWrappedLineData& lineData = WData().m_linesData[line];
- // if we had some number of rows before, use this number, otherwise
- // just make sure that the test below (rowsNew != rowsOld) will be true
- int rowsOld;
- if ( lineData.IsValid() )
- {
- rowsOld = lineData.GetExtraRowCount();
- }
- else // line wasn't laid out yet
- {
- // assume it changed entirely as we can't do anything better
- rowsOld = -1;
- }
- // now change the line
- MData().m_lines[line] = text;
- // OPT: we choose to lay it out immediately instead of delaying it
- // until it is needed because it allows us to avoid invalidating
- // lines further down if the number of rows didn't change, but
- // maybe we can improve this even further?
- LayoutLine(line, lineData);
- int rowsNew = lineData.GetExtraRowCount();
- if ( rowsNew != rowsOld )
- {
- // we have to update the line wrap marks as this is normally done
- // by LayoutLines() which we bypassed by calling LayoutLine()
- // directly
- wxTextCoord rowFirst = lineData.GetFirstRow(),
- rowCount = wxMax(rowsOld, rowsNew);
- RefreshLineWrapMarks(rowFirst, rowFirst + rowCount);
- // next, if this is not the last line, as the number of rows in it
- // changed, we need to shift all the lines below it
- if ( (size_t)line < WData().m_linesData.GetCount() )
- {
- // number of rows changed shifting all lines below
- WData().InvalidateLinesBelow(line + 1);
- }
- // the number of rows changed
- return true;
- }
- }
- else // no line wrap
- {
- MData().m_lines[line] = text;
- }
- // the number of rows didn't change
- return false;
- }
- void wxTextCtrl::RemoveLine(wxTextCoord line)
- {
- MData().m_lines.RemoveAt(line);
- if ( WrapLines() )
- {
- // we need to recalculate all the starting rows from this line, but we
- // can avoid doing it if this line was never calculated: this means
- // that we will recalculate all lines below it anyhow later if needed
- if ( WData().IsValidLine(line) )
- {
- WData().InvalidateLinesBelow(line);
- }
- WData().m_linesData.RemoveAt(line);
- }
- }
- void wxTextCtrl::InsertLine(wxTextCoord line, const wxString& text)
- {
- MData().m_lines.Insert(text, line);
- if ( WrapLines() )
- {
- WData().m_linesData.Insert(new wxWrappedLineData, line);
- // invalidate everything below it
- WData().InvalidateLinesBelow(line);
- }
- }
- void wxTextCtrl::Replace(wxTextPos from, wxTextPos to, const wxString& text)
- {
- wxTextCoord colStart, colEnd,
- lineStart, lineEnd;
- if ( (from > to) ||
- !PositionToXY(from, &colStart, &lineStart) ||
- !PositionToXY(to, &colEnd, &lineEnd) )
- {
- wxFAIL_MSG(wxT("invalid range in wxTextCtrl::Replace"));
- return;
- }
- #ifdef WXDEBUG_TEXT_REPLACE
- // a straighforward (but very inefficient) way of calculating what the new
- // value should be
- wxString textTotal = GetValue();
- wxString textTotalNew(textTotal, (size_t)from);
- textTotalNew += text;
- if ( (size_t)to < textTotal.length() )
- textTotalNew += textTotal.c_str() + (size_t)to;
- #endif // WXDEBUG_TEXT_REPLACE
- // remember the old selection and reset it immediately: we must do it
- // before calling Refresh(anything) as, at least under GTK, this leads to
- // an _immediate_ repaint (under MSW it is delayed) and hence parts of
- // text would be redrawn as selected if we didn't reset the selection
- int selStartOld = m_selStart,
- selEndOld = m_selEnd;
- m_selStart =
- m_selEnd = -1;
- if ( IsSingleLine() )
- {
- // replace the part of the text with the new value
- wxString valueNew(m_value, (size_t)from);
- // remember it for later use
- wxCoord startNewText = GetTextWidth(valueNew);
- valueNew += text;
- if ( (size_t)to < m_value.length() )
- {
- valueNew += m_value.c_str() + (size_t)to;
- }
- // we usually refresh till the end of line except of the most common case
- // when some text is appended to the end of the string in which case we
- // refresh just it
- wxCoord widthNewText;
- if ( (size_t)from < m_value.length() )
- {
- // refresh till the end of line
- widthNewText = 0;
- }
- else // text appended, not replaced
- {
- // refresh only the new text
- widthNewText = GetTextWidth(text);
- }
- m_value = valueNew;
- // force SData().m_colLastVisible update
- SData().m_colLastVisible = -1;
- // repaint
- RefreshPixelRange(0, startNewText, widthNewText);
- }
- else // multiline
- {
- //OPT: special case for replacements inside single line?
- /*
- Join all the lines in the replacement range into one string, then
- replace a part of it with the new text and break it into lines again.
- */
- // (0) we want to know if this replacement changes the number of rows
- // as if it does we need to refresh everything below the changed
- // text (it will be shifted...) and we can avoid it if there is no
- // row relayout
- bool rowsNumberChanged = false;
- // (1) join lines
- const wxArrayString& linesOld = GetLines();
- wxString textOrig;
- wxTextCoord line;
- for ( line = lineStart; line <= lineEnd; line++ )
- {
- if ( line > lineStart )
- {
- // from the previous line
- textOrig += wxT('\n');
- }
- textOrig += linesOld[line];
- }
- // we need to append the '\n' for the last line unless there is no
- // following line
- size_t countOld = linesOld.GetCount();
- // (2) replace text in the combined string
- // (2a) leave the part before replaced area unchanged
- wxString textNew(textOrig, colStart);
- // these values will be used to refresh the changed area below
- wxCoord widthNewText,
- startNewText = GetTextWidth(textNew);
- if ( (size_t)colStart == linesOld[lineStart].length() )
- {
- // text appended, refresh just enough to show the new text
- widthNewText = GetTextWidth(text.BeforeFirst(wxT('\n')));
- }
- else // text inserted, refresh till the end of line
- {
- widthNewText = 0;
- }
- // (2b) insert new text
- textNew += text;
- // (2c) and append the end of the old text
- // adjust for index shift: to is relative to colStart, not 0
- size_t toRel = (size_t)((to - from) + colStart);
- if ( toRel < textOrig.length() )
- {
- textNew += textOrig.c_str() + toRel;
- }
- // (3) break it into lines
- wxArrayString lines;
- const wxChar *curLineStart = textNew.c_str();
- for ( const wxChar *p = textNew.c_str(); ; p++ )
- {
- // end of line/text?
- if ( !*p || *p == wxT('\n') )
- {
- lines.Add(wxString(curLineStart, p));
- if ( !*p )
- break;
- curLineStart = p + 1;
- }
- }
- #ifdef WXDEBUG_TEXT_REPLACE
- // (3a) all empty tokens should be counted as replacing with "foo" and
- // with "foo\n" should have different effects
- wxArrayString lines2 = wxStringTokenize(textNew, wxT("\n"),
- wxTOKEN_RET_EMPTY_ALL);
- if ( lines2.IsEmpty() )
- {
- lines2.Add(wxEmptyString);
- }
- wxASSERT_MSG( lines.GetCount() == lines2.GetCount(),
- wxT("Replace() broken") );
- for ( size_t n = 0; n < lines.GetCount(); n++ )
- {
- wxASSERT_MSG( lines[n] == lines2[n], wxT("Replace() broken") );
- }
- #endif // WXDEBUG_TEXT_REPLACE
- // (3b) special case: if we replace everything till the end we need to
- // keep an empty line or the lines would disappear completely
- // (this also takes care of never leaving m_lines empty)
- if ( ((size_t)lineEnd == countOld - 1) && lines.IsEmpty() )
- {
- lines.Add(wxEmptyString);
- }
- size_t nReplaceCount = lines.GetCount(),
- nReplaceLine = 0;
- // (4) merge into the array
- // (4a) replace
- for ( line = lineStart; line <= lineEnd; line++, nReplaceLine++ )
- {
- if ( nReplaceLine < nReplaceCount )
- {
- // we have the replacement line for this one
- if ( ReplaceLine(line, lines[nReplaceLine]) )
- {
- rowsNumberChanged = true;
- }
- UpdateMaxWidth(line);
- }
- else // no more replacement lines
- {
- // (4b) delete all extra lines (note that we need to delete
- // them backwards because indices shift while we do it)
- bool deletedLongestLine = false;
- for ( wxTextCoord lineDel = lineEnd; lineDel >= line; lineDel-- )
- {
- if ( lineDel == MData().m_lineLongest )
- {
- // we will need to recalc the max line width
- deletedLongestLine = true;
- }
- RemoveLine(lineDel);
- }
- if ( deletedLongestLine )
- {
- RecalcMaxWidth();
- }
- // even the line number changed
- rowsNumberChanged = true;
- // update line to exit the loop
- line = lineEnd + 1;
- }
- }
- // (4c) insert the new lines
- if ( nReplaceLine < nReplaceCount )
- {
- // even the line number changed
- rowsNumberChanged = true;
- do
- {
- InsertLine(++lineEnd, lines[nReplaceLine++]);
- UpdateMaxWidth(lineEnd);
- }
- while ( nReplaceLine < nReplaceCount );
- }
- // (5) now refresh the changed area
- // update the (cached) last position first as refresh functions use it
- m_posLast += text.length() - to + from;
- // we may optimize refresh if the number of rows didn't change - but if
- // it did we have to refresh everything below the part we chanegd as
- // well as it might have moved
- if ( !rowsNumberChanged )
- {
- // refresh the line we changed
- if ( !WrapLines() )
- {
- RefreshPixelRange(lineStart++, startNewText, widthNewText);
- }
- else
- {
- //OPT: we shouldn't refresh the unchanged part of the line in
- // this case, but instead just refresh the tail of it - the
- // trouble is that we don't know here where does this tail
- // start
- }
- // number of rows didn't change, refresh the updated rows and the
- // last one
- if ( lineStart <= lineEnd )
- RefreshLineRange(lineStart, lineEnd);
- }
- else // rows number did change
- {
- if ( !WrapLines() )
- {
- // refresh only part of the first line
- RefreshPixelRange(lineStart++, startNewText, widthNewText);
- }
- //else: we have to refresh everything as some part of the text
- // could be in the previous row before but moved to the next
- // one now (due to word wrap)
- wxTextCoord lineEnd = GetLines().GetCount() - 1;
- if ( lineStart <= lineEnd )
- RefreshLineRange(lineStart, lineEnd);
- // refresh text rect left below
- RefreshLineRange(lineEnd + 1, 0);
- // the vert scrollbar might [dis]appear
- MData().m_updateScrollbarY = true;
- }
- // must recalculate it - will do later
- m_value.clear();
- }
- #ifdef WXDEBUG_TEXT_REPLACE
- // optimized code above should give the same result as straightforward
- // computation in the beginning
- wxASSERT_MSG( GetValue() == textTotalNew, wxT("error in Replace()") );
- #endif // WXDEBUG_TEXT_REPLACE
- // update the current position: note that we always put the cursor at the
- // end of the replacement text
- DoSetInsertionPoint(from + text.length());
- // and the selection: this is complicated by the fact that selection coords
- // must be first updated to reflect change in text coords, i.e. if we had
- // selection from 17 to 19 and we just removed this range, we don't have to
- // refresh anything, so we can't just use ClearSelection() here
- if ( selStartOld != -1 )
- {
- // refresh the parst of the selection outside the changed text (which
- // we already refreshed)
- if ( selStartOld < from )
- RefreshTextRange(selStartOld, from);
- if ( to < selEndOld )
- RefreshTextRange(to, selEndOld);
- }
- // now call it to do the rest (not related to refreshing)
- ClearSelection();
- SendTextUpdatedEventIfAllowed();
- }
- void wxTextCtrl::Remove(wxTextPos from, wxTextPos to)
- {
- // Replace() only works with correctly ordered arguments, so exchange them
- // if necessary
- OrderPositions(from, to);
- Replace(from, to, wxEmptyString);
- }
- void wxTextCtrl::WriteText(const wxString& text)
- {
- // replace the selection with the new text
- RemoveSelection();
- Replace(m_curPos, m_curPos, text);
- }
- void wxTextCtrl::AppendText(const wxString& text)
- {
- SetInsertionPointEnd();
- WriteText(text);
- }
- // ----------------------------------------------------------------------------
- // current position
- // ----------------------------------------------------------------------------
- void wxTextCtrl::SetInsertionPoint(wxTextPos pos)
- {
- wxCHECK_RET( pos >= 0 && pos <= GetLastPosition(),
- wxT("insertion point position out of range") );
- // don't do anything if it didn't change
- if ( pos != m_curPos )
- {
- DoSetInsertionPoint(pos);
- }
- if ( !IsSingleLine() )
- {
- // moving cursor should reset the stored abscissa (even if the cursor
- // position didn't actually change!)
- MData().m_xCaret = -1;
- }
- ClearSelection();
- }
- void wxTextCtrl::InitInsertionPoint()
- {
- // so far always put it in the beginning
- DoSetInsertionPoint(0);
- // this will also set the selection anchor correctly
- ClearSelection();
- }
- void wxTextCtrl::MoveInsertionPoint(wxTextPos pos)
- {
- wxASSERT_MSG( pos >= 0 && pos <= GetLastPosition(),
- wxT("DoSetInsertionPoint() can only be called with valid pos") );
- m_curPos = pos;
- PositionToXY(m_curPos, &m_curCol, &m_curRow);
- }
- void wxTextCtrl::DoSetInsertionPoint(wxTextPos pos)
- {
- MoveInsertionPoint(pos);
- ShowPosition(pos);
- }
- void wxTextCtrl::SetInsertionPointEnd()
- {
- SetInsertionPoint(GetLastPosition());
- }
- wxTextPos wxTextCtrl::GetInsertionPoint() const
- {
- return m_curPos;
- }
- wxTextPos wxTextCtrl::GetLastPosition() const
- {
- wxTextPos pos;
- if ( IsSingleLine() )
- {
- pos = m_value.length();
- }
- else // multiline
- {
- #ifdef WXDEBUG_TEXT
- pos = 0;
- size_t nLineCount = GetLineCount();
- for ( size_t nLine = 0; nLine < nLineCount; nLine++ )
- {
- // +1 is because the positions at the end of this line and of the
- // start of the next one are different
- pos += GetLines()[nLine].length() + 1;
- }
- if ( pos > 0 )
- {
- // the last position is at the end of the last line, not in the
- // beginning of the next line after it
- pos--;
- }
- // more probable reason of this would be to forget to update m_posLast
- wxASSERT_MSG( pos == m_posLast, wxT("bug in GetLastPosition()") );
- #endif // WXDEBUG_TEXT
- pos = m_posLast;
- }
- return pos;
- }
- // ----------------------------------------------------------------------------
- // selection
- // ----------------------------------------------------------------------------
- void wxTextCtrl::GetSelection(wxTextPos* from, wxTextPos* to) const
- {
- if ( from )
- *from = m_selStart;
- if ( to )
- *to = m_selEnd;
- }
- wxString wxTextCtrl::GetSelectionText() const
- {
- wxString sel;
- if ( HasSelection() )
- {
- if ( IsSingleLine() )
- {
- sel = m_value.Mid(m_selStart, m_selEnd - m_selStart);
- }
- else // multiline
- {
- wxTextCoord colStart, lineStart,
- colEnd, lineEnd;
- PositionToXY(m_selStart, &colStart, &lineStart);
- PositionToXY(m_selEnd, &colEnd, &lineEnd);
- // as always, we need to check for the special case when the start
- // and end line are the same
- if ( lineEnd == lineStart )
- {
- sel = GetLines()[lineStart].Mid(colStart, colEnd - colStart);
- }
- else // sel on multiple lines
- {
- // take the end of the first line
- sel = GetLines()[lineStart].c_str() + colStart;
- sel += wxT('\n');
- // all intermediate ones
- for ( wxTextCoord line = lineStart + 1; line < lineEnd; line++ )
- {
- sel << GetLines()[line] << wxT('\n');
- }
- // and the start of the last one
- sel += GetLines()[lineEnd].Left(colEnd);
- }
- }
- }
- return sel;
- }
- void wxTextCtrl::SetSelection(wxTextPos from, wxTextPos to)
- {
- // selecting till -1 is the same as selecting to the end
- if ( to == -1 )
- {
- // and selecting (-1, -1) range is the same as selecting everything, by
- // convention
- if ( from == -1 )
- from = 0;
- to = GetLastPosition();
- }
- if ( from == -1 || to == from )
- {
- ClearSelection();
- }
- else // valid sel range
- {
- // remember the 'to' position as the current position, used to move the
- // caret there later
- wxTextPos toOrig = to;
- OrderPositions(from, to);
- wxCHECK_RET( to <= GetLastPosition(),
- wxT("invalid range in wxTextCtrl::SetSelection") );
- if ( from != m_selStart || to != m_selEnd )
- {
- // we need to use temp vars as RefreshTextRange() may call DoDraw()
- // directly and so m_selStart/End must be reset by then
- wxTextPos selStartOld = m_selStart,
- selEndOld = m_selEnd;
- m_selStart = from;
- m_selEnd = to;
- wxLogTrace(wxT("text"), wxT("Selection range is %ld-%ld"),
- m_selStart, m_selEnd);
- // refresh only the part of text which became (un)selected if
- // possible
- if ( selStartOld == m_selStart )
- {
- RefreshTextRange(selEndOld, m_selEnd);
- }
- else if ( selEndOld == m_selEnd )
- {
- RefreshTextRange(m_selStart, selStartOld);
- }
- else
- {
- // OPT: could check for other cases too but it is probably not
- // worth it as the two above are the most common ones
- if ( selStartOld != -1 )
- RefreshTextRange(selStartOld, selEndOld);
- if ( m_selStart != -1 )
- RefreshTextRange(m_selStart, m_selEnd);
- }
- // we need to fully repaint the invalidated areas of the window
- // before scrolling it (from DoSetInsertionPoint which is typically
- // called after SetSelection()), otherwise they may stay unpainted
- m_targetWindow->Update();
- }
- //else: nothing to do
- // the insertion point is put at the location where the caret was moved
- DoSetInsertionPoint(toOrig);
- }
- }
- void wxTextCtrl::ClearSelection()
- {
- if ( HasSelection() )
- {
- // we need to use temp vars as RefreshTextRange() may call DoDraw()
- // directly (see above as well)
- wxTextPos selStart = m_selStart,
- selEnd = m_selEnd;
- // no selection any more
- m_selStart =
- m_selEnd = -1;
- // refresh the old selection
- RefreshTextRange(selStart, selEnd);
- }
- // the anchor should be moved even if there was no selection previously
- m_selAnchor = m_curPos;
- }
- void wxTextCtrl::RemoveSelection()
- {
- if ( !HasSelection() )
- return;
- Remove(m_selStart, m_selEnd);
- }
- bool wxTextCtrl::GetSelectedPartOfLine(wxTextCoord line,
- wxTextPos *start, wxTextPos *end) const
- {
- if ( start )
- *start = -1;
- if ( end )
- *end = -1;
- if ( !HasSelection() )
- {
- // no selection at all, hence no selection in this line
- return false;
- }
- wxTextCoord lineStart, colStart;
- PositionToXY(m_selStart, &colStart, &lineStart);
- if ( lineStart > line )
- {
- // this line is entirely above the selection
- return false;
- }
- wxTextCoord lineEnd, colEnd;
- PositionToXY(m_selEnd, &colEnd, &lineEnd);
- if ( lineEnd < line )
- {
- // this line is entirely below the selection
- return false;
- }
- if ( line == lineStart )
- {
- if ( start )
- *start = colStart;
- if ( end )
- *end = lineEnd == lineStart ? colEnd : GetLineLength(line);
- }
- else if ( line == lineEnd )
- {
- if ( start )
- *start = lineEnd == lineStart ? colStart : 0;
- if ( end )
- *end = colEnd;
- }
- else // the line is entirely inside the selection
- {
- if ( start )
- *start = 0;
- if ( end )
- *end = GetLineLength(line);
- }
- return true;
- }
- // ----------------------------------------------------------------------------
- // flags
- // ----------------------------------------------------------------------------
- bool wxTextCtrl::IsModified() const
- {
- return m_isModified;
- }
- bool wxTextCtrl::IsEditable() const
- {
- // disabled control can never be edited
- return m_isEditable && IsEnabled();
- }
- void wxTextCtrl::MarkDirty()
- {
- m_isModified = true;
- }
- void wxTextCtrl::DiscardEdits()
- {
- m_isModified = false;
- }
- void wxTextCtrl::SetEditable(bool editable)
- {
- if ( editable != m_isEditable )
- {
- m_isEditable = editable;
- // the caret (dis)appears
- CreateCaret();
- // the appearance of the control might have changed
- Refresh();
- }
- }
- // ----------------------------------------------------------------------------
- // col/lines <-> positiā¦
Large files files are truncated, but you can click here to view the full file