sw/inc/IDocumentState.hxx | 1 sw/inc/crsrsh.hxx | 34 ++ sw/inc/viscrs.hxx | 9 sw/source/core/crsr/crsrsh.cxx | 235 ++++++++++++++++--- sw/source/core/crsr/viscrs.cxx | 80 ++++-- sw/source/core/doc/DocumentStateManager.cxx | 346 +++++++++++++++++++++++++++- sw/source/core/doc/doccorr.cxx | 23 + sw/source/core/inc/DocumentStateManager.hxx | 1 8 files changed, 667 insertions(+), 62 deletions(-)
New commits: commit 910ccfe6a35c1a039196e29defa7074d35158e3a Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Wed Jan 29 11:38:26 2025 +0100 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Thu May 8 14:45:23 2025 +0200 LOCRDT sw: yrs peer cursors Factor out a class sw::VisibleCursorState containing all data required for a peer cursor. Let SwCursorShell::UpdateCursor() etc correct also the peer cursors locally in the sw shells, else the cursors will be painted in the wrong place; every peer updates its own cursor in the yrs model. Split SwVisibleCursor::SetPosAndShow() into 2 functions, to find the new position, and actually paint things. Peer cursors are not VCL cursors (they don't need to blink) but painted in SwSelPaintRects::Show() in color via drawinglayer. Change-Id: I21ae8cb3fd76759a10b5c226cf3294a5c9fc6451 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/180900 Reviewed-by: Michael Stahl <michael.st...@allotropia.de> Tested-by: Jenkins diff --git a/sw/inc/IDocumentState.hxx b/sw/inc/IDocumentState.hxx index bfacc071df9f..31b60078ddf2 100644 --- a/sw/inc/IDocumentState.hxx +++ b/sw/inc/IDocumentState.hxx @@ -68,6 +68,7 @@ public: = 0; virtual void YrsRemoveCommentImpl(rtl::OString const& rCommentId) = 0; virtual void YrsRemoveComment(SwPosition const& rPos, rtl::OString const& rCommentId) = 0; + virtual void YrsNotifyCursorUpdate() = 0; #endif protected: diff --git a/sw/inc/crsrsh.hxx b/sw/inc/crsrsh.hxx index 5f0317250484..50f00e4d000c 100644 --- a/sw/inc/crsrsh.hxx +++ b/sw/inc/crsrsh.hxx @@ -143,12 +143,27 @@ std::optional<OUString> ReplaceBackReferences(const i18nutil::SearchOptions2& rS bool GetRanges(std::vector<std::shared_ptr<SwUnoCursor>> & rRanges, SwDoc & rDoc, SwPaM const& rDelPam); +struct VisibleCursorState +{ + SwRect m_aCharRect; ///< Char-SRectangle on which the cursor is located + Point m_aCursorHeight; ///< height & offset from visible Cursor + + SwShellCursor* m_pCurrentCursor; ///< currently active cursor + SwVisibleCursor *m_pVisibleCursor; ///< cursor displayed in view + bool m_bSVCursorVis : 1; ///< SV-Cursor visible/invisible + bool m_bOverwriteCursor : 1; ///< true -> show Overwrite Cursor + + bool IsOverwriteCursor() const { return m_bOverwriteCursor; } + void SetOverwriteCursor( bool bFlag ) { m_bOverwriteCursor = bFlag; } +}; + } // namespace sw class SAL_DLLPUBLIC_RTTI SwCursorShell : public SwViewShell , public sw::BroadcastingModify , public ::sw::IShellCursorSupplier + , protected ::sw::VisibleCursorState { friend class SwCallLink; friend class SwVisibleCursor; @@ -173,8 +188,6 @@ public: private: - SwRect m_aCharRect; ///< Char-SRectangle on which the cursor is located - Point m_aCursorHeight; ///< height & offset from visible Cursor Point m_aOldRBPos; ///< Right/Bottom of last VisArea // (used in Invalidate by Cursor) @@ -184,15 +197,23 @@ private: format changes at cursor position.*/ Link<SwCursorShell&,void> m_aGrfArrivedLnk; ///< Link calls to UI if a graphic is arrived - SwShellCursor* m_pCurrentCursor; ///< current cursor SwShellCursor* m_pStackCursor; ///< stack for the cursor - SwVisibleCursor *m_pVisibleCursor; ///< the visible cursor SwBlockCursor *m_pBlockCursor; ///< interface of cursor for block (=rectangular) selection SwShellTableCursor* m_pTableCursor; /**< table Cursor; only in tables when the selection lays over 2 columns */ +#if defined(YRS) +public: + ::std::unordered_map<OString, ::std::unique_ptr<VisibleCursorState>> m_PeerCursors; + SwVisibleCursor * FindVisibleCursorForPeer(SwSelPaintRects const& rCursor) const; + void YrsAddCursor(OString const& rId, ::std::optional<SwPosition> const& roPoint, ::std::optional<SwPosition> const& roMark, OUString const& rAuthor); + void YrsSetCursor(OString const& rId, ::std::optional<SwPosition> const& roPoint, ::std::optional<SwPosition> const& roMark); + void YrsDelCursor(OString const& rId); +private: +#endif + SwNodeIndex* m_pBoxIdx; ///< for recognizing of the changed SwTableBox* m_pBoxPtr; ///< table row @@ -217,7 +238,6 @@ private: int m_nMarkedListLevel; bool m_bHasFocus : 1; ///< Shell is "active" in a window - bool m_bSVCursorVis : 1; ///< SV-Cursor visible/invisible bool m_bChgCallFlag : 1; ///< attribute change inside Start- and EndAction bool m_bVisPortChgd : 1; ///< in VisPortChg-Call // (used in Invalidate by the Cursor) @@ -236,7 +256,6 @@ private: bool m_bAutoUpdateCells : 1; // true -> autoformat cells bool m_bBasicHideCursor : 1; // true -> HideCursor from Basic bool m_bSetCursorInReadOnly : 1;// true -> Cursor is allowed in ReadOnly-Areas - bool m_bOverwriteCursor : 1; // true -> show Overwrite Cursor // true -> send accessible events when cursor changes // (set to false when using internal-only helper cursor) @@ -493,9 +512,6 @@ public: void ShowCursors( bool bCursorVis ); void HideCursors(); - bool IsOverwriteCursor() const { return m_bOverwriteCursor; } - void SetOverwriteCursor( bool bFlag ) { m_bOverwriteCursor = bFlag; } - bool IsSendAccessibleCursorEvents() const { return m_bSendAccessibleCursorEvents; }; void SetSendAccessibleCursorEvents(bool bEnable) { m_bSendAccessibleCursorEvents = bEnable; }; diff --git a/sw/inc/viscrs.hxx b/sw/inc/viscrs.hxx index 2aba4bbd7110..afb5d96c9a4c 100644 --- a/sw/inc/viscrs.hxx +++ b/sw/inc/viscrs.hxx @@ -30,6 +30,7 @@ namespace sdr::overlay { class OverlaySelection; } +namespace sw { struct VisibleCursorState; } class SwCursorShell; class SfxViewShell; class SwContentControlButton; @@ -41,6 +42,7 @@ class SW_DLLPUBLIC SwVisibleCursor friend void InitCore(); friend void FinitCore(); + ::sw::VisibleCursorState const& m_rState; const SwCursorShell* m_pCursorShell; vcl::Cursor m_aTextCursor; @@ -52,7 +54,7 @@ class SW_DLLPUBLIC SwVisibleCursor bool m_bIsDragCursor; public: - SwVisibleCursor( const SwCursorShell * pCShell ); + SwVisibleCursor(::sw::VisibleCursorState const& rState, const SwCursorShell * pCShell); ~SwVisibleCursor(); void Show(); @@ -60,10 +62,15 @@ public: bool IsVisible() const { return m_bIsVisible; } void SetDragCursor( bool bFlag = true ) { m_bIsDragCursor = bFlag; } + ::std::pair<SwRect, bool> SetPos(); void SetPosAndShow(SfxViewShell const * pViewShell); const vcl::Cursor& GetTextCursor() const; std::optional<OString> getLOKPayload(int nType, int nViewId) const; + +#if defined(YRS) + ::std::optional<OUString> m_Author; +#endif }; // From here classes/methods for selections. diff --git a/sw/source/core/crsr/crsrsh.cxx b/sw/source/core/crsr/crsrsh.cxx index d1d33f19e58d..27cdec5cd827 100644 --- a/sw/source/core/crsr/crsrsh.cxx +++ b/sw/source/core/crsr/crsrsh.cxx @@ -60,6 +60,9 @@ #include <comphelper/flagguard.hxx> #include <strings.hrc> #include <IDocumentLayoutAccess.hxx> +#if defined(YRS) +#include <IDocumentState.hxx> +#endif #include <LibreOfficeKit/LibreOfficeKitEnums.h> #include <comphelper/lok.hxx> #include <comphelper/sequence.hxx> @@ -1778,6 +1781,15 @@ void SwCursorShell::Paint(vcl::RenderContext& rRenderContext, const tools::Recta bVis = true; m_pVisibleCursor->Hide(); } +#if defined(YRS) + for (auto const& it : m_PeerCursors) + { + if (it.second->m_pCurrentCursor) + { + it.second->m_pVisibleCursor->Hide(); + } + } +#endif // re-paint area SwViewShell::Paint(rRenderContext, rRect); @@ -1807,6 +1819,15 @@ void SwCursorShell::Paint(vcl::RenderContext& rRenderContext, const tools::Recta if( m_bSVCursorVis && bVis ) // also show SV cursor again m_pVisibleCursor->Show(); +#if defined(YRS) + for (auto const& it : m_PeerCursors) + { + if (it.second->m_pCurrentCursor && it.second->m_bSVCursorVis) + { + it.second->m_pVisibleCursor->Show(); + } + } +#endif } void SwCursorShell::VisPortChgd( const SwRect & rRect ) @@ -1987,6 +2008,7 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd, ScrollSizeMo // If the current cursor is in a table and point/mark in different boxes, // then the table mode is active (also if it is already active: m_pTableCursor) SwPaM* pTstCursor = getShellCursor( true ); + // TODO yrs: table selection is possible in RO mode if( pTstCursor->HasMark() && !m_pBlockCursor && SwDoc::IsInTable( pTstCursor->GetPoint()->GetNode() ) && ( m_pTableCursor || @@ -2164,7 +2186,17 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd, ScrollSizeMo } m_pVisibleCursor->Hide(); // always hide visible Cursor +#if defined(YRS) + for (auto const& it : m_PeerCursors) + { + if (it.second->m_pCurrentCursor) + { + it.second->m_pVisibleCursor->Hide(); + } + } +#endif + // for peers, leave it to the peer to move out of hidden/protected; // are we perhaps in a protected / hidden Section ? { SwShellCursor* pShellCursor = getShellCursor( true ); @@ -2216,7 +2248,8 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd, ScrollSizeMo // loops _behind_ the last node in the selection, which always works if you // are in content.) To achieve this, we'll force cursor(s) to point into // content, if UpdateCursorPos() hasn't already done so. - for(SwPaM& rCmp : m_pCurrentCursor->GetRingContainer()) + auto const MoveIntoContent = [](SwShellCursor *const pCursor) { + for (SwPaM& rCmp : pCursor->GetRingContainer()) { // start will move forwards, end will move backwards bool bPointIsStart = ( rCmp.Start() == rCmp.GetPoint() ); @@ -2238,12 +2271,28 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd, ScrollSizeMo } } } + }; + MoveIntoContent(m_pCurrentCursor); +#if defined(YRS) + for (auto const& it : m_PeerCursors) + { + if (it.second->m_pCurrentCursor) + { + MoveIntoContent(it.second->m_pCurrentCursor); + } + } +#endif + SwContentFrame *pPointFrame; - SwRect aOld( m_aCharRect ); + auto const Impl = [this, eFlags, eScrollSizeMode](::sw::VisibleCursorState & rState, SwContentFrame **const ppFrame, bool isThisShell) + { + SwRect aOld(rState.m_aCharRect); bool bFirst = true; SwContentFrame *pFrame; int nLoopCnt = 100; - SwShellCursor* pShellCursor = getShellCursor( true ); + SwShellCursor* pShellCursor = isThisShell + ? static_cast<SwCursorShell&>(rState).getShellCursor(true) + : rState.m_pCurrentCursor; do { bool bAgainst; @@ -2261,7 +2310,7 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd, ScrollSizeMo { const SwStartNode* pNd = pShellCursor->GetPointNode().FindTableBoxStartNode(); if ( pNd && pNd->GetTableBox()->GetRedlineType() == RedlineType::Delete ) - return; + return false; } do @@ -2277,7 +2326,7 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd, ScrollSizeMo pFrame->PrepareCursor(); // In protected Fly? but ignore in case of frame selection - if( !IsReadOnlyAvailable() && pFrame->IsProtected() && + if (isThisShell && !IsReadOnlyAvailable() && pFrame->IsProtected() && ( !Imp()->GetDrawView() || !Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() ) && (!mxDoc->GetDocShell() || @@ -2300,7 +2349,7 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd, ScrollSizeMo GetDoc()->GetDocShell()->SetReadOnlyUI(); CallChgLnk(); // notify UI! } - return; + return false; } } @@ -2321,9 +2370,9 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd, ScrollSizeMo } while( bAgainst ); SwCursorMoveState aTmpState( m_eMvState ); - aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + aTmpState.m_bSetInReadOnly = !isThisShell || IsReadOnlyAvailable(); aTmpState.m_bRealHeight = true; - aTmpState.m_bRealWidth = IsOverwriteCursor(); + aTmpState.m_bRealWidth = rState.m_bOverwriteCursor; aTmpState.m_nCursorBidiLevel = pShellCursor->GetCursorBidiLevel(); // #i27615#,#i30453# @@ -2336,25 +2385,26 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd, ScrollSizeMo { DisableCallbackAction a(*GetLayout()); // tdf#91602 prevent recursive Action - if (!pFrame->GetCharRect(m_aCharRect, *pShellCursor->GetPoint(), &aTmpState)) + if (!pFrame->GetCharRect(rState.m_aCharRect, *pShellCursor->GetPoint(), &aTmpState)) { Point& rPt = pShellCursor->GetPtPos(); - rPt = m_aCharRect.Center(); + rPt = rState.m_aCharRect.Center(); pFrame->GetModelPositionForViewPoint( pShellCursor->GetPoint(), rPt, &aTmpState ); } } - UISizeNotify(); // tdf#96256 update view size + if (isThisShell) + UISizeNotify(); // tdf#96256 update view size if( !pShellCursor->HasMark() ) - m_aCursorHeight = aTmpState.m_aRealHeight; + rState.m_aCursorHeight = aTmpState.m_aRealHeight; else { - m_aCursorHeight.setX(0); - m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ? - -m_aCharRect.Width() : m_aCharRect.Height()); + rState.m_aCursorHeight.setX(0); + rState.m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ? + -rState.m_aCharRect.Width() : rState.m_aCharRect.Height()); } - if( !bFirst && aOld == m_aCharRect ) + if (!bFirst && aOld == rState.m_aCharRect) break; // if the layout says that we are after the 100th iteration still in @@ -2365,14 +2415,14 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd, ScrollSizeMo OSL_ENSURE( false, "endless loop? CharRect != OldCharRect "); break; } - aOld = m_aCharRect; + aOld = rState.m_aCharRect; bFirst = false; // update cursor Points to the new Positions - pShellCursor->GetPtPos().setX(m_aCharRect.Left()); - pShellCursor->GetPtPos().setY(m_aCharRect.Top()); + pShellCursor->GetPtPos().setX(rState.m_aCharRect.Left()); + pShellCursor->GetPtPos().setY(rState.m_aCharRect.Top()); - if( !(eFlags & SwCursorShell::UPDOWN )) // delete old Pos. of Up/Down + if (isThisShell && !(eFlags & SwCursorShell::UPDOWN)) // delete old Pos. of Up/Down { DisableCallbackAction a(*GetLayout()); pFrame->Calc(GetOut()); @@ -2382,7 +2432,7 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd, ScrollSizeMo } // scroll Cursor to visible area - if( m_bHasFocus && eFlags & SwCursorShell::SCROLLWIN && + if (isThisShell && m_bHasFocus && eFlags & SwCursorShell::SCROLLWIN && (HasSelection() || eFlags & SwCursorShell::READONLY || !IsCursorReadonly() || GetViewOptions()->IsSelectionInReadonly()) ) { @@ -2395,8 +2445,25 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd, ScrollSizeMo } } while( eFlags & SwCursorShell::SCROLLWIN ); + *ppFrame = pFrame; + return true; + }; +#if defined(YRS) + for (auto const& it : m_PeerCursors) + { + if (it.second->m_pCurrentCursor) + { + SwContentFrame *pDummy; + Impl(*it.second, &pDummy, false); + } + } +#endif + if (!Impl(*this, &pPointFrame, true)) + { + return; + } - assert(pFrame); + assert(pPointFrame); if( m_pBlockCursor ) RefreshBlockCursor(); @@ -2420,6 +2487,15 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd, ScrollSizeMo pNxt = pNxt->GetNext(); } } +#if defined(YRS) + for (auto const& it : m_PeerCursors) + { + if (it.second->m_pCurrentCursor) + { + it.second->m_pCurrentCursor->SwSelPaintRects::Show(); + } + } +#endif } } @@ -2427,7 +2503,7 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd, ScrollSizeMo #if !ENABLE_WASM_STRIP_ACCESSIBILITY if (Imp()->IsAccessible() && m_bSendAccessibleCursorEvents) - Imp()->InvalidateAccessibleCursorPosition( pFrame ); + Imp()->InvalidateAccessibleCursorPosition(pPointFrame); #endif // switch from blinking cursor to read-only-text-selection cursor @@ -2450,6 +2526,16 @@ void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd, ScrollSizeMo if( m_bSVCursorVis ) m_pVisibleCursor->Show(); // show again +#if defined(YRS) + for (auto const& it : m_PeerCursors) + { + if (it.second->m_pCurrentCursor && it.second->m_bSVCursorVis) + { + it.second->m_pVisibleCursor->Show(); + } + } + GetDoc()->getIDocumentState().YrsNotifyCursorUpdate(); +#endif if (comphelper::LibreOfficeKit::isActive()) sendLOKCursorUpdates(); @@ -3154,6 +3240,97 @@ SwVisibleCursor* SwCursorShell::GetVisibleCursor() const return m_pVisibleCursor; } +#if defined(YRS) +void SwCursorShell::YrsAddCursor(OString const& rId, ::std::optional<SwPosition> const& roPoint, + ::std::optional<SwPosition> const& roMark, OUString const& rAuthor) +{ + SwShellCursor * pCursor{nullptr}; + if (roPoint) + { + pCursor = new SwShellCursor{*this, *roPoint}; + if (roMark) + { + pCursor->SetMark(); + *pCursor->GetMark() = *roMark; + } + } + + auto const [it, inserted]{m_PeerCursors.emplace(rId, new VisibleCursorState{SwRect{}, Point{}, pCursor, nullptr, true, false})}; + assert(inserted); + it->second->m_pVisibleCursor = new SwVisibleCursor{*it->second, this}; + it->second->m_pVisibleCursor->m_Author = rAuthor; + + // TODO check if pos is valid? just call UC? add cursor parameter so it doesnt check all of them? + UpdateCursor(); +} + +void SwCursorShell::YrsSetCursor(OString const& rId, ::std::optional<SwPosition> const& roPoint, + ::std::optional<SwPosition> const& roMark) +{ + auto const it{m_PeerCursors.find(rId)}; + assert(it != m_PeerCursors.end()); + if (!it->second->m_pCurrentCursor && !roPoint) + { + return; // cursor moved in EE + } + if (it->second->m_pCurrentCursor && !roPoint) + { + it->second->m_pVisibleCursor->Hide(); + it->second->m_pCurrentCursor->Hide(); + delete it->second->m_pCurrentCursor; + it->second->m_pCurrentCursor = nullptr; + return; + } + else if (!it->second->m_pCurrentCursor && roPoint) + { + it->second->m_pCurrentCursor = new SwShellCursor{*this, *roPoint}; + } + else + { + *it->second->m_pCurrentCursor->GetPoint() = *roPoint; + } + if (it->second->m_pCurrentCursor->HasMark() && !roMark) + { + it->second->m_pCurrentCursor->DeleteMark(); + } + else if (roMark) + { + if (!it->second->m_pCurrentCursor->HasMark()) + { + it->second->m_pCurrentCursor->SetMark(); + } + *it->second->m_pCurrentCursor->GetMark() = *roMark; + } + + // TODO check if pos is valid? just call UC? add cursor parameter so it doesnt check all of them? + UpdateCursor(); +} + +void SwCursorShell::YrsDelCursor(OString const& rId) +{ + auto const it{m_PeerCursors.find(rId)}; + assert(it != m_PeerCursors.end()); + if (it->second->m_pCurrentCursor) + { + it->second->m_pCurrentCursor->Hide(); + } + m_PeerCursors.erase(it); +} + +SwVisibleCursor * SwCursorShell::FindVisibleCursorForPeer(SwSelPaintRects const& rCursor) const +{ + for (auto const& it : m_PeerCursors) + { + if (it.second->m_pCurrentCursor == &rCursor) + { + return it.second->m_pVisibleCursor; + } + } + return nullptr; +} + +#endif + bool SwCursorShell::IsOverReadOnlyPos( const Point& rPt ) const { Point aPt( rPt ); @@ -3389,7 +3566,7 @@ SwCursorShell::SwCursorShell( SwCursorShell& rShell, vcl::Window *pInitWin ) m_bCallChgLnk = m_bHasFocus = m_bAutoUpdateCells = true; m_bSVCursorVis = true; m_bSetCursorInReadOnly = true; - m_pVisibleCursor = new SwVisibleCursor( this ); + m_pVisibleCursor = new SwVisibleCursor(*this, this); m_bMacroExecAllowed = rShell.IsMacroExecAllowed(); m_aLayoutIdle.SetPriority(TaskPriority::LOWEST); @@ -3439,7 +3616,7 @@ SwCursorShell::SwCursorShell( SwDoc& rDoc, vcl::Window *pInitWin, m_bSVCursorVis = true; m_bSetCursorInReadOnly = true; - m_pVisibleCursor = new SwVisibleCursor( this ); + m_pVisibleCursor = new SwVisibleCursor(*this, this); m_bMacroExecAllowed = true; m_aLayoutIdle.SetPriority(TaskPriority::LOWEST); @@ -3456,6 +3633,14 @@ SwCursorShell::~SwCursorShell() else ClearTableBoxContent(); +#if defined(YRS) + for (auto const& it : m_PeerCursors) + { + delete it.second->m_pVisibleCursor; + delete it.second->m_pCurrentCursor; + } +#endif + delete m_pVisibleCursor; delete m_pBlockCursor; delete m_pTableCursor; diff --git a/sw/source/core/crsr/viscrs.cxx b/sw/source/core/crsr/viscrs.cxx index e492544a4805..2e640750dcb3 100644 --- a/sw/source/core/crsr/viscrs.cxx +++ b/sw/source/core/crsr/viscrs.cxx @@ -36,6 +36,7 @@ #include <txtfld.hxx> #include <scriptinfo.hxx> #include <view.hxx> +#include <swmodule.hxx> #include <IDocumentLayoutAccess.hxx> #include <svx/sdr/overlay/overlaymanager.hxx> @@ -75,11 +76,16 @@ tools::Long SwSelPaintRects::s_nPixPtY = 0; MapMode* SwSelPaintRects::s_pMapMode = nullptr; // Starting from here: classes / methods for the non-text-cursor -SwVisibleCursor::SwVisibleCursor( const SwCursorShell * pCShell ) - : m_pCursorShell( pCShell ) +SwVisibleCursor::SwVisibleCursor(::sw::VisibleCursorState const& rState, + const SwCursorShell *const pCShell) + : m_rState(rState) + , m_pCursorShell(pCShell) , m_nPageLastTime(0) { - pCShell->GetWin()->SetCursor( &m_aTextCursor ); + if (&rState == pCShell) + { + pCShell->GetWin()->SetCursor( &m_aTextCursor ); + } m_bIsVisible = m_aTextCursor.IsVisible(); m_bIsDragCursor = false; m_aTextCursor.SetWidth( 0 ); @@ -90,7 +96,10 @@ SwVisibleCursor::~SwVisibleCursor() if( m_bIsVisible && m_aTextCursor.IsVisible() ) m_aTextCursor.Hide(); - m_pCursorShell->GetWin()->SetCursor( nullptr ); + if (m_pCursorShell->GetWin()->GetCursor() == &m_aTextCursor) + { + m_pCursorShell->GetWin()->SetCursor( nullptr ); + } } void SwVisibleCursor::Show() @@ -100,7 +109,7 @@ void SwVisibleCursor::Show() m_bIsVisible = true; // display at all? - if( m_pCursorShell->VisArea().Overlaps( m_pCursorShell->m_aCharRect ) || comphelper::LibreOfficeKit::isActive() ) + if (m_pCursorShell->VisArea().Overlaps(m_rState.m_aCharRect) || comphelper::LibreOfficeKit::isActive()) SetPosAndShow(nullptr); } } @@ -133,33 +142,33 @@ OString buildHyperlinkJSON(const OUString& sText, const OUString& sLink) } -void SwVisibleCursor::SetPosAndShow(SfxViewShell const * pViewShell) +::std::pair<SwRect, bool> SwVisibleCursor::SetPos() { SwRect aRect; - tools::Long nTmpY = m_pCursorShell->m_aCursorHeight.getY(); + tools::Long nTmpY = m_rState.m_aCursorHeight.getY(); if( 0 > nTmpY ) { nTmpY = -nTmpY; m_aTextCursor.SetOrientation( 900_deg10 ); - aRect = SwRect( m_pCursorShell->m_aCharRect.Pos(), - Size( m_pCursorShell->m_aCharRect.Height(), nTmpY ) ); - aRect.Pos().setX(aRect.Pos().getX() + m_pCursorShell->m_aCursorHeight.getX()); - if( m_pCursorShell->IsOverwriteCursor() ) + aRect = SwRect( m_rState.m_aCharRect.Pos(), + Size( m_rState.m_aCharRect.Height(), nTmpY ) ); + aRect.Pos().setX(aRect.Pos().getX() + m_rState.m_aCursorHeight.getX()); + if (m_rState.IsOverwriteCursor()) aRect.Pos().setY(aRect.Pos().getY() + aRect.Width()); } else { m_aTextCursor.SetOrientation(); - aRect = SwRect( m_pCursorShell->m_aCharRect.Pos(), - Size( m_pCursorShell->m_aCharRect.Width(), nTmpY ) ); - aRect.Pos().setY(aRect.Pos().getY() + m_pCursorShell->m_aCursorHeight.getX()); + aRect = SwRect( m_rState.m_aCharRect.Pos(), + Size( m_rState.m_aCharRect.Width(), nTmpY ) ); + aRect.Pos().setY(aRect.Pos().getY() + m_rState.m_aCursorHeight.getX()); } // check if cursor should show the current cursor bidi level m_aTextCursor.SetDirection(); - const SwCursor* pTmpCursor = m_pCursorShell->GetCursor_(); + const SwCursor* pTmpCursor = m_rState.m_pCurrentCursor; - if ( pTmpCursor && !m_pCursorShell->IsOverwriteCursor() ) + if ( pTmpCursor && !m_rState.IsOverwriteCursor() ) { SwNode& rNode = pTmpCursor->GetPoint()->GetNode(); if( rNode.IsTextNode() ) @@ -201,7 +210,7 @@ void SwVisibleCursor::SetPosAndShow(SfxViewShell const * pViewShell) if (!comphelper::LibreOfficeKit::isActive()) ::SwAlignRect( aRect, static_cast<SwViewShell const *>(m_pCursorShell), m_pCursorShell->GetOut() ); } - if( !m_pCursorShell->IsOverwriteCursor() || m_bIsDragCursor || + if( !m_rState.IsOverwriteCursor() || m_bIsDragCursor || m_pCursorShell->IsSelection() ) aRect.Width( 0 ); @@ -209,6 +218,12 @@ void SwVisibleCursor::SetPosAndShow(SfxViewShell const * pViewShell) m_aTextCursor.SetSize( aRect.SSize() ); m_aTextCursor.SetPos( aRect.Pos() ); + return { aRect, bIsCursorPosChanged }; +} + +void SwVisibleCursor::SetPosAndShow(SfxViewShell const * pViewShell) +{ + auto const [aRect, bIsCursorPosChanged] {SetPos()}; if (comphelper::LibreOfficeKit::isActive()) { @@ -346,7 +361,7 @@ const vcl::Cursor& SwVisibleCursor::GetTextCursor() const return m_aTextCursor; } -SwSelPaintRects::SwSelPaintRects( const SwCursorShell& rCSh ) +SwSelPaintRects::SwSelPaintRects(const SwCursorShell& rCSh) : m_pCursorShell( &rCSh ) #if HAVE_FEATURE_DESKTOP , m_bShowTextInputFieldOverlay(true) @@ -456,8 +471,16 @@ void SwSelPaintRects::Show(std::vector<OString>* pSelectionRectangles) if (xTargetOverlay.is()) { + ::std::optional<Color> oColor; +#if defined(YRS) + if (SwVisibleCursor *const pVisibleCursor{GetShell()->FindVisibleCursorForPeer(*this)}) + { + ::std::size_t const authorId{SwModule::get()->InsertRedlineAuthor(*pVisibleCursor->m_Author)}; + oColor.emplace(SwPostItMgr::GetColorAnchor(authorId)); + } +#endif // get the system's highlight color - const Color aHighlight(SvtOptionsDrawinglayer::getHilightColor()); + const Color aHighlight(oColor ? *oColor : SvtOptionsDrawinglayer::getHilightColor()); // create correct selection m_pCursorOverlay.reset( new sdr::overlay::OverlaySelection( @@ -943,6 +966,25 @@ void SwShellCursor::FillRects() { GetShell()->GetLayout()->CalcFrameRects(*this, *this); } +#if defined(YRS) + if (!HasMark()) + { + if (SwVisibleCursor *const pVisibleCursor{GetShell()->FindVisibleCursorForPeer(*this)}) + { + // use OutDev.GetSettings().GetStyleSettings().GetCursorSize() as width? + auto [cursorRect, _] {pVisibleCursor->SetPos()}; + if (cursorRect.IsEmpty()) + { + cursorRect.Width(20); + } + SAL_DEBUG("YRS FillRects extra rect " << cursorRect); + emplace_back(cursorRect); + SwRect const temp{Point{cursorRect.Left() - cursorRect.Height()/2, + cursorRect.Bottom()}, Size{cursorRect.Height() + 20, 20}}; + emplace_back(temp); + } + } +#endif } void SwShellCursor::Show(SfxViewShell const * pViewShell) diff --git a/sw/source/core/doc/DocumentStateManager.cxx b/sw/source/core/doc/DocumentStateManager.cxx index 011c3c1e03e6..91f39d7f7634 100644 --- a/sw/source/core/doc/DocumentStateManager.cxx +++ b/sw/source/core/doc/DocumentStateManager.cxx @@ -29,6 +29,7 @@ #include <view.hxx> #include <docufld.hxx> #include <PostItMgr.hxx> +#include <swmodule.hxx> #include <IDocumentFieldsAccess.hxx> #include <txtannotationfld.hxx> #include <ndtxt.hxx> @@ -120,7 +121,9 @@ YrsTransactionSupplier::YrsTransactionSupplier() YrsTransactionSupplier::~YrsTransactionSupplier() { - assert(m_pCurrentWriteTransaction == nullptr); + // 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; } YTransaction * YrsTransactionSupplier::GetReadTransaction() @@ -623,13 +626,217 @@ extern "C" void observe_comments(void *const pState, uint32_t count, YEvent cons } } -extern "C" void observe_cursors(void *const pState, uint32_t count, YEvent const*const events) +struct ObserveCursorState : public ObserveState { -#if 1 - (void) pState; - (void) count; - (void) events; + struct Update { + OString const peerId; + ::std::optional<OUString> const oAuthor; + ::std::pair<int64_t, int64_t> const point; + ::std::optional<::std::pair<int64_t, int64_t>> const oMark; + }; + ::std::vector<Update> CursorUpdates; +}; + +void YrsCursorUpdates(ObserveCursorState & rState) +{ + for (auto const& it : rState.CursorUpdates) + { +#if defined(LOPLUGIN_BLOCK_BLOCK) #endif + { + ::std::optional<SwPosition> oMark; + if (it.oMark) + { + yvalidate(SwNodeOffset{it.oMark->first} < rState.rDoc.GetNodes().Count()); + SwNode & rNode{*rState.rDoc.GetNodes()[SwNodeOffset{it.oMark->first}]}; + yvalidate(rNode.IsTextNode()); + yvalidate(it.oMark->second <= o3tl::make_unsigned(rNode.GetTextNode()->Len())); + oMark.emplace(*rNode.GetTextNode(), static_cast<sal_Int32>(it.oMark->second)); + } + + yvalidate(SwNodeOffset{it.point.first} < rState.rDoc.GetNodes().Count()); + SwNode & rNode{*rState.rDoc.GetNodes()[SwNodeOffset{it.point.first}]}; + yvalidate(rNode.IsTextNode()); + yvalidate(it.point.second <= o3tl::make_unsigned(rNode.GetTextNode()->Len())); + SwPosition const point{*rNode.GetTextNode(), static_cast<sal_Int32>(it.point.second)}; + + for (SwViewShell & rShell : rState.rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()->GetRingContainer()) + { + if (auto const pShell{dynamic_cast<SwCursorShell *>(&rShell)}) + { + if (it.oAuthor) + { + pShell->YrsAddCursor(it.peerId, {point}, oMark, *it.oAuthor); + } + else + { + pShell->YrsSetCursor(it.peerId, {point}, oMark); + } + } + } + } + } +} + +void YrsReadCursor(ObserveCursorState & rState, OString const& rPeerId, + YOutput const& rCursor, ::std::optional<OUString> const oAuthor) +{ + switch (rCursor.tag) + { + case Y_ARRAY: + { + Branch const*const pArray{rCursor.value.y_type}; + auto const len{yarray_len(pArray)}; + if (len == 3 || len == 5) + { + // TODO cursor in EE + } + else if (len == 2 || len == 4) + { + // the only reason this stuff has a hope of working with + // integers is that the SwNodes is read-only + ::std::optional<::std::pair<int64_t, int64_t>> oMark; + if (len == 4) + { + ::std::unique_ptr<YOutput, YOutputDeleter> const pNode{yarray_get(pArray, rState.pTxn, 2)}; + yvalidate(pNode->tag == Y_JSON_INT); + ::std::unique_ptr<YOutput, YOutputDeleter> const pContent{yarray_get(pArray, rState.pTxn, 3)}; + yvalidate(pContent->tag == Y_JSON_INT); + oMark.emplace(pNode->value.integer, pContent->value.integer); + } + + ::std::unique_ptr<YOutput, YOutputDeleter> const pNode{yarray_get(pArray, rState.pTxn, 0)}; + yvalidate(pNode->tag == Y_JSON_INT); + ::std::unique_ptr<YOutput, YOutputDeleter> const pContent{yarray_get(pArray, rState.pTxn, 1)}; + yvalidate(pContent->tag == Y_JSON_INT); + ::std::pair<int64_t, int64_t> const pos{pNode->value.integer, pContent->value.integer}; + rState.CursorUpdates.emplace_back(rPeerId, oAuthor, pos, oMark); + } + else + yvalidate(false); + break; + } + case Y_JSON_NULL: + for (SwViewShell & rShell : rState.rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()->GetRingContainer()) + { + if (auto const pShell{dynamic_cast<SwCursorShell *>(&rShell)}) + { + if (oAuthor) + { + pShell->YrsAddCursor(rPeerId, {}, {}, *oAuthor); + } + else + { + pShell->YrsSetCursor(rPeerId, {}, {}); + } + } + } + break; + default: + yvalidate(false); + } +} + +extern "C" void observe_cursors(void *const pState, uint32_t count, YEvent const*const events) +{ + SAL_DEBUG("YRS observe_cursors"); + // note: it (very rarely) happens that observe_cursors will be called + // when a cursor is moved into a comment that is newly inserted, but + // observe_comments hasn't been called to actually insert the comment yet + // => need to buffer all the cursor updates and replay them at the end... + ObserveCursorState & rState{*static_cast<ObserveCursorState*>(pState)}; + + for (decltype(count) i = 0; i < count; ++i) + { + switch (events[i].tag) + { + case Y_MAP: + { + // new peer? + YMapEvent const*const pEvent{&events[i].content.map}; + uint32_t lenP{0}; + /*YPathSegment *const pPath{*/ymap_event_path(pEvent, &lenP); + yvalidate(lenP == 0); + uint32_t lenK{0}; + YEventKeyChange *const pChange{ymap_event_keys(pEvent, &lenK)}; + for (decltype(lenK) j = 0; j < lenK; ++j) + { + OString const peerId{pChange[j].key}; + yvalidate(peerId != OString::number(ydoc_id(rState.rYrsSupplier.GetYDoc()))); // should never be updated by peers? + switch (pChange[j].tag) + { + case Y_EVENT_KEY_CHANGE_UPDATE: + yvalidate(false); + case Y_EVENT_KEY_CHANGE_ADD: + { + switch (pChange[j].new_value->tag) + { + case Y_ARRAY: + { + Branch const*const pArray{pChange[j].new_value->value.y_type}; + auto const len{yarray_len(pArray)}; + yvalidate(len == 2); + ::std::unique_ptr<YOutput, YOutputDeleter> const pAuthor{ + yarray_get(pArray, rState.pTxn, 0)}; + yvalidate(pAuthor->tag == Y_JSON_STR && pAuthor->len < SAL_MAX_INT32); + OUString const author{pAuthor->value.str, + static_cast<sal_Int32>(pAuthor->len), RTL_TEXTENCODING_UTF8}; + ::std::unique_ptr<YOutput, YOutputDeleter> const pCursor{ + yarray_get(pArray, rState.pTxn, 1)}; + YrsReadCursor(rState, peerId, *pCursor, {author}); + break; + } + default: + yvalidate(false); + } + break; + } + case Y_EVENT_KEY_CHANGE_DELETE: + { + for (SwViewShell & rShell : rState.rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()->GetRingContainer()) + { + if (auto const pShell{dynamic_cast<SwCursorShell *>(&rShell)}) + { + pShell->YrsDelCursor(peerId); + } + } + assert(false); // TODO cannot test this currently? + break; + } + default: + assert(false); + } + } + break; + } + case Y_ARRAY: + { + YArrayEvent const*const pEvent{&events[i].content.array}; + // position changed? + uint32_t lenP{0}; + YPathSegment *const pPath{yarray_event_path(pEvent, &lenP)}; + yvalidate(lenP == 1); + yvalidate(pPath[0].tag == Y_EVENT_PATH_KEY); + OString const peerId{pPath[0].value.key}; + ypath_destroy(pPath, lenP); + + uint32_t lenC{0}; + YEventChange *const pChange{yarray_event_delta(pEvent, &lenC)}; + // position update looks like this + yvalidate(lenC == 3); + yvalidate(pChange[0].tag == Y_EVENT_CHANGE_RETAIN); + yvalidate(pChange[0].len == 1); + yvalidate(pChange[1].tag == Y_EVENT_CHANGE_DELETE); + yvalidate(pChange[1].len == 1); + yvalidate(pChange[2].tag == Y_EVENT_CHANGE_ADD); + yvalidate(pChange[2].len == 1); + YrsReadCursor(rState, peerId, pChange[2].values[0], {}); + break; + } + default: + assert(false); + } + } } void writeLength(sal_Int8 *& rpBuf, sal_Int32 const len) @@ -726,9 +933,11 @@ IMPL_LINK(YrsThread, HandleMessage, void*, pVoid, void) // YText - need to be notified on new comments being created... ObserveState state{*m_pDSM->m_pYrsSupplier, m_pDSM->m_rDoc, pTxn}; 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(), &state, observe_cursors); + YSubscription *const pSubCursors = yobserve_deep(m_pDSM->m_pYrsSupplier->GetCursorMap(), &cursorState, observe_cursors); m_pDSM->m_pYrsSupplier->CommitTransaction(true); + YrsCursorUpdates(cursorState); m_pDSM->m_pYrsSupplier->SetMode(IYrsTransactionSupplier::Mode::Edit); yunobserve(pSubComments); yunobserve(pSubCursors); @@ -956,6 +1165,127 @@ void DocumentStateManager::YrsRemoveComment(SwPosition const& rPos, OString cons // either update the cursors here, or wait for round-trip? } +void DocumentStateManager::YrsNotifyCursorUpdate() +{ + SwWrtShell *const pShell{dynamic_cast<SwWrtShell*>(m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell())}; + if (!m_pYrsSupplier || !pShell->GetView().GetPostItMgr()) + { + return; + } + SAL_DEBUG("YRS NotifyCursorUpdate"); + YTransaction *const pTxn{m_pYrsSupplier->GetWriteTransaction()}; + YDoc *const pYDoc{m_pYrsSupplier->GetYDoc()}; + auto const id{ydoc_id(pYDoc)}; + ::std::unique_ptr<YOutput, YOutputDeleter> pEntry{ymap_get(m_pYrsSupplier->m_pCursors, pTxn, OString::number(id).getStr())}; + if (pEntry == nullptr) + { + OString const author{OUStringToOString(SwModule::get()->GetRedlineAuthor(SwModule::get()->GetRedlineAuthor()), RTL_TEXTENCODING_UTF8)}; + ::std::vector<YInput> elements; + elements.push_back(yinput_string(author.getStr())); + elements.push_back(yinput_null()); + YInput const input{yinput_yarray(elements.data(), 2)}; + ymap_insert(m_pYrsSupplier->m_pCursors, pTxn, OString::number(id).getStr(), &input); + pEntry.reset(ymap_get(m_pYrsSupplier->m_pCursors, pTxn, OString::number(id).getStr())); + } + assert(pEntry); + yvalidate(pEntry->tag == Y_ARRAY); + yvalidate(yarray_len(pEntry->value.y_type) == 2); + ::std::unique_ptr<YOutput, YOutputDeleter> const pCurrent{yarray_get(pEntry->value.y_type, pTxn, 1)}; + if (sw::annotation::SwAnnotationWin *const pWin{ + pShell->GetView().GetPostItMgr()->GetActiveSidebarWin()}) + { + // TODO StickyIndex cannot be inserted into YDoc ? + ESelection const sel{pWin->GetOutlinerView()->GetSelection()}; + ::std::vector<YInput> positions; + // the ID of the comment + positions.push_back(yinput_string(pWin->GetOutlinerView()->GetEditView().GetYrsCommentId().getStr())); + positions.push_back(yinput_long(sel.start.nPara)); + positions.push_back(yinput_long(sel.start.nIndex)); + if (pWin->GetOutlinerView()->HasSelection()) + { + positions.push_back(yinput_long(sel.end.nPara)); + positions.push_back(yinput_long(sel.end.nIndex)); + if (pCurrent == nullptr || pCurrent->tag != Y_ARRAY + || yarray_len(pCurrent->value.y_type) != 5 + || strcmp(yarray_get(pCurrent->value.y_type, pTxn, 0)->value.str, positions[0].value.str) != 0 + || yarray_get(pCurrent->value.y_type, pTxn, 1)->value.integer != positions[1].value.integer + || yarray_get(pCurrent->value.y_type, pTxn, 2)->value.integer != positions[2].value.integer + || yarray_get(pCurrent->value.y_type, pTxn, 3)->value.integer != positions[3].value.integer + || yarray_get(pCurrent->value.y_type, pTxn, 4)->value.integer != positions[4].value.integer) + { + 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(); + } + } + else + { + if (pCurrent == nullptr || pCurrent->tag != Y_ARRAY + || yarray_len(pCurrent->value.y_type) != 3 + || strcmp(yarray_get(pCurrent->value.y_type, pTxn, 0)->value.str, positions[0].value.str) != 0 + || yarray_get(pCurrent->value.y_type, pTxn, 1)->value.integer != positions[1].value.integer + || yarray_get(pCurrent->value.y_type, pTxn, 2)->value.integer != positions[2].value.integer) + { + 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(); + } + } + } + else if (!pShell->IsStdMode() + || pShell->GetSelectedObjCount() != 0 + || pShell->IsTableMode() + || pShell->IsBlockMode()) + { + if (pCurrent == nullptr || pCurrent->tag != Y_JSON_NULL) + { + 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(); + } + } + else + { + SwPaM const*const pCursor{pShell->GetCursor()}; + ::std::vector<YInput> positions; + positions.push_back(yinput_long(pCursor->GetPoint()->GetNodeIndex().get())); + positions.push_back(yinput_long(pCursor->GetPoint()->GetContentIndex())); + if (pCursor->HasMark()) + { + positions.push_back(yinput_long(pCursor->GetMark()->GetNodeIndex().get())); + positions.push_back(yinput_long(pCursor->GetMark()->GetContentIndex())); + if (pCurrent == nullptr || pCurrent->tag != Y_ARRAY + || yarray_len(pCurrent->value.y_type) != 4 + || yarray_get(pCurrent->value.y_type, pTxn, 0)->value.integer != positions[0].value.integer + || yarray_get(pCurrent->value.y_type, pTxn, 1)->value.integer != positions[1].value.integer + || yarray_get(pCurrent->value.y_type, pTxn, 2)->value.integer != positions[2].value.integer + || yarray_get(pCurrent->value.y_type, pTxn, 3)->value.integer != positions[3].value.integer) + { + 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(); + } + } + else + { + if (pCurrent == nullptr || pCurrent->tag != Y_ARRAY + || yarray_len(pCurrent->value.y_type) != 2 + || yarray_get(pCurrent->value.y_type, pTxn, 0)->value.integer != positions[0].value.integer + || yarray_get(pCurrent->value.y_type, pTxn, 1)->value.integer != positions[1].value.integer) + { + 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(); + } + } + } +} + void DocumentStateManager::YrsInitAcceptor() { if (!getenv("YRSACCEPT") || m_pYrsReader.is()) @@ -1049,7 +1379,7 @@ void DocumentStateManager::YrsInitAcceptor() } // initiate sync of comments to other side //AddComment would have done this m_pYrsSupplier->GetWriteTransaction(); - YrsCommitModified(); + YrsNotifyCursorUpdate(); } catch (uno::Exception const&) // exception here will cause UAF from SwView later { diff --git a/sw/source/core/doc/doccorr.cxx b/sw/source/core/doc/doccorr.cxx index 61cad6e1eca4..177b8becb957 100644 --- a/sw/source/core/doc/doccorr.cxx +++ b/sw/source/core/doc/doccorr.cxx @@ -121,6 +121,19 @@ void PaMCorrAbs( const SwPaM& rRange, if( pCursorShell->IsTableMode() ) lcl_PaMCorrAbs( const_cast<SwPaM &>(*pCursorShell->GetTableCrs()), aStart, aEnd, aNewPos ); + +#if defined(YRS) + for (auto const& it : pCursorShell->m_PeerCursors) + { + if (it.second->m_pCurrentCursor) + { + for (SwPaM & rPaM : it.second->m_pCurrentCursor->GetRingContainer()) + { + lcl_PaMCorrAbs(rPaM, aStart, aEnd, aNewPos); + } + } + } +#endif } } @@ -279,6 +292,16 @@ void PaMCorrRel( const SwNode &rOldNode, if( pCursorShell->IsTableMode() ) lcl_PaMCorrRel1( pCursorShell->GetTableCrs(), pOldNode, aNewPos, nCntIdx ); + +#if defined(YRS) + for (auto const& it : pCursorShell->m_PeerCursors) + { + for (SwPaM & rPaM : it.second->m_pCurrentCursor->GetRingContainer()) + { + lcl_PaMCorrRel1(&rPaM, pOldNode, aNewPos, nCntIdx); + } + } +#endif } } diff --git a/sw/source/core/inc/DocumentStateManager.hxx b/sw/source/core/inc/DocumentStateManager.hxx index 0457e7920f70..b26f3bc9d58e 100644 --- a/sw/source/core/inc/DocumentStateManager.hxx +++ b/sw/source/core/inc/DocumentStateManager.hxx @@ -87,6 +87,7 @@ public: SwPostItField const& rField, bool isInsert) override; void YrsRemoveCommentImpl(OString const& rCommentId) override; void YrsRemoveComment(SwPosition const& rPos, OString const& rCommentId) override; + void YrsNotifyCursorUpdate() override; #endif };