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>
This commit is contained in:
Michael Stahl
2025-05-12 11:33:51 +02:00
parent 7143fc5d6f
commit 0ff4c66190
13 changed files with 466 additions and 87 deletions
+2
View File
@@ -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;
+86 -57
View File
@@ -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;
}
if (bSet)
{
pView->getImpl().SetEditSelection(sel);
return {};
}
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};
}
// TODO test this
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())
{
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 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;
+45
View File
@@ -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());
+10 -1
View File
@@ -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; }
+169 -2
View File
@@ -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 )
{
+4
View File
@@ -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);
+7
View File
@@ -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
};
+1 -1
View File
@@ -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);
+3
View File
@@ -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);
+91 -24
View File
@@ -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
}
+16
View File
@@ -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();
@@ -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;
+3 -2
View File
@@ -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;