summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Stahl <michael.stahl@allotropia.de>2025-02-14 18:03:16 +0100
committerMichael Stahl <michael.stahl@allotropia.de>2025-05-12 11:33:51 +0200
commit0ff4c6619031582b6c520db90a17ed285973e1be (patch)
tree671ea1783ed1c278268dfb8a233ba9d42764933b
parent7143fc5d6f746543162d3de8320c10a0f7aeaca5 (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.hxx2
-rw-r--r--editeng/source/editeng/editdoc.cxx139
-rw-r--r--editeng/source/editeng/editview.cxx45
-rw-r--r--editeng/source/editeng/impedit.hxx11
-rw-r--r--editeng/source/editeng/impedit2.cxx171
-rw-r--r--editeng/source/editeng/impedit4.cxx4
-rw-r--r--include/editeng/editview.hxx7
-rw-r--r--svx/source/dialog/weldeditview.cxx2
-rw-r--r--sw/inc/AnnotationWin.hxx3
-rw-r--r--sw/source/core/doc/DocumentStateManager.cxx115
-rw-r--r--sw/source/uibase/docvw/AnnotationWin2.cxx16
-rw-r--r--sw/source/uibase/docvw/SidebarTxtControl.cxx29
-rw-r--r--sw/source/uibase/docvw/SidebarTxtControl.hxx5
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;