Files
wxWidgets/src/univ/textctrl.cpp
2000-11-10 21:14:20 +00:00

3216 lines
88 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: univ/textctrl.cpp
// Purpose: wxTextCtrl
// Author: Vadim Zeitlin
// Modified by:
// Created: 15.09.00
// RCS-ID: $Id$
// Copyright: (c) 2000 Vadim Zeitlin
// Licence: wxWindows license
/////////////////////////////////////////////////////////////////////////////
/*
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/tokenzr.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 (very inefficient!)
#define WXDEBUG_TEXT_REPLACE
#ifndef __WXDEBUG__
#undef WXDEBUG_TEXT
#undef WXDEBUG_TEXT_REPLACE
#endif
// ----------------------------------------------------------------------------
// private functions
// ----------------------------------------------------------------------------
// exchange two positions so that from is always less than or equal to to
static inline void OrderPositions(long& from, long& to)
{
if ( from > to )
{
long tmp = from;
from = to;
to = tmp;
}
}
// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------
// names of text ctrl commands
#define wxTEXT_COMMAND_INSERT _T("insert")
#define wxTEXT_COMMAND_REMOVE _T("remove")
// ----------------------------------------------------------------------------
// 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
long m_from;
};
// remove text command
class wxTextCtrlRemoveCommand : public wxTextCtrlCommand
{
public:
wxTextCtrlRemoveCommand(long from, long 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
long 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(OnChar)
EVT_SIZE(OnSize)
EVT_IDLE(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_colStart = 0;
m_ofsHorz = 0;
m_colLastVisible = -1;
m_posLastVisible = -1;
m_posLast =
m_curPos =
m_curCol =
m_curRow = 0;
m_scrollRangeX =
m_scrollRangeY = 0;
m_updateScrollbarX =
m_updateScrollbarY = FALSE;
m_widthMax = -1;
m_lineLongest = 0;
// init wxScrollHelper
SetWindow(this);
// init the undo manager
m_cmdProcessor = new wxTextCtrlCommandProcessor(this);
}
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;
}
// TODO: support wxTE_NO_VSCROLL (?)
}
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
m_lines.Add(wxEmptyString);
// 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") );
}
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;
return TRUE;
}
wxTextCtrl::~wxTextCtrl()
{
delete m_cmdProcessor;
}
// ----------------------------------------------------------------------------
// set/get the value
// ----------------------------------------------------------------------------
void wxTextCtrl::SetValue(const wxString& value)
{
if ( IsSingleLine() && (value == GetSLValue()) )
{
// nothing changed
return;
}
Replace(0, GetLastPosition(), value);
}
wxString wxTextCtrl::GetValue() const
{
wxString value;
if ( IsSingleLine() )
{
value = GetSLValue();
}
else // multiline
{
size_t count = m_lines.GetCount();
for ( size_t n = 0; n < count; n++ )
{
if ( n )
value += _T('\n'); // from preceding line
value += m_lines[n];
}
}
return value;
}
void wxTextCtrl::Clear()
{
SetValue(_T(""));
}
void wxTextCtrl::Replace(long from, long to, const wxString& text)
{
long 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
// the first attempt at implementing Replace() - it was meant to be more
// efficient than the current code but it also is much more complicated
// and, worse, still has a few bugs and so as I'm not even sure any more
// that it is really more efficient, I'm dropping it in favour of much
// simpler code below
#if 0
/*
The algorithm of Replace():
1. change the line where replacement starts
a) keep the text in the beginning of it unchanged
b) replace the middle (if lineEnd == lineStart) or everything to the
end with the first line of replacement text
2. delete all lines between lineStart and lineEnd (excluding)
3. insert the lines of the replacement text
4. change the line where replacement ends:
a) remove the part which is in replacement range
b) insert the last line of replacement text
c) insert the end of the first line if lineEnd == lineStart
d) keep the end unchanged
In the code below the steps 2 and 3 are merged and are done in parallel for
efficiency reasons (it is better to change lines in place rather than
remove/insert them from a potentially huge array)
*/
// break the replacement text into lines
wxArrayString lines = wxStringTokenize(text, _T("\n"),
wxTOKEN_RET_EMPTY_ALL);
size_t nReplaceCount = lines.GetCount();
// first deal with the starting line: (1) take the part before the text
// being replaced
wxString lineFirstOrig = GetLineText(lineStart);
wxString lineFirst(lineFirstOrig, (size_t)colStart);
// remember the start of the update region for later use
wxCoord startNewText = GetTextWidth(lineFirst);
// (2) add the text which replaces this part
if ( !lines.IsEmpty() )
{
lineFirst += lines[0u];
}
// (3) and add the text which is left if we replace text only in this line
wxString lineFirstEnd;
if ( lineEnd == lineStart )
{
wxASSERT_MSG((size_t)colEnd <= lineFirstOrig.length(), _T("logic bug"));
lineFirstEnd = lineFirstOrig.c_str() + (size_t)colEnd;
if ( nReplaceCount == 1 )
{
// we keep everything on this line
lineFirst += lineFirstEnd;
}
else
{
lineEnd++;
}
}
// (4) refresh the part of the line which changed
// we usually refresh till the end of line except of the most common case
// when some text is appended to the end of it in which case we refresh
// just the newly appended text
wxCoord widthNewText;
if ( (lineEnd == lineStart) &&
((size_t)colStart == lineFirstOrig.length()) )
{
// text appended, not replaced, so refresh only the new text
widthNewText = GetTextWidth(lines[0u]);
}
else
{
// refresh till the end of line as all its tail changed
// OPT: should only refresh the part occupied by the text, but OTOH
// the line won't be really repainted beyond it anyhow due to the
// checks in DoDrawTextInRect(), so is it really worth it?
widthNewText = 0;
}
RefreshPixelRange(lineStart, startNewText, widthNewText);
// (5) modify the line
if ( IsSingleLine() )
{
SetSLValue(lineFirst);
// force m_colLastVisible update
m_colLastVisible = -1;
// consistency check: when replacing text in a single line control, we
// shouldn't have more than one line
wxASSERT_MSG(lines.GetCount() <= 1,
_T("can't have more than one line in this wxTextCtrl"));
// update the current position
DoSetInsertionPoint(from + text.length());
// and the selection (do it after setting the cursor to have correct value
// for selection anchor)
ClearSelection();
// nothing more can happen to us, so bail out
return;
}
else // multiline
{
m_lines[lineStart] = lineFirst;
}
// now replace all intermediate lines entirely
bool refreshAllBelow = FALSE;
// (1) modify all lines which are really repaced
size_t nReplaceLine = 1;
for ( long line = lineStart + 1; line < lineEnd; line++ )
{
if ( nReplaceLine < nReplaceCount )
{
// replace line
m_lines[line] = lines[nReplaceLine++];
}
else // no more replacement text - remove line(s)
{
// (2) remove all lines for which there is no more replacement
// text (this is slightly more efficient than continuing to
// run the loop)
// adjust the index by the number of lines removed
lineEnd -= lineEnd - line;
// and remove them
while ( line < lineEnd )
{
m_lines.RemoveAt(line++);
}
// the lines below will scroll up
refreshAllBelow = TRUE;
}
}
// all the rest is only necessary if there is more than one line of
// replacement text
if ( nReplaceCount > 1 )
{
// (3) if there are still lines left in the replacement text, insert
// them before modifying the last line
if ( nReplaceLine < nReplaceCount - 1 )
{
// the lines below will scroll down
refreshAllBelow = TRUE;
while ( nReplaceLine < nReplaceCount - 1 ) // OPT: insert all at once?
{
// insert line and adjust for index change by incrementing lineEnd
m_lines.Insert(lines[nReplaceLine++], lineEnd++);
}
}
// (4) refresh the lines: if we had replaced exactly the same number of
// lines that we had before, we can just refresh these lines,
// otherwise the lines below will change as well, so we have to
// refresh them too
if ( refreshAllBelow || (lineStart < lineEnd - 1) )
{
RefreshLineRange(lineStart + 1, refreshAllBelow ? -1 : lineEnd - 1);
}
// now deal with the last line: (1) replace its beginning with the end
// of the replacement text
wxString lineLast;
if ( nReplaceLine < nReplaceCount )
{
wxASSERT_MSG(nReplaceLine == nReplaceCount - 1, _T("logic error"));
lineLast = lines[nReplaceLine];
}
// (2) add the text which was at the end of first line if we replaced
// its middle with multiline text
if ( lineEnd == lineStart )
{
lineLast += lineFirstEnd;
}
// (3) add the tail of the old last line if anything is left
if ( (size_t)lineEnd < m_lines.GetCount() )
{
wxString lineLastOrig = GetLineText(lineEnd);
if ( (size_t)colEnd < lineLastOrig.length() )
{
lineLast += lineLastOrig.c_str() + (size_t)colEnd;
}
m_lines[lineEnd] = lineLast;
}
else // the number of lines increased, just append the new one
{
m_lines.Add(lineLast);
}
// (4) always refresh the last line entirely if it hadn't been already
// refreshed above
if ( !refreshAllBelow )
{
RefreshPixelRange(lineEnd, 0, 0); // entire line
}
}
//else: only one line of replacement text
#else // 1 (new replacement code)
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;
}
// OPT: is the following really ok? not sure any more now at 2 am...
// 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 m_colLastVisible update
m_colLastVisible = -1;
// repaint
RefreshPixelRange(0, startNewText, widthNewText);
}
//OPT: special case for replacements inside single line?
else // multiline
{
/*
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.
*/
// (1) join lines
wxString textOrig;
long line;
for ( line = lineStart; line <= lineEnd; line++ )
{
if ( line > lineStart )
{
// from the previous line
textOrig += _T('\n');
}
textOrig += m_lines[line];
}
// we need to append the '\n' for the last line unless there is no
// following line
size_t countOld = m_lines.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 == m_lines[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
// (3a) all empty tokens should be counted as replacing with "foo" and
// with "foo\n" should have different effects
wxArrayString lines = wxStringTokenize(textNew, _T("\n"),
wxTOKEN_RET_EMPTY_ALL);
if ( lines.IsEmpty() )
{
lines.Add(wxEmptyString);
}
// (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
m_lines[line] = lines[nReplaceLine];
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 ( long lineDel = lineEnd; lineDel >= line; lineDel-- )
{
if ( lineDel == m_lineLongest )
{
// we will need to recalc the max line width
deletedLongestLine = TRUE;
}
m_lines.RemoveAt(lineDel);
}
if ( deletedLongestLine )
{
RecalcMaxWidth();
}
// update line to exit the loop
line = lineEnd + 1;
}
}
// (4c) insert the new lines
while ( nReplaceLine < nReplaceCount )
{
m_lines.Insert(lines[nReplaceLine++], ++lineEnd);
UpdateMaxWidth(lineEnd);
}
// (5) now refresh the changed area
RefreshPixelRange(lineStart, startNewText, widthNewText);
if ( m_lines.GetCount() == countOld )
{
// number of lines didn't change, refresh the updated lines and the
// last one
if ( lineStart < lineEnd )
RefreshLineRange(lineStart + 1, lineEnd);
}
else
{
// number of lines did change, we need to refresh everything below
// the start line
RefreshLineRange(lineStart + 1,
wxMax(m_lines.GetCount(), countOld) - 1);
// the vert scrollbar might [dis]appear
m_updateScrollbarY = TRUE;
}
#endif // 0/1
// update the (cached) last position
m_posLast += text.length() - to + from;
}
#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 (do it after setting the cursor to have correct value
// for selection anchor)
ClearSelection();
}
void wxTextCtrl::Remove(long from, long 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(long 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);
}
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::DoSetInsertionPoint(long 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);
ShowPosition(m_curPos);
}
void wxTextCtrl::SetInsertionPointEnd()
{
SetInsertionPoint(GetLastPosition());
}
long wxTextCtrl::GetInsertionPoint() const
{
return m_curPos;
}
long wxTextCtrl::GetLastPosition() const
{
long pos;
if ( IsSingleLine() )
{
pos = GetSLValue().length();
}
else // multiline
{
#ifdef WXDEBUG_TEXT
pos = 0;
size_t nLineCount = m_lines.GetCount();
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 += m_lines[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(long* from, long* to) const
{
if ( from )
*from = m_selStart;
if ( to )
*to = m_selEnd;
}
wxString wxTextCtrl::GetSelectionText() const
{
wxString sel;
if ( HasSelection() )
{
if ( IsSingleLine() )
{
sel = GetSLValue().Mid(m_selStart, m_selEnd - m_selStart);
}
else // multiline
{
long 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 = m_lines[lineStart].Mid(colStart, colEnd - colStart);
}
else // sel on multiple lines
{
// take the end of the first line
sel = m_lines[lineStart].c_str() + colStart;
sel += _T('\n');
// all intermediate ones
for ( long line = lineStart + 1; line < lineEnd; line++ )
{
sel << m_lines[line] << _T('\n');
}
// and the start of the last one
sel += m_lines[lineEnd].Left(colEnd);
}
}
}
return sel;
}
void wxTextCtrl::SetSelection(long from, long to)
{
if ( from == -1 || to == from )
{
ClearSelection();
}
else // valid sel range
{
if ( from >= to )
{
long tmp = from;
from = to;
to = tmp;
}
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
long 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);
}
}
//else: nothing to do
}
}
void wxTextCtrl::ClearSelection()
{
if ( HasSelection() )
{
// we need to use temp vars as RefreshTextRange() may call DoDraw()
// directly (see above as well)
long 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(long line, int *start, int *end) const
{
if ( start )
*start = -1;
if ( end )
*end = -1;
if ( !HasSelection() )
{
// no selection at all, hence no selection in this line
return FALSE;
}
long lineStart, colStart;
PositionToXY(m_selStart, &colStart, &lineStart);
if ( lineStart > line )
{
// this line is entirely above the selection
return FALSE;
}
long 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(long line) const
{
if ( IsSingleLine() )
{
wxASSERT_MSG( line == 0, _T("invalid GetLineLength() parameter") );
return GetSLValue().length();
}
else // multiline
{
wxCHECK_MSG( (size_t)line < m_lines.GetCount(), -1,
_T("line index out of range") );
return m_lines[line].length();
}
}
wxString wxTextCtrl::GetLineText(long line) const
{
if ( IsSingleLine() )
{
wxASSERT_MSG( line == 0, _T("invalid GetLineLength() parameter") );
return m_value;
}
else // multiline
{
wxCHECK_MSG( (size_t)line < m_lines.GetCount(), _T(""),
_T("line index out of range") );
return m_lines[line];
}
}
int wxTextCtrl::GetNumberOfLines() const
{
// there is always 1 line, even if the text is empty
return IsSingleLine() ? 1 : m_lines.GetCount();
}
long wxTextCtrl::XYToPosition(long x, long 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 >= m_lines.GetCount() )
{
// this position is below the text
return GetLastPosition();
}
long 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 += m_lines[nLine].length() + 1;
}
// take into account also the position in line
if ( (size_t)x > m_lines[y].length() )
{
// don't return position in the next line
x = m_lines[y].length();
}
return pos + x;
}
}
bool wxTextCtrl::PositionToXY(long pos, long *x, long *y) const
{
if ( IsSingleLine() )
{
if ( (size_t)pos > GetSLValue().length() )
return FALSE;
if ( x )
*x = pos;
if ( y )
*y = 0;
return TRUE;
}
else // multiline
{
long posCur = 0;
size_t nLineCount = m_lines.GetCount();
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
long posNew = posCur + m_lines[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;
}
}
// pos may be -1 to show the current position
void wxTextCtrl::ShowPosition(long pos)
{
HideCaret();
if ( IsSingleLine() )
{
ShowHorzPosition(GetCaretPosition(pos));
}
else if ( m_scrollRangeX || m_scrollRangeY ) // multiline with scrollbars
{
int xStart, yStart;
GetViewStart(&xStart, &yStart);
long row, col;
if ( (pos == -1) || (pos == m_curPos) )
{
row = m_curRow;
col = m_curCol;
}
else
{
// TODO
wxFAIL_MSG(_T("not implemented for multiline"));
return;
}
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 ( m_scrollRangeY )
{
if ( row < yStart )
{
Scroll(0, row);
}
else // we are currently in or below the view area
{
int yEnd = yStart + rectText.height / GetCharHeight() - 1;
if ( yEnd < row )
{
// scroll down: the current item should appear at the
// bottom of the view
Scroll(0, row - (yEnd - yStart));
}
}
}
// scroll the position horizontally into view
if ( 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 posLeft and posRight be entirely inside
// the view (i.e. the current character)
wxString line = GetLineText(row);
wxCoord posLeft = GetTextWidth(line.Left(col));
// make xStart the first visible pixel (and not position)
int wChar = GetCharWidth();
xStart *= GetCharWidth();
if ( posLeft < xStart )
{
Scroll(posLeft / wChar, row);
}
else // maybe we're beyond the right border of the view?
{
wxCoord posRight = posLeft + GetTextWidth(line[(size_t)col]);
if ( posRight > xStart + rectText.width )
{
Scroll(posRight / 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);
}
long 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;
}
long 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
long 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)
{
text->SetInsertionPoint(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
{
wxCoord w, h;
GetTextExtent(GetTextToShow(GetLineText(0)), &w, &h);
int wChar = GetCharWidth(),
hChar = GetCharHeight();
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()
{
m_rectText = GetRenderer()->
GetTextClientArea(this,
wxRect(wxPoint(0, 0), GetClientSize()));
// only scroll this rect when the window is scrolled
SetTargetRect(GetRealTextArea());
UpdateLastVisible();
}
void wxTextCtrl::UpdateLastVisible()
{
// this method is only used for horizontal "scrollbarless" scrolling which
// is used only with single line controls
if ( !IsSingleLine() )
return;
// OPT: estimate the correct value first, just adjust it later
wxString text;
wxCoord w, wOld;
w =
wOld = 0;
m_colLastVisible = m_colStart;
const wxChar *pc = m_value.c_str() + (size_t)m_colStart;
for ( ; *pc; pc++ )
{
text += *pc;
wOld = w;
w = GetTextWidth(text);
if ( w > m_rectText.width )
{
// this char is too much
break;
}
m_colLastVisible++;
}
m_posLastVisible = wOld;
wxLogTrace(_T("text"), _T("Last visible column/position is %d/%ld"),
m_colLastVisible, m_posLastVisible);
}
void wxTextCtrl::OnSize(wxSizeEvent& event)
{
UpdateTextRect();
m_updateScrollbarX =
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
{
// 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 = m_rectText;
int hLine = GetCharHeight();
rectText.height = (m_rectText.height / hLine) * hLine;
return rectText;
}
wxTextCtrlHitTestResult wxTextCtrl::HitTestLine(const wxString& line,
wxCoord x,
long *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
col = line.length();
res = wxTE_HT_AFTER;
}
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
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 -1 if we must move left, +1 to move right and 0 when
// we're exactly on the character we need
int matchDir = 0;
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 == 1 )
{
// we were going to the right and, finally, moved beyond
// the original position - stop on the previous one
col--;
break;
}
if ( matchDir == 0 )
{
// we just started iterating, now we know that we should
// move to the left
matchDir = -1;
}
//else: we are still to the right of the target, continue
}
else // width < x
{
// invert the logic above
if ( matchDir == -1 )
{
// with the exception that we don't need to backtrack here
break;
}
if ( matchDir == 0 )
{
// go to the right
matchDir = 1;
}
}
// this is not supposed to happen
wxASSERT_MSG( matchDir, _T("logic error in wxTextCtrl::HitTest") );
if ( matchDir == 1 )
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,
long *colOut, long *rowOut) const
{
// is the point in the text area or to the right or below it?
wxTextCtrlHitTestResult res = wxTE_HT_ON_TEXT;
// we accept the window coords and adjust for both the client and text area
// offsets ourselves
int x, y;
wxPoint pt = GetClientAreaOrigin();
pt += m_rectText.GetPosition();
CalcUnscrolledPosition(pos.x - pt.x, pos.y - pt.y, &x, &y);
// row calculation is simple as we assume that all lines have the same
// height
int row = y / GetCharHeight();
int rowMax = GetNumberOfLines() - 1;
if ( row > rowMax )
{
// clicking below the text is the same as clicking on the last line
row = rowMax;
res = wxTE_HT_AFTER;
}
else if ( row < 0 )
{
row = 0;
res = wxTE_HT_BEFORE;
}
// now find the position in the line
wxTextCtrlHitTestResult res2 =
HitTestLine(GetTextToShow(GetLineText(row)), x, colOut);
if ( res == wxTE_HT_ON_TEXT )
{
res = res2;
}
if ( rowOut )
*rowOut = row;
return res;
}
// ----------------------------------------------------------------------------
// 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
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 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 m_ofsHorz is always 0 and scrolling is
done as usual for wxScrollWindow.
*/
void wxTextCtrl::ShowHorzPosition(wxCoord pos)
{
// pos is the logical position to show
// m_ofsHorz is the fisrt logical position shown
if ( pos < m_ofsHorz )
{
// scroll backwards
long col;
HitTestLine(GetSLValue(), 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;
}
// m_ofsHorz + width is the last logical position shown
if ( pos > m_ofsHorz + width)
{
// scroll forward
long col;
HitTestLine(GetSLValue(), 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(long 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 m_colStart
wxCoord ofsHorz = GetTextWidth(GetLineText(0).Left(col));
if ( ofsHorz != m_ofsHorz )
{
// what is currently shown?
if ( m_colLastVisible == -1 )
{
UpdateLastVisible();
}
// NB1: to scroll to the right, offset must be negative, hence the
// order of operands
int dx = m_ofsHorz - ofsHorz;
// NB2: we call Refresh() below which results in a call to
// DoDraw(), so we must update m_ofsHorz before calling it
m_ofsHorz = ofsHorz;
m_colStart = col;
// scroll only the rectangle inside which there is the text
if ( dx > 0 )
{
// scrolling to the right: 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();
}
else
{
// when scrolling to the left, we need to scroll the old rectangle
// occupied by the text - then the area where there was no text
// before will be refreshed and redrawn
// but we still need to force updating after scrolling
m_colLastVisible = -1;
}
wxRect rect = m_rectText;
rect.width = m_posLastVisible;
rect = ScrollNoRefresh(dx, 0, &rect);
/*
we need to manually refresh the part which ScrollWindow() doesn't
refresh: 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 + m_posLastVisible;
rect.width = m_rectText.width - m_posLastVisible;
}
else
{
// just extend the rect covering the uncovered area to the edge of
// the text rect
rect.width += m_rectText.width - m_posLastVisible;
}
Refresh(TRUE, &rect);
}
}
void wxTextCtrl::CalcUnscrolledPosition(int x, int y, int *xx, int *yy) const
{
wxScrollHelper::CalcUnscrolledPosition(x + m_ofsHorz, y, xx, yy);
}
void wxTextCtrl::DoPrepareDC(wxDC& dc)
{
// for single line controls we only have to deal with m_ofsHorz and it's
// useless to call base class version as they don't use normal scrolling
if ( m_ofsHorz )
{
// adjust the DC origin if the text is shifted
wxPoint pt = dc.GetDeviceOrigin();
dc.SetDeviceOrigin(pt.x - m_ofsHorz, pt.y);
}
else
{
wxScrollHelper::DoPrepareDC(dc);
}
}
void wxTextCtrl::UpdateMaxWidth(long line)
{
// check if the max width changes after this line was modified
wxCoord widthMaxOld = m_widthMax,
width;
GetTextExtent(GetLineText(line), &width, NULL);
if ( line == m_lineLongest )
{
// this line was the longest one, is it still?
if ( width > m_widthMax )
{
m_widthMax = width;
}
else if ( width < 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 m_widthMax as it might be not calculated yet
if ( width > GetMaxWidth() )
{
m_widthMax = width;
m_lineLongest = line;
}
}
m_updateScrollbarX = m_widthMax != widthMaxOld;
}
wxCoord wxTextCtrl::GetMaxWidth() const
{
if ( 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->m_widthMax = 0;
size_t count = m_lines.GetCount();
for ( size_t n = 0; n < count; n++ )
{
wxCoord width;
dc.GetTextExtent(m_lines[n], &width, NULL);
if ( width > m_widthMax )
{
// remember the width and the line which has it
self->m_widthMax = width;
self->m_lineLongest = n;
}
}
}
wxASSERT_MSG( m_widthMax != -1, _T("should have at least 1 line") );
return m_widthMax;
}
void wxTextCtrl::UpdateScrollbars()
{
wxSize size = GetRealTextArea().GetSize();
// is our height enough to show all items?
int nLines = GetNumberOfLines();
wxCoord lineHeight = GetCharHeight();
bool showScrollbarY = nLines*lineHeight > size.y;
// is our width enough to show the longest line?
wxCoord charWidth, maxWidth;
bool showScrollbarX;
if ( GetWindowStyle() && wxHSCROLL )
{
charWidth = GetCharWidth();
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 ? nLines : 0;
if ( (scrollRangeY != m_scrollRangeY) || (scrollRangeX != m_scrollRangeX) )
{
int x, y;
GetViewStart(&x, &y);
SetScrollbars(charWidth, lineHeight,
scrollRangeX, scrollRangeY,
x, y);
m_scrollRangeX = scrollRangeX;
m_scrollRangeY = scrollRangeY;
// bring the current position in view
ShowPosition(-1);
}
}
void wxTextCtrl::OnIdle(wxIdleEvent& event)
{
// notice that single line text control never has scrollbars
if ( !IsSingleLine() && (m_updateScrollbarX || m_updateScrollbarY) )
{
UpdateScrollbars();
m_updateScrollbarX =
m_updateScrollbarY = FALSE;
}
event.Skip();
}
// ----------------------------------------------------------------------------
// refresh
// ----------------------------------------------------------------------------
void wxTextCtrl::RefreshLineRange(long lineFirst, long lineLast)
{
wxASSERT_MSG( lineFirst <= lineLast, _T("no lines to refresh") );
wxRect rect;
// rect.x is already 0
rect.width = m_rectText.width;
wxCoord h = GetCharHeight();
rect.y = lineFirst*h;
// don't refresh beyond the window boundary
wxCoord bottom = (lineLast + 1)*h;
if ( bottom > m_rectText.height )
bottom = m_rectText.height;
rect.SetBottom(bottom);
RefreshTextRect(rect);
}
void wxTextCtrl::RefreshTextRange(long start, long 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);
long colStart, lineStart;
if ( !PositionToXY(start, &colStart, &lineStart) )
{
// the range is entirely beyond the end of the text, nothing to do
return;
}
long 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 ( long 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
long posStart = line == lineStart ? colStart : 0;
long 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;
}
RefreshColRange(line, posStart, posCount);
}
}
void wxTextCtrl::RefreshColRange(long line, long start, long count)
{
wxString text = GetLineText(line);
RefreshPixelRange(line,
GetTextWidth(text.Left((size_t)start)),
GetTextWidth(text.Mid((size_t)start, (size_t)count)));
}
void wxTextCtrl::RefreshPixelRange(long line, wxCoord start, wxCoord width)
{
wxCoord h = GetCharHeight();
wxRect rect;
rect.x = start;
rect.y = line*h;
if ( width == 0 )
{
// till the end of line
rect.width = GetTotalWidth() - rect.x;
}
else
{
// only part of line
rect.width = width;
}
rect.height = h;
RefreshTextRect(rect);
}
void wxTextCtrl::RefreshTextRect(wxRect& rect)
{
if ( IsSingleLine() )
{
// account for horz scrolling
rect.x -= m_ofsHorz;
}
else // multiline
{
CalcScrolledPosition(rect.x, rect.y, &rect.x, &rect.y);
}
// account for the text area offset
rect.Offset(m_rectText.GetPosition());
wxLogTrace(_T("text"), _T("Refreshing (%d, %d)-(%d, %d)"),
rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
Refresh(TRUE, &rect);
}
// ----------------------------------------------------------------------------
// 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::DrawTextLine(wxDC& dc, const wxRect& rect,
const wxString& text,
long selStart, long selEnd)
{
if ( selStart == -1 )
{
// just draw it as is
dc.DrawText(text, rect.x, rect.y);
}
else // we have selection
{
wxCoord width,
x = rect.x;
// draw the part before selection
wxString s(text, (size_t)selStart);
if ( !s.empty() )
{
dc.DrawText(s, x, rect.y);
dc.GetTextExtent(s, &width, NULL);
x += width;
}
// draw the selection itself
s = wxString(text.c_str() + selStart, text.c_str() + selEnd);
if ( !s.empty() )
{
wxColour colFg = dc.GetTextForeground();
dc.SetTextForeground(wxTHEME_COLOUR(HIGHLIGHT_TEXT));
dc.SetTextBackground(wxTHEME_COLOUR(HIGHLIGHT));
dc.SetBackgroundMode(wxSOLID);
dc.DrawText(s, x, rect.y);
dc.GetTextExtent(s, &width, NULL);
x += width;
dc.SetBackgroundMode(wxTRANSPARENT);
dc.SetTextForeground(colFg);
}
// draw the final part
s = text.c_str() + selEnd;
if ( !s.empty() )
{
dc.DrawText(s, x, rect.y);
}
}
}
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 of lines to refresh
wxPoint pt1 = rectUpdate.GetPosition();
long colStart, lineStart;
(void)HitTest(pt1, &colStart, &lineStart);
wxPoint pt2 = pt1;
pt2.x += rectUpdate.width;
pt2.y += rectUpdate.height;
long colEnd, lineEnd;
(void)HitTest(pt2, &colEnd, &lineEnd);
// pt1 and pt2 will run along the left and right update rect borders
// respectively from top to bottom (NB: they're in device coords)
pt2.y = pt1.y;
// prepare for drawing
wxRect rectText;
rectText.height = GetCharHeight();
rectText.y = m_rectText.y + lineStart*rectText.height;
// do draw the invalidated parts of each line
for ( long line = lineStart;
line <= lineEnd;
line++,
rectText.y += rectText.height,
pt1.y += rectText.height,
pt2.y += rectText.height )
{
// calculate the update rect in text positions for this line
if ( HitTest(pt1, &colStart, NULL) == wxTE_HT_AFTER )
{
// the update rect is beyond the end of line, no need to redraw
// anything
continue;
}
// don't show the columns which are scrolled out to the left
if ( colStart < m_colStart )
colStart = m_colStart;
(void)HitTest(pt2, &colEnd, NULL);
// colEnd may be less than colStart if colStart was changed by the
// assignment above
if ( colEnd < colStart )
colEnd = colStart;
// for single line controls we may additionally cut off everything
// which is to the right of the last visible position
if ( IsSingleLine() )
{
// don't draw the chars beyond the rightmost one
if ( m_colLastVisible == -1 )
{
// recalculate this rightmost column
UpdateLastVisible();
}
if ( colStart > m_colLastVisible )
{
// don't bother redrawing something that is beyond the last
// visible position
continue;
}
if ( colEnd > m_colLastVisible )
colEnd = m_colLastVisible;
// we don't draw the last character because it may be shown only
// partially in single line mode (in multi line we can't avoid
// showing parts of characters anyhow)
if ( colEnd > colStart )
colEnd--;
}
// 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
int selStart, selEnd;
GetSelectedPartOfLine(line, &selStart, &selEnd);
if ( selStart != -1 )
{
// 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();
}
// calculate the logical text coords
rectText.x = m_rectText.x + GetTextWidth(textLine.Left(colStart));
rectText.width = GetTextWidth(text);
// do draw the text
DrawTextLine(dc, rectText, text, selStart, selEnd);
wxLogTrace(_T("text"), _T("Line %ld: positions %ld-%ld redrawn."),
line, colStart, colEnd);
}
}
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)
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);
}
// 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() )
{
GetCaret()->Show();
m_hasCaret = TRUE;
}
}
// ----------------------------------------------------------------------------
// caret
// ----------------------------------------------------------------------------
bool wxTextCtrl::SetFont(const wxFont& font)
{
if ( !wxControl::SetFont(font) )
return FALSE;
// recreate it, in fact
CreateCaret();
// and refresh everything, of course
InitInsertionPoint();
ClearSelection();
RecalcMaxWidth();
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, GetCharHeight());
#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);
}
wxCoord wxTextCtrl::GetCaretPosition(long pos) const
{
wxString textBeforeCaret(GetLineText(m_curRow),
(size_t)(pos == -1 ? m_curCol : pos));
return GetTextWidth(textBeforeCaret);
}
void wxTextCtrl::ShowCaret(bool show)
{
wxCaret *caret = GetCaret();
if ( caret )
{
// position it correctly (taking scrolling into account)
wxCoord xCaret, yCaret;
CalcScrolledPosition(m_rectText.x + GetCaretPosition() - m_ofsHorz,
m_rectText.y + m_curRow*GetCharHeight(),
&xCaret,
&yCaret);
caret->Move(xCaret, yCaret);
// and show it there
caret->Show(show);
}
}
// ----------------------------------------------------------------------------
// input
// ----------------------------------------------------------------------------
wxString wxTextCtrl::GetInputHandlerType() const
{
return wxINP_HANDLER_TEXTCTRL;
}
bool wxTextCtrl::PerformAction(const wxControlAction& actionOrig,
long numArg,
const wxString& strArg)
{
// has the text changed as result of this action?
bool textChanged = 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)
static const long INVALID_POS_VALUE = -2;
long 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 ( m_curRow > 0 )
newPos = XYToPosition(m_curCol, m_curRow - 1);
}
else if ( action == wxACTION_TEXT_DOWN )
{
if ( (size_t)m_curRow < m_lines.GetCount() )
newPos = XYToPosition(m_curCol, m_curRow + 1);
}
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_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;
long posLast = GetLastPosition();
if ( newPos > posLast )
newPos = posLast;
if ( del )
{
// if we have the selection, remove just it
long 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 ( 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 &&
keycode != WXK_DELETE &&
keycode != WXK_BACK )
{
PerformAction(wxACTION_TEXT_INSERT, -1, (wxChar)keycode);
// skip event.Skip() below
return;
}
}
event.Skip();
}
// ----------------------------------------------------------------------------
// wxStdTextCtrlInputHandler
// ----------------------------------------------------------------------------
wxStdTextCtrlInputHandler::wxStdTextCtrlInputHandler(wxInputHandler *inphand)
: wxStdInputHandler(inphand)
{
m_winCapture = (wxTextCtrl *)NULL;
}
/* static */
long wxStdTextCtrlInputHandler::HitTest(const wxTextCtrl *text,
const wxPoint& pos)
{
long col, row;
(void)text->HitTest(pos, &col, &row);
return text->XYToPosition(col, row);
}
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;
// 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();
long 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);
long pos = HitTest(text, event.GetPosition());
if ( pos != -1 )
{
text->PerformAction(wxACTION_TEXT_EXTEND_SEL, pos);
}
}
return wxStdInputHandler::HandleMouseMove(control, event);
}
#endif // wxUSE_TEXTCTRL