LOCRDT sfx2,sw: Undo
Finally, add the comment id to SwPostItField too, so that undo actions can recreate the comment with the same id. Ensure that all inserted comments have an id (including via UNO API) so that the Undo can record it (SwHistorySetTextField/SwUndoAttr). Also this requires a more careful management of committing the YTransaction, to distinguish between creating new Undo stack items and extending the existing one. Change-Id: I986264dee956ec4b219ab8c7148d44fee4bbfa2b Reviewed-on: https://gerrit.libreoffice.org/c/core/+/185866 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.stahl@allotropia.de>
This commit is contained in:
+88
-5
@@ -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.
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <com/sun/star/uno/Any.h>
|
||||
#include <editeng/yrstransactionsupplier.hxx>
|
||||
#include <optional>
|
||||
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<SwPosition> 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:
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
#include <sal/config.h>
|
||||
|
||||
#include <config_collab.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <editeng/outlobj.hxx>
|
||||
@@ -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;
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
#include <IDocumentFieldsAccess.hxx>
|
||||
#include <txtannotationfld.hxx>
|
||||
#include <ndtxt.hxx>
|
||||
#include <undobj.hxx>
|
||||
#include <rolbck.hxx>
|
||||
|
||||
#include <editeng/yrs.hxx>
|
||||
#include <editeng/editview.hxx>
|
||||
@@ -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<YDoc, YDocDeleter>{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<YDoc, YDocDeleter> 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<OString, ::std::vector<SwAnnotationItem *>> 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<connection::XConnection> m_xConnection;
|
||||
DocumentStateManager * m_pDSM;
|
||||
bool m_hasFirstState{false};
|
||||
|
||||
public:
|
||||
YrsThread(uno::Reference<connection::XConnection> 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<ObserveState*>(pState)};
|
||||
ObserveCommentState & rState{*static_cast<ObserveCommentState*>(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<SwWrtShell*>(rState.rDoc.getIDocumentLayoutAccess().GetCurrentViewShell())};
|
||||
|
||||
pShell->StartUndo(SwUndoId::INSDOKUMENT, nullptr);
|
||||
|
||||
::std::vector<::std::tuple<OString, uint64_t, uint64_t>> posUpdates;
|
||||
::std::vector<::std::tuple<OString, uint64_t, uint64_t>> startUpdates;
|
||||
::std::map<::std::pair<uint64_t, uint64_t>, std::pair<OString, Branch const*>> 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<SwAnnotationItem *> 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<YOutput, YOutputDeleter> const pPos{yarray_get(pArray, pTxn, 0)};
|
||||
yvalidate(pPos->tag == Y_ARRAY);
|
||||
::std::unique_ptr<YOutput, YOutputDeleter> const pNode{yarray_get(pPos->value.y_type, pTxn, 0)};
|
||||
yvalidate(pNode->tag == Y_JSON_INT);
|
||||
::std::unique_ptr<YOutput, YOutputDeleter> 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<YOutput, YOutputDeleter> const pPos{yarray_get(pArray, pTxn, 0)};
|
||||
yvalidate(pPos->tag == Y_ARRAY);
|
||||
::std::unique_ptr<YOutput, YOutputDeleter> const pNode{yarray_get(pPos->value.y_type, pTxn, 0)};
|
||||
yvalidate(pNode->tag == Y_JSON_INT);
|
||||
::std::unique_ptr<YOutput, YOutputDeleter> 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<SwWrtShell*>(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<sal_Int32>(it.first.second)};
|
||||
SAL_INFO("sw.yrs", "YRS " << anchorPos);
|
||||
SwWrtShell *const pShell{dynamic_cast<SwWrtShell*>(rState.rDoc.getIDocumentLayoutAccess().GetCurrentViewShell())};
|
||||
SwPostItFieldType* pType = static_cast<SwPostItFieldType*>(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>(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<SwWrtShell*>(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<char const*>(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<char const*>(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<DummyUndo>(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<YOutput, YOutputDeleter> 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<SwAnnotationItem *> 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<DummyUndo const*>(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<DummyUndo const*>(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<SwWrtShell*>(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<sal_Int8> buf(5);
|
||||
sal_Int8 * it{buf.getArray()};
|
||||
writeLength(it, 1);
|
||||
*it = ::std::underlying_type_t<Message>(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<sal_Int8> buf(5);
|
||||
sal_Int8 * it{buf.getArray()};
|
||||
writeLength(it, 1);
|
||||
*it = ::std::underlying_type_t<Message>(Message::RequestStateVector);
|
||||
try {
|
||||
m_pYrsReader->m_xConnection->write(buf);
|
||||
}
|
||||
catch (io::IOException const&)
|
||||
{
|
||||
TOOLS_WARN_EXCEPTION("sw", "YRS CommitTransaction");
|
||||
m_pYrsReader->m_xConnection->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1858,6 +1858,10 @@ std::unique_ptr<SwField> SwPostItField::Copy() const
|
||||
if (mpText)
|
||||
pRet->SetTextObject( *mpText );
|
||||
|
||||
#if ENABLE_YRS
|
||||
pRet->SetYrsCommentId(m_CommentId);
|
||||
#endif
|
||||
|
||||
// Note: member <m_xTextObject> not copied.
|
||||
|
||||
return std::unique_ptr<SwField>(pRet.release());
|
||||
|
||||
@@ -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<SwPosition> 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
|
||||
};
|
||||
|
||||
|
||||
@@ -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<SfxListUndoAction*>(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)
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
#include <strings.hrc>
|
||||
#include <bookmark.hxx>
|
||||
#include <frameformats.hxx>
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
@@ -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<SwPostItField const&>(*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<SwPostItField const*>(m_pField->GetField())->GetYrsCommentId()};
|
||||
assert(!commentId.isEmpty());
|
||||
pTextNd->GetDoc().getIDocumentState().YrsAddCommentImpl(pos, commentId);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -59,6 +59,11 @@
|
||||
#include <frameformats.hxx>
|
||||
#include <editsh.hxx>
|
||||
|
||||
#if ENABLE_YRS
|
||||
#include <docufld.hxx>
|
||||
#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<SwPostItField const*>(pItem->GetField())->GetYrsCommentId()};
|
||||
assert(!commentId.isEmpty());
|
||||
rDoc.getIDocumentState().YrsAddCommentImpl(pos, commentId);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<SwPostItField const*>(mpFormatField->GetField()));
|
||||
#endif
|
||||
}
|
||||
UpdateData();
|
||||
Invalidate();
|
||||
collectUIInformation(u"SETRESOLVED"_ustr,get_id());
|
||||
#if ENABLE_YRS
|
||||
mrView.GetDocShell()->GetDoc()->getIDocumentState().YrsNotifySetResolved(
|
||||
GetOutlinerView()->GetEditView().GetYrsCommentId(),
|
||||
*static_cast<SwPostItField const*>(mpFormatField->GetField()));
|
||||
#endif
|
||||
}
|
||||
|
||||
void SwAnnotationWin::ToggleResolved()
|
||||
@@ -376,6 +379,9 @@ void SwAnnotationWin::UpdateData()
|
||||
SwPosition aPosition( pTextField->GetTextNode(), pTextField->GetStart() );
|
||||
rUndoRedo.AppendUndo(
|
||||
std::make_unique<SwUndoFieldFromDoc>(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<SwUndoFieldFromDoc>(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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<SwPostItField const&>(*SwCursorShell::GetTextFieldAtPos(&pos, ::sw::GetTextAttrMode::Default)->GetFormatField().GetField()),
|
||||
true);
|
||||
pCurShell->GetDoc()->getIDocumentState().YrsCommitModified();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
Reference in New Issue
Block a user