diff options
author | Miklos Vajna <vmiklos@collabora.com> | 2021-11-10 08:50:08 +0100 |
---|---|---|
committer | Mike Kaganski <mike.kaganski@collabora.com> | 2021-11-11 09:11:10 +0100 |
commit | c72e500ccaf0ce2261c5233b80fba9342778f810 (patch) | |
tree | d4b7875fc6fecaaf5bc2b774dfde513addd488ee /sw/source | |
parent | 6cc10c4492f59c6ab56c31a80712f604608d7f0d (diff) |
sw: allow undo of typing in 2 views independent from each other
Undoing out of order is dangerous by default, so limit this to a very
specific case as a start, that allows growing in follow-up commits.
For now, allow out of order undo if:
1) redo stack is empty
2) we're in LOK mode (different views represent different users)
3) we undo a single action (count is 1)
4) the top undo action doesn't belong to the current view
5) the top and the previous undo actions are independent
Which only requires that SwUndoInsert::UndoImpl() is independent for two
different paragraphs, which seems to be the case.
Independent undo actions opt in for this, currently the only such
allowed undo action is SwUndoInsert ("typing"), which adds characters to
a single text node. Even those are only considered independent if they
operate on different text nodes.
On the positive side, this allows out of order undo in the frequent case
where two users collaborate on a long document and they just type some
new content into the document at different paragraphs.
(cherry picked from commit 8e8e72f08b01a284cf1a90b888d48acfb6a33d2e)
Conflicts:
sw/source/core/inc/UndoManager.hxx
sw/source/uibase/shells/basesh.cxx
Change-Id: Ibb4551e8f7046b4947491b8bf751eaa0cbb2d060
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/124955
Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Diffstat (limited to 'sw/source')
-rw-r--r-- | sw/source/core/edit/edundo.cxx | 5 | ||||
-rw-r--r-- | sw/source/core/inc/UndoCore.hxx | 5 | ||||
-rw-r--r-- | sw/source/core/inc/UndoInsert.hxx | 2 | ||||
-rw-r--r-- | sw/source/core/inc/UndoManager.hxx | 9 | ||||
-rw-r--r-- | sw/source/core/undo/docundo.cxx | 71 | ||||
-rw-r--r-- | sw/source/core/undo/unins.cxx | 5 | ||||
-rw-r--r-- | sw/source/uibase/inc/wrtsh.hxx | 2 | ||||
-rw-r--r-- | sw/source/uibase/shells/basesh.cxx | 21 | ||||
-rw-r--r-- | sw/source/uibase/wrtsh/wrtundo.cxx | 4 |
9 files changed, 111 insertions, 13 deletions
diff --git a/sw/source/core/edit/edundo.cxx b/sw/source/core/edit/edundo.cxx index a55f45651fba..4a5c3c73bd88 100644 --- a/sw/source/core/edit/edundo.cxx +++ b/sw/source/core/edit/edundo.cxx @@ -94,7 +94,7 @@ void SwEditShell::HandleUndoRedoContext(::sw::UndoRedoContext & rContext) } } -void SwEditShell::Undo(sal_uInt16 const nCount) +void SwEditShell::Undo(sal_uInt16 const nCount, sal_uInt16 nOffset) { CurrShell aCurr( this ); @@ -128,8 +128,7 @@ void SwEditShell::Undo(sal_uInt16 const nCount) try { for (sal_uInt16 i = 0; i < nCount; ++i) { - bRet = GetDoc()->GetIDocumentUndoRedo().Undo() - || bRet; + bRet = GetDoc()->GetIDocumentUndoRedo().UndoWithOffset(nOffset) || bRet; } } catch (const css::uno::Exception &) { TOOLS_WARN_EXCEPTION("sw.core", "SwEditShell::Undo()"); diff --git a/sw/source/core/inc/UndoCore.hxx b/sw/source/core/inc/UndoCore.hxx index 8fb5e8dcf3f7..af6025c121b0 100644 --- a/sw/source/core/inc/UndoCore.hxx +++ b/sw/source/core/inc/UndoCore.hxx @@ -106,11 +106,16 @@ public: o_rpMarkList = m_pMarkList; } + void SetUndoOffset(size_t nUndoOffset) { m_nUndoOffset = nUndoOffset; } + + size_t GetUndoOffset() override { return m_nUndoOffset; } + private: SwDoc & m_rDoc; IShellCursorSupplier & m_rCursorSupplier; SwFrameFormat * m_pSelFormat; SdrMarkList * m_pMarkList; + size_t m_nUndoOffset = 0; }; class RepeatContext diff --git a/sw/source/core/inc/UndoInsert.hxx b/sw/source/core/inc/UndoInsert.hxx index 801997d65e39..d5476b432a27 100644 --- a/sw/source/core/inc/UndoInsert.hxx +++ b/sw/source/core/inc/UndoInsert.hxx @@ -86,6 +86,8 @@ public: virtual SwRewriter GetRewriter() const override; void SetWithRsid() { m_bWithRsid = true; } + + bool IsIndependent(const SwUndoInsert& rOther) const; }; SwRewriter diff --git a/sw/source/core/inc/UndoManager.hxx b/sw/source/core/inc/UndoManager.hxx index 4113d54d8f52..93126322f809 100644 --- a/sw/source/core/inc/UndoManager.hxx +++ b/sw/source/core/inc/UndoManager.hxx @@ -80,6 +80,7 @@ public: virtual size_t GetUndoActionCount(const bool bCurrentLevel = true) const override; size_t GetRedoActionCount(const bool bCurrentLevel = true) const override; void SetView(SwView* pView) override; + bool UndoWithOffset(size_t nUndoOffset) override; // SfxUndoManager virtual void AddUndoAction(std::unique_ptr<SfxUndoAction> pAction, @@ -95,6 +96,12 @@ public: SwNodes & GetUndoNodes(); void SetDocShell(SwDocShell* pDocShell); + /** + * Checks if the topmost undo action owned by pView is independent from the topmost action undo + * action. + */ + bool IsViewUndoActionIndependent(const SwView* pView) const; + protected: virtual void EmptyActionsChanged() override; @@ -119,7 +126,7 @@ private: SwView* m_pView; enum class UndoOrRedoType { Undo, Redo }; - bool impl_DoUndoRedo(UndoOrRedoType undoOrRedo); + bool impl_DoUndoRedo(UndoOrRedoType undoOrRedo, size_t nUndoOffset); // UGLY: should not be called using SdrUndoManager::Repeat; diff --git a/sw/source/core/undo/docundo.cxx b/sw/source/core/undo/docundo.cxx index 6e350836fc20..8c46f659cf06 100644 --- a/sw/source/core/undo/docundo.cxx +++ b/sw/source/core/undo/docundo.cxx @@ -40,6 +40,8 @@ #include <sfx2/viewfrm.hxx> #include <sfx2/bindings.hxx> +#include <UndoInsert.hxx> + using namespace ::com::sun::star; // the undo array should never grow beyond this limit: @@ -351,6 +353,61 @@ UndoManager::EndUndo(SwUndoId eUndoId, SwRewriter const*const pRewriter) return eUndoId; } +/** + * Checks if the topmost undo action owned by pView is independent from the topmost action undo + * action. + */ +bool UndoManager::IsViewUndoActionIndependent(const SwView* pView) const +{ + if (GetUndoActionCount() <= 1 || SdrUndoManager::GetRedoActionCount() > 0) + { + // Single or less undo, owned by an other view; or redo actions that might depend on the + // current undo order. + return false; + } + + if (!pView) + { + return false; + } + + // Last undo action that doesn't belong to the view. + const SfxUndoAction* pTopAction = GetUndoAction(); + + ViewShellId nViewId = pView->GetViewShellId(); + + // Earlier undo action that belongs to the view, but is not the top one. + const SfxUndoAction* pViewAction = nullptr; + const SfxUndoAction* pAction = GetUndoAction(1); + if (pAction->GetViewShellId() == nViewId) + { + pViewAction = pAction; + } + + if (!pViewAction) + { + // Found no earlier undo action that belongs to the view. + return false; + } + + auto pTopSwAction = dynamic_cast<const SwUndo*>(pTopAction); + if (!pTopSwAction || pTopSwAction->GetId() != SwUndoId::TYPING) + { + return false; + } + + auto pViewSwAction = dynamic_cast<const SwUndo*>(pViewAction); + if (!pViewSwAction || pViewSwAction->GetId() != SwUndoId::TYPING) + { + return false; + } + + const auto& rTopInsert = *static_cast<const SwUndoInsert*>(pTopSwAction); + const auto& rViewInsert = *static_cast<const SwUndoInsert*>(pViewSwAction); + + return rViewInsert.IsIndependent(rTopInsert); +} + bool UndoManager::GetLastUndoInfo( OUString *const o_pStr, SwUndoId *const o_pId, const SwView* pView) const @@ -368,7 +425,8 @@ UndoManager::GetLastUndoInfo( { // If another view created the undo action, prevent undoing it from this view. ViewShellId nViewShellId = pView ? pView->GetViewShellId() : m_pDocShell->GetView()->GetViewShellId(); - if (pAction->GetViewShellId() != nViewShellId) + // Unless we know that the other view's undo action is independent from us. + if (pAction->GetViewShellId() != nViewShellId && !IsViewUndoActionIndependent(pView)) { if (o_pId) { @@ -571,7 +629,7 @@ private: } -bool UndoManager::impl_DoUndoRedo(UndoOrRedoType undoOrRedo) +bool UndoManager::impl_DoUndoRedo(UndoOrRedoType undoOrRedo, size_t nUndoOffset) { SwDoc & rDoc(GetUndoNodes().GetDoc()); @@ -600,6 +658,7 @@ bool UndoManager::impl_DoUndoRedo(UndoOrRedoType undoOrRedo) bool bRet(false); ::sw::UndoRedoContext context(rDoc, *pEditShell); + context.SetUndoOffset(nUndoOffset); // N.B. these may throw! if (UndoOrRedoType::Undo == undoOrRedo) @@ -629,7 +688,9 @@ bool UndoManager::impl_DoUndoRedo(UndoOrRedoType undoOrRedo) return bRet; } -bool UndoManager::Undo() +bool UndoManager::Undo() { return UndoWithOffset(0); } + +bool UndoManager::UndoWithOffset(size_t nUndoOffset) { if(isTextEditActive()) { @@ -637,7 +698,7 @@ bool UndoManager::Undo() } else { - return impl_DoUndoRedo(UndoOrRedoType::Undo); + return impl_DoUndoRedo(UndoOrRedoType::Undo, nUndoOffset); } } @@ -649,7 +710,7 @@ bool UndoManager::Redo() } else { - return impl_DoUndoRedo(UndoOrRedoType::Redo); + return impl_DoUndoRedo(UndoOrRedoType::Redo, /*nUndoOffset=*/0); } } diff --git a/sw/source/core/undo/unins.cxx b/sw/source/core/undo/unins.cxx index dcd3c4c689f0..0dd0d24d77cd 100644 --- a/sw/source/core/undo/unins.cxx +++ b/sw/source/core/undo/unins.cxx @@ -475,6 +475,11 @@ SwRewriter SwUndoInsert::GetRewriter() const return aResult; } +bool SwUndoInsert::IsIndependent(const SwUndoInsert& rOther) const +{ + return m_nNode != rOther.m_nNode; +} + class SwUndoReplace::Impl : private SwUndoSaveContent { diff --git a/sw/source/uibase/inc/wrtsh.hxx b/sw/source/uibase/inc/wrtsh.hxx index a98cb26bfdfc..3374c10e55dc 100644 --- a/sw/source/uibase/inc/wrtsh.hxx +++ b/sw/source/uibase/inc/wrtsh.hxx @@ -371,7 +371,7 @@ typedef bool (SwWrtShell::*FNSimpleMove)(); enum class FieldDialogPressedButton { NONE, Previous, Next }; - void Do( DoType eDoType, sal_uInt16 nCnt = 1 ); + void Do(DoType eDoType, sal_uInt16 nCnt = 1, sal_uInt16 nOffset = 0); OUString GetDoString( DoType eDoType ) const; OUString GetRepeatString() const; void GetDoStrings( DoType eDoType, SfxStringListItem& rStrLstItem ) const; diff --git a/sw/source/uibase/shells/basesh.cxx b/sw/source/uibase/shells/basesh.cxx index 57e9394fc03c..fc7c5daf8ce4 100644 --- a/sw/source/uibase/shells/basesh.cxx +++ b/sw/source/uibase/shells/basesh.cxx @@ -96,6 +96,7 @@ #include <shellres.hxx> #include <UndoTable.hxx> +#include <UndoManager.hxx> FlyMode SwBaseShell::eFrameMode = FLY_DRAG_END; @@ -554,7 +555,25 @@ void SwBaseShell::ExecUndo(SfxRequest &rReq) { for (SwViewShell& rShell : rWrtShell.GetRingContainer()) rShell.LockPaint(); - rWrtShell.Do( SwWrtShell::UNDO, nCnt ); + + sal_uInt16 nUndoOffset = 0; + if (comphelper::LibreOfficeKit::isActive() && !bRepair && nCnt == 1) + { + sw::UndoManager& rManager = rWrtShell.GetDoc()->GetUndoManager(); + const SfxUndoAction* pAction = rManager.GetUndoAction(); + SwView& rView = rWrtShell.GetView(); + ViewShellId nViewShellId = rView.GetViewShellId(); + if (pAction->GetViewShellId() != nViewShellId + && rManager.IsViewUndoActionIndependent(&rView)) + { + // Execute the undo with an offset: don't undo the top action, but an + // earlier one, since it's independent and that belongs to our view. + nUndoOffset = 1; + } + } + + rWrtShell.Do(SwWrtShell::UNDO, nCnt, nUndoOffset); + for (SwViewShell& rShell : rWrtShell.GetRingContainer()) rShell.UnlockPaint(); } diff --git a/sw/source/uibase/wrtsh/wrtundo.cxx b/sw/source/uibase/wrtsh/wrtundo.cxx index 1e92006a232b..b409e4472513 100644 --- a/sw/source/uibase/wrtsh/wrtundo.cxx +++ b/sw/source/uibase/wrtsh/wrtundo.cxx @@ -29,7 +29,7 @@ // Undo ends all modes. If a selection is emerged by the Undo, // this must be considered for further action. -void SwWrtShell::Do( DoType eDoType, sal_uInt16 nCnt ) +void SwWrtShell::Do(DoType eDoType, sal_uInt16 nCnt, sal_uInt16 nOffset) { // #105332# save current state of DoesUndo() bool bSaveDoesUndo = DoesUndo(); @@ -41,7 +41,7 @@ void SwWrtShell::Do( DoType eDoType, sal_uInt16 nCnt ) DoUndo(false); // #i21739# // Reset modes EnterStdMode(); - SwEditShell::Undo(nCnt); + SwEditShell::Undo(nCnt, nOffset); break; case REDO: DoUndo(false); // #i21739# |