/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/ #include <DocumentStateManager.hxx> #include <doc.hxx> #include <DocumentStatisticsManager.hxx> #include <IDocumentUndoRedo.hxx> #include <DocumentLayoutManager.hxx> #include <acorrect.hxx>
YrsTransactionSupplier::~YrsTransactionSupplier()
{ // with cursors there will always be a pending transaction, and there is no api to cancel it, so just ignore it... //assert(m_pCurrentWriteTransaction == nullptr);
(void) m_pCurrentWriteTransaction;
}
extern"C"void observe_comments(void *const pState, uint32_t count, YEvent const*const events)
{
SAL_INFO("sw.yrs", "YRS observe_comments");
ObserveState & rState{*static_cast<ObserveState*>(pState)}; // DO NOT call rState.rYrsSupplier.GetWriteTransaction()!
YTransaction *const pTxn{rState.pTxn}; // ??? that is TransactionMut - there is no way to construct YTransaction from it??? YTransaction *const pTxn{pEvent->txn};
void YrsCursorUpdates(ObserveCursorState & rState)
{ for (autoconst& it : rState.CursorUpdates)
{ if (it.oCommentId)
{ autoconst 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)
{
yvalidate(SwNodeOffset{it.oMark->first} < rState.rDoc.GetNodes().Count());
SwNode & rNode{*rState.rDoc.GetNodes()[SwNodeOffset{it.oMark->first}]};
yvalidate(rNode.IsTextNode());
yvalidate(it.oMark->second <= o3tl::make_unsigned(rNode.GetTextNode()->Len()));
oMark.emplace(*rNode.GetTextNode(), static_cast<sal_Int32>(it.oMark->second));
}
extern"C"void observe_cursors(void *const pState, uint32_t count, YEvent const*const events)
{
SAL_INFO("sw.yrs", "YRS observe_cursors"); // note: it (very rarely) happens that observe_cursors will be called // when a cursor is moved into a comment that is newly inserted, but // observe_comments hasn't been called to actually insert the comment yet // => need to buffer all the cursor updates and replay them at the end...
ObserveCursorState & rState{*static_cast<ObserveCursorState*>(pState)};
for (decltype(count) i = 0; i < count; ++i)
{ switch (events[i].tag)
{ case Y_MAP:
{ // new peer?
YMapEvent const*const pEvent{&events[i].content.map};
uint32_t lenP{0}; /*YPathSegment *const pPath{*/ymap_event_path(pEvent, &lenP);
yvalidate(lenP == 0);
uint32_t lenK{0};
YEventKeyChange *const pChange{ymap_event_keys(pEvent, &lenK)}; for (decltype(lenK) j = 0; j < lenK; ++j)
{
OString const peerId{pChange[j].key};
yvalidate(peerId != OString::number(ydoc_id(rState.rYrsSupplier.GetYDoc()))); // should never be updated by peers? switch (pChange[j].tag)
{ case Y_EVENT_KEY_CHANGE_UPDATE:
yvalidate(false); case Y_EVENT_KEY_CHANGE_ADD:
{ switch (pChange[j].new_value->tag)
{ case Y_ARRAY:
{
Branch const*const pArray{pChange[j].new_value->value.y_type}; autoconst len{yarray_len(pArray)};
yvalidate(len == 2);
::std::unique_ptr<YOutput, YOutputDeleter> const pAuthor{
yarray_get(pArray, rState.pTxn, 0)};
yvalidate(pAuthor->tag == Y_JSON_STR && pAuthor->len < SAL_MAX_INT32);
OUString const author{pAuthor->value.str, static_cast<sal_Int32>(pAuthor->len), RTL_TEXTENCODING_UTF8};
::std::unique_ptr<YOutput, YOutputDeleter> const pCursor{
yarray_get(pArray, rState.pTxn, 1)};
YrsReadCursor(rState, peerId, *pCursor, author, true); break;
} default:
yvalidate(false);
} break;
} case Y_EVENT_KEY_CHANGE_DELETE:
{ for (SwViewShell & rShell : rState.rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()->GetRingContainer())
{ if (autoconst pShell{dynamic_cast<SwCursorShell *>(&rShell)})
{
pShell->YrsDelCursor(peerId);
}
}
assert(false); // TODO cannot test this currently? break;
} default:
assert(false);
}
} break;
} case Y_ARRAY:
{
YArrayEvent const*const pEvent{&events[i].content.array}; // position changed?
uint32_t lenP{0};
YPathSegment *const pPath{yarray_event_path(pEvent, &lenP)};
yvalidate(lenP == 1);
yvalidate(pPath[0].tag == Y_EVENT_PATH_KEY);
OString const peerId{pPath[0].value.key};
ypath_destroy(pPath, lenP);
uint32_t lenC{0};
YEventChange *const pChange{yarray_event_delta(pEvent, &lenC)}; // position update looks like this
yvalidate(lenC == 3);
yvalidate(pChange[0].tag == Y_EVENT_CHANGE_RETAIN);
yvalidate(pChange[0].len == 1);
yvalidate(pChange[1].tag == Y_EVENT_CHANGE_DELETE);
yvalidate(pChange[1].len == 1);
yvalidate(pChange[2].tag == Y_EVENT_CHANGE_ADD);
yvalidate(pChange[2].len == 1);
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;
} #if ENABLE_YRS_WEAK case Y_WEAK_LINK:
{ // not sure what this is, but yffi doesn't have any API // for it, let's hope we can just ignore it // YWeakLinkEvent const*const pEvent{&events[i].content.weak}; break;
} #endif default:
assert(false);
}
}
}
void DocumentStateManager::YrsAddCommentImpl(SwPosition const& rAnchorPos, OString const& commentId)
{
SAL_INFO("sw.yrs", "YRS AddCommentImpl");
::std::vector<SwAnnotationItem *> items; // ??? TODO how should this work for multiple viewshells? every shell has its own EditEngine? unclear. for (SwViewShell & rShell : m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()->GetRingContainer())
{ for (::std::unique_ptr<SwAnnotationItem> const& it : *rShell.GetPostItMgr())
{ if (it->GetAnchorPosition() == rAnchorPos)
{
items.emplace_back(it.get()); // for a loaded document, GetOrCreateAnnotationWindowForLatestPostItField() cannot be used bool isNew{false};
SwAnnotationWin *const pWin{
rShell.GetPostItMgr()->GetOrCreateAnnotationWindow(*it, isNew)};
assert(pWin);
pWin->GetOutlinerView()->GetEditView().SetYrsCommentId(m_pYrsSupplier.get(), commentId);
}
}
}
m_pYrsSupplier->m_Comments.emplace(commentId, items);
}
void DocumentStateManager::YrsAddComment(SwPosition const& rPos,
::std::optional<SwPosition> const oAnchorStart, SwPostItField const& rField, boolconst isInsert)
{
SAL_INFO("sw.yrs", "YRS AddComment " << rPos);
OString const commentId{m_pYrsSupplier->GenNewCommentId()}; // this calls EditViewInvalidate so prevent destroying pTxn
YrsAddCommentImpl(rPos, commentId);
YTransaction *const pTxn{m_pYrsSupplier->GetWriteTransaction()}; // first, adjust position of all other comments in the paragraph if (isInsert)
{ // it could be faster to get the SwPostItField from the node, but then can't get the commentId for (autoconst& it : m_pYrsSupplier->m_Comments)
{
SwAnnotationItem const*const pItem{it.second.front()};
SwPosition const& rItPos{pItem->GetAnchorPosition()}; if (rPos.nNode == rItPos.nNode && rPos.nContent <= rItPos.nContent
&& it.first != commentId)
{
::std::unique_ptr<YOutput, YOutputDeleter> const pComment{
ymap_get(m_pYrsSupplier->m_pComments, pTxn, it.first.getStr())};
assert(pComment);
::std::unique_ptr<YOutput, YOutputDeleter> const pPos{yarray_get(pComment->value.y_type, pTxn, 0)};
assert(pPos);
::std::unique_ptr<YOutput, YOutputDeleter> const pPosN{yarray_get(pPos->value.y_type, pTxn, 0)};
::std::unique_ptr<YOutput, YOutputDeleter> const pPosC{yarray_get(pPos->value.y_type, pTxn, 1)}; // SwTextNode::Update already moved rItPos
assert(pPosN->value.integer == rItPos.GetNodeIndex().get());
assert(pPosC->value.integer + 1 == rItPos.GetContentIndex());
yarray_remove_range(pPos->value.y_type, pTxn, 0, 2);
YInput const anchorNode{yinput_long(rItPos.GetNodeIndex().get())};
YInput const anchorContent{yinput_long(rItPos.GetContentIndex())};
YInput posArray[]{anchorNode, anchorContent};
yarray_insert_range(pPos->value.y_type, pTxn, 0, posArray, 2);
} // anchor start can be in a different node than the field!
SwTextAnnotationField const& rHint{*static_cast<SwTextAnnotationField const*>(
pItem->GetFormatField().GetTextField())};
::sw::mark::AnnotationMark const*const pMark{rHint.GetAnnotationMark()}; if (pMark != nullptr
&& it.first != commentId // see testImageCommentAtChar with start == field pos
&& rPos.nNode == pMark->GetMarkStart().nNode
&& rPos.nContent <= pMark->GetMarkStart().nContent)
{
::std::unique_ptr<YOutput, YOutputDeleter> const pComment{
ymap_get(m_pYrsSupplier->m_pComments, pTxn, it.first.getStr())};
assert(pComment);
::std::unique_ptr<YOutput, YOutputDeleter> const pPos{yarray_get(pComment->value.y_type, pTxn, 0)};
assert(pPos);
assert(yarray_len(pPos->value.y_type) == 4);
::std::unique_ptr<YOutput, YOutputDeleter> const pPosN{yarray_get(pPos->value.y_type, pTxn, 2)};
::std::unique_ptr<YOutput, YOutputDeleter> const pPosC{yarray_get(pPos->value.y_type, pTxn, 3)}; // SwTextNode::Update already moved pMark
assert(pPosN->value.integer == pMark->GetMarkStart().GetNodeIndex().get());
assert(pPosC->value.integer + 1 == pMark->GetMarkStart().GetContentIndex());
yarray_remove_range(pPos->value.y_type, pTxn, 2, 2);
YInput const anchorStartNode{yinput_long(pMark->GetMarkStart().GetNodeIndex().get())};
YInput const anchorStartContent{yinput_long(pMark->GetMarkStart().GetContentIndex())};
YInput posArray[]{anchorStartNode, anchorStartContent};
yarray_insert_range(pPos->value.y_type, pTxn, 2, posArray, 2);
}
}
}
YInput const anchorNode{yinput_long(rPos.GetNodeIndex().get())};
YInput const anchorContent{yinput_long(rPos.GetContentIndex())};
YInput const anchorStartNode{oAnchorStart ? yinput_long(oAnchorStart->GetNodeIndex().get()) : yinput_undefined()};
YInput const anchorStartContent{oAnchorStart ? yinput_long(oAnchorStart->GetContentIndex()) : yinput_undefined()};
YInput posArray[]{anchorNode, anchorContent, anchorStartNode, anchorStartContent};
YInput const anchor{yinput_yarray(posArray, oAnchorStart ? 4 : 2)};
OString const authorString{OUStringToOString(rField.GetPar1(), RTL_TEXTENCODING_UTF8)};
OString const initialsString{OUStringToOString(rField.GetInitials(), RTL_TEXTENCODING_UTF8)};
OUStringBuffer dateBuf;
::sax::Converter::convertDateTime(dateBuf, rField.GetDateTime().GetUNODateTime(), nullptr, true);
OString const dateString{OUStringToOString(dateBuf.makeStringAndClear(), RTL_TEXTENCODING_UTF8)}; charconst*const propsNames[]{ "author", "initials", "date", "resolved", "parent" };
YInput const author{yinput_string(authorString.getStr())};
YInput const initials{yinput_string(initialsString.getStr())};
YInput const date{yinput_string(dateString.getStr())};
OString parentId; if (rField.GetParentPostItId() != 0)
{
sw::annotation::SwAnnotationWin const*const pWin{
m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()->GetPostItMgr()->GetAnnotationWin(rField.GetParentPostItId())};
assert(pWin);
parentId = pWin->GetOutlinerView()->GetEditView().GetYrsCommentId();
}
YInput const parent{yinput_string(parentId.getStr())};
YInput const resolved{yinput_bool(rField.GetResolved() ? Y_TRUE : Y_FALSE)};
YInput propsArray[]{author, initials, date, resolved, parent};
YInput const properties{yinput_ymap(const_cast<char**>(propsNames), propsArray, parentId.getLength() ? 5 : 4)};
YInput const text{yinput_ytext(const_cast<char*>(""))};
YInput commentArray[]{anchor, properties, text};
YInput const comment{yinput_yarray(commentArray, 3)};
ymap_insert(m_pYrsSupplier->m_pComments, pTxn, commentId.getStr(), &comment); // just use first one? // or check which one is active = (GetStyle() & WB_DIALOGCONTROL)
m_pYrsSupplier->m_Comments.find(commentId)->second.front()->mpPostIt->GetOutlinerView()->GetEditView().YrsWriteEEState();
// either update the cursors here, or wait for round-trip? //do it in 1 caller so that load document can batch it? CommitModified(); // SetModified is called earlier
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.