Fix undo and redo actions for replacing string.
1. Add embeddertest for ReplaceSelection action.
2. Add CFXEU_ReplaceSel to |m_UndoItemStack| to indicate the
beginning and end of a replace action. So every time
ReplaceSelection() is called, the size of |m_UndoItemStack| will
increase by 4 (Including CFXEU_Clear, CFXEU_InsertText and two
CFXEU_ReplaceSels as begin/end markers). When undo/redo is
performed for ReplaceSelection(), |m_UndoItemStack| skips the
CFXEU_ReplaceSel markers, and keeps processing undo/redo for
InsertText() and ClearSelection() actions.
Bug: pdfium:838
Change-Id: Ia0e30a7cef9268d03e8d34c11eded9f1511798bc
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/56671
Commit-Queue: Hui Yingst <nigi@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/fpdf_formfill_embeddertest.cpp b/fpdfsdk/fpdf_formfill_embeddertest.cpp
index d5f6fba..bfcbe8c 100644
--- a/fpdfsdk/fpdf_formfill_embeddertest.cpp
+++ b/fpdfsdk/fpdf_formfill_embeddertest.cpp
@@ -2270,3 +2270,51 @@
}
CheckFocusedFieldText(L"Banana");
}
+
+TEST_F(FPDFFormFillTextFormEmbedderTest, ReplaceSelection) {
+ ScopedFPDFWideString text_to_insert = GetFPDFWideString(L"XYZ");
+ ClickOnFormFieldAtPoint(RegularFormBegin());
+ CheckCanUndo(false);
+ CheckCanRedo(false);
+
+ TypeTextIntoTextField(2, RegularFormBegin());
+ CheckFocusedFieldText(L"AB");
+ CheckSelection(L"");
+ SelectTextWithKeyboard(1, FWL_VKEY_Right, RegularFormBegin());
+ CheckSelection(L"A");
+
+ FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get());
+ CheckFocusedFieldText(L"XYZB");
+ CheckCanUndo(true);
+ CheckCanRedo(false);
+
+ PerformUndo();
+ CheckFocusedFieldText(L"AB");
+ CheckCanUndo(true);
+ CheckCanRedo(true);
+
+ PerformUndo();
+ CheckFocusedFieldText(L"A");
+ CheckCanUndo(true);
+ CheckCanRedo(true);
+
+ PerformUndo();
+ CheckFocusedFieldText(L"");
+ CheckCanUndo(false);
+ CheckCanRedo(true);
+
+ PerformRedo();
+ CheckFocusedFieldText(L"A");
+ CheckCanUndo(true);
+ CheckCanRedo(true);
+
+ PerformRedo();
+ CheckFocusedFieldText(L"AB");
+ CheckCanUndo(true);
+ CheckCanRedo(true);
+
+ PerformRedo();
+ CheckFocusedFieldText(L"XYZB");
+ CheckCanUndo(true);
+ CheckCanRedo(false);
+}
diff --git a/fpdfsdk/pwl/cpwl_edit.cpp b/fpdfsdk/pwl/cpwl_edit.cpp
index d006e55..cce0b1c 100644
--- a/fpdfsdk/pwl/cpwl_edit.cpp
+++ b/fpdfsdk/pwl/cpwl_edit.cpp
@@ -406,8 +406,7 @@
}
void CPWL_Edit::ReplaceSel(const WideString& wsText) {
- m_pEdit->ClearSelection();
- m_pEdit->InsertText(wsText, FX_CHARSET_Default);
+ m_pEdit->ReplaceSelection(wsText);
}
CFX_FloatRect CPWL_Edit::GetFocusRect() const {
diff --git a/fpdfsdk/pwl/cpwl_edit_ctrl.cpp b/fpdfsdk/pwl/cpwl_edit_ctrl.cpp
index 818e476..7e03e55 100644
--- a/fpdfsdk/pwl/cpwl_edit_ctrl.cpp
+++ b/fpdfsdk/pwl/cpwl_edit_ctrl.cpp
@@ -55,8 +55,7 @@
}
void CPWL_EditCtrl::ReplaceSelection(const WideString& text) {
- m_pEdit->ClearSelection();
- m_pEdit->InsertText(text, FX_CHARSET_Default);
+ m_pEdit->ReplaceSelection(text);
}
bool CPWL_EditCtrl::RePosChildWnd() {
diff --git a/fpdfsdk/pwl/cpwl_edit_impl.cpp b/fpdfsdk/pwl/cpwl_edit_impl.cpp
index 23eee8b..0988c1f 100644
--- a/fpdfsdk/pwl/cpwl_edit_impl.cpp
+++ b/fpdfsdk/pwl/cpwl_edit_impl.cpp
@@ -210,10 +210,13 @@
void CPWL_EditImpl_Undo::Undo() {
m_bWorking = true;
- if (CanUndo()) {
- m_UndoItemStack[m_nCurUndoPos - 1]->Undo();
+ int nUndoRemain = 1;
+ while (CanUndo() && nUndoRemain > 0) {
+ nUndoRemain += m_UndoItemStack[m_nCurUndoPos - 1]->Undo();
m_nCurUndoPos--;
+ nUndoRemain--;
}
+ ASSERT(nUndoRemain == 0);
m_bWorking = false;
}
@@ -223,10 +226,13 @@
void CPWL_EditImpl_Undo::Redo() {
m_bWorking = true;
- if (CanRedo()) {
- m_UndoItemStack[m_nCurUndoPos]->Redo();
+ int nRedoRemain = 1;
+ while (CanRedo() && nRedoRemain > 0) {
+ nRedoRemain += m_UndoItemStack[m_nCurUndoPos]->Redo();
m_nCurUndoPos++;
+ nRedoRemain--;
}
+ ASSERT(nRedoRemain == 0);
m_bWorking = false;
}
@@ -268,16 +274,18 @@
CFXEU_InsertWord::~CFXEU_InsertWord() {}
-void CFXEU_InsertWord::Redo() {
+int CFXEU_InsertWord::Redo() {
m_pEdit->SelectNone();
m_pEdit->SetCaret(m_wpOld);
m_pEdit->InsertWord(m_Word, m_nCharset, false, true);
+ return 0;
}
-void CFXEU_InsertWord::Undo() {
+int CFXEU_InsertWord::Undo() {
m_pEdit->SelectNone();
m_pEdit->SetCaret(m_wpNew);
m_pEdit->Backspace(false, true);
+ return 0;
}
CFXEU_InsertReturn::CFXEU_InsertReturn(CPWL_EditImpl* pEdit,
@@ -289,16 +297,43 @@
CFXEU_InsertReturn::~CFXEU_InsertReturn() {}
-void CFXEU_InsertReturn::Redo() {
+int CFXEU_InsertReturn::Redo() {
m_pEdit->SelectNone();
m_pEdit->SetCaret(m_wpOld);
m_pEdit->InsertReturn(false, true);
+ return 0;
}
-void CFXEU_InsertReturn::Undo() {
+int CFXEU_InsertReturn::Undo() {
m_pEdit->SelectNone();
m_pEdit->SetCaret(m_wpNew);
m_pEdit->Backspace(false, true);
+ return 0;
+}
+
+CFXEU_ReplaceSelection::CFXEU_ReplaceSelection(CPWL_EditImpl* pEdit,
+ bool bIsEnd)
+ : m_pEdit(pEdit), m_bEnd(bIsEnd) {
+ ASSERT(m_pEdit);
+}
+
+CFXEU_ReplaceSelection::~CFXEU_ReplaceSelection() {}
+
+int CFXEU_ReplaceSelection::Redo() {
+ m_pEdit->SelectNone();
+ if (IsEnd())
+ return 0;
+ // Redo ClearSelection, InsertText and ReplaceSelection's end marker
+ return 3;
+}
+
+int CFXEU_ReplaceSelection::Undo() {
+ m_pEdit->SelectNone();
+ if (!IsEnd())
+ return 0;
+ // Undo InsertText, ClearSelection and ReplaceSelection's beginning
+ // marker
+ return 3;
}
CFXEU_Backspace::CFXEU_Backspace(CPWL_EditImpl* pEdit,
@@ -316,19 +351,21 @@
CFXEU_Backspace::~CFXEU_Backspace() {}
-void CFXEU_Backspace::Redo() {
+int CFXEU_Backspace::Redo() {
m_pEdit->SelectNone();
m_pEdit->SetCaret(m_wpOld);
m_pEdit->Backspace(false, true);
+ return 0;
}
-void CFXEU_Backspace::Undo() {
+int CFXEU_Backspace::Undo() {
m_pEdit->SelectNone();
m_pEdit->SetCaret(m_wpNew);
if (m_wpNew.nSecIndex != m_wpOld.nSecIndex)
m_pEdit->InsertReturn(false, true);
else
m_pEdit->InsertWord(m_Word, m_nCharset, false, true);
+ return 0;
}
CFXEU_Delete::CFXEU_Delete(CPWL_EditImpl* pEdit,
@@ -348,19 +385,21 @@
CFXEU_Delete::~CFXEU_Delete() {}
-void CFXEU_Delete::Redo() {
+int CFXEU_Delete::Redo() {
m_pEdit->SelectNone();
m_pEdit->SetCaret(m_wpOld);
m_pEdit->Delete(false, true);
+ return 0;
}
-void CFXEU_Delete::Undo() {
+int CFXEU_Delete::Undo() {
m_pEdit->SelectNone();
m_pEdit->SetCaret(m_wpNew);
if (m_bSecEnd)
m_pEdit->InsertReturn(false, true);
else
m_pEdit->InsertWord(m_Word, m_nCharset, false, true);
+ return 0;
}
CFXEU_Clear::CFXEU_Clear(CPWL_EditImpl* pEdit,
@@ -372,17 +411,19 @@
CFXEU_Clear::~CFXEU_Clear() {}
-void CFXEU_Clear::Redo() {
+int CFXEU_Clear::Redo() {
m_pEdit->SelectNone();
m_pEdit->SetSelection(m_wrSel.BeginPos, m_wrSel.EndPos);
m_pEdit->Clear(false, true);
+ return 0;
}
-void CFXEU_Clear::Undo() {
+int CFXEU_Clear::Undo() {
m_pEdit->SelectNone();
m_pEdit->SetCaret(m_wrSel.BeginPos);
m_pEdit->InsertText(m_swText, FX_CHARSET_Default, false, true);
m_pEdit->SetSelection(m_wrSel.BeginPos, m_wrSel.EndPos);
+ return 0;
}
CFXEU_InsertText::CFXEU_InsertText(CPWL_EditImpl* pEdit,
@@ -400,16 +441,18 @@
CFXEU_InsertText::~CFXEU_InsertText() {}
-void CFXEU_InsertText::Redo() {
+int CFXEU_InsertText::Redo() {
m_pEdit->SelectNone();
m_pEdit->SetCaret(m_wpOld);
m_pEdit->InsertText(m_swText, m_nCharset, false, true);
+ return 0;
}
-void CFXEU_InsertText::Undo() {
+int CFXEU_InsertText::Undo() {
m_pEdit->SelectNone();
m_pEdit->SetSelection(m_wpOld, m_wpNew);
m_pEdit->Clear(false, true);
+ return 0;
}
// static
@@ -1685,6 +1728,13 @@
}
}
+void CPWL_EditImpl::ReplaceSelection(const WideString& text) {
+ AddEditUndoItem(pdfium::MakeUnique<CFXEU_ReplaceSelection>(this, false));
+ ClearSelection();
+ InsertText(text, FX_CHARSET_Default);
+ AddEditUndoItem(pdfium::MakeUnique<CFXEU_ReplaceSelection>(this, true));
+}
+
bool CPWL_EditImpl::Redo() {
if (m_bEnableUndo) {
if (m_Undo.CanRedo()) {
diff --git a/fpdfsdk/pwl/cpwl_edit_impl.h b/fpdfsdk/pwl/cpwl_edit_impl.h
index 0472863..889ed78 100644
--- a/fpdfsdk/pwl/cpwl_edit_impl.h
+++ b/fpdfsdk/pwl/cpwl_edit_impl.h
@@ -98,8 +98,16 @@
public:
virtual ~IFX_Edit_UndoItem() = default;
- virtual void Undo() = 0;
- virtual void Redo() = 0;
+ // Undo/Redo the current undo item and returns the number of additional items
+ // to be processed in |m_UndoItemStack| to fully undo/redo the action. (An
+ // example is CFXEU_ReplaceSelection::Undo(), if CFXEU_ReplaceSelection marks
+ // the end of a replace action, CFXEU_ReplaceSelection::Undo() returns 3
+ // because 3 more undo items need to be processed to revert the replace
+ // action: insert text, clear selection and the CFXEU_ReplaceSelection which
+ // marks the beginning of replace action.) Implementations should return 0 by
+ // default.
+ virtual int Undo() = 0;
+ virtual int Redo() = 0;
};
class CFXEU_InsertWord final : public IFX_Edit_UndoItem {
@@ -112,8 +120,8 @@
~CFXEU_InsertWord() override;
// IFX_Edit_UndoItem:
- void Redo() override;
- void Undo() override;
+ int Redo() override;
+ int Undo() override;
private:
UnownedPtr<CPWL_EditImpl> m_pEdit;
@@ -132,8 +140,8 @@
~CFXEU_InsertReturn() override;
// IFX_Edit_UndoItem:
- void Redo() override;
- void Undo() override;
+ int Redo() override;
+ int Undo() override;
private:
UnownedPtr<CPWL_EditImpl> m_pEdit;
@@ -142,6 +150,22 @@
CPVT_WordPlace m_wpNew;
};
+class CFXEU_ReplaceSelection final : public IFX_Edit_UndoItem {
+ public:
+ CFXEU_ReplaceSelection(CPWL_EditImpl* pEdit, bool bIsEnd);
+ ~CFXEU_ReplaceSelection() override;
+
+ // IFX_Edit_UndoItem:
+ int Redo() override;
+ int Undo() override;
+
+ private:
+ bool IsEnd() const { return m_bEnd; }
+
+ UnownedPtr<CPWL_EditImpl> m_pEdit;
+ const bool m_bEnd; // indicate whether this is the end of replace action
+};
+
class CFXEU_Backspace final : public IFX_Edit_UndoItem {
public:
CFXEU_Backspace(CPWL_EditImpl* pEdit,
@@ -152,8 +176,8 @@
~CFXEU_Backspace() override;
// IFX_Edit_UndoItem:
- void Redo() override;
- void Undo() override;
+ int Redo() override;
+ int Undo() override;
private:
UnownedPtr<CPWL_EditImpl> m_pEdit;
@@ -175,8 +199,8 @@
~CFXEU_Delete() override;
// IFX_Edit_UndoItem:
- void Redo() override;
- void Undo() override;
+ int Redo() override;
+ int Undo() override;
private:
UnownedPtr<CPWL_EditImpl> m_pEdit;
@@ -196,8 +220,8 @@
~CFXEU_Clear() override;
// IFX_Edit_UndoItem:
- void Redo() override;
- void Undo() override;
+ int Redo() override;
+ int Undo() override;
private:
UnownedPtr<CPWL_EditImpl> m_pEdit;
@@ -216,8 +240,8 @@
~CFXEU_InsertText() override;
// IFX_Edit_UndoItem:
- void Redo() override;
- void Undo() override;
+ int Redo() override;
+ int Undo() override;
private:
UnownedPtr<CPWL_EditImpl> m_pEdit;
@@ -289,6 +313,7 @@
bool Delete();
bool ClearSelection();
bool InsertText(const WideString& sText, int32_t charset);
+ void ReplaceSelection(const WideString& text);
bool Redo();
bool Undo();
CPVT_WordPlace WordIndexToWordPlace(int32_t index) const;