Files
wxWidgets/src/univ/textctrl.cpp
Vadim Zeitlin 442b35b53b changed copyright to SciTech one
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@10795 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2001-07-02 19:42:27 +00:00

4902 lines
141 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: univ/textctrl.cpp
// Purpose: wxTextCtrl
// Author: Vadim Zeitlin
// Modified by:
// Created: 15.09.00
// RCS-ID: $Id$
// Copyright: (c) 2000 SciTech Software, Inc. (www.scitechsoft.com)
// Licence: wxWindows license
/////////////////////////////////////////////////////////////////////////////
/*
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) 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
// ----------------------------------------------------------------------------
#ifdef __GNUG__
#pragma implementation "univtextctrl.h"
#endif
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#if wxUSE_TEXTCTRL
#ifndef WX_PRECOMP
#include "wx/log.h"
#include "wx/dcclient.h"
#include "wx/validate.h"
#include "wx/textctrl.h"
#endif
#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"
// turn extra wxTextCtrl-specific debugging on/off
#define WXDEBUG_TEXT
// turn wxTextCtrl::Replace() debugging on (slows down code a *lot*!)
#define WXDEBUG_TEXT_REPLACE
#ifndef __WXDEBUG__
#undef WXDEBUG_TEXT
#undef WXDEBUG_TEXT_REPLACE
#endif
// wxStringTokenize only needed for debug checks
#ifdef WXDEBUG_TEXT_REPLACE
#include "wx/tokenzr.h"
#endif // WXDEBUG_TEXT_REPLACE
// ----------------------------------------------------------------------------
// 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 _T("insert")
#define wxTEXT_COMMAND_REMOVE _T("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 = -2;
// 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 WXDLLEXPORT 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 WXDLLEXPORT 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 WXDLLEXPORT 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(), _T("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(), _T("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(), _T("this line hadn't been laid out") );
return m_rowsWidth[row];
}
// return the number of rows
size_t GetRowCount() const
{
wxASSERT_MSG( IsValid(), _T("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(), _T("this line hadn't been laid out") );
return m_rowsStart.GetCount();
}
// return the first row of this line
wxTextCoord GetFirstRow() const
{
wxASSERT_MSG( IsValid(), _T("this line hadn't been laid out") );
return m_rowFirst;
}
// return the first row of the next line
wxTextCoord GetNextRow() const
{
wxASSERT_MSG( IsValid(), _T("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(), _T("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,
_T("this column is not in this row at all!") );
return colRowEnd == colNextRowStart - 1;
}
}
// caller got it wrong
wxFAIL_MSG( _T("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 WXDLLEXPORT 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 wxWindows 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(_T("shouldn't be called")); return FALSE; }
virtual bool Undo() { wxFAIL_MSG(_T("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 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 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, wxControl)
EVT_CHAR(wxTextCtrl::OnChar)
EVT_SIZE(wxTextCtrl::OnSize)
EVT_IDLE(wxTextCtrl::OnIdle)
END_EVENT_TABLE()
IMPLEMENT_DYNAMIC_CLASS(wxTextCtrl, wxControl)
// ----------------------------------------------------------------------------
// creation
// ----------------------------------------------------------------------------
void wxTextCtrl::Init()
{
m_selAnchor =
m_selStart =
m_selEnd = -1;
m_isModified = FALSE;
m_isEditable = TRUE;
m_posLast =
m_curPos =
m_curCol =
m_curRow = 0;
m_heightLine =
m_widthAvg = -1;
// init wxScrollHelper
SetWindow(this);
// 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;
}
if ( style & wxTE_WORDWRAP )
{
// wrapping words means wrapping, hence no horz scrollbar
style &= ~wxHSCROLL;
}
// 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
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 ( !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),
_T("wxTE_PASSWORD can't be used with multiline ctrls") );
}
RecalcFontMetrics();
SetValue(value);
SetBestSize(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);
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::SetValue(const wxString& value)
{
if ( IsSingleLine() && (value == GetValue()) )
{
// nothing changed
return;
}
Replace(0, GetLastPosition(), value);
if ( IsSingleLine() )
{
SetInsertionPoint(0);
}
// TODO: should we generate the event or not, finally?
}
const wxArrayString& wxTextCtrl::GetLines() const
{
return MData().m_lines;
}
size_t wxTextCtrl::GetLineCount() const
{
return MData().m_lines.GetCount();
}
wxString wxTextCtrl::GetValue() 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 << _T('\n') << lines[n];
}
}
return m_value;
}
void wxTextCtrl::Clear()
{
SetValue(_T(""));
}
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 our 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 chnage, but
// maybe we can imporve 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(_T("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 += _T('\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(_T('\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 == _T('\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, _T("\n"),
wxTOKEN_RET_EMPTY_ALL);
if ( lines2.IsEmpty() )
{
lines2.Add(wxEmptyString);
}
wxASSERT_MSG( lines.GetCount() == lines2.GetCount(),
_T("Replace() broken") );
for ( size_t n = 0; n < lines.GetCount(); n++ )
{
wxASSERT_MSG( lines[n] == lines2[n], _T("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, _T("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();
}
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, _T(""));
}
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(),
_T("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(),
_T("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, _T("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 += _T('\n');
// all intermediate ones
for ( wxTextCoord line = lineStart + 1; line < lineEnd; line++ )
{
sel << GetLines()[line] << _T('\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 && from != -1 )
{
to = GetLastPosition();
}
if ( from == -1 || to == from )
{
ClearSelection();
}
else // valid sel range
{
OrderPositions(from, to);
wxCHECK_RET( to <= GetLastPosition(),
_T("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(_T("text"), _T("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 end of selection
DoSetInsertionPoint(to);
}
}
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::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 <-> position correspondence
// ----------------------------------------------------------------------------
/*
A few remarks about this stuff:
o The numbering of the text control columns/rows starts from 0.
o Start of first line is position 0, its last position is line.length()
o Start of the next line is the last position of the previous line + 1
*/
int wxTextCtrl::GetLineLength(wxTextCoord line) const
{
if ( IsSingleLine() )
{
wxASSERT_MSG( line == 0, _T("invalid GetLineLength() parameter") );
return m_value.length();
}
else // multiline
{
wxCHECK_MSG( (size_t)line < GetLineCount(), -1,
_T("line index out of range") );
return GetLines()[line].length();
}
}
wxString wxTextCtrl::GetLineText(wxTextCoord line) const
{
if ( IsSingleLine() )
{
wxASSERT_MSG( line == 0, _T("invalid GetLineLength() parameter") );
return m_value;
}
else // multiline
{
wxCHECK_MSG( (size_t)line < GetLineCount(), _T(""),
_T("line index out of range") );
return GetLines()[line];
}
}
int wxTextCtrl::GetNumberOfLines() const
{
// there is always 1 line, even if the text is empty
return IsSingleLine() ? 1 : GetLineCount();
}
wxTextPos wxTextCtrl::XYToPosition(wxTextCoord x, wxTextCoord y) const
{
// note that this method should accept any values of x and y and return -1
// if they are out of range
if ( IsSingleLine() )
{
return x > GetLastPosition() || y > 0 ? -1 : x;
}
else // multiline
{
if ( (size_t)y >= GetLineCount() )
{
// this position is below the text
return GetLastPosition();
}
wxTextPos pos = 0;
for ( size_t nLine = 0; nLine < (size_t)y; 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;
}
// take into account also the position in line
if ( (size_t)x > GetLines()[y].length() )
{
// don't return position in the next line
x = GetLines()[y].length();
}
return pos + x;
}
}
bool wxTextCtrl::PositionToXY(wxTextPos pos,
wxTextCoord *x, wxTextCoord *y) const
{
if ( IsSingleLine() )
{
if ( (size_t)pos > m_value.length() )
return FALSE;
if ( x )
*x = pos;
if ( y )
*y = 0;
return TRUE;
}
else // multiline
{
wxTextPos posCur = 0;
size_t nLineCount = GetLineCount();
for ( size_t nLine = 0; nLine < nLineCount; nLine++ )
{
// +1 is because the start the start of the next line is one
// position after the end of this one
wxTextPos posNew = posCur + GetLines()[nLine].length() + 1;
if ( posNew > pos )
{
// we've found the line, now just calc the column
if ( x )
*x = pos - posCur;
if ( y )
*y = nLine;
#ifdef WXDEBUG_TEXT
wxASSERT_MSG( XYToPosition(pos - posCur, nLine) == pos,
_T("XYToPosition() or PositionToXY() broken") );
#endif // WXDEBUG_TEXT
return TRUE;
}
else // go further down
{
posCur = posNew;
}
}
// beyond the last line
return FALSE;
}
}
wxTextCoord wxTextCtrl::GetRowsPerLine(wxTextCoord line) const
{
// a normal line has one row
wxTextCoord numRows = 1;
if ( WrapLines() )
{
// add the number of additional rows
numRows += WData().m_linesData[line].GetExtraRowCount();
}
return numRows;
}
wxTextCoord wxTextCtrl::GetRowCount() const
{
wxTextCoord count = GetLineCount();
if (count == 0)
return 0;
if ( WrapLines() )
{
count = GetFirstRowOfLine(count - 1) +
WData().m_linesData[count - 1].GetRowCount();
}
return count;
}
wxTextCoord wxTextCtrl::GetRowAfterLine(wxTextCoord line) const
{
if ( !WrapLines() )
return line + 1;
if ( !WData().IsValidLine(line) )
{
LayoutLines(line);
}
return WData().m_linesData[line].GetNextRow();
}
wxTextCoord wxTextCtrl::GetFirstRowOfLine(wxTextCoord line) const
{
if ( !WrapLines() )
return line;
if ( !WData().IsValidLine(line) )
{
LayoutLines(line);
}
return WData().m_linesData[line].GetFirstRow();
}
bool wxTextCtrl::PositionToLogicalXY(wxTextPos pos,
wxCoord *xOut,
wxCoord *yOut) const
{
wxTextCoord col, line;
// optimization for special (but common) case when we already have the col
// and line
if ( pos == m_curPos )
{
col = m_curCol;
line = m_curRow;
}
else // must really calculate col/line from pos
{
if ( !PositionToXY(pos, &col, &line) )
return FALSE;
}
int hLine = GetLineHeight();
wxCoord x, y;
wxString textLine = GetLineText(line);
if ( IsSingleLine() || !WrapLines() )
{
x = GetTextWidth(textLine.Left(col));
y = line*hLine;
}
else // difficult case: multline control with line wrap
{
y = GetFirstRowOfLine(line);
wxTextCoord colRowStart;
y += GetRowInLine(line, col, &colRowStart);
y *= hLine;
// x is the width of the text before this position in this row
x = GetTextWidth(textLine.Mid(colRowStart, col - colRowStart));
}
if ( xOut )
*xOut = x;
if ( yOut )
*yOut = y;
return TRUE;
}
bool wxTextCtrl::PositionToDeviceXY(wxTextPos pos,
wxCoord *xOut,
wxCoord *yOut) const
{
wxCoord x, y;
if ( !PositionToLogicalXY(pos, &x, &y) )
return FALSE;
// finally translate the logical text rect coords into physical client
// coords
CalcScrolledPosition(m_rectText.x + x, m_rectText.y + y, xOut, yOut);
return TRUE;
}
wxPoint wxTextCtrl::GetCaretPosition() const
{
wxCoord xCaret, yCaret;
if ( !PositionToDeviceXY(m_curPos, &xCaret, &yCaret) )
{
wxFAIL_MSG( _T("Caret can't be beyond the text!") );
}
return wxPoint(xCaret, yCaret);
}
// pos may be -1 to show the current position
void wxTextCtrl::ShowPosition(wxTextPos pos)
{
HideCaret();
if ( IsSingleLine() )
{
ShowHorzPosition(GetTextWidth(m_value.Left(pos)));
}
else if ( MData().m_scrollRangeX || MData().m_scrollRangeY ) // multiline with scrollbars
{
int xStart, yStart;
GetViewStart(&xStart, &yStart);
if ( pos == -1 )
pos = m_curPos;
wxCoord x, y;
PositionToLogicalXY(pos, &x, &y);
wxRect rectText = GetRealTextArea();
// scroll the position vertically into view: if it is currently above
// it, make it the first one, otherwise the last one
if ( MData().m_scrollRangeY )
{
y /= GetLineHeight();
if ( y < yStart )
{
Scroll(0, y);
}
else // we are currently in or below the view area
{
// find the last row currently shown
wxTextCoord yEnd;
if ( WrapLines() )
{
// to find the last row we need to use the generic HitTest
wxTextCoord col;
// OPT this is a bit silly: we undo this in HitTest(), so
// it would be better to factor out the common
// functionality into a separate function (OTOH it
// won't probably save us that much)
wxPoint pt(0, rectText.height - 1);
pt += GetClientAreaOrigin();
pt += m_rectText.GetPosition();
HitTest(pt, &col, &yEnd);
// find the row inside the line
yEnd = GetFirstRowOfLine(yEnd) + GetRowInLine(yEnd, col);
}
else
{
// finding the last line is easy if each line has exactly
// one row
yEnd = yStart + rectText.height / GetLineHeight() - 1;
}
if ( yEnd < y )
{
// scroll down: the current item should appear at the
// bottom of the view
Scroll(0, y - (yEnd - yStart));
}
}
}
// scroll the position horizontally into view
//
// we follow what I believe to be Windows behaviour here, that is if
// the position is already entirely in the view we do nothing, but if
// we do have to scroll the window to bring it into view, we scroll it
// not just enough to show the position but slightly more so that this
// position is at 1/3 of the window width from the closest border to it
// (I'm not sure that Windows does exactly this but it looks like this)
if ( MData().m_scrollRangeX )
{
// unlike for the rows, xStart doesn't correspond to the starting
// column as they all have different widths, so we need to
// translate everything to pixels
// we want the text between x and x2 be entirely inside the view
// (i.e. the current character)
// make xStart the first visible pixel (and not position)
int wChar = GetAverageWidth();
xStart *= wChar;
if ( x < xStart )
{
// we want the position of this column be 1/3 to the right of
// the left edge
x -= rectText.width / 3;
if ( x < 0 )
x = 0;
Scroll(x / wChar, y);
}
else // maybe we're beyond the right border of the view?
{
wxTextCoord col, row;
if ( PositionToXY(pos, &col, &row) )
{
wxString lineText = GetLineText(row);
wxCoord x2 = x + GetTextWidth(lineText[(size_t)col]);
if ( x2 > xStart + rectText.width )
{
// we want the position of this column be 1/3 to the
// left of the right edge, i.e. 2/3 right of the left
// one
x2 -= (2*rectText.width)/3;
if ( x2 < 0 )
x2 = 0;
Scroll(x2 / wChar, row);
}
}
}
}
}
//else: multiline but no scrollbars, hence nothing to do
ShowCaret();
}
// ----------------------------------------------------------------------------
// word stuff
// ----------------------------------------------------------------------------
/*
TODO: we could have (easy to do) vi-like options for word movement, i.e.
distinguish between inlusive/exclusive words and between words and
WORDS (in vim sense) and also, finally, make the set of characters
which make up a word configurable - currently we use the exclusive
WORDS only (coincidentally, this is what Windows edit control does)
For future references, here is what vim help says:
A word consists of a sequence of letters, digits and underscores, or
a sequence of other non-blank characters, separated with white space
(spaces, tabs, <EOL>). This can be changed with the 'iskeyword'
option.
A WORD consists of a sequence of non-blank characters, separated with
white space. An empty line is also considered to be a word and a
WORD.
*/
static inline bool IsWordChar(wxChar ch)
{
return !wxIsspace(ch);
}
wxTextPos wxTextCtrl::GetWordStart() const
{
if ( m_curPos == -1 || m_curPos == 0 )
return 0;
if ( m_curCol == 0 )
{
// go to the end of the previous line
return m_curPos - 1;
}
// it shouldn't be possible to learn where the word starts in the password
// text entry zone
if ( IsPassword() )
return 0;
// start at the previous position
const wxChar *p0 = GetLineText(m_curRow).c_str();
const wxChar *p = p0 + m_curCol - 1;
// find the end of the previous word
while ( (p > p0) && !IsWordChar(*p) )
p--;
// now find the beginning of this word
while ( (p > p0) && IsWordChar(*p) )
p--;
// we might have gone too far
if ( !IsWordChar(*p) )
p++;
return (m_curPos - m_curCol) + p - p0;
}
wxTextPos wxTextCtrl::GetWordEnd() const
{
if ( m_curPos == -1 )
return 0;
wxString line = GetLineText(m_curRow);
if ( (size_t)m_curCol == line.length() )
{
// if we're on the last position in the line, go to the next one - if
// it exists
wxTextPos pos = m_curPos;
if ( pos < GetLastPosition() )
pos++;
return pos;
}
// it shouldn't be possible to learn where the word ends in the password
// text entry zone
if ( IsPassword() )
return GetLastPosition();
// start at the current position
const wxChar *p0 = line.c_str();
const wxChar *p = p0 + m_curCol;
// find the start of the next word
while ( *p && !IsWordChar(*p) )
p++;
// now find the end of it
while ( *p && IsWordChar(*p) )
p++;
// and find the start of the next word
while ( *p && !IsWordChar(*p) )
p++;
return (m_curPos - m_curCol) + p - p0;
}
// ----------------------------------------------------------------------------
// clipboard stuff
// ----------------------------------------------------------------------------
void wxTextCtrl::Copy()
{
#if wxUSE_CLIPBOARD
if ( HasSelection() )
{
wxClipboardLocker clipLock;
// wxTextFile::Translate() is needed to transform all '\n' into "\r\n"
wxString text = wxTextFile::Translate(GetTextToShow(GetSelectionText()));
wxTextDataObject *data = new wxTextDataObject(text);
wxTheClipboard->SetData(data);
}
#endif // wxUSE_CLIPBOARD
}
void wxTextCtrl::Cut()
{
(void)DoCut();
}
bool wxTextCtrl::DoCut()
{
if ( !HasSelection() )
return FALSE;
Copy();
RemoveSelection();
return TRUE;
}
void wxTextCtrl::Paste()
{
(void)DoPaste();
}
bool wxTextCtrl::DoPaste()
{
#if wxUSE_CLIPBOARD
wxClipboardLocker clipLock;
wxTextDataObject data;
if ( wxTheClipboard->IsSupported(data.GetFormat())
&& wxTheClipboard->GetData(data) )
{
// reverse transformation: '\r\n\" -> '\n'
wxString text = wxTextFile::Translate(data.GetText(),
wxTextFileType_Unix);
if ( !text.empty() )
{
WriteText(text);
return TRUE;
}
}
#endif // wxUSE_CLIPBOARD
return FALSE;
}
// ----------------------------------------------------------------------------
// Undo and redo
// ----------------------------------------------------------------------------
wxTextCtrlInsertCommand *
wxTextCtrlCommandProcessor::IsInsertCommand(wxCommand *command)
{
return (wxTextCtrlInsertCommand *)
(command && (command->GetName() == wxTEXT_COMMAND_INSERT)
? command : NULL);
}
void wxTextCtrlCommandProcessor::Store(wxCommand *command)
{
wxTextCtrlInsertCommand *cmdIns = IsInsertCommand(command);
if ( cmdIns )
{
if ( IsCompressing() )
{
wxTextCtrlInsertCommand *
cmdInsLast = IsInsertCommand(GetCurrentCommand());
// it is possible that we don't have any last command at all if,
// for example, it was undone since the last Store(), so deal with
// this case too
if ( cmdInsLast )
{
cmdInsLast->Append(cmdIns);
delete cmdIns;
// don't need to call the base class version
return;
}
}
// append the following insert commands to this one
m_compressInserts = TRUE;
// let the base class version will do the job normally
}
else // not an insert command
{
// stop compressing insert commands - this won't work with the last
// command not being an insert one anyhow
StopCompressing();
// let the base class version will do the job normally
}
wxCommandProcessor::Store(command);
}
void wxTextCtrlInsertCommand::Append(wxTextCtrlInsertCommand *other)
{
m_text += other->m_text;
}
bool wxTextCtrlInsertCommand::CanUndo() const
{
return m_from != -1;
}
bool wxTextCtrlInsertCommand::Do(wxTextCtrl *text)
{
// the text is going to be inserted at the current position, remember where
// exactly it is
m_from = text->GetInsertionPoint();
// and now do insert it
text->WriteText(m_text);
return TRUE;
}
bool wxTextCtrlInsertCommand::Undo(wxTextCtrl *text)
{
wxCHECK_MSG( CanUndo(), FALSE, _T("impossible to undo insert cmd") );
// remove the text from where we inserted it
text->Remove(m_from, m_from + m_text.length());
return TRUE;
}
bool wxTextCtrlRemoveCommand::CanUndo() const
{
// if we were executed, we should have the text we removed
return !m_textDeleted.empty();
}
bool wxTextCtrlRemoveCommand::Do(wxTextCtrl *text)
{
text->SetSelection(m_from, m_to);
m_textDeleted = text->GetSelectionText();
text->RemoveSelection();
return TRUE;
}
bool wxTextCtrlRemoveCommand::Undo(wxTextCtrl *text)
{
// it is possible that the text was deleted and that we can't restore text
// at the same position we removed it any more
wxTextPos posLast = text->GetLastPosition();
text->SetInsertionPoint(m_from > posLast ? posLast : m_from);
text->WriteText(m_textDeleted);
return TRUE;
}
void wxTextCtrl::Undo()
{
// the caller must check it
wxASSERT_MSG( CanUndo(), _T("can't call Undo() if !CanUndo()") );
m_cmdProcessor->Undo();
}
void wxTextCtrl::Redo()
{
// the caller must check it
wxASSERT_MSG( CanRedo(), _T("can't call Undo() if !CanUndo()") );
m_cmdProcessor->Redo();
}
bool wxTextCtrl::CanUndo() const
{
return IsEditable() && m_cmdProcessor->CanUndo();
}
bool wxTextCtrl::CanRedo() const
{
return IsEditable() && m_cmdProcessor->CanRedo();
}
// ----------------------------------------------------------------------------
// geometry
// ----------------------------------------------------------------------------
wxSize wxTextCtrl::DoGetBestClientSize() const
{
// when we're called for the very first time from Create() we must
// calculate the font metrics here because we can't do it before calling
// Create() (there is no window yet and wxGTK crashes) but we need them
// here
if ( m_heightLine == -1 )
{
wxConstCast(this, wxTextCtrl)->RecalcFontMetrics();
}
wxCoord w, h;
GetTextExtent(GetTextToShow(GetLineText(0)), &w, &h);
int wChar = GetAverageWidth(),
hChar = GetLineHeight();
int widthMin = wxMax(10*wChar, 100);
if ( w < widthMin )
w = widthMin;
if ( h < hChar )
h = hChar;
if ( !IsSingleLine() )
{
// let the control have a reasonable number of lines
int lines = GetNumberOfLines();
if ( lines < 5 )
lines = 5;
else if ( lines > 10 )
lines = 10;
h *= 10;
}
wxRect rectText;
rectText.width = w;
rectText.height = h;
wxRect rectTotal = GetRenderer()->GetTextTotalArea(this, rectText);
return wxSize(rectTotal.width, rectTotal.height);
}
void wxTextCtrl::UpdateTextRect()
{
wxRect rectTotal(wxPoint(0, 0), GetClientSize());
wxCoord *extraSpace = WrapLines() ? &WData().m_widthMark : NULL;
m_rectText = GetRenderer()->GetTextClientArea(this, rectTotal, extraSpace);
// code elsewhere is confused by negative rect size
if ( m_rectText.width <= 0 )
m_rectText.width = 1;
if ( m_rectText.height <= 0 )
m_rectText.height = 1;
if ( !IsSingleLine() )
{
// invalidate it so that GetRealTextArea() will recalc it
MData().m_rectTextReal.width = 0;
// only scroll this rect when the window is scrolled: note that we have
// to scroll not only the text but the line wrap marks too if we show
// them
wxRect rectText = GetRealTextArea();
if ( extraSpace && *extraSpace )
{
rectText.width += *extraSpace;
}
SetTargetRect(rectText);
// relayout all lines
if ( WrapLines() )
{
WData().m_rowFirstInvalid = 0;
// increase timestamp: this means that the lines which had been
// laid out before will be relayd out the next time LayoutLines()
// is called because their timestamp will be smaller than the
// current one
WData().m_timestamp++;
}
}
UpdateLastVisible();
}
void wxTextCtrl::UpdateLastVisible()
{
// this method is only used for horizontal "scrollbarless" scrolling which
// is used only with single line controls
if ( !IsSingleLine() )
return;
// use (efficient) HitTestLine to find the last visible character
wxString text = m_value.Mid((size_t)SData().m_colStart /* to the end */);
wxTextCoord col;
switch ( HitTestLine(text, m_rectText.width, &col) )
{
case wxTE_HT_BEYOND:
// everything is visible
SData().m_colLastVisible = text.length();
// calc it below
SData().m_posLastVisible = -1;
break;
/*
case wxTE_HT_BEFORE:
case wxTE_HT_BELOW:
*/
default:
wxFAIL_MSG(_T("unexpected HitTestLine() return value"));
// fall through
case wxTE_HT_ON_TEXT:
if ( col > 0 )
{
// the last entirely seen character is the previous one because
// this one is only partly visible - unless the width of the
// string is exactly the max width
SData().m_posLastVisible = GetTextWidth(text.Truncate(col + 1));
if ( SData().m_posLastVisible > m_rectText.width )
{
// this character is not entirely visible, take the
// previous one
col--;
// recalc it
SData().m_posLastVisible = -1;
}
//else: we can just see it
SData().m_colLastVisible = col;
}
break;
}
// calculate the width of the text really shown
if ( SData().m_posLastVisible == -1 )
{
SData().m_posLastVisible = GetTextWidth(text.Truncate(SData().m_colLastVisible + 1));
}
// current value is relative the start of the string text which starts at
// SData().m_colStart, we need an absolute offset into string
SData().m_colLastVisible += SData().m_colStart;
wxLogTrace(_T("text"), _T("Last visible column/position is %d/%ld"),
SData().m_colLastVisible, SData().m_posLastVisible);
}
void wxTextCtrl::OnSize(wxSizeEvent& event)
{
UpdateTextRect();
if ( !IsSingleLine() )
{
#if 0
// update them immediately because if we are called for the first time,
// we need to create them in order for the base class version to
// position the scrollbars correctly - if we don't do it now, it won't
// happen at all if we don't get more size events
UpdateScrollbars();
#endif // 0
MData().m_updateScrollbarX =
MData().m_updateScrollbarY = TRUE;
}
event.Skip();
}
wxCoord wxTextCtrl::GetTotalWidth() const
{
wxCoord w;
CalcUnscrolledPosition(m_rectText.width, 0, &w, NULL);
return w;
}
wxCoord wxTextCtrl::GetTextWidth(const wxString& text) const
{
wxCoord w;
GetTextExtent(GetTextToShow(text), &w, NULL);
return w;
}
wxRect wxTextCtrl::GetRealTextArea() const
{
// for single line text control it's just the same as text rect
if ( IsSingleLine() )
return m_rectText;
// the real text area always holds an entire number of lines, so the only
// difference with the text area is a narrow strip along the bottom border
wxRect rectText = MData().m_rectTextReal;
if ( !rectText.width )
{
// recalculate it
rectText = m_rectText;
// when we're called for the very first time, the line height might not
// had been calculated yet, so do get it now
wxTextCtrl *self = wxConstCast(this, wxTextCtrl);
self->RecalcFontMetrics();
int hLine = GetLineHeight();
rectText.height = (m_rectText.height / hLine) * hLine;
// cache the result
self->MData().m_rectTextReal = rectText;
}
return rectText;
}
wxTextCoord wxTextCtrl::GetRowInLine(wxTextCoord line,
wxTextCoord col,
wxTextCoord *colRowStart) const
{
wxASSERT_MSG( WrapLines(), _T("shouldn't be called") );
const wxWrappedLineData& lineData = WData().m_linesData[line];
if ( !WData().IsValidLine(line) )
LayoutLines(line);
// row is here counted a bit specially: 0 is the 2nd row of the line (1st
// extra row)
size_t row = 0,
rowMax = lineData.GetExtraRowCount();
if ( rowMax )
{
row = 0;
while ( (row < rowMax) && (col >= lineData.GetExtraRowStart(row)) )
row++;
// it's ok here that row is 1 greater than needed: like this, it is
// counted as a normal (and not extra) row
}
//else: only one row anyhow
if ( colRowStart )
{
// +1 because we need a real row number, not the extra row one
*colRowStart = lineData.GetRowStart(row);
// this can't happen, of course
wxASSERT_MSG( *colRowStart <= col, _T("GetRowInLine() is broken") );
}
return row;
}
void wxTextCtrl::LayoutLine(wxTextCoord line, wxWrappedLineData& lineData) const
{
// FIXME: this uses old GetPartOfWrappedLine() which is not used anywhere
// else now and has rather awkward interface for our needs here
lineData.m_rowsStart.Empty();
lineData.m_rowsWidth.Empty();
const wxString& text = GetLineText(line);
wxCoord widthRow;
size_t colRowStart = 0;
do
{
size_t lenRow = GetPartOfWrappedLine
(
text.c_str() + colRowStart,
&widthRow
);
// remember the start of this row (not for the first one as
// it's always 0) and its width
if ( colRowStart )
lineData.m_rowsStart.Add(colRowStart);
lineData.m_rowsWidth.Add(widthRow);
colRowStart += lenRow;
}
while ( colRowStart < text.length() );
// put the current timestamp on it
lineData.m_timestamp = WData().m_timestamp;
}
void wxTextCtrl::LayoutLines(wxTextCoord lineLast) const
{
wxASSERT_MSG( WrapLines(), _T("should only be used for line wrapping") );
// if we were called, some line was dirty and if it was dirty we must have
// had m_rowFirstInvalid set to something too
wxTextCoord lineFirst = WData().m_rowFirstInvalid;
wxASSERT_MSG( lineFirst != -1, _T("nothing to layout?") );
wxTextCoord rowFirst, rowCur;
if ( lineFirst )
{
// start after the last known valid line
const wxWrappedLineData& lineData = WData().m_linesData[lineFirst - 1];
rowFirst = lineData.GetFirstRow() + lineData.GetRowCount();
}
else // no valid lines, start at row 0
{
rowFirst = 0;
}
rowCur = rowFirst;
for ( wxTextCoord line = lineFirst; line <= lineLast; line++ )
{
// set the starting row for this line
wxWrappedLineData& lineData = WData().m_linesData[line];
lineData.m_rowFirst = rowCur;
// had the line been already broken into rows?
//
// if so, compare its timestamp with the current one: if nothing has
// been changed, don't relayout it
if ( !lineData.IsValid() ||
(lineData.m_timestamp < WData().m_timestamp) )
{
// now do break it in rows
LayoutLine(line, lineData);
}
rowCur += lineData.GetRowCount();
}
// we are now valid at least up to this line, but if it is the last one we
// just don't have any more invalid rows at all
if ( (size_t)lineLast == WData().m_linesData.GetCount() -1 )
{
lineLast = -1;
}
wxTextCtrl *self = wxConstCast(this, wxTextCtrl);
self->WData().m_rowFirstInvalid = lineLast;
// also refresh the line end indicators (FIXME shouldn't do it always!)
self->RefreshLineWrapMarks(rowFirst, rowCur);
}
size_t wxTextCtrl::GetPartOfWrappedLine(const wxChar* text,
wxCoord *widthReal) const
{
// this function is slow, it shouldn't be called unless really needed
wxASSERT_MSG( WrapLines(), _T("shouldn't be called") );
wxString s(text);
wxTextCoord col;
wxCoord wReal = -1;
switch ( HitTestLine(s, m_rectText.width, &col) )
{
/*
case wxTE_HT_BEFORE:
case wxTE_HT_BELOW:
*/
default:
wxFAIL_MSG(_T("unexpected HitTestLine() return value"));
// fall through
case wxTE_HT_ON_TEXT:
if ( col > 0 )
{
// the last entirely seen character is the previous one because
// this one is only partly visible - unless the width of the
// string is exactly the max width
wReal = GetTextWidth(s.Truncate(col + 1));
if ( wReal > m_rectText.width )
{
// this character is not entirely visible, take the
// previous one
col--;
// recalc the width
wReal = -1;
}
//else: we can just see it
// wrap at any character or only at words boundaries?
if ( !(GetWindowStyle() & wxTE_LINEWRAP) )
{
// find the (last) not word char before this word
wxTextCoord colWordStart;
for ( colWordStart = col;
colWordStart && IsWordChar(s[(size_t)colWordStart]);
colWordStart-- )
;
if ( colWordStart > 0 )
{
if ( colWordStart != col )
{
// will have to recalc the real width
wReal = -1;
col = colWordStart;
}
}
//else: only a single word, have to wrap it here
}
}
break;
case wxTE_HT_BEYOND:
break;
}
// we return the number of characters, not the index of the last one
if ( (size_t)col < s.length() )
{
// but don't return more than this (empty) string has
col++;
}
if ( widthReal )
{
if ( wReal == -1 )
{
// calc it if not done yet
wReal = GetTextWidth(s.Truncate(col));
}
*widthReal = wReal;
}
// VZ: old, horribly inefficient code which can still be used for checking
// the result (in line, not word, wrap mode only) - to be removed later
#if 0
wxTextCtrl *self = wxConstCast(this, wxTextCtrl);
wxClientDC dc(self);
dc.SetFont(GetFont());
self->DoPrepareDC(dc);
wxCoord widthMax = m_rectText.width;
// the text which we can keep in this ROW
wxString str;
wxCoord w, wOld;
for ( wOld = w = 0; *text && (w <= widthMax); )
{
wOld = w;
str += *text++;
dc.GetTextExtent(str, &w, NULL);
}
if ( w > widthMax )
{
// if we wrapped, the last letter was one too much
if ( str.length() > 1 )
{
// remove it
str.erase(str.length() - 1, 1);
}
else // but always keep at least one letter in each row
{
// the real width then is the last value of w and not teh one
// before last
wOld = w;
}
}
else // we didn't wrap
{
wOld = w;
}
wxASSERT( col == str.length() );
if ( widthReal )
{
wxASSERT( *widthReal == wOld );
*widthReal = wOld;
}
//return str.length();
#endif
return col;
}
// OPT: this function is called a lot - would be nice to optimize it but I
// don't really know how yet
wxTextCtrlHitTestResult wxTextCtrl::HitTestLine(const wxString& line,
wxCoord x,
wxTextCoord *colOut) const
{
wxTextCtrlHitTestResult res = wxTE_HT_ON_TEXT;
int col;
wxTextCtrl *self = wxConstCast(this, wxTextCtrl);
wxClientDC dc(self);
dc.SetFont(GetFont());
self->DoPrepareDC(dc);
wxCoord width;
dc.GetTextExtent(line, &width, NULL);
if ( x >= width )
{
// clicking beyond the end of line is equivalent to clicking at
// the end of it, so return the last line column
col = line.length();
if ( col )
{
// unless the line is empty and so doesn't have any column at all -
// in this case return 0, what else can we do?
col--;
}
res = wxTE_HT_BEYOND;
}
else if ( x < 0 )
{
col = 0;
res = wxTE_HT_BEFORE;
}
else // we're inside the line
{
// now calculate the column: first, approximate it with fixed-width
// value and then calculate the correct value iteratively: note that
// we use the first character of the line instead of (average)
// GetCharWidth(): it is common to have lines of dashes, for example,
// and this should give us much better approximation in such case
//
// OPT: maybe using (cache) m_widthAvg would be still faster? profile!
dc.GetTextExtent(line[0], &width, NULL);
col = x / width;
if ( col < 0 )
{
col = 0;
}
else if ( (size_t)col > line.length() )
{
col = line.length();
}
// matchDir is the direction in which we should move to reach the
// character containing the given position
enum
{
Match_Left = -1,
Match_None = 0,
Match_Right = 1
} matchDir = Match_None;
for ( ;; )
{
// check that we didn't go beyond the line boundary
if ( col < 0 )
{
col = 0;
break;
}
if ( (size_t)col > line.length() )
{
col = line.length();
break;
}
wxString strBefore(line, (size_t)col);
dc.GetTextExtent(strBefore, &width, NULL);
if ( width > x )
{
if ( matchDir == Match_Right )
{
// we were going to the right and, finally, moved beyond
// the original position - stop on the previous one
col--;
break;
}
if ( matchDir == Match_None )
{
// we just started iterating, now we know that we should
// move to the left
matchDir = Match_Left;
}
//else: we are still to the right of the target, continue
}
else // width < x
{
// invert the logic above
if ( matchDir == Match_Left )
{
// with the exception that we don't need to backtrack here
break;
}
if ( matchDir == Match_None )
{
// go to the right
matchDir = Match_Right;
}
}
// this is not supposed to happen
wxASSERT_MSG( matchDir, _T("logic error in wxTextCtrl::HitTest") );
if ( matchDir == Match_Right )
col++;
else
col--;
}
}
// check that we calculated it correctly
#ifdef WXDEBUG_TEXT
if ( res == wxTE_HT_ON_TEXT )
{
wxCoord width1;
wxString text = line.Left(col);
dc.GetTextExtent(text, &width1, NULL);
if ( (size_t)col < line.length() )
{
wxCoord width2;
text += line[col];
dc.GetTextExtent(text, &width2, NULL);
wxASSERT_MSG( (width1 <= x) && (x < width2),
_T("incorrect HitTestLine() result") );
}
else // we return last char
{
wxASSERT_MSG( x >= width1, _T("incorrect HitTestLine() result") );
}
}
#endif // WXDEBUG_TEXT
if ( colOut )
*colOut = col;
return res;
}
wxTextCtrlHitTestResult wxTextCtrl::HitTest(const wxPoint& pos,
wxTextCoord *colOut,
wxTextCoord *rowOut) const
{
return HitTest2(pos.y, pos.x, 0, rowOut, colOut, NULL, NULL);
}
wxTextCtrlHitTestResult wxTextCtrl::HitTestLogical(const wxPoint& pos,
wxTextCoord *colOut,
wxTextCoord *rowOut) const
{
return HitTest2(pos.y, pos.x, 0, rowOut, colOut, NULL, NULL, FALSE);
}
wxTextCtrlHitTestResult wxTextCtrl::HitTest2(wxCoord y0,
wxCoord x10,
wxCoord x20,
wxTextCoord *rowOut,
wxTextCoord *colStart,
wxTextCoord *colEnd,
wxTextCoord *colRowStartOut,
bool deviceCoords) const
{
// is the point in the text area or to the right or below it?
wxTextCtrlHitTestResult res = wxTE_HT_ON_TEXT;
// translate the window coords x0 and y0 into the client coords in the text
// area by adjusting for both the client and text area offsets (unless this
// was already done)
int x1, y;
if ( deviceCoords )
{
wxPoint pt = GetClientAreaOrigin() + m_rectText.GetPosition();
CalcUnscrolledPosition(x10 - pt.x, y0 - pt.y, &x1, &y);
}
else
{
y = y0;
x1 = x10;
}
// calculate the row (it is really a LINE, not a ROW)
wxTextCoord row;
// these vars are used only for WrapLines() case
wxTextCoord colRowStart = 0;
size_t rowLen = 0;
if ( colRowStartOut )
*colRowStartOut = 0;
int hLine = GetLineHeight();
if ( y < 0 )
{
// and clicking before it is the same as clicking on the first one
row = 0;
res = wxTE_HT_BEFORE;
}
else // y >= 0
{
wxTextCoord rowLast = GetNumberOfLines() - 1;
row = y / hLine;
if ( IsSingleLine() || !WrapLines() )
{
// in this case row calculation is simple as all lines have the
// same height and so row is the same as line
if ( row > rowLast )
{
// clicking below the text is the same as clicking on the last
// line
row = rowLast;
res = wxTE_HT_BELOW;
}
}
else // multline control with line wrap
{
// use binary search to find the line containing this row
const wxArrayWrappedLinesData& linesData = WData().m_linesData;
size_t lo = 0,
hi = linesData.GetCount(),
cur;
while ( lo < hi )
{
cur = (lo + hi)/2;
const wxWrappedLineData& lineData = linesData[cur];
if ( !WData().IsValidLine(cur) )
LayoutLines(cur);
wxTextCoord rowFirst = lineData.GetFirstRow();
if ( row < rowFirst )
{
hi = cur;
}
else
{
// our row is after the first row of the cur line:
// obviously, if cur is the last line, it contains this
// row, otherwise we have to test that it is before the
// first row of the next line
bool found = cur == linesData.GetCount() - 1;
if ( found )
{
// if the row is beyond the end of text, adjust it to
// be the last one and set res accordingly
if ( (size_t)(row - rowFirst) >= lineData.GetRowCount() )
{
res = wxTE_HT_BELOW;
row = lineData.GetRowCount() + rowFirst - 1;
}
}
else // not the last row
{
const wxWrappedLineData&
lineNextData = linesData[cur + 1];
if ( !WData().IsValidLine(cur + 1) )
LayoutLines(cur + 1);
found = row < lineNextData.GetFirstRow();
}
if ( found )
{
colRowStart = lineData.GetRowStart(row - rowFirst);
rowLen = lineData.GetRowLength(row - rowFirst,
GetLines()[cur].length());
row = cur;
break;
}
else
{
lo = cur;
}
}
}
}
}
if ( res == wxTE_HT_ON_TEXT )
{
// now find the position in the line
wxString lineText = GetLineText(row),
rowText;
if ( colRowStart || rowLen )
{
// look in this row only, not in whole line
rowText = lineText.Mid(colRowStart, rowLen);
}
else
{
// just take the whole string
rowText = lineText;
}
if ( colStart )
{
res = HitTestLine(GetTextToShow(rowText), x1, colStart);
if ( colRowStart )
{
if ( colRowStartOut )
{
// give them the column offset in this ROW in pixels
*colRowStartOut = colRowStart;
}
// take into account that the ROW doesn't start in the
// beginning of the LINE
*colStart += colRowStart;
}
if ( colEnd )
{
// the hit test result we return is for x1, so throw out
// the result for x2 here
int x2 = x1 + x20 - x10;
(void)HitTestLine(GetTextToShow(rowText), x2, colEnd);
*colEnd += colRowStart;
}
}
}
else // before/after vertical text span
{
if ( colStart )
{
// fill the column with the first/last position in the
// corresponding line
if ( res == wxTE_HT_BEFORE )
*colStart = 0;
else // res == wxTE_HT_BELOW
*colStart = GetLineText(GetNumberOfLines() - 1).length();
}
}
if ( rowOut )
{
// give them the row in text coords (as is)
*rowOut = row;
}
return res;
}
bool wxTextCtrl::GetLineAndRow(wxTextCoord row,
wxTextCoord *lineOut,
wxTextCoord *rowInLineOut) const
{
wxTextCoord line,
rowInLine = 0;
if ( row < 0 )
return FALSE;
int nLines = GetNumberOfLines();
if ( WrapLines() )
{
const wxArrayWrappedLinesData& linesData = WData().m_linesData;
for ( line = 0; line < nLines; line++ )
{
if ( !WData().IsValidLine(line) )
LayoutLines(line);
if ( row < linesData[line].GetNextRow() )
{
// we found the right line
rowInLine = row - linesData[line].GetFirstRow();
break;
}
}
if ( line == nLines )
{
// the row is out of range
return FALSE;
}
}
else // no line wrapping, everything is easy
{
if ( row >= nLines )
return FALSE;
line = row;
}
if ( lineOut )
*lineOut = line;
if ( rowInLineOut )
*rowInLineOut = rowInLine;
return TRUE;
}
// ----------------------------------------------------------------------------
// scrolling
// ----------------------------------------------------------------------------
/*
wxTextCtrl has not one but two scrolling mechanisms: one is semi-automatic
scrolling in both horizontal and vertical direction implemented using
wxScrollHelper and the second one is manual scrolling implemented using
SData().m_ofsHorz and used by the single line controls without scroll bar.
The first version (the standard one) always scrolls by fixed amount which is
fine for vertical scrolling as all lines have the same height but is rather
ugly for horizontal scrolling if proportional font is used. This is why we
manually update and use SData().m_ofsHorz which contains the length of the string
which is hidden beyond the left borde. An important property of text
controls using this kind of scrolling is that an entire number of characters
is always shown and that parts of characters never appear on display -
neither in the leftmost nor rightmost positions.
Once again, for multi line controls SData().m_ofsHorz is always 0 and scrolling is
done as usual for wxScrollWindow.
*/
void wxTextCtrl::ShowHorzPosition(wxCoord pos)
{
wxASSERT_MSG( IsSingleLine(), _T("doesn't work for multiline") );
// pos is the logical position to show
// SData().m_ofsHorz is the fisrt logical position shown
if ( pos < SData().m_ofsHorz )
{
// scroll backwards
wxTextCoord col;
HitTestLine(m_value, pos, &col);
ScrollText(col);
}
else
{
wxCoord width = m_rectText.width;
if ( !width )
{
// if we are called from the ctor, m_rectText is not initialized
// yet, so do it now
UpdateTextRect();
width = m_rectText.width;
}
// SData().m_ofsHorz + width is the last logical position shown
if ( pos > SData().m_ofsHorz + width)
{
// scroll forward
wxTextCoord col;
HitTestLine(m_value, pos - width, &col);
ScrollText(col + 1);
}
}
}
// scroll the window horizontally so that the first visible character becomes
// the one at this position
void wxTextCtrl::ScrollText(wxTextCoord col)
{
wxASSERT_MSG( IsSingleLine(),
_T("ScrollText() is for single line controls only") );
// never scroll beyond the left border
if ( col < 0 )
col = 0;
// OPT: could only get the extent of the part of the string between col
// and SData().m_colStart
wxCoord ofsHorz = GetTextWidth(GetLineText(0).Left(col));
if ( ofsHorz != SData().m_ofsHorz )
{
// remember the last currently used pixel
int posLastVisible = SData().m_posLastVisible;
if ( posLastVisible == -1 )
{
// this may happen when we're called very early, during the
// controls construction
UpdateLastVisible();
posLastVisible = SData().m_posLastVisible;
}
// NB1: to scroll to the right, offset must be negative, hence the
// order of operands
int dx = SData().m_ofsHorz - ofsHorz;
// NB2: we call Refresh() below which results in a call to
// DoDraw(), so we must update SData().m_ofsHorz before calling it
SData().m_ofsHorz = ofsHorz;
SData().m_colStart = col;
// after changing m_colStart, recalc the last visible position: we need
// to recalc the last visible position beore scrolling in order to make
// it appear exactly at the right edge of the text area after scrolling
UpdateLastVisible();
#if 0 // do we?
if ( dx < 0 )
{
// we want to force the update of it after scrolling
SData().m_colLastVisible = -1;
}
#endif
// scroll only the rectangle inside which there is the text
wxRect rect = m_rectText;
rect.width = posLastVisible;
rect = ScrollNoRefresh(dx, 0, &rect);
/*
we need to manually refresh the part which ScrollWindow() doesn't
refresh (with new API this means the part outside the rect returned
by ScrollNoRefresh): indeed, if we had this:
********o
where '*' is text and 'o' is blank area at the end (too small to
hold the next char) then after scrolling by 2 positions to the left
we're going to have
******RRo
where 'R' is the area refreshed by ScrollWindow() - but we still
need to refresh the 'o' at the end as it may be now big enough to
hold the new character shifted into view.
when we are scrolling to the right, we need to update this rect as
well because it might have contained something before but doesn't
contain anything any more
*/
// we can combine both rectangles into one when scrolling to the left,
// but we need two separate Refreshes() otherwise
if ( dx > 0 )
{
// refresh the uncovered part on the left
Refresh(TRUE, &rect);
// and now the area on the right
rect.x = m_rectText.x + posLastVisible;
rect.width = m_rectText.width - posLastVisible;
}
else // scrolling to the left
{
// just extend the rect covering the uncovered area to the edge of
// the text rect
rect.width += m_rectText.width - posLastVisible;
}
Refresh(TRUE, &rect);
// I don't know exactly why is this needed here but without it we may
// scroll the window again (from the same method) before the previously
// invalidated area is repainted when typing *very* quickly - and this
// may lead to the display corruption
Update();
}
}
void wxTextCtrl::CalcUnscrolledPosition(int x, int y, int *xx, int *yy) const
{
if ( IsSingleLine() )
{
// we don't use wxScrollHelper
if ( xx )
*xx = x + SData().m_ofsHorz;
if ( yy )
*yy = y;
}
else
{
// let the base class do it
wxScrollHelper::CalcUnscrolledPosition(x, y, xx, yy);
}
}
void wxTextCtrl::CalcScrolledPosition(int x, int y, int *xx, int *yy) const
{
if ( IsSingleLine() )
{
// we don't use wxScrollHelper
if ( xx )
*xx = x - SData().m_ofsHorz;
if ( yy )
*yy = y;
}
else
{
// let the base class do it
wxScrollHelper::CalcScrolledPosition(x, y, xx, yy);
}
}
void wxTextCtrl::DoPrepareDC(wxDC& dc)
{
// for single line controls we only have to deal with SData().m_ofsHorz and it's
// useless to call base class version as they don't use normal scrolling
if ( IsSingleLine() && SData().m_ofsHorz )
{
// adjust the DC origin if the text is shifted
wxPoint pt = dc.GetDeviceOrigin();
dc.SetDeviceOrigin(pt.x - SData().m_ofsHorz, pt.y);
}
else
{
wxScrollHelper::DoPrepareDC(dc);
}
}
void wxTextCtrl::UpdateMaxWidth(wxTextCoord line)
{
// OPT!
// check if the max width changes after this line was modified
wxCoord widthMaxOld = MData().m_widthMax,
width;
GetTextExtent(GetLineText(line), &width, NULL);
if ( line == MData().m_lineLongest )
{
// this line was the longest one, is it still?
if ( width > MData().m_widthMax )
{
MData().m_widthMax = width;
}
else if ( width < MData().m_widthMax )
{
// we need to find the new longest line
RecalcMaxWidth();
}
//else: its length didn't change, nothing to do
}
else // it wasn't the longest line, but maybe it became it?
{
// GetMaxWidth() and not MData().m_widthMax as it might be not calculated yet
if ( width > GetMaxWidth() )
{
MData().m_widthMax = width;
MData().m_lineLongest = line;
}
}
MData().m_updateScrollbarX = MData().m_widthMax != widthMaxOld;
}
void wxTextCtrl::RecalcFontMetrics()
{
m_heightLine = GetCharHeight();
m_widthAvg = GetCharWidth();
}
void wxTextCtrl::RecalcMaxWidth()
{
wxASSERT_MSG( !IsSingleLine(), _T("only used for multiline") );
MData().m_widthMax = -1;
(void)GetMaxWidth();
}
wxCoord wxTextCtrl::GetMaxWidth() const
{
if ( MData().m_widthMax == -1 )
{
// recalculate it
// OPT: should we remember the widths of all the lines?
wxTextCtrl *self = wxConstCast(this, wxTextCtrl);
wxClientDC dc(self);
dc.SetFont(GetFont());
self->MData().m_widthMax = 0;
size_t count = GetLineCount();
for ( size_t n = 0; n < count; n++ )
{
wxCoord width;
dc.GetTextExtent(GetLines()[n], &width, NULL);
if ( width > MData().m_widthMax )
{
// remember the width and the line which has it
self->MData().m_widthMax = width;
self->MData().m_lineLongest = n;
}
}
}
wxASSERT_MSG( MData().m_widthMax != -1, _T("should have at least 1 line") );
return MData().m_widthMax;
}
void wxTextCtrl::UpdateScrollbars()
{
wxASSERT_MSG( !IsSingleLine(), _T("only used for multiline") );
wxSize size = GetRealTextArea().GetSize();
// is our height enough to show all items?
wxTextCoord nRows = GetRowCount();
wxCoord lineHeight = GetLineHeight();
bool showScrollbarY = nRows*lineHeight > size.y;
// is our width enough to show the longest line?
wxCoord charWidth, maxWidth;
bool showScrollbarX;
if ( !WrapLines() )
{
charWidth = GetAverageWidth();
maxWidth = GetMaxWidth();
showScrollbarX = maxWidth > size.x;
}
else // never show the horz scrollbar
{
// just to suppress compiler warnings about using uninit vars below
charWidth = maxWidth = 0;
showScrollbarX = FALSE;
}
// calc the scrollbars ranges
int scrollRangeX = showScrollbarX
? (maxWidth + 2*charWidth - 1) / charWidth
: 0;
int scrollRangeY = showScrollbarY ? nRows : 0;
int scrollRangeXOld = MData().m_scrollRangeX,
scrollRangeYOld = MData().m_scrollRangeY;
if ( (scrollRangeY != scrollRangeYOld) || (scrollRangeX != scrollRangeXOld) )
{
int x, y;
GetViewStart(&x, &y);
#if 0
// we want to leave the scrollbars at the same position which means
// that x and y have to be adjusted as the number of positions may have
// changed
//
// the number of positions is calculated from knowing that last
// position = range - thumbSize and thumbSize == pageSize which is
// equal to the window width / pixelsPerLine
if ( scrollRangeXOld )
{
x *= scrollRangeX - m_rectText.width / charWidth;
x /= scrollRangeXOld - m_rectText.width / charWidth;
}
if ( scrollRangeYOld )
y *= scrollRangeY / scrollRangeYOld;
#endif // 0
SetScrollbars(charWidth, lineHeight,
scrollRangeX, scrollRangeY,
x, y,
TRUE /* no refresh */);
if ( scrollRangeXOld )
{
x *= scrollRangeX - m_rectText.width / charWidth;
x /= scrollRangeXOld - m_rectText.width / charWidth;
Scroll(x, y);
}
MData().m_scrollRangeX = scrollRangeX;
MData().m_scrollRangeY = scrollRangeY;
// bring the current position in view
ShowPosition(-1);
}
MData().m_updateScrollbarX =
MData().m_updateScrollbarY = FALSE;
}
void wxTextCtrl::OnIdle(wxIdleEvent& event)
{
// notice that single line text control never has scrollbars
if ( !IsSingleLine() &&
(MData().m_updateScrollbarX || MData().m_updateScrollbarY) )
{
UpdateScrollbars();
}
event.Skip();
}
bool wxTextCtrl::SendAutoScrollEvents(wxScrollWinEvent& event) const
{
bool forward = event.GetEventType() == wxEVT_SCROLLWIN_LINEDOWN;
if ( event.GetOrientation() == wxHORIZONTAL )
{
return forward ? m_curCol <= GetLineLength(m_curRow) : m_curCol > 0;
}
else // wxVERTICAL
{
return forward ? m_curRow < GetNumberOfLines() : m_curRow > 0;
}
}
// ----------------------------------------------------------------------------
// refresh
// ----------------------------------------------------------------------------
void wxTextCtrl::RefreshSelection()
{
if ( HasSelection() )
{
RefreshTextRange(m_selStart, m_selEnd);
}
}
void wxTextCtrl::RefreshLineRange(wxTextCoord lineFirst, wxTextCoord lineLast)
{
wxASSERT_MSG( lineFirst <= lineLast || !lineLast,
_T("no lines to refresh") );
wxRect rect;
// rect.x is already 0
rect.width = m_rectText.width;
wxCoord h = GetLineHeight();
wxTextCoord rowFirst;
if ( lineFirst < GetNumberOfLines() )
{
rowFirst = GetFirstRowOfLine(lineFirst);
}
else // lineFirst == GetNumberOfLines()
{
// lineFirst may be beyond the last line only if we refresh till
// the end, otherwise it's illegal
wxASSERT_MSG( lineFirst == GetNumberOfLines() && !lineLast,
_T("invalid line range") );
rowFirst = GetRowAfterLine(lineFirst - 1);
}
rect.y = rowFirst*h;
if ( lineLast )
{
// refresh till this line (inclusive)
wxTextCoord rowLast = GetRowAfterLine(lineLast);
rect.height = (rowLast - rowFirst + 1)*h;
}
else // lineLast == 0 means to refresh till the end
{
// FIXME: calc it exactly
rect.height = 32000;
}
RefreshTextRect(rect);
}
void wxTextCtrl::RefreshTextRange(wxTextPos start, wxTextPos end)
{
wxCHECK_RET( start != -1 && end != -1,
_T("invalid RefreshTextRange() arguments") );
// accept arguments in any order as it is more conenient for the caller
OrderPositions(start, end);
// this is acceptable but we don't do anything in this case
if ( start == end )
return;
wxTextPos colStart, lineStart;
if ( !PositionToXY(start, &colStart, &lineStart) )
{
// the range is entirely beyond the end of the text, nothing to do
return;
}
wxTextCoord colEnd, lineEnd;
if ( !PositionToXY(end, &colEnd, &lineEnd) )
{
// the range spans beyond the end of text, refresh to the end
colEnd = -1;
lineEnd = GetNumberOfLines() - 1;
}
// refresh all lines one by one
for ( wxTextCoord line = lineStart; line <= lineEnd; line++ )
{
// refresh the first line from the start of the range to the end, the
// intermediate ones entirely and the last one from the beginning to
// the end of the range
wxTextPos posStart = line == lineStart ? colStart : 0;
size_t posCount;
if ( (line != lineEnd) || (colEnd == -1) )
{
// intermediate line or the last one but we need to refresh it
// until the end anyhow - do it
posCount = wxSTRING_MAXLEN;
}
else // last line
{
// refresh just the positions in between the start and the end one
posCount = colEnd - posStart;
}
if ( posCount )
RefreshColRange(line, posStart, posCount);
}
}
void wxTextCtrl::RefreshColRange(wxTextCoord line,
wxTextPos start,
size_t count)
{
wxString text = GetLineText(line);
wxASSERT_MSG( (size_t)start <= text.length() && count,
_T("invalid RefreshColRange() parameter") );
RefreshPixelRange(line,
GetTextWidth(text.Left((size_t)start)),
GetTextWidth(text.Mid((size_t)start, (size_t)count)));
}
// this method accepts "logical" coords in the sense that they are coordinates
// in a logical line but it can span several rows if we wrap lines and
// RefreshPixelRange() will then refresh several rows
void wxTextCtrl::RefreshPixelRange(wxTextCoord line,
wxCoord start,
wxCoord width)
{
// we will use line text only in line wrap case
wxString text;
if ( WrapLines() )
{
text = GetLineText(line);
}
// special case: width == 0 means to refresh till the end of line
if ( width == 0 )
{
// refresh till the end of visible line
width = GetTotalWidth();
if ( WrapLines() )
{
// refresh till the end of text
wxCoord widthAll = GetTextWidth(text);
// extend width to the end of ROW
width = widthAll - widthAll % width + width;
}
// no need to refresh beyond the end of line
width -= start;
}
//else: just refresh the specified part
wxCoord h = GetLineHeight();
wxRect rect;
rect.x = start;
rect.y = GetFirstRowOfLine(line)*h;
rect.height = h;
if ( WrapLines() )
{
// (1) skip all rows which we don't touch at all
const wxWrappedLineData& lineData = WData().m_linesData[line];
if ( !WData().IsValidLine(line) )
LayoutLines(line);
wxCoord wLine = 0; // suppress compiler warning about uninit var
size_t rowLast = lineData.GetRowCount(),
row = 0;
while ( (row < rowLast) &&
(rect.x > (wLine = lineData.GetRowWidth(row++))) )
{
rect.x -= wLine;
rect.y += h;
}
// (2) now refresh all lines except the last one: note that the first
// line is refreshed from the given start to the end, all the next
// ones - entirely
while ( (row < rowLast) && (width > wLine - rect.x) )
{
rect.width = GetTotalWidth() - rect.x;
RefreshTextRect(rect);
width -= wLine - rect.x;
rect.x = 0;
rect.y += h;
wLine = lineData.GetRowWidth(row++);
}
// (3) the code below will refresh the last line
}
rect.width = width;
RefreshTextRect(rect);
}
void wxTextCtrl::RefreshTextRect(const wxRect& rectClient, bool textOnly)
{
wxRect rect;
CalcScrolledPosition(rectClient.x, rectClient.y, &rect.x, &rect.y);
rect.width = rectClient.width;
rect.height = rectClient.height;
// account for the text area offset
rect.Offset(m_rectText.GetPosition());
// don't refresh beyond the text area unless we're refreshing the line wrap
// marks in which case textOnly is FALSE
if ( textOnly )
{
if ( rect.GetRight() > m_rectText.GetRight() )
{
rect.SetRight(m_rectText.GetRight());
if ( rect.width <= 0 )
{
// nothing to refresh
return;
}
}
}
// check the bottom boundary always, even for the line wrap marks
if ( rect.GetBottom() > m_rectText.GetBottom() )
{
rect.SetBottom(m_rectText.GetBottom());
if ( rect.height <= 0 )
{
// nothing to refresh
return;
}
}
// never refresh before the visible rect
if ( rect.x < m_rectText.x )
rect.x = m_rectText.x;
if ( rect.y < m_rectText.y )
rect.y = m_rectText.y;
wxLogTrace(_T("text"), _T("Refreshing (%d, %d)-(%d, %d)"),
rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
Refresh(TRUE, &rect);
}
void wxTextCtrl::RefreshLineWrapMarks(wxTextCoord rowFirst,
wxTextCoord rowLast)
{
if ( WData().m_widthMark )
{
wxRect rectMarks;
rectMarks.x = m_rectText.width;
rectMarks.width = WData().m_widthMark;
rectMarks.y = rowFirst*GetLineHeight();
rectMarks.height = (rowLast - rowFirst)*GetLineHeight();
RefreshTextRect(rectMarks, FALSE /* don't limit to text area */);
}
}
// ----------------------------------------------------------------------------
// border drawing
// ----------------------------------------------------------------------------
void wxTextCtrl::DoDrawBorder(wxDC& dc, const wxRect& rect)
{
m_renderer->DrawTextBorder(dc, GetBorder(), rect, GetStateFlags());
}
// ----------------------------------------------------------------------------
// client area drawing
// ----------------------------------------------------------------------------
/*
Several remarks about wxTextCtrl redraw logic:
1. only the regions which must be updated are redrawn, this means that we
never Refresh() the entire window but use RefreshPixelRange() and
ScrollWindow() which only refresh small parts of it and iterate over the
update region in our DoDraw()
2. the text displayed on the screen is obtained using GetTextToShow(): it
should be used for all drawing/measuring
*/
wxString wxTextCtrl::GetTextToShow(const wxString& text) const
{
wxString textShown;
if ( IsPassword() )
textShown = wxString(_T('*'), text.length());
else
textShown = text;
return textShown;
}
void wxTextCtrl::DoDrawTextInRect(wxDC& dc, const wxRect& rectUpdate)
{
// debugging trick to see the update rect visually
#ifdef WXDEBUG_TEXT
static int s_countUpdates = -1;
if ( s_countUpdates != -1 )
{
wxWindowDC dc(this);
dc.SetBrush(*(++s_countUpdates % 2 ? wxRED_BRUSH : wxGREEN_BRUSH));
dc.SetPen(*wxTRANSPARENT_PEN);
dc.DrawRectangle(rectUpdate);
}
#endif // WXDEBUG_TEXT
// calculate the range lineStart..lineEnd of lines to redraw
wxTextCoord lineStart, lineEnd;
if ( IsSingleLine() )
{
lineStart =
lineEnd = 0;
}
else // multiline
{
wxPoint pt = rectUpdate.GetPosition();
(void)HitTest(pt, NULL, &lineStart);
pt.y += rectUpdate.height;
(void)HitTest(pt, NULL, &lineEnd);
}
// prepare for drawing
wxCoord hLine = GetLineHeight();
// these vars will be used for hit testing of the current row
wxCoord y = rectUpdate.y;
const wxCoord x1 = rectUpdate.x;
const wxCoord x2 = rectUpdate.x + rectUpdate.width;
wxRect rectText;
rectText.height = hLine;
wxCoord yClient = y - GetClientAreaOrigin().y;
// we want to always start at the top of the line, otherwise if we redraw a
// rect whose top is in the middle of a line, we'd draw this line shifted
yClient -= (yClient - m_rectText.y) % hLine;
if ( IsSingleLine() )
{
rectText.y = yClient;
}
else // multiline, adjust for scrolling
{
CalcUnscrolledPosition(0, yClient, NULL, &rectText.y);
}
wxRenderer *renderer = GetRenderer();
// do draw the invalidated parts of each line: note that we iterate here
// over ROWs, not over LINEs
for ( wxTextCoord line = lineStart;
y < rectUpdate.y + rectUpdate.height;
y += hLine,
rectText.y += hLine )
{
// calculate the update rect in text positions for this line
wxTextCoord colStart, colEnd, colRowStart;
wxTextCtrlHitTestResult ht = HitTest2(y, x1, x2,
&line, &colStart, &colEnd,
&colRowStart);
if ( (ht == wxTE_HT_BEYOND) || (ht == wxTE_HT_BELOW) )
{
wxASSERT_MSG( line <= lineEnd, _T("how did we get that far?") );
if ( line == lineEnd )
{
// we redrew everything
break;
}
// the update rect is beyond the end of line, no need to redraw
// anything on this line - but continue with the remaining ones
continue;
}
// for single line controls we may additionally cut off everything
// which is to the right of the last visible position
if ( IsSingleLine() )
{
// don't show the columns which are scrolled out to the left
if ( colStart < SData().m_colStart )
colStart = SData().m_colStart;
// colEnd may be less than colStart if colStart was changed by the
// assignment above
if ( colEnd < colStart )
colEnd = colStart;
// don't draw the chars beyond the rightmost one
if ( SData().m_colLastVisible == -1 )
{
// recalculate this rightmost column
UpdateLastVisible();
}
if ( colStart > SData().m_colLastVisible )
{
// don't bother redrawing something that is beyond the last
// visible position
continue;
}
if ( colEnd > SData().m_colLastVisible )
{
colEnd = SData().m_colLastVisible;
}
}
// extract the part of line we need to redraw
wxString textLine = GetTextToShow(GetLineText(line));
wxString text = textLine.Mid(colStart, colEnd - colStart + 1);
// now deal with the selection: only do something if at least part of
// the line is selected
wxTextPos selStart, selEnd;
if ( GetSelectedPartOfLine(line, &selStart, &selEnd) )
{
// and if this part is (at least partly) in the current row
if ( (selStart <= colEnd) &&
(selEnd >= wxMax(colStart, colRowStart)) )
{
// these values are relative to the start of the line while the
// string passed to DrawTextLine() is only part of it, so
// adjust the selection range accordingly
selStart -= colStart;
selEnd -= colStart;
if ( selStart < 0 )
selStart = 0;
if ( (size_t)selEnd >= text.length() )
selEnd = text.length();
}
else
{
// reset selStart and selEnd to avoid passing them to
// DrawTextLine() below
selStart =
selEnd = -1;
}
}
// calculate the text coords on screen
wxASSERT_MSG( colStart >= colRowStart, _T("invalid string part") );
wxCoord ofsStart = GetTextWidth(
textLine.Mid(colRowStart,
colStart - colRowStart));
rectText.x = m_rectText.x + ofsStart;
rectText.width = GetTextWidth(text);
// do draw the text
renderer->DrawTextLine(dc, text, rectText, selStart, selEnd,
GetStateFlags());
wxLogTrace(_T("text"), _T("Line %ld: positions %ld-%ld redrawn."),
line, colStart, colEnd);
}
}
void wxTextCtrl::DoDrawLineWrapMarks(wxDC& dc, const wxRect& rectUpdate)
{
wxASSERT_MSG( WrapLines() && WData().m_widthMark,
_T("shouldn't be called at all") );
wxRenderer *renderer = GetRenderer();
wxRect rectMark;
rectMark.x = rectUpdate.x;
rectMark.width = rectUpdate.width;
wxCoord yTop = GetClientAreaOrigin().y;
CalcUnscrolledPosition(0, rectUpdate.y - yTop, NULL, &rectMark.y);
wxCoord hLine = GetLineHeight();
rectMark.height = hLine;
wxTextCoord line, rowInLine;
wxCoord yBottom;
CalcUnscrolledPosition(0, rectUpdate.GetBottom() - yTop, NULL, &yBottom);
for ( ; rectMark.y < yBottom; rectMark.y += hLine )
{
if ( !GetLineAndRow(rectMark.y / hLine, &line, &rowInLine) )
{
// we went beyond the end of text
break;
}
// is this row continued on the next one?
if ( !WData().m_linesData[line].IsLastRow(rowInLine) )
{
renderer->DrawLineWrapMark(dc, rectMark);
}
}
}
void wxTextCtrl::DoDraw(wxControlRenderer *renderer)
{
// hide the caret while we're redrawing the window and show it after we are
// done with it
wxCaretSuspend cs(this);
// prepare the DC
wxDC& dc = renderer->GetDC();
dc.SetFont(GetFont());
dc.SetTextForeground(GetForegroundColour());
// get the intersection of the update region with the text area: note that
// the update region is in window coords and text area is in the client
// ones, so it must be shifted before computing intersection
wxRegion rgnUpdate = GetUpdateRegion();
wxRect rectTextArea = GetRealTextArea();
wxPoint pt = GetClientAreaOrigin();
wxRect rectTextAreaAdjusted = rectTextArea;
rectTextAreaAdjusted.x += pt.x;
rectTextAreaAdjusted.y += pt.y;
rgnUpdate.Intersect(rectTextAreaAdjusted);
// even though the drawing is already clipped to the update region, we must
// explicitly clip it to the rect we will use as otherwise parts of letters
// might be drawn outside of it (if even a small part of a charater is
// inside, HitTest() will return its column and DrawText() can't draw only
// the part of the character, of course)
#ifdef __WXMSW__
// FIXME: is this really a bug in wxMSW?
rectTextArea.width--;
#endif // __WXMSW__
dc.SetClippingRegion(rectTextArea);
// adjust for scrolling
DoPrepareDC(dc);
// and now refresh the invalidated parts of the window
wxRegionIterator iter(rgnUpdate);
for ( ; iter.HaveRects(); iter++ )
{
wxRect r = iter.GetRect();
// this is a workaround for wxGTK::wxRegion bug
#ifdef __WXGTK__
if ( !r.width || !r.height )
{
// ignore invalid rect
continue;
}
#endif // __WXGTK__
DoDrawTextInRect(dc, r);
}
// now redraw the line wrap marks (if we draw them)
if ( WrapLines() && WData().m_widthMark )
{
// this is the rect inside which line wrap marks are drawn
wxRect rectMarks;
rectMarks.x = rectTextAreaAdjusted.GetRight() + 1;
rectMarks.y = rectTextAreaAdjusted.y;
rectMarks.width = WData().m_widthMark;
rectMarks.height = rectTextAreaAdjusted.height;
rgnUpdate = GetUpdateRegion();
rgnUpdate.Intersect(rectMarks);
wxRect rectUpdate = rgnUpdate.GetBox();
if ( rectUpdate.width && rectUpdate.height )
{
// the marks are outside previously set clipping region
dc.DestroyClippingRegion();
DoDrawLineWrapMarks(dc, rectUpdate);
}
}
// show caret first time only: we must show it after drawing the text or
// the display can be corrupted when it's hidden
if ( !m_hasCaret && GetCaret() )
{
ShowCaret();
m_hasCaret = TRUE;
}
}
// ----------------------------------------------------------------------------
// caret
// ----------------------------------------------------------------------------
bool wxTextCtrl::SetFont(const wxFont& font)
{
if ( !wxControl::SetFont(font) )
return FALSE;
// and refresh everything, of course
InitInsertionPoint();
ClearSelection();
// update geometry parameters
UpdateTextRect();
RecalcFontMetrics();
if ( !IsSingleLine() )
{
UpdateScrollbars();
RecalcMaxWidth();
}
// recreate it, in fact
CreateCaret();
Refresh();
return TRUE;
}
bool wxTextCtrl::Enable(bool enable)
{
if ( !wxTextCtrlBase::Enable(enable) )
return FALSE;
ShowCaret(enable);
return TRUE;
}
void wxTextCtrl::CreateCaret()
{
wxCaret *caret;
if ( IsEditable() )
{
// FIXME use renderer
caret = new wxCaret(this, 1, GetLineHeight());
#ifndef __WXMSW__
caret->SetBlinkTime(0);
#endif // __WXMSW__
}
else
{
// read only controls don't have the caret
caret = (wxCaret *)NULL;
}
// SetCaret() will delete the old caret if any
SetCaret(caret);
}
void wxTextCtrl::ShowCaret(bool show)
{
wxCaret *caret = GetCaret();
if ( caret )
{
// (re)position caret correctly
caret->Move(GetCaretPosition());
// and show it there
caret->Show(show);
}
}
// ----------------------------------------------------------------------------
// vertical scrolling (multiline only)
// ----------------------------------------------------------------------------
size_t wxTextCtrl::GetLinesPerPage() const
{
if ( IsSingleLine() )
return 1;
return GetRealTextArea().height / GetLineHeight();
}
wxTextPos wxTextCtrl::GetPositionAbove()
{
wxCHECK_MSG( !IsSingleLine(), INVALID_POS_VALUE,
_T("can't move cursor vertically in a single line control") );
// move the cursor up by one ROW not by one LINE: this means that
// we should really use HitTest() and not just go to the same
// position in the previous line
wxPoint pt = GetCaretPosition() - m_rectText.GetPosition();
if ( MData().m_xCaret == -1 )
{
// remember the initial cursor abscissa
MData().m_xCaret = pt.x;
}
else
{
// use the remembered abscissa
pt.x = MData().m_xCaret;
}
CalcUnscrolledPosition(pt.x, pt.y, &pt.x, &pt.y);
pt.y -= GetLineHeight();
wxTextCoord col, row;
if ( HitTestLogical(pt, &col, &row) == wxTE_HT_BEFORE )
{
// can't move further
return INVALID_POS_VALUE;
}
return XYToPosition(col, row);
}
wxTextPos wxTextCtrl::GetPositionBelow()
{
wxCHECK_MSG( !IsSingleLine(), INVALID_POS_VALUE,
_T("can't move cursor vertically in a single line control") );
// see comments for wxACTION_TEXT_UP
wxPoint pt = GetCaretPosition() - m_rectText.GetPosition();
if ( MData().m_xCaret == -1 )
{
// remember the initial cursor abscissa
MData().m_xCaret = pt.x;
}
else
{
// use the remembered abscissa
pt.x = MData().m_xCaret;
}
CalcUnscrolledPosition(pt.x, pt.y, &pt.x, &pt.y);
pt.y += GetLineHeight();
wxTextCoord col, row;
if ( HitTestLogical(pt, &col, &row) == wxTE_HT_BELOW )
{
// can't go further down
return INVALID_POS_VALUE;
}
// note that wxTE_HT_BEYOND is ok: it happens when we go down
// from a longer line to a shorter one, for example (OTOH
// wxTE_HT_BEFORE can never happen)
return XYToPosition(col, row);
}
// ----------------------------------------------------------------------------
// input
// ----------------------------------------------------------------------------
bool wxTextCtrl::PerformAction(const wxControlAction& actionOrig,
long numArg,
const wxString& strArg)
{
// has the text changed as result of this action?
bool textChanged = FALSE;
// the remembered cursor abscissa for multiline text controls is usually
// reset after each user action but for ones which do use it (UP and DOWN
// for example) we shouldn't do it - as indicated by this flag
bool rememberAbscissa = FALSE;
// the command this action corresponds to or NULL if this action doesn't
// change text at all or can't be undone
wxTextCtrlCommand *command = (wxTextCtrlCommand *)NULL;
wxString action;
bool del = FALSE,
sel = FALSE;
if ( actionOrig.StartsWith(wxACTION_TEXT_PREFIX_DEL, &action) )
{
if ( IsEditable() )
del = TRUE;
}
else if ( actionOrig.StartsWith(wxACTION_TEXT_PREFIX_SEL, &action) )
{
sel = TRUE;
}
else // not selection nor delete action
{
action = actionOrig;
}
// set newPos to -2 as it can't become equal to it in the assignments below
// (but it can become -1)
wxTextPos newPos = INVALID_POS_VALUE;
if ( action == wxACTION_TEXT_HOME )
{
newPos = m_curPos - m_curCol;
}
else if ( action == wxACTION_TEXT_END )
{
newPos = m_curPos + GetLineLength(m_curRow) - m_curCol;
}
else if ( (action == wxACTION_TEXT_GOTO) ||
(action == wxACTION_TEXT_FIRST) ||
(action == wxACTION_TEXT_LAST) )
{
if ( action == wxACTION_TEXT_FIRST )
numArg = 0;
else if ( action == wxACTION_TEXT_LAST )
numArg = GetLastPosition();
//else: numArg already contains the position
newPos = numArg;
}
else if ( action == wxACTION_TEXT_UP )
{
if ( !IsSingleLine() )
{
newPos = GetPositionAbove();
if ( newPos != INVALID_POS_VALUE )
{
// remember where the cursor original had been
rememberAbscissa = TRUE;
}
}
}
else if ( action == wxACTION_TEXT_DOWN )
{
if ( !IsSingleLine() )
{
newPos = GetPositionBelow();
if ( newPos != INVALID_POS_VALUE )
{
// remember where the cursor original had been
rememberAbscissa = TRUE;
}
}
}
else if ( action == wxACTION_TEXT_LEFT )
{
newPos = m_curPos - 1;
}
else if ( action == wxACTION_TEXT_WORD_LEFT )
{
newPos = GetWordStart();
}
else if ( action == wxACTION_TEXT_RIGHT )
{
newPos = m_curPos + 1;
}
else if ( action == wxACTION_TEXT_WORD_RIGHT )
{
newPos = GetWordEnd();
}
else if ( action == wxACTION_TEXT_INSERT )
{
if ( IsEditable() && !strArg.empty() )
{
// inserting text can be undone
command = new wxTextCtrlInsertCommand(strArg);
textChanged = TRUE;
}
}
else if ( (action == wxACTION_TEXT_PAGE_UP) ||
(action == wxACTION_TEXT_PAGE_DOWN) )
{
if ( !IsSingleLine() )
{
size_t count = GetLinesPerPage();
if ( count > PAGE_OVERLAP_IN_LINES )
{
// pages should overlap slightly to allow the reader to keep
// orientation in the text
count -= PAGE_OVERLAP_IN_LINES;
}
// remember where the cursor original had been
rememberAbscissa = TRUE;
bool goUp = action == wxACTION_TEXT_PAGE_UP;
for ( size_t line = 0; line < count; line++ )
{
wxTextPos pos = goUp ? GetPositionAbove() : GetPositionBelow();
if ( pos == INVALID_POS_VALUE )
{
// can't move further
break;
}
MoveInsertionPoint(pos);
newPos = pos;
}
// we implement the Unix scrolling model here: cursor will always
// be on the first line after Page Down and on the last one after
// Page Up
//
// Windows programs usually keep the cursor line offset constant
// but do we really need it?
wxCoord y;
if ( goUp )
{
// find the line such that when it is the first one, the
// current position is in the last line
wxTextPos pos = 0;
for ( size_t line = 0; line < count; line++ )
{
pos = GetPositionAbove();
if ( pos == INVALID_POS_VALUE )
break;
MoveInsertionPoint(pos);
}
MoveInsertionPoint(newPos);
PositionToLogicalXY(pos, NULL, &y);
}
else // scrolled down
{
PositionToLogicalXY(newPos, NULL, &y);
}
// scroll vertically only
Scroll(-1, y);
}
}
else if ( action == wxACTION_TEXT_SEL_WORD )
{
SetSelection(GetWordStart(), GetWordEnd());
}
else if ( action == wxACTION_TEXT_ANCHOR_SEL )
{
newPos = numArg;
}
else if ( action == wxACTION_TEXT_EXTEND_SEL )
{
SetSelection(m_selAnchor, numArg);
}
else if ( action == wxACTION_TEXT_COPY )
{
Copy();
}
else if ( action == wxACTION_TEXT_CUT )
{
if ( IsEditable() )
Cut();
}
else if ( action == wxACTION_TEXT_PASTE )
{
if ( IsEditable() )
Paste();
}
else if ( action == wxACTION_TEXT_UNDO )
{
if ( CanUndo() )
Undo();
}
else if ( action == wxACTION_TEXT_REDO )
{
if ( CanRedo() )
Redo();
}
else
{
return wxControl::PerformAction(action, numArg, strArg);
}
if ( newPos != INVALID_POS_VALUE )
{
// bring the new position into the range
if ( newPos < 0 )
newPos = 0;
wxTextPos posLast = GetLastPosition();
if ( newPos > posLast )
newPos = posLast;
if ( del )
{
// if we have the selection, remove just it
wxTextPos from, to;
if ( HasSelection() )
{
from = m_selStart;
to = m_selEnd;
}
else
{
// otherwise delete everything between current position and
// the new one
if ( m_curPos != newPos )
{
from = m_curPos;
to = newPos;
}
else // nothing to delete
{
// prevent test below from working
from = INVALID_POS_VALUE;
// and this is just to silent the compiler warning
to = 0;
}
}
if ( from != INVALID_POS_VALUE )
{
command = new wxTextCtrlRemoveCommand(from, to);
}
}
else // cursor movement command
{
// just go there
DoSetInsertionPoint(newPos);
if ( sel )
{
SetSelection(m_selAnchor, m_curPos);
}
else // simple movement
{
// clear the existing selection
ClearSelection();
}
}
if ( !rememberAbscissa && !IsSingleLine() )
{
MData().m_xCaret = -1;
}
}
if ( command )
{
// execute and remember it to be able to undo it later
m_cmdProcessor->Submit(command);
// undoable commands always change text
textChanged = TRUE;
}
else // no undoable command
{
// m_cmdProcessor->StopCompressing()
}
if ( textChanged )
{
wxASSERT_MSG( IsEditable(), _T("non editable control changed?") );
wxCommandEvent event(wxEVT_COMMAND_TEXT_UPDATED, GetId());
InitCommandEvent(event);
event.SetString(GetValue());
GetEventHandler()->ProcessEvent(event);
// as the text changed...
m_isModified = TRUE;
}
return TRUE;
}
void wxTextCtrl::OnChar(wxKeyEvent& event)
{
// only process the key events from "simple keys" here
if ( !event.HasModifiers() )
{
int keycode = event.GetKeyCode();
if ( keycode == WXK_RETURN )
{
if ( IsSingleLine() || (GetWindowStyle() & wxTE_PROCESS_ENTER) )
{
wxCommandEvent event(wxEVT_COMMAND_TEXT_ENTER, GetId());
InitCommandEvent(event);
event.SetString(GetValue());
GetEventHandler()->ProcessEvent(event);
}
else // interpret <Enter> normally: insert new line
{
PerformAction(wxACTION_TEXT_INSERT, -1, _T('\n'));
}
}
else if ( keycode < 255 && isprint(keycode) )
{
PerformAction(wxACTION_TEXT_INSERT, -1, (wxChar)keycode);
// skip event.Skip() below
return;
}
}
#ifdef __WXDEBUG__
// Ctrl-R refreshes the control in debug mode
else if ( event.ControlDown() && event.GetKeyCode() == 'r' )
Refresh();
#endif // __WXDEBUG__
event.Skip();
}
// ----------------------------------------------------------------------------
// wxStdTextCtrlInputHandler
// ----------------------------------------------------------------------------
wxStdTextCtrlInputHandler::wxStdTextCtrlInputHandler(wxInputHandler *inphand)
: wxStdInputHandler(inphand)
{
m_winCapture = (wxTextCtrl *)NULL;
}
/* static */
wxTextPos wxStdTextCtrlInputHandler::HitTest(const wxTextCtrl *text,
const wxPoint& pt)
{
wxTextCoord col, row;
wxTextCtrlHitTestResult ht = text->HitTest(pt, &col, &row);
wxTextPos pos = text->XYToPosition(col, row);
// if the point is after the last column we must adjust the position to be
// the last position in the line (unless it is already the last)
if ( (ht == wxTE_HT_BEYOND) && (pos < text->GetLastPosition()) )
{
pos++;
}
return pos;
}
bool wxStdTextCtrlInputHandler::HandleKey(wxControl *control,
const wxKeyEvent& event,
bool pressed)
{
// we're only interested in key presses
if ( !pressed )
return FALSE;
int keycode = event.GetKeyCode();
wxControlAction action;
wxString str;
bool ctrlDown = event.ControlDown(),
shiftDown = event.ShiftDown();
if ( shiftDown )
{
action = wxACTION_TEXT_PREFIX_SEL;
}
// the only key combination with Alt we recognize is Alt-Bksp for undo, so
// treat it first separately
if ( event.AltDown() )
{
if ( keycode == WXK_BACK && !ctrlDown && !shiftDown )
action = wxACTION_TEXT_UNDO;
}
else switch ( keycode )
{
// cursor movement
case WXK_HOME:
action << (ctrlDown ? wxACTION_TEXT_FIRST
: wxACTION_TEXT_HOME);
break;
case WXK_END:
action << (ctrlDown ? wxACTION_TEXT_LAST
: wxACTION_TEXT_END);
break;
case WXK_UP:
if ( !ctrlDown )
action << wxACTION_TEXT_UP;
break;
case WXK_DOWN:
if ( !ctrlDown )
action << wxACTION_TEXT_DOWN;
break;
case WXK_LEFT:
action << (ctrlDown ? wxACTION_TEXT_WORD_LEFT
: wxACTION_TEXT_LEFT);
break;
case WXK_RIGHT:
action << (ctrlDown ? wxACTION_TEXT_WORD_RIGHT
: wxACTION_TEXT_RIGHT);
break;
case WXK_PAGEDOWN:
case WXK_NEXT:
// we don't map Ctrl-PgUp/Dn to anything special - what should it
// to? for now, it's the same as without control
action << wxACTION_TEXT_PAGE_DOWN;
break;
case WXK_PAGEUP:
case WXK_PRIOR:
action << wxACTION_TEXT_PAGE_UP;
break;
// delete
case WXK_DELETE:
if ( !ctrlDown )
action << wxACTION_TEXT_PREFIX_DEL << wxACTION_TEXT_RIGHT;
break;
case WXK_BACK:
if ( !ctrlDown )
action << wxACTION_TEXT_PREFIX_DEL << wxACTION_TEXT_LEFT;
break;
// something else
default:
// reset the action as it could be already set to one of the
// prefixes
action = wxACTION_NONE;
if ( ctrlDown )
{
switch ( keycode )
{
case 'A':
action = wxACTION_TEXT_REDO;
break;
case 'C':
action = wxACTION_TEXT_COPY;
break;
case 'V':
action = wxACTION_TEXT_PASTE;
break;
case 'X':
action = wxACTION_TEXT_CUT;
break;
case 'Z':
action = wxACTION_TEXT_UNDO;
break;
}
}
}
if ( (action != wxACTION_NONE) && (action != wxACTION_TEXT_PREFIX_SEL) )
{
control->PerformAction(action, -1, str);
return TRUE;
}
return wxStdInputHandler::HandleKey(control, event, pressed);
}
bool wxStdTextCtrlInputHandler::HandleMouse(wxControl *control,
const wxMouseEvent& event)
{
if ( event.LeftDown() )
{
wxASSERT_MSG( !m_winCapture, _T("left button going down twice?") );
wxTextCtrl *text = wxStaticCast(control, wxTextCtrl);
m_winCapture = text;
m_winCapture->CaptureMouse();
text->HideCaret();
wxTextPos pos = HitTest(text, event.GetPosition());
if ( pos != -1 )
{
text->PerformAction(wxACTION_TEXT_ANCHOR_SEL, pos);
}
}
else if ( event.LeftDClick() )
{
// select the word the cursor is on
control->PerformAction(wxACTION_TEXT_SEL_WORD);
}
else if ( event.LeftUp() )
{
if ( m_winCapture )
{
m_winCapture->ShowCaret();
m_winCapture->ReleaseMouse();
m_winCapture = (wxTextCtrl *)NULL;
}
}
return wxStdInputHandler::HandleMouse(control, event);
}
bool wxStdTextCtrlInputHandler::HandleMouseMove(wxControl *control,
const wxMouseEvent& event)
{
if ( m_winCapture )
{
// track it
wxTextCtrl *text = wxStaticCast(m_winCapture, wxTextCtrl);
wxTextPos pos = HitTest(text, event.GetPosition());
if ( pos != -1 )
{
text->PerformAction(wxACTION_TEXT_EXTEND_SEL, pos);
}
}
return wxStdInputHandler::HandleMouseMove(control, event);
}
bool wxStdTextCtrlInputHandler::HandleFocus(wxControl *control,
const wxFocusEvent& event)
{
wxTextCtrl *text = wxStaticCast(control, wxTextCtrl);
// the selection appearance changes depending on whether we have the focus
text->RefreshSelection();
// never refresh entirely
return FALSE;
}
#endif // wxUSE_TEXTCTRL