office-gobmx/sw/source/core/crsr/bookmark.cxx
Noel Grandin efe65eaaac GetMarkStart/GetMarkEnd is hot
when importing docs with lots of fieldmarks.
But once we have computed the one, computing the other is essentially
free, so instead of doing the computation twice, return both values when
we need both

Change-Id: Icdd9d4ed4cf1e6c9b26fdb20507c4f964f9e9f90
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/171853
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
Tested-by: Jenkins
2024-08-14 12:56:30 +02:00

1329 lines
47 KiB
C++

/* -*- 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();
}
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: */