git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/wxUNIVERSAL@8715 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
3216 lines
88 KiB
C++
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
|