2338 lines
101 KiB
C++
2338 lines
101 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 <sal/config.h>
|
|
|
|
#include <string_view>
|
|
|
|
#include <editsh.hxx>
|
|
|
|
#include <com/sun/star/container/XEnumerationAccess.hpp>
|
|
#include <com/sun/star/container/XContentEnumerationAccess.hpp>
|
|
#include <com/sun/star/document/XActionLockable.hpp>
|
|
#include <com/sun/star/document/XDocumentProperties.hpp>
|
|
#include <com/sun/star/drawing/FillStyle.hpp>
|
|
#include <com/sun/star/drawing/HomogenMatrix3.hpp>
|
|
#include <com/sun/star/drawing/LineStyle.hpp>
|
|
#include <com/sun/star/drawing/XShape.hpp>
|
|
#include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp>
|
|
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
|
|
#include <com/sun/star/lang/XServiceInfo.hpp>
|
|
#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
|
|
#include <com/sun/star/text/RelOrientation.hpp>
|
|
#include <com/sun/star/text/TextContentAnchorType.hpp>
|
|
#include <com/sun/star/text/VertOrientation.hpp>
|
|
#include <com/sun/star/text/WrapTextMode.hpp>
|
|
#include <com/sun/star/text/XTextContent.hpp>
|
|
#include <com/sun/star/text/XTextDocument.hpp>
|
|
#include <com/sun/star/text/XTextField.hpp>
|
|
#include <com/sun/star/text/XTextRange.hpp>
|
|
#include <com/sun/star/text/XParagraphAppend.hpp>
|
|
#include <com/sun/star/text/XParagraphCursor.hpp>
|
|
#include <com/sun/star/awt/FontWeight.hpp>
|
|
#include <com/sun/star/rdf/XMetadatable.hpp>
|
|
#include <com/sun/star/security/DocumentDigitalSignatures.hpp>
|
|
#include <com/sun/star/security/XCertificate.hpp>
|
|
|
|
#include <basegfx/matrix/b2dhommatrix.hxx>
|
|
#include <comphelper/propertysequence.hxx>
|
|
#include <comphelper/propertyvalue.hxx>
|
|
#include <comphelper/processfactory.hxx>
|
|
#include <comphelper/sequence.hxx>
|
|
#include <comphelper/scopeguard.hxx>
|
|
#include <comphelper/string.hxx>
|
|
#include <editeng/unoprnms.hxx>
|
|
#include <sfx2/classificationhelper.hxx>
|
|
#include <svx/ClassificationCommon.hxx>
|
|
#include <svx/ClassificationField.hxx>
|
|
#include <svl/cryptosign.hxx>
|
|
#include <svl/sigstruct.hxx>
|
|
#include <utility>
|
|
#include <vcl/svapp.hxx>
|
|
#include <vcl/weld.hxx>
|
|
#include <vcl/virdev.hxx>
|
|
|
|
#include <redline.hxx>
|
|
#include <poolfmt.hxx>
|
|
#include <hintids.hxx>
|
|
#include <doc.hxx>
|
|
#include <IDocumentUndoRedo.hxx>
|
|
#include <ndtxt.hxx>
|
|
#include <paratr.hxx>
|
|
#include <viewopt.hxx>
|
|
#include <SwRewriter.hxx>
|
|
#include <numrule.hxx>
|
|
#include <swundo.hxx>
|
|
#include <docary.hxx>
|
|
#include <docsh.hxx>
|
|
#include <unoprnms.hxx>
|
|
#include <rootfrm.hxx>
|
|
#include <pagefrm.hxx>
|
|
#include <txtfrm.hxx>
|
|
#include <rdfhelper.hxx>
|
|
#include <sfx2/watermarkitem.hxx>
|
|
|
|
#include <SwStyleNameMapper.hxx>
|
|
#include <unoparagraph.hxx>
|
|
#include <strings.hrc>
|
|
#include <undobj.hxx>
|
|
#include <UndoParagraphSignature.hxx>
|
|
#include <txtatr.hxx>
|
|
#include <fmtmeta.hxx>
|
|
#include <unotxdoc.hxx>
|
|
#include <unotextbodyhf.hxx>
|
|
#include <unoport.hxx>
|
|
|
|
#include <comphelper/diagnose_ex.hxx>
|
|
#include <IDocumentRedlineAccess.hxx>
|
|
|
|
constexpr OUString WATERMARK_NAME = u"PowerPlusWaterMarkObject"_ustr;
|
|
#define WATERMARK_AUTO_SIZE sal_uInt32(1)
|
|
|
|
namespace
|
|
{
|
|
constexpr OUString MetaFilename(u"tscp/bails.rdf"_ustr);
|
|
constexpr OUString MetaNS(u"urn:bails"_ustr);
|
|
constexpr OUString ParagraphSignatureRDFNamespace = u"urn:bails:loext:paragraph:signature:"_ustr;
|
|
constexpr OUString ParagraphSignatureIdRDFName = u"urn:bails:loext:paragraph:signature:id"_ustr;
|
|
constexpr OUString ParagraphSignatureDigestRDFName = u":digest"_ustr;
|
|
constexpr OUString ParagraphSignatureDateRDFName = u":date"_ustr;
|
|
constexpr OUString ParagraphSignatureUsageRDFName = u":usage"_ustr;
|
|
constexpr OUString ParagraphSignatureLastIdRDFName = u"urn:bails:loext:paragraph:signature:lastid"_ustr;
|
|
constexpr OUString ParagraphClassificationNameRDFName = u"urn:bails:loext:paragraph:classification:name"_ustr;
|
|
constexpr OUString ParagraphClassificationValueRDFName = u"urn:bails:loext:paragraph:classification:value"_ustr;
|
|
constexpr OUString ParagraphClassificationAbbrRDFName = u"urn:bails:loext:paragraph:classification:abbreviation"_ustr;
|
|
constexpr OUString ParagraphClassificationFieldNamesRDFName = u"urn:bails:loext:paragraph:classification:fields"_ustr;
|
|
constexpr OUString MetadataFieldServiceName = u"com.sun.star.text.textfield.MetadataField"_ustr;
|
|
constexpr OUString DocInfoServiceName = u"com.sun.star.text.TextField.DocInfo.Custom"_ustr;
|
|
|
|
/// Find all page styles which are currently used in the document.
|
|
std::vector<OUString> lcl_getUsedPageStyles(SwViewShell const * pShell)
|
|
{
|
|
std::vector<OUString> aReturn;
|
|
|
|
SwRootFrame* pLayout = pShell->GetLayout();
|
|
for (SwFrame* pFrame = pLayout->GetLower(); pFrame; pFrame = pFrame->GetNext())
|
|
{
|
|
SwPageFrame* pPage = static_cast<SwPageFrame*>(pFrame);
|
|
if (const SwPageDesc *pDesc = pPage->FindPageDesc())
|
|
{
|
|
OUString sStyleName;
|
|
SwStyleNameMapper::FillProgName(pDesc->GetName(), sStyleName, SwGetPoolIdFromName::PageDesc);
|
|
aReturn.push_back(sStyleName);
|
|
}
|
|
}
|
|
|
|
return aReturn;
|
|
}
|
|
|
|
/// Search for a field named rFieldName of type rServiceName in xText and return it.
|
|
uno::Reference<text::XTextField> lcl_findField(const uno::Reference<text::XText>& xText, const OUString& rServiceName, std::u16string_view rFieldName)
|
|
{
|
|
uno::Reference<text::XTextField> xField;
|
|
uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xText, uno::UNO_QUERY);
|
|
uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
|
|
while (xParagraphs->hasMoreElements())
|
|
{
|
|
uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
|
|
uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
|
|
while (xTextPortions->hasMoreElements())
|
|
{
|
|
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
|
|
OUString aTextPortionType;
|
|
xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
|
|
if (aTextPortionType != UNO_NAME_TEXT_FIELD)
|
|
continue;
|
|
|
|
uno::Reference<lang::XServiceInfo> xTextField;
|
|
xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
|
|
if (!xTextField->supportsService(rServiceName))
|
|
continue;
|
|
|
|
OUString aName;
|
|
uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY);
|
|
xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName;
|
|
if (aName == rFieldName)
|
|
{
|
|
xField = uno::Reference<text::XTextField>(xTextField, uno::UNO_QUERY);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return xField;
|
|
}
|
|
|
|
/// Search for a field named rFieldName of type rServiceName in xText and return true iff found.
|
|
bool lcl_hasField(const uno::Reference<text::XText>& xText, const OUString& rServiceName, std::u16string_view rFieldName)
|
|
{
|
|
return lcl_findField(xText, rServiceName, rFieldName).is();
|
|
}
|
|
|
|
/// Search for a frame with WATERMARK_NAME in name of type rServiceName in xText. Returns found name in rShapeName.
|
|
uno::Reference<drawing::XShape> lcl_getWatermark(const uno::Reference<text::XText>& xText,
|
|
const OUString& rServiceName, OUString& rShapeName, bool& bSuccess)
|
|
{
|
|
bSuccess = false;
|
|
uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xText, uno::UNO_QUERY);
|
|
uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
|
|
while (xParagraphs->hasMoreElements())
|
|
{
|
|
uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
|
|
if (!xTextPortionEnumerationAccess.is())
|
|
continue;
|
|
|
|
bSuccess = true;
|
|
|
|
uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
|
|
while (xTextPortions->hasMoreElements())
|
|
{
|
|
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
|
|
OUString aTextPortionType;
|
|
xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
|
|
if (aTextPortionType != "Frame")
|
|
continue;
|
|
|
|
uno::Reference<container::XContentEnumerationAccess> xContentEnumerationAccess(xTextPortion, uno::UNO_QUERY);
|
|
if (!xContentEnumerationAccess.is())
|
|
continue;
|
|
|
|
uno::Reference<container::XEnumeration> xEnumeration = xContentEnumerationAccess->createContentEnumeration(u"com.sun.star.text.TextContent"_ustr);
|
|
if (!xEnumeration->hasMoreElements())
|
|
continue;
|
|
|
|
uno::Reference<lang::XServiceInfo> xWatermark(xEnumeration->nextElement(), uno::UNO_QUERY);
|
|
if (!xWatermark->supportsService(rServiceName))
|
|
continue;
|
|
|
|
uno::Reference<container::XNamed> xNamed(xWatermark, uno::UNO_QUERY);
|
|
|
|
if (!xNamed->getName().match(WATERMARK_NAME))
|
|
continue;
|
|
|
|
rShapeName = xNamed->getName();
|
|
|
|
uno::Reference<drawing::XShape> xShape(xWatermark, uno::UNO_QUERY);
|
|
return xShape;
|
|
}
|
|
}
|
|
|
|
return uno::Reference<drawing::XShape>();
|
|
}
|
|
|
|
/// Extract the text of the paragraph without any of the fields.
|
|
/// TODO: Consider moving to SwTextNode, or extend ModelToViewHelper.
|
|
OString lcl_getParagraphBodyText(const uno::Reference<text::XTextContent>& xText)
|
|
{
|
|
OUStringBuffer strBuf;
|
|
uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xText, uno::UNO_QUERY);
|
|
if (!xTextPortionEnumerationAccess.is())
|
|
return OString();
|
|
|
|
uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
|
|
while (xTextPortions->hasMoreElements())
|
|
{
|
|
uno::Any elem = xTextPortions->nextElement();
|
|
|
|
//TODO: Consider including hidden and conditional texts/portions.
|
|
OUString aTextPortionType;
|
|
uno::Reference<beans::XPropertySet> xPropertySet(elem, uno::UNO_QUERY);
|
|
xPropertySet->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
|
|
if (aTextPortionType == "Text")
|
|
{
|
|
uno::Reference<text::XTextRange> xTextRange(elem, uno::UNO_QUERY);
|
|
if (xTextRange.is())
|
|
strBuf.append(xTextRange->getString());
|
|
}
|
|
}
|
|
|
|
// Cleanup the dummy characters added by fields (which we exclude).
|
|
comphelper::string::remove(strBuf, CH_TXT_ATR_INPUTFIELDSTART);
|
|
comphelper::string::remove(strBuf, CH_TXT_ATR_INPUTFIELDEND);
|
|
comphelper::string::remove(strBuf, CH_TXTATR_BREAKWORD);
|
|
|
|
return strBuf.makeStringAndClear().trim().toUtf8();
|
|
}
|
|
|
|
template <typename T>
|
|
std::map<OUString, OUString> lcl_getRDFStatements(const rtl::Reference<SwXTextDocument>& xModel,
|
|
const T& xRef)
|
|
{
|
|
try
|
|
{
|
|
const css::uno::Reference<css::rdf::XResource> xSubject(xRef, uno::UNO_QUERY);
|
|
return SwRDFHelper::getStatements(xModel, MetaNS, xSubject);
|
|
}
|
|
catch (const ::css::uno::Exception&)
|
|
{
|
|
}
|
|
|
|
return std::map<OUString, OUString>();
|
|
}
|
|
|
|
/// Returns RDF (key, value) pair associated with the field, if any.
|
|
std::pair<OUString, OUString> lcl_getFieldRDFByPrefix(const rtl::Reference<SwXTextDocument>& xModel,
|
|
const uno::Reference<css::text::XTextField>& xField,
|
|
std::u16string_view sPrefix)
|
|
{
|
|
for (const auto& pair : lcl_getRDFStatements(xModel, xField))
|
|
{
|
|
if (pair.first.startsWith(sPrefix))
|
|
return pair;
|
|
}
|
|
|
|
return std::make_pair(OUString(), OUString());
|
|
}
|
|
|
|
/// Returns RDF (key, value) pair associated with the field, if any.
|
|
template <typename T>
|
|
std::pair<OUString, OUString> lcl_getRDF(const rtl::Reference<SwXTextDocument>& xModel,
|
|
const T& xRef,
|
|
const OUString& sRDFName)
|
|
{
|
|
const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xRef);
|
|
const auto it = aStatements.find(sRDFName);
|
|
return (it != aStatements.end()) ? std::make_pair(it->first, it->second) : std::make_pair(OUString(), OUString());
|
|
}
|
|
|
|
/// Returns true iff the field in question is paragraph signature.
|
|
/// Note: must have associated RDF, since signatures are otherwise just metadata fields.
|
|
bool lcl_IsParagraphSignatureField(const rtl::Reference<SwXTextDocument>& xModel,
|
|
const uno::Reference<css::text::XTextField>& xField)
|
|
{
|
|
return (lcl_getRDF(xModel, xField, ParagraphSignatureIdRDFName).first == ParagraphSignatureIdRDFName);
|
|
}
|
|
|
|
uno::Reference<text::XTextField> lcl_findFieldByRDF(const rtl::Reference<SwXTextDocument>& xModel,
|
|
const uno::Reference<text::XTextContent>& xParagraph,
|
|
const OUString& sRDFName,
|
|
std::u16string_view sRDFValue)
|
|
{
|
|
uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
|
|
if (!xTextPortionEnumerationAccess.is())
|
|
return uno::Reference<text::XTextField>();
|
|
|
|
uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
|
|
if (!xTextPortions.is())
|
|
return uno::Reference<text::XTextField>();
|
|
|
|
while (xTextPortions->hasMoreElements())
|
|
{
|
|
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
|
|
OUString aTextPortionType;
|
|
xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
|
|
if (aTextPortionType != UNO_NAME_TEXT_FIELD)
|
|
continue;
|
|
|
|
uno::Reference<lang::XServiceInfo> xTextField;
|
|
xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
|
|
if (!xTextField->supportsService(MetadataFieldServiceName))
|
|
continue;
|
|
|
|
uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY);
|
|
const std::pair<OUString, OUString> pair = lcl_getRDF(xModel, xField, sRDFName);
|
|
if (pair.first == sRDFName && (sRDFValue.empty() || sRDFValue == pair.second))
|
|
return xField;
|
|
}
|
|
|
|
return uno::Reference<text::XTextField>();
|
|
}
|
|
|
|
struct SignatureDescr
|
|
{
|
|
OUString msSignature;
|
|
OUString msUsage;
|
|
OUString msDate;
|
|
|
|
bool isValid() const { return !msSignature.isEmpty(); }
|
|
};
|
|
|
|
SignatureDescr lcl_getSignatureDescr(const rtl::Reference<SwXTextDocument>& xModel,
|
|
const uno::Reference<css::text::XTextContent>& xParagraph,
|
|
std::u16string_view sFieldId)
|
|
{
|
|
SignatureDescr aDescr;
|
|
|
|
const OUString prefix = ParagraphSignatureRDFNamespace + sFieldId;
|
|
const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xParagraph);
|
|
|
|
const auto itSig = aStatements.find(prefix + ParagraphSignatureDigestRDFName);
|
|
aDescr.msSignature = (itSig != aStatements.end() ? itSig->second : OUString());
|
|
|
|
const auto itDate = aStatements.find(prefix + ParagraphSignatureDateRDFName);
|
|
aDescr.msDate = (itDate != aStatements.end() ? itDate->second : OUString());
|
|
|
|
const auto itUsage = aStatements.find(prefix + ParagraphSignatureUsageRDFName);
|
|
aDescr.msUsage = (itUsage != aStatements.end() ? itUsage->second : OUString());
|
|
|
|
return aDescr;
|
|
}
|
|
|
|
SignatureDescr lcl_getSignatureDescr(const rtl::Reference<SwXTextDocument>& xModel,
|
|
const uno::Reference<css::text::XTextContent>& xParagraph,
|
|
const uno::Reference<css::text::XTextField>& xField)
|
|
{
|
|
const OUString sFieldId = lcl_getRDF(xModel, xField, ParagraphSignatureIdRDFName).second;
|
|
if (!sFieldId.isEmpty())
|
|
return lcl_getSignatureDescr(xModel, xParagraph, sFieldId);
|
|
|
|
return SignatureDescr();
|
|
}
|
|
|
|
/// Validate and create the signature field display text from the fields.
|
|
std::pair<bool, OUString> lcl_MakeParagraphSignatureFieldText(const SignatureDescr& aDescr,
|
|
const OString& utf8Text)
|
|
{
|
|
OUString msg = SwResId(STR_INVALID_SIGNATURE);
|
|
bool valid = false;
|
|
|
|
if (aDescr.isValid())
|
|
{
|
|
const char* pData = utf8Text.getStr();
|
|
const std::vector<unsigned char> data(pData, pData + utf8Text.getLength());
|
|
|
|
OString encSignature;
|
|
if (aDescr.msSignature.convertToString(&encSignature, RTL_TEXTENCODING_UTF8, 0))
|
|
{
|
|
const std::vector<unsigned char> sig(svl::crypto::DecodeHexString(encSignature));
|
|
SignatureInformation aInfo(0);
|
|
valid = svl::crypto::Signing::Verify(data, false, sig, aInfo);
|
|
valid = valid
|
|
&& aInfo.nStatus == xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED;
|
|
|
|
assert(aInfo.GetSigningCertificate()); // it was valid
|
|
msg = SwResId(STR_SIGNED_BY) + ": " + aInfo.GetSigningCertificate()->X509Subject + ", " +
|
|
aDescr.msDate;
|
|
msg += (!aDescr.msUsage.isEmpty() ? (" (" + aDescr.msUsage + "): ") : u": "_ustr);
|
|
msg += (valid ? SwResId(STR_VALID) : SwResId(STR_INVALID));
|
|
}
|
|
}
|
|
|
|
return std::make_pair(valid, msg);
|
|
}
|
|
|
|
/// Validate and return validation result and signature field display text.
|
|
std::pair<bool, OUString>
|
|
lcl_MakeParagraphSignatureFieldText(const rtl::Reference<SwXTextDocument>& xModel,
|
|
const uno::Reference<css::text::XTextContent>& xParagraph,
|
|
const uno::Reference<css::text::XTextField>& xField,
|
|
const OString& utf8Text)
|
|
{
|
|
const SignatureDescr aDescr = lcl_getSignatureDescr(xModel, xParagraph, xField);
|
|
return lcl_MakeParagraphSignatureFieldText(aDescr, utf8Text);
|
|
}
|
|
|
|
/// Generate the next valid ID for the new signature on this paragraph.
|
|
OUString lcl_getNextSignatureId(const rtl::Reference<SwXTextDocument>& xModel,
|
|
const uno::Reference<text::XTextContent>& xParagraph)
|
|
{
|
|
const OUString sFieldId = lcl_getRDF(xModel, xParagraph, ParagraphSignatureLastIdRDFName).second;
|
|
return OUString::number(!sFieldId.isEmpty() ? sFieldId.toInt32() + 1 : 1);
|
|
}
|
|
|
|
/// Creates and inserts Paragraph Signature Metadata field and creates the RDF entry
|
|
uno::Reference<text::XTextField> lcl_InsertParagraphSignature(const rtl::Reference<SwXTextDocument>& xModel,
|
|
const uno::Reference<text::XTextContent>& xParagraph,
|
|
const OUString& signature,
|
|
const OUString& usage)
|
|
{
|
|
uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel);
|
|
auto xField = uno::Reference<text::XTextField>(xMultiServiceFactory->createInstance(MetadataFieldServiceName), uno::UNO_QUERY);
|
|
|
|
// Add the signature at the end.
|
|
xField->attach(xParagraph->getAnchor()->getEnd());
|
|
|
|
const OUString sId = lcl_getNextSignatureId(xModel, xParagraph);
|
|
|
|
const css::uno::Reference<css::rdf::XResource> xSubject(xField, uno::UNO_QUERY);
|
|
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xSubject, ParagraphSignatureIdRDFName, sId);
|
|
|
|
// First convert the UTC UNIX timestamp to a tools::DateTime then to local time.
|
|
DateTime aDateTime = DateTime::CreateFromUnixTime(time(nullptr));
|
|
aDateTime.ConvertToLocalTime();
|
|
OUStringBuffer rBuffer;
|
|
rBuffer.append(static_cast<sal_Int32>(aDateTime.GetYear()));
|
|
rBuffer.append('-');
|
|
if (aDateTime.GetMonth() < 10)
|
|
rBuffer.append('0');
|
|
rBuffer.append(static_cast<sal_Int32>(aDateTime.GetMonth()));
|
|
rBuffer.append('-');
|
|
if (aDateTime.GetDay() < 10)
|
|
rBuffer.append('0');
|
|
rBuffer.append(static_cast<sal_Int32>(aDateTime.GetDay()));
|
|
|
|
// Now set the RDF on the paragraph, since that's what is preserved in .doc(x).
|
|
const css::uno::Reference<css::rdf::XResource> xParaSubject(xParagraph, uno::UNO_QUERY);
|
|
const OUString prefix = ParagraphSignatureRDFNamespace + sId;
|
|
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, ParagraphSignatureLastIdRDFName, sId);
|
|
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureDigestRDFName, signature);
|
|
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureUsageRDFName, usage);
|
|
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureDateRDFName, rBuffer.makeStringAndClear());
|
|
|
|
return xField;
|
|
}
|
|
|
|
/// Updates the signature field text if changed and returns true only iff updated.
|
|
bool lcl_DoUpdateParagraphSignatureField(SwDoc& rDoc,
|
|
const uno::Reference<css::text::XTextField>& xField,
|
|
const OUString& sDisplayText)
|
|
{
|
|
// Disable undo to avoid introducing noise when we edit the metadata field.
|
|
const bool isUndoEnabled = rDoc.GetIDocumentUndoRedo().DoesUndo();
|
|
rDoc.GetIDocumentUndoRedo().DoUndo(false);
|
|
comphelper::ScopeGuard const g([&rDoc, isUndoEnabled]() {
|
|
rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
|
|
});
|
|
|
|
try
|
|
{
|
|
uno::Reference<css::text::XTextRange> xText(xField, uno::UNO_QUERY);
|
|
const OUString curText = xText->getString();
|
|
if (curText != sDisplayText)
|
|
{
|
|
xText->setString(sDisplayText);
|
|
return true;
|
|
}
|
|
}
|
|
catch (const uno::Exception&)
|
|
{
|
|
// We failed; avoid crashing.
|
|
DBG_UNHANDLED_EXCEPTION("sw.uno", "Failed to update paragraph signature");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Updates the signature field text if changed and returns true only iff updated.
|
|
bool lcl_UpdateParagraphSignatureField(SwDoc& rDoc,
|
|
const rtl::Reference<SwXTextDocument>& xModel,
|
|
const uno::Reference<css::text::XTextContent>& xParagraph,
|
|
const uno::Reference<css::text::XTextField>& xField,
|
|
const OString& utf8Text)
|
|
{
|
|
const OUString sDisplayText
|
|
= lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text).second;
|
|
return lcl_DoUpdateParagraphSignatureField(rDoc, xField, sDisplayText);
|
|
}
|
|
|
|
void lcl_RemoveParagraphMetadataField(const uno::Reference<css::text::XTextField>& xField)
|
|
{
|
|
uno::Reference<css::text::XTextRange> xParagraph(xField->getAnchor());
|
|
xParagraph->getText()->removeTextContent(xField);
|
|
}
|
|
|
|
/// Returns true iff the field in question is paragraph classification.
|
|
/// Note: must have associated RDF, since classifications are otherwise just metadata fields.
|
|
bool lcl_IsParagraphClassificationField(const rtl::Reference<SwXTextDocument>& xModel,
|
|
const uno::Reference<css::text::XTextField>& xField,
|
|
std::u16string_view sKey)
|
|
{
|
|
const std::pair<OUString, OUString> rdfPair = lcl_getRDF(xModel, xField, ParagraphClassificationNameRDFName);
|
|
return rdfPair.first == ParagraphClassificationNameRDFName && (sKey.empty() || rdfPair.second == sKey);
|
|
}
|
|
|
|
uno::Reference<text::XTextField> lcl_FindParagraphClassificationField(const rtl::Reference<SwXTextDocument>& xModel,
|
|
const rtl::Reference<SwXParagraph>& xParagraph,
|
|
std::u16string_view sKey = u"")
|
|
{
|
|
uno::Reference<text::XTextField> xTextField;
|
|
|
|
if (!xParagraph.is())
|
|
return xTextField;
|
|
|
|
// Enumerate text portions to find metadata fields. This is expensive, best to enumerate fields only.
|
|
rtl::Reference<SwXTextPortionEnumeration> xTextPortions = xParagraph->createTextFieldsEnumeration();
|
|
while (xTextPortions->hasMoreElements())
|
|
{
|
|
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
|
|
OUString aTextPortionType;
|
|
xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
|
|
if (aTextPortionType != UNO_NAME_TEXT_FIELD)
|
|
continue;
|
|
|
|
uno::Reference<lang::XServiceInfo> xServiceInfo;
|
|
xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xServiceInfo;
|
|
if (!xServiceInfo->supportsService(MetadataFieldServiceName))
|
|
continue;
|
|
|
|
uno::Reference<text::XTextField> xField(xServiceInfo, uno::UNO_QUERY);
|
|
if (lcl_IsParagraphClassificationField(xModel, xField, sKey))
|
|
{
|
|
xTextField = std::move(xField);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return xTextField;
|
|
}
|
|
|
|
/// Creates and inserts Paragraph Classification Metadata field and creates the RDF entry
|
|
uno::Reference<text::XTextField> lcl_InsertParagraphClassification(const rtl::Reference<SwXTextDocument>& xModel,
|
|
const uno::Reference<text::XTextContent>& xParent)
|
|
{
|
|
auto xField = uno::Reference<text::XTextField>(xModel->createInstance(MetadataFieldServiceName), uno::UNO_QUERY);
|
|
|
|
// Add the classification at the start.
|
|
xField->attach(xParent->getAnchor()->getStart());
|
|
return xField;
|
|
}
|
|
|
|
/// Updates the paragraph classification field text if changed and returns true only iff updated.
|
|
bool lcl_UpdateParagraphClassificationField(SwDoc* pDoc,
|
|
const rtl::Reference<SwXTextDocument>& xModel,
|
|
const uno::Reference<css::text::XTextContent>& xTextNode,
|
|
const OUString& sKey,
|
|
const OUString& sValue,
|
|
const OUString& sDisplayText)
|
|
{
|
|
// Disable undo to avoid introducing noise when we edit the metadata field.
|
|
const bool isUndoEnabled = pDoc->GetIDocumentUndoRedo().DoesUndo();
|
|
pDoc->GetIDocumentUndoRedo().DoUndo(false);
|
|
comphelper::ScopeGuard const g([pDoc, isUndoEnabled] () {
|
|
pDoc->GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
|
|
});
|
|
|
|
uno::Reference<text::XTextField> xField = lcl_InsertParagraphClassification(xModel, xTextNode);
|
|
|
|
css::uno::Reference<css::rdf::XResource> xFieldSubject(xField, uno::UNO_QUERY);
|
|
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, sKey, sValue);
|
|
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphClassificationNameRDFName, sKey);
|
|
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphClassificationValueRDFName, sValue);
|
|
|
|
css::uno::Reference<css::rdf::XResource> xNodeSubject(xTextNode, uno::UNO_QUERY);
|
|
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, sKey, sValue);
|
|
|
|
return lcl_DoUpdateParagraphSignatureField(*pDoc, xField, sDisplayText);
|
|
}
|
|
|
|
void lcl_ValidateParagraphSignatures(SwDoc& rDoc, const uno::Reference<text::XTextContent>& xParagraph, const bool updateDontRemove, const uno::Sequence<uno::Reference<css::rdf::XURI>>& rGraphNames)
|
|
{
|
|
SwDocShell* pDocShell = rDoc.GetDocShell();
|
|
if (!pDocShell)
|
|
return;
|
|
|
|
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
|
|
|
|
// Check if the paragraph is signed.
|
|
try
|
|
{
|
|
const css::uno::Reference<css::rdf::XResource> xSubject(xParagraph, uno::UNO_QUERY);
|
|
std::map<OUString, OUString> aStatements = SwRDFHelper::getStatements(xModel, rGraphNames, xSubject);
|
|
const auto it = aStatements.find(ParagraphSignatureLastIdRDFName);
|
|
if (it == aStatements.end() || it->second.isEmpty())
|
|
return;
|
|
}
|
|
catch (const ::css::uno::Exception&)
|
|
{
|
|
return;
|
|
}
|
|
|
|
uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
|
|
if (!xTextPortionEnumerationAccess.is())
|
|
return;
|
|
|
|
uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
|
|
if (!xTextPortions.is())
|
|
return;
|
|
|
|
// Get the text (without fields).
|
|
const OString utf8Text = lcl_getParagraphBodyText(xParagraph);
|
|
if (utf8Text.isEmpty())
|
|
return;
|
|
|
|
while (xTextPortions->hasMoreElements())
|
|
{
|
|
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
|
|
OUString aTextPortionType;
|
|
xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
|
|
if (aTextPortionType != UNO_NAME_TEXT_FIELD)
|
|
continue;
|
|
|
|
uno::Reference<lang::XServiceInfo> xTextField;
|
|
xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
|
|
if (!xTextField->supportsService(MetadataFieldServiceName))
|
|
continue;
|
|
|
|
uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY);
|
|
if (!lcl_IsParagraphSignatureField(xModel, xField))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (updateDontRemove)
|
|
{
|
|
lcl_UpdateParagraphSignatureField(rDoc, xModel, xParagraph, xField, utf8Text);
|
|
}
|
|
else if (!lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text).first)
|
|
{
|
|
rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
|
|
rDoc.GetIDocumentUndoRedo().AppendUndo(
|
|
std::make_unique<SwUndoParagraphSigning>(rDoc, xField, xParagraph, false));
|
|
lcl_RemoveParagraphMetadataField(xField);
|
|
rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
SwTextFormatColl& SwEditShell::GetDfltTextFormatColl() const
|
|
{
|
|
return *GetDoc()->GetDfltTextFormatColl();
|
|
}
|
|
|
|
sal_uInt16 SwEditShell::GetTextFormatCollCount() const
|
|
{
|
|
return GetDoc()->GetTextFormatColls()->size();
|
|
}
|
|
|
|
SwTextFormatColl& SwEditShell::GetTextFormatColl(sal_uInt16 nFormatColl) const
|
|
{
|
|
return *((*(GetDoc()->GetTextFormatColls()))[nFormatColl]);
|
|
}
|
|
|
|
static void insertFieldToDocument(uno::Reference<lang::XMultiServiceFactory> const & rxMultiServiceFactory,
|
|
uno::Reference<text::XText> const & rxText, uno::Reference<text::XParagraphCursor> const & rxParagraphCursor,
|
|
OUString const & rsKey)
|
|
{
|
|
uno::Reference<beans::XPropertySet> xField(rxMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY);
|
|
xField->setPropertyValue(UNO_NAME_NAME, uno::Any(rsKey));
|
|
uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);
|
|
|
|
rxText->insertTextContent(rxParagraphCursor, xTextContent, false);
|
|
}
|
|
|
|
static void removeAllClassificationFields(std::u16string_view rPolicy, uno::Reference<text::XText> const & rxText)
|
|
{
|
|
uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(rxText, uno::UNO_QUERY);
|
|
uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
|
|
while (xParagraphs->hasMoreElements())
|
|
{
|
|
uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
|
|
uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
|
|
while (xTextPortions->hasMoreElements())
|
|
{
|
|
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
|
|
OUString aTextPortionType;
|
|
xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
|
|
if (aTextPortionType != UNO_NAME_TEXT_FIELD)
|
|
continue;
|
|
|
|
uno::Reference<lang::XServiceInfo> xTextField;
|
|
xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
|
|
if (!xTextField->supportsService(DocInfoServiceName))
|
|
continue;
|
|
|
|
OUString aName;
|
|
uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY);
|
|
xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName;
|
|
if (aName.startsWith(rPolicy))
|
|
{
|
|
uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY);
|
|
rxText->removeTextContent(xField);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static sal_Int32 getNumberOfParagraphs(uno::Reference<text::XText> const & xText)
|
|
{
|
|
uno::Reference<container::XEnumerationAccess> xParagraphEnumAccess(xText, uno::UNO_QUERY);
|
|
uno::Reference<container::XEnumeration> xParagraphEnum = xParagraphEnumAccess->createEnumeration();
|
|
sal_Int32 nResult = 0;
|
|
while (xParagraphEnum->hasMoreElements())
|
|
{
|
|
xParagraphEnum->nextElement();
|
|
nResult++;
|
|
}
|
|
return nResult;
|
|
}
|
|
|
|
static void equaliseNumberOfParagraph(std::vector<svx::ClassificationResult> const & rResults, uno::Reference<text::XText> const & xText)
|
|
{
|
|
sal_Int32 nNumberOfParagraphs = 0;
|
|
for (svx::ClassificationResult const & rResult : rResults)
|
|
{
|
|
if (rResult.meType == svx::ClassificationType::PARAGRAPH)
|
|
nNumberOfParagraphs++;
|
|
}
|
|
|
|
while (getNumberOfParagraphs(xText) < nNumberOfParagraphs)
|
|
{
|
|
uno::Reference<text::XParagraphAppend> xParagraphAppend(xText, uno::UNO_QUERY);
|
|
xParagraphAppend->finishParagraph(uno::Sequence<beans::PropertyValue>());
|
|
}
|
|
}
|
|
|
|
void SwEditShell::ApplyAdvancedClassification(std::vector<svx::ClassificationResult> const & rResults)
|
|
{
|
|
SwDocShell* pDocShell = GetDoc()->GetDocShell();
|
|
if (!pDocShell)
|
|
return;
|
|
|
|
const SfxObjectShell* pObjSh = SfxObjectShell::Current();
|
|
if (!pObjSh)
|
|
return;
|
|
|
|
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
|
|
uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
|
|
uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
|
|
|
|
uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel);
|
|
|
|
uno::Reference<document::XDocumentProperties> xDocumentProperties = pObjSh->getDocProperties();
|
|
|
|
const OUString sPolicy = SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType());
|
|
const std::vector<OUString> aUsedPageStyles = lcl_getUsedPageStyles(this);
|
|
for (const OUString& rPageStyleName : aUsedPageStyles)
|
|
{
|
|
uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
|
|
|
|
// HEADER
|
|
bool bHeaderIsOn = false;
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
|
|
uno::Reference<text::XText> xHeaderText;
|
|
if (bHeaderIsOn)
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
|
|
if (xHeaderText.is())
|
|
removeAllClassificationFields(sPolicy, xHeaderText);
|
|
|
|
// FOOTER
|
|
bool bFooterIsOn = false;
|
|
xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn;
|
|
uno::Reference<text::XText> xFooterText;
|
|
if (bFooterIsOn)
|
|
xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText;
|
|
if (xFooterText.is())
|
|
removeAllClassificationFields(sPolicy, xFooterText);
|
|
}
|
|
|
|
// Clear properties
|
|
uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();
|
|
svx::classification::removeAllProperties(xPropertyContainer);
|
|
|
|
SfxClassificationHelper aHelper(xDocumentProperties);
|
|
|
|
// Apply properties from the BA policy
|
|
for (svx::ClassificationResult const & rResult : rResults)
|
|
{
|
|
if (rResult.meType == svx::ClassificationType::CATEGORY)
|
|
{
|
|
aHelper.SetBACName(rResult.msName, SfxClassificationHelper::getPolicyType());
|
|
}
|
|
}
|
|
|
|
sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType());
|
|
|
|
// Insert origin document property
|
|
svx::classification::insertCreationOrigin(xPropertyContainer, aCreator, sfx::ClassificationCreationOrigin::MANUAL);
|
|
|
|
// Insert full text as document property
|
|
svx::classification::insertFullTextualRepresentationAsDocumentProperty(xPropertyContainer, aCreator, rResults);
|
|
|
|
for (const OUString& rPageStyleName : aUsedPageStyles)
|
|
{
|
|
uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
|
|
|
|
// HEADER
|
|
bool bHeaderIsOn = false;
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
|
|
if (!bHeaderIsOn)
|
|
xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::Any(true));
|
|
uno::Reference<text::XText> xHeaderText;
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
|
|
equaliseNumberOfParagraph(rResults, xHeaderText);
|
|
|
|
// FOOTER
|
|
bool bFooterIsOn = false;
|
|
xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn;
|
|
if (!bFooterIsOn)
|
|
xPageStyle->setPropertyValue(UNO_NAME_FOOTER_IS_ON, uno::Any(true));
|
|
uno::Reference<text::XText> xFooterText;
|
|
xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText;
|
|
equaliseNumberOfParagraph(rResults, xFooterText);
|
|
|
|
// SET/DELETE WATERMARK
|
|
SfxWatermarkItem aWatermarkItem;
|
|
aWatermarkItem.SetText(aHelper.GetDocumentWatermark());
|
|
SetWatermark(aWatermarkItem);
|
|
|
|
uno::Reference<text::XParagraphCursor> xHeaderParagraphCursor(xHeaderText->createTextCursor(), uno::UNO_QUERY);
|
|
uno::Reference<text::XParagraphCursor> xFooterParagraphCursor(xFooterText->createTextCursor(), uno::UNO_QUERY);
|
|
|
|
sal_Int32 nParagraph = -1;
|
|
|
|
for (svx::ClassificationResult const & rResult : rResults)
|
|
{
|
|
switch(rResult.meType)
|
|
{
|
|
case svx::ClassificationType::TEXT:
|
|
{
|
|
OUString sKey = aCreator.makeNumberedTextKey();
|
|
|
|
svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName);
|
|
insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
|
|
insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
|
|
}
|
|
break;
|
|
|
|
case svx::ClassificationType::CATEGORY:
|
|
{
|
|
OUString sKey = aCreator.makeCategoryNameKey();
|
|
insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
|
|
insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
|
|
}
|
|
break;
|
|
|
|
case svx::ClassificationType::MARKING:
|
|
{
|
|
OUString sKey = aCreator.makeNumberedMarkingKey();
|
|
svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName);
|
|
insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
|
|
insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
|
|
}
|
|
break;
|
|
|
|
case svx::ClassificationType::INTELLECTUAL_PROPERTY_PART:
|
|
{
|
|
OUString sKey = aCreator.makeNumberedIntellectualPropertyPartKey();
|
|
svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName);
|
|
insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
|
|
insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
|
|
}
|
|
break;
|
|
|
|
case svx::ClassificationType::PARAGRAPH:
|
|
{
|
|
nParagraph++;
|
|
|
|
if (nParagraph != 0) // only jump to next paragraph, if we aren't at the first paragraph
|
|
{
|
|
xHeaderParagraphCursor->gotoNextParagraph(false);
|
|
xFooterParagraphCursor->gotoNextParagraph(false);
|
|
}
|
|
|
|
xHeaderParagraphCursor->gotoStartOfParagraph(false);
|
|
xFooterParagraphCursor->gotoStartOfParagraph(false);
|
|
|
|
uno::Reference<beans::XPropertySet> xHeaderPropertySet(xHeaderParagraphCursor, uno::UNO_QUERY_THROW);
|
|
uno::Reference<beans::XPropertySet> xFooterPropertySet(xFooterParagraphCursor, uno::UNO_QUERY_THROW);
|
|
if (rResult.msName == "BOLD")
|
|
{
|
|
xHeaderPropertySet->setPropertyValue(u"CharWeight"_ustr, uno::Any(awt::FontWeight::BOLD));
|
|
xFooterPropertySet->setPropertyValue(u"CharWeight"_ustr, uno::Any(awt::FontWeight::BOLD));
|
|
}
|
|
else
|
|
{
|
|
xHeaderPropertySet->setPropertyValue(u"CharWeight"_ustr, uno::Any(awt::FontWeight::NORMAL));
|
|
xFooterPropertySet->setPropertyValue(u"CharWeight"_ustr, uno::Any(awt::FontWeight::NORMAL));
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<svx::ClassificationResult> SwEditShell::CollectAdvancedClassification()
|
|
{
|
|
std::vector<svx::ClassificationResult> aResult;
|
|
|
|
SwDocShell* pDocShell = GetDoc()->GetDocShell();
|
|
if (!pDocShell)
|
|
return aResult;
|
|
|
|
const SfxObjectShell* pObjSh = SfxObjectShell::Current();
|
|
if (!pObjSh)
|
|
return aResult;
|
|
|
|
const OUString sBlank;
|
|
|
|
uno::Reference<document::XDocumentProperties> xDocumentProperties = pObjSh->getDocProperties();
|
|
uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();
|
|
sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType());
|
|
|
|
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
|
|
uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
|
|
uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
|
|
|
|
std::vector<OUString> aPageStyles = lcl_getUsedPageStyles(this);
|
|
const OUString& aPageStyleString = aPageStyles.back();
|
|
uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(aPageStyleString), uno::UNO_QUERY);
|
|
|
|
bool bHeaderIsOn = false;
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
|
|
if (!bHeaderIsOn)
|
|
{
|
|
const OUString aValue = svx::classification::getProperty(xPropertyContainer, aCreator.makeCategoryNameKey());
|
|
if (!aValue.isEmpty())
|
|
aResult.emplace_back(svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank);
|
|
|
|
return aResult;
|
|
}
|
|
|
|
uno::Reference<text::XText> xHeaderText;
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
|
|
|
|
uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xHeaderText, uno::UNO_QUERY);
|
|
uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
|
|
|
|
// set to true if category was found in the header
|
|
bool bFoundClassificationCategory = false;
|
|
|
|
while (xParagraphs->hasMoreElements())
|
|
{
|
|
uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
|
|
if (!xTextPortionEnumerationAccess.is())
|
|
continue;
|
|
uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
|
|
|
|
// Check font weight
|
|
uno::Reference<beans::XPropertySet> xParagraphPropertySet(xTextPortionEnumerationAccess, uno::UNO_QUERY_THROW);
|
|
uno::Any aAny = xParagraphPropertySet->getPropertyValue(u"CharWeight"_ustr);
|
|
|
|
OUString sWeight = (aAny.get<float>() >= awt::FontWeight::BOLD) ? u"BOLD"_ustr : u"NORMAL"_ustr;
|
|
|
|
aResult.emplace_back(svx::ClassificationType::PARAGRAPH, sWeight, sBlank, sBlank);
|
|
|
|
// Process portions
|
|
while (xTextPortions->hasMoreElements())
|
|
{
|
|
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
|
|
OUString aTextPortionType;
|
|
xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
|
|
if (aTextPortionType != UNO_NAME_TEXT_FIELD)
|
|
continue;
|
|
|
|
uno::Reference<lang::XServiceInfo> xTextField;
|
|
xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
|
|
if (!xTextField->supportsService(DocInfoServiceName))
|
|
continue;
|
|
|
|
OUString aName;
|
|
uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY);
|
|
xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName;
|
|
|
|
if (aCreator.isMarkingTextKey(aName))
|
|
{
|
|
const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
|
|
if (!aValue.isEmpty())
|
|
aResult.emplace_back(svx::ClassificationType::TEXT, aValue, sBlank, sBlank);
|
|
}
|
|
else if (aCreator.isCategoryNameKey(aName))
|
|
{
|
|
const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
|
|
if (!aValue.isEmpty())
|
|
aResult.emplace_back(svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank);
|
|
bFoundClassificationCategory = true;
|
|
}
|
|
else if (aCreator.isCategoryIdentifierKey(aName))
|
|
{
|
|
const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
|
|
if (!aValue.isEmpty())
|
|
aResult.emplace_back(svx::ClassificationType::CATEGORY, sBlank, sBlank, aValue);
|
|
bFoundClassificationCategory = true;
|
|
}
|
|
else if (aCreator.isMarkingKey(aName))
|
|
{
|
|
const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
|
|
if (!aValue.isEmpty())
|
|
aResult.emplace_back(svx::ClassificationType::MARKING, aValue, sBlank, sBlank);
|
|
}
|
|
else if (aCreator.isIntellectualPropertyPartKey(aName))
|
|
{
|
|
const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
|
|
if (!aValue.isEmpty())
|
|
aResult.emplace_back(svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, aValue, sBlank, sBlank);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bFoundClassificationCategory)
|
|
{
|
|
const OUString aValue = svx::classification::getProperty(xPropertyContainer, aCreator.makeCategoryNameKey());
|
|
if (!aValue.isEmpty())
|
|
aResult.emplace_back(svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank);
|
|
}
|
|
|
|
return aResult;
|
|
}
|
|
|
|
void SwEditShell::SetClassification(const OUString& rName, SfxClassificationPolicyType eType)
|
|
{
|
|
SwDocShell* pDocShell = GetDoc()->GetDocShell();
|
|
if (!pDocShell)
|
|
return;
|
|
|
|
SfxClassificationHelper aHelper(pDocShell->getDocProperties());
|
|
|
|
const bool bHadWatermark = !aHelper.GetDocumentWatermark().isEmpty();
|
|
|
|
// This updates the infobar as well.
|
|
aHelper.SetBACName(rName, eType);
|
|
|
|
// Insert origin document property
|
|
uno::Reference<beans::XPropertyContainer> xPropertyContainer = pDocShell->getDocProperties()->getUserDefinedProperties();
|
|
sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType());
|
|
svx::classification::insertCreationOrigin(xPropertyContainer, aCreator, sfx::ClassificationCreationOrigin::BAF_POLICY);
|
|
|
|
bool bHeaderIsNeeded = aHelper.HasDocumentHeader();
|
|
bool bFooterIsNeeded = aHelper.HasDocumentFooter();
|
|
OUString aWatermark = aHelper.GetDocumentWatermark();
|
|
bool bWatermarkIsNeeded = !aWatermark.isEmpty();
|
|
|
|
if (!bHeaderIsNeeded && !bFooterIsNeeded && !bWatermarkIsNeeded && !bHadWatermark)
|
|
return;
|
|
|
|
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
|
|
uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
|
|
uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
|
|
const uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames();
|
|
|
|
for (const OUString& rPageStyleName : aStyles)
|
|
{
|
|
uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
|
|
uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel);
|
|
|
|
if (bHeaderIsNeeded || bWatermarkIsNeeded || bHadWatermark)
|
|
{
|
|
// If the header is off, turn it on.
|
|
bool bHeaderIsOn = false;
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
|
|
if (!bHeaderIsOn)
|
|
xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::Any(true));
|
|
|
|
// If the header already contains a document header field, no need to do anything.
|
|
uno::Reference<text::XText> xHeaderText;
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
|
|
|
|
if (bHeaderIsNeeded)
|
|
{
|
|
if (!lcl_hasField(xHeaderText, DocInfoServiceName, Concat2View(SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCHEADER())))
|
|
{
|
|
// Append a field to the end of the header text.
|
|
uno::Reference<beans::XPropertySet> xField(xMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY);
|
|
xField->setPropertyValue(UNO_NAME_NAME, uno::Any(SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCHEADER()));
|
|
uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);
|
|
xHeaderText->insertTextContent(xHeaderText->getEnd(), xTextContent, /*bAbsorb=*/false);
|
|
}
|
|
}
|
|
|
|
SfxWatermarkItem aWatermarkItem;
|
|
aWatermarkItem.SetText(aWatermark);
|
|
SetWatermark(aWatermarkItem);
|
|
}
|
|
|
|
if (bFooterIsNeeded)
|
|
{
|
|
// If the footer is off, turn it on.
|
|
bool bFooterIsOn = false;
|
|
xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn;
|
|
if (!bFooterIsOn)
|
|
xPageStyle->setPropertyValue(UNO_NAME_FOOTER_IS_ON, uno::Any(true));
|
|
|
|
// If the footer already contains a document header field, no need to do anything.
|
|
uno::Reference<text::XText> xFooterText;
|
|
xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText;
|
|
static OUString sFooter = SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCFOOTER();
|
|
if (!lcl_hasField(xFooterText, DocInfoServiceName, sFooter))
|
|
{
|
|
// Append a field to the end of the footer text.
|
|
uno::Reference<beans::XPropertySet> xField(xMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY);
|
|
xField->setPropertyValue(UNO_NAME_NAME, uno::Any(sFooter));
|
|
uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);
|
|
xFooterText->insertTextContent(xFooterText->getEnd(), xTextContent, /*bAbsorb=*/false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// We pass xParent and xNodeSubject even though they point to the same thing because the UNO_QUERY is
|
|
// on a performance-sensitive path.
|
|
static void lcl_ApplyParagraphClassification(SwDoc* pDoc,
|
|
const rtl::Reference<SwXTextDocument>& xModel,
|
|
const rtl::Reference<SwXParagraph>& xParent,
|
|
const css::uno::Reference<css::rdf::XResource>& xNodeSubject,
|
|
std::vector<svx::ClassificationResult> aResults)
|
|
{
|
|
if (!xNodeSubject.is())
|
|
return;
|
|
|
|
// Remove all paragraph classification fields.
|
|
for (;;)
|
|
{
|
|
uno::Reference<text::XTextField> xTextField = lcl_FindParagraphClassificationField(xModel, xParent);
|
|
if (!xTextField.is())
|
|
break;
|
|
lcl_RemoveParagraphMetadataField(xTextField);
|
|
}
|
|
|
|
if (aResults.empty())
|
|
return;
|
|
|
|
// Since we always insert at the start of the paragraph,
|
|
// need to insert in reverse order.
|
|
std::reverse(aResults.begin(), aResults.end());
|
|
// Ignore "PARAGRAPH" types
|
|
std::erase_if(aResults,
|
|
[](const svx::ClassificationResult& rResult)-> bool
|
|
{ return rResult.meType == svx::ClassificationType::PARAGRAPH; });
|
|
|
|
sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
|
|
std::vector<OUString> aFieldNames;
|
|
for (size_t nIndex = 0; nIndex < aResults.size(); ++nIndex)
|
|
{
|
|
const svx::ClassificationResult& rResult = aResults[nIndex];
|
|
|
|
const bool isLast = nIndex == 0;
|
|
const bool isFirst = (nIndex == aResults.size() - 1);
|
|
OUString sKey;
|
|
OUString sValue = rResult.msName;
|
|
switch (rResult.meType)
|
|
{
|
|
case svx::ClassificationType::TEXT:
|
|
{
|
|
sKey = aKeyCreator.makeNumberedTextKey();
|
|
}
|
|
break;
|
|
|
|
case svx::ClassificationType::CATEGORY:
|
|
{
|
|
if (rResult.msIdentifier.isEmpty())
|
|
{
|
|
sKey = aKeyCreator.makeCategoryNameKey();
|
|
}
|
|
else
|
|
{
|
|
sValue = rResult.msIdentifier;
|
|
sKey = aKeyCreator.makeCategoryIdentifierKey();
|
|
}
|
|
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, ParagraphClassificationAbbrRDFName, rResult.msAbbreviatedName);
|
|
}
|
|
break;
|
|
|
|
case svx::ClassificationType::MARKING:
|
|
{
|
|
sKey = aKeyCreator.makeNumberedMarkingKey();
|
|
}
|
|
break;
|
|
|
|
case svx::ClassificationType::INTELLECTUAL_PROPERTY_PART:
|
|
{
|
|
sKey = aKeyCreator.makeNumberedIntellectualPropertyPartKey();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
OUString sDisplayText = (isFirst ? ("(" + rResult.msAbbreviatedName) : rResult.msAbbreviatedName);
|
|
if (isLast)
|
|
sDisplayText += ")";
|
|
lcl_UpdateParagraphClassificationField(pDoc, xModel, xParent, sKey, sValue, sDisplayText);
|
|
aFieldNames.emplace_back(sKey);
|
|
}
|
|
|
|
// Correct the order
|
|
std::reverse(aFieldNames.begin(), aFieldNames.end());
|
|
OUStringBuffer sFieldNames;
|
|
bool first = true;
|
|
for (const OUString& rFieldName : aFieldNames)
|
|
{
|
|
if (!first)
|
|
sFieldNames.append("/");
|
|
sFieldNames.append(rFieldName);
|
|
first = false;
|
|
}
|
|
|
|
const OUString sOldFieldNames = lcl_getRDF(xModel, xNodeSubject, ParagraphClassificationFieldNamesRDFName).second;
|
|
SwRDFHelper::removeStatement(xModel, MetaNS, xNodeSubject, ParagraphClassificationFieldNamesRDFName, sOldFieldNames);
|
|
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, ParagraphClassificationFieldNamesRDFName, sFieldNames.makeStringAndClear());
|
|
}
|
|
|
|
void SwEditShell::ApplyParagraphClassification(std::vector<svx::ClassificationResult> aResults)
|
|
{
|
|
SwDocShell* pDocShell = GetDoc()->GetDocShell();
|
|
if (!pDocShell || !GetCursor() || !GetCursor()->Start())
|
|
return;
|
|
|
|
SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode();
|
|
if (pNode == nullptr)
|
|
return;
|
|
|
|
// Prevent recursive validation since this is triggered on node updates, which we do below.
|
|
const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
|
|
comphelper::ScopeGuard const g([this, bOldValidationFlag]() {
|
|
SetParagraphSignatureValidation(bOldValidationFlag);
|
|
});
|
|
|
|
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
|
|
rtl::Reference<SwXParagraph> xParent = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr);
|
|
lcl_ApplyParagraphClassification(GetDoc(), xModel, xParent, css::uno::Reference<css::rdf::XResource>(xParent), std::move(aResults));
|
|
}
|
|
|
|
static std::vector<svx::ClassificationResult> lcl_CollectParagraphClassification(const rtl::Reference<SwXTextDocument>& xModel, const uno::Reference<text::XTextContent>& xParagraph)
|
|
{
|
|
std::vector<svx::ClassificationResult> aResult;
|
|
|
|
uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
|
|
if (!xTextPortionEnumerationAccess.is())
|
|
return aResult;
|
|
|
|
uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
|
|
|
|
const sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
|
|
|
|
while (xTextPortions->hasMoreElements())
|
|
{
|
|
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
|
|
OUString aTextPortionType;
|
|
xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
|
|
if (aTextPortionType != UNO_NAME_TEXT_FIELD)
|
|
continue;
|
|
|
|
uno::Reference<lang::XServiceInfo> xField;
|
|
xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xField;
|
|
if (!xField->supportsService(MetadataFieldServiceName))
|
|
continue;
|
|
|
|
uno::Reference<text::XTextField> xTextField(xField, uno::UNO_QUERY);
|
|
const OUString sPolicy = SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType());
|
|
const std::pair<OUString, OUString> rdfNamePair = lcl_getFieldRDFByPrefix(xModel, xTextField, sPolicy);
|
|
|
|
uno::Reference<text::XTextRange> xTextRange(xField, uno::UNO_QUERY);
|
|
const OUString aName = rdfNamePair.first;
|
|
const OUString aValue = rdfNamePair.second;
|
|
static constexpr OUString sBlank(u""_ustr);
|
|
if (aKeyCreator.isMarkingTextKey(aName))
|
|
{
|
|
aResult.emplace_back(svx::ClassificationType::TEXT, aValue, sBlank, sBlank);
|
|
}
|
|
else if (aKeyCreator.isCategoryNameKey(aName))
|
|
{
|
|
aResult.emplace_back(svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank);
|
|
}
|
|
else if (aKeyCreator.isCategoryIdentifierKey(aName))
|
|
{
|
|
aResult.emplace_back(svx::ClassificationType::CATEGORY, sBlank, sBlank, aValue);
|
|
}
|
|
else if (aKeyCreator.isMarkingKey(aName))
|
|
{
|
|
aResult.emplace_back(svx::ClassificationType::MARKING, aValue, sBlank, sBlank);
|
|
}
|
|
else if (aKeyCreator.isIntellectualPropertyPartKey(aName))
|
|
{
|
|
aResult.emplace_back(svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, xTextRange->getString(), sBlank, sBlank);
|
|
}
|
|
}
|
|
|
|
return aResult;
|
|
}
|
|
|
|
std::vector<svx::ClassificationResult> SwEditShell::CollectParagraphClassification()
|
|
{
|
|
std::vector<svx::ClassificationResult> aResult;
|
|
|
|
SwDocShell* pDocShell = GetDoc()->GetDocShell();
|
|
if (!pDocShell || !GetCursor() || !GetCursor()->Start())
|
|
return aResult;
|
|
|
|
SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode();
|
|
if (pNode == nullptr)
|
|
return aResult;
|
|
|
|
rtl::Reference<SwXParagraph> xParent = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr);
|
|
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
|
|
return lcl_CollectParagraphClassification(xModel, xParent);
|
|
}
|
|
|
|
static sal_Int16 lcl_GetAngle(const drawing::HomogenMatrix3& rMatrix)
|
|
{
|
|
basegfx::B2DHomMatrix aTransformation;
|
|
basegfx::B2DTuple aScale;
|
|
basegfx::B2DTuple aTranslate;
|
|
double fRotate = 0;
|
|
double fShear = 0;
|
|
|
|
aTransformation.set(0, 0, rMatrix.Line1.Column1);
|
|
aTransformation.set(0, 1, rMatrix.Line1.Column2);
|
|
aTransformation.set(0, 2, rMatrix.Line1.Column3);
|
|
aTransformation.set(1, 0, rMatrix.Line2.Column1);
|
|
aTransformation.set(1, 1, rMatrix.Line2.Column2);
|
|
aTransformation.set(1, 2, rMatrix.Line2.Column3);
|
|
// For this to be a valid 2D transform matrix, the last row must be [0,0,1]
|
|
assert( rMatrix.Line3.Column1 == 0 );
|
|
assert( rMatrix.Line3.Column2 == 0 );
|
|
assert( rMatrix.Line3.Column3 == 1 );
|
|
|
|
aTransformation.decompose(aScale, aTranslate, fRotate, fShear);
|
|
sal_Int16 nDeg = round(basegfx::rad2deg(fRotate));
|
|
return nDeg < 0 ? round(nDeg) * -1 : round(360.0 - nDeg);
|
|
}
|
|
|
|
SfxWatermarkItem SwEditShell::GetWatermark() const
|
|
{
|
|
SwDocShell* pDocShell = GetDoc()->GetDocShell();
|
|
if (!pDocShell)
|
|
return SfxWatermarkItem();
|
|
|
|
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
|
|
uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
|
|
uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
|
|
std::vector<OUString> aUsedPageStyles = lcl_getUsedPageStyles(this);
|
|
for (const OUString& rPageStyleName : aUsedPageStyles)
|
|
{
|
|
uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
|
|
|
|
bool bHeaderIsOn = false;
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
|
|
if (!bHeaderIsOn)
|
|
return SfxWatermarkItem();
|
|
|
|
uno::Reference<text::XText> xHeaderText;
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
|
|
|
|
OUString sWatermark = u""_ustr;
|
|
bool bSuccess = false;
|
|
uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, u"com.sun.star.drawing.CustomShape"_ustr, sWatermark, bSuccess);
|
|
|
|
if (xWatermark.is())
|
|
{
|
|
SfxWatermarkItem aItem;
|
|
uno::Reference<text::XTextRange> xTextRange(xWatermark, uno::UNO_QUERY);
|
|
uno::Reference<beans::XPropertySet> xPropertySet(xWatermark, uno::UNO_QUERY);
|
|
Color nColor;
|
|
sal_Int16 nTransparency;
|
|
OUString aFont;
|
|
drawing::HomogenMatrix3 aMatrix;
|
|
|
|
aItem.SetText(xTextRange->getString());
|
|
|
|
if (xPropertySet->getPropertyValue(UNO_NAME_CHAR_FONT_NAME) >>= aFont)
|
|
aItem.SetFont(aFont);
|
|
if (xPropertySet->getPropertyValue(UNO_NAME_FILLCOLOR) >>= nColor)
|
|
aItem.SetColor(nColor);
|
|
if (xPropertySet->getPropertyValue(u"Transformation"_ustr) >>= aMatrix)
|
|
aItem.SetAngle(lcl_GetAngle(aMatrix));
|
|
if (xPropertySet->getPropertyValue(UNO_NAME_FILL_TRANSPARENCE) >>= nTransparency)
|
|
aItem.SetTransparency(nTransparency);
|
|
|
|
return aItem;
|
|
}
|
|
}
|
|
return SfxWatermarkItem();
|
|
}
|
|
|
|
static void lcl_placeWatermarkInHeader(const SfxWatermarkItem& rWatermark,
|
|
const rtl::Reference<SwXTextDocument>& xModel,
|
|
const uno::Reference<beans::XPropertySet>& xPageStyle,
|
|
const uno::Reference<text::XText>& xHeaderText)
|
|
{
|
|
if (!xHeaderText.is())
|
|
return;
|
|
|
|
OUString aShapeServiceName = u"com.sun.star.drawing.CustomShape"_ustr;
|
|
OUString sWatermark = WATERMARK_NAME;
|
|
bool bSuccess = false;
|
|
uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, aShapeServiceName, sWatermark, bSuccess);
|
|
|
|
bool bDeleteWatermark = rWatermark.GetText().isEmpty();
|
|
if (xWatermark.is())
|
|
{
|
|
drawing::HomogenMatrix3 aMatrix;
|
|
Color nColor = 0xc0c0c0;
|
|
sal_Int16 nTransparency = 50;
|
|
sal_Int16 nAngle = 45;
|
|
OUString aFont = u""_ustr;
|
|
|
|
uno::Reference<beans::XPropertySet> xPropertySet(xWatermark, uno::UNO_QUERY);
|
|
xPropertySet->getPropertyValue(UNO_NAME_CHAR_FONT_NAME) >>= aFont;
|
|
xPropertySet->getPropertyValue(UNO_NAME_FILLCOLOR) >>= nColor;
|
|
xPropertySet->getPropertyValue(UNO_NAME_FILL_TRANSPARENCE) >>= nTransparency;
|
|
xPropertySet->getPropertyValue(u"Transformation"_ustr) >>= aMatrix;
|
|
nAngle = lcl_GetAngle(aMatrix);
|
|
|
|
// If the header already contains a watermark, see if it its text is up to date.
|
|
uno::Reference<text::XTextRange> xTextRange(xWatermark, uno::UNO_QUERY);
|
|
if (xTextRange->getString() != rWatermark.GetText()
|
|
|| aFont != rWatermark.GetFont()
|
|
|| nColor != rWatermark.GetColor()
|
|
|| nAngle != rWatermark.GetAngle()
|
|
|| nTransparency != rWatermark.GetTransparency()
|
|
|| bDeleteWatermark)
|
|
{
|
|
// No: delete it and we'll insert a replacement.
|
|
uno::Reference<lang::XComponent> xComponent(xWatermark, uno::UNO_QUERY);
|
|
xComponent->dispose();
|
|
xWatermark.clear();
|
|
}
|
|
}
|
|
|
|
if (!bSuccess || xWatermark.is() || bDeleteWatermark)
|
|
return;
|
|
|
|
const OUString& sFont = rWatermark.GetFont();
|
|
sal_Int16 nAngle = rWatermark.GetAngle();
|
|
sal_Int16 nTransparency = rWatermark.GetTransparency();
|
|
Color nColor = rWatermark.GetColor();
|
|
|
|
// Calc the ratio.
|
|
double fRatio = 0;
|
|
|
|
ScopedVclPtrInstance<VirtualDevice> pDevice;
|
|
vcl::Font aFont = pDevice->GetFont();
|
|
aFont.SetFamilyName(sFont);
|
|
aFont.SetFontSize(Size(0, 96));
|
|
pDevice->SetFont(aFont);
|
|
|
|
auto nTextWidth = pDevice->GetTextWidth(rWatermark.GetText());
|
|
if (nTextWidth)
|
|
{
|
|
fRatio = pDevice->GetTextHeight();
|
|
fRatio /= nTextWidth;
|
|
}
|
|
|
|
// Calc the size.
|
|
sal_Int32 nWidth = 0;
|
|
awt::Size aSize;
|
|
xPageStyle->getPropertyValue(UNO_NAME_SIZE) >>= aSize;
|
|
if (aSize.Width < aSize.Height)
|
|
{
|
|
// Portrait.
|
|
sal_Int32 nLeftMargin = 0;
|
|
xPageStyle->getPropertyValue(UNO_NAME_LEFT_MARGIN) >>= nLeftMargin;
|
|
sal_Int32 nRightMargin = 0;
|
|
xPageStyle->getPropertyValue(UNO_NAME_RIGHT_MARGIN) >>= nRightMargin;
|
|
nWidth = aSize.Width - nLeftMargin - nRightMargin;
|
|
}
|
|
else
|
|
{
|
|
// Landscape.
|
|
sal_Int32 nTopMargin = 0;
|
|
xPageStyle->getPropertyValue(UNO_NAME_TOP_MARGIN) >>= nTopMargin;
|
|
sal_Int32 nBottomMargin = 0;
|
|
xPageStyle->getPropertyValue(UNO_NAME_BOTTOM_MARGIN) >>= nBottomMargin;
|
|
nWidth = aSize.Height - nTopMargin - nBottomMargin;
|
|
}
|
|
sal_Int32 nHeight = fRatio * nWidth;
|
|
|
|
// Create and insert the shape.
|
|
uno::Reference<drawing::XShape> xShape(xModel->createInstance(aShapeServiceName), uno::UNO_QUERY);
|
|
|
|
uno::Reference<container::XNamed> xNamed(xShape, uno::UNO_QUERY);
|
|
xNamed->setName(sWatermark);
|
|
|
|
basegfx::B2DHomMatrix aTransformation;
|
|
aTransformation.identity();
|
|
aTransformation.scale(nWidth, nHeight);
|
|
aTransformation.rotate(-basegfx::deg2rad(nAngle));
|
|
drawing::HomogenMatrix3 aMatrix;
|
|
aMatrix.Line1.Column1 = aTransformation.get(0, 0);
|
|
aMatrix.Line1.Column2 = aTransformation.get(0, 1);
|
|
aMatrix.Line1.Column3 = aTransformation.get(0, 2);
|
|
aMatrix.Line2.Column1 = aTransformation.get(1, 0);
|
|
aMatrix.Line2.Column2 = aTransformation.get(1, 1);
|
|
aMatrix.Line2.Column3 = aTransformation.get(1, 2);
|
|
aMatrix.Line3.Column1 = 0;
|
|
aMatrix.Line3.Column2 = 0;
|
|
aMatrix.Line3.Column3 = 1;
|
|
uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
|
|
xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_TYPE, uno::Any(text::TextContentAnchorType_AT_CHARACTER));
|
|
uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY);
|
|
xHeaderText->insertTextContent(xHeaderText->getEnd(), xTextContent, false);
|
|
|
|
// The remaining properties have to be set after the shape is inserted: do that in one batch to avoid flickering.
|
|
uno::Reference<document::XActionLockable> xLockable(xShape, uno::UNO_QUERY);
|
|
xLockable->addActionLock();
|
|
xPropertySet->setPropertyValue(UNO_NAME_FILLCOLOR, uno::Any(static_cast<sal_Int32>(nColor)));
|
|
xPropertySet->setPropertyValue(UNO_NAME_FILLSTYLE, uno::Any(drawing::FillStyle_SOLID));
|
|
xPropertySet->setPropertyValue(UNO_NAME_FILL_TRANSPARENCE, uno::Any(nTransparency));
|
|
xPropertySet->setPropertyValue(UNO_NAME_LINESTYLE, uno::Any(drawing::LineStyle_NONE));
|
|
xPropertySet->setPropertyValue(UNO_NAME_OPAQUE, uno::Any(false));
|
|
xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT, uno::Any(false));
|
|
xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWWIDTH, uno::Any(false));
|
|
xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEHEIGHT, uno::Any(nHeight));
|
|
xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEWIDTH, uno::Any(nWidth));
|
|
xPropertySet->setPropertyValue(UNO_NAME_TEXT_WRAP, uno::Any(text::WrapTextMode_THROUGH));
|
|
xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION, uno::Any(text::RelOrientation::PAGE_PRINT_AREA));
|
|
xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT_RELATION, uno::Any(text::RelOrientation::PAGE_PRINT_AREA));
|
|
xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME, uno::Any(sFont));
|
|
xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME_ASIAN, uno::Any(sFont));
|
|
xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME_COMPLEX, uno::Any(sFont));
|
|
xPropertySet->setPropertyValue(UNO_NAME_CHAR_HEIGHT, uno::Any(WATERMARK_AUTO_SIZE));
|
|
xPropertySet->setPropertyValue(u"Transformation"_ustr, uno::Any(aMatrix));
|
|
|
|
uno::Reference<text::XTextRange> xTextRange(xShape, uno::UNO_QUERY);
|
|
xTextRange->setString(rWatermark.GetText());
|
|
|
|
uno::Reference<drawing::XEnhancedCustomShapeDefaulter> xDefaulter(xShape, uno::UNO_QUERY);
|
|
xDefaulter->createCustomShapeDefaults(u"fontwork-plain-text"_ustr);
|
|
|
|
auto aGeomPropSeq = xPropertySet->getPropertyValue(u"CustomShapeGeometry"_ustr).get< uno::Sequence<beans::PropertyValue> >();
|
|
auto aGeomPropVec = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(aGeomPropSeq);
|
|
uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
|
|
{
|
|
{"TextPath", uno::Any(true)},
|
|
}));
|
|
auto it = std::find_if(aGeomPropVec.begin(), aGeomPropVec.end(), [](const beans::PropertyValue& rValue)
|
|
{
|
|
return rValue.Name == "TextPath";
|
|
});
|
|
if (it == aGeomPropVec.end())
|
|
aGeomPropVec.push_back(comphelper::makePropertyValue(u"TextPath"_ustr, aPropertyValues));
|
|
else
|
|
it->Value <<= aPropertyValues;
|
|
xPropertySet->setPropertyValue(u"CustomShapeGeometry"_ustr, uno::Any(comphelper::containerToSequence(aGeomPropVec)));
|
|
|
|
// tdf#108494, tdf#109313 the header height was switched to height of a watermark
|
|
// and shape was moved to the lower part of a page, force position update
|
|
xPropertySet->getPropertyValue(u"Transformation"_ustr) >>= aMatrix;
|
|
xPropertySet->setPropertyValue(u"Transformation"_ustr, uno::Any(aMatrix));
|
|
|
|
xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT, uno::Any(text::HoriOrientation::CENTER));
|
|
xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT, uno::Any(text::VertOrientation::CENTER));
|
|
|
|
xLockable->removeActionLock();
|
|
}
|
|
|
|
void SwEditShell::SetWatermark(const SfxWatermarkItem& rWatermark)
|
|
{
|
|
SwDocShell* pDocShell = GetDoc()->GetDocShell();
|
|
if (!pDocShell)
|
|
return;
|
|
const bool bNoWatermark = rWatermark.GetText().isEmpty();
|
|
|
|
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
|
|
uno::Reference<container::XNameAccess> xStyleFamilies = xModel->getStyleFamilies();
|
|
uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(u"PageStyles"_ustr), uno::UNO_QUERY);
|
|
const uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames();
|
|
|
|
for (const OUString& rPageStyleName : aStyles)
|
|
{
|
|
uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
|
|
|
|
// If the header is off, turn it on.
|
|
bool bHeaderIsOn = false;
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
|
|
if (!bHeaderIsOn)
|
|
{
|
|
if (bNoWatermark)
|
|
continue; // the style doesn't have any watermark - no need to do anything
|
|
|
|
xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::Any(true));
|
|
}
|
|
|
|
// backup header height
|
|
bool bDynamicHeight = true;
|
|
sal_Int32 nOldValue;
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_HEIGHT) >>= nOldValue;
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT) >>= bDynamicHeight;
|
|
xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(false));
|
|
|
|
// If the header already contains a document header field, no need to do anything.
|
|
uno::Reference<text::XText> xHeaderText;
|
|
uno::Reference<text::XText> xHeaderTextFirst;
|
|
uno::Reference<text::XText> xHeaderTextLeft;
|
|
uno::Reference<text::XText> xHeaderTextRight;
|
|
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
|
|
lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderText);
|
|
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_FIRST) >>= xHeaderTextFirst;
|
|
lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextFirst);
|
|
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_LEFT) >>= xHeaderTextLeft;
|
|
lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextLeft);
|
|
|
|
xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_RIGHT) >>= xHeaderTextRight;
|
|
lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextRight);
|
|
|
|
// tdf#108494 the header height was switched to height of a watermark
|
|
// and shape was moved to the lower part of a page
|
|
xPageStyle->setPropertyValue(UNO_NAME_HEADER_HEIGHT, uno::Any(sal_Int32(11)));
|
|
xPageStyle->setPropertyValue(UNO_NAME_HEADER_HEIGHT, uno::Any(nOldValue));
|
|
xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(bDynamicHeight));
|
|
}
|
|
}
|
|
|
|
SwUndoParagraphSigning::SwUndoParagraphSigning(SwDoc& rDoc,
|
|
uno::Reference<text::XTextField> xField,
|
|
uno::Reference<text::XTextContent> xParent,
|
|
const bool bRemove)
|
|
: SwUndo(SwUndoId::PARA_SIGN_ADD, &rDoc),
|
|
m_rDoc(rDoc),
|
|
m_xField(std::move(xField)),
|
|
m_xParent(std::move(xParent)),
|
|
m_bRemove(bRemove)
|
|
{
|
|
// Save the metadata and field content to undo/redo.
|
|
if (SwDocShell* pShell = m_rDoc.GetDocShell())
|
|
{
|
|
rtl::Reference<SwXTextDocument> xModel = pShell->GetBaseModel();
|
|
const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, m_xField);
|
|
const auto it = aStatements.find(ParagraphSignatureIdRDFName);
|
|
if (it != aStatements.end())
|
|
m_signature = it->second;
|
|
|
|
const auto it2 = aStatements.find(ParagraphSignatureUsageRDFName);
|
|
if (it2 != aStatements.end())
|
|
m_usage = it2->second;
|
|
|
|
uno::Reference<css::text::XTextRange> xText(m_xField, uno::UNO_QUERY);
|
|
m_display = xText->getString();
|
|
}
|
|
}
|
|
|
|
void SwUndoParagraphSigning::UndoImpl(::sw::UndoRedoContext&)
|
|
{
|
|
if (m_bRemove)
|
|
Remove();
|
|
else
|
|
Insert();
|
|
}
|
|
|
|
void SwUndoParagraphSigning::RedoImpl(::sw::UndoRedoContext&)
|
|
{
|
|
if (m_bRemove)
|
|
Insert();
|
|
else
|
|
Remove();
|
|
}
|
|
|
|
void SwUndoParagraphSigning::RepeatImpl(::sw::RepeatContext&)
|
|
{
|
|
}
|
|
|
|
void SwUndoParagraphSigning::Insert()
|
|
{
|
|
// Disable undo to avoid introducing noise when we edit the metadata field.
|
|
const bool isUndoEnabled = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
|
|
m_rDoc.GetIDocumentUndoRedo().DoUndo(false);
|
|
|
|
// Prevent validation since this will trigger a premature validation
|
|
// upon inserting, but before setting the metadata.
|
|
SwEditShell* pEditSh = m_rDoc.GetEditShell();
|
|
const bool bOldValidationFlag = pEditSh && pEditSh->SetParagraphSignatureValidation(false);
|
|
comphelper::ScopeGuard const g([&] () {
|
|
if (pEditSh)
|
|
pEditSh->SetParagraphSignatureValidation(bOldValidationFlag);
|
|
m_rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
|
|
});
|
|
|
|
if (SwDocShell* pShell = m_rDoc.GetDocShell())
|
|
{
|
|
m_xField = lcl_InsertParagraphSignature(pShell->GetBaseModel(), m_xParent, m_signature, m_usage);
|
|
lcl_DoUpdateParagraphSignatureField(m_rDoc, m_xField, m_display);
|
|
}
|
|
}
|
|
|
|
void SwUndoParagraphSigning::Remove()
|
|
{
|
|
// Disable undo to avoid introducing noise when we edit the metadata field.
|
|
const bool isUndoEnabled = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
|
|
m_rDoc.GetIDocumentUndoRedo().DoUndo(false);
|
|
|
|
// Prevent validation since this will trigger a premature validation
|
|
// upon removing.
|
|
SwEditShell* pEditSh = m_rDoc.GetEditShell();
|
|
const bool bOldValidationFlag = pEditSh && pEditSh->SetParagraphSignatureValidation(false);
|
|
comphelper::ScopeGuard const g([&] () {
|
|
if (pEditSh)
|
|
pEditSh->SetParagraphSignatureValidation(bOldValidationFlag);
|
|
m_rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
|
|
});
|
|
|
|
lcl_RemoveParagraphMetadataField(m_xField);
|
|
}
|
|
|
|
void SwEditShell::SignParagraph()
|
|
{
|
|
SwDoc& rDoc = *GetDoc();
|
|
SwDocShell* pDocShell = rDoc.GetDocShell();
|
|
if (!pDocShell || !GetCursor() || !GetCursor()->Start())
|
|
return;
|
|
const SwPosition* pPosStart = GetCursor()->Start();
|
|
if (!pPosStart)
|
|
return;
|
|
SwTextNode* pNode = pPosStart->GetNode().GetTextNode();
|
|
if (!pNode)
|
|
return;
|
|
|
|
// Table text signing is not supported.
|
|
if (pNode->FindTableNode() != nullptr)
|
|
return;
|
|
|
|
// 1. Get the text (without fields).
|
|
const rtl::Reference<SwXParagraph> xParagraph = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr);
|
|
const OString utf8Text = lcl_getParagraphBodyText(xParagraph);
|
|
if (utf8Text.isEmpty())
|
|
return;
|
|
|
|
// 2. Get certificate.
|
|
uno::Reference<security::XDocumentDigitalSignatures> xSigner(
|
|
// here none of the version-dependent methods are called
|
|
security::DocumentDigitalSignatures::createDefault(
|
|
comphelper::getProcessComponentContext()));
|
|
|
|
uno::Sequence<css::beans::PropertyValue> aProperties;
|
|
uno::Reference<security::XCertificate> xCertificate = xSigner->chooseCertificateWithProps(aProperties);
|
|
if (!xCertificate.is())
|
|
return;
|
|
|
|
// 3. Sign it.
|
|
svl::crypto::SigningContext aSigningContext;
|
|
aSigningContext.m_xCertificate = std::move(xCertificate);
|
|
svl::crypto::Signing signing(aSigningContext);
|
|
signing.AddDataRange(utf8Text.getStr(), utf8Text.getLength());
|
|
OStringBuffer sigBuf;
|
|
if (!signing.Sign(sigBuf))
|
|
return;
|
|
|
|
const OUString signature = OStringToOUString(sigBuf, RTL_TEXTENCODING_UTF8, 0);
|
|
|
|
auto it = std::find_if(aProperties.begin(), aProperties.end(), [](const beans::PropertyValue& rValue)
|
|
{
|
|
return rValue.Name == "Usage";
|
|
});
|
|
|
|
OUString aUsage;
|
|
if (it != aProperties.end())
|
|
it->Value >>= aUsage;
|
|
|
|
// 4. Add metadata
|
|
// Prevent validation since this will trigger a premature validation
|
|
// upon inserting, but before setting the metadata.
|
|
const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
|
|
comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
|
|
SetParagraphSignatureValidation(bOldValidationFlag);
|
|
});
|
|
|
|
rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
|
|
|
|
const rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
|
|
uno::Reference<css::text::XTextField> xField = lcl_InsertParagraphSignature(xModel, xParagraph, signature, aUsage);
|
|
|
|
lcl_UpdateParagraphSignatureField(*GetDoc(), xModel, xParagraph, xField, utf8Text);
|
|
|
|
rDoc.GetIDocumentUndoRedo().AppendUndo(
|
|
std::make_unique<SwUndoParagraphSigning>(rDoc, xField, xParagraph, true));
|
|
|
|
rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
|
|
}
|
|
|
|
void SwEditShell::ValidateParagraphSignatures(SwTextNode* pNode, bool updateDontRemove)
|
|
{
|
|
if (!pNode || !IsParagraphSignatureValidationEnabled())
|
|
return;
|
|
|
|
// Table text signing is not supported.
|
|
if (pNode->FindTableNode() != nullptr)
|
|
return;
|
|
|
|
// Prevent recursive validation since this is triggered on node updates, which we do below.
|
|
const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
|
|
comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
|
|
SetParagraphSignatureValidation(bOldValidationFlag);
|
|
});
|
|
|
|
SwDocShell* pDocShell = GetDoc()->GetDocShell();
|
|
if (!pDocShell)
|
|
return;
|
|
|
|
uno::Sequence<uno::Reference<css::rdf::XURI>> aGraphNames = SwRDFHelper::getGraphNames(pDocShell->GetBaseModel(), MetaNS);
|
|
rtl::Reference<SwXParagraph> xParentText = SwXParagraph::CreateXParagraph(*GetDoc(), pNode, nullptr);
|
|
lcl_ValidateParagraphSignatures(*GetDoc(), xParentText, updateDontRemove, aGraphNames);
|
|
}
|
|
|
|
void SwEditShell::ValidateCurrentParagraphSignatures(bool updateDontRemove)
|
|
{
|
|
SwDocShell* pDocShell = GetDoc()->GetDocShell();
|
|
if (!pDocShell || !GetCursor() || !GetCursor()->Start() || !IsParagraphSignatureValidationEnabled())
|
|
return;
|
|
|
|
SwPaM* pPaM = GetCursor();
|
|
const SwPosition* pPosStart = pPaM->Start();
|
|
SwTextNode* pNode = pPosStart->GetNode().GetTextNode();
|
|
ValidateParagraphSignatures(pNode, updateDontRemove);
|
|
}
|
|
|
|
void SwEditShell::ValidateAllParagraphSignatures(bool updateDontRemove)
|
|
{
|
|
SwDocShell* pDocShell = GetDoc()->GetDocShell();
|
|
if (!pDocShell || !IsParagraphSignatureValidationEnabled())
|
|
return;
|
|
|
|
// Prevent recursive validation since this is triggered on node updates, which we do below.
|
|
const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
|
|
comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
|
|
SetParagraphSignatureValidation(bOldValidationFlag);
|
|
});
|
|
|
|
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
|
|
uno::Reference<text::XText> xParent = xModel->getText();
|
|
uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xParent, uno::UNO_QUERY);
|
|
if (!xParagraphEnumerationAccess.is())
|
|
return;
|
|
uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
|
|
if (!xParagraphs.is())
|
|
return;
|
|
uno::Sequence<uno::Reference<css::rdf::XURI>> aGraphNames = SwRDFHelper::getGraphNames(pDocShell->GetBaseModel(), MetaNS);
|
|
while (xParagraphs->hasMoreElements())
|
|
{
|
|
uno::Reference<text::XTextContent> xParagraph(xParagraphs->nextElement(), uno::UNO_QUERY);
|
|
lcl_ValidateParagraphSignatures(*GetDoc(), xParagraph, updateDontRemove, aGraphNames);
|
|
}
|
|
}
|
|
|
|
static uno::Reference<text::XTextField> lcl_GetParagraphMetadataFieldAtIndex(const SwDocShell* pDocSh, SwTextNode const * pNode, const sal_uLong index)
|
|
{
|
|
uno::Reference<text::XTextField> xTextField;
|
|
if (pNode != nullptr && pDocSh != nullptr)
|
|
{
|
|
SwTextAttr* pAttr = pNode->GetTextAttrAt(index, RES_TXTATR_METAFIELD);
|
|
SwTextMeta* pTextMeta = static_txtattr_cast<SwTextMeta*>(pAttr);
|
|
if (pTextMeta != nullptr)
|
|
{
|
|
SwFormatMeta& rFormatMeta(static_cast<SwFormatMeta&>(pTextMeta->GetAttr()));
|
|
if (::sw::Meta* pMeta = rFormatMeta.GetMeta())
|
|
{
|
|
const css::uno::Reference<css::rdf::XResource> xSubject = pMeta->MakeUnoObject();
|
|
rtl::Reference<SwXTextDocument> xModel = pDocSh->GetBaseModel();
|
|
const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xSubject);
|
|
if (aStatements.find(ParagraphSignatureIdRDFName) != aStatements.end() ||
|
|
aStatements.find(ParagraphClassificationNameRDFName) != aStatements.end())
|
|
{
|
|
xTextField = uno::Reference<text::XTextField>(xSubject, uno::UNO_QUERY);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return xTextField;
|
|
}
|
|
|
|
void SwEditShell::RestoreMetadataFieldsAndValidateParagraphSignatures()
|
|
{
|
|
SwDocShell* pDocShell = GetDoc()->GetDocShell();
|
|
if (!pDocShell || !IsParagraphSignatureValidationEnabled())
|
|
return;
|
|
|
|
// Prevent recursive validation since this is triggered on node updates, which we do below.
|
|
const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
|
|
comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
|
|
SetParagraphSignatureValidation(bOldValidationFlag);
|
|
});
|
|
|
|
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
|
|
rtl::Reference<SwXBodyText> xBodyText = xModel->getBodyText();
|
|
if (!xBodyText.is())
|
|
return;
|
|
rtl::Reference<SwXParagraphEnumeration> xParagraphs = xBodyText->createParagraphEnumeration();
|
|
|
|
static constexpr OUString sBlank(u""_ustr);
|
|
const sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
|
|
uno::Sequence<uno::Reference<css::rdf::XURI>> aGraphNames = SwRDFHelper::getGraphNames(xModel, MetaNS);
|
|
while (xParagraphs->hasMoreElements())
|
|
{
|
|
uno::Reference<text::XTextContent> xParaOrTable(xParagraphs->nextElement(), uno::UNO_QUERY);
|
|
rtl::Reference<SwXParagraph> xParagraph(dynamic_cast<SwXParagraph*>(xParaOrTable.get()));
|
|
|
|
try
|
|
{
|
|
const css::uno::Reference<css::rdf::XResource> xSubject(xParagraph);
|
|
std::map<OUString, OUString> aParagraphStatements = SwRDFHelper::getStatements(xModel, aGraphNames, xSubject);
|
|
auto it = aParagraphStatements.find(ParagraphClassificationFieldNamesRDFName);
|
|
const OUString sFieldNames = (it != aParagraphStatements.end()) ? it->second : OUString();
|
|
|
|
std::vector<svx::ClassificationResult> aResults;
|
|
if (!sFieldNames.isEmpty())
|
|
{
|
|
// Order the fields
|
|
sal_Int32 nIndex = 0;
|
|
do
|
|
{
|
|
const OUString sCurFieldName = sFieldNames.getToken(0, '/', nIndex);
|
|
if (sCurFieldName.isEmpty())
|
|
break;
|
|
|
|
OUString sName;
|
|
OUString sValue;
|
|
it = aParagraphStatements.find(sCurFieldName);
|
|
if (it != aParagraphStatements.end())
|
|
{
|
|
sName = it->first;
|
|
sValue = it->second;
|
|
}
|
|
|
|
if (aKeyCreator.isMarkingTextKey(sName))
|
|
{
|
|
aResults.emplace_back(svx::ClassificationType::TEXT, sValue, sValue, sBlank);
|
|
}
|
|
else if (aKeyCreator.isCategoryNameKey(sName))
|
|
{
|
|
const std::pair<OUString, OUString> pairAbbr = lcl_getRDF(xModel, xSubject, ParagraphClassificationAbbrRDFName);
|
|
const OUString sAbbreviatedName = (!pairAbbr.second.isEmpty() ? pairAbbr.second : sValue);
|
|
aResults.emplace_back(svx::ClassificationType::CATEGORY, sValue, sAbbreviatedName, sBlank);
|
|
}
|
|
else if (aKeyCreator.isCategoryIdentifierKey(sName))
|
|
{
|
|
const std::pair<OUString, OUString> pairAbbr = lcl_getRDF(xModel, xSubject, ParagraphClassificationAbbrRDFName);
|
|
const OUString sAbbreviatedName = (!pairAbbr.second.isEmpty() ? pairAbbr.second : sValue);
|
|
aResults.emplace_back(svx::ClassificationType::CATEGORY, sBlank, sAbbreviatedName, sValue);
|
|
}
|
|
else if (aKeyCreator.isMarkingKey(sName))
|
|
{
|
|
aResults.emplace_back(svx::ClassificationType::MARKING, sValue, sValue, sBlank);
|
|
}
|
|
else if (aKeyCreator.isIntellectualPropertyPartKey(sName))
|
|
{
|
|
aResults.emplace_back(svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, sValue, sValue, sBlank);
|
|
}
|
|
}
|
|
while (nIndex >= 0);
|
|
}
|
|
|
|
// Update classification based on results.
|
|
lcl_ApplyParagraphClassification(GetDoc(), xModel, xParagraph, xSubject, std::move(aResults));
|
|
|
|
// Get Signatures
|
|
std::map<OUString, SignatureDescr> aSignatures;
|
|
for (const auto& pair : aParagraphStatements)
|
|
{
|
|
const OUString& sName = pair.first;
|
|
if (sName.startsWith(ParagraphSignatureRDFNamespace))
|
|
{
|
|
const OUString sSuffix = sName.copy(ParagraphSignatureRDFNamespace.getLength());
|
|
const sal_Int32 index = sSuffix.indexOf(":");
|
|
if (index >= 0)
|
|
{
|
|
const OUString id = sSuffix.copy(0, index);
|
|
const OUString type = sSuffix.copy(index);
|
|
const OUString& sValue = pair.second;
|
|
if (type == ParagraphSignatureDateRDFName)
|
|
aSignatures[id].msDate = sValue;
|
|
else if (type == ParagraphSignatureUsageRDFName)
|
|
aSignatures[id].msUsage = sValue;
|
|
else if (type == ParagraphSignatureDigestRDFName)
|
|
aSignatures[id].msSignature = sValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const auto& pair : aSignatures)
|
|
{
|
|
uno::Reference<text::XTextField> xField = lcl_findFieldByRDF(xModel, xParagraph, ParagraphSignatureIdRDFName, pair.first);
|
|
if (!xField.is())
|
|
{
|
|
xField = uno::Reference<text::XTextField>(xModel->createInstance(MetadataFieldServiceName), uno::UNO_QUERY);
|
|
|
|
// Add the signature at the end.
|
|
xField->attach(xParagraph->getAnchor()->getEnd());
|
|
|
|
const css::uno::Reference<css::rdf::XResource> xFieldSubject(xField, uno::UNO_QUERY);
|
|
SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphSignatureIdRDFName, pair.first);
|
|
|
|
const OString utf8Text = lcl_getParagraphBodyText(xParagraph);
|
|
lcl_UpdateParagraphSignatureField(*GetDoc(), xModel, xParagraph, xField, utf8Text);
|
|
}
|
|
}
|
|
|
|
lcl_ValidateParagraphSignatures(*GetDoc(), xParagraph, true, aGraphNames); // Validate and Update signatures.
|
|
}
|
|
catch (const std::exception&)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SwEditShell::IsCursorInParagraphMetadataField() const
|
|
{
|
|
if (GetCursor() && GetCursor()->Start())
|
|
{
|
|
SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode();
|
|
const sal_uLong index = GetCursor()->Start()->GetContentIndex();
|
|
uno::Reference<text::XTextField> xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index);
|
|
return xField.is();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SwEditShell::RemoveParagraphMetadataFieldAtCursor()
|
|
{
|
|
if (GetCursor() && GetCursor()->Start())
|
|
{
|
|
SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode();
|
|
sal_uLong index = GetCursor()->Start()->GetContentIndex();
|
|
uno::Reference<text::XTextField> xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index);
|
|
if (!xField.is())
|
|
{
|
|
// Try moving the cursor to see if we're _facing_ a metafield or not,
|
|
// as opposed to being within one.
|
|
index--; // Backspace moves left
|
|
|
|
xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index);
|
|
}
|
|
|
|
if (xField.is())
|
|
{
|
|
lcl_RemoveParagraphMetadataField(xField);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static OUString lcl_GetParagraphClassification(SfxClassificationHelper & rHelper, sfx::ClassificationKeyCreator const & rKeyCreator,
|
|
const rtl::Reference<SwXTextDocument>& xModel, const rtl::Reference<SwXParagraph>& xParagraph)
|
|
{
|
|
uno::Reference<text::XTextField> xTextField;
|
|
xTextField = lcl_FindParagraphClassificationField(xModel, xParagraph, rKeyCreator.makeCategoryIdentifierKey());
|
|
if (xTextField.is())
|
|
{
|
|
const std::pair<OUString, OUString> rdfValuePair = lcl_getRDF(xModel, xTextField, ParagraphClassificationValueRDFName);
|
|
return rHelper.GetBACNameForIdentifier(rdfValuePair.second);
|
|
}
|
|
|
|
xTextField = lcl_FindParagraphClassificationField(xModel, xParagraph, rKeyCreator.makeCategoryNameKey());
|
|
if (xTextField.is())
|
|
{
|
|
return lcl_getRDF(xModel, xTextField, ParagraphClassificationNameRDFName).second;
|
|
}
|
|
|
|
return OUString();
|
|
}
|
|
|
|
static OUString lcl_GetHighestClassificationParagraphClass(SwPaM* pCursor)
|
|
{
|
|
OUString sHighestClass;
|
|
|
|
SwTextNode* pNode = pCursor->Start()->GetNode().GetTextNode();
|
|
if (pNode == nullptr)
|
|
return sHighestClass;
|
|
|
|
SwDocShell* pDocShell = pNode->GetDoc().GetDocShell();
|
|
if (!pDocShell)
|
|
return sHighestClass;
|
|
|
|
SfxClassificationHelper aHelper(pDocShell->getDocProperties());
|
|
sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
|
|
|
|
rtl::Reference<SwXTextDocument> xModel = pDocShell->GetBaseModel();
|
|
rtl::Reference<SwXBodyText> xBodyText = xModel->getBodyText();
|
|
|
|
rtl::Reference<SwXParagraphEnumeration> xParagraphs = xBodyText->createParagraphEnumeration();
|
|
while (xParagraphs->hasMoreElements())
|
|
{
|
|
uno::Reference<text::XTextContent> xParaOrTable(xParagraphs->nextElement(), uno::UNO_QUERY);
|
|
rtl::Reference<SwXParagraph> xParagraph(dynamic_cast<SwXParagraph*>(xParaOrTable.get()));
|
|
const OUString sCurrentClass = lcl_GetParagraphClassification(aHelper, aKeyCreator, xModel, xParagraph);
|
|
sHighestClass = aHelper.GetHigherClass(sHighestClass, sCurrentClass);
|
|
}
|
|
|
|
return sHighestClass;
|
|
}
|
|
|
|
void SwEditShell::ClassifyDocPerHighestParagraphClass()
|
|
{
|
|
SwDocShell* pDocShell = GetDoc()->GetDocShell();
|
|
if (!pDocShell)
|
|
return;
|
|
|
|
// Bail out as early as possible if we don't have paragraph classification.
|
|
if (!SwRDFHelper::hasMetadataGraph(pDocShell->GetBaseModel(), MetaNS))
|
|
return;
|
|
|
|
uno::Reference<document::XDocumentProperties> xDocumentProperties = pDocShell->getDocProperties();
|
|
uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();
|
|
|
|
sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
|
|
SfxClassificationHelper aHelper(xDocumentProperties);
|
|
|
|
OUString sHighestClass = lcl_GetHighestClassificationParagraphClass(GetCursor());
|
|
|
|
const OUString aClassificationCategory = svx::classification::getProperty(xPropertyContainer, aKeyCreator.makeCategoryNameKey());
|
|
|
|
if (!aClassificationCategory.isEmpty())
|
|
{
|
|
sHighestClass = aHelper.GetHigherClass(sHighestClass, aClassificationCategory);
|
|
}
|
|
|
|
if (aClassificationCategory != sHighestClass)
|
|
{
|
|
std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr,
|
|
VclMessageType::Question, VclButtonsType::Ok,
|
|
SwResId(STR_CLASSIFICATION_LEVEL_CHANGED)));
|
|
xQueryBox->run();
|
|
}
|
|
|
|
const SfxClassificationPolicyType eHighestClassType = SfxClassificationHelper::stringToPolicyType(sHighestClass);
|
|
|
|
// Prevent paragraph signature validation since the below changes (f.e. watermarking) are benign.
|
|
const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
|
|
comphelper::ScopeGuard const g([this, bOldValidationFlag]() {
|
|
SetParagraphSignatureValidation(bOldValidationFlag);
|
|
});
|
|
|
|
// Check the origin, if "manual" (created via advanced classification dialog),
|
|
// then we just need to set the category name.
|
|
if (sfx::getCreationOriginProperty(xPropertyContainer, aKeyCreator) == sfx::ClassificationCreationOrigin::MANUAL)
|
|
{
|
|
aHelper.SetBACName(sHighestClass, eHighestClassType);
|
|
ApplyAdvancedClassification(CollectAdvancedClassification());
|
|
}
|
|
else
|
|
{
|
|
SetClassification(sHighestClass, eHighestClassType);
|
|
}
|
|
}
|
|
|
|
// #i62675#
|
|
void SwEditShell::SetTextFormatColl(SwTextFormatColl *pFormat,
|
|
const bool bResetListAttrs,
|
|
SetAttrMode nMode)
|
|
{
|
|
SwTextFormatColl *pLocal = pFormat? pFormat: (*GetDoc()->GetTextFormatColls())[0];
|
|
StartAllAction();
|
|
|
|
SwRewriter aRewriter;
|
|
|
|
aRewriter.AddRule(UndoArg1, pLocal->GetName());
|
|
|
|
GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::SETFMTCOLL, &aRewriter);
|
|
for(SwPaM& rPaM : GetCursor()->GetRingContainer())
|
|
{
|
|
if (!rPaM.HasReadonlySel( GetViewOptions()->IsFormView(), true))
|
|
{
|
|
// store previous paragraph style for track changes
|
|
OUString sParaStyleName;
|
|
sal_uInt16 nPoolId = USHRT_MAX;
|
|
SwContentNode * pCnt = rPaM.Start()->GetNode().GetContentNode();
|
|
if ( pCnt && pCnt->GetTextNode() && GetDoc()->getIDocumentRedlineAccess().IsRedlineOn() )
|
|
{
|
|
const SwTextFormatColl* pTextFormatColl = pCnt->GetTextNode()->GetTextColl();
|
|
sal_uInt16 nStylePoolId = pTextFormatColl->GetPoolFormatId();
|
|
// default paragraph style
|
|
if ( nStylePoolId == RES_POOLCOLL_STANDARD )
|
|
nPoolId = nStylePoolId;
|
|
else
|
|
sParaStyleName = pTextFormatColl->GetName();
|
|
}
|
|
|
|
// Change the paragraph style to pLocal and remove all direct paragraph formatting.
|
|
GetDoc()->SetTextFormatColl(rPaM, pLocal, true, bResetListAttrs, !!(nMode & SetAttrMode::REMOVE_ALL_ATTR), GetLayout());
|
|
|
|
// If there are hints on the nodes which cover the whole node, then remove those, too.
|
|
SwPaM aPaM(*rPaM.Start(), *rPaM.End());
|
|
if (SwTextNode* pEndTextNode = aPaM.End()->GetNode().GetTextNode())
|
|
{
|
|
aPaM.Start()->SetContent(0);
|
|
aPaM.End()->SetContent(pEndTextNode->GetText().getLength());
|
|
}
|
|
GetDoc()->RstTextAttrs(aPaM, /*bInclRefToxMark=*/false, /*bExactRange=*/true, GetLayout());
|
|
|
|
// add redline tracking the previous paragraph style
|
|
if ( GetDoc()->getIDocumentRedlineAccess().IsRedlineOn() &&
|
|
// multi-paragraph ParagraphFormat redline ranges
|
|
// haven't supported by AppendRedline(), yet
|
|
// TODO handle multi-paragraph selections, too,
|
|
// e.g. by breaking them to single paragraphs
|
|
aPaM.Start()->GetNode() == aPaM.End()->GetNode() )
|
|
{
|
|
SwRangeRedline * pRedline = new SwRangeRedline( RedlineType::ParagraphFormat, aPaM );
|
|
auto const result(GetDoc()->getIDocumentRedlineAccess().AppendRedline( pRedline, true));
|
|
// store original paragraph style to reject formatting change
|
|
if ( IDocumentRedlineAccess::AppendResult::IGNORED != result &&
|
|
( nPoolId == RES_POOLCOLL_STANDARD || !sParaStyleName.isEmpty() ) )
|
|
{
|
|
std::unique_ptr<SwRedlineExtraData_FormatColl> xExtra;
|
|
xExtra.reset(new SwRedlineExtraData_FormatColl(sParaStyleName, nPoolId, nullptr));
|
|
pRedline->SetExtraData( xExtra.get() );
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::SETFMTCOLL, &aRewriter);
|
|
EndAllAction();
|
|
}
|
|
|
|
SwTextFormatColl* SwEditShell::MakeTextFormatColl(const OUString& rFormatCollName,
|
|
SwTextFormatColl* pParent)
|
|
{
|
|
SwTextFormatColl *pColl;
|
|
if ( pParent == nullptr )
|
|
pParent = &GetTextFormatColl(0);
|
|
pColl = GetDoc()->MakeTextFormatColl(rFormatCollName, pParent);
|
|
if ( pColl == nullptr )
|
|
{
|
|
OSL_FAIL( "MakeTextFormatColl failed" );
|
|
}
|
|
return pColl;
|
|
|
|
}
|
|
|
|
void SwEditShell::FillByEx(SwTextFormatColl* pColl)
|
|
{
|
|
SwPaM * pCursor = GetCursor();
|
|
SwContentNode * pCnt = pCursor->GetPointContentNode();
|
|
if (pCnt->IsTextNode()) // uhm... what nonsense would happen if not?
|
|
{ // only need properties-node because BREAK/PAGEDESC filtered anyway!
|
|
pCnt = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode());
|
|
}
|
|
const SfxItemSet* pSet = pCnt->GetpSwAttrSet();
|
|
if( !pSet )
|
|
return;
|
|
|
|
// JP 05.10.98: Special treatment if one of the attributes Break/PageDesc/NumRule(auto) is
|
|
// in the ItemSet. Otherwise there will be too much or wrong processing (NumRules!)
|
|
// Bug 57568
|
|
|
|
// Do NOT copy AutoNumRules into the template
|
|
const SwNumRuleItem* pItem;
|
|
const SwNumRule* pRule = nullptr;
|
|
if (SfxItemState::SET == pSet->GetItemState(RES_BREAK, false)
|
|
|| SfxItemState::SET == pSet->GetItemState(RES_PAGEDESC, false)
|
|
|| ((pItem = pSet->GetItemIfSet(RES_PARATR_NUMRULE, false))
|
|
&& nullptr != (pRule = GetDoc()->FindNumRulePtr(pItem->GetValue()))
|
|
&& pRule->IsAutoRule()))
|
|
{
|
|
SfxItemSet aSet( *pSet );
|
|
aSet.ClearItem( RES_BREAK );
|
|
aSet.ClearItem( RES_PAGEDESC );
|
|
|
|
if (pRule
|
|
|| ((pItem = pSet->GetItemIfSet(RES_PARATR_NUMRULE, false))
|
|
&& nullptr != (pRule = GetDoc()->FindNumRulePtr(pItem->GetValue()))
|
|
&& pRule->IsAutoRule()))
|
|
aSet.ClearItem( RES_PARATR_NUMRULE );
|
|
|
|
if( aSet.Count() )
|
|
GetDoc()->ChgFormat(*pColl, aSet );
|
|
}
|
|
else
|
|
GetDoc()->ChgFormat(*pColl, *pSet );
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|