/* -*- 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 <memory>
#include <bookmark.hxx>
#include <IDocumentUndoRedo.hxx>
#include <IDocumentLinksAdministration.hxx>
#include <IDocumentState.hxx>
#include <doc.hxx>
#include <ndtxt.hxx>
#include <pam.hxx>
#include <swserv.hxx>
#include <sfx2/linkmgr.hxx>
#include <sfx2/viewsh.hxx>
#include <UndoBookmark.hxx>
#include <unobookmark.hxx>
#include <utility>
#include <xmloff/odffields.hxx>
#include <libxml/xmlwriter.h>
#include <comphelper/random.hxx>
#include <comphelper/sequence.hxx>
#include <comphelper/anytostring.hxx>
#include <sal/log.hxx>
#include <svl/numformat.hxx>
#include <svl/zforlist.hxx>
#include <edtwin.hxx>
#include <DateFormFieldButton.hxx>
#include <DropDownFormFieldButton.hxx>
#include <DocumentContentOperationsManager.hxx>
#include <comphelper/lok.hxx>
#include <txtfrm.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <rtl/strbuf.hxx>
#include <strings.hrc>
#include <tools/json_writer.hxx>
using namespace ::sw::mark;
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
namespace sw::mark
{
SwPosition FindFieldSep(Fieldmark const& rMark)
{
auto [/*const SwPosition&*/ rStartPos, rEndPos] = rMark.GetMarkStartEnd();
SwNodes const& rNodes(rStartPos.GetNodes());
SwNodeOffset const nStartNode(rStartPos.GetNodeIndex());
SwNodeOffset const nEndNode(rEndPos.GetNodeIndex());
int nFields(0);
std::optional<SwPosition> ret;
for (SwNodeOffset n = nEndNode; nStartNode <= n; --n)
{
SwNode *const pNode(rNodes[n]);
if (pNode->IsTextNode())
{
SwTextNode & rTextNode(*pNode->GetTextNode());
sal_Int32 const nStart(n == nStartNode
? rStartPos.GetContentIndex() + 1
: 0);
sal_Int32 const nEnd(n == nEndNode
// subtract 1 to ignore the end char
? rEndPos.GetContentIndex() - 1
: rTextNode.Len());
for (sal_Int32 i = nEnd; nStart < i; --i)
{
const sal_Unicode c(rTextNode.GetText()[i - 1]);
switch (c)
{
case CH_TXT_ATR_FIELDSTART:
--nFields;
assert(0 <= nFields);
break;
case CH_TXT_ATR_FIELDEND:
++nFields;
// fields in field result could happen by manual
// editing, although the field update deletes them
break;
case CH_TXT_ATR_FIELDSEP:
if (nFields == 0)
{
assert(!ret); // one per field
ret.emplace(rTextNode, i - 1);
#ifndef DBG_UTIL
return *ret;
#endif
}
break;
}
}
}
else if (pNode->IsEndNode() && !pNode->StartOfSectionNode()->IsSectionNode())
{
assert(nStartNode <= pNode->StartOfSectionIndex());
// fieldmark cannot overlap node section, unless it's a section
n = pNode->StartOfSectionIndex();
}
else
{
assert(pNode->IsNoTextNode() || pNode->IsSectionNode()
|| (pNode->IsEndNode() && pNode->StartOfSectionNode()->IsSectionNode()));
}
}
assert(ret); // must have found it
return *ret;
}
} // namespace sw::mark
namespace
{
void lcl_FixPosition(SwPosition& rPos)
{
// make sure the position has 1) the proper node, and 2) a proper index
SwTextNode* pTextNode = rPos.GetNode().GetTextNode();
if(pTextNode == nullptr && rPos.GetContentIndex() > 0)
{
SAL_INFO(
"sw.core",
"illegal position: " << rPos.GetContentIndex()
<< " without proper TextNode");
rPos.nContent.Assign(nullptr, 0);
}
else if(pTextNode != nullptr && rPos.GetContentIndex() > pTextNode->Len())
{
SAL_INFO(
"sw.core",
"illegal position: " << rPos.GetContentIndex()
<< " is beyond " << pTextNode->Len());
rPos.nContent.Assign(pTextNode, pTextNode->Len());
}
}
void lcl_AssertFieldMarksSet(const Fieldmark& rField,
const sal_Unicode aStartMark,
const sal_Unicode aEndMark)
{
if (aEndMark != CH_TXT_ATR_FORMELEMENT)
{
SwPosition const& rStart(rField.GetMarkStart());
assert(rStart.GetNode().GetTextNode()->GetText()[rStart.GetContentIndex()] == aStartMark); (void) rStart; (void) aStartMark;
SwPosition const sepPos(sw::mark::FindFieldSep(rField));
assert(sepPos.GetNode().GetTextNode()->GetText()[sepPos.GetContentIndex()] == CH_TXT_ATR_FIELDSEP); (void) sepPos;
}
else
{ // must be m_pPos1 < m_pPos2 because of asymmetric SplitNode update
assert(rField.GetMarkPos().GetContentIndex() + 1 == rField.GetOtherMarkPos().GetContentIndex());
}
SwPosition const& rEnd(rField.GetMarkEnd());
assert(rEnd.GetNode().GetTextNode()->GetText()[rEnd.GetContentIndex() - 1] == aEndMark); (void) rEnd;
}
void lcl_SetFieldMarks(Fieldmark& rField,
SwDoc& io_rDoc,
const sal_Unicode aStartMark,
const sal_Unicode aEndMark,
SwPosition const*const pSepPos)
{
io_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::UI_REPLACE, nullptr);
OUString startChar(aStartMark);
if (aEndMark != CH_TXT_ATR_FORMELEMENT
&& rField.GetMarkStart() == rField.GetMarkEnd())
{
// do only 1 InsertString call - to expand existing bookmarks at the
// position over the whole field instead of just aStartMark
startChar += OUStringChar(CH_TXT_ATR_FIELDSEP) + OUStringChar(aEndMark);
}
SwPosition start = rField.GetMarkStart();
if (aEndMark != CH_TXT_ATR_FORMELEMENT)
{
SwPaM aStartPaM(start);
io_rDoc.getIDocumentContentOperations().InsertString(aStartPaM, startChar);
start.AdjustContent( -startChar.getLength() ); // restore, it was moved by InsertString
// do not manipulate via reference directly but call SetMarkStartPos
// which works even if start and end pos were the same
rField.SetMarkStartPos( start );
SwPosition& rEnd = rField.GetMarkEnd(); // note: retrieve after
// setting start, because if start==end it can go stale, see SetMarkPos()
assert(pSepPos == nullptr || (start < *pSepPos && *pSepPos <= rEnd));
if (startChar.getLength() == 1)
{
*aStartPaM.GetPoint() = pSepPos ? *pSepPos : rEnd;
io_rDoc.getIDocumentContentOperations().InsertString(aStartPaM, OUString(CH_TXT_ATR_FIELDSEP));
if (!pSepPos || rEnd < *pSepPos)
{ // rEnd is not moved automatically if it's same as insert pos
rEnd.AdjustContent(1);
}
}
assert(pSepPos == nullptr || (start < *pSepPos && *pSepPos <= rEnd));
}
else
{
assert(pSepPos == nullptr);
}
SwPosition& rEnd = rField.GetMarkEnd();
if (aEndMark && startChar.getLength() == 1)
{
SwPaM aEndPaM(rEnd);
io_rDoc.getIDocumentContentOperations().InsertString(aEndPaM, OUString(aEndMark));
if (aEndMark != CH_TXT_ATR_FORMELEMENT)
{
rEnd.AdjustContent(1); // InsertString didn't move non-empty mark
}
else
{ // InsertString moved the mark's end, not its start
assert(rField.GetMarkPos().GetContentIndex() + 1 == rField.GetOtherMarkPos().GetContentIndex());
}
}
lcl_AssertFieldMarksSet(rField, aStartMark, aEndMark);
io_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::UI_REPLACE, nullptr);
}
void lcl_RemoveFieldMarks(const Fieldmark& rField,
SwDoc& io_rDoc,
const sal_Unicode aStartMark,
const sal_Unicode aEndMark)
{
io_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::UI_REPLACE, nullptr);
const SwPosition& rStart = rField.GetMarkStart();
SwTextNode const*const pStartTextNode = rStart.GetNode().GetTextNode();
assert(pStartTextNode);
if (aEndMark != CH_TXT_ATR_FORMELEMENT)
{
(void) pStartTextNode;
// check this before start / end because of the +1 / -1 ...
SwPosition const sepPos(sw::mark::FindFieldSep(rField));
io_rDoc.GetDocumentContentOperationsManager().DeleteDummyChar(rStart, aStartMark);
io_rDoc.GetDocumentContentOperationsManager().DeleteDummyChar(sepPos, CH_TXT_ATR_FIELDSEP);
}
const SwPosition& rEnd = rField.GetMarkEnd();
SwTextNode *const pEndTextNode = rEnd.GetNode().GetTextNode();
assert(pEndTextNode);
const sal_Int32 nEndPos = (rEnd == rStart)
? rEnd.GetContentIndex()
: rEnd.GetContentIndex() - 1;
assert(pEndTextNode->GetText()[nEndPos] == aEndMark);
SwPosition const aEnd(*pEndTextNode, nEndPos);
io_rDoc.GetDocumentContentOperationsManager().DeleteDummyChar(aEnd, aEndMark);
io_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::UI_REPLACE, nullptr);
}
auto InvalidatePosition(SwPosition const& rPos) -> void
{
SwUpdateAttr const aHint(rPos.GetContentIndex(), rPos.GetContentIndex(), 0);
rPos.GetNode().GetTextNode()->CallSwClientNotify(sw::LegacyModifyHint(&aHint, &aHint));
}
}
namespace sw::mark
{
MarkBase::MarkBase(const SwPaM& aPaM,
OUString aName)
: m_oPos1(*aPaM.GetPoint())
, m_aName(std::move(aName))
{
m_oPos1->SetOwner(this);
lcl_FixPosition(*m_oPos1);
if (aPaM.HasMark() && (*aPaM.GetMark() != *aPaM.GetPoint()))
{
MarkBase::SetOtherMarkPos(*(aPaM.GetMark()));
lcl_FixPosition(*m_oPos2);
}
}
void MarkBase::SetXBookmark(rtl::Reference<SwXBookmark> const& xBkmk)
{ m_wXBookmark = xBkmk.get(); }
// For fieldmarks, the CH_TXT_ATR_FIELDSTART and CH_TXT_ATR_FIELDEND
// themselves are part of the covered range. This is guaranteed by
// TextFieldmark::InitDoc/lcl_AssureFieldMarksSet.
bool MarkBase::IsCoveringPosition(const SwPosition& rPos) const
{
auto [/*const SwPosition&*/ rStartPos, rEndPos] = GetMarkStartEnd();
return rStartPos <= rPos && rPos < rEndPos;
}
void MarkBase::SetMarkPos(const SwPosition& rNewPos)
{
m_oPos1.emplace(rNewPos);
m_oPos1->SetOwner(this);
}
void MarkBase::SetOtherMarkPos(const SwPosition& rNewPos)
{
m_oPos2.emplace(rNewPos);
m_oPos2->SetOwner(this);
}
OUString MarkBase::ToString( ) const
{
return "Mark: ( Name, [ Node1, Index1 ] ): ( " + m_aName + ", [ "
+ OUString::number( sal_Int32(GetMarkPos().GetNodeIndex()) ) + ", "
+ OUString::number( GetMarkPos().GetContentIndex( ) ) + " ] )";
}
void MarkBase::dumpAsXml(xmlTextWriterPtr pWriter) const
{
(void)xmlTextWriterStartElement(pWriter, BAD_CAST("MarkBase"));
(void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(m_aName.toUtf8().getStr()));
(void)xmlTextWriterStartElement(pWriter, BAD_CAST("markPos"));
GetMarkPos().dumpAsXml(pWriter);
(void)xmlTextWriterEndElement(pWriter);
if (IsExpanded())
{
(void)xmlTextWriterStartElement(pWriter, BAD_CAST("otherMarkPos"));
GetOtherMarkPos().dumpAsXml(pWriter);
(void)xmlTextWriterEndElement(pWriter);
}
(void)xmlTextWriterEndElement(pWriter);
}
MarkBase::~MarkBase()
{ }
OUString MarkBase::GenerateNewName(std::u16string_view rPrefix)
{
static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr);
if (bHack)
{
static sal_Int64 nIdCounter = SAL_CONST_INT64(6000000000);
return rPrefix + OUString::number(nIdCounter++);
}
else
{
static OUString sUniquePostfix;
static sal_Int32 nCount = SAL_MAX_INT32;
if(nCount == SAL_MAX_INT32)
{
unsigned int const n(comphelper::rng::uniform_uint_distribution(0,
std::numeric_limits<unsigned int>::max()));
sUniquePostfix = "_" + OUString::number(n);
nCount = 0;
}
// putting the counter in front of the random parts will speed up string comparisons
return rPrefix + OUString::number(nCount++) + sUniquePostfix;
}
}
void MarkBase::SwClientNotify(const SwModify&, const SfxHint& rHint)
{
CallSwClientNotify(rHint);
if (rHint.GetId() != SfxHintId::SwLegacyModify)
return;
auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
if(RES_REMOVE_UNO_OBJECT == pLegacy->GetWhich())
{ // invalidate cached uno object
SetXBookmark(nullptr);
}
}
auto MarkBase::InvalidateFrames() -> void
{
}
NavigatorReminder::NavigatorReminder(const SwPaM& rPaM)
: MarkBase(rPaM, MarkBase::GenerateNewName(u"__NavigatorReminder__"))
{ }
UnoMark::UnoMark(const SwPaM& aPaM)
: MarkBase(aPaM, MarkBase::GenerateNewName(u"__UnoMark__"))
{ }
DdeBookmark::DdeBookmark(const SwPaM& aPaM)
: MarkBase(aPaM, MarkBase::GenerateNewName(u"__DdeLink__"))
{ }
void DdeBookmark::SetRefObject(SwServerObject* pObj)
{
m_aRefObj = pObj;
}
void DdeBookmark::DeregisterFromDoc(SwDoc& rDoc)
{
if(m_aRefObj.is())
rDoc.getIDocumentLinksAdministration().GetLinkManager().RemoveServer(m_aRefObj.get());
}
DdeBookmark::~DdeBookmark()
{
if( m_aRefObj.is() )
{
if(m_aRefObj->HasDataLinks())
{
::sfx2::SvLinkSource* p = m_aRefObj.get();
p->SendDataChanged();
}
m_aRefObj->SetNoServer();
}
}
Bookmark::Bookmark(const SwPaM& aPaM,
const vcl::KeyCode& rCode,
const OUString& rName)
: DdeBookmark(aPaM)
, m_aCode(rCode)
, m_bHidden(false)
{
m_aName = rName;
}
void Bookmark::sendLOKDeleteCallback()
{
if (!comphelper::LibreOfficeKit::isActive() || GetMarkPos().GetDoc().IsClipBoard())
return;
SfxViewShell* pViewShell = SfxViewShell::Current();
if (!pViewShell)
return;
OUString fieldCommand = GetName();
tools::JsonWriter aJson;
aJson.put("commandName", ".uno:DeleteBookmark");
aJson.put("success", true);
{
auto result = aJson.startNode("result");
aJson.put("DeleteBookmark", fieldCommand);
}
pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString());
}
void Bookmark::InitDoc(SwDoc& io_rDoc,
sw::mark::InsertMode const, SwPosition const*const)
{
if (io_rDoc.GetIDocumentUndoRedo().DoesUndo())
{
io_rDoc.GetIDocumentUndoRedo().AppendUndo(
std::make_unique<SwUndoInsBookmark>(*this));
}
io_rDoc.getIDocumentState().SetModified();
InvalidateFrames();
}
void Bookmark::DeregisterFromDoc(SwDoc& io_rDoc)
{
DdeBookmark::DeregisterFromDoc(io_rDoc);
if (io_rDoc.GetIDocumentUndoRedo().DoesUndo())
{
io_rDoc.GetIDocumentUndoRedo().AppendUndo(
std::make_unique<SwUndoDeleteBookmark>(*this));
}
io_rDoc.getIDocumentState().SetModified();
InvalidateFrames();
}
// invalidate text frames in case it's hidden or Formatting Marks enabled
auto Bookmark::InvalidateFrames() -> void
{
InvalidatePosition(GetMarkPos());
if (IsExpanded())
{
InvalidatePosition(GetOtherMarkPos());
}
}
void Bookmark::Hide(bool const isHide)
{
if (isHide != m_bHidden)
{
m_bHidden = isHide;
InvalidateFrames();
}
}
void Bookmark::SetHideCondition(OUString const& rHideCondition)
{
if (m_sHideCondition != rHideCondition)
{
m_sHideCondition = rHideCondition;
// don't eval condition here yet - probably only needed for
// UI editing condition and that doesn't exist yet
}
}
::sfx2::IXmlIdRegistry& Bookmark::GetRegistry()
{
SwDoc& rDoc( GetMarkPos().GetDoc() );
return rDoc.GetXmlIdRegistry();
}
bool Bookmark::IsInClipboard() const
{
SwDoc& rDoc( GetMarkPos().GetDoc() );
return rDoc.IsClipBoard();
}
bool Bookmark::IsInUndo() const
{
return false;
}
bool Bookmark::IsInContent() const
{
SwDoc& rDoc( GetMarkPos().GetDoc() );
return !rDoc.IsInHeaderFooter( GetMarkPos().GetNode() );
}
uno::Reference< rdf::XMetadatable > Bookmark::MakeUnoObject()
{
SwDoc& rDoc( GetMarkPos().GetDoc() );
const uno::Reference< rdf::XMetadatable> xMeta(
SwXBookmark::CreateXBookmark(rDoc, this) );
return xMeta;
}
Fieldmark::Fieldmark(const SwPaM& rPaM)
: MarkBase(rPaM, MarkBase::GenerateNewName(u"__Fieldmark__"))
{
if(!IsExpanded())
SetOtherMarkPos(GetMarkPos());
}
void Fieldmark::SetMarkStartPos( const SwPosition& rNewStartPos )
{
if ( GetMarkPos( ) <= GetOtherMarkPos( ) )
return SetMarkPos( rNewStartPos );
else
return SetOtherMarkPos( rNewStartPos );
}
OUString Fieldmark::ToString( ) const
{
return "Fieldmark: ( Name, Type, [ Nd1, Id1 ], [ Nd2, Id2 ] ): ( " + m_aName + ", "
+ m_aFieldname + ", [ " + OUString::number( sal_Int32(GetMarkPos().GetNodeIndex( )) )
+ ", " + OUString::number( GetMarkPos( ).GetContentIndex( ) ) + " ], ["
+ OUString::number( sal_Int32(GetOtherMarkPos().GetNodeIndex( )) ) + ", "
+ OUString::number( GetOtherMarkPos( ).GetContentIndex( ) ) + " ] ) ";
}
void Fieldmark::Invalidate( )
{
// TODO: Does exist a better solution to trigger a format of the
// fieldmark portion? If yes, please use it.
SwPaM aPaM( GetMarkPos(), GetOtherMarkPos() );
aPaM.InvalidatePaM();
}
void Fieldmark::dumpAsXml(xmlTextWriterPtr pWriter) const
{
(void)xmlTextWriterStartElement(pWriter, BAD_CAST("Fieldmark"));
(void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fieldname"), BAD_CAST(m_aFieldname.toUtf8().getStr()));
(void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fieldHelptext"), BAD_CAST(m_aFieldHelptext.toUtf8().getStr()));
MarkBase::dumpAsXml(pWriter);
(void)xmlTextWriterStartElement(pWriter, BAD_CAST("parameters"));
for (auto& rParam : m_vParams)
{
(void)xmlTextWriterStartElement(pWriter, BAD_CAST("parameter"));
(void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(rParam.first.toUtf8().getStr()));
(void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(comphelper::anyToString(rParam.second).toUtf8().getStr()));
(void)xmlTextWriterEndElement(pWriter);
}
(void)xmlTextWriterEndElement(pWriter);
(void)xmlTextWriterEndElement(pWriter);
}
TextFieldmark::TextFieldmark(const SwPaM& rPaM, const OUString& rName)
: Fieldmark(rPaM)
, m_pDocumentContentOperationsManager(nullptr)
{
if ( !rName.isEmpty() )
m_aName = rName;
}
TextFieldmark::~TextFieldmark()
{
if (!comphelper::LibreOfficeKit::isActive() || GetMarkPos().GetDoc().IsClipBoard())
return;
SfxViewShell* pViewShell = SfxViewShell::Current();
if (!pViewShell)
return;
OUString fieldCommand;
(*GetParameters())[ODF_CODE_PARAM] >>= fieldCommand;
tools::JsonWriter aJson;
aJson.put("commandName", ".uno:DeleteTextFormField");
aJson.put("success", true);
{
auto result = aJson.startNode("result");
aJson.put("DeleteTextFormField", fieldCommand);
}
pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString());
}
void TextFieldmark::InitDoc(SwDoc& io_rDoc,
sw::mark::InsertMode const eMode, SwPosition const*const pSepPos)
{
m_pDocumentContentOperationsManager = &io_rDoc.GetDocumentContentOperationsManager();
if (eMode == sw::mark::InsertMode::New)
{
lcl_SetFieldMarks(*this, io_rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND, pSepPos);
}
else
{
lcl_AssertFieldMarksSet(*this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND);
}
}
void TextFieldmark::ReleaseDoc(SwDoc& rDoc)
{
IDocumentUndoRedo & rIDUR(rDoc.GetIDocumentUndoRedo());
if (rIDUR.DoesUndo())
{
rIDUR.AppendUndo(std::make_unique<SwUndoDelTextFieldmark>(*this));
}
::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes
lcl_RemoveFieldMarks(*this, rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND);
// notify layouts to unhide - for the entire fieldmark, as in InitDoc()
SwPaM const tmp(GetMarkPos(), GetOtherMarkPos());
sw::UpdateFramesForRemoveDeleteRedline(rDoc, tmp);
}
OUString TextFieldmark::GetContent() const
{
const SwTextNode& rTextNode = *GetMarkEnd().GetNode().GetTextNode();
SwPosition const sepPos(sw::mark::FindFieldSep(*this));
const sal_Int32 nStart(sepPos.GetContentIndex());
const sal_Int32 nEnd(GetMarkEnd().GetContentIndex());
OUString sContent;
const sal_Int32 nLen = rTextNode.GetText().getLength();
if (nStart + 1 < nLen && nEnd <= nLen && nEnd > nStart + 2)
sContent = rTextNode.GetText().copy(nStart + 1, nEnd - nStart - 2);
return sContent;
}
void TextFieldmark::ReplaceContent(const OUString& sNewContent)
{
if (!m_pDocumentContentOperationsManager)
return;
SwPosition const sepPos(sw::mark::FindFieldSep(*this));
const sal_Int32 nStart(sepPos.GetContentIndex());
const sal_Int32 nEnd(GetMarkEnd().GetContentIndex());
const sal_Int32 nLen = GetMarkEnd().GetNode().GetTextNode()->GetText().getLength();
if (nStart + 1 < nLen && nEnd <= nLen && nEnd > nStart + 2)
{
SwPaM aFieldPam(GetMarkStart().GetNode(), nStart + 1,
GetMarkStart().GetNode(), nEnd - 1);
m_pDocumentContentOperationsManager->ReplaceRange(aFieldPam, sNewContent, false);
}
else
{
SwPaM aFieldStartPam(GetMarkStart().GetNode(), nStart + 1);
m_pDocumentContentOperationsManager->InsertString(aFieldStartPam, sNewContent);
}
Invalidate();
}
bool TextFieldmark::HasDefaultContent() const
{
return GetContent() == vEnSpaces;
}
NonTextFieldmark::NonTextFieldmark(const SwPaM& rPaM)
: Fieldmark(rPaM)
{ }
void NonTextFieldmark::InitDoc(SwDoc& io_rDoc,
sw::mark::InsertMode const eMode, SwPosition const*const pSepPos)
{
assert(pSepPos == nullptr);
if (eMode == sw::mark::InsertMode::New)
{
lcl_SetFieldMarks(*this, io_rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT, pSepPos);
}
else
{
lcl_AssertFieldMarksSet(*this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT);
}
}
void NonTextFieldmark::ReleaseDoc(SwDoc& rDoc)
{
IDocumentUndoRedo & rIDUR(rDoc.GetIDocumentUndoRedo());
if (rIDUR.DoesUndo())
{
rIDUR.AppendUndo(std::make_unique<SwUndoDelNoTextFieldmark>(*this));
}
::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes
lcl_RemoveFieldMarks(*this, rDoc,
CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT);
}
CheckboxFieldmark::CheckboxFieldmark(const SwPaM& rPaM, const OUString& rName)
: NonTextFieldmark(rPaM)
{
if (!rName.isEmpty())
m_aName = rName;
}
void CheckboxFieldmark::SetChecked(bool checked)
{
if ( IsChecked() != checked )
{
(*GetParameters())[ODF_FORMCHECKBOX_RESULT] <<= checked;
// mark document as modified
SwDoc& rDoc( GetMarkPos().GetDoc() );
rDoc.getIDocumentState().SetModified();
}
}
bool CheckboxFieldmark::IsChecked() const
{
bool bResult = false;
parameter_map_t::const_iterator pResult = GetParameters()->find(ODF_FORMCHECKBOX_RESULT);
if(pResult != GetParameters()->end())
pResult->second >>= bResult;
return bResult;
}
OUString CheckboxFieldmark::GetContent() const
{
return IsChecked() ? "1" : "0";
}
void CheckboxFieldmark::ReplaceContent(const OUString& sNewContent)
{
SetChecked(sNewContent.toBoolean());
Invalidate();
}
FieldmarkWithDropDownButton::FieldmarkWithDropDownButton(const SwPaM& rPaM)
: NonTextFieldmark(rPaM)
, m_pButton(nullptr)
{
}
FieldmarkWithDropDownButton::~FieldmarkWithDropDownButton()
{
m_pButton.disposeAndClear();
}
void FieldmarkWithDropDownButton::RemoveButton()
{
if(m_pButton)
m_pButton.disposeAndClear();
}
void FieldmarkWithDropDownButton::LaunchPopup()
{
if (!m_pButton)
return;
m_pButton->Invalidate();
m_pButton->LaunchPopup();
}
DropDownFieldmark::DropDownFieldmark(const SwPaM& rPaM, const OUString& rName)
: FieldmarkWithDropDownButton(rPaM)
{
if (!rName.isEmpty())
m_aName = rName;
}
DropDownFieldmark::~DropDownFieldmark()
{
}
void DropDownFieldmark::ShowButton(SwEditWin* pEditWin)
{
if(pEditWin)
{
if(!m_pButton)
m_pButton = VclPtr<DropDownFormFieldButton>::Create(pEditWin, *this);
m_pButton->CalcPosAndSize(m_aPortionPaintArea);
m_pButton->Show();
}
}
void DropDownFieldmark::RemoveButton()
{
FieldmarkWithDropDownButton::RemoveButton();
}
/** GetContent
* @param pIndex The zero-based index to retrieve
* [in] if pIndex is null or negative, return the listbox's chosen result,
* else return the indicated entry (or last entry for invalid choice).
* [out] the index of the returned result or -1 if error
*/
OUString DropDownFieldmark::GetContent(sal_Int32* pIndex) const
{
sal_Int32 nIndex = pIndex ? *pIndex : -1;
auto rParameters = *GetParameters();
if (nIndex < 0)
rParameters[ODF_FORMDROPDOWN_RESULT] >>= nIndex;
uno::Sequence<OUString> aSeq;
rParameters[ODF_FORMDROPDOWN_LISTENTRY] >>= aSeq;
nIndex = std::min(nIndex, aSeq.getLength() - 1);
if (nIndex < 0)
{
if (pIndex)
*pIndex = -1;
return OUString();
}
if (pIndex)
*pIndex = nIndex;
return aSeq[nIndex];
}
OUString DropDownFieldmark::GetContent() const
{
return GetContent(nullptr);
}
/** AddContent : INSERTS a new choice
* @param rText: The choice to add to the list choices.
*
* @param pIndex [optional]
* [in] If pIndex is null or invalid, append to the end of the list.
* [out] Modified to point to the position of the choice if it already exists.
*/
void DropDownFieldmark::AddContent(const OUString& rText, sal_Int32* pIndex)
{
uno::Sequence<OUString> aSeq;
sw::mark::Fieldmark::parameter_map_t* pParameters = GetParameters();
(*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] >>= aSeq;
// no duplicates: if it already exists, modify the given index to point to it
const sal_Int32 nCurrentTextPos = comphelper::findValue(aSeq, rText);
if (nCurrentTextPos != -1)
{
if (pIndex)
*pIndex = nCurrentTextPos;
return;
}
const sal_Int32 nLen = aSeq.getLength();
const sal_Int32 nNewPos = pIndex && *pIndex > -1 ? std::min(*pIndex, nLen) : nLen;
// need to shift list result index up if adding new entry before it
sal_Int32 nResultIndex = -1;
(*pParameters)[ODF_FORMDROPDOWN_RESULT] >>= nResultIndex;
if (nNewPos <= nResultIndex)
(*pParameters)[ODF_FORMDROPDOWN_RESULT] <<= nResultIndex + 1;
auto aList = comphelper::sequenceToContainer<std::vector<OUString>>(aSeq);
if (nNewPos < nLen)
aList.insert(aList.begin() + nNewPos, rText);
else
{
if (pIndex)
*pIndex = nLen;
aList.push_back(rText);
}
(*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] <<= comphelper::containerToSequence(aList);
Invalidate();
}
/**
* ReplaceContent : changes the list result index or renames the existing choices
* @param pText
* [in] If pIndex is null, change the list result index to this provided choice
* (but do nothing if pText is an invalid choice)
* else rename that entry.
*
* @param pIndex
* [in] If pText is null, change the list result index to this provided Index
* (or the last position if it is an invalid choice)
* else rename this entry (doing nothing for invalid indexes).
* [out] If pIndex is invalid, it is modified to use the last position.
*
* This function allows duplicate entries - which is also allowed in MS Word.
*/
void DropDownFieldmark::ReplaceContent(const OUString* pText, sal_Int32* pIndex)
{
if (!pIndex && !pText)
return;
uno::Sequence<OUString> aSeq;
sw::mark::Fieldmark::parameter_map_t* pParameters = GetParameters();
(*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] >>= aSeq;
const sal_Int32 nLen = aSeq.getLength();
if (!pText)
{
if (*pIndex < 0 || *pIndex >= nLen)
*pIndex = nLen - 1;
// select pIndex as the new value for the list box
(*pParameters)[ODF_FORMDROPDOWN_RESULT] <<= *pIndex;
Invalidate();
return;
}
if (!pIndex)
{
const sal_Int32 nNewPos = comphelper::findValue(aSeq, *pText);
if (nNewPos != -1)
{
(*pParameters)[ODF_FORMDROPDOWN_RESULT] <<= nNewPos;
Invalidate();
}
return;
}
if (*pIndex > -1 && *pIndex < nLen)
{
auto aList = comphelper::sequenceToContainer<std::vector<OUString>>(aSeq);
aList[*pIndex] = *pText;
(*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] <<= comphelper::containerToSequence(aList);
Invalidate();
}
}
void DropDownFieldmark::ReplaceContent(const OUString& rNewContent)
{
ReplaceContent(&rNewContent, nullptr);
}
/**
* Remove everything if the given index is negative, else remove the given index (if valid).
* If deleting the currently selected choice, reset the selection to the first choice.
*/
void DropDownFieldmark::DelContent(sal_Int32 nDelIndex)
{
sw::mark::Fieldmark::parameter_map_t* pParameters = GetParameters();
uno::Sequence<OUString> aSeq;
if (nDelIndex < 0)
{
pParameters->erase(ODF_FORMDROPDOWN_RESULT);
(*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] <<= aSeq;
Invalidate();
return;
}
(*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] >>= aSeq;
if (nDelIndex >= aSeq.getLength())
return;
// If deleting the current choice, select the first entry instead
// else need to shift list result index down if deleting an entry before it
sal_Int32 nResultIndex = -1;
(*pParameters)[ODF_FORMDROPDOWN_RESULT] >>= nResultIndex;
if (nDelIndex == nResultIndex)
nResultIndex = 0;
else if (nDelIndex < nResultIndex)
--nResultIndex;
comphelper::removeElementAt(aSeq, nDelIndex);
if (nResultIndex != -1)
(*pParameters)[ODF_FORMDROPDOWN_RESULT] <<= nResultIndex;
(*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] <<= aSeq;
Invalidate();
}
void DropDownFieldmark::SetPortionPaintArea(const SwRect& rPortionPaintArea)
{
m_aPortionPaintArea = rPortionPaintArea;
if(m_pButton)
{
m_pButton->Show();
m_pButton->CalcPosAndSize(m_aPortionPaintArea);
}
}
void DropDownFieldmark::SendLOKShowMessage(const SfxViewShell* pViewShell)
{
if (!comphelper::LibreOfficeKit::isActive())
return;
if (!pViewShell || pViewShell->isLOKMobilePhone())
return;
if (m_aPortionPaintArea.IsEmpty())
return;
OStringBuffer sPayload;
sPayload = OString::Concat("{\"action\": \"show\","
" \"type\": \"drop-down\", \"textArea\": \"") +
m_aPortionPaintArea.SVRect().toString() + "\",";
// Add field params to the message
sPayload.append(" \"params\": { \"items\": [");
// List items
auto pParameters = this->GetParameters();
auto pListEntriesIter = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY);
css::uno::Sequence<OUString> vListEntries;
if (pListEntriesIter != pParameters->end())
{
pListEntriesIter->second >>= vListEntries;
for (const OUString& sItem : vListEntries)
sPayload.append("\"" + OUStringToOString(sItem, RTL_TEXTENCODING_UTF8) + "\", ");
sPayload.setLength(sPayload.getLength() - 2);
}
sPayload.append("], ");
// Selected item
auto pSelectedItemIter = pParameters->find(ODF_FORMDROPDOWN_RESULT);
sal_Int32 nSelection = -1;
if (pSelectedItemIter != pParameters->end())
{
pSelectedItemIter->second >>= nSelection;
}
sPayload.append("\"selected\": \"" + OString::number(nSelection) + "\", ");
// Placeholder text
sPayload.append("\"placeholderText\": \"" + OUStringToOString(SwResId(STR_DROP_DOWN_EMPTY_LIST), RTL_TEXTENCODING_UTF8) + "\"}}");
pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_FORM_FIELD_BUTTON, sPayload.toString());
}
void DropDownFieldmark::SendLOKHideMessage(const SfxViewShell* pViewShell)
{
pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_FORM_FIELD_BUTTON,
"{\"action\": \"hide\", \"type\": \"drop-down\"}"_ostr);
}
DateFieldmark::DateFieldmark(const SwPaM& rPaM)
: FieldmarkWithDropDownButton(rPaM)
, m_pNumberFormatter(nullptr)
, m_pDocumentContentOperationsManager(nullptr)
{
}
DateFieldmark::~DateFieldmark()
{
}
void DateFieldmark::InitDoc(SwDoc& io_rDoc,
sw::mark::InsertMode eMode, SwPosition const*const pSepPos)
{
m_pNumberFormatter = io_rDoc.GetNumberFormatter();
m_pDocumentContentOperationsManager = &io_rDoc.GetDocumentContentOperationsManager();
if (eMode == sw::mark::InsertMode::New)
{
lcl_SetFieldMarks(*this, io_rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND, pSepPos);
}
else
{
lcl_AssertFieldMarksSet(*this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND);
}
}
void DateFieldmark::ReleaseDoc(SwDoc& rDoc)
{
IDocumentUndoRedo & rIDUR(rDoc.GetIDocumentUndoRedo());
if (rIDUR.DoesUndo())
{
// TODO does this need a 3rd Undo class?
rIDUR.AppendUndo(std::make_unique<SwUndoDelTextFieldmark>(*this));
}
::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes
lcl_RemoveFieldMarks(*this, rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND);
// notify layouts to unhide - for the entire fieldmark, as in InitDoc()
SwPaM const tmp(GetMarkPos(), GetOtherMarkPos());
sw::UpdateFramesForRemoveDeleteRedline(rDoc, tmp);
}
void DateFieldmark::ShowButton(SwEditWin* pEditWin)
{
if(pEditWin)
{
if(!m_pButton)
m_pButton = VclPtr<DateFormFieldButton>::Create(pEditWin, *this, m_pNumberFormatter);
SwRect aPaintArea(m_aPaintAreaStart.TopLeft(), m_aPaintAreaEnd.BottomRight());
m_pButton->CalcPosAndSize(aPaintArea);
m_pButton->Show();
}
}
void DateFieldmark::SetPortionPaintAreaStart(const SwRect& rPortionPaintArea)
{
if (rPortionPaintArea.IsEmpty())
return;
m_aPaintAreaStart = rPortionPaintArea;
InvalidateCurrentDateParam();
}
void DateFieldmark::SetPortionPaintAreaEnd(const SwRect& rPortionPaintArea)
{
if (rPortionPaintArea.IsEmpty())
return;
if(m_aPaintAreaEnd == rPortionPaintArea &&
m_pButton && m_pButton->IsVisible())
return;
m_aPaintAreaEnd = rPortionPaintArea;
if(m_pButton)
{
m_pButton->Show();
SwRect aPaintArea(m_aPaintAreaStart.TopLeft(), m_aPaintAreaEnd.BottomRight());
m_pButton->CalcPosAndSize(aPaintArea);
m_pButton->Invalidate();
}
InvalidateCurrentDateParam();
}
OUString DateFieldmark::GetContent() const
{
const SwTextNode* const pTextNode = GetMarkEnd().GetNode().GetTextNode();
SwPosition const sepPos(sw::mark::FindFieldSep(*this));
const sal_Int32 nStart(sepPos.GetContentIndex());
const sal_Int32 nEnd (GetMarkEnd().GetContentIndex());
OUString sContent;
if(nStart + 1 < pTextNode->GetText().getLength() && nEnd <= pTextNode->GetText().getLength() &&
nEnd > nStart + 2)
sContent = pTextNode->GetText().copy(nStart + 1, nEnd - nStart - 2);
return sContent;
}
void DateFieldmark::ReplaceContent(const OUString& sNewContent)
{
if(!m_pDocumentContentOperationsManager)
return;
const SwTextNode* const pTextNode = GetMarkEnd().GetNode().GetTextNode();
SwPosition const sepPos(sw::mark::FindFieldSep(*this));
const sal_Int32 nStart(sepPos.GetContentIndex());
const sal_Int32 nEnd (GetMarkEnd().GetContentIndex());
if(nStart + 1 < pTextNode->GetText().getLength() && nEnd <= pTextNode->GetText().getLength() &&
nEnd > nStart + 2)
{
SwPaM aFieldPam(GetMarkStart().GetNode(), nStart + 1,
GetMarkStart().GetNode(), nEnd - 1);
m_pDocumentContentOperationsManager->ReplaceRange(aFieldPam, sNewContent, false);
}
else
{
SwPaM aFieldStartPam(GetMarkStart().GetNode(), nStart + 1);
m_pDocumentContentOperationsManager->InsertString(aFieldStartPam, sNewContent);
}
}
std::pair<bool, double> DateFieldmark::GetCurrentDate() const
{
// Check current date param first
std::pair<bool, double> aResult = ParseCurrentDateParam();
if(aResult.first)
return aResult;
const sw::mark::Fieldmark::parameter_map_t* pParameters = GetParameters();
bool bFoundValidDate = false;
double dCurrentDate = 0;
OUString sDateFormat;
auto pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT);
if (pResult != pParameters->end())
{
pResult->second >>= sDateFormat;
}
OUString sLang;
pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT_LANGUAGE);
if (pResult != pParameters->end())
{
pResult->second >>= sLang;
}
// Get current content of the field
OUString sContent = GetContent();
sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(sDateFormat, LanguageTag(sLang).getLanguageType());
if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
{
sal_Int32 nCheckPos = 0;
SvNumFormatType nType;
m_pNumberFormatter->PutEntry(sDateFormat,
nCheckPos,
nType,
nFormat,
LanguageTag(sLang).getLanguageType());
}
if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
{
bFoundValidDate = m_pNumberFormatter->IsNumberFormat(sContent, nFormat, dCurrentDate);
}
return std::pair<bool, double>(bFoundValidDate, dCurrentDate);
}
void DateFieldmark::SetCurrentDate(double fDate)
{
// Replace current content with the selected date
ReplaceContent(GetDateInCurrentDateFormat(fDate));
// Also save the current date in a standard format
sw::mark::Fieldmark::parameter_map_t* pParameters = GetParameters();
(*pParameters)[ODF_FORMDATE_CURRENTDATE] <<= GetDateInStandardDateFormat(fDate);
}
OUString DateFieldmark::GetDateInStandardDateFormat(double fDate) const
{
OUString sCurrentDate;
sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(ODF_FORMDATE_CURRENTDATE_FORMAT, ODF_FORMDATE_CURRENTDATE_LANGUAGE);
if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
{
sal_Int32 nCheckPos = 0;
SvNumFormatType nType;
OUString sFormat = ODF_FORMDATE_CURRENTDATE_FORMAT;
m_pNumberFormatter->PutEntry(sFormat,
nCheckPos,
nType,
nFormat,
ODF_FORMDATE_CURRENTDATE_LANGUAGE);
}
if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
{
const Color* pCol = nullptr;
m_pNumberFormatter->GetOutputString(fDate, nFormat, sCurrentDate, &pCol, false);
}
return sCurrentDate;
}
std::pair<bool, double> DateFieldmark::ParseCurrentDateParam() const
{
bool bFoundValidDate = false;
double dCurrentDate = 0;
const sw::mark::Fieldmark::parameter_map_t* pParameters = GetParameters();
auto pResult = pParameters->find(ODF_FORMDATE_CURRENTDATE);
OUString sCurrentDate;
if (pResult != pParameters->end())
{
pResult->second >>= sCurrentDate;
}
if(!sCurrentDate.isEmpty())
{
sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(ODF_FORMDATE_CURRENTDATE_FORMAT, ODF_FORMDATE_CURRENTDATE_LANGUAGE);
if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
{
sal_Int32 nCheckPos = 0;
SvNumFormatType nType;
OUString sFormat = ODF_FORMDATE_CURRENTDATE_FORMAT;
m_pNumberFormatter->PutEntry(sFormat,
nCheckPos,
nType,
nFormat,
ODF_FORMDATE_CURRENTDATE_LANGUAGE);
}
if(nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
{
bFoundValidDate = m_pNumberFormatter->IsNumberFormat(sCurrentDate, nFormat, dCurrentDate);
}
}
return std::pair<bool, double>(bFoundValidDate, dCurrentDate);
}
OUString DateFieldmark::GetDateInCurrentDateFormat(double fDate) const
{
// Get current date format and language
OUString sDateFormat;
const sw::mark::Fieldmark::parameter_map_t* pParameters = GetParameters();
auto pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT);
if (pResult != pParameters->end())
{
pResult->second >>= sDateFormat;
}
OUString sLang;
pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT_LANGUAGE);
if (pResult != pParameters->end())
{
pResult->second >>= sLang;
}
// Fill the content with the specified format
OUString sCurrentContent;
sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(sDateFormat, LanguageTag(sLang).getLanguageType());
if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
{
sal_Int32 nCheckPos = 0;
SvNumFormatType nType;
OUString sFormat = sDateFormat;
m_pNumberFormatter->PutEntry(sFormat,
nCheckPos,
nType,
nFormat,
LanguageTag(sLang).getLanguageType());
}
if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
{
const Color* pCol = nullptr;
m_pNumberFormatter->GetOutputString(fDate, nFormat, sCurrentContent, &pCol, false);
}
return sCurrentContent;
}
void DateFieldmark::InvalidateCurrentDateParam()
{
std::pair<bool, double> aResult = ParseCurrentDateParam();
if(!aResult.first)
return;
// Current date became invalid
if(GetDateInCurrentDateFormat(aResult.second) != GetContent())
{
sw::mark::Fieldmark::parameter_map_t* pParameters = GetParameters();
(*pParameters)[ODF_FORMDATE_CURRENTDATE] <<= OUString();
}
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'Assign' is required to be utilized.
↑ V530 The return value of function 'Assign' is required to be utilized.
↑ V547 Expression 'nResultIndex != - 1' is always false.
↑ V595 The 'pIndex' pointer was utilized before it was verified against nullptr. Check lines: 914, 919.
↑ V547 Expression 'nDelIndex == nResultIndex' is always false.
↑ V547 Expression 'nDelIndex < nResultIndex' is always false.
↑ V1007 The value from the potentially uninitialized optional 'ret' is used. Probably it is a mistake.