diff options
author | Michael Stahl <michael.stahl@allotropia.de> | 2025-02-14 18:03:16 +0100 |
---|---|---|
committer | Michael Stahl <michael.stahl@allotropia.de> | 2025-05-12 11:33:51 +0200 |
commit | 0ff4c6619031582b6c520db90a17ed285973e1be (patch) | |
tree | 671ea1783ed1c278268dfb8a233ba9d42764933b | |
parent | 7143fc5d6f746543162d3de8320c10a0f7aeaca5 (diff) |
LOCRDT editeng,sw: yrs peer cursors for editengine
The cursors are represented as integers in yrs as a first step, which
isn't ideal.
Surprisingly EditView doesn't really have functions to correct the
cursor, only ImpEditEngine::UpdateSelections() for the case when nodes
are deleted, apparently there's only one active EditView at a time
typically so it wasn't needed? Typically the active EditView's cursor
is assigned from a return value of ImpEditEngine.
Add some new functions to correct peer cursors and call them all over
ImpEditEngine; unfortunately UpdateSelectionsDelete() isn't easy to get
working on EditView's actual EditSelection cursor so leave that for the
future...
To fix EE paint of non-active comment, add SwAnnotationWin::Paint()
To get things to paint with "gen", it's enough to invalidate the
SwAnnotationWin, but for gtk3 a queue_draw() must be called, else it's
only painted when clicking on the window.
Cursors are painted in color in SidebarTextControl::Paint()
Change-Id: Id8696238290405554ece1b9961676972a559b58e
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/181679
Tested-by: Jenkins
Reviewed-by: Michael Stahl <michael.stahl@allotropia.de>
-rw-r--r-- | editeng/inc/editdoc.hxx | 2 | ||||
-rw-r--r-- | editeng/source/editeng/editdoc.cxx | 139 | ||||
-rw-r--r-- | editeng/source/editeng/editview.cxx | 45 | ||||
-rw-r--r-- | editeng/source/editeng/impedit.hxx | 11 | ||||
-rw-r--r-- | editeng/source/editeng/impedit2.cxx | 171 | ||||
-rw-r--r-- | editeng/source/editeng/impedit4.cxx | 4 | ||||
-rw-r--r-- | include/editeng/editview.hxx | 7 | ||||
-rw-r--r-- | svx/source/dialog/weldeditview.cxx | 2 | ||||
-rw-r--r-- | sw/inc/AnnotationWin.hxx | 3 | ||||
-rw-r--r-- | sw/source/core/doc/DocumentStateManager.cxx | 115 | ||||
-rw-r--r-- | sw/source/uibase/docvw/AnnotationWin2.cxx | 16 | ||||
-rw-r--r-- | sw/source/uibase/docvw/SidebarTxtControl.cxx | 29 | ||||
-rw-r--r-- | sw/source/uibase/docvw/SidebarTxtControl.hxx | 5 |
13 files changed, 464 insertions, 85 deletions
diff --git a/editeng/inc/editdoc.hxx b/editeng/inc/editdoc.hxx index eb0d79da7e6e..cb252ee0c877 100644 --- a/editeng/inc/editdoc.hxx +++ b/editeng/inc/editdoc.hxx @@ -134,6 +134,8 @@ public: void YrsWriteEEState(); void YrsReadEEState(YTransaction *, ImpEditEngine & rIEE); void YrsApplyEEDelta(YTransaction *, YTextEvent const* pEvent, ImpEditEngine & rIEE); + ::std::optional<EditSelection> YrsReadEECursor(::std::pair<int64_t, int64_t> point, + ::std::optional<::std::pair<int64_t, int64_t>> oMark); void YrsSetStyle(sal_Int32 nPara, ::std::u16string_view rStyle); void YrsSetParaAttr(sal_Int32 nPara, SfxPoolItem const& rItem); OString GetYrsCommentId() const; diff --git a/editeng/source/editeng/editdoc.cxx b/editeng/source/editeng/editdoc.cxx index 505ee1e118ac..45c89ef0dcfd 100644 --- a/editeng/source/editeng/editdoc.cxx +++ b/editeng/source/editeng/editdoc.cxx @@ -2712,75 +2712,105 @@ void EditDoc::YrsReadEEState(YTransaction *const pTxn, ImpEditEngine & rIEE) rIEE.RemoveParagraph(nodes); // remove pre-existing one from InitDoc() } -static void YrsAdjustCursors(ImpEditEngine & rIEE, EditDoc & rDoc, - sal_Int32 const node, sal_Int32 const pos, ContentNode *const pNewNode, sal_Int32 const delta) +::std::optional<EditSelection> EditDoc::YrsReadEECursor( + ::std::pair<int64_t, int64_t> const i_point, + ::std::optional<::std::pair<int64_t, int64_t>> const i_oMark) { - for (EditView *const pView : rIEE.GetEditViews()) + // TODO should not use ints here? + ::std::optional<EditPaM> oMark; + if (i_oMark) { - bool bSet{false}; - EditSelection sel{pView->getImpl().GetEditSelection()}; - ContentNode const*const pNode{rDoc.GetObject(node)}; - if (sel.Min().GetNode() == pNode - && pos <= sel.Min().GetIndex()) +// yvalidate(i_oMark->first < Count()); + if (Count() <= i_oMark->first) { - sel.Min().SetNode(pNewNode); - sel.Min().SetIndex(sel.Min().GetIndex() + delta); - bSet = true; + return {}; } - if (sel.Max().GetNode() == pNode - && pos <= sel.Max().GetIndex()) + ContentNode & rNode{*GetObject(i_oMark->first)}; +// yvalidate(i_oMark->second <= o3tl::make_unsigned(rNode.Len())); + if (o3tl::make_unsigned(rNode.Len()) < i_oMark->second) { - sel.Max().SetNode(pNewNode); - sel.Max().SetIndex(sel.Max().GetIndex() + delta); - bSet = true; + return {}; } - if (bSet) + oMark.emplace(&rNode, static_cast<sal_Int32>(i_oMark->second)); + } + + // the problem with using ints here is that multiple local edits may + // occur before the peer sends the updated cursor for the first edit, + // and then its position may be invalid; work around this here by + // ignoring obviously invalid cursors. +// yvalidate(i_point.first < Count()); + if (Count() <= i_point.first) + { + return {}; + } + ContentNode & rNode{*GetObject(i_point.first)}; +// yvalidate(i_point.second <= o3tl::make_unsigned(rNode.Len())); + if (o3tl::make_unsigned(rNode.Len()) < i_point.second) + { + return {}; + } + EditPaM point{&rNode, static_cast<sal_Int32>(i_point.second)}; + + return EditSelection{point, oMark ? *oMark : point}; +} + +static bool UpdatePosDelete(EditDoc & rDoc, EditPaM & rPos, ESelection const& rDeleted) +{ + auto const nPosNode{rDoc.GetPos(rPos.GetNode())}; + if (rDeleted.start.nPara == nPosNode) + { + if (rDeleted.start.nIndex < rPos.GetIndex()) { - pView->getImpl().SetEditSelection(sel); + if (rDeleted.start.nPara == rDeleted.end.nPara && rDeleted.end.nIndex < rPos.GetIndex()) + { + rPos.SetIndex(rPos.GetIndex() - (rDeleted.end.nIndex - rDeleted.start.nIndex)); + } + else + { + rPos.SetIndex(rDeleted.start.nIndex); + } + return true; } } + else if (rDeleted.start.nPara < nPosNode && nPosNode <= rDeleted.end.nPara) + { + if (nPosNode == rDeleted.end.nPara && rDeleted.end.nIndex < rPos.GetIndex()) + { + rPos.SetIndex(rPos.GetIndex() - (rDeleted.end.nIndex - rDeleted.start.nIndex)); + } + else + { + rPos.SetIndex(rDeleted.start.nIndex); + } + rPos.SetNode(rDoc.GetObject(rDeleted.start.nPara)); + return true; + } +#if 0 + else if (rDeleted.end.nPara < nPosNode) + { + rPos.SetNode(rDoc.GetObject(nPosNode - (rDeleted.end.nPara - rDeleted.start.nPara))); + return true; + } +#endif + return false; + } -// TODO test this +// TODO this is now sort of duplicated as UpdateSelectionsDelete, but +// consolidating that would probably require replacing EditSelection with +// ESelection in EditView static void YrsAdjustCursorsDel(ImpEditEngine & rIEE, EditDoc & rDoc, sal_Int32 const startNode, sal_Int32 const startPos, sal_Int32 const endNode, sal_Int32 const endPos) { + ESelection const deleted{startNode, startPos, endNode, endPos}; for (EditView *const pView : rIEE.GetEditViews()) { - bool bSet{false}; EditSelection sel{pView->getImpl().GetEditSelection()}; - ContentNode *const pStartNode{rDoc.GetObject(startNode)}; - ContentNode const*const pEndNode{rDoc.GetObject(endNode)}; - if ((sel.Min().GetNode() == pStartNode && startPos < sel.Min().GetIndex()) - || (startNode < rDoc.GetPos(sel.Min().GetNode()) && rDoc.GetPos(sel.Min().GetNode()) < endNode) - || (sel.Min().GetNode() == pEndNode && sel.Min().GetIndex() < endPos)) - { - sel.Min().SetNode(pStartNode); - sel.Min().SetIndex(startPos); - bSet = true; - } - else if (sel.Min().GetNode() == pEndNode) - { - sel.Min().SetNode(pStartNode); - sel.Min().SetIndex(startPos + sel.Min().GetIndex() - endPos); - bSet = true; - } - if ((sel.Max().GetNode() == pStartNode && startPos < sel.Max().GetIndex()) - || (startNode < rDoc.GetPos(sel.Max().GetNode()) && rDoc.GetPos(sel.Max().GetNode()) < endNode) - || (sel.Max().GetNode() == pEndNode && sel.Max().GetIndex() < endPos)) - { - sel.Max().SetNode(pStartNode); - sel.Max().SetIndex(startPos); - bSet = true; - } - else if (sel.Max().GetNode() == pEndNode) - { - sel.Max().SetNode(pStartNode); - sel.Max().SetIndex(startPos + sel.Max().GetIndex() - endPos); - bSet = true; - } - if (bSet) + bool isChanged{false}; + isChanged |= UpdatePosDelete(rDoc, sel.Min(), deleted); + isChanged |= UpdatePosDelete(rDoc, sel.Max(), deleted); + if (isChanged) { pView->getImpl().SetEditSelection(sel); } @@ -2832,7 +2862,6 @@ void EditDoc::YrsApplyEEDelta(YTransaction *const /*pTxn*/, YTextEvent const*con { return {rEntry.key, rEntry.value}; }; EditPaM const pam{maContents[node].get(), pos}; YrsImplInsertFeature<YDeltaAttr>(rIEE, pam, pChange[i].attributes, pChange[i].attributes_len, GetAttr); - YrsAdjustCursors(rIEE, *this, node, pos, maContents[node].get(), 1); ++pos; break; } @@ -2845,9 +2874,8 @@ void EditDoc::YrsApplyEEDelta(YTransaction *const /*pTxn*/, YTextEvent const*con EditSelection const sel{EditPaM{maContents[node].get(), pos}}; rIEE.InsertText(sel, str.copy(index, iPara)); } - EditPaM const newPos{rIEE.SplitContent(node, pos + iPara)}; + rIEE.SplitContent(node, pos + iPara); rIEE.SetStyleSheet(node, pStyle); - YrsAdjustCursors(rIEE, *this, node, pos, const_cast<ContentNode*>(newPos.GetNode()), -pos); index = iPara + 1; pos = 0; ++node; @@ -2868,7 +2896,6 @@ void EditDoc::YrsApplyEEDelta(YTransaction *const /*pTxn*/, YTextEvent const*con { EditSelection const sel{EditPaM{maContents[node].get(), pos}}; rIEE.InsertText(sel, str.copy(index, str.getLength() - index)); - YrsAdjustCursors(rIEE, *this, node, pos, maContents[node].get(), str.getLength() - index); pos += str.getLength() - index; } break; @@ -2936,6 +2963,8 @@ void EditDoc::YrsApplyEEDelta(YTransaction *const /*pTxn*/, YTextEvent const*con if (pChange[i].tag == Y_EVENT_CHANGE_DELETE) { YrsAdjustCursorsDel(rIEE, *this, nodeStart, posStart, node, pos); + ESelection const deleted{rIEE.CreateESel(*oSel)}; + rIEE.UpdateSelectionsDelete(deleted); rIEE.DeleteSelected(*oSel); node = nodeStart; pos = posStart; diff --git a/editeng/source/editeng/editview.cxx b/editeng/source/editeng/editview.cxx index 29f0065ffeda..65152d487577 100644 --- a/editeng/source/editeng/editview.cxx +++ b/editeng/source/editeng/editview.cxx @@ -347,6 +347,51 @@ void EditView::GetSelectionRectangles(std::vector<tools::Rectangle>& rLogicRects return getImpl().GetSelectionRectangles(getImpl().GetEditSelection(), rLogicRects); } +#if defined(YRS) +void EditView::YrsGetSelectionRectangles( + ::std::vector<::std::pair<OUString, ::std::vector<tools::Rectangle>>>& rLogicRects) const +{ + for (auto const& it : getImpl().m_PeerCursors) + { + rLogicRects.push_back({it.second.first, {}}); + EditSelection const sel{getEditEngine().getImpl().CreateSel(it.second.second)}; + if (sel.HasRange()) + { + getImpl().GetSelectionRectangles(sel, rLogicRects.back().second); + } + else + { + ParaPortion const& rParaPortion{getEditEngine().GetParaPortions().getRef(it.second.second.start.nPara)}; + auto const oCursor{getImpl().ImplGetCursorRectAndMaybeScroll(sel.Min(), rParaPortion, false)}; + if (oCursor) + { + auto const rect{std::get<0>(*oCursor)}; + rLogicRects.back().second.push_back(rect); + } + } + } +} + +void EditView::YrsApplyEECursor(OString const& rPeerId, OUString const& rAuthor, + ::std::pair<int64_t, int64_t> const point, + ::std::optional<::std::pair<int64_t, int64_t>> const oMark) +{ + ::std::optional<EditSelection> const oSel{getEditEngine().GetEditDoc().YrsReadEECursor(point, oMark)}; + if (!oSel) + { + SAL_DEBUG("YRS ignoring invalid cursor position"); + return; + } + ESelection const esel{getEditEngine().getImpl().CreateESel(*oSel)}; + getImpl().m_PeerCursors[rPeerId] = {rAuthor, esel}; +} + +bool EditView::YrsDelEECursor(OString const& rId) +{ + return getImpl().m_PeerCursors.erase(rId) != 0; +} +#endif + Point EditView::CalculateTextPaintStartPosition() const { return getImpEditEngine().CalculateTextPaintStartPosition(getImpl()); diff --git a/editeng/source/editeng/impedit.hxx b/editeng/source/editeng/impedit.hxx index de80aa861af7..4353d6ba2068 100644 --- a/editeng/source/editeng/impedit.hxx +++ b/editeng/source/editeng/impedit.hxx @@ -287,6 +287,13 @@ private: EESelectionMode meSelectionMode; EditSelection maEditSelection; EEAnchorMode meAnchorMode; +#if defined(YRS) +public: + /// when SwAnnotationWin::UpdateData() is called, the EE is cleared + /// and recreated, so use ESelection not EditSelection to survive this! + ::std::unordered_map<OString, ::std::pair<OUString, ESelection>> m_PeerCursors; +private: +#endif /// mechanism to change from the classic refresh mode that simply // invalidates the area where text was changed. When set, the invalidate @@ -702,7 +709,7 @@ private: void ImpBreakLine(ParaPortion& rParaPortion, EditLine& rLine, TextPortion const * pPortion, sal_Int32 nPortionStart, tools::Long nRemainingWidth, bool bCanHyphenate); void ImpAdjustBlocks(ParaPortion& rParaPortion, EditLine& rLine, tools::Long nRemainingSpace ); - EditPaM ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pRight, bool bBackward = false ); + EditPaM ImpConnectParagraphs(ContentNode* pLeft, ContentNode* pRight, bool bBackward = false, bool isUpdateCursors = true); EditPaM ImpDeleteSelection(const EditSelection& rCurSel); EditPaM ImpInsertParaBreak( EditPaM& rPaM, bool bKeepEndingAttribs = true ); EditPaM ImpInsertParaBreak( const EditSelection& rEditSelection ); @@ -1013,6 +1020,8 @@ public: bool UpdateSelection(EditSelection &); void UpdateSelections(); + void UpdateSelectionsDelete(ESelection const& rDeleted); + void UpdateSelectionsInsert(ESelection const& rInserted); void EnableUndo( bool bEnable ); bool IsUndoEnabled() const { return mbUndoEnabled; } diff --git a/editeng/source/editeng/impedit2.cxx b/editeng/source/editeng/impedit2.cxx index bc00e49613d0..1b022a323960 100644 --- a/editeng/source/editeng/impedit2.cxx +++ b/editeng/source/editeng/impedit2.cxx @@ -474,6 +474,7 @@ bool ImpEditEngine::Command( const CommandEvent& rCEvt, EditView* pView ) FormatAndLayout( pView ); } + UpdateSelections(); // other views EditSelection aNewSel( EditPaM( mpIMEInfos->aPos.GetNode(), mpIMEInfos->aPos.GetIndex()+pData->GetCursorPos() ) ); pView->SetSelection( CreateESel( aNewSel ) ); pView->SetInsertMode( !pData->IsCursorOverwrite() ); @@ -2212,7 +2213,8 @@ EditSelection ImpEditEngine::ImpMoveParagraphs( Range aOldPositions, sal_Int32 n } -EditPaM ImpEditEngine::ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pRight, bool bBackward ) +EditPaM ImpEditEngine::ImpConnectParagraphs(ContentNode* pLeft, ContentNode* pRight, + bool bBackward, bool const isUpdateCursors) { OSL_ENSURE( pLeft != pRight, "Join together the same paragraph ?" ); OSL_ENSURE(maEditDoc.GetPos(pLeft) != EE_PARA_MAX, "Inserted node not found (1)"); @@ -2230,6 +2232,7 @@ EditPaM ImpEditEngine::ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pR sal_Int32 nParagraphTobeDeleted = maEditDoc.GetPos( pRight ); maDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pRight, nParagraphTobeDeleted )); + ESelection const deleted{nParagraphTobeDeleted - 1, pLeft->Len(), nParagraphTobeDeleted, 0}; GetEditEnginePtr()->ParagraphConnected( maEditDoc.GetPos( pLeft ), maEditDoc.GetPos( pRight ) ); @@ -2297,6 +2300,11 @@ EditPaM ImpEditEngine::ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pR } } + if (isUpdateCursors) + { + UpdateSelectionsDelete(deleted); + } + TextModified(); return aPaM; @@ -2398,6 +2406,7 @@ EditPaM ImpEditEngine::ImpDeleteSelection(const EditSelection& rCurSel) if ( !rCurSel.HasRange() ) return rCurSel.Min(); + ESelection const deleted{CreateESel(rCurSel)}; EditSelection aCurSel(rCurSel); aCurSel.Adjust( maEditDoc ); EditPaM aStartPaM(aCurSel.Min()); @@ -2440,7 +2449,7 @@ EditPaM ImpEditEngine::ImpDeleteSelection(const EditSelection& rCurSel) assert(pPortion); pPortion->MarkSelectionInvalid( 0 ); // Join together... - aStartPaM = ImpConnectParagraphs( aStartPaM.GetNode(), aEndPaM.GetNode() ); + aStartPaM = ImpConnectParagraphs(aStartPaM.GetNode(), aEndPaM.GetNode(), false, false); } else { @@ -2450,6 +2459,7 @@ EditPaM ImpEditEngine::ImpDeleteSelection(const EditSelection& rCurSel) pPortion->MarkInvalid( aEndPaM.GetIndex(), aStartPaM.GetIndex() - aEndPaM.GetIndex() ); } + UpdateSelectionsDelete(deleted); UpdateSelections(); TextModified(); return aStartPaM; @@ -2467,8 +2477,13 @@ void ImpEditEngine::RemoveParagraph( sal_Int32 nPara ) if ( pNode && pPortion ) { // No Undo encapsulation needed. + auto const len{pNode->Len()}; ImpRemoveParagraph(nPara); InvalidateFromParagraph(nPara); + ESelection const deleted{nPara == 0 ? nPara : nPara - 1, + nPara == 0 ? 0 : maEditDoc.GetObject(nPara-1)->Len(), + nPara, len}; + UpdateSelectionsDelete(deleted); UpdateSelections(); if (IsUpdateLayout()) FormatAndLayout(); @@ -2830,6 +2845,10 @@ EditPaM ImpEditEngine::ImpInsertText(const EditSelection& aCurSel, const OUStrin UndoActionEnd(); + ESelection inserted{CreateEPaM(aCurPaM)}; + inserted.end = CreateEPaM(aPaM); + UpdateSelectionsInsert(inserted); + TextModified(); return aPaM; } @@ -2876,6 +2895,10 @@ EditPaM ImpEditEngine::ImpInsertFeature(const EditSelection& rCurSel, const SfxP assert(pPortion); pPortion->MarkInvalid( aPaM.GetIndex()-1, 1 ); + ESelection inserted{CreateEPaM(rCurSel.Min())}; + inserted.end = CreateEPaM(aPaM); + UpdateSelectionsInsert(inserted); + TextModified(); return aPaM; @@ -2955,6 +2978,10 @@ EditPaM ImpEditEngine::ImpInsertParaBreak( EditPaM& rPaM, bool bKeepEndingAttrib if( nullptr != rPaM.GetNode() ) rPaM.GetNode()->checkAndDeleteEmptyAttribs(); // if empty Attributes have emerged. + ESelection inserted{CreateEPaM(rPaM)}; + inserted.end = CreateEPaM(aPaM); + UpdateSelectionsInsert(inserted); + TextModified(); return aPaM; } @@ -3805,10 +3832,150 @@ void ImpEditEngine::UpdateSelections() { pView->getImpl().SetEditSelection(aCurSel); } +#if defined(YRS) + for (auto & it : pView->getImpl().m_PeerCursors) + { + EditSelection sel(CreateSel(it.second.second)); + if (UpdateSelection(sel)) + { + assert(false); // should have called Insert/Delete? + it.second.second = CreateESel(sel); + } + } +#endif } maDeletedNodes.clear(); } +#if defined(YRS) +static bool UpdatePosDelete(EPaM & rPos, ESelection const& rDeleted) +{ + // correct towards start: RemoveParagraph(Count()) can't work otherwise + assert(rDeleted.IsAdjusted()); + if (rDeleted.start.nPara == rPos.nPara) + { + if (rDeleted.start.nIndex < rPos.nIndex) + { + if (rDeleted.start.nPara == rDeleted.end.nPara && rDeleted.end.nIndex < rPos.nIndex) + { + rPos.nIndex -= (rDeleted.end.nIndex - rDeleted.start.nIndex); + } + else + { + rPos.nIndex = rDeleted.start.nIndex; + } + return true; + } + } + else if (rDeleted.start.nPara < rPos.nPara && rPos.nPara <= rDeleted.end.nPara) + { + if (rPos.nPara == rDeleted.end.nPara && rDeleted.end.nIndex < rPos.nIndex) + { + rPos.nIndex -= rDeleted.end.nIndex - rDeleted.start.nIndex; + } + else + { + rPos.nIndex = rDeleted.start.nIndex; + } + rPos.nPara = rDeleted.start.nPara; + return true; + } + else if (rDeleted.end.nPara < rPos.nPara) + { + rPos.nPara -= rDeleted.end.nPara - rDeleted.start.nPara; + return true; + } + return false; +} +#endif + +static bool UpdatePosInsert(EPaM & rPos, ESelection const& rInserted) +{ + assert(rInserted.IsAdjusted()); + if (rInserted.start.nPara == rPos.nPara) + { + if (rInserted.start.nIndex <= rPos.nIndex) + { + rPos.nPara = rInserted.end.nPara; + rPos.nIndex = rInserted.end.nIndex + (rPos.nIndex - rInserted.start.nIndex); + return true; + } + } + else if (rInserted.start.nPara < rPos.nPara && rInserted.start.nPara != rInserted.end.nPara) + { + rPos.nPara += rInserted.end.nPara - rInserted.start.nPara; + return true; + } + return false; +} + +void ImpEditEngine::UpdateSelectionsDelete(ESelection const& rDeleted) +{ + ESelection deleted(rDeleted); + deleted.Adjust(); + for (EditView *const pView : maEditViews) + { + // FIXME fails because CreateESel needs the deleted nodes! but if it's + // called before deleting the nodes, it will assign wrong nodes... + // perhaps the cursors could be stored as ESelection in the first + // place, but that would require reviewing all code that inserts or + // deletes nodes if it does the update in the correct place... +#if 0 + EditSelection const sel{pView->getImpl().GetEditSelection()}; + ESelection esel{CreateESel(sel)}; + bool isChanged{false}; + isChanged |= UpdatePosDelete(esel.start, deleted); + isChanged |= UpdatePosDelete(esel.end, deleted); + if (isChanged) + { + pView->getImpl().SetEditSelection(CreateSel(esel)); + } +#else + (void)pView; + (void)deleted; +#endif +#if defined(YRS) + for (auto & it : pView->getImpl().m_PeerCursors) + { + UpdatePosDelete(it.second.second.start, deleted); + UpdatePosDelete(it.second.second.end, deleted); + } +#endif + } +} + +void ImpEditEngine::UpdateSelectionsInsert(ESelection const& rInserted) +{ + for (EditView *const pView : maEditViews) + { + EditSelection const sel{pView->getImpl().GetEditSelection()}; + ESelection esel{CreateESel(sel)}; + // nodes have been inserted, but CreateESel counts them: adjust for this! + if (rInserted.start.nPara < esel.start.nPara) + { + esel.start.nPara -= rInserted.end.nPara - rInserted.start.nPara; + } + if (rInserted.start.nPara < esel.end.nPara) + { + esel.end.nPara -= rInserted.end.nPara - rInserted.start.nPara; + } + bool isChanged{false}; + isChanged |= UpdatePosInsert(esel.start, rInserted); + isChanged |= UpdatePosInsert(esel.end, rInserted); + if (isChanged) + { + pView->getImpl().SetEditSelection(CreateSel(esel)); + } +#if defined(YRS) + for (auto & it : pView->getImpl().m_PeerCursors) + { + UpdatePosInsert(it.second.second.start, rInserted); + UpdatePosInsert(it.second.second.end, rInserted); + } +#endif + } +} + EditSelection ImpEditEngine::ConvertSelection( sal_Int32 nStartPara, sal_Int32 nStartPos, sal_Int32 nEndPara, sal_Int32 nEndPos ) { diff --git a/editeng/source/editeng/impedit4.cxx b/editeng/source/editeng/impedit4.cxx index 3b7520a39658..54580a5439b4 100644 --- a/editeng/source/editeng/impedit4.cxx +++ b/editeng/source/editeng/impedit4.cxx @@ -3240,11 +3240,15 @@ short ImpEditEngine::ReplaceTextOnly( DBG_ASSERT( (nCurrentPos+1) < pNode->Len(), "TransliterateText - String smaller than expected!" ); GetEditDoc().RemoveChars( EditPaM( pNode, nCurrentPos+1 ), -nDiff); + ESelection const deleted{maEditDoc.GetPos(pNode), nCurrentPos+1, maEditDoc.GetPos(pNode), nCurrentPos-nDiff}; + UpdateSelectionsDelete(deleted); } else { DBG_ASSERT( nDiff == 1, "TransliterateText - Diff other than expected! But should work..." ); GetEditDoc().InsertText( EditPaM( pNode, nCurrentPos ), OUStringChar(rNewText[n]) ); + ESelection const inserted{maEditDoc.GetPos(pNode), nCurrentPos, maEditDoc.GetPos(pNode), nCurrentPos+nDiff}; + UpdateSelectionsInsert(inserted); } nDiffs = sal::static_int_cast< short >(nDiffs + nDiff); diff --git a/include/editeng/editview.hxx b/include/editeng/editview.hxx index 750848eaee91..4e1ee56ef7ce 100644 --- a/include/editeng/editview.hxx +++ b/include/editeng/editview.hxx @@ -41,6 +41,7 @@ class IYrsTransactionSupplier; typedef struct TransactionInner YTransaction; typedef struct YTextEvent YTextEvent; +typedef struct Branch Branch; #endif class EditTextObject; @@ -416,6 +417,12 @@ public: void YrsReadEEState(YTransaction *); void YrsApplyEEDelta(YTransaction *, YTextEvent const* pEvent); OString GetYrsCommentId() const; + void YrsApplyEECursor(OString const& rPeerId, OUString const& rAuthor, + ::std::pair<int64_t, int64_t> point, + ::std::optional<::std::pair<int64_t, int64_t>> oMark); + bool YrsDelEECursor(OString const& rPeerId); + void YrsGetSelectionRectangles( + ::std::vector<::std::pair<OUString, ::std::vector<tools::Rectangle>>>& rLogicRects) const; #endif }; diff --git a/svx/source/dialog/weldeditview.cxx b/svx/source/dialog/weldeditview.cxx index 86d64454e0b4..399245547284 100644 --- a/svx/source/dialog/weldeditview.cxx +++ b/svx/source/dialog/weldeditview.cxx @@ -206,7 +206,6 @@ void WeldEditView::PaintSelection(vcl::RenderContext& rRenderContext, tools::Rec aLogicRanges.emplace_back(nLeft, nTop, nRight, nBottom); } - // get the system's highlight color sdr::overlay::OverlaySelection aCursorOverlay(sdr::overlay::OverlayType::Transparent, color, std::move(aLogicRanges), true); @@ -248,6 +247,7 @@ void WeldEditView::DoPaint(vcl::RenderContext& rRenderContext, const tools::Rect std::vector<tools::Rectangle> aLogicRects; pEditView->GetSelectionRectangles(aLogicRects); + // get the system's highlight color const Color aHighlight(SvtOptionsDrawinglayer::getHilightColor()); PaintSelection(rRenderContext, rRect, aLogicRects, aHighlight); diff --git a/sw/inc/AnnotationWin.hxx b/sw/inc/AnnotationWin.hxx index c63d6d339d1d..63e7abeff956 100644 --- a/sw/inc/AnnotationWin.hxx +++ b/sw/inc/AnnotationWin.hxx @@ -211,11 +211,14 @@ class SAL_DLLPUBLIC_RTTI SwAnnotationWin final : public InterimItemWindow bool IsRootNote() const; void SetAsRoot(); + void queue_draw(); + private: virtual void LoseFocus() override; virtual void GetFocus() override; + virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override; void SetSizePixel( const Size& rNewSize ) override; DECL_DLLPRIVATE_LINK(ModifyHdl, LinkParamNone*, void); diff --git a/sw/source/core/doc/DocumentStateManager.cxx b/sw/source/core/doc/DocumentStateManager.cxx index 91f39d7f7634..c0735e01de94 100644 --- a/sw/source/core/doc/DocumentStateManager.cxx +++ b/sw/source/core/doc/DocumentStateManager.cxx @@ -630,6 +630,7 @@ struct ObserveCursorState : public ObserveState { struct Update { OString const peerId; + ::std::optional<OString> const oCommentId; ::std::optional<OUString> const oAuthor; ::std::pair<int64_t, int64_t> const point; ::std::optional<::std::pair<int64_t, int64_t>> const oMark; @@ -641,8 +642,25 @@ void YrsCursorUpdates(ObserveCursorState & rState) { for (auto const& it : rState.CursorUpdates) { -#if defined(LOPLUGIN_BLOCK_BLOCK) -#endif + if (it.oCommentId) + { + auto const it2{rState.rYrsSupplier.GetComments().find(*it.oCommentId)}; + yvalidate(it2 != rState.rYrsSupplier.GetComments().end()); + SwAnnotationWin & rWin{*it2->second.front()->mpPostIt}; + // note: rState.pTxn is invalid at this point! + rWin.GetOutlinerView()->GetEditView().YrsApplyEECursor(it.peerId, *it.oAuthor, it.point, it.oMark); + if ((rWin.GetStyle() & WB_DIALOGCONTROL) == 0) + { + // note: Invalidate does work with gen but does not with gtk3 + //rWin.Invalidate(); // not active window, force paint + rWin.queue_draw(); + } + else + { // apparently this repaints active window + rWin.GetOutlinerView()->GetEditView().Invalidate(); + } + } + else { ::std::optional<SwPosition> oMark; if (it.oMark) @@ -678,8 +696,43 @@ void YrsCursorUpdates(ObserveCursorState & rState) } } +void YrsInvalidateEECursors(ObserveState const& rState, + OString const& rPeerId, OString const*const pCommentId) +{ + for (auto const& it : rState.rYrsSupplier.GetComments()) + { + if (pCommentId == nullptr || *pCommentId != it.first) + { + SwAnnotationWin & rWin{*it.second.front()->mpPostIt}; + if (rWin.GetOutlinerView()->GetEditView().YrsDelEECursor(rPeerId)) + { + rWin.queue_draw(); // repaint + } + } + } +} + +void YrsInvalidateSwCursors(ObserveState const& rState, + OString const& rPeerId, OUString const& rAuthor, bool const isAdd) +{ + for (SwViewShell & rShell : rState.rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()->GetRingContainer()) + { + if (auto const pShell{dynamic_cast<SwCursorShell *>(&rShell)}) + { + if (isAdd) + { + pShell->YrsAddCursor(rPeerId, {}, {}, rAuthor); + } + else + { + pShell->YrsSetCursor(rPeerId, {}, {}); + } + } + } +} + void YrsReadCursor(ObserveCursorState & rState, OString const& rPeerId, - YOutput const& rCursor, ::std::optional<OUString> const oAuthor) + YOutput const& rCursor, OUString const& rAuthor, bool const isAdd) { switch (rCursor.tag) { @@ -689,10 +742,32 @@ void YrsReadCursor(ObserveCursorState & rState, OString const& rPeerId, auto const len{yarray_len(pArray)}; if (len == 3 || len == 5) { - // TODO cursor in EE + ::std::unique_ptr<YOutput, YOutputDeleter> const pComment{yarray_get(pArray, rState.pTxn, 0)}; + yvalidate(pComment->tag == Y_JSON_STR && pComment->len < SAL_MAX_INT32); + OString const commentId{pComment->value.str, static_cast<sal_Int32>(pComment->len)}; + YrsInvalidateEECursors(rState, rPeerId, &commentId); + YrsInvalidateSwCursors(rState, rPeerId, rAuthor, false); + ::std::optional<::std::pair<int64_t, int64_t>> oMark; + if (len == 5) + { + ::std::unique_ptr<YOutput, YOutputDeleter> const pNode{ + yarray_get(pArray, rState.pTxn, 3)}; + yvalidate(pNode->tag == Y_JSON_INT); + ::std::unique_ptr<YOutput, YOutputDeleter> const pContent{ + yarray_get(pArray, rState.pTxn, 4)}; + 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, 1)}; + yvalidate(pNode->tag == Y_JSON_INT); + ::std::unique_ptr<YOutput, YOutputDeleter> const pContent{yarray_get(pArray, rState.pTxn, 2)}; + 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, ::std::optional<OString>{commentId}, ::std::optional<OUString>{rAuthor}, pos, oMark); } else if (len == 2 || len == 4) { + YrsInvalidateEECursors(rState, rPeerId, nullptr); // 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; @@ -710,27 +785,17 @@ void YrsReadCursor(ObserveCursorState & rState, OString const& rPeerId, ::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); + rState.CursorUpdates.emplace_back(rPeerId, ::std::optional<OString>{}, + isAdd ? ::std::optional<OUString>{rAuthor} : ::std::optional<OUString>{}, + 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, {}, {}); - } - } - } + YrsInvalidateEECursors(rState, rPeerId, nullptr); + YrsInvalidateSwCursors(rState, rPeerId, rAuthor, isAdd); break; default: yvalidate(false); @@ -783,7 +848,7 @@ extern "C" void observe_cursors(void *const pState, uint32_t count, YEvent const 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}); + YrsReadCursor(rState, peerId, *pCursor, author, true); break; } default: @@ -830,7 +895,10 @@ extern "C" void observe_cursors(void *const pState, uint32_t count, YEvent const 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], {}); + Branch const*const pArray{yarray_event_target(pEvent)}; + ::std::unique_ptr<YOutput, YOutputDeleter> const pAuthor{yarray_get(pArray, rState.pTxn, 0)}; + OUString const author{pAuthor->value.str, static_cast<sal_Int32>(pAuthor->len), RTL_TEXTENCODING_UTF8}; + YrsReadCursor(rState, peerId, pChange[2].values[0], author, false); break; } default: @@ -1168,12 +1236,12 @@ void DocumentStateManager::YrsRemoveComment(SwPosition const& rPos, OString cons void DocumentStateManager::YrsNotifyCursorUpdate() { SwWrtShell *const pShell{dynamic_cast<SwWrtShell*>(m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell())}; - if (!m_pYrsSupplier || !pShell->GetView().GetPostItMgr()) + YTransaction *const pTxn{m_pYrsSupplier ? m_pYrsSupplier->GetWriteTransaction() : nullptr}; + if (!pTxn || !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())}; @@ -1464,7 +1532,6 @@ void DocumentStateManager::SetModified() #if defined(YRS) SAL_DEBUG("YRS SetModified"); - // FIXME: this is called only on LoseFocus! not while editing comment YrsCommitModified(); #endif } diff --git a/sw/source/uibase/docvw/AnnotationWin2.cxx b/sw/source/uibase/docvw/AnnotationWin2.cxx index 59843da348d6..4560672bb304 100644 --- a/sw/source/uibase/docvw/AnnotationWin2.cxx +++ b/sw/source/uibase/docvw/AnnotationWin2.cxx @@ -937,6 +937,22 @@ void SwAnnotationWin::LoseFocus() { } +void SwAnnotationWin::queue_draw() +{ + if (mxSidebarTextControlWin) + { + mxSidebarTextControlWin->queue_draw(); + } +} + +void SwAnnotationWin::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + if (mxSidebarTextControl) + { + mxSidebarTextControl->Paint(rRenderContext, rRect); + } +} + void SwAnnotationWin::ShowNote() { SetPosAndSize(); diff --git a/sw/source/uibase/docvw/SidebarTxtControl.cxx b/sw/source/uibase/docvw/SidebarTxtControl.cxx index e59dbe75c2de..ebc8cde72761 100644 --- a/sw/source/uibase/docvw/SidebarTxtControl.cxx +++ b/sw/source/uibase/docvw/SidebarTxtControl.cxx @@ -57,6 +57,7 @@ #include <IDocumentDeviceAccess.hxx> #if defined(YRS) #include <IDocumentState.hxx> +#include <swmodule.hxx> #endif #include <redline.hxx> #include <memory> @@ -205,9 +206,18 @@ OUString SidebarTextControl::RequestHelp(tools::Rectangle& rHelpRect) #if defined(YRS) void SidebarTextControl::EditViewInvalidate(const tools::Rectangle& rRect) { + SAL_DEBUG("YRS EditViewInvalidate"); + mrDocView.GetDocShell()->GetDoc()->getIDocumentState().YrsNotifyCursorUpdate(); mrDocView.GetDocShell()->GetDoc()->getIDocumentState().YrsCommitModified(); return WeldEditView::EditViewInvalidate(rRect); } + +void SidebarTextControl::EditViewSelectionChange() +{ + SAL_DEBUG("YRS EditViewSelectionChange"); + mrDocView.GetDocShell()->GetDoc()->getIDocumentState().YrsNotifyCursorUpdate(); + return WeldEditView::EditViewSelectionChange(); +} #endif void SidebarTextControl::EditViewScrollStateChange() @@ -268,6 +278,25 @@ void SidebarTextControl::Paint(vcl::RenderContext& rRenderContext, const tools:: DoPaint(rRenderContext, rRect); +#if defined(YRS) + if (EditView *const pEditView{GetEditView()}) + { + rRenderContext.Push(vcl::PushFlags::ALL); + rRenderContext.SetClipRegion(); + + ::std::vector<::std::pair<OUString, ::std::vector<tools::Rectangle>>> rects; + pEditView->YrsGetSelectionRectangles(rects); + for (auto const& it : rects) + { + ::std::size_t const authorId{SwModule::get()->InsertRedlineAuthor(it.first)}; + Color const color{SwPostItMgr::GetColorAnchor(authorId)}; + PaintSelection(rRenderContext, rRect, it.second, color); + } + + rRenderContext.Pop(); + } +#endif + if (mrSidebarWin.GetLayoutStatus() != SwPostItHelper::DELETED) return; diff --git a/sw/source/uibase/docvw/SidebarTxtControl.hxx b/sw/source/uibase/docvw/SidebarTxtControl.hxx index bbce1ac833e7..22b7a8d3fb32 100644 --- a/sw/source/uibase/docvw/SidebarTxtControl.hxx +++ b/sw/source/uibase/docvw/SidebarTxtControl.hxx @@ -40,8 +40,6 @@ class SidebarTextControl : public WeldEditView void MakeVisible(); protected: - virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override; - virtual bool Command(const CommandEvent& rCEvt) override; virtual void GetFocus() override; virtual void LoseFocus() override; @@ -53,12 +51,15 @@ class SidebarTextControl : public WeldEditView SwView& rDocView, SwPostItMgr& rPostItMgr); + virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override; + virtual EditView* GetEditView() const override; virtual EditEngine* GetEditEngine() const override; #if defined(YRS) virtual void EditViewInvalidate(const tools::Rectangle& rRect) override; + virtual void EditViewSelectionChange() override; #endif virtual void EditViewScrollStateChange() override; |