diff --git a/README.yrs b/README.yrs index 080a40a41792..66fbe6a68d69 100644 --- a/README.yrs +++ b/README.yrs @@ -16,6 +16,8 @@ Then, put the yrs build directory in autogen.input: `--with-yrs=/path/to/y-crdt` +All the related code should be behind macros in `config_host/config_collab.h` + ### How to run To prevent crashes at runtime, set the environment variable @@ -29,19 +31,100 @@ Currently, communication happens over a hard-coded pipe: (you can also create a new Writer document but that will be boring if all you can do is insert comments into empty doc) -* start another soffice YRSCONNECT=1 with a different user profile, create - new Writer document, and it will connect and load the document from the - other side +* start another soffice with YRSCONNECT=1 with a different user profile, + create new Writer document, and it will connect and load the document from + the other side All sorts of paragraph and character formattings should work inside comments. +Peer cursors should be displayed both in the sw document body and inside +comments. + Inserting hyperlinks also works, although sadly i wasn't able to figure out how to enable the menu items in read-only mode, so it only works in editable mode. -Undo/Redo doesn't work at all, it's disabled in readonly mode anyway. - Switching to editable mode is also possible, but only comment-related editing is synced via yrs, so if other editing operations change the positions of comments, a crash will be inevitable. +### Implementation + +Most of it is in 2 classes: EditDoc and sw::DocumentStateManager (for now); +the latter gets a new member YrsTransactionSupplier. + +DocumentStateManager starts a thread to communicate, and this sends new +messages to the main thread via PostUserEvent(). + +The EditDoc models of the comments are duplicated in a yrs YDocument model +and this is then synced remotely by yrs. + +The structure of the yrs model is: + +* YMap of comments (key: CommentId created from peer id + counter) + - YArray + - anchor pos: 2 or 4 ints [manually updated when editing sw] + - YMap of comment properties + - YText containing mapped EditDoc state +* YMap of cursors (key: peer id) + - either sw cursor: 2 or 4 ints [manually updated when editing sw] + or EditDoc position: CommentId + 2 or 4 ints, or WeakRef + or Y_JSON_NULL (for sw non-text selections, effectively ignored) + +Some confusing object relationships: + +SwAnnotationWin -> Outliner -> OutlinerEditEng -> EditEngine -> ImpEditEngine -> EditDoc + -> OutlinerView -> EditView -> ImpEditView -> EditEngine + + -> SidebarTextControl + +### Undo + +There was no Undo for edits inside comments anyway, only when losing the +focus a SwUndoFieldFromDoc is created. + +There are 2 approaches how Undo could work: either let all the SwUndo +actions modify the yrs model, or use the yrs yundo_manager and ensure that +for every top-level SwUndo there is exactly one item in the yundo_manager's +stack, so that Undo/Redo will have the same effect in the sw model and +the yrs model. + +Let's try if the second approach can be made to work. + +The yundo_manager by default creates stack items based on a timer, so we +configure that to a 2^31 timeout and invoke yundo_manager_stop() to create +all the items manually. + +yundo_manager_undo()/redo() etc internally create a YTransaction and commit +it, which will of course fail if one already exists! + +The yundo_manager creates a new item also for things like cursor movements +(because we put the cursors in the same YDocument as the content); there is +a way to filter the changes by Branch, but that filtering happens when the +undo/redo is invoked, not when the stack item is created - so the +"temporary" stack item is always extended with yrs changes until a "real" +change that has a corresponding SwUndo happens and there is a corresponding +yundo_manager_stop() then. + +There are still some corner cases where the 2 undo stacks aren't synced so +there are various workarounds like DummyUndo action or m_nTempUndoOffset +counter for these. + +Also the SwUndoFieldFromDoc batch action is problematic: it is created when +the comment loses focus, but all editing operations in the comment are +inserted in the yrs model immediately as they happen - so if changes are +received from peers, the creation of the SwUndoFieldFromDoc must be forced +to happen before the peer changes are applied in the sw document model, to +keep the actions in order. + +The comment id is used as a key in yrs ymap so it should be the same when +the Undo or Redo inserts a comment again, hence it's added to SwPostItField. + +For edits that are received from peers, what happens is that all the +received changes must be grouped into one SwUndo (list) action because there +is going to be one yrs undo item; and invoking Undo will just send the +changes to peers and they will create a new undo stack item as the local +instance goes back in the undo stack, and it's not possible to do it +differently because the undo stack items may contain different changes on +each peer. + diff --git a/sfx2/source/control/dispatch.cxx b/sfx2/source/control/dispatch.cxx index 4dba2cd3b4fd..782e61fe7e2f 100644 --- a/sfx2/source/control/dispatch.cxx +++ b/sfx2/source/control/dispatch.cxx @@ -1704,6 +1704,8 @@ bool SfxDispatcher::FindServer_(sal_uInt16 nSlot, SfxSlotServer& rServer) { OUString sCommand = pSlot->GetCommand(); if (sCommand == u".uno:InsertAnnotation"_ustr + || sCommand == u".uno:Undo"_ustr + || sCommand == u".uno:Redo"_ustr || ((sCommand == u".uno:FontDialog"_ustr || sCommand == u".uno:ParagraphDialog"_ustr) && pIFace->GetClassName() == "SwAnnotationShell"_ostr)) diff --git a/sw/inc/IDocumentState.hxx b/sw/inc/IDocumentState.hxx index c05d895f11b1..c3423ce1924b 100644 --- a/sw/inc/IDocumentState.hxx +++ b/sw/inc/IDocumentState.hxx @@ -25,6 +25,7 @@ #include #include #include +class SfxUndoAction; struct SwPosition; class SwPostItField; #endif @@ -61,9 +62,10 @@ public: virtual void YrsInitAcceptor() = 0; virtual void YrsInitConnector(css::uno::Any const& raConnector) = 0; virtual IYrsTransactionSupplier::Mode SetYrsMode(IYrsTransactionSupplier::Mode mode) = 0; - virtual void YrsCommitModified() = 0; + virtual void YrsCommitModified(bool isUnfinishedUndo) = 0; virtual void YrsNotifySetResolved(OString const& rCommentId, SwPostItField const& rField) = 0; + virtual OString YrsGenNewCommentId() = 0; virtual void YrsAddCommentImpl(SwPosition const& rPos, OString const& rCommentId) = 0; virtual void YrsAddComment(SwPosition const& rPos, ::std::optional oAnchorStart, SwPostItField const& rField, bool isInsert) @@ -71,6 +73,9 @@ public: virtual void YrsRemoveCommentImpl(rtl::OString const& rCommentId) = 0; virtual void YrsRemoveComment(SwPosition const& rPos) = 0; virtual void YrsNotifyCursorUpdate() = 0; + virtual void YrsEndUndo() = 0; + virtual void YrsDoUndo(SfxUndoAction const* pUndo) = 0; + virtual void YrsDoRedo(SfxUndoAction const* pUndo) = 0; #endif protected: diff --git a/sw/inc/docufld.hxx b/sw/inc/docufld.hxx index 2183f133bde6..6502c5962ee0 100644 --- a/sw/inc/docufld.hxx +++ b/sw/inc/docufld.hxx @@ -20,6 +20,8 @@ #include +#include + #include #include @@ -464,6 +466,12 @@ class SW_DLLPUBLIC SwPostItField final : public SwField sal_uInt32 m_nParaId; sal_uInt32 m_nParentPostItId; SwMarkName m_sParentName; /// Parent comment's name. +#if ENABLE_YRS + OString m_CommentId; +public: + OString GetYrsCommentId() const { return m_CommentId; } + void SetYrsCommentId(OString const& rCommentId) { m_CommentId = rCommentId; } +#endif public: static sal_uInt32 s_nLastPostItId; diff --git a/sw/source/core/doc/DocumentStateManager.cxx b/sw/source/core/doc/DocumentStateManager.cxx index 7c02b968d809..9cdefcc8125e 100644 --- a/sw/source/core/doc/DocumentStateManager.cxx +++ b/sw/source/core/doc/DocumentStateManager.cxx @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include #include @@ -63,6 +65,12 @@ namespace sw #if ENABLE_YRS using sw::annotation::SwAnnotationWin; +#define yvalidate_undo(rDSM, offset) { \ + SAL_INFO("sw.yrs", "UNDO sw: " << (rDSM).m_rDoc.GetIDocumentUndoRedo().GetUndoActionCount() << " yrs: " << yundo_manager_undo_stack_len((rDSM).m_pYrsSupplier->GetUndoManager()) << " offset: " << offset << " tempoffset: " << (rDSM).m_pYrsSupplier->m_nTempUndoOffset); \ + assert((rDSM).m_rDoc.GetIDocumentUndoRedo().GetUndoActionCount(false) + (offset) == yundo_manager_undo_stack_len((rDSM).m_pYrsSupplier->GetUndoManager()) + (rDSM).m_pYrsSupplier->m_nTempUndoOffset \ + || (rDSM).m_pYrsReader == nullptr); \ +} + namespace { enum class Message : sal_uInt8 @@ -83,19 +91,35 @@ struct YDocDeleter { void operator()(YDoc *const p) const { ydoc_destroy(p); } } return ::std::unique_ptr{pYDoc}; } +// max possible timeout => force combining everything until yundo_manager_stop() +const YUndoManagerOptions g_YUndoOptions{SAL_MAX_INT32}; + +class DummyUndo : public SwUndo +{ +public: + DummyUndo(SwDoc & rDoc) + : SwUndo(SwUndoId::COMPAREDOC, rDoc) + {} + virtual void UndoImpl(::sw::UndoRedoContext &) override {} + virtual void RedoImpl(::sw::UndoRedoContext &) override {} +}; + } // namespace class YrsTransactionSupplier : public IYrsTransactionSupplier { private: friend class DocumentStateManager; + friend class YrsThread; ::std::unique_ptr m_pYDoc; Branch * m_pComments; Branch * m_pCursors; + YUndoManager * m_pUndoManager; YTransaction * m_pCurrentReadTransaction { nullptr }; YTransaction * m_pCurrentWriteTransaction { nullptr }; int m_nLocalComments { 0 }; ::std::map> m_Comments; + int m_nTempUndoOffset { 0 }; ///< for "unfinished" yrs undo (cursor move or EE edit) public: YrsTransactionSupplier(); @@ -104,6 +128,7 @@ public: YDoc* GetYDoc() override { return m_pYDoc.get(); } Branch* GetCommentMap() override { return m_pComments; } Branch* GetCursorMap() override { return m_pCursors; } + YUndoManager * GetUndoManager() { return m_pUndoManager; } YTransaction * GetReadTransaction() override; YTransaction * GetWriteTransaction() override; bool CommitTransaction(bool isForce = false) override; @@ -116,11 +141,15 @@ YrsTransactionSupplier::YrsTransactionSupplier() // ymap implicitly calls transact_mut() , m_pComments(ymap(m_pYDoc.get(), "comments")) , m_pCursors(ymap(m_pYDoc.get(), "cursors")) + , m_pUndoManager(yundo_manager(m_pYDoc.get(), &g_YUndoOptions)) { + yundo_manager_add_scope(m_pUndoManager, m_pComments); } YrsTransactionSupplier::~YrsTransactionSupplier() { + YrsTransactionSupplier::CommitTransaction(true); // yundo_manager_destroy requires no txn exists + yundo_manager_destroy(m_pUndoManager); // with cursors there will always be a pending transaction, and there is no api to cancel it, so just ignore it... //assert(m_pCurrentWriteTransaction == nullptr); (void) m_pCurrentWriteTransaction; @@ -188,11 +217,17 @@ OString YrsTransactionSupplier::GenNewCommentId() return OString::number(id) + OString::number(counter); } +OString DocumentStateManager::YrsGenNewCommentId() +{ + return m_pYrsSupplier->GenNewCommentId(); +} + class YrsThread : public ::salhelper::Thread { public: uno::Reference m_xConnection; DocumentStateManager * m_pDSM; + bool m_hasFirstState{false}; public: YrsThread(uno::Reference const& xConnection, @@ -236,14 +271,23 @@ struct ObserveState YTransaction *const pTxn; }; +struct ObserveCommentState : public ObserveState +{ + bool isUndoAction = false; +}; + extern "C" void observe_comments(void *const pState, uint32_t count, YEvent const*const events) { SAL_INFO("sw.yrs", "YRS observe_comments"); - ObserveState & rState{*static_cast(pState)}; + ObserveCommentState & rState{*static_cast(pState)}; // DO NOT call rState.rYrsSupplier.GetWriteTransaction()! YTransaction *const pTxn{rState.pTxn}; // ??? that is TransactionMut - there is no way to construct YTransaction from it??? YTransaction *const pTxn{pEvent->txn}; + SwWrtShell *const pShell{dynamic_cast(rState.rDoc.getIDocumentLayoutAccess().GetCurrentViewShell())}; + + pShell->StartUndo(SwUndoId::INSDOKUMENT, nullptr); + ::std::vector<::std::tuple> posUpdates; ::std::vector<::std::tuple> startUpdates; ::std::map<::std::pair, std::pair> newComments; @@ -270,14 +314,7 @@ extern "C" void observe_comments(void *const pState, uint32_t count, YEvent cons SwAnnotationWin & rWin{*rState.rYrsSupplier.GetComments().find(commentId)->second.front()->mpPostIt}; rWin.GetOutlinerView()->GetEditView().YrsApplyEEDelta(rState.pTxn, pEvent); - if ((rWin.GetStyle() & WB_DIALOGCONTROL) == 0) - { - rWin.UpdateData(); // not active window, force update - } - else - { // apparently this repaints active window - rWin.GetOutlinerView()->GetEditView().Invalidate(); - } + rWin.UpdateData(); // invalidate, and create SwUndo } break; case Y_ARRAY: @@ -302,40 +339,83 @@ extern "C" void observe_comments(void *const pState, uint32_t count, YEvent cons switch (lenC) { case 2: - yvalidate(pChange[0].tag == Y_EVENT_CHANGE_DELETE); - yvalidate(pChange[0].len == 2); - yvalidate(pChange[1].tag == Y_EVENT_CHANGE_ADD); - yvalidate(pChange[1].len == 2); - yvalidate(pChange[1].values[0].tag == Y_JSON_INT); - yvalidate(pChange[1].values[1].tag == Y_JSON_INT); - posUpdates.emplace_back(commentId, pChange[1].values[0].value.integer, pChange[1].values[1].value.integer); + if (pChange[0].tag == Y_EVENT_CHANGE_DELETE) + { + yvalidate(pChange[0].len == 2); + yvalidate(pChange[1].tag == Y_EVENT_CHANGE_ADD); + yvalidate(pChange[1].len == 2); + yvalidate(pChange[1].values[0].tag == Y_JSON_INT); + yvalidate(pChange[1].values[1].tag == Y_JSON_INT); + posUpdates.emplace_back(commentId, pChange[1].values[0].value.integer, pChange[1].values[1].value.integer); + } + else // Undo can send with different order... + { + yvalidate(pChange[0].tag == Y_EVENT_CHANGE_ADD); + yvalidate(pChange[0].len == 2); + yvalidate(pChange[0].values[0].tag == Y_JSON_INT); + yvalidate(pChange[0].values[1].tag == Y_JSON_INT); + yvalidate(pChange[1].tag == Y_EVENT_CHANGE_DELETE); + yvalidate(pChange[1].len == 2); + posUpdates.emplace_back(commentId, pChange[0].values[0].value.integer, pChange[0].values[1].value.integer); + } break; case 3: yvalidate(pChange[0].tag == Y_EVENT_CHANGE_RETAIN); yvalidate(pChange[0].len == 2); - yvalidate(pChange[1].tag == Y_EVENT_CHANGE_DELETE); - yvalidate(pChange[1].len == 2); - yvalidate(pChange[2].tag == Y_EVENT_CHANGE_ADD); - yvalidate(pChange[2].len == 2); - yvalidate(pChange[2].values[0].tag == Y_JSON_INT); - yvalidate(pChange[2].values[1].tag == Y_JSON_INT); - startUpdates.emplace_back(commentId, pChange[2].values[0].value.integer, pChange[2].values[1].value.integer); + if (pChange[1].tag == Y_EVENT_CHANGE_DELETE) + { + yvalidate(pChange[1].len == 2); + yvalidate(pChange[2].tag == Y_EVENT_CHANGE_ADD); + yvalidate(pChange[2].len == 2); + yvalidate(pChange[2].values[0].tag == Y_JSON_INT); + yvalidate(pChange[2].values[1].tag == Y_JSON_INT); + startUpdates.emplace_back(commentId, pChange[2].values[0].value.integer, pChange[2].values[1].value.integer); + } + else // Undo can send with different order... + { + yvalidate(pChange[1].tag == Y_EVENT_CHANGE_ADD); + yvalidate(pChange[1].len == 2); + yvalidate(pChange[1].values[0].tag == Y_JSON_INT); + yvalidate(pChange[1].values[1].tag == Y_JSON_INT); + yvalidate(pChange[2].tag == Y_EVENT_CHANGE_DELETE); + yvalidate(pChange[2].len == 2); + startUpdates.emplace_back(commentId, pChange[1].values[0].value.integer, pChange[1].values[1].value.integer); + } break; case 4: - yvalidate(pChange[0].tag == Y_EVENT_CHANGE_DELETE); - yvalidate(pChange[0].len == 2); - yvalidate(pChange[1].tag == Y_EVENT_CHANGE_ADD); - yvalidate(pChange[1].len == 2); - yvalidate(pChange[1].values[0].tag == Y_JSON_INT); - yvalidate(pChange[1].values[1].tag == Y_JSON_INT); - yvalidate(pChange[2].tag == Y_EVENT_CHANGE_DELETE); - yvalidate(pChange[2].len == 2); - yvalidate(pChange[3].tag == Y_EVENT_CHANGE_ADD); - yvalidate(pChange[3].len == 2); - yvalidate(pChange[3].values[0].tag == Y_JSON_INT); - yvalidate(pChange[3].values[1].tag == Y_JSON_INT); - posUpdates.emplace_back(commentId, pChange[1].values[0].value.integer, pChange[1].values[1].value.integer); - startUpdates.emplace_back(commentId, pChange[3].values[0].value.integer, pChange[3].values[1].value.integer); + if (pChange[0].tag == Y_EVENT_CHANGE_DELETE) + { + yvalidate(pChange[0].len == 2); + yvalidate(pChange[1].tag == Y_EVENT_CHANGE_ADD); + yvalidate(pChange[1].len == 2); + yvalidate(pChange[1].values[0].tag == Y_JSON_INT); + yvalidate(pChange[1].values[1].tag == Y_JSON_INT); + yvalidate(pChange[2].tag == Y_EVENT_CHANGE_DELETE); + yvalidate(pChange[2].len == 2); + yvalidate(pChange[3].tag == Y_EVENT_CHANGE_ADD); + yvalidate(pChange[3].len == 2); + yvalidate(pChange[3].values[0].tag == Y_JSON_INT); + yvalidate(pChange[3].values[1].tag == Y_JSON_INT); + posUpdates.emplace_back(commentId, pChange[1].values[0].value.integer, pChange[1].values[1].value.integer); + startUpdates.emplace_back(commentId, pChange[3].values[0].value.integer, pChange[3].values[1].value.integer); + } + else // Undo can send with different order... + { + yvalidate(pChange[0].tag == Y_EVENT_CHANGE_ADD); + yvalidate(pChange[0].len == 2); + yvalidate(pChange[0].values[0].tag == Y_JSON_INT); + yvalidate(pChange[0].values[1].tag == Y_JSON_INT); + yvalidate(pChange[1].tag == Y_EVENT_CHANGE_DELETE); + yvalidate(pChange[1].len == 2); + yvalidate(pChange[2].tag == Y_EVENT_CHANGE_ADD); + yvalidate(pChange[2].len == 2); + yvalidate(pChange[2].values[0].tag == Y_JSON_INT); + yvalidate(pChange[2].values[1].tag == Y_JSON_INT); + yvalidate(pChange[3].tag == Y_EVENT_CHANGE_DELETE); + yvalidate(pChange[3].len == 2); + posUpdates.emplace_back(commentId, pChange[0].values[0].value.integer, pChange[0].values[1].value.integer); + startUpdates.emplace_back(commentId, pChange[2].values[0].value.integer, pChange[2].values[1].value.integer); + } break; default: yvalidate(false); @@ -351,6 +431,42 @@ extern "C" void observe_comments(void *const pState, uint32_t count, YEvent cons Branch const*const pMap{ymap_event_target(pEvent)}; uint32_t lenP{0}; YPathSegment *const pPath{ymap_event_path(pEvent, &lenP)}; + + auto const HandleDeleteComment = [=](OString const& rCommentId, + ::std::vector const& rItems) + { + pShell->Push(); + *pShell->GetCursor()->GetPoint() = rItems.front()->GetAnchorPosition(); + pShell->SetMark(); + pShell->Right(SwCursorSkipMode::Chars, true, 1, false, false); + pShell->DelRight(); + pShell->Pop(SwCursorShell::PopMode::DeleteStack); + rState.rDoc.getIDocumentState().YrsRemoveCommentImpl(rCommentId); + }; + + auto const HandleNewComment = [=, &newComments](auto const& rChange) + { + // new comment + yvalidate(pMap == rState.rYrsSupplier.GetCommentMap()); + yvalidate(lenP == 0); + Branch const*const pArray{rChange.new_value->value.y_type}; + yvalidate(yarray_len(pArray) == 3); + ::std::unique_ptr const pPos{yarray_get(pArray, pTxn, 0)}; + yvalidate(pPos->tag == Y_ARRAY); + ::std::unique_ptr const pNode{yarray_get(pPos->value.y_type, pTxn, 0)}; + yvalidate(pNode->tag == Y_JSON_INT); + ::std::unique_ptr const pContent{yarray_get(pPos->value.y_type, pTxn, 1)}; + yvalidate(pContent->tag == Y_JSON_INT); + + if (!newComments.insert({ + {pNode->value.integer, pContent->value.integer}, + {rChange.key, pArray} + }).second) + { + abort(); + } + }; + uint32_t lenK{0}; YEventKeyChange *const pChange{ymap_event_keys(pEvent, &lenK)}; for (decltype(lenK) j = 0; j < lenK; ++j) @@ -370,25 +486,7 @@ extern "C" void observe_comments(void *const pState, uint32_t count, YEvent cons break; case Y_ARRAY: { - // new comment - yvalidate(pMap == rState.rYrsSupplier.GetCommentMap()); - yvalidate(lenP == 0); - Branch const*const pArray{pChange[j].new_value->value.y_type}; - yvalidate(yarray_len(pArray) == 3); - ::std::unique_ptr const pPos{yarray_get(pArray, pTxn, 0)}; - yvalidate(pPos->tag == Y_ARRAY); - ::std::unique_ptr const pNode{yarray_get(pPos->value.y_type, pTxn, 0)}; - yvalidate(pNode->tag == Y_JSON_INT); - ::std::unique_ptr const pContent{yarray_get(pPos->value.y_type, pTxn, 1)}; - yvalidate(pContent->tag == Y_JSON_INT); - - if (!newComments.insert({ - {pNode->value.integer, pContent->value.integer}, - {pChange[j].key, pArray} - }).second) - { - abort(); - } + HandleNewComment(pChange[j]); } break; case Y_MAP: @@ -421,14 +519,7 @@ extern "C" void observe_comments(void *const pState, uint32_t count, YEvent cons OString const commentId{pChange[j].key}; auto const it{rState.rYrsSupplier.GetComments().find(commentId)}; yvalidate(it != rState.rYrsSupplier.GetComments().end()); - SwWrtShell *const pShell{dynamic_cast(rState.rDoc.getIDocumentLayoutAccess().GetCurrentViewShell())}; - pShell->Push(); - *pShell->GetCursor()->GetPoint() = it->second.front()->GetAnchorPosition(); - pShell->SetMark(); - pShell->Right(SwCursorSkipMode::Chars, true, 1, false, false); - pShell->DelRight(); - pShell->Pop(SwCursorShell::PopMode::DeleteStack); - rState.rDoc.getIDocumentState().YrsRemoveCommentImpl(commentId); + HandleDeleteComment(commentId, it->second); break; } default: @@ -437,7 +528,7 @@ extern "C" void observe_comments(void *const pState, uint32_t count, YEvent cons break; case Y_EVENT_KEY_CHANGE_UPDATE: { - OString const prop{pChange[j].key}; + OString const key{pChange[j].key}; switch (pChange[j].new_value->tag) { case Y_JSON_BOOL: @@ -449,15 +540,27 @@ extern "C" void observe_comments(void *const pState, uint32_t count, YEvent cons // in props map... yvalidate(pPath[1].tag == Y_EVENT_PATH_INDEX); yvalidate(pPath[1].value.index == 1); - yvalidate(prop == "resolved"); + yvalidate(key == "resolved"); auto const it{rState.rYrsSupplier.GetComments().find(commentId)}; yvalidate(it != rState.rYrsSupplier.GetComments().end()); + it->second.front()->mpPostIt->SetResolved( pChange[j].new_value->value.flag == Y_TRUE); break; } + case Y_ARRAY: + { + auto const it{rState.rYrsSupplier.GetComments().find(key)}; + if (it != rState.rYrsSupplier.GetComments().end()) + { // comment may or may not exist + // if it does, have to delete it + HandleDeleteComment(key, it->second); + } + HandleNewComment(pChange[j]); + break; + } default: assert(false); } @@ -567,7 +670,6 @@ extern "C" void observe_comments(void *const pState, uint32_t count, YEvent cons yvalidate(it.first.second <= o3tl::make_unsigned(rNode.GetTextNode()->Len())); SwPosition anchorPos{*rNode.GetTextNode(), static_cast(it.first.second)}; SAL_INFO("sw.yrs", "YRS " << anchorPos); - SwWrtShell *const pShell{dynamic_cast(rState.rDoc.getIDocumentLayoutAccess().GetCurrentViewShell())}; SwPostItFieldType* pType = static_cast(pShell->GetFieldType(0, SwFieldIds::Postit)); auto pField{ new SwPostItField{ @@ -582,6 +684,8 @@ extern "C" void observe_comments(void *const pState, uint32_t count, YEvent cons { pField->SetParentPostItId(*oParentId); } + OString const commentId{it.second.first}; + pField->SetYrsCommentId(commentId); pShell->Push(); *pShell->GetCursor()->GetPoint() = anchorPos; @@ -598,10 +702,11 @@ extern "C" void observe_comments(void *const pState, uint32_t count, YEvent cons assert(b); (void)b; pShell->Pop(SwCursorShell::PopMode::DeleteCurrent); --anchorPos.nContent; - OString const commentId{it.second.first}; rState.rDoc.getIDocumentState().YrsAddCommentImpl(anchorPos, commentId); - rState.rYrsSupplier.GetComments().find(commentId)->second.front()->mpPostIt->GetOutlinerView()->GetEditView().YrsReadEEState(rState.pTxn); + SwAnnotationWin & rWin{*rState.rYrsSupplier.GetComments().find(commentId)->second.front()->mpPostIt}; + rWin.GetOutlinerView()->GetEditView().YrsReadEEState(rState.pTxn); + rWin.UpdateData(); // force SwPostItField::mpText for Undo! } // comments inserted, now check position updates for consistency @@ -624,6 +729,11 @@ extern "C" void observe_comments(void *const pState, uint32_t count, YEvent cons yvalidate(o3tl::make_unsigned(pos.GetNodeIndex().get()) == ::std::get<1>(rUpdate)); yvalidate(o3tl::make_unsigned(pos.GetContentIndex()) == ::std::get<2>(rUpdate)); } + + if (pShell->EndUndo() != SwUndoId::EMPTY) + { + rState.isUndoAction = true; + } } struct ObserveCursorState : public ObserveState @@ -1017,7 +1127,32 @@ IMPL_LINK(YrsThread, HandleMessage, void*, pVoid, void) } case ::std::underlying_type_t(Message::SendStateDiff): { + if (m_pDSM->m_pYrsSupplier->m_nTempUndoOffset != 0) + { + SAL_INFO("sw.yrs", "pre apply update: finish EE undo"); + yvalidate_undo(*m_pDSM, 0); + // prevent our yrs undo from being combined with peer yrs undo + m_pDSM->m_pYrsSupplier->CommitTransaction(); + yundo_manager_stop(m_pDSM->m_pYrsSupplier->GetUndoManager()); + // reset it before creating the SwUndo - not ideal but there + // is currently nothing to check it before the SwUndo and + // YrsEndUndo() then checks it + m_pDSM->m_pYrsSupplier->m_nTempUndoOffset = 0; + SwWrtShell *const pShell{dynamic_cast(m_pDSM->m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell())}; + if (sw::annotation::SwAnnotationWin *const pWin{ + pShell->GetView().GetPostItMgr()->GetActiveSidebarWin()}) + { + // note: this does YrsEndUndo() and thus possibly + // YrsCommitModified() but that should be a no-op because + // we called it already (unconditionally!) + pWin->UpdateData(); + } + else + // this should not be possible? LoseFocus calls YrsEndUndo + assert(false); + } SAL_INFO("sw.yrs", "apply update: " << yupdate_debug_v1(reinterpret_cast(pBuf->begin()) + 1, length - 1)); + yvalidate_undo(*m_pDSM, 0); YTransaction *const pTxn{m_pDSM->m_pYrsSupplier->GetWriteTransaction()}; m_pDSM->m_pYrsSupplier->SetMode(IYrsTransactionSupplier::Mode::Replay); auto const err = ytransaction_apply(pTxn, reinterpret_cast(pBuf->begin()) + 1, length - 1); @@ -1028,16 +1163,38 @@ IMPL_LINK(YrsThread, HandleMessage, void*, pVoid, void) } // let's have one observe_deep instead of a observe on every // YText - need to be notified on new comments being created... - ObserveState state{*m_pDSM->m_pYrsSupplier, m_pDSM->m_rDoc, pTxn}; + ObserveCommentState state{*m_pDSM->m_pYrsSupplier, m_pDSM->m_rDoc, pTxn, false}; YSubscription *const pSubComments = yobserve_deep(m_pDSM->m_pYrsSupplier->GetCommentMap(), &state, observe_comments); ObserveCursorState cursorState{*m_pDSM->m_pYrsSupplier, m_pDSM->m_rDoc, pTxn, {}}; // not sure if yweak_observe would work for (weakref) cursors YSubscription *const pSubCursors = yobserve_deep(m_pDSM->m_pYrsSupplier->GetCursorMap(), &cursorState, observe_cursors); + auto const undos{yundo_manager_undo_stack_len(m_pDSM->m_pYrsSupplier->GetUndoManager())}; m_pDSM->m_pYrsSupplier->CommitTransaction(true); + if (state.isUndoAction) + { + yundo_manager_stop(m_pDSM->m_pYrsSupplier->GetUndoManager()); + } + else if (undos != yundo_manager_undo_stack_len(m_pDSM->m_pYrsSupplier->GetUndoManager())) + { + // argh, something changed but nothing changed! + // example: both peers undo a delete comment then sync. + // add a dummy SwUndo to match the yrs one. + yundo_manager_stop(m_pDSM->m_pYrsSupplier->GetUndoManager()); + m_pDSM->m_rDoc.GetIDocumentUndoRedo().AppendUndo( + ::std::make_unique(m_pDSM->m_rDoc)); + } YrsCursorUpdates(cursorState); + yvalidate_undo(*m_pDSM, 0); m_pDSM->m_pYrsSupplier->SetMode(IYrsTransactionSupplier::Mode::Edit); yunobserve(pSubComments); yunobserve(pSubCursors); + if (!m_hasFirstState) + { + // the first message contains the "initial state" - don't allow Undo before that + m_pDSM->m_rDoc.GetIDocumentUndoRedo().DelAllUndoObj(); + yundo_manager_clear(m_pDSM->m_pYrsSupplier->GetUndoManager()); + m_hasFirstState = true; + } break; } } @@ -1061,10 +1218,13 @@ void DocumentStateManager::YrsNotifySetResolved(OString const& rCommentId, SwPos ::std::unique_ptr const pProps{yarray_get(pComment->value.y_type, pTxn, 1)}; YInput const resolved{yinput_bool(rField.GetResolved() ? Y_TRUE : Y_FALSE)}; ymap_insert(pProps->value.y_type, pTxn, "resolved", &resolved); - YrsCommitModified(); + YrsCommitModified(false); + yundo_manager_stop(m_pYrsSupplier->GetUndoManager()); + // UpdateData() is called later than this + yvalidate_undo(*this, 1); } -void DocumentStateManager::YrsAddCommentImpl(SwPosition const& rAnchorPos, OString const& commentId) +void DocumentStateManager::YrsAddCommentImpl(SwPosition const& rAnchorPos, OString const& rCommentId) { SAL_INFO("sw.yrs", "YRS AddCommentImpl"); ::std::vector items; @@ -1081,11 +1241,11 @@ void DocumentStateManager::YrsAddCommentImpl(SwPosition const& rAnchorPos, OStri SwAnnotationWin *const pWin{ rShell.GetPostItMgr()->GetOrCreateAnnotationWindow(*it, isNew)}; assert(pWin); - pWin->GetOutlinerView()->GetEditView().SetYrsCommentId(m_pYrsSupplier.get(), commentId); + pWin->GetOutlinerView()->GetEditView().SetYrsCommentId(m_pYrsSupplier.get(), rCommentId); } } } - m_pYrsSupplier->m_Comments.emplace(commentId, items); + m_pYrsSupplier->m_Comments.emplace(rCommentId, items); } void DocumentStateManager::YrsAddComment(SwPosition const& rPos, @@ -1093,7 +1253,8 @@ void DocumentStateManager::YrsAddComment(SwPosition const& rPos, bool const isInsert) { SAL_INFO("sw.yrs", "YRS AddComment " << rPos); - OString const commentId{m_pYrsSupplier->GenNewCommentId()}; + OString const commentId{rField.GetYrsCommentId()}; + assert(!commentId.isEmpty()); // this calls EditViewInvalidate so prevent destroying pTxn YrsAddCommentImpl(rPos, commentId); YTransaction *const pTxn{m_pYrsSupplier->GetWriteTransaction()}; @@ -1189,6 +1350,13 @@ void DocumentStateManager::YrsAddComment(SwPosition const& rPos, // either update the cursors here, or wait for round-trip? //do it in 1 caller so that load document can batch it? CommitModified(); // SetModified is called earlier + if (isInsert) + { + // stop causes commit to create a new undo stack item + YrsCommitModified(false); + yundo_manager_stop(m_pYrsSupplier->GetUndoManager()); + yvalidate_undo(*this, 0); + } } void DocumentStateManager::YrsRemoveCommentImpl(OString const& rCommentId) @@ -1210,7 +1378,7 @@ void DocumentStateManager::YrsRemoveComment(SwPosition const& rPos) OString const commentId{it2->first}; YrsRemoveCommentImpl(commentId); YTransaction *const pTxn{m_pYrsSupplier->GetWriteTransaction()}; - if (!pTxn) + if (!pTxn || !m_rDoc.GetIDocumentUndoRedo().DoesUndo()) { return; } @@ -1267,9 +1435,50 @@ void DocumentStateManager::YrsRemoveComment(SwPosition const& rPos) yarray_insert_range(pPos->value.y_type, pTxn, 2, posArray, 2); } } + YrsCommitModified(false); + yundo_manager_stop(m_pYrsSupplier->GetUndoManager()); + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { // SwUndoDelete is being constructed on stack and not yet inserted! + yvalidate_undo(*this, 1); + } // either update the cursors here, or wait for round-trip? } +void DocumentStateManager::YrsEndUndo() +{ + if (m_pYrsSupplier->m_Mode == IYrsTransactionSupplier::Mode::Edit) + { + YrsCommitModified(false); + yundo_manager_stop(m_pYrsSupplier->GetUndoManager()); + yvalidate_undo(*this, 0); + } +} + +void DocumentStateManager::YrsDoUndo(SfxUndoAction const*const pUndo) +{ + YrsCommitModified(false); + // this creates a transaction and commits + yvalidate(yundo_manager_undo(m_pYrsSupplier->GetUndoManager()) == Y_TRUE + // ugly: yundo_manager_undo returns false both for nothing happened + // and for error so manually ignore the case where it's expected + // nothing happens + || dynamic_cast(pUndo) != nullptr + || !m_pYrsReader.is()); // CppunitTest_sw_core_docnode asserts +//implicit yundo_manager_stop(m_pYrsSupplier->GetUndoManager()); + yvalidate_undo(*this, 0); +} + +void DocumentStateManager::YrsDoRedo(SfxUndoAction const*const pUndo) +{ + YrsCommitModified(false); + // this creates a transaction and commits + yvalidate(yundo_manager_redo(m_pYrsSupplier->GetUndoManager()) == Y_TRUE + || dynamic_cast(pUndo) != nullptr + || !m_pYrsReader.is()); // CppunitTest_sw_core_docnode asserts +//implicit yundo_manager_stop(m_pYrsSupplier->GetUndoManager()); + yvalidate_undo(*this, 0); +} + void DocumentStateManager::YrsNotifyCursorUpdate() { SwWrtShell *const pShell{dynamic_cast(m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell())}; @@ -1303,7 +1512,7 @@ void DocumentStateManager::YrsNotifyCursorUpdate() #if ENABLE_YRS_WEAK if (pWin->GetOutlinerView()->GetEditView().YrsWriteEECursor(pTxn, *pEntry->value.y_type, pCurrent.get())) { - YrsCommitModified(); + YrsCommitModified(true); } #else ESelection const sel{pWin->GetOutlinerView()->GetSelection()}; @@ -1327,7 +1536,7 @@ void DocumentStateManager::YrsNotifyCursorUpdate() YInput const input{yinput_yarray(positions.data(), 5)}; yarray_remove_range(pEntry->value.y_type, pTxn, 1, 1); yarray_insert_range(pEntry->value.y_type, pTxn, 1, &input, 1); - YrsCommitModified(); + YrsCommitModified(true); } } else @@ -1341,7 +1550,7 @@ void DocumentStateManager::YrsNotifyCursorUpdate() YInput const input{yinput_yarray(positions.data(), 3)}; yarray_remove_range(pEntry->value.y_type, pTxn, 1, 1); yarray_insert_range(pEntry->value.y_type, pTxn, 1, &input, 1); - YrsCommitModified(); + YrsCommitModified(true); } } #endif @@ -1356,7 +1565,7 @@ void DocumentStateManager::YrsNotifyCursorUpdate() YInput const input{yinput_null()}; yarray_remove_range(pEntry->value.y_type, pTxn, 1, 1); yarray_insert_range(pEntry->value.y_type, pTxn, 1, &input, 1); - YrsCommitModified(); + YrsCommitModified(true); } } else @@ -1379,7 +1588,7 @@ void DocumentStateManager::YrsNotifyCursorUpdate() YInput const input{yinput_yarray(positions.data(), 4)}; yarray_remove_range(pEntry->value.y_type, pTxn, 1, 1); yarray_insert_range(pEntry->value.y_type, pTxn, 1, &input, 1); - YrsCommitModified(); + YrsCommitModified(true); } } else @@ -1395,7 +1604,7 @@ void DocumentStateManager::YrsNotifyCursorUpdate() YInput const input{yinput_yarray(positions.data(), 2)}; yarray_remove_range(pEntry->value.y_type, pTxn, 1, 1); yarray_insert_range(pEntry->value.y_type, pTxn, 1, &input, 1); - YrsCommitModified(); + YrsCommitModified(true); } } } @@ -1492,6 +1701,9 @@ void DocumentStateManager::YrsInitAcceptor() } YrsAddComment(pos, oAnchorStart, rField, false); } + YrsCommitModified(false); // no txn for undo! + m_pYrsReader->m_hasFirstState = true; + yundo_manager_clear(m_pYrsSupplier->GetUndoManager()); // initiate sync of comments to other side //AddComment would have done this m_pYrsSupplier->GetWriteTransaction(); YrsNotifyCursorUpdate(); @@ -1576,29 +1788,39 @@ void DocumentStateManager::SetModified() if( m_rDoc.GetAutoCorrExceptWord() && !m_rDoc.GetAutoCorrExceptWord()->IsDeleted() ) m_rDoc.DeleteAutoCorrExceptWord(); - -#if ENABLE_YRS - SAL_INFO("sw.yrs", "YRS SetModified"); - YrsCommitModified(); -#endif } #if ENABLE_YRS -void DocumentStateManager::YrsCommitModified() +void DocumentStateManager::YrsCommitModified(bool const isUnfinishedUndo) { - if (m_pYrsSupplier->CommitTransaction() && m_pYrsReader.is()) + auto const undos{yundo_manager_undo_stack_len(m_pYrsSupplier->GetUndoManager())}; + if (!isUnfinishedUndo) { - uno::Sequence buf(5); - sal_Int8 * it{buf.getArray()}; - writeLength(it, 1); - *it = ::std::underlying_type_t(Message::RequestStateVector); - try { - m_pYrsReader->m_xConnection->write(buf); - } - catch (io::IOException const&) + m_pYrsSupplier->m_nTempUndoOffset = 0; + } + if (m_pYrsSupplier->CommitTransaction()) + { + if (isUnfinishedUndo) { - TOOLS_WARN_EXCEPTION("sw", "YRS CommitTransaction"); - m_pYrsReader->m_xConnection->close(); + if (undos != yundo_manager_undo_stack_len(m_pYrsSupplier->GetUndoManager())) + { + m_pYrsSupplier->m_nTempUndoOffset = -1; + } + } + if (m_pYrsReader.is()) + { + uno::Sequence buf(5); + sal_Int8 * it{buf.getArray()}; + writeLength(it, 1); + *it = ::std::underlying_type_t(Message::RequestStateVector); + try { + m_pYrsReader->m_xConnection->write(buf); + } + catch (io::IOException const&) + { + TOOLS_WARN_EXCEPTION("sw", "YRS CommitTransaction"); + m_pYrsReader->m_xConnection->close(); + } } } } diff --git a/sw/source/core/fields/docufld.cxx b/sw/source/core/fields/docufld.cxx index 67cffc40bad7..b1b32f188ebe 100644 --- a/sw/source/core/fields/docufld.cxx +++ b/sw/source/core/fields/docufld.cxx @@ -1858,6 +1858,10 @@ std::unique_ptr SwPostItField::Copy() const if (mpText) pRet->SetTextObject( *mpText ); +#if ENABLE_YRS + pRet->SetYrsCommentId(m_CommentId); +#endif + // Note: member not copied. return std::unique_ptr(pRet.release()); diff --git a/sw/source/core/inc/DocumentStateManager.hxx b/sw/source/core/inc/DocumentStateManager.hxx index 8245796bac95..ffade6f23613 100644 --- a/sw/source/core/inc/DocumentStateManager.hxx +++ b/sw/source/core/inc/DocumentStateManager.hxx @@ -79,15 +79,19 @@ public: void YrsInitAcceptor() override; void YrsInitConnector(css::uno::Any const& raConnector) override; IYrsTransactionSupplier::Mode SetYrsMode(IYrsTransactionSupplier::Mode mode) override; - void YrsCommitModified() override; + void YrsCommitModified(bool isUnfinishedUndo) override; void YrsNotifySetResolved(OString const& rCommentId, SwPostItField const& rField) override; + OString YrsGenNewCommentId() override; void YrsAddCommentImpl(SwPosition const& rPos, OString const& rCommentId) override; void YrsAddComment(SwPosition const& rPos, ::std::optional oAnchorStart, SwPostItField const& rField, bool isInsert) override; void YrsRemoveCommentImpl(OString const& rCommentId) override; void YrsRemoveComment(SwPosition const& rPos) override; void YrsNotifyCursorUpdate() override; + void YrsEndUndo() override; + void YrsDoUndo(SfxUndoAction const* pUndo) override; + void YrsDoRedo(SfxUndoAction const* pUndo) override; #endif }; diff --git a/sw/source/core/undo/docundo.cxx b/sw/source/core/undo/docundo.cxx index 56a068890239..8f9fc0e4e464 100644 --- a/sw/source/core/undo/docundo.cxx +++ b/sw/source/core/undo/docundo.cxx @@ -702,11 +702,30 @@ bool UndoManager::impl_DoUndoRedo(UndoOrRedoType undoOrRedo, size_t nUndoOffset) // N.B. these may throw! if (UndoOrRedoType::Undo == undoOrRedo) { +#if ENABLE_YRS + SfxUndoAction * pUndo{GetUndoActionCount() ? GetUndoAction() : nullptr}; + if (auto const*const pListAction = dynamic_cast(pUndo)) + { + if (pListAction->GetId() == sal_uInt16(SwUndoId::HEADER_FOOTER)) + { // urgh! argh! this one destroys itself! aieeeee! + pUndo = nullptr; + } + } +#endif bRet = SdrUndoManager::UndoWithContext(context); +#if ENABLE_YRS + m_rState.YrsDoUndo(pUndo); +#endif } else { +#if ENABLE_YRS + SfxUndoAction *const pUndo{GetRedoActionCount() ? GetRedoAction() : nullptr}; +#endif bRet = SdrUndoManager::RedoWithContext(context); +#if ENABLE_YRS + m_rState.YrsDoRedo(pUndo); +#endif } if (bRet) diff --git a/sw/source/core/undo/rolbck.cxx b/sw/source/core/undo/rolbck.cxx index 7906e9496fd4..c961e6d539b6 100644 --- a/sw/source/core/undo/rolbck.cxx +++ b/sw/source/core/undo/rolbck.cxx @@ -59,6 +59,7 @@ #include #include #include + #include #include @@ -360,11 +361,10 @@ void SwHistorySetTextField::SetInDoc( SwDoc& rDoc, bool ) if (m_nFieldWhich == SwFieldIds::Postit) { SwPosition const pos{*pTextNd, m_nPos}; - pTextNd->GetDoc().getIDocumentState().YrsAddComment( - pos, {}, // FIXME no way to get anchor start here? - static_cast(*pTextNd->GetFieldTextAttrAt(pos.GetContentIndex(), - ::sw::GetTextAttrMode::Default)->GetFormatField().GetField()), - true); + // do use the same comment id because it's a ymap key! + OString const commentId{static_cast(m_pField->GetField())->GetYrsCommentId()}; + assert(!commentId.isEmpty()); + pTextNd->GetDoc().getIDocumentState().YrsAddCommentImpl(pos, commentId); } #endif } diff --git a/sw/source/core/undo/unattr.cxx b/sw/source/core/undo/unattr.cxx index 0fb796de9a2c..d54b37e7c999 100644 --- a/sw/source/core/undo/unattr.cxx +++ b/sw/source/core/undo/unattr.cxx @@ -59,6 +59,11 @@ #include #include +#if ENABLE_YRS +#include +#endif + + SwUndoFormatAttrHelper::SwUndoFormatAttrHelper(SwFormat& rFormat, bool bSvDrwPt) : SwClient(&rFormat) , m_rFormat(rFormat) @@ -927,6 +932,16 @@ void SwUndoAttr::redoAttribute(SwPaM& rPam, const sw::UndoRedoContext & rContext rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); } else { rDoc.getIDocumentContentOperations().InsertItemSet( rPam, m_AttrSet, m_nInsertFlags ); +#if ENABLE_YRS + SwFormatField const*const pItem{m_AttrSet.GetItemIfSet(RES_TXTATR_ANNOTATION, false)}; + if (pItem != nullptr && pItem->GetField()->Which() == SwFieldIds::Postit) + { + SwPosition const pos{rPam.GetPoint()->nContent, -1}; + OString const commentId{static_cast(pItem->GetField())->GetYrsCommentId()}; + assert(!commentId.isEmpty()); + rDoc.getIDocumentState().YrsAddCommentImpl(pos, commentId); + } +#endif } } diff --git a/sw/source/core/unocore/unofield.cxx b/sw/source/core/unocore/unofield.cxx index 746d21b1509f..e9d70d74d236 100644 --- a/sw/source/core/unocore/unofield.cxx +++ b/sw/source/core/unocore/unofield.cxx @@ -1394,6 +1394,9 @@ void SAL_CALL SwXTextField::attach( pPostItField->SetTextObject( m_pImpl->m_xTextObject->CreateText() ); pPostItField->SetPar2(m_pImpl->m_xTextObject->GetText()); } +#if ENABLE_YRS + pPostItField->SetYrsCommentId(pDoc->getIDocumentState().YrsGenNewCommentId()); +#endif xField.reset(pPostItField); } break; diff --git a/sw/source/uibase/docvw/AnnotationWin.cxx b/sw/source/uibase/docvw/AnnotationWin.cxx index 25d3dfdecf8b..21a79b078755 100644 --- a/sw/source/uibase/docvw/AnnotationWin.cxx +++ b/sw/source/uibase/docvw/AnnotationWin.cxx @@ -261,15 +261,18 @@ void SwAnnotationWin::SetResolved(bool resolved) mxMetadataResolved->hide(); if(IsResolved() != oldState) + { mbResolvedStateUpdated = true; +#if ENABLE_YRS + // for undo, before UpdateData() + mrView.GetDocShell()->GetDoc()->getIDocumentState().YrsNotifySetResolved( + GetOutlinerView()->GetEditView().GetYrsCommentId(), + *static_cast(mpFormatField->GetField())); +#endif + } UpdateData(); Invalidate(); collectUIInformation(u"SETRESOLVED"_ustr,get_id()); -#if ENABLE_YRS - mrView.GetDocShell()->GetDoc()->getIDocumentState().YrsNotifySetResolved( - GetOutlinerView()->GetEditView().GetYrsCommentId(), - *static_cast(mpFormatField->GetField())); -#endif } void SwAnnotationWin::ToggleResolved() @@ -376,6 +379,9 @@ void SwAnnotationWin::UpdateData() SwPosition aPosition( pTextField->GetTextNode(), pTextField->GetStart() ); rUndoRedo.AppendUndo( std::make_unique(aPosition, *pOldField, *mpField, true)); +#if ENABLE_YRS + mrView.GetDocShell()->GetDoc()->getIDocumentState().YrsEndUndo(); +#endif } // so we get a new layout of notes (anchor position is still the same and we would otherwise not get one) mrMgr.SetLayout(); @@ -503,6 +509,9 @@ void SwAnnotationWin::InitAnswer(OutlinerParaObject const & rText) SwPosition aPosition( pTextField->GetTextNode(), pTextField->GetStart() ); rUndoRedo.AppendUndo( std::make_unique(aPosition, *pOldField, *mpField, true)); +#if ENABLE_YRS +// no! there is a StartUndo wrapping this mrView.GetDocShell()->GetDoc()->getIDocumentState().YrsEndUndo(); +#endif } mpOutliner->SetModifyHdl( LINK( this, SwAnnotationWin, ModifyHdl ) ); mpOutliner->ClearModifyFlag(); diff --git a/sw/source/uibase/docvw/SidebarTxtControl.cxx b/sw/source/uibase/docvw/SidebarTxtControl.cxx index 945bbecfd11a..56554988499c 100644 --- a/sw/source/uibase/docvw/SidebarTxtControl.cxx +++ b/sw/source/uibase/docvw/SidebarTxtControl.cxx @@ -208,7 +208,7 @@ void SidebarTextControl::EditViewInvalidate(const tools::Rectangle& rRect) { SAL_INFO("sw.yrs", "YRS EditViewInvalidate"); mrDocView.GetDocShell()->GetDoc()->getIDocumentState().YrsNotifyCursorUpdate(); - mrDocView.GetDocShell()->GetDoc()->getIDocumentState().YrsCommitModified(); + mrDocView.GetDocShell()->GetDoc()->getIDocumentState().YrsCommitModified(true); return WeldEditView::EditViewInvalidate(rRect); } diff --git a/sw/source/uibase/fldui/fldmgr.cxx b/sw/source/uibase/fldui/fldmgr.cxx index a150cb86b498..27ceba387d23 100644 --- a/sw/source/uibase/fldui/fldmgr.cxx +++ b/sw/source/uibase/fldui/fldmgr.cxx @@ -921,6 +921,9 @@ bool SwFieldMgr::InsertField( pPostItField->SetParentPostItId(std::get<1>(*rData.m_oParentId)); pPostItField->SetParentName(std::get<2>(*rData.m_oParentId)); } +#if ENABLE_YRS + pPostItField->SetYrsCommentId(pCurShell->GetDoc()->getIDocumentState().YrsGenNewCommentId()); +#endif pField.reset(pPostItField); } break; @@ -1591,7 +1594,6 @@ bool SwFieldMgr::InsertField( pos, oAnchorStart, static_cast(*SwCursorShell::GetTextFieldAtPos(&pos, ::sw::GetTextAttrMode::Default)->GetFormatField().GetField()), true); - pCurShell->GetDoc()->getIDocumentState().YrsCommitModified(); } #endif