diff options
author | Vojtěch Doležal <dolezvo1@cvut.cz> | 2023-02-27 08:52:35 +0100 |
---|---|---|
committer | Miklos Vajna <vmiklos@collabora.com> | 2023-03-07 07:12:31 +0000 |
commit | 7b99871635cd48c2a8a1d0afbd7afc60a45cc2ff (patch) | |
tree | d2238324dac2096656a5b682bcedd288be5a5787 | |
parent | cc45d748e241ea96fd0c81154e3dd49f9fc58357 (diff) |
tdf#153396 - Bibliography marks improvements
Adds option to separate function of "URL" into (listed) "URL" and "Target URL" to allow for more flexibility (in that case if target URL is empty, bibliography mark hyperlink leads to bibliography table row if possible)
When writing tests also found and fixed bug where exporting new file with anchor link bibliography mark crashes LO.
Change-Id: Ic1b5c8c9590c0338dcfc4fa3a981142bddae0113
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/147868
Tested-by: Jenkins
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
-rw-r--r-- | include/xmloff/xmltoken.hxx | 2 | ||||
-rw-r--r-- | sw/inc/authfld.hxx | 5 | ||||
-rw-r--r-- | sw/inc/helpids.h | 2 | ||||
-rw-r--r-- | sw/inc/strings.hrc | 2 | ||||
-rw-r--r-- | sw/inc/toxe.hxx | 2 | ||||
-rw-r--r-- | sw/qa/core/text/text.cxx | 154 | ||||
-rw-r--r-- | sw/source/core/fields/authfld.cxx | 30 | ||||
-rw-r--r-- | sw/source/core/fields/fldbas.cxx | 10 | ||||
-rw-r--r-- | sw/source/core/text/EnhancedPDFExportHelper.cxx | 149 | ||||
-rw-r--r-- | sw/source/ui/index/cnttab.cxx | 2 | ||||
-rw-r--r-- | sw/source/ui/index/swuiidxmrk.cxx | 34 | ||||
-rw-r--r-- | sw/source/uibase/docvw/edtwin2.cxx | 10 | ||||
-rw-r--r-- | sw/source/uibase/shells/textsh1.cxx | 14 | ||||
-rw-r--r-- | sw/source/uibase/utlui/initui.cxx | 2 | ||||
-rw-r--r-- | sw/source/uibase/wrtsh/wrtsh2.cxx | 74 | ||||
-rw-r--r-- | sw/uiconfig/swriter/ui/bibliofragment.ui | 24 | ||||
-rw-r--r-- | vcl/source/gdi/pdfwriter_impl.cxx | 7 | ||||
-rw-r--r-- | xmloff/source/core/xmltoken.cxx | 2 | ||||
-rw-r--r-- | xmloff/source/text/txtflde.cxx | 10 | ||||
-rw-r--r-- | xmloff/source/text/txtfldi.cxx | 8 | ||||
-rw-r--r-- | xmloff/source/token/tokens.txt | 2 |
21 files changed, 495 insertions, 50 deletions
diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx index 7c6cea0482d3..5262f3b3ab5e 100644 --- a/include/xmloff/xmltoken.hxx +++ b/include/xmloff/xmltoken.hxx @@ -3465,6 +3465,8 @@ namespace xmloff::token { XML_MARGIN_GUTTER, XML_LOCAL_URL, + XML_TARGET_URL, + XML_USE_TARGET_URL, // Math ml XML_DIR, diff --git a/sw/inc/authfld.hxx b/sw/inc/authfld.hxx index def78f913e14..26e4843a6ad2 100644 --- a/sw/inc/authfld.hxx +++ b/sw/inc/authfld.hxx @@ -199,8 +199,13 @@ public: OUString GetAuthority(const SwRootFrame *pLayout, const SwForm *pTOX = nullptr) const; + bool UseTargetURL() const; + bool HasURL() const; OUString GetAbsoluteURL() const; + + bool HasTargetURL() const; + OUString GetAbsoluteTargetURL() const; /** * Returns full URI for the URL, relative if specified * \param bRelative whether the path should be relative (when dealing with local files) diff --git a/sw/inc/helpids.h b/sw/inc/helpids.h index 65c49e22f311..78f52fd436ea 100644 --- a/sw/inc/helpids.h +++ b/sw/inc/helpids.h @@ -76,6 +76,8 @@ inline constexpr OStringLiteral HID_EDIT_FORMULA = "SW_HID_EDIT_FORMULA"; #define HID_AUTH_FIELD_CUSTOM5 "SW_HID_AUTH_FIELD_CUSTOM5" #define HID_AUTH_FIELD_ISBN "SW_HID_AUTH_FIELD_ISBN" #define HID_AUTH_FIELD_LOCAL_URL "SW_HID_AUTH_FIELD_LOCAL_URL" +#define HID_AUTH_FIELD_TARGET_URL "SW_HID_AUTH_FIELD_TARGET_URL" +#define HID_AUTH_FIELD_USE_TARGET_URL "SW_HID_AUTH_FIELD_USE_TARGET_URL" inline constexpr OStringLiteral HID_BUSINESS_FMT_PAGE = "SW_HID_BUSINESS_FMT_PAGE"; inline constexpr OStringLiteral HID_BUSINESS_FMT_PAGE_CONT = "SW_HID_BUSINESS_FMT_PAGE_CONT"; diff --git a/sw/inc/strings.hrc b/sw/inc/strings.hrc index 96e58d395164..8af6eef01a4d 100644 --- a/sw/inc/strings.hrc +++ b/sw/inc/strings.hrc @@ -816,6 +816,8 @@ #define STR_AUTH_FIELD_CUSTOM5 NC_("STR_AUTH_FIELD_CUSTOM5", "User-defined5") #define STR_AUTH_FIELD_ISBN NC_("STR_AUTH_FIELD_ISBN", "ISBN") #define STR_AUTH_FIELD_LOCAL_URL NC_("STR_AUTH_FIELD_LOCAL_URL", "Local copy") +#define STR_AUTH_FIELD_TARGET_URL NC_("STR_AUTH_FIELD_TARGET_URL", "Target URL") +#define STR_AUTH_FIELD_USE_TARGET_URL NC_("STR_AUTH_FIELD_USE_TARGET_URL", "Use Target URL as hyperlink") #define STR_IDXMRK_EDIT NC_("STR_IDXMRK_EDIT", "Edit Index Entry") #define STR_IDXMRK_INSERT NC_("STR_IDXMRK_INSERT", "Insert Index Entry") diff --git a/sw/inc/toxe.hxx b/sw/inc/toxe.hxx index b2f08e66c320..05084c84c2e8 100644 --- a/sw/inc/toxe.hxx +++ b/sw/inc/toxe.hxx @@ -115,6 +115,8 @@ enum ToxAuthorityField AUTH_FIELD_CUSTOM5, AUTH_FIELD_ISBN, AUTH_FIELD_LOCAL_URL, + AUTH_FIELD_TARGET_URL, + AUTH_FIELD_USE_TARGET_URL, AUTH_FIELD_END }; diff --git a/sw/qa/core/text/text.cxx b/sw/qa/core/text/text.cxx index d16e7b324656..7ace4df111c6 100644 --- a/sw/qa/core/text/text.cxx +++ b/sw/qa/core/text/text.cxx @@ -12,7 +12,9 @@ #include <memory> #include <com/sun/star/text/BibliographyDataType.hpp> +#include <com/sun/star/text/ControlCharacter.hpp> #include <com/sun/star/text/WritingMode2.hpp> +#include <com/sun/star/text/XDocumentIndex.hpp> #include <vcl/gdimtf.hxx> #include <vcl/filter/PDFiumLibrary.hxx> @@ -135,6 +137,158 @@ CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testBibliographyUrlPdfExport) CPPUNIT_ASSERT(pPdfPage->hasLinks()); } +CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testBibliographyUrlPdfExport2) +{ + // Given a document with a bibliography entry field: + std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get(); + if (!pPDFium) + { + return; + } + createSwDoc(); + uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xField( + xFactory->createInstance("com.sun.star.text.TextField.Bibliography"), uno::UNO_QUERY); + uno::Sequence<beans::PropertyValue> aFields = { + comphelper::makePropertyValue("BibiliographicType", text::BibliographyDataType::WWW), + comphelper::makePropertyValue("Identifier", OUString("AT")), + comphelper::makePropertyValue("Author", OUString("Author")), + comphelper::makePropertyValue("Title", OUString("Title")), + comphelper::makePropertyValue("URL", OUString("#page=1")), + }; + xField->setPropertyValue("Fields", uno::Any(aFields)); + uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XText> xText = xTextDocument->getText(); + uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor(); + uno::Reference<text::XTextContent> xContent(xField, uno::UNO_QUERY); + xText->insertTextContent(xCursor, xContent, /*bAbsorb=*/false); + + // When exporting to PDF: + save("writer_pdf_Export"); + + // Then make sure the field links when the Target URL is set + // (this test is important, isn't the same as the one above) + std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport(); + std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); + CPPUNIT_ASSERT(pPdfPage->hasLinks()); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testBibliographyUrlPdfExport3) +{ + // Given a document with a bibliography entry field: + std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get(); + if (!pPDFium) + { + return; + } + createSwDoc(); + uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xField( + xFactory->createInstance("com.sun.star.text.TextField.Bibliography"), uno::UNO_QUERY); + uno::Sequence<beans::PropertyValue> aFields = { + comphelper::makePropertyValue("BibiliographicType", text::BibliographyDataType::WWW), + comphelper::makePropertyValue("Identifier", OUString("AT")), + comphelper::makePropertyValue("Author", OUString("Author")), + comphelper::makePropertyValue("Title", OUString("Title")), + comphelper::makePropertyValue("TargetURL", OUString("#page=1")), + }; + xField->setPropertyValue("Fields", uno::Any(aFields)); + uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XText> xText = xTextDocument->getText(); + uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor(); + uno::Reference<text::XTextContent> xContent(xField, uno::UNO_QUERY); + xText->insertTextContent(xCursor, xContent, /*bAbsorb=*/false); + + // When exporting to PDF: + save("writer_pdf_Export"); + + // Then make sure there are no links since UseTargetURL is not set + std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport(); + std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); + CPPUNIT_ASSERT(!pPdfPage->hasLinks()); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testBibliographyUrlPdfExport4) +{ + // Given a document with a bibliography entry field: + std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get(); + if (!pPDFium) + { + return; + } + createSwDoc(); + uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xField( + xFactory->createInstance("com.sun.star.text.TextField.Bibliography"), uno::UNO_QUERY); + uno::Sequence<beans::PropertyValue> aFields = { + comphelper::makePropertyValue("BibiliographicType", text::BibliographyDataType::WWW), + comphelper::makePropertyValue("Identifier", OUString("AT")), + comphelper::makePropertyValue("Author", OUString("Author")), + comphelper::makePropertyValue("Title", OUString("Title")), + comphelper::makePropertyValue("TargetURL", OUString("#page=1")), + comphelper::makePropertyValue("UseTargetURL", OUString("true")), + }; + xField->setPropertyValue("Fields", uno::Any(aFields)); + uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XText> xText = xTextDocument->getText(); + uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor(); + uno::Reference<text::XTextContent> xContent(xField, uno::UNO_QUERY); + xText->insertTextContent(xCursor, xContent, /*bAbsorb=*/false); + + // When exporting to PDF: + save("writer_pdf_Export"); + + // Then make sure the field links when the Target URL is set + std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport(); + std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); + CPPUNIT_ASSERT(pPdfPage->hasLinks()); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testBibliographyUrlPdfExport5) +{ + // Given a document with a bibliography entry field: + std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get(); + if (!pPDFium) + { + return; + } + createSwDoc(); + uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xField( + xFactory->createInstance("com.sun.star.text.TextField.Bibliography"), uno::UNO_QUERY); + uno::Sequence<beans::PropertyValue> aFields = { + comphelper::makePropertyValue("BibiliographicType", text::BibliographyDataType::WWW), + comphelper::makePropertyValue("Identifier", OUString("AT")), + comphelper::makePropertyValue("Author", OUString("Author")), + comphelper::makePropertyValue("Title", OUString("Title")), + comphelper::makePropertyValue("UseTargetURL", OUString("true")), + }; + xField->setPropertyValue("Fields", uno::Any(aFields)); + uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XText> xText = xTextDocument->getText(); + uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor(); + uno::Reference<text::XTextContent> xContent(xField, uno::UNO_QUERY); + xText->insertTextContent(xCursor, xContent, /*bAbsorb=*/false); + // Create a bibliography table. + uno::Reference<text::XTextContent> xTable( + xFactory->createInstance("com.sun.star.text.Bibliography"), uno::UNO_QUERY); + xCursor->gotoEnd(/*bExpand=*/false); + xText->insertControlCharacter(xCursor, text::ControlCharacter::APPEND_PARAGRAPH, + /*bAbsorb=*/false); + xText->insertTextContent(xCursor, xTable, /*bAbsorb=*/false); + // Update the table + uno::Reference<text::XDocumentIndex> xTableIndex(xTable, uno::UNO_QUERY); + xTableIndex->update(); + + // When exporting to PDF: + save("writer_pdf_Export"); + + // Then make sure the mark links to the table when table is present + std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport(); + std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0); + CPPUNIT_ASSERT(pPdfPage->hasLinks()); +} + CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testTabOverMarginSection) { createSwDoc("tabovermargin-section.fodt"); diff --git a/sw/source/core/fields/authfld.cxx b/sw/source/core/fields/authfld.cxx index 44d1e39dd3ae..d1d50506d741 100644 --- a/sw/source/core/fields/authfld.cxx +++ b/sw/source/core/fields/authfld.cxx @@ -634,6 +634,12 @@ OUString SwAuthorityField::GetAuthority(const SwRootFrame* pLayout, const SwForm return aText; } +bool SwAuthorityField::UseTargetURL() const +{ + const OUString& rValue = GetAuthEntry()->GetAuthorField(AUTH_FIELD_USE_TARGET_URL); + return rValue.toAsciiLowerCase() == "true"; +} + bool SwAuthorityField::HasURL() const { const OUString& rURL = GetAuthEntry()->GetAuthorField(AUTH_FIELD_URL); @@ -650,6 +656,22 @@ OUString SwAuthorityField::GetAbsoluteURL() const INetURLObject::DecodeMechanism::WithCharset); } +bool SwAuthorityField::HasTargetURL() const +{ + const OUString& rURL = GetAuthEntry()->GetAuthorField(AUTH_FIELD_TARGET_URL); + return !rURL.isEmpty(); +} + +OUString SwAuthorityField::GetAbsoluteTargetURL() const +{ + const OUString& rURL = GetAuthEntry()->GetAuthorField(AUTH_FIELD_TARGET_URL); + SwDoc* pDoc = static_cast<SwAuthorityFieldType*>(GetTyp())->GetDoc(); + SwDocShell* pDocShell = pDoc->GetDocShell(); + OUString aBasePath = pDocShell->getDocumentBaseURL(); + return INetURLObject::GetAbsURL(aBasePath, rURL, INetURLObject::EncodeMechanism::WasEncoded, + INetURLObject::DecodeMechanism::WithCharset); +} + OUString SwAuthorityField::GetURI(bool bRelative) const { OUString sTmp = GetFieldText(AUTH_FIELD_URL); @@ -751,7 +773,9 @@ const char* const aFieldNames[] = "Custom4", "Custom5", "ISBN", - "LocalURL" + "LocalURL", + "TargetURL", + "UseTargetURL" }; void SwAuthEntry::dumpAsXml(xmlTextWriterPtr pWriter) const @@ -808,8 +832,8 @@ bool SwAuthorityField::PutValue( const Any& rAny, sal_uInt16 /*nWhichId*/ ) if(!(rAny >>= aParam)) return false; - OUStringBuffer sBuf(+AUTH_FIELD_LOCAL_URL); - comphelper::string::padToLength(sBuf, AUTH_FIELD_LOCAL_URL, TOX_STYLE_DELIMITER); + OUStringBuffer sBuf(+(AUTH_FIELD_END - 1)); + comphelper::string::padToLength(sBuf, (AUTH_FIELD_END - 1), TOX_STYLE_DELIMITER); OUString sToSet(sBuf.makeStringAndClear()); for(const PropertyValue& rParam : std::as_const(aParam)) { diff --git a/sw/source/core/fields/fldbas.cxx b/sw/source/core/fields/fldbas.cxx index 71afe0d7e425..80ae487f93fe 100644 --- a/sw/source/core/fields/fldbas.cxx +++ b/sw/source/core/fields/fldbas.cxx @@ -423,7 +423,8 @@ bool SwField::HasClickHdl() const case SwFieldIds::GetRef: case SwFieldIds::Macro: case SwFieldIds::Input: - case SwFieldIds::Dropdown : + case SwFieldIds::Dropdown: + case SwFieldIds::TableOfAuthorities: bRet = true; break; @@ -431,13 +432,6 @@ bool SwField::HasClickHdl() const bRet = static_cast<const SwSetExpField*>(this)->GetInputFlag(); break; - case SwFieldIds::TableOfAuthorities: - { - const auto pAuthorityField = static_cast<const SwAuthorityField*>(this); - bRet = pAuthorityField->HasURL(); - break; - } - default: break; } return bRet; diff --git a/sw/source/core/text/EnhancedPDFExportHelper.cxx b/sw/source/core/text/EnhancedPDFExportHelper.cxx index 053b67990eda..1dde77106624 100644 --- a/sw/source/core/text/EnhancedPDFExportHelper.cxx +++ b/sw/source/core/text/EnhancedPDFExportHelper.cxx @@ -803,6 +803,13 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) bLanguage = true; break; + case vcl::PDFWriter::BibEntry : + bTextDecorationType = + bBaselineShift = + bLinkAttribute = + bLanguage = true; + break; + default: break; } @@ -2246,6 +2253,50 @@ void SwEnhancedPDFExportHelper::ExportAuthorityEntryLinks() return; } + // Create PDF destinations for bibliography table entries + std::vector<std::tuple<const SwTOXBase*, const OUString*, sal_Int32>> vDestinations; + // string is the row node text, sal_Int32 is number of the destination + // Note: This way of iterating doesn't seem to take into account TOXes + // that are in a frame, probably in some other cases too + { + mrSh.GotoPage(1); + while (mrSh.GotoNextTOXBase()) + { + const SwTOXBase* pIteratedTOX = nullptr; + while ((pIteratedTOX = mrSh.GetCurTOX()) != nullptr + && pIteratedTOX->GetType() == TOX_AUTHORITIES) + { + if (const SwNode& rCurrentNode = mrSh.GetCursor()->GetPoint()->GetNode(); + rCurrentNode.GetNodeType() == SwNodeType::Text) + { + if (mrSh.GetCursor()->GetPoint()->GetNode().FindSectionNode()->GetSection().GetType() + == SectionType::ToxContent) // this checks it's not a heading + { + // Destination Rectangle + const SwRect& rDestRect = mrSh.GetCharRect(); + + const SwPageFrame* pCurrPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Destination PageNum + const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect ); + + // Destination Export + if ( -1 != nDestPageNum ) + { + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); + const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + const OUString* vNodeText = &static_cast<const SwTextNode*>(&rCurrentNode)->GetText(); + vDestinations.emplace_back(pIteratedTOX, vNodeText, nDestId); + } + } + } + mrSh.MovePara(GoNextPara, fnParaStart); + } + } + } + + // Generate links to matching entries in the bibliography tables std::vector<SwFormatField*> aFields; SwFieldType* pType = mrSh.GetFieldType(SwFieldIds::TableOfAuthorities, OUString()); if (!pType) @@ -2264,38 +2315,90 @@ void SwEnhancedPDFExportHelper::ExportAuthorityEntryLinks() const auto& rAuthorityField = *static_cast<const SwAuthorityField*>(pFormatField->GetField()); - if (!rAuthorityField.HasURL()) - { - continue; - } - const OUString& rURL = rAuthorityField.GetAuthEntry()->GetAuthorField(AUTH_FIELD_URL); - const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode(); - if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField)) + if ((!rAuthorityField.UseTargetURL() && rAuthorityField.HasURL()) + || (rAuthorityField.UseTargetURL() && rAuthorityField.HasTargetURL())) { - continue; - } + // Since the conditions indicate usage of a URL link to it + const OUString& rURL = rAuthorityField.GetAuthEntry()->GetAuthorField( + rAuthorityField.UseTargetURL() ? AUTH_FIELD_TARGET_URL : AUTH_FIELD_URL); - OUString const content(rAuthorityField.ExpandField(true, mrSh.GetLayout())); + const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode(); + if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField)) + { + continue; + } - // Select the field. - mrSh.SwCursorShell::SetMark(); - mrSh.SwCursorShell::Right(1, SwCursorSkipMode::Chars); + OUString const content(rAuthorityField.ExpandField(true, mrSh.GetLayout())); - // Create the links. - for (const auto& rLinkRect : *mrSh.SwCursorShell::GetCursor_()) - { - for (const auto& rLinkPageNum : CalcOutputPageNums(rLinkRect)) + // Select the field. + mrSh.SwCursorShell::SetMark(); + mrSh.SwCursorShell::Right(1, SwCursorSkipMode::Chars); + + // Create the links. + for (const auto& rLinkRect : *mrSh.SwCursorShell::GetCursor_()) { - tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, rLinkRect.SVRect())); - sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, content, rLinkPageNum); - IdMapEntry aLinkEntry(rLinkRect, nLinkId); - s_aLinkIdMap.push_back(aLinkEntry); - pPDFExtOutDevData->SetLinkURL(nLinkId, rURL); + for (const auto& rLinkPageNum : CalcOutputPageNums(rLinkRect)) + { + tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, rLinkRect.SVRect())); + sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, content, rLinkPageNum); + IdMapEntry aLinkEntry(rLinkRect, nLinkId); + s_aLinkIdMap.push_back(aLinkEntry); + pPDFExtOutDevData->SetLinkURL(nLinkId, rURL); + } } + mrSh.SwCursorShell::ClearMark(); } + else if (rAuthorityField.UseTargetURL()) + { + // Since the bibliography mark doesn't have target URL, try linking to a bibliography table + sal_Int32 nDestId = -1; + + std::unordered_map<const SwTOXBase*, OUString> vFormattedFieldStrings; + for (const auto& rDestinationTuple : vDestinations) + { + if (vFormattedFieldStrings.find(std::get<0>(rDestinationTuple)) + == vFormattedFieldStrings.end()) + vFormattedFieldStrings.emplace(std::get<0>(rDestinationTuple), + rAuthorityField.GetAuthority(mrSh.GetLayout(), + &std::get<0>(rDestinationTuple)->GetTOXForm())); + + if (vFormattedFieldStrings.at(std::get<0>(rDestinationTuple)) == *std::get<1>(rDestinationTuple)) + { + nDestId = std::get<2>(rDestinationTuple); + break; + } + } + + if (nDestId == -1) + continue; + + const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode(); + if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField)) + { + continue; + } + + OUString const content(rAuthorityField.ExpandField(true, mrSh.GetLayout())); - mrSh.SwCursorShell::ClearMark(); + // Select the field. + mrSh.SwCursorShell::SetMark(); + mrSh.SwCursorShell::Right(1, SwCursorSkipMode::Chars); + + // Create the links. + for (const auto& rLinkRect : *mrSh.SwCursorShell::GetCursor_()) + { + for (const auto& rLinkPageNum : CalcOutputPageNums(rLinkRect)) + { + tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, rLinkRect.SVRect())); + sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, content, rLinkPageNum); + IdMapEntry aLinkEntry(rLinkRect, nLinkId); + s_aLinkIdMap.push_back(aLinkEntry); + pPDFExtOutDevData->SetLinkDest(nLinkId, nDestId); + } + } + mrSh.SwCursorShell::ClearMark(); + } } } diff --git a/sw/source/ui/index/cnttab.cxx b/sw/source/ui/index/cnttab.cxx index 9955a85cd3d3..ce234a14f293 100644 --- a/sw/source/ui/index/cnttab.cxx +++ b/sw/source/ui/index/cnttab.cxx @@ -1899,6 +1899,8 @@ namespace STR_AUTH_FIELD_CUSTOM5, STR_AUTH_FIELD_ISBN, STR_AUTH_FIELD_LOCAL_URL, + STR_AUTH_FIELD_TARGET_URL, + STR_AUTH_FIELD_USE_TARGET_URL, }; } diff --git a/sw/source/ui/index/swuiidxmrk.cxx b/sw/source/ui/index/swuiidxmrk.cxx index 656eacedae02..4c9ea292bee7 100644 --- a/sw/source/ui/index/swuiidxmrk.cxx +++ b/sw/source/ui/index/swuiidxmrk.cxx @@ -1091,6 +1091,7 @@ class SwCreateAuthEntryDlg_Impl : public weld::GenericDialogController std::unique_ptr<weld::Button> m_xLocalBrowseButton; std::unique_ptr<weld::CheckButton> m_xLocalPageCB; std::unique_ptr<weld::SpinButton> m_xLocalPageSB; + std::unique_ptr<weld::CheckButton> m_xUseTargetURLCB; DECL_LINK(IdentifierHdl, weld::ComboBox&, void); DECL_LINK(ShortNameHdl, weld::Entry&, void); @@ -1147,6 +1148,8 @@ const TextInfo aTextInfoArr[] = {AUTH_FIELD_ANNOTE, HID_AUTH_FIELD_ANNOTE }, {AUTH_FIELD_NOTE, HID_AUTH_FIELD_NOTE }, {AUTH_FIELD_URL, HID_AUTH_FIELD_URL }, + {AUTH_FIELD_USE_TARGET_URL, HID_AUTH_FIELD_USE_TARGET_URL }, + {AUTH_FIELD_TARGET_URL, HID_AUTH_FIELD_TARGET_URL }, {AUTH_FIELD_LOCAL_URL, HID_AUTH_FIELD_LOCAL_URL }, {AUTH_FIELD_CUSTOM1, HID_AUTH_FIELD_CUSTOM1 }, {AUTH_FIELD_CUSTOM2, HID_AUTH_FIELD_CUSTOM2 }, @@ -1567,6 +1570,8 @@ namespace STR_AUTH_FIELD_CUSTOM5, STR_AUTH_FIELD_ISBN, STR_AUTH_FIELD_LOCAL_URL, + STR_AUTH_FIELD_TARGET_URL, + STR_AUTH_FIELD_USE_TARGET_URL, }; } @@ -1655,6 +1660,29 @@ SwCreateAuthEntryDlg_Impl::SwCreateAuthEntryDlg_Impl(weld::Window* pParent, m_xIdentifierBox->set_help_id(aCurInfo.pHelpId); m_aFixedTexts.back()->set_mnemonic_widget(m_xIdentifierBox.get()); } + else if (AUTH_FIELD_USE_TARGET_URL == aCurInfo.nToxField) + { + m_pBoxes[nIndex] = m_aBuilders.back()->weld_box("togglebox"); + + if (bLeft) + m_aOrigContainers.back()->move(m_pBoxes[nIndex].get(), m_xLeft.get()); + else + m_aOrigContainers.back()->move(m_pBoxes[nIndex].get(), m_xRight.get()); + + m_pBoxes[nIndex]->set_grid_left_attach(1); + m_pBoxes[nIndex]->set_grid_top_attach(bLeft ? nLeftRow : nRightRow); + m_pBoxes[nIndex]->set_hexpand(true); + + m_xUseTargetURLCB = m_aBuilders.back()->weld_check_button("usetargeturlcb"); + m_xUseTargetURLCB->set_active(m_bNewEntryMode + || pFields[aCurInfo.nToxField].toAsciiLowerCase() == "true"); + m_xUseTargetURLCB->set_grid_left_attach(1); + m_xUseTargetURLCB->set_grid_top_attach(bLeft ? nLeftRow : nRightRow); + m_xUseTargetURLCB->set_hexpand(true); + m_xUseTargetURLCB->show(); + m_xUseTargetURLCB->set_help_id(aCurInfo.pHelpId); + m_aFixedTexts.back()->set_mnemonic_widget(m_xUseTargetURLCB.get()); + } else { m_pBoxes[nIndex] = m_aBuilders.back()->weld_box("vbox"); @@ -1747,6 +1775,12 @@ OUString SwCreateAuthEntryDlg_Impl::GetEntryText(ToxAuthorityField eField) cons return m_xIdentifierBox->get_active_text(); } + if (AUTH_FIELD_USE_TARGET_URL == eField) + { + assert(m_xUseTargetURLCB && "No UseTargetURL"); + return (m_xUseTargetURLCB->get_active() ? OUString("true") : OUString("false")); + } + for(int nIndex = 0; nIndex < AUTH_FIELD_END; nIndex++) { const TextInfo aCurInfo = aTextInfoArr[nIndex]; diff --git a/sw/source/uibase/docvw/edtwin2.cxx b/sw/source/uibase/docvw/edtwin2.cxx index 573b08dc4f47..01c668be4586 100644 --- a/sw/source/uibase/docvw/edtwin2.cxx +++ b/sw/source/uibase/docvw/edtwin2.cxx @@ -375,11 +375,17 @@ void SwEditWin::RequestHelp(const HelpEvent &rEvt) const auto pAuthorityField = static_cast<const SwAuthorityField*>(pField); sText = pAuthorityField->GetAuthority(rSh.GetLayout()); - if (pAuthorityField->HasURL()) + if (!pAuthorityField->UseTargetURL() && pAuthorityField->HasURL()) { - const OUString& rURL = pAuthorityField->GetURI(false); + const OUString& rURL = pAuthorityField->GetAbsoluteURL(); sText += "\n" + SfxHelp::GetURLHelpText(rURL); } + else if (pAuthorityField->UseTargetURL() && pAuthorityField->HasTargetURL()) + { + const OUString& rURL = pAuthorityField->GetAbsoluteTargetURL(); + sText += "\n" + SfxHelp::GetURLHelpText(rURL); + } + break; } diff --git a/sw/source/uibase/shells/textsh1.cxx b/sw/source/uibase/shells/textsh1.cxx index 156572854c19..f1d9522a89f7 100644 --- a/sw/source/uibase/shells/textsh1.cxx +++ b/sw/source/uibase/shells/textsh1.cxx @@ -1821,13 +1821,20 @@ void SwTextShell::Execute(SfxRequest &rReq) if (pField && pField->GetTyp()->Which() == SwFieldIds::TableOfAuthorities) { const auto& rAuthorityField = *static_cast<const SwAuthorityField*>(pField); - if (rAuthorityField.HasURL()) + if (!rAuthorityField.UseTargetURL() && rAuthorityField.HasURL()) { // Bibliography entry with URL also provides a hyperlink. const OUString& rURL = rAuthorityField.GetAuthEntry()->GetAuthorField(AUTH_FIELD_URL); ::LoadURL(rWrtSh, rURL, LoadUrlFlags::NewView, /*rTargetFrameName=*/OUString()); } + else if (rAuthorityField.UseTargetURL() && rAuthorityField.HasTargetURL()) + { + // Bibliography entry with URL also provides a hyperlink. + const OUString& rURL + = rAuthorityField.GetAuthEntry()->GetAuthorField(AUTH_FIELD_TARGET_URL); + ::LoadURL(rWrtSh, rURL, LoadUrlFlags::NewView, /*rTargetFrameName=*/OUString()); + } } } } @@ -2552,7 +2559,10 @@ void SwTextShell::GetState( SfxItemSet &rSet ) { // Bibliography entry with URL also provides a hyperlink. const auto& rAuthorityField = *static_cast<const SwAuthorityField*>(pField); - bAuthorityFieldURL = rAuthorityField.HasURL(); + if (!rAuthorityField.UseTargetURL()) + bAuthorityFieldURL = rAuthorityField.HasURL(); + else + bAuthorityFieldURL = rAuthorityField.HasTargetURL(); } if (SfxItemState::SET > aSet.GetItemState(RES_TXTATR_INETFMT, false) && !bAuthorityFieldURL) diff --git a/sw/source/uibase/utlui/initui.cxx b/sw/source/uibase/utlui/initui.cxx index 4438935585b9..51e2b8e8894e 100644 --- a/sw/source/uibase/utlui/initui.cxx +++ b/sw/source/uibase/utlui/initui.cxx @@ -236,6 +236,8 @@ namespace STR_AUTH_FIELD_CUSTOM5, STR_AUTH_FIELD_ISBN, STR_AUTH_FIELD_LOCAL_URL, + STR_AUTH_FIELD_TARGET_URL, + STR_AUTH_FIELD_USE_TARGET_URL, }; } diff --git a/sw/source/uibase/wrtsh/wrtsh2.cxx b/sw/source/uibase/wrtsh/wrtsh2.cxx index b9995c0922b4..3249b39e86eb 100644 --- a/sw/source/uibase/wrtsh/wrtsh2.cxx +++ b/sw/source/uibase/wrtsh/wrtsh2.cxx @@ -49,6 +49,7 @@ #include <swabstdlg.hxx> #include <SwRewriter.hxx> #include <authfld.hxx> +#include <ndtxt.hxx> #include <com/sun/star/document/XDocumentProperties.hpp> #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> @@ -342,8 +343,11 @@ void SwWrtShell::ClickToField(const SwField& rField, bool bExecHyperlinks) { addCurrentPosition(); - // cross reference field must not be selected because it moves the cursor - if (SwFieldIds::GetRef != rField.GetTyp()->Which()) + // Since the cross reference and bibliography mark move the cursor, + // only select the field if it's not a Ctrl+Click + if (!bExecHyperlinks + || (SwFieldIds::GetRef != rField.GetTyp()->Which() + && SwFieldIds::TableOfAuthorities != rField.GetTyp()->Which())) { StartAllAction(); Right( SwCursorSkipMode::Chars, true, 1, false ); // Select the field. @@ -405,22 +409,74 @@ void SwWrtShell::ClickToField(const SwField& rField, bool bExecHyperlinks) case SwFieldIds::TableOfAuthorities: { if (!bExecHyperlinks) + break; // Since it's not a Ctrl+Click, do not jump anywhere + + Point vStartPoint = GetCursor_()->GetPtPos(); + const SwAuthorityField* pField = static_cast<const SwAuthorityField*>(&rField); + + if (!pField->UseTargetURL() && pField->HasURL()) { - break; - } + // Since user didn't opt in to use Target URL, open standard URL - auto pField = static_cast<const SwAuthorityField*>(&rField); - if (!pField->HasURL()) + const OUString& rURL = pField->GetAbsoluteURL(); + ::LoadURL(*this, rURL, LoadUrlFlags::NewView, /*rTargetFrameName=*/OUString()); + } + else if (pField->UseTargetURL() && pField->HasTargetURL()) { - break; + // Since user opted in to use Target URL and field has Target URL, use it + + const OUString& rURL = pField->GetAbsoluteTargetURL(); + ::LoadURL(*this, rURL, LoadUrlFlags::NewView, /*rTargetFrameName=*/OUString()); } + else if (pField->UseTargetURL()) + { + // Since user opted in to use Target URL but the field doesn't have Target URL, + // try finding matching bibliography table line + + const bool bWasViewLocked = IsViewLocked(); + LockView(true); - const OUString& rURL = pField->GetAbsoluteURL(); - ::LoadURL(*this, rURL, LoadUrlFlags::NewView, /*rTargetFrameName=*/OUString()); + // Note: This way of iterating doesn't seem to take into account TOXes + // that are in a frame, probably in some other cases too + GotoPage(1); + while (GotoNextTOXBase()) + { + const SwTOXBase* pIteratedTOX = nullptr; + const SwTOXBase* pPreviousTOX = nullptr; + OUString vFieldText; + while ((pIteratedTOX = GetCurTOX()) != nullptr + && pIteratedTOX->GetType() == TOX_AUTHORITIES) + { + if (pIteratedTOX != pPreviousTOX) + vFieldText = pField->GetAuthority(GetLayout(), &pIteratedTOX->GetTOXForm()); + + if (const SwNode& rCurrentNode = GetCursor()->GetPoint()->GetNode(); + rCurrentNode.GetNodeType() == SwNodeType::Text + && (GetCursor()->GetPoint()->GetNode().FindSectionNode()->GetSection().GetType() + == SectionType::ToxContent) // this checks it's not a heading + && static_cast<const SwTextNode*>(&rCurrentNode)->GetText() == vFieldText) + { + // Since a node has been found that is a text node, isn't a heading, + // and has text matching to text generated by the field, jump to it + LockView(bWasViewLocked); + ShowCursor(); + return; + } + pPreviousTOX = pIteratedTOX; + FwdPara(); + } + } + // Since a matching node has not been found, return to original position + SetCursor(&vStartPoint); + LockView(bWasViewLocked); + } } break; case SwFieldIds::GetRef: + if (!bExecHyperlinks) + break; + StartAllAction(); SwCursorShell::GotoRefMark( static_cast<const SwGetRefField&>(rField).GetSetRefName(), static_cast<const SwGetRefField&>(rField).GetSubType(), diff --git a/sw/uiconfig/swriter/ui/bibliofragment.ui b/sw/uiconfig/swriter/ui/bibliofragment.ui index f35db697e1e7..2652711b4472 100644 --- a/sw/uiconfig/swriter/ui/bibliofragment.ui +++ b/sw/uiconfig/swriter/ui/bibliofragment.ui @@ -109,6 +109,30 @@ </packing> </child> <child> + <object class="GtkBox" id="togglebox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkCheckButton" id="usetargeturlcb"> + <property name="label" translatable="yes" context=""></property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> <object class="GtkComboBoxText" id="listbox"> <property name="visible">True</property> <property name="can_focus">False</property> diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 8b1a8b6287da..2b17a18dd33b 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -3631,7 +3631,12 @@ we check in the following sequence: } else { - INetURLObject aNewURL(rtl::Uri::convertRelToAbs(m_aContext.BaseURL, url)); + INetURLObject aNewURL(rtl::Uri::convertRelToAbs( + (m_aContext.BaseURL.getLength() > 0 ? + m_aContext.BaseURL : + //use dummy location if empty + u"http://ahost.ax"), + url)); aTargetURL = aNewURL; //reassign the new target URL //recompute the target protocol, with the new URL diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx index 7eb6f14eee54..daa658011ae3 100644 --- a/xmloff/source/core/xmltoken.cxx +++ b/xmloff/source/core/xmltoken.cxx @@ -3469,6 +3469,8 @@ namespace xmloff::token { TOKEN("margin-gutter", XML_MARGIN_GUTTER), TOKEN("local-url", XML_LOCAL_URL), + TOKEN("target-url", XML_TARGET_URL), + TOKEN("use-target-url", XML_USE_TARGET_URL), TOKEN("dir", XML_DIR ), TOKEN("displaystyle", XML_DISPLAYSTYLE ), diff --git a/xmloff/source/text/txtflde.cxx b/xmloff/source/text/txtflde.cxx index 58ad8604f0a1..71093e61420a 100644 --- a/xmloff/source/text/txtflde.cxx +++ b/xmloff/source/text/txtflde.cxx @@ -2708,7 +2708,7 @@ void XMLTextFieldExport::ProcessBibliographyData( if (!sStr.isEmpty()) { XMLTokenEnum eElement = MapBibliographyFieldName(rProp.Name); - if (eElement == XML_URL || eElement == XML_LOCAL_URL) + if (eElement == XML_URL || eElement == XML_LOCAL_URL || eElement == XML_TARGET_URL) { sStr = GetExport().GetRelativeReference(sStr); } @@ -3414,6 +3414,14 @@ enum XMLTokenEnum XMLTextFieldExport::MapBibliographyFieldName(std::u16string_vi { eName = XML_LOCAL_URL; } + else if (sName == u"TargetURL") + { + eName = XML_TARGET_URL; + } + else if (sName == u"UseTargetURL") + { + eName = XML_USE_TARGET_URL; + } else { SAL_WARN("xmloff.text", "Unknown bibliography info data"); diff --git a/xmloff/source/text/txtfldi.cxx b/xmloff/source/text/txtfldi.cxx index af2bd8aa2ac1..e2fad89cac9d 100644 --- a/xmloff/source/text/txtfldi.cxx +++ b/xmloff/source/text/txtfldi.cxx @@ -2969,7 +2969,7 @@ void XMLBibliographyFieldImportContext::startFastElement( else { OUString aStringValue = aIter.toString(); - if (nToken == XML_URL || nToken == XML_LOCAL_URL) + if (nToken == XML_URL || nToken == XML_LOCAL_URL || nToken == XML_TARGET_URL) { aStringValue = GetImport().GetAbsoluteReference(aStringValue); } @@ -3113,6 +3113,12 @@ const char* XMLBibliographyFieldImportContext::MapBibliographyFieldName( case XML_LOCAL_URL: pName = "LocalURL"; break; + case XML_TARGET_URL: + pName = "TargetURL"; + break; + case XML_USE_TARGET_URL: + pName = "UseTargetURL"; + break; default: assert(false && "Unknown bibliography info data"); pName = nullptr; diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt index 576efe9b8bd5..098d8dd4c501 100644 --- a/xmloff/source/token/tokens.txt +++ b/xmloff/source/token/tokens.txt @@ -3216,6 +3216,8 @@ page-content-top page-content-bottom margin-gutter local-url +target-url +use-target-url dir displaystyle infinity |