1. wxTextCtrl::Replace() does work now - added many tests for it in univ sample
2. extracted wxCommand and wxCommandProcessor to separate files 3. fixed bug in wxStringTokenizer with strings consisting of sole delimiter 4. implemented (but untested) undo/redo for text ctrl git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/wxUNIVERSAL@8698 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
2
TODO
2
TODO
@@ -12,7 +12,6 @@ wxTextCtrl
|
||||
? text ctrl display pb when text is truncated
|
||||
* too much is refreshed when double clicking (word select)
|
||||
|
||||
! update rect overlaps with horz scrollbar
|
||||
!! own ScrollWindow() for horz scrolling as we must always scroll by char!
|
||||
|
||||
All
|
||||
@@ -46,6 +45,7 @@ All
|
||||
+ text ctrl horz scrolling
|
||||
+ DoDraw should iterate over update region instead of using bounding box
|
||||
+ listbox: horz scrollbar doesn't appear
|
||||
+ wxTextCtrl update rect overlaps with horz scrollbar
|
||||
|
||||
MSW
|
||||
|
||||
|
106
include/wx/cmdproc.h
Normal file
106
include/wx/cmdproc.h
Normal file
@@ -0,0 +1,106 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Name: wx/cmdproc.h
|
||||
// Purpose: undo/redo capable command processing framework
|
||||
// Author: Julian Smart (extracted from docview.h by VZ)
|
||||
// Modified by:
|
||||
// Created: 05.11.00
|
||||
// RCS-ID: $Id$
|
||||
// Copyright: (c) wxWindows team
|
||||
// Licence: wxWindows licence
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _WX_CMDPROC_H_
|
||||
#define _WX_CMDPROC_H_
|
||||
|
||||
#ifdef __GNUG__
|
||||
#pragma interface "cmdproc.h"
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxCommand: a single command capable of performing itself
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class WXDLLEXPORT wxCommand : public wxObject
|
||||
{
|
||||
public:
|
||||
wxCommand(bool canUndoIt = FALSE, const wxString& name = "");
|
||||
~wxCommand();
|
||||
|
||||
// Override this to perform a command
|
||||
virtual bool Do() = 0;
|
||||
|
||||
// Override this to undo a command
|
||||
virtual bool Undo() = 0;
|
||||
|
||||
virtual bool CanUndo() const { return m_canUndo; }
|
||||
virtual wxString GetName() const { return m_commandName; }
|
||||
|
||||
protected:
|
||||
bool m_canUndo;
|
||||
wxString m_commandName;
|
||||
|
||||
private:
|
||||
DECLARE_CLASS(wxCommand)
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxCommandProcessor: wxCommand manager
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class WXDLLEXPORT wxCommandProcessor : public wxObject
|
||||
{
|
||||
public:
|
||||
// if max number of commands is -1, it is unlimited
|
||||
wxCommandProcessor(int maxCommands = -1);
|
||||
virtual ~wxCommandProcessor();
|
||||
|
||||
// Pass a command to the processor. The processor calls Do(); if
|
||||
// successful, is appended to the command history unless storeIt is FALSE.
|
||||
virtual bool Submit(wxCommand *command, bool storeIt = TRUE);
|
||||
|
||||
// just store the command without executing it
|
||||
virtual void Store(wxCommand *command);
|
||||
|
||||
virtual bool Undo();
|
||||
virtual bool Redo();
|
||||
virtual bool CanUndo() const;
|
||||
virtual bool CanRedo() const;
|
||||
|
||||
virtual void Initialize();
|
||||
virtual void SetMenuStrings();
|
||||
|
||||
#if wxUSE_MENUS
|
||||
// Call this to manage an edit menu.
|
||||
void SetEditMenu(wxMenu *menu) { m_commandEditMenu = menu; }
|
||||
wxMenu *GetEditMenu() const { return m_commandEditMenu; }
|
||||
#endif // wxUSE_MENUS
|
||||
|
||||
// command list access
|
||||
wxList& GetCommands() const { return (wxList&) m_commands; }
|
||||
wxCommand *GetCurrentCommand() const
|
||||
{
|
||||
return (wxCommand *)(m_currentCommand ? m_currentCommand->Data() : NULL);
|
||||
}
|
||||
int GetMaxCommands() const { return m_maxNoCommands; }
|
||||
virtual void ClearCommands();
|
||||
|
||||
protected:
|
||||
// for further flexibility, command processor doesn't call wxCommand::Do()
|
||||
// and Undo() directly but uses these functions which can be overridden in
|
||||
// the derived class
|
||||
virtual bool DoCommand(wxCommand& cmd);
|
||||
virtual bool UndoCommand(wxCommand& cmd);
|
||||
|
||||
int m_maxNoCommands;
|
||||
wxList m_commands;
|
||||
wxNode* m_currentCommand;
|
||||
|
||||
#if wxUSE_MENUS
|
||||
wxMenu* m_commandEditMenu;
|
||||
#endif // wxUSE_MENUS
|
||||
|
||||
private:
|
||||
DECLARE_DYNAMIC_CLASS(wxCommandProcessor)
|
||||
};
|
||||
|
||||
#endif // _WX_CMDPROC_H_
|
@@ -31,7 +31,6 @@ class WXDLLEXPORT wxView;
|
||||
class WXDLLEXPORT wxDocTemplate;
|
||||
class WXDLLEXPORT wxDocManager;
|
||||
class WXDLLEXPORT wxPrintInfo;
|
||||
class WXDLLEXPORT wxCommand;
|
||||
class WXDLLEXPORT wxCommandProcessor;
|
||||
class WXDLLEXPORT wxFileHistory;
|
||||
class WXDLLEXPORT wxConfigBase;
|
||||
@@ -513,65 +512,6 @@ protected:
|
||||
};
|
||||
#endif // wxUSE_PRINTING_ARCHITECTURE
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Command processing framework
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class WXDLLEXPORT wxCommand : public wxObject
|
||||
{
|
||||
DECLARE_CLASS(wxCommand)
|
||||
|
||||
public:
|
||||
wxCommand(bool canUndoIt = FALSE, const wxString& name = "");
|
||||
~wxCommand();
|
||||
|
||||
// Override this to perform a command
|
||||
virtual bool Do() = 0;
|
||||
|
||||
// Override this to undo a command
|
||||
virtual bool Undo() = 0;
|
||||
|
||||
virtual bool CanUndo() const { return m_canUndo; }
|
||||
virtual wxString GetName() const { return m_commandName; }
|
||||
|
||||
protected:
|
||||
bool m_canUndo;
|
||||
wxString m_commandName;
|
||||
};
|
||||
|
||||
class WXDLLEXPORT wxCommandProcessor : public wxObject
|
||||
{
|
||||
DECLARE_DYNAMIC_CLASS(wxCommandProcessor)
|
||||
|
||||
public:
|
||||
wxCommandProcessor(int maxCommands = 100);
|
||||
~wxCommandProcessor();
|
||||
|
||||
// Pass a command to the processor. The processor calls Do(); if
|
||||
// successful, is appended to the command history unless storeIt is FALSE.
|
||||
virtual bool Submit(wxCommand *command, bool storeIt = TRUE);
|
||||
virtual bool Undo();
|
||||
virtual bool Redo();
|
||||
virtual bool CanUndo() const;
|
||||
virtual bool CanRedo() const;
|
||||
|
||||
// Call this to manage an edit menu.
|
||||
void SetEditMenu(wxMenu *menu) { m_commandEditMenu = menu; }
|
||||
wxMenu *GetEditMenu() const { return m_commandEditMenu; }
|
||||
virtual void SetMenuStrings();
|
||||
virtual void Initialize();
|
||||
|
||||
wxList& GetCommands() const { return (wxList&) m_commands; }
|
||||
int GetMaxCommands() const { return m_maxNoCommands; }
|
||||
virtual void ClearCommands();
|
||||
|
||||
protected:
|
||||
int m_maxNoCommands;
|
||||
wxList m_commands;
|
||||
wxNode* m_currentCommand;
|
||||
wxMenu* m_commandEditMenu;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// File history management
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@@ -17,6 +17,7 @@
|
||||
#endif
|
||||
|
||||
class WXDLLEXPORT wxCaret;
|
||||
class WXDLLEXPORT wxTextCtrlCommandProcessor;
|
||||
|
||||
#include "wx/scrolwin.h" // for wxScrollHelper
|
||||
|
||||
@@ -25,6 +26,9 @@ class WXDLLEXPORT wxCaret;
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// cursor movement and also selection and delete operations
|
||||
#define wxACTION_TEXT_GOTO _T("goto") // to pos in numArg
|
||||
#define wxACTION_TEXT_FIRST _T("first") // go to pos 0
|
||||
#define wxACTION_TEXT_LAST _T("last") // go to last pos
|
||||
#define wxACTION_TEXT_HOME _T("home")
|
||||
#define wxACTION_TEXT_END _T("end")
|
||||
#define wxACTION_TEXT_LEFT _T("left")
|
||||
@@ -56,6 +60,10 @@ class WXDLLEXPORT wxCaret;
|
||||
#define wxACTION_TEXT_SEL_WORD _T("wordsel")
|
||||
#define wxACTION_TEXT_SEL_LINE _T("linesel")
|
||||
|
||||
// undo or redo
|
||||
#define wxACTION_TEXT_UNDO _T("undo")
|
||||
#define wxACTION_TEXT_REDO _T("redo")
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxTextCtrl::HitTest return values
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -102,6 +110,8 @@ public:
|
||||
const wxValidator& validator = wxDefaultValidator,
|
||||
const wxString& name = wxTextCtrlNameStr);
|
||||
|
||||
virtual ~wxTextCtrl();
|
||||
|
||||
// implement base class pure virtuals
|
||||
// ----------------------------------
|
||||
|
||||
@@ -332,6 +342,11 @@ protected:
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
// clipboard operations (unlike the versions without Do prefix, they have a
|
||||
// return code)
|
||||
bool DoCut();
|
||||
bool DoPaste();
|
||||
|
||||
private:
|
||||
// update the scrollbars (only called from OnIdle)
|
||||
void UpdateScrollbars();
|
||||
@@ -390,6 +405,9 @@ private:
|
||||
// the max line length in pixels
|
||||
wxCoord m_widthMax;
|
||||
|
||||
// the object to which we delegate our undo/redo implementation
|
||||
wxTextCtrlCommandProcessor *m_cmdProcessor;
|
||||
|
||||
DECLARE_EVENT_TABLE()
|
||||
DECLARE_DYNAMIC_CLASS(wxTextCtrl)
|
||||
};
|
||||
|
@@ -121,6 +121,8 @@ protected:
|
||||
void OnLeftUp(wxMouseEvent& event);
|
||||
|
||||
private:
|
||||
void TestTextCtrlReplace(wxTextCtrl *text, const wxString& value);
|
||||
|
||||
// any class wishing to process wxWindows events must use this macro
|
||||
DECLARE_EVENT_TABLE()
|
||||
};
|
||||
@@ -394,16 +396,42 @@ MyUnivFrame::MyUnivFrame(const wxString& title)
|
||||
text->SetSize(sizeText);
|
||||
#else
|
||||
wxTextCtrl *text = new wxTextCtrl(this, -1, //_T("Hello,\nMultiverse!"),
|
||||
"0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n",
|
||||
//"0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n",
|
||||
"",
|
||||
wxPoint(10, 30),
|
||||
wxSize(-1, 150),
|
||||
wxTE_MULTILINE);
|
||||
|
||||
// test wxTextCtrl::Replace()
|
||||
TestTextCtrlReplace(text, "");
|
||||
TestTextCtrlReplace(text, "0\n1\n2\n3");
|
||||
TestTextCtrlReplace(text, "0\n1\n2\n3\n");
|
||||
TestTextCtrlReplace(text, "first\nsecond\n\nthird line");
|
||||
#endif
|
||||
text->SetFocus();
|
||||
//text->SetEditable(FALSE);
|
||||
#endif // !TEST_TEXT_ONLY/TEST_TEXT_ONLY
|
||||
}
|
||||
|
||||
void MyUnivFrame::TestTextCtrlReplace(wxTextCtrl *text, const wxString& value)
|
||||
{
|
||||
long last = value.length();
|
||||
for ( long from = 0; from <= last; from++ )
|
||||
{
|
||||
for ( long to = from; to <= last; to++ )
|
||||
{
|
||||
text->SetValue(value);
|
||||
text->Replace(from, to, "");
|
||||
text->SetValue(value);
|
||||
text->Replace(from, to, "foo");
|
||||
text->SetValue(value);
|
||||
text->Replace(from, to, _T("a\nb\n"));
|
||||
text->SetValue(value);
|
||||
text->Replace(from, to, _T("a\nb\nc"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MyUnivFrame::OnButton(wxCommandEvent& event)
|
||||
{
|
||||
int btn = event.GetId();
|
||||
|
284
src/common/cmdproc.cpp
Normal file
284
src/common/cmdproc.cpp
Normal file
@@ -0,0 +1,284 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Name: src/common/cmdproc.cpp
|
||||
// Purpose: wxCommand and wxCommandProcessor classes
|
||||
// Author: Julian Smart (extracted from docview.h by VZ)
|
||||
// Modified by:
|
||||
// Created: 05.11.00
|
||||
// RCS-ID: $Id$
|
||||
// Copyright: (c) wxWindows team
|
||||
// Licence: wxWindows licence
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// ============================================================================
|
||||
// declarations
|
||||
// ============================================================================
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// headers
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#ifdef __GNUG__
|
||||
#pragma implementation "cmdproc.h"
|
||||
#endif
|
||||
|
||||
// For compilers that support precompilation, includes "wx.h".
|
||||
#include "wx/wxprec.h"
|
||||
|
||||
#ifdef __BORLANDC__
|
||||
#pragma hdrstop
|
||||
#endif
|
||||
|
||||
#ifndef WX_PRECOMP
|
||||
#include "wx/string.h"
|
||||
#endif //WX_PRECOMP
|
||||
|
||||
#include "wx/cmdproc.h"
|
||||
|
||||
// ============================================================================
|
||||
// implementation
|
||||
// ============================================================================
|
||||
|
||||
IMPLEMENT_CLASS(wxCommand, wxObject)
|
||||
IMPLEMENT_DYNAMIC_CLASS(wxCommandProcessor, wxObject)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxCommand
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
wxCommand::wxCommand(bool canUndoIt, const wxString& name)
|
||||
{
|
||||
m_canUndo = canUndoIt;
|
||||
m_commandName = name;
|
||||
}
|
||||
|
||||
wxCommand::~wxCommand()
|
||||
{
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Command processor
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
wxCommandProcessor::wxCommandProcessor(int maxCommands)
|
||||
{
|
||||
m_maxNoCommands = maxCommands;
|
||||
m_currentCommand = (wxNode *) NULL;
|
||||
#if wxUSE_MENUS
|
||||
m_commandEditMenu = (wxMenu *) NULL;
|
||||
#endif // wxUSE_MENUS
|
||||
}
|
||||
|
||||
wxCommandProcessor::~wxCommandProcessor()
|
||||
{
|
||||
ClearCommands();
|
||||
}
|
||||
|
||||
bool wxCommandProcessor::DoCommand(wxCommand& cmd)
|
||||
{
|
||||
return cmd.Do();
|
||||
}
|
||||
|
||||
bool wxCommandProcessor::UndoCommand(wxCommand& cmd)
|
||||
{
|
||||
return cmd.Undo();
|
||||
}
|
||||
|
||||
// Pass a command to the processor. The processor calls Do();
|
||||
// if successful, is appended to the command history unless
|
||||
// storeIt is FALSE.
|
||||
bool wxCommandProcessor::Submit(wxCommand *command, bool storeIt)
|
||||
{
|
||||
wxCHECK_MSG( command, FALSE, _T("no command in wxCommandProcessor::Submit") );
|
||||
|
||||
if ( !DoCommand(*command) )
|
||||
return FALSE;
|
||||
|
||||
if ( storeIt )
|
||||
Store(command);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void wxCommandProcessor::Store(wxCommand *command)
|
||||
{
|
||||
wxCHECK_RET( command, _T("no command in wxCommandProcessor::Store") );
|
||||
|
||||
if (m_commands.Number() == m_maxNoCommands)
|
||||
{
|
||||
wxNode *firstNode = m_commands.First();
|
||||
wxCommand *firstCommand = (wxCommand *)firstNode->Data();
|
||||
delete firstCommand;
|
||||
delete firstNode;
|
||||
}
|
||||
|
||||
// Correct a bug: we must chop off the current 'branch'
|
||||
// so that we're at the end of the command list.
|
||||
if (!m_currentCommand)
|
||||
ClearCommands();
|
||||
else
|
||||
{
|
||||
wxNode *node = m_currentCommand->Next();
|
||||
while (node)
|
||||
{
|
||||
wxNode *next = node->Next();
|
||||
delete (wxCommand *)node->Data();
|
||||
delete node;
|
||||
node = next;
|
||||
}
|
||||
}
|
||||
|
||||
m_commands.Append(command);
|
||||
m_currentCommand = m_commands.Last();
|
||||
SetMenuStrings();
|
||||
}
|
||||
|
||||
bool wxCommandProcessor::Undo()
|
||||
{
|
||||
wxCommand *command = GetCurrentCommand();
|
||||
if ( command && command->CanUndo() )
|
||||
{
|
||||
if ( UndoCommand(*command) )
|
||||
{
|
||||
m_currentCommand = m_currentCommand->Previous();
|
||||
SetMenuStrings();
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bool wxCommandProcessor::Redo()
|
||||
{
|
||||
wxCommand *redoCommand = (wxCommand *) NULL;
|
||||
wxNode *redoNode = (wxNode *) NULL;
|
||||
if (m_currentCommand && m_currentCommand->Next())
|
||||
{
|
||||
redoCommand = (wxCommand *)m_currentCommand->Next()->Data();
|
||||
redoNode = m_currentCommand->Next();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_commands.Number() > 0)
|
||||
{
|
||||
redoCommand = (wxCommand *)m_commands.First()->Data();
|
||||
redoNode = m_commands.First();
|
||||
}
|
||||
}
|
||||
|
||||
if (redoCommand)
|
||||
{
|
||||
bool success = DoCommand(*redoCommand);
|
||||
if (success)
|
||||
{
|
||||
m_currentCommand = redoNode;
|
||||
SetMenuStrings();
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bool wxCommandProcessor::CanUndo() const
|
||||
{
|
||||
wxCommand *command = GetCurrentCommand();
|
||||
|
||||
return command && command->CanUndo();
|
||||
}
|
||||
|
||||
bool wxCommandProcessor::CanRedo() const
|
||||
{
|
||||
if ((m_currentCommand != (wxNode*) NULL) && (m_currentCommand->Next() == (wxNode*) NULL))
|
||||
return FALSE;
|
||||
|
||||
if ((m_currentCommand != (wxNode*) NULL) && (m_currentCommand->Next() != (wxNode*) NULL))
|
||||
return TRUE;
|
||||
|
||||
if ((m_currentCommand == (wxNode*) NULL) && (m_commands.Number() > 0))
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void wxCommandProcessor::Initialize()
|
||||
{
|
||||
m_currentCommand = m_commands.Last();
|
||||
SetMenuStrings();
|
||||
}
|
||||
|
||||
void wxCommandProcessor::SetMenuStrings()
|
||||
{
|
||||
#if wxUSE_MENUS
|
||||
if (m_commandEditMenu)
|
||||
{
|
||||
wxString buf;
|
||||
if (m_currentCommand)
|
||||
{
|
||||
wxCommand *command = (wxCommand *)m_currentCommand->Data();
|
||||
wxString commandName(command->GetName());
|
||||
if (commandName == wxT("")) commandName = _("Unnamed command");
|
||||
bool canUndo = command->CanUndo();
|
||||
if (canUndo)
|
||||
buf = wxString(_("&Undo ")) + commandName;
|
||||
else
|
||||
buf = wxString(_("Can't &Undo ")) + commandName;
|
||||
|
||||
m_commandEditMenu->SetLabel(wxID_UNDO, buf);
|
||||
m_commandEditMenu->Enable(wxID_UNDO, canUndo);
|
||||
|
||||
// We can redo, if we're not at the end of the history.
|
||||
if (m_currentCommand->Next())
|
||||
{
|
||||
wxCommand *redoCommand = (wxCommand *)m_currentCommand->Next()->Data();
|
||||
wxString redoCommandName(redoCommand->GetName());
|
||||
if (redoCommandName == wxT("")) redoCommandName = _("Unnamed command");
|
||||
buf = wxString(_("&Redo ")) + redoCommandName;
|
||||
m_commandEditMenu->SetLabel(wxID_REDO, buf);
|
||||
m_commandEditMenu->Enable(wxID_REDO, TRUE);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_commandEditMenu->SetLabel(wxID_REDO, _("&Redo"));
|
||||
m_commandEditMenu->Enable(wxID_REDO, FALSE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_commandEditMenu->SetLabel(wxID_UNDO, _("&Undo"));
|
||||
m_commandEditMenu->Enable(wxID_UNDO, FALSE);
|
||||
|
||||
if (m_commands.Number() == 0)
|
||||
{
|
||||
m_commandEditMenu->SetLabel(wxID_REDO, _("&Redo"));
|
||||
m_commandEditMenu->Enable(wxID_REDO, FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
// currentCommand is NULL but there are commands: this means that
|
||||
// we've undone to the start of the list, but can redo the first.
|
||||
wxCommand *redoCommand = (wxCommand *)m_commands.First()->Data();
|
||||
wxString redoCommandName(redoCommand->GetName());
|
||||
if (redoCommandName == wxT("")) redoCommandName = _("Unnamed command");
|
||||
buf = wxString(_("&Redo ")) + redoCommandName;
|
||||
m_commandEditMenu->SetLabel(wxID_REDO, buf);
|
||||
m_commandEditMenu->Enable(wxID_REDO, TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // wxUSE_MENUS
|
||||
}
|
||||
|
||||
void wxCommandProcessor::ClearCommands()
|
||||
{
|
||||
wxNode *node = m_commands.First();
|
||||
while (node)
|
||||
{
|
||||
wxCommand *command = (wxCommand *)node->Data();
|
||||
delete command;
|
||||
delete node;
|
||||
node = m_commands.First();
|
||||
}
|
||||
m_currentCommand = (wxNode *) NULL;
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Name: docview.cpp
|
||||
// Name: src/common/docview.cpp
|
||||
// Purpose: Document/view classes
|
||||
// Author: Julian Smart
|
||||
// Modified by:
|
||||
@@ -57,6 +57,7 @@
|
||||
#include "wx/docview.h"
|
||||
#include "wx/confbase.h"
|
||||
#include "wx/file.h"
|
||||
#include "wx/cmdproc.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@@ -87,8 +88,6 @@
|
||||
IMPLEMENT_DYNAMIC_CLASS(wxDocPrintout, wxPrintout)
|
||||
#endif
|
||||
|
||||
IMPLEMENT_CLASS(wxCommand, wxObject)
|
||||
IMPLEMENT_DYNAMIC_CLASS(wxCommandProcessor, wxObject)
|
||||
IMPLEMENT_DYNAMIC_CLASS(wxFileHistory, wxObject)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -1768,222 +1767,6 @@ void wxDocPrintout::GetPageInfo(int *minPage, int *maxPage, int *selPageFrom, in
|
||||
|
||||
#endif // wxUSE_PRINTING_ARCHITECTURE
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Command processing framework
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
wxCommand::wxCommand(bool canUndoIt, const wxString& name)
|
||||
{
|
||||
m_canUndo = canUndoIt;
|
||||
m_commandName = name;
|
||||
}
|
||||
|
||||
wxCommand::~wxCommand()
|
||||
{
|
||||
}
|
||||
|
||||
// Command processor
|
||||
wxCommandProcessor::wxCommandProcessor(int maxCommands)
|
||||
{
|
||||
m_maxNoCommands = maxCommands;
|
||||
m_currentCommand = (wxNode *) NULL;
|
||||
m_commandEditMenu = (wxMenu *) NULL;
|
||||
}
|
||||
|
||||
wxCommandProcessor::~wxCommandProcessor()
|
||||
{
|
||||
ClearCommands();
|
||||
}
|
||||
|
||||
// Pass a command to the processor. The processor calls Do();
|
||||
// if successful, is appended to the command history unless
|
||||
// storeIt is FALSE.
|
||||
bool wxCommandProcessor::Submit(wxCommand *command, bool storeIt)
|
||||
{
|
||||
bool success = command->Do();
|
||||
if (success && storeIt)
|
||||
{
|
||||
if (m_commands.Number() == m_maxNoCommands)
|
||||
{
|
||||
wxNode *firstNode = m_commands.First();
|
||||
wxCommand *firstCommand = (wxCommand *)firstNode->Data();
|
||||
delete firstCommand;
|
||||
delete firstNode;
|
||||
}
|
||||
|
||||
// Correct a bug: we must chop off the current 'branch'
|
||||
// so that we're at the end of the command list.
|
||||
if (!m_currentCommand)
|
||||
ClearCommands();
|
||||
else
|
||||
{
|
||||
wxNode *node = m_currentCommand->Next();
|
||||
while (node)
|
||||
{
|
||||
wxNode *next = node->Next();
|
||||
delete (wxCommand *)node->Data();
|
||||
delete node;
|
||||
node = next;
|
||||
}
|
||||
}
|
||||
|
||||
m_commands.Append(command);
|
||||
m_currentCommand = m_commands.Last();
|
||||
SetMenuStrings();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool wxCommandProcessor::Undo()
|
||||
{
|
||||
if (m_currentCommand)
|
||||
{
|
||||
wxCommand *command = (wxCommand *)m_currentCommand->Data();
|
||||
if (command->CanUndo())
|
||||
{
|
||||
bool success = command->Undo();
|
||||
if (success)
|
||||
{
|
||||
m_currentCommand = m_currentCommand->Previous();
|
||||
SetMenuStrings();
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bool wxCommandProcessor::Redo()
|
||||
{
|
||||
wxCommand *redoCommand = (wxCommand *) NULL;
|
||||
wxNode *redoNode = (wxNode *) NULL;
|
||||
if (m_currentCommand && m_currentCommand->Next())
|
||||
{
|
||||
redoCommand = (wxCommand *)m_currentCommand->Next()->Data();
|
||||
redoNode = m_currentCommand->Next();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_commands.Number() > 0)
|
||||
{
|
||||
redoCommand = (wxCommand *)m_commands.First()->Data();
|
||||
redoNode = m_commands.First();
|
||||
}
|
||||
}
|
||||
|
||||
if (redoCommand)
|
||||
{
|
||||
bool success = redoCommand->Do();
|
||||
if (success)
|
||||
{
|
||||
m_currentCommand = redoNode;
|
||||
SetMenuStrings();
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bool wxCommandProcessor::CanUndo() const
|
||||
{
|
||||
if (m_currentCommand)
|
||||
return ((wxCommand *)m_currentCommand->Data())->CanUndo();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bool wxCommandProcessor::CanRedo() const
|
||||
{
|
||||
if ((m_currentCommand != (wxNode*) NULL) && (m_currentCommand->Next() == (wxNode*) NULL))
|
||||
return FALSE;
|
||||
|
||||
if ((m_currentCommand != (wxNode*) NULL) && (m_currentCommand->Next() != (wxNode*) NULL))
|
||||
return TRUE;
|
||||
|
||||
if ((m_currentCommand == (wxNode*) NULL) && (m_commands.Number() > 0))
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void wxCommandProcessor::Initialize()
|
||||
{
|
||||
m_currentCommand = m_commands.Last();
|
||||
SetMenuStrings();
|
||||
}
|
||||
|
||||
void wxCommandProcessor::SetMenuStrings()
|
||||
{
|
||||
if (m_commandEditMenu)
|
||||
{
|
||||
wxString buf;
|
||||
if (m_currentCommand)
|
||||
{
|
||||
wxCommand *command = (wxCommand *)m_currentCommand->Data();
|
||||
wxString commandName(command->GetName());
|
||||
if (commandName == wxT("")) commandName = _("Unnamed command");
|
||||
bool canUndo = command->CanUndo();
|
||||
if (canUndo)
|
||||
buf = wxString(_("&Undo ")) + commandName;
|
||||
else
|
||||
buf = wxString(_("Can't &Undo ")) + commandName;
|
||||
|
||||
m_commandEditMenu->SetLabel(wxID_UNDO, buf);
|
||||
m_commandEditMenu->Enable(wxID_UNDO, canUndo);
|
||||
|
||||
// We can redo, if we're not at the end of the history.
|
||||
if (m_currentCommand->Next())
|
||||
{
|
||||
wxCommand *redoCommand = (wxCommand *)m_currentCommand->Next()->Data();
|
||||
wxString redoCommandName(redoCommand->GetName());
|
||||
if (redoCommandName == wxT("")) redoCommandName = _("Unnamed command");
|
||||
buf = wxString(_("&Redo ")) + redoCommandName;
|
||||
m_commandEditMenu->SetLabel(wxID_REDO, buf);
|
||||
m_commandEditMenu->Enable(wxID_REDO, TRUE);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_commandEditMenu->SetLabel(wxID_REDO, _("&Redo"));
|
||||
m_commandEditMenu->Enable(wxID_REDO, FALSE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_commandEditMenu->SetLabel(wxID_UNDO, _("&Undo"));
|
||||
m_commandEditMenu->Enable(wxID_UNDO, FALSE);
|
||||
|
||||
if (m_commands.Number() == 0)
|
||||
{
|
||||
m_commandEditMenu->SetLabel(wxID_REDO, _("&Redo"));
|
||||
m_commandEditMenu->Enable(wxID_REDO, FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
// currentCommand is NULL but there are commands: this means that
|
||||
// we've undone to the start of the list, but can redo the first.
|
||||
wxCommand *redoCommand = (wxCommand *)m_commands.First()->Data();
|
||||
wxString redoCommandName(redoCommand->GetName());
|
||||
if (redoCommandName == wxT("")) redoCommandName = _("Unnamed command");
|
||||
buf = wxString(_("&Redo ")) + redoCommandName;
|
||||
m_commandEditMenu->SetLabel(wxID_REDO, buf);
|
||||
m_commandEditMenu->Enable(wxID_REDO, TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wxCommandProcessor::ClearCommands()
|
||||
{
|
||||
wxNode *node = m_commands.First();
|
||||
while (node)
|
||||
{
|
||||
wxCommand *command = (wxCommand *)node->Data();
|
||||
delete command;
|
||||
delete node;
|
||||
node = m_commands.First();
|
||||
}
|
||||
m_currentCommand = (wxNode *) NULL;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// File history processor
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@@ -2,7 +2,7 @@
|
||||
// Name: tokenzr.cpp
|
||||
// Purpose: String tokenizer
|
||||
// Author: Guilhem Lavaux
|
||||
// Modified by: Vadim Zeitlin
|
||||
// Modified by: Vadim Zeitlin (almost full rewrite)
|
||||
// Created: 04/22/98
|
||||
// RCS-ID: $Id$
|
||||
// Copyright: (c) Guilhem Lavaux
|
||||
@@ -105,10 +105,16 @@ bool wxStringTokenizer::HasMoreTokens() const
|
||||
|
||||
if ( m_string.find_first_not_of(m_delims) == wxString::npos )
|
||||
{
|
||||
// no non empty tokens left, but in wxTOKEN_RET_EMPTY_ALL mode we
|
||||
// still may return TRUE if GetNextToken() wasn't called yet for the
|
||||
// last trailing empty token
|
||||
return m_mode == wxTOKEN_RET_EMPTY_ALL ? m_hasMore : FALSE;
|
||||
// no non empty tokens left, but in 2 cases we still may return TRUE if
|
||||
// GetNextToken() wasn't called yet for this empty token:
|
||||
//
|
||||
// a) in wxTOKEN_RET_EMPTY_ALL mode we always do it
|
||||
// b) in wxTOKEN_RET_EMPTY mode we do it in the special case of a
|
||||
// string containing only the delimiter: then there is an empty
|
||||
// token just before it
|
||||
return (m_mode == wxTOKEN_RET_EMPTY_ALL) ||
|
||||
(m_mode == wxTOKEN_RET_EMPTY && m_pos == 0)
|
||||
? m_hasMore : FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -12,8 +12,8 @@
|
||||
/*
|
||||
Search for "OPT" for possible optimizations
|
||||
|
||||
Possible optimization would be to always store the coords in the text in
|
||||
triplets (pos, col, line) and update them simultaneously instead of
|
||||
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.
|
||||
@@ -58,6 +58,8 @@
|
||||
#include "wx/univ/colschem.h"
|
||||
#include "wx/univ/theme.h"
|
||||
|
||||
#include "wx/cmdproc.h"
|
||||
|
||||
// turn extra wxTextCtrl-specific debugging on/off
|
||||
#define WXDEBUG_TEXT
|
||||
|
||||
@@ -73,6 +75,7 @@
|
||||
// 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 )
|
||||
@@ -83,6 +86,127 @@ static inline void OrderPositions(long& from, long& to)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// 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)
|
||||
{
|
||||
}
|
||||
|
||||
// combine the 2 commands together
|
||||
void Append(wxTextCtrlInsertCommand *other);
|
||||
|
||||
virtual bool Do(wxTextCtrl *text);
|
||||
virtual bool Undo(wxTextCtrl *text);
|
||||
|
||||
private:
|
||||
wxString m_text;
|
||||
};
|
||||
|
||||
// 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 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
|
||||
// ============================================================================
|
||||
@@ -131,6 +255,9 @@ void wxTextCtrl::Init()
|
||||
|
||||
// init wxScrollHelper
|
||||
SetWindow(this);
|
||||
|
||||
// init the undo manager
|
||||
m_cmdProcessor = new wxTextCtrlCommandProcessor(this);
|
||||
}
|
||||
|
||||
bool wxTextCtrl::Create(wxWindow *parent,
|
||||
@@ -184,6 +311,11 @@ bool wxTextCtrl::Create(wxWindow *parent,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
wxTextCtrl::~wxTextCtrl()
|
||||
{
|
||||
delete m_cmdProcessor;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// set/get the value
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -514,6 +646,7 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text)
|
||||
// repaint
|
||||
RefreshPixelRange(0, startNewText, widthNewText);
|
||||
}
|
||||
//OPT: special case for replacements inside single line?
|
||||
else // multiline
|
||||
{
|
||||
/*
|
||||
@@ -528,18 +661,25 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text)
|
||||
{
|
||||
if ( line > lineStart )
|
||||
{
|
||||
// from previous line
|
||||
// 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);
|
||||
wxCoord widthNewText,
|
||||
startNewText = GetTextWidth(textNew);
|
||||
if ( (size_t)colStart == m_lines[lineStart].length() )
|
||||
{
|
||||
// text appended, refresh just enough to show the new text
|
||||
@@ -550,23 +690,36 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text)
|
||||
widthNewText = 0;
|
||||
}
|
||||
|
||||
// (2b) insert new text
|
||||
textNew += text;
|
||||
size_t toRel = (size_t)((to - from) + colStart); // adjust for index shift
|
||||
|
||||
// (2c) and append the end of the old text
|
||||
|
||||
// adjust for index shift: to is relative to colStart, not 0
|
||||
size_t toRel = (size_t)((to - from) + colStart);
|
||||
if ( toRel < textOrig.length() )
|
||||
{
|
||||
textNew += textOrig.c_str() + toRel;
|
||||
}
|
||||
|
||||
// (3) break it into lines
|
||||
wxArrayString lines;
|
||||
if ( textNew.empty() )
|
||||
|
||||
// (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() )
|
||||
{
|
||||
// special case: if the replacement string is empty we still want to
|
||||
// have one (empty) string in the lines array but wxStringTokenize()
|
||||
// won't put anything in it in this case, so do it ourselves
|
||||
lines.Add(wxEmptyString);
|
||||
}
|
||||
else // break into lines normally
|
||||
|
||||
// (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 = wxStringTokenize(textNew, _T("\n"), wxTOKEN_RET_EMPTY_ALL);
|
||||
lines.Add(wxEmptyString);
|
||||
}
|
||||
|
||||
size_t nReplaceCount = lines.GetCount(),
|
||||
@@ -574,8 +727,6 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text)
|
||||
|
||||
// (4) merge into the array
|
||||
|
||||
size_t countOld = m_lines.GetCount();
|
||||
|
||||
// (4a) replace
|
||||
for ( line = lineStart; line <= lineEnd; line++, nReplaceLine++ )
|
||||
{
|
||||
@@ -588,11 +739,15 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text)
|
||||
}
|
||||
else // no more replacement lines
|
||||
{
|
||||
// (4b) delete all extra lines
|
||||
while ( line <= lineEnd )
|
||||
// (4b) delete all extra lines (note that we need to delete
|
||||
// them backwards because indices shift while we do it)
|
||||
for ( long lineDel = lineEnd; lineDel >= line; lineDel-- )
|
||||
{
|
||||
m_lines.RemoveAt(line++);
|
||||
m_lines.RemoveAt(lineDel);
|
||||
}
|
||||
|
||||
// update line to exit the loop
|
||||
line = lineEnd + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -629,6 +784,12 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text)
|
||||
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());
|
||||
@@ -636,12 +797,6 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text)
|
||||
// and the selection (do it after setting the cursor to have correct value
|
||||
// for selection anchor)
|
||||
ClearSelection();
|
||||
|
||||
#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
|
||||
}
|
||||
|
||||
void wxTextCtrl::Remove(long from, long to)
|
||||
@@ -1297,15 +1452,27 @@ void wxTextCtrl::Copy()
|
||||
|
||||
void wxTextCtrl::Cut()
|
||||
{
|
||||
if ( HasSelection() )
|
||||
(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;
|
||||
@@ -1315,30 +1482,132 @@ void wxTextCtrl::Paste()
|
||||
&& wxTheClipboard->GetData(data) )
|
||||
{
|
||||
// reverse transformation: '\r\n\" -> '\n'
|
||||
WriteText(wxTextFile::Translate(data.GetText(), wxTextFileType_Unix));
|
||||
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());
|
||||
|
||||
// this would be a logic error in the code here as the flag is only
|
||||
// set after adding insert command and reset after adding any other
|
||||
// one
|
||||
wxCHECK_RET( cmdInsLast,
|
||||
_T("when compressing commands last must be insert") );
|
||||
|
||||
cmdInsLast->Append(cmdIns);
|
||||
|
||||
delete cmdIns;
|
||||
|
||||
// don't need to call the base class version
|
||||
return;
|
||||
}
|
||||
else // not compressing
|
||||
{
|
||||
// append the following insert commands to this one
|
||||
m_compressInserts = TRUE;
|
||||
}
|
||||
}
|
||||
else // not an insert command
|
||||
{
|
||||
// stop compressing insert commands - this won't work with the last
|
||||
// command not being an insert one anyhow
|
||||
StopCompressing();
|
||||
|
||||
// the base class version will do the job normally
|
||||
}
|
||||
|
||||
wxCommandProcessor::Store(command);
|
||||
}
|
||||
|
||||
void wxTextCtrlInsertCommand::Append(wxTextCtrlInsertCommand *other)
|
||||
{
|
||||
m_text += other->m_text;
|
||||
}
|
||||
|
||||
bool wxTextCtrlInsertCommand::Do(wxTextCtrl *text)
|
||||
{
|
||||
text->WriteText(m_text);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
bool wxTextCtrlInsertCommand::Undo(wxTextCtrl *text)
|
||||
{
|
||||
wxFAIL_MSG(_T("TODO"));
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bool wxTextCtrlRemoveCommand::Do(wxTextCtrl *text)
|
||||
{
|
||||
text->SetSelection(m_from, m_to);
|
||||
m_textDeleted = text->GetSelectionText();
|
||||
text->RemoveSelection();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
bool wxTextCtrlRemoveCommand::Undo(wxTextCtrl *text)
|
||||
{
|
||||
wxFAIL_MSG(_T("TODO"));
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void wxTextCtrl::Undo()
|
||||
{
|
||||
wxFAIL_MSG(_T("not implemented"));
|
||||
// the caller must check it
|
||||
wxASSERT_MSG( CanUndo(), _T("can't call Undo() if !CanUndo()") );
|
||||
|
||||
m_cmdProcessor->Undo();
|
||||
}
|
||||
|
||||
void wxTextCtrl::Redo()
|
||||
{
|
||||
wxFAIL_MSG(_T("not implemented"));
|
||||
}
|
||||
// the caller must check it
|
||||
wxASSERT_MSG( CanRedo(), _T("can't call Undo() if !CanUndo()") );
|
||||
|
||||
m_cmdProcessor->Redo();
|
||||
}
|
||||
|
||||
bool wxTextCtrl::CanUndo() const
|
||||
{
|
||||
return FALSE;
|
||||
return IsEditable() && m_cmdProcessor->CanUndo();
|
||||
}
|
||||
|
||||
bool wxTextCtrl::CanRedo() const
|
||||
{
|
||||
return FALSE;
|
||||
return IsEditable() && m_cmdProcessor->CanRedo();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -1616,7 +1885,7 @@ wxTextCtrlHitTestResult wxTextCtrl::HitTest(const wxPoint& pos,
|
||||
|
||||
// row calculation is simple as we assume that all lines have the same
|
||||
// height
|
||||
int row = (y - 1) / GetCharHeight();
|
||||
int row = y / GetCharHeight();
|
||||
int rowMax = GetNumberOfLines() - 1;
|
||||
if ( row > rowMax )
|
||||
{
|
||||
@@ -2381,8 +2650,13 @@ 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;
|
||||
@@ -2414,6 +2688,18 @@ bool wxTextCtrl::PerformAction(const wxControlAction& actionOrig,
|
||||
{
|
||||
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 )
|
||||
@@ -2444,7 +2730,8 @@ bool wxTextCtrl::PerformAction(const wxControlAction& actionOrig,
|
||||
{
|
||||
if ( IsEditable() && !strArg.empty() )
|
||||
{
|
||||
WriteText(strArg);
|
||||
// inserting text can be undone
|
||||
command = new wxTextCtrlInsertCommand(strArg);
|
||||
|
||||
textChanged = TRUE;
|
||||
}
|
||||
@@ -2475,6 +2762,16 @@ bool wxTextCtrl::PerformAction(const wxControlAction& actionOrig,
|
||||
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);
|
||||
@@ -2493,9 +2790,11 @@ bool wxTextCtrl::PerformAction(const wxControlAction& actionOrig,
|
||||
if ( del )
|
||||
{
|
||||
// if we have the selection, remove just it
|
||||
long from, to;
|
||||
if ( HasSelection() )
|
||||
{
|
||||
RemoveSelection();
|
||||
from = m_selStart;
|
||||
to = m_selEnd;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2503,10 +2802,22 @@ bool wxTextCtrl::PerformAction(const wxControlAction& actionOrig,
|
||||
// the new one
|
||||
if ( m_curPos != newPos )
|
||||
{
|
||||
Remove(m_curPos, newPos);
|
||||
|
||||
textChanged = TRUE;
|
||||
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
|
||||
@@ -2526,6 +2837,19 @@ bool wxTextCtrl::PerformAction(const wxControlAction& actionOrig,
|
||||
}
|
||||
}
|
||||
|
||||
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?") );
|
||||
@@ -2600,7 +2924,8 @@ bool wxStdTextCtrlInputHandler::HandleKey(wxControl *control,
|
||||
const wxKeyEvent& event,
|
||||
bool pressed)
|
||||
{
|
||||
if ( !pressed )
|
||||
// we're only interested in key presses without Alt modifier
|
||||
if ( !pressed || event.AltDown() )
|
||||
return FALSE;
|
||||
|
||||
wxControlAction action;
|
||||
@@ -2616,18 +2941,22 @@ bool wxStdTextCtrlInputHandler::HandleKey(wxControl *control,
|
||||
{
|
||||
// cursor movement
|
||||
case WXK_HOME:
|
||||
action << wxACTION_TEXT_HOME;
|
||||
action << (ctrlDown ? wxACTION_TEXT_FIRST
|
||||
: wxACTION_TEXT_HOME);
|
||||
break;
|
||||
|
||||
case WXK_END:
|
||||
action << wxACTION_TEXT_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;
|
||||
|
||||
@@ -2643,10 +2972,12 @@ bool wxStdTextCtrlInputHandler::HandleKey(wxControl *control,
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -2660,6 +2991,10 @@ bool wxStdTextCtrlInputHandler::HandleKey(wxControl *control,
|
||||
{
|
||||
switch ( keycode )
|
||||
{
|
||||
case 'A':
|
||||
action = wxACTION_TEXT_REDO;
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
action = wxACTION_TEXT_COPY;
|
||||
break;
|
||||
@@ -2671,11 +3006,15 @@ bool wxStdTextCtrlInputHandler::HandleKey(wxControl *control,
|
||||
case 'X':
|
||||
action = wxACTION_TEXT_CUT;
|
||||
break;
|
||||
|
||||
case 'Z':
|
||||
action = wxACTION_TEXT_UNDO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( action != wxACTION_NONE )
|
||||
if ( (action != wxACTION_NONE) && (action != wxACTION_TEXT_PREFIX_SEL) )
|
||||
{
|
||||
control->PerformAction(action, -1, str);
|
||||
|
||||
|
@@ -596,6 +596,7 @@ void wxWindow::SetScrollbar(int orient,
|
||||
int range,
|
||||
bool refresh)
|
||||
{
|
||||
bool hasClientSizeChanged = FALSE;
|
||||
wxScrollBar *scrollbar = GetScrollbar(orient);
|
||||
if ( range )
|
||||
{
|
||||
@@ -611,6 +612,9 @@ void wxWindow::SetScrollbar(int orient,
|
||||
else
|
||||
m_scrollbarHorz = scrollbar;
|
||||
|
||||
// the client area diminished as we created a scrollbar
|
||||
hasClientSizeChanged = TRUE;
|
||||
|
||||
PositionScrollbars();
|
||||
}
|
||||
else if ( GetWindowStyle() & wxALWAYS_SHOW_SB )
|
||||
@@ -639,6 +643,9 @@ void wxWindow::SetScrollbar(int orient,
|
||||
else
|
||||
m_scrollbarHorz = NULL;
|
||||
|
||||
// the client area increased as we removed a scrollbar
|
||||
hasClientSizeChanged = TRUE;
|
||||
|
||||
// the size of the remaining scrollbar must be adjusted
|
||||
if ( m_scrollbarHorz || m_scrollbarVert )
|
||||
{
|
||||
@@ -647,6 +654,13 @@ void wxWindow::SetScrollbar(int orient,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// give the window a chance to relayout
|
||||
if ( hasClientSizeChanged )
|
||||
{
|
||||
wxSizeEvent event(GetSize());
|
||||
(void)GetEventHandler()->ProcessEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void wxWindow::SetScrollPos(int orient, int pos, bool refresh)
|
||||
|
Reference in New Issue
Block a user