1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2 /* 3 * This file is part of the LibreOffice project. 4 * 5 * This Source Code Form is subject to the terms of the Mozilla Public 6 * License, v. 2.0. If a copy of the MPL was not distributed with this 7 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 * 9 * This file incorporates work covered by the following license notice: 10 * 11 * Licensed to the Apache Software Foundation (ASF) under one or more 12 * contributor license agreements. See the NOTICE file distributed 13 * with this work for additional information regarding copyright 14 * ownership. The ASF licenses this file to you under the Apache 15 * License, Version 2.0 (the "License"); you may not use this file 16 * except in compliance with the License. You may obtain a copy of 17 * the License at http://www.apache.org/licenses/LICENSE-2.0 . 18 */ 19 20 #include <memory> 21 #include <bookmrk.hxx> 22 #include <IDocumentUndoRedo.hxx> 23 #include <IDocumentLinksAdministration.hxx> 24 #include <IDocumentState.hxx> 25 #include <doc.hxx> 26 #include <ndtxt.hxx> 27 #include <pam.hxx> 28 #include <swserv.hxx> 29 #include <sfx2/linkmgr.hxx> 30 #include <sfx2/viewsh.hxx> 31 #include <UndoBookmark.hxx> 32 #include <unobookmark.hxx> 33 #include <xmloff/odffields.hxx> 34 #include <libxml/xmlwriter.h> 35 #include <comphelper/random.hxx> 36 #include <comphelper/anytostring.hxx> 37 #include <sal/log.hxx> 38 #include <svl/zforlist.hxx> 39 #include <edtwin.hxx> 40 #include <DateFormFieldButton.hxx> 41 #include <DropDownFormFieldButton.hxx> 42 #include <DocumentContentOperationsManager.hxx> 43 #include <comphelper/lok.hxx> 44 #include <txtfrm.hxx> 45 #include <LibreOfficeKit/LibreOfficeKitEnums.h> 46 #include <rtl/strbuf.hxx> 47 #include <strings.hrc> 48 49 using namespace ::sw::mark; 50 using namespace ::com::sun::star; 51 using namespace ::com::sun::star::uno; 52 53 namespace sw::mark 54 { 55 FindFieldSep(IFieldmark const & rMark)56 SwPosition FindFieldSep(IFieldmark const& rMark) 57 { 58 SwPosition const& rStartPos(rMark.GetMarkStart()); 59 SwPosition const& rEndPos(rMark.GetMarkEnd()); 60 SwNodes const& rNodes(rStartPos.nNode.GetNodes()); 61 sal_uLong const nStartNode(rStartPos.nNode.GetIndex()); 62 sal_uLong const nEndNode(rEndPos.nNode.GetIndex()); 63 int nFields(0); 64 std::optional<SwPosition> ret; 65 for (sal_uLong n = nEndNode; nStartNode <= n; --n) 66 { 67 SwNode *const pNode(rNodes[n]); 68 if (pNode->IsTextNode()) 69 { 70 SwTextNode & rTextNode(*pNode->GetTextNode()); 71 sal_Int32 const nStart(n == nStartNode 72 ? rStartPos.nContent.GetIndex() + 1 73 : 0); 74 sal_Int32 const nEnd(n == nEndNode 75 // subtract 1 to ignore the end char 76 ? rEndPos.nContent.GetIndex() - 1 77 : rTextNode.Len()); 78 for (sal_Int32 i = nEnd; nStart < i; --i) 79 { 80 const sal_Unicode c(rTextNode.GetText()[i - 1]); 81 switch (c) 82 { 83 case CH_TXT_ATR_FIELDSTART: 84 --nFields; 85 assert(0 <= nFields); 86 break; 87 case CH_TXT_ATR_FIELDEND: 88 ++nFields; 89 // fields in field result could happen by manual 90 // editing, although the field update deletes them 91 break; 92 case CH_TXT_ATR_FIELDSEP: 93 if (nFields == 0) 94 { 95 assert(!ret); // one per field 96 ret = SwPosition(rTextNode, i - 1); 97 #ifndef DBG_UTIL 98 return *ret; 99 #endif 100 } 101 break; 102 } 103 } 104 } 105 else if (pNode->IsEndNode() && !pNode->StartOfSectionNode()->IsSectionNode()) 106 { 107 assert(nStartNode <= pNode->StartOfSectionIndex()); 108 // fieldmark cannot overlap node section, unless it's a section 109 n = pNode->StartOfSectionIndex(); 110 } 111 else 112 { 113 assert(pNode->IsNoTextNode() || pNode->IsSectionNode()); 114 } 115 } 116 assert(ret); // must have found it 117 return *ret; 118 } 119 } // namespace sw::mark 120 121 namespace 122 { lcl_FixPosition(SwPosition & rPos)123 void lcl_FixPosition(SwPosition& rPos) 124 { 125 // make sure the position has 1) the proper node, and 2) a proper index 126 SwTextNode* pTextNode = rPos.nNode.GetNode().GetTextNode(); 127 if(pTextNode == nullptr && rPos.nContent.GetIndex() > 0) 128 { 129 SAL_INFO( 130 "sw.core", 131 "illegal position: " << rPos.nContent.GetIndex() 132 << " without proper TextNode"); 133 rPos.nContent.Assign(nullptr, 0); 134 } 135 else if(pTextNode != nullptr && rPos.nContent.GetIndex() > pTextNode->Len()) 136 { 137 SAL_INFO( 138 "sw.core", 139 "illegal position: " << rPos.nContent.GetIndex() 140 << " is beyond " << pTextNode->Len()); 141 rPos.nContent.Assign(pTextNode, pTextNode->Len()); 142 } 143 } 144 lcl_AssertFieldMarksSet(const Fieldmark & rField,const sal_Unicode aStartMark,const sal_Unicode aEndMark)145 void lcl_AssertFieldMarksSet(const Fieldmark& rField, 146 const sal_Unicode aStartMark, 147 const sal_Unicode aEndMark) 148 { 149 if (aEndMark != CH_TXT_ATR_FORMELEMENT) 150 { 151 SwPosition const& rStart(rField.GetMarkStart()); 152 assert(rStart.nNode.GetNode().GetTextNode()->GetText()[rStart.nContent.GetIndex()] == aStartMark); (void) rStart; (void) aStartMark; 153 SwPosition const sepPos(sw::mark::FindFieldSep(rField)); 154 assert(sepPos.nNode.GetNode().GetTextNode()->GetText()[sepPos.nContent.GetIndex()] == CH_TXT_ATR_FIELDSEP); (void) sepPos; 155 } 156 SwPosition const& rEnd(rField.GetMarkEnd()); 157 assert(rEnd.nNode.GetNode().GetTextNode()->GetText()[rEnd.nContent.GetIndex() - 1] == aEndMark); (void) rEnd; 158 } 159 lcl_SetFieldMarks(Fieldmark & rField,SwDoc & io_rDoc,const sal_Unicode aStartMark,const sal_Unicode aEndMark,SwPosition const * const pSepPos)160 void lcl_SetFieldMarks(Fieldmark& rField, 161 SwDoc& io_rDoc, 162 const sal_Unicode aStartMark, 163 const sal_Unicode aEndMark, 164 SwPosition const*const pSepPos) 165 { 166 io_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::UI_REPLACE, nullptr); 167 OUString startChar(aStartMark); 168 if (aEndMark != CH_TXT_ATR_FORMELEMENT 169 && rField.GetMarkStart() == rField.GetMarkEnd()) 170 { 171 // do only 1 InsertString call - to expand existing bookmarks at the 172 // position over the whole field instead of just aStartMark 173 startChar += OUStringChar(CH_TXT_ATR_FIELDSEP) + OUStringChar(aEndMark); 174 } 175 176 SwPosition start = rField.GetMarkStart(); 177 if (aEndMark != CH_TXT_ATR_FORMELEMENT) 178 { 179 SwPaM aStartPaM(start); 180 io_rDoc.getIDocumentContentOperations().InsertString(aStartPaM, startChar); 181 start.nContent -= startChar.getLength(); // restore, it was moved by InsertString 182 // do not manipulate via reference directly but call SetMarkStartPos 183 // which works even if start and end pos were the same 184 rField.SetMarkStartPos( start ); 185 SwPosition& rEnd = rField.GetMarkEnd(); // note: retrieve after 186 // setting start, because if start==end it can go stale, see SetMarkPos() 187 assert(pSepPos == nullptr || (start < *pSepPos && *pSepPos <= rEnd)); 188 if (startChar.getLength() == 1) 189 { 190 *aStartPaM.GetPoint() = pSepPos ? *pSepPos : rEnd; 191 io_rDoc.getIDocumentContentOperations().InsertString(aStartPaM, OUString(CH_TXT_ATR_FIELDSEP)); 192 if (!pSepPos || rEnd < *pSepPos) 193 { // rEnd is not moved automatically if it's same as insert pos 194 ++rEnd.nContent; 195 } 196 } 197 assert(pSepPos == nullptr || (start < *pSepPos && *pSepPos <= rEnd)); 198 } 199 else 200 { 201 assert(pSepPos == nullptr); 202 } 203 204 SwPosition& rEnd = rField.GetMarkEnd(); 205 if (aEndMark && startChar.getLength() == 1) 206 { 207 SwPaM aEndPaM(rEnd); 208 io_rDoc.getIDocumentContentOperations().InsertString(aEndPaM, OUString(aEndMark)); 209 ++rEnd.nContent; 210 } 211 lcl_AssertFieldMarksSet(rField, aStartMark, aEndMark); 212 213 io_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::UI_REPLACE, nullptr); 214 } 215 lcl_RemoveFieldMarks(const Fieldmark & rField,SwDoc & io_rDoc,const sal_Unicode aStartMark,const sal_Unicode aEndMark)216 void lcl_RemoveFieldMarks(const Fieldmark& rField, 217 SwDoc& io_rDoc, 218 const sal_Unicode aStartMark, 219 const sal_Unicode aEndMark) 220 { 221 io_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::UI_REPLACE, nullptr); 222 223 const SwPosition& rStart = rField.GetMarkStart(); 224 SwTextNode const*const pStartTextNode = rStart.nNode.GetNode().GetTextNode(); 225 assert(pStartTextNode); 226 if (aEndMark != CH_TXT_ATR_FORMELEMENT) 227 { 228 (void) pStartTextNode; 229 // check this before start / end because of the +1 / -1 ... 230 SwPosition const sepPos(sw::mark::FindFieldSep(rField)); 231 io_rDoc.GetDocumentContentOperationsManager().DeleteDummyChar(rStart, aStartMark); 232 io_rDoc.GetDocumentContentOperationsManager().DeleteDummyChar(sepPos, CH_TXT_ATR_FIELDSEP); 233 } 234 235 const SwPosition& rEnd = rField.GetMarkEnd(); 236 SwTextNode *const pEndTextNode = rEnd.nNode.GetNode().GetTextNode(); 237 assert(pEndTextNode); 238 const sal_Int32 nEndPos = (rEnd == rStart) 239 ? rEnd.nContent.GetIndex() 240 : rEnd.nContent.GetIndex() - 1; 241 assert(pEndTextNode->GetText()[nEndPos] == aEndMark); 242 SwPosition const aEnd(*pEndTextNode, nEndPos); 243 io_rDoc.GetDocumentContentOperationsManager().DeleteDummyChar(aEnd, aEndMark); 244 245 io_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::UI_REPLACE, nullptr); 246 } 247 InvalidatePosition(SwPosition const & rPos)248 auto InvalidatePosition(SwPosition const& rPos) -> void 249 { 250 SwUpdateAttr const aHint(rPos.nContent.GetIndex(), rPos.nContent.GetIndex(), 0); 251 rPos.nNode.GetNode().GetTextNode()->CallSwClientNotify(sw::LegacyModifyHint(&aHint, &aHint)); 252 } 253 } 254 255 namespace sw::mark 256 { MarkBase(const SwPaM & aPaM,const OUString & rName)257 MarkBase::MarkBase(const SwPaM& aPaM, 258 const OUString& rName) 259 : m_pPos1(new SwPosition(*(aPaM.GetPoint()))) 260 , m_aName(rName) 261 { 262 m_pPos1->nContent.SetMark(this); 263 lcl_FixPosition(*m_pPos1); 264 if (aPaM.HasMark() && (*aPaM.GetMark() != *aPaM.GetPoint())) 265 { 266 MarkBase::SetOtherMarkPos(*(aPaM.GetMark())); 267 lcl_FixPosition(*m_pPos2); 268 } 269 } 270 271 // For fieldmarks, the CH_TXT_ATR_FIELDSTART and CH_TXT_ATR_FIELDEND 272 // themselves are part of the covered range. This is guaranteed by 273 // TextFieldmark::InitDoc/lcl_AssureFieldMarksSet. IsCoveringPosition(const SwPosition & rPos) const274 bool MarkBase::IsCoveringPosition(const SwPosition& rPos) const 275 { 276 return GetMarkStart() <= rPos && rPos < GetMarkEnd(); 277 } 278 SetMarkPos(const SwPosition & rNewPos)279 void MarkBase::SetMarkPos(const SwPosition& rNewPos) 280 { 281 std::make_unique<SwPosition>(rNewPos).swap(m_pPos1); 282 m_pPos1->nContent.SetMark(this); 283 } 284 SetOtherMarkPos(const SwPosition & rNewPos)285 void MarkBase::SetOtherMarkPos(const SwPosition& rNewPos) 286 { 287 std::make_unique<SwPosition>(rNewPos).swap(m_pPos2); 288 m_pPos2->nContent.SetMark(this); 289 } 290 ToString() const291 OUString MarkBase::ToString( ) const 292 { 293 return "Mark: ( Name, [ Node1, Index1 ] ): ( " + m_aName + ", [ " 294 + OUString::number( GetMarkPos().nNode.GetIndex( ) ) + ", " 295 + OUString::number( GetMarkPos().nContent.GetIndex( ) ) + " ] )"; 296 } 297 dumpAsXml(xmlTextWriterPtr pWriter) const298 void MarkBase::dumpAsXml(xmlTextWriterPtr pWriter) const 299 { 300 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("MarkBase")); 301 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(m_aName.toUtf8().getStr())); 302 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("markPos")); 303 GetMarkPos().dumpAsXml(pWriter); 304 (void)xmlTextWriterEndElement(pWriter); 305 if (IsExpanded()) 306 { 307 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("otherMarkPos")); 308 GetOtherMarkPos().dumpAsXml(pWriter); 309 (void)xmlTextWriterEndElement(pWriter); 310 } 311 (void)xmlTextWriterEndElement(pWriter); 312 } 313 ~MarkBase()314 MarkBase::~MarkBase() 315 { } 316 GenerateNewName(std::u16string_view rPrefix)317 OUString MarkBase::GenerateNewName(std::u16string_view rPrefix) 318 { 319 static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr); 320 321 if (bHack) 322 { 323 static sal_Int64 nIdCounter = SAL_CONST_INT64(6000000000); 324 return rPrefix + OUString::number(nIdCounter++); 325 } 326 else 327 { 328 static OUString sUniquePostfix; 329 static sal_Int32 nCount = SAL_MAX_INT32; 330 if(nCount == SAL_MAX_INT32) 331 { 332 unsigned int const n(comphelper::rng::uniform_uint_distribution(0, 333 std::numeric_limits<unsigned int>::max())); 334 sUniquePostfix = "_" + OUString::number(n); 335 nCount = 0; 336 } 337 // putting the counter in front of the random parts will speed up string comparisons 338 return rPrefix + OUString::number(nCount++) + sUniquePostfix; 339 } 340 } 341 SwClientNotify(const SwModify &,const SfxHint & rHint)342 void MarkBase::SwClientNotify(const SwModify&, const SfxHint& rHint) 343 { 344 CallSwClientNotify(rHint); 345 if (rHint.GetId() != SfxHintId::SwLegacyModify) 346 return; 347 auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); 348 if(RES_REMOVE_UNO_OBJECT == pLegacy->GetWhich()) 349 { // invalidate cached uno object 350 SetXBookmark(uno::Reference<text::XTextContent>(nullptr)); 351 } 352 } 353 InvalidateFrames()354 auto MarkBase::InvalidateFrames() -> void 355 { 356 } 357 NavigatorReminder(const SwPaM & rPaM)358 NavigatorReminder::NavigatorReminder(const SwPaM& rPaM) 359 : MarkBase(rPaM, MarkBase::GenerateNewName(u"__NavigatorReminder__")) 360 { } 361 UnoMark(const SwPaM & aPaM)362 UnoMark::UnoMark(const SwPaM& aPaM) 363 : MarkBase(aPaM, MarkBase::GenerateNewName(u"__UnoMark__")) 364 { } 365 DdeBookmark(const SwPaM & aPaM)366 DdeBookmark::DdeBookmark(const SwPaM& aPaM) 367 : MarkBase(aPaM, MarkBase::GenerateNewName(u"__DdeLink__")) 368 { } 369 SetRefObject(SwServerObject * pObj)370 void DdeBookmark::SetRefObject(SwServerObject* pObj) 371 { 372 m_aRefObj = pObj; 373 } 374 DeregisterFromDoc(SwDoc & rDoc)375 void DdeBookmark::DeregisterFromDoc(SwDoc& rDoc) 376 { 377 if(m_aRefObj.is()) 378 rDoc.getIDocumentLinksAdministration().GetLinkManager().RemoveServer(m_aRefObj.get()); 379 } 380 ~DdeBookmark()381 DdeBookmark::~DdeBookmark() 382 { 383 if( m_aRefObj.is() ) 384 { 385 if(m_aRefObj->HasDataLinks()) 386 { 387 ::sfx2::SvLinkSource* p = m_aRefObj.get(); 388 p->SendDataChanged(); 389 } 390 m_aRefObj->SetNoServer(); 391 } 392 } 393 Bookmark(const SwPaM & aPaM,const vcl::KeyCode & rCode,const OUString & rName)394 Bookmark::Bookmark(const SwPaM& aPaM, 395 const vcl::KeyCode& rCode, 396 const OUString& rName) 397 : DdeBookmark(aPaM) 398 , ::sfx2::Metadatable() 399 , m_aCode(rCode) 400 , m_bHidden(false) 401 { 402 m_aName = rName; 403 } 404 InitDoc(SwDoc & io_rDoc,sw::mark::InsertMode const,SwPosition const * const)405 void Bookmark::InitDoc(SwDoc& io_rDoc, 406 sw::mark::InsertMode const, SwPosition const*const) 407 { 408 if (io_rDoc.GetIDocumentUndoRedo().DoesUndo()) 409 { 410 io_rDoc.GetIDocumentUndoRedo().AppendUndo( 411 std::make_unique<SwUndoInsBookmark>(*this)); 412 } 413 io_rDoc.getIDocumentState().SetModified(); 414 InvalidateFrames(); 415 } 416 DeregisterFromDoc(SwDoc & io_rDoc)417 void Bookmark::DeregisterFromDoc(SwDoc& io_rDoc) 418 { 419 DdeBookmark::DeregisterFromDoc(io_rDoc); 420 421 if (io_rDoc.GetIDocumentUndoRedo().DoesUndo()) 422 { 423 io_rDoc.GetIDocumentUndoRedo().AppendUndo( 424 std::make_unique<SwUndoDeleteBookmark>(*this)); 425 } 426 io_rDoc.getIDocumentState().SetModified(); 427 InvalidateFrames(); 428 } 429 430 // invalidate text frames in case it's hidden or Formatting Marks enabled InvalidateFrames()431 auto Bookmark::InvalidateFrames() -> void 432 { 433 InvalidatePosition(GetMarkPos()); 434 if (IsExpanded()) 435 { 436 InvalidatePosition(GetOtherMarkPos()); 437 } 438 } 439 Hide(bool const isHide)440 void Bookmark::Hide(bool const isHide) 441 { 442 if (isHide != m_bHidden) 443 { 444 m_bHidden = isHide; 445 InvalidateFrames(); 446 } 447 } 448 SetHideCondition(OUString const & rHideCondition)449 void Bookmark::SetHideCondition(OUString const& rHideCondition) 450 { 451 if (m_sHideCondition != rHideCondition) 452 { 453 m_sHideCondition = rHideCondition; 454 InvalidateFrames(); 455 } 456 } 457 GetRegistry()458 ::sfx2::IXmlIdRegistry& Bookmark::GetRegistry() 459 { 460 SwDoc& rDoc( GetMarkPos().GetDoc() ); 461 return rDoc.GetXmlIdRegistry(); 462 } 463 IsInClipboard() const464 bool Bookmark::IsInClipboard() const 465 { 466 SwDoc& rDoc( GetMarkPos().GetDoc() ); 467 return rDoc.IsClipBoard(); 468 } 469 IsInUndo() const470 bool Bookmark::IsInUndo() const 471 { 472 return false; 473 } 474 IsInContent() const475 bool Bookmark::IsInContent() const 476 { 477 SwDoc& rDoc( GetMarkPos().GetDoc() ); 478 return !rDoc.IsInHeaderFooter( GetMarkPos().nNode ); 479 } 480 MakeUnoObject()481 uno::Reference< rdf::XMetadatable > Bookmark::MakeUnoObject() 482 { 483 SwDoc& rDoc( GetMarkPos().GetDoc() ); 484 const uno::Reference< rdf::XMetadatable> xMeta( 485 SwXBookmark::CreateXBookmark(rDoc, this), uno::UNO_QUERY); 486 return xMeta; 487 } 488 Fieldmark(const SwPaM & rPaM)489 Fieldmark::Fieldmark(const SwPaM& rPaM) 490 : MarkBase(rPaM, MarkBase::GenerateNewName(u"__Fieldmark__")) 491 { 492 if(!IsExpanded()) 493 SetOtherMarkPos(GetMarkPos()); 494 } 495 SetMarkStartPos(const SwPosition & rNewStartPos)496 void Fieldmark::SetMarkStartPos( const SwPosition& rNewStartPos ) 497 { 498 if ( GetMarkPos( ) <= GetOtherMarkPos( ) ) 499 return SetMarkPos( rNewStartPos ); 500 else 501 return SetOtherMarkPos( rNewStartPos ); 502 } 503 SetMarkEndPos(const SwPosition & rNewEndPos)504 void Fieldmark::SetMarkEndPos( const SwPosition& rNewEndPos ) 505 { 506 if ( GetMarkPos( ) <= GetOtherMarkPos( ) ) 507 return SetOtherMarkPos( rNewEndPos ); 508 else 509 return SetMarkPos( rNewEndPos ); 510 } 511 ToString() const512 OUString Fieldmark::ToString( ) const 513 { 514 return "Fieldmark: ( Name, Type, [ Nd1, Id1 ], [ Nd2, Id2 ] ): ( " + m_aName + ", " 515 + m_aFieldname + ", [ " + OUString::number( GetMarkPos().nNode.GetIndex( ) ) 516 + ", " + OUString::number( GetMarkPos( ).nContent.GetIndex( ) ) + " ], [" 517 + OUString::number( GetOtherMarkPos().nNode.GetIndex( ) ) + ", " 518 + OUString::number( GetOtherMarkPos( ).nContent.GetIndex( ) ) + " ] ) "; 519 } 520 Invalidate()521 void Fieldmark::Invalidate( ) 522 { 523 // TODO: Does exist a better solution to trigger a format of the 524 // fieldmark portion? If yes, please use it. 525 SwPaM aPaM( GetMarkPos(), GetOtherMarkPos() ); 526 aPaM.InvalidatePaM(); 527 } 528 dumpAsXml(xmlTextWriterPtr pWriter) const529 void Fieldmark::dumpAsXml(xmlTextWriterPtr pWriter) const 530 { 531 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("Fieldmark")); 532 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fieldname"), BAD_CAST(m_aFieldname.toUtf8().getStr())); 533 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fieldHelptext"), BAD_CAST(m_aFieldHelptext.toUtf8().getStr())); 534 MarkBase::dumpAsXml(pWriter); 535 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("parameters")); 536 for (auto& rParam : m_vParams) 537 { 538 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("parameter")); 539 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(rParam.first.toUtf8().getStr())); 540 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(comphelper::anyToString(rParam.second).toUtf8().getStr())); 541 (void)xmlTextWriterEndElement(pWriter); 542 } 543 (void)xmlTextWriterEndElement(pWriter); 544 (void)xmlTextWriterEndElement(pWriter); 545 } 546 TextFieldmark(const SwPaM & rPaM,const OUString & rName)547 TextFieldmark::TextFieldmark(const SwPaM& rPaM, const OUString& rName) 548 : Fieldmark(rPaM) 549 { 550 if ( !rName.isEmpty() ) 551 m_aName = rName; 552 } 553 InitDoc(SwDoc & io_rDoc,sw::mark::InsertMode const eMode,SwPosition const * const pSepPos)554 void TextFieldmark::InitDoc(SwDoc& io_rDoc, 555 sw::mark::InsertMode const eMode, SwPosition const*const pSepPos) 556 { 557 if (eMode == sw::mark::InsertMode::New) 558 { 559 lcl_SetFieldMarks(*this, io_rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND, pSepPos); 560 } 561 else 562 { 563 lcl_AssertFieldMarksSet(*this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND); 564 } 565 } 566 ReleaseDoc(SwDoc & rDoc)567 void TextFieldmark::ReleaseDoc(SwDoc& rDoc) 568 { 569 IDocumentUndoRedo & rIDUR(rDoc.GetIDocumentUndoRedo()); 570 if (rIDUR.DoesUndo()) 571 { 572 rIDUR.AppendUndo(std::make_unique<SwUndoDelTextFieldmark>(*this)); 573 } 574 ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes 575 lcl_RemoveFieldMarks(*this, rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND); 576 // notify layouts to unhide - for the entire fieldmark, as in InitDoc() 577 SwPaM const tmp(GetMarkPos(), GetOtherMarkPos()); 578 sw::UpdateFramesForRemoveDeleteRedline(rDoc, tmp); 579 } 580 NonTextFieldmark(const SwPaM & rPaM)581 NonTextFieldmark::NonTextFieldmark(const SwPaM& rPaM) 582 : Fieldmark(rPaM) 583 { } 584 InitDoc(SwDoc & io_rDoc,sw::mark::InsertMode const eMode,SwPosition const * const pSepPos)585 void NonTextFieldmark::InitDoc(SwDoc& io_rDoc, 586 sw::mark::InsertMode const eMode, SwPosition const*const pSepPos) 587 { 588 assert(pSepPos == nullptr); 589 if (eMode == sw::mark::InsertMode::New) 590 { 591 lcl_SetFieldMarks(*this, io_rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT, pSepPos); 592 593 // For some reason the end mark is moved from 1 by the Insert: 594 // we don't want this for checkboxes 595 SwPosition aNewEndPos = GetMarkEnd(); 596 aNewEndPos.nContent--; 597 SetMarkEndPos( aNewEndPos ); 598 } 599 else 600 { 601 lcl_AssertFieldMarksSet(*this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT); 602 } 603 } 604 ReleaseDoc(SwDoc & rDoc)605 void NonTextFieldmark::ReleaseDoc(SwDoc& rDoc) 606 { 607 IDocumentUndoRedo & rIDUR(rDoc.GetIDocumentUndoRedo()); 608 if (rIDUR.DoesUndo()) 609 { 610 rIDUR.AppendUndo(std::make_unique<SwUndoDelNoTextFieldmark>(*this)); 611 } 612 ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes 613 lcl_RemoveFieldMarks(*this, rDoc, 614 CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT); 615 } 616 617 CheckboxFieldmark(const SwPaM & rPaM)618 CheckboxFieldmark::CheckboxFieldmark(const SwPaM& rPaM) 619 : NonTextFieldmark(rPaM) 620 { } 621 SetChecked(bool checked)622 void CheckboxFieldmark::SetChecked(bool checked) 623 { 624 if ( IsChecked() != checked ) 625 { 626 (*GetParameters())[OUString(ODF_FORMCHECKBOX_RESULT)] <<= checked; 627 // mark document as modified 628 SwDoc& rDoc( GetMarkPos().GetDoc() ); 629 rDoc.getIDocumentState().SetModified(); 630 } 631 } 632 IsChecked() const633 bool CheckboxFieldmark::IsChecked() const 634 { 635 bool bResult = false; 636 parameter_map_t::const_iterator pResult = GetParameters()->find(OUString(ODF_FORMCHECKBOX_RESULT)); 637 if(pResult != GetParameters()->end()) 638 pResult->second >>= bResult; 639 return bResult; 640 } 641 FieldmarkWithDropDownButton(const SwPaM & rPaM)642 FieldmarkWithDropDownButton::FieldmarkWithDropDownButton(const SwPaM& rPaM) 643 : NonTextFieldmark(rPaM) 644 , m_pButton(nullptr) 645 { 646 } 647 ~FieldmarkWithDropDownButton()648 FieldmarkWithDropDownButton::~FieldmarkWithDropDownButton() 649 { 650 m_pButton.disposeAndClear(); 651 } 652 RemoveButton()653 void FieldmarkWithDropDownButton::RemoveButton() 654 { 655 if(m_pButton) 656 m_pButton.disposeAndClear(); 657 } 658 DropDownFieldmark(const SwPaM & rPaM)659 DropDownFieldmark::DropDownFieldmark(const SwPaM& rPaM) 660 : FieldmarkWithDropDownButton(rPaM) 661 { 662 } 663 ~DropDownFieldmark()664 DropDownFieldmark::~DropDownFieldmark() 665 { 666 } 667 ShowButton(SwEditWin * pEditWin)668 void DropDownFieldmark::ShowButton(SwEditWin* pEditWin) 669 { 670 if(pEditWin) 671 { 672 if(!m_pButton) 673 m_pButton = VclPtr<DropDownFormFieldButton>::Create(pEditWin, *this); 674 m_pButton->CalcPosAndSize(m_aPortionPaintArea); 675 m_pButton->Show(); 676 } 677 } 678 RemoveButton()679 void DropDownFieldmark::RemoveButton() 680 { 681 FieldmarkWithDropDownButton::RemoveButton(); 682 } 683 SetPortionPaintArea(const SwRect & rPortionPaintArea)684 void DropDownFieldmark::SetPortionPaintArea(const SwRect& rPortionPaintArea) 685 { 686 m_aPortionPaintArea = rPortionPaintArea; 687 if(m_pButton) 688 { 689 m_pButton->Show(); 690 m_pButton->CalcPosAndSize(m_aPortionPaintArea); 691 } 692 } 693 SendLOKShowMessage(SfxViewShell * pViewShell)694 void DropDownFieldmark::SendLOKShowMessage(SfxViewShell* pViewShell) 695 { 696 if (!comphelper::LibreOfficeKit::isActive()) 697 return; 698 699 if (!pViewShell || pViewShell->isLOKMobilePhone()) 700 return; 701 702 if (m_aPortionPaintArea.IsEmpty()) 703 return; 704 705 OStringBuffer sPayload; 706 sPayload = OString::Concat("{\"action\": \"show\"," 707 " \"type\": \"drop-down\", \"textArea\": \"") + 708 m_aPortionPaintArea.SVRect().toString() + "\","; 709 // Add field params to the message 710 sPayload.append(" \"params\": { \"items\": ["); 711 712 // List items 713 auto pParameters = this->GetParameters(); 714 auto pListEntriesIter = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY); 715 css::uno::Sequence<OUString> vListEntries; 716 if (pListEntriesIter != pParameters->end()) 717 { 718 pListEntriesIter->second >>= vListEntries; 719 for (const OUString& sItem : std::as_const(vListEntries)) 720 sPayload.append("\"" + OUStringToOString(sItem, RTL_TEXTENCODING_UTF8) + "\", "); 721 sPayload.setLength(sPayload.getLength() - 2); 722 } 723 sPayload.append("], "); 724 725 // Selected item 726 auto pSelectedItemIter = pParameters->find(ODF_FORMDROPDOWN_RESULT); 727 sal_Int32 nSelection = -1; 728 if (pSelectedItemIter != pParameters->end()) 729 { 730 pSelectedItemIter->second >>= nSelection; 731 } 732 sPayload.append("\"selected\": \"" + OString::number(nSelection) + "\", "); 733 734 // Placeholder text 735 sPayload.append("\"placeholderText\": \"" + OUStringToOString(SwResId(STR_DROP_DOWN_EMPTY_LIST), RTL_TEXTENCODING_UTF8) + "\"}}"); 736 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_FORM_FIELD_BUTTON, sPayload.toString().getStr()); 737 } 738 SendLOKHideMessage(SfxViewShell * pViewShell)739 void DropDownFieldmark::SendLOKHideMessage(SfxViewShell* pViewShell) 740 { 741 OString sPayload = "{\"action\": \"hide\", \"type\": \"drop-down\"}"; 742 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_FORM_FIELD_BUTTON, sPayload.getStr()); 743 } 744 DateFieldmark(const SwPaM & rPaM)745 DateFieldmark::DateFieldmark(const SwPaM& rPaM) 746 : FieldmarkWithDropDownButton(rPaM) 747 , m_pNumberFormatter(nullptr) 748 , m_pDocumentContentOperationsManager(nullptr) 749 { 750 } 751 ~DateFieldmark()752 DateFieldmark::~DateFieldmark() 753 { 754 } 755 InitDoc(SwDoc & io_rDoc,sw::mark::InsertMode eMode,SwPosition const * const pSepPos)756 void DateFieldmark::InitDoc(SwDoc& io_rDoc, 757 sw::mark::InsertMode eMode, SwPosition const*const pSepPos) 758 { 759 m_pNumberFormatter = io_rDoc.GetNumberFormatter(); 760 m_pDocumentContentOperationsManager = &io_rDoc.GetDocumentContentOperationsManager(); 761 if (eMode == sw::mark::InsertMode::New) 762 { 763 lcl_SetFieldMarks(*this, io_rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND, pSepPos); 764 } 765 else 766 { 767 lcl_AssertFieldMarksSet(*this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND); 768 } 769 } 770 ReleaseDoc(SwDoc & rDoc)771 void DateFieldmark::ReleaseDoc(SwDoc& rDoc) 772 { 773 IDocumentUndoRedo & rIDUR(rDoc.GetIDocumentUndoRedo()); 774 if (rIDUR.DoesUndo()) 775 { 776 // TODO does this need a 3rd Undo class? 777 rIDUR.AppendUndo(std::make_unique<SwUndoDelTextFieldmark>(*this)); 778 } 779 ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes 780 lcl_RemoveFieldMarks(*this, rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND); 781 // notify layouts to unhide - for the entire fieldmark, as in InitDoc() 782 SwPaM const tmp(GetMarkPos(), GetOtherMarkPos()); 783 sw::UpdateFramesForRemoveDeleteRedline(rDoc, tmp); 784 } 785 ShowButton(SwEditWin * pEditWin)786 void DateFieldmark::ShowButton(SwEditWin* pEditWin) 787 { 788 if(pEditWin) 789 { 790 if(!m_pButton) 791 m_pButton = VclPtr<DateFormFieldButton>::Create(pEditWin, *this, m_pNumberFormatter); 792 SwRect aPaintArea(m_aPaintAreaStart.TopLeft(), m_aPaintAreaEnd.BottomRight()); 793 m_pButton->CalcPosAndSize(aPaintArea); 794 m_pButton->Show(); 795 } 796 } 797 SetPortionPaintAreaStart(const SwRect & rPortionPaintArea)798 void DateFieldmark::SetPortionPaintAreaStart(const SwRect& rPortionPaintArea) 799 { 800 if (rPortionPaintArea.IsEmpty()) 801 return; 802 803 m_aPaintAreaStart = rPortionPaintArea; 804 InvalidateCurrentDateParam(); 805 } 806 SetPortionPaintAreaEnd(const SwRect & rPortionPaintArea)807 void DateFieldmark::SetPortionPaintAreaEnd(const SwRect& rPortionPaintArea) 808 { 809 if (rPortionPaintArea.IsEmpty()) 810 return; 811 812 if(m_aPaintAreaEnd == rPortionPaintArea && 813 m_pButton && m_pButton->IsVisible()) 814 return; 815 816 m_aPaintAreaEnd = rPortionPaintArea; 817 if(m_pButton) 818 { 819 m_pButton->Show(); 820 SwRect aPaintArea(m_aPaintAreaStart.TopLeft(), m_aPaintAreaEnd.BottomRight()); 821 m_pButton->CalcPosAndSize(aPaintArea); 822 m_pButton->Invalidate(); 823 } 824 InvalidateCurrentDateParam(); 825 } 826 GetContent() const827 OUString DateFieldmark::GetContent() const 828 { 829 const SwTextNode* const pTextNode = GetMarkEnd().nNode.GetNode().GetTextNode(); 830 SwPosition const sepPos(sw::mark::FindFieldSep(*this)); 831 const sal_Int32 nStart(sepPos.nContent.GetIndex()); 832 const sal_Int32 nEnd (GetMarkEnd().nContent.GetIndex()); 833 834 OUString sContent; 835 if(nStart + 1 < pTextNode->GetText().getLength() && nEnd <= pTextNode->GetText().getLength() && 836 nEnd > nStart + 2) 837 sContent = pTextNode->GetText().copy(nStart + 1, nEnd - nStart - 2); 838 return sContent; 839 } 840 ReplaceContent(const OUString & sNewContent)841 void DateFieldmark::ReplaceContent(const OUString& sNewContent) 842 { 843 if(!m_pDocumentContentOperationsManager) 844 return; 845 846 const SwTextNode* const pTextNode = GetMarkEnd().nNode.GetNode().GetTextNode(); 847 SwPosition const sepPos(sw::mark::FindFieldSep(*this)); 848 const sal_Int32 nStart(sepPos.nContent.GetIndex()); 849 const sal_Int32 nEnd (GetMarkEnd().nContent.GetIndex()); 850 851 if(nStart + 1 < pTextNode->GetText().getLength() && nEnd <= pTextNode->GetText().getLength() && 852 nEnd > nStart + 2) 853 { 854 SwPaM aFieldPam(GetMarkStart().nNode, nStart + 1, 855 GetMarkStart().nNode, nEnd - 1); 856 m_pDocumentContentOperationsManager->ReplaceRange(aFieldPam, sNewContent, false); 857 } 858 else 859 { 860 SwPaM aFieldStartPam(GetMarkStart().nNode, nStart + 1); 861 m_pDocumentContentOperationsManager->InsertString(aFieldStartPam, sNewContent); 862 } 863 864 } 865 GetCurrentDate() const866 std::pair<bool, double> DateFieldmark::GetCurrentDate() const 867 { 868 // Check current date param first 869 std::pair<bool, double> aResult = ParseCurrentDateParam(); 870 if(aResult.first) 871 return aResult; 872 873 const sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters(); 874 bool bFoundValidDate = false; 875 double dCurrentDate = 0; 876 OUString sDateFormat; 877 auto pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT); 878 if (pResult != pParameters->end()) 879 { 880 pResult->second >>= sDateFormat; 881 } 882 883 OUString sLang; 884 pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT_LANGUAGE); 885 if (pResult != pParameters->end()) 886 { 887 pResult->second >>= sLang; 888 } 889 890 // Get current content of the field 891 OUString sContent = GetContent(); 892 893 sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(sDateFormat, LanguageTag(sLang).getLanguageType()); 894 if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) 895 { 896 sal_Int32 nCheckPos = 0; 897 SvNumFormatType nType; 898 m_pNumberFormatter->PutEntry(sDateFormat, 899 nCheckPos, 900 nType, 901 nFormat, 902 LanguageTag(sLang).getLanguageType()); 903 } 904 905 if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND) 906 { 907 bFoundValidDate = m_pNumberFormatter->IsNumberFormat(sContent, nFormat, dCurrentDate); 908 } 909 return std::pair<bool, double>(bFoundValidDate, dCurrentDate); 910 } 911 SetCurrentDate(double fDate)912 void DateFieldmark::SetCurrentDate(double fDate) 913 { 914 // Replace current content with the selected date 915 ReplaceContent(GetDateInCurrentDateFormat(fDate)); 916 917 // Also save the current date in a standard format 918 sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters(); 919 (*pParameters)[ODF_FORMDATE_CURRENTDATE] <<= GetDateInStandardDateFormat(fDate); 920 } 921 GetDateInStandardDateFormat(double fDate) const922 OUString DateFieldmark::GetDateInStandardDateFormat(double fDate) const 923 { 924 OUString sCurrentDate; 925 sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(ODF_FORMDATE_CURRENTDATE_FORMAT, ODF_FORMDATE_CURRENTDATE_LANGUAGE); 926 if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) 927 { 928 sal_Int32 nCheckPos = 0; 929 SvNumFormatType nType; 930 OUString sFormat = ODF_FORMDATE_CURRENTDATE_FORMAT; 931 m_pNumberFormatter->PutEntry(sFormat, 932 nCheckPos, 933 nType, 934 nFormat, 935 ODF_FORMDATE_CURRENTDATE_LANGUAGE); 936 } 937 938 if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND) 939 { 940 const Color* pCol = nullptr; 941 m_pNumberFormatter->GetOutputString(fDate, nFormat, sCurrentDate, &pCol, false); 942 } 943 return sCurrentDate; 944 } 945 ParseCurrentDateParam() const946 std::pair<bool, double> DateFieldmark::ParseCurrentDateParam() const 947 { 948 bool bFoundValidDate = false; 949 double dCurrentDate = 0; 950 951 const sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters(); 952 auto pResult = pParameters->find(ODF_FORMDATE_CURRENTDATE); 953 OUString sCurrentDate; 954 if (pResult != pParameters->end()) 955 { 956 pResult->second >>= sCurrentDate; 957 } 958 if(!sCurrentDate.isEmpty()) 959 { 960 sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(ODF_FORMDATE_CURRENTDATE_FORMAT, ODF_FORMDATE_CURRENTDATE_LANGUAGE); 961 if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) 962 { 963 sal_Int32 nCheckPos = 0; 964 SvNumFormatType nType; 965 OUString sFormat = ODF_FORMDATE_CURRENTDATE_FORMAT; 966 m_pNumberFormatter->PutEntry(sFormat, 967 nCheckPos, 968 nType, 969 nFormat, 970 ODF_FORMDATE_CURRENTDATE_LANGUAGE); 971 } 972 973 if(nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND) 974 { 975 bFoundValidDate = m_pNumberFormatter->IsNumberFormat(sCurrentDate, nFormat, dCurrentDate); 976 } 977 } 978 return std::pair<bool, double>(bFoundValidDate, dCurrentDate); 979 } 980 981 GetDateInCurrentDateFormat(double fDate) const982 OUString DateFieldmark::GetDateInCurrentDateFormat(double fDate) const 983 { 984 // Get current date format and language 985 OUString sDateFormat; 986 const sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters(); 987 auto pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT); 988 if (pResult != pParameters->end()) 989 { 990 pResult->second >>= sDateFormat; 991 } 992 993 OUString sLang; 994 pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT_LANGUAGE); 995 if (pResult != pParameters->end()) 996 { 997 pResult->second >>= sLang; 998 } 999 1000 // Fill the content with the specified format 1001 OUString sCurrentContent; 1002 sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(sDateFormat, LanguageTag(sLang).getLanguageType()); 1003 if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) 1004 { 1005 sal_Int32 nCheckPos = 0; 1006 SvNumFormatType nType; 1007 OUString sFormat = sDateFormat; 1008 m_pNumberFormatter->PutEntry(sFormat, 1009 nCheckPos, 1010 nType, 1011 nFormat, 1012 LanguageTag(sLang).getLanguageType()); 1013 } 1014 1015 if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND) 1016 { 1017 const Color* pCol = nullptr; 1018 m_pNumberFormatter->GetOutputString(fDate, nFormat, sCurrentContent, &pCol, false); 1019 } 1020 return sCurrentContent; 1021 } 1022 InvalidateCurrentDateParam()1023 void DateFieldmark::InvalidateCurrentDateParam() 1024 { 1025 std::pair<bool, double> aResult = ParseCurrentDateParam(); 1026 if(!aResult.first) 1027 return; 1028 1029 // Current date became invalid 1030 if(GetDateInCurrentDateFormat(aResult.second) != GetContent()) 1031 { 1032 sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters(); 1033 (*pParameters)[ODF_FORMDATE_CURRENTDATE] <<= OUString(); 1034 } 1035 } 1036 } 1037 1038 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 1039