diff --git a/Makefile.fetch b/Makefile.fetch index 04d38de2172c..370e8bbb8dba 100644 --- a/Makefile.fetch +++ b/Makefile.fetch @@ -206,7 +206,6 @@ $(WORKDIR)/download: $(BUILDDIR)/config_$(gb_Side).mk $(SRCDIR)/download.lst $(S $(call fetch_Optional,MORE_FONTS,FONT_ALEF_TARBALL) \ $(call fetch_Optional,MORE_FONTS,FONT_AMIRI_TARBALL) \ $(call fetch_Optional,MORE_FONTS,FONT_REEM_TARBALL) \ - $(call fetch_Optional,MORE_FONTS,FONT_SCHEHERAZADE_TARBALL) \ $(call fetch_Optional,MESON,MESON_TARBALL) \ $(call fetch_Optional,MSPUB,MSPUB_TARBALL) \ $(call fetch_Optional,MWAW,MWAW_TARBALL) \ diff --git a/Repository.mk b/Repository.mk index f9002d087c2a..d7b5fa525e0b 100644 --- a/Repository.mk +++ b/Repository.mk @@ -1080,7 +1080,6 @@ $(eval $(call gb_Helper_register_packages_for_install,ooo_fonts,\ fonts_noto_serif_hebrew \ fonts_noto_serif_lao \ fonts_reem \ - fonts_scheherazade \ ) \ )) diff --git a/RepositoryExternal.mk b/RepositoryExternal.mk index fa7288206da6..65c8cf703290 100644 --- a/RepositoryExternal.mk +++ b/RepositoryExternal.mk @@ -1388,7 +1388,7 @@ endif define gb_LinkTarget__use_cairo $(call gb_LinkTarget_use_package,$(1),cairo) $(call gb_LinkTarget_use_package,$(1),pixman) -$(call gb_LinkTarget_use_external,$(1),freetype_headers) +$(call gb_LinkTarget_use_external,$(1),freetype) $(call gb_LinkTarget_set_include,$(1),\ -I$(gb_UnpackedTarball_workdir)/cairo \ -I$(gb_UnpackedTarball_workdir)/cairo/src \ @@ -4251,7 +4251,7 @@ gb_ExternalExecutable__register_xmllint := else # ! SYSTEM_LIBXML_FOR_BUILD define gb_ExternalExecutable__register_xmllint -$(call gb_ExternalExecutable_set_internal,xmllint,$(WORKDIR_FOR_BUILD)/UnpackedTarball/libxml2/$(if $(filter MSC,$(COM)),$(if $(MSVC_USE_DEBUG_RUNTIME),Debug,Release))/xmllint$(gb_Executable_EXT_for_build),libxml2) +$(call gb_ExternalExecutable_set_internal,xmllint,$(WORKDIR_FOR_BUILD)/UnpackedTarball/libxml2/xmllint$(gb_Executable_EXT_for_build),libxml2) $(call gb_ExternalExecutable_add_dependencies,xmllint,\ $(if $(filter WNT,$(OS)),$(call gb_Package_get_target,icu_ure)) \ $(call gb_Package_get_target,libxml2) \ diff --git a/android/app/AndroidManifest.xml b/android/app/AndroidManifest.xml index 098f7e6abba7..96cf5b32da4d 100644 --- a/android/app/AndroidManifest.xml +++ b/android/app/AndroidManifest.xml @@ -74,6 +74,7 @@ + diff --git a/android/app/res/values-nl/strings.xml b/android/app/res/values-nl/strings.xml index c521790d56c4..24214bcf70cf 100644 --- a/android/app/res/values-nl/strings.xml +++ b/android/app/res/values-nl/strings.xml @@ -86,7 +86,7 @@ OK Annuleren Huidige UNO-opdracht - PDF-export voltooit + PDF-export voltooid Kan niet exporteren naar PDF Kan bestand niet opslaan Fout @@ -106,7 +106,7 @@ Experimentele modus Schakel de ontwikkelaarsmodus in, waarin u UNO-opdrachten binnen de app kunt verzenden. Gebruik op eigen risico. Opslaan als… - Opslaan voltooit + Opslaan voltooid Het document opslaan voordat u het sluit? Extra lengte invoeren in 100ste/mm Maximale kwaliteit diff --git a/android/app/src/java/org/libreoffice/LibreOfficeMainActivity.java b/android/app/src/java/org/libreoffice/LibreOfficeMainActivity.java index 215515a207e9..896c448fd9f4 100644 --- a/android/app/src/java/org/libreoffice/LibreOfficeMainActivity.java +++ b/android/app/src/java/org/libreoffice/LibreOfficeMainActivity.java @@ -330,11 +330,16 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Shared private boolean copyFileToTemp(Uri documentUri) { // CSV files need a .csv suffix to be opened in Calc. + // Markdown files need an .md suffix to be imported as Markdown and not plain text String suffix = null; String intentType = getIntent().getType(); + if (intentType == null) + intentType = getContentResolver().getType(documentUri); // K-9 mail uses the first, GMail uses the second variant. if ("text/comma-separated-values".equals(intentType) || "text/csv".equals(intentType)) suffix = ".csv"; + else if ("text/markdown".equals(intentType)) + suffix = ".md"; try { mTempFile = File.createTempFile("LibreOffice", suffix, this.getCacheDir()); diff --git a/android/app/src/java/org/libreoffice/ui/FileUtilities.java b/android/app/src/java/org/libreoffice/ui/FileUtilities.java index f7fa856217fc..3af0a624e438 100644 --- a/android/app/src/java/org/libreoffice/ui/FileUtilities.java +++ b/android/app/src/java/org/libreoffice/ui/FileUtilities.java @@ -85,6 +85,7 @@ public class FileUtilities { // Other mExtnMap.put(".csv", CALC); + mExtnMap.put(".md", DOC); mExtnMap.put(".wps", DOC); mExtnMap.put(".key", IMPRESS); mExtnMap.put(".abw", DOC); diff --git a/android/app/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java b/android/app/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java index a6fa8fe44af2..b7326a34ed70 100644 --- a/android/app/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java +++ b/android/app/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java @@ -111,6 +111,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements View.OnC "application/vnd.openxmlformats-officedocument.presentationml.template", "text/csv", "text/comma-separated-values", + "text/markdown", "application/vnd.ms-works", "application/vnd.apple.keynote", "application/x-abiword", diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index 4829d80d5481..4ffd8d7a6dce 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] constraintlayout_version = "2.2.1" -gradle_version = "9.3.1" +gradle_version = "9.4.1" material_version = "1.13.0" preference_version = "1.2.1" diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 22e55d385d22..a9b345fd3a39 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=a3c4ba4aca8f0075688b9c5b18939fd28e8cb4357c227da5c1d9f38343791439 -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-all.zip +distributionSha256Sum=87a2216cc1f9122192d4e0fe905ffdf1b4c72cff797e9f733b174e157cadd396 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-all.zip validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/basic/source/runtime/ddectrl.cxx b/basic/source/runtime/ddectrl.cxx index 41e5c53d84de..b0bc4fd0064d 100644 --- a/basic/source/runtime/ddectrl.cxx +++ b/basic/source/runtime/ddectrl.cxx @@ -22,8 +22,8 @@ #include "ddectrl.hxx" #include -#define DDE_FIRSTERR 0x4000 -#define DDE_LASTERR 0x4011 +constexpr tools::Long DDE_FIRSTERR = 0x4000; +constexpr tools::Long DDE_LASTERR = 0x4011; const ErrCode nDdeErrMap[] = { diff --git a/chart2/inc/strings.hrc b/chart2/inc/strings.hrc index 9d09aef45c36..7cb8c04eba1d 100644 --- a/chart2/inc/strings.hrc +++ b/chart2/inc/strings.hrc @@ -185,6 +185,8 @@ #define STR_INVALID_TIME_UNIT NC_("STR_INVALID_TIME_UNIT", "The major and minor interval need to be greater or equal to the resolution. Check your input.") #define STR_UNSUPPORTED_CHART_TYPE NC_("STR_UNSUPPORTED_CHART_TYPE", "One or more chart types are unsupported in this version of %PRODUCTNAME") +#define STR_RANGE_FOR_VALUETYPE NC_("STR_RANGE_FOR_VALUETYPE", "Ran~ge for %VALUETYPE") + #endif /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/qa/extras/chart2import2.cxx b/chart2/qa/extras/chart2import2.cxx index 04e0770dd143..27758c956449 100644 --- a/chart2/qa/extras/chart2import2.cxx +++ b/chart2/qa/extras/chart2import2.cxx @@ -14,6 +14,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -2038,6 +2041,66 @@ CPPUNIT_TEST_FIXTURE(Chart2ImportTest2, TestParetoDefaultColor) CPPUNIT_ASSERT_EQUAL(nCorrectColor, nFillColor); } +CPPUNIT_TEST_FIXTURE(Chart2ImportTest2, testTdf164516BarOfPieDiagram) +{ + loadFromFile(u"ods/chart.ods"); + + uno::Reference xChartComp = getChartCompFromSheet(0, 0); + uno::Reference xChartDoc(xChartComp, uno::UNO_QUERY_THROW); + uno::Reference xFactory(xChartDoc, uno::UNO_QUERY_THROW); + + // tdf#164516 - switch the chart type to bar-of-pie using the chart API + uno::Reference xDiagram( + xFactory->createInstance(u"com.sun.star.chart.BarOfPieDiagram"_ustr), uno::UNO_QUERY); + xChartDoc->setDiagram(xDiagram); + + // Without the fix in place, this test would have failed with + // - Expected: com.sun.star.chart.BarOfPieDiagram + // - Actual : com.sun.star.chart.BarDiagram + // i.e. the service name was unknown and the chart stays a bar chart + CPPUNIT_ASSERT_EQUAL(u"com.sun.star.chart.BarOfPieDiagram"_ustr, + xChartDoc->getDiagram()->getDiagramType()); + + uno::Reference xChart2Doc(xChartComp, uno::UNO_QUERY_THROW); + uno::Reference xChartType = getChartTypeFromDoc(xChart2Doc, 0); + CPPUNIT_ASSERT_EQUAL(u"com.sun.star.chart2.PieChartType"_ustr, xChartType->getChartType()); + + uno::Reference xPropSet(xChartType, uno::UNO_QUERY_THROW); + chart2::PieChartSubType eSubType; + CPPUNIT_ASSERT(xPropSet->getPropertyValue(u"SubPieType"_ustr) >>= eSubType); + CPPUNIT_ASSERT_EQUAL(chart2::PieChartSubType_BAR, eSubType); +} + +CPPUNIT_TEST_FIXTURE(Chart2ImportTest2, testTdf164516PieOfPieDiagram) +{ + loadFromFile(u"ods/chart.ods"); + + uno::Reference xChartComp = getChartCompFromSheet(0, 0); + uno::Reference xChartDoc(xChartComp, uno::UNO_QUERY_THROW); + uno::Reference xFactory(xChartDoc, uno::UNO_QUERY_THROW); + + // tdf#164516 - switch the chart type to pie-of-pie using the chart API + uno::Reference xDiagram( + xFactory->createInstance(u"com.sun.star.chart.PieOfPieDiagram"_ustr), uno::UNO_QUERY); + xChartDoc->setDiagram(xDiagram); + + // Without the fix in place, this test would have failed with + // - Expected: com.sun.star.chart.PieOfPieDiagram + // - Actual : com.sun.star.chart.BarDiagram + // i.e. the service name was unknown and the chart stays a bar chart + CPPUNIT_ASSERT_EQUAL(u"com.sun.star.chart.PieOfPieDiagram"_ustr, + xChartDoc->getDiagram()->getDiagramType()); + + uno::Reference xChart2Doc(xChartComp, uno::UNO_QUERY_THROW); + uno::Reference xChartType = getChartTypeFromDoc(xChart2Doc, 0); + CPPUNIT_ASSERT_EQUAL(u"com.sun.star.chart2.PieChartType"_ustr, xChartType->getChartType()); + + uno::Reference xPropSet(xChartType, uno::UNO_QUERY_THROW); + chart2::PieChartSubType eSubType; + CPPUNIT_ASSERT(xPropSet->getPropertyValue(u"SubPieType"_ustr) >>= eSubType); + CPPUNIT_ASSERT_EQUAL(chart2::PieChartSubType_PIE, eSubType); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/controller/chartapiwrapper/ChartDocumentWrapper.cxx b/chart2/source/controller/chartapiwrapper/ChartDocumentWrapper.cxx index 2888ee9b0672..59fddcc390e2 100644 --- a/chart2/source/controller/chartapiwrapper/ChartDocumentWrapper.cxx +++ b/chart2/source/controller/chartapiwrapper/ChartDocumentWrapper.cxx @@ -84,6 +84,8 @@ enum eServiceType SERVICE_NAME_NET_DIAGRAM, SERVICE_NAME_FILLED_NET_DIAGRAM, SERVICE_NAME_PIE_DIAGRAM, + SERVICE_NAME_BAR_OF_PIE_DIAGRAM, + SERVICE_NAME_PIE_OF_PIE_DIAGRAM, SERVICE_NAME_STOCK_DIAGRAM, SERVICE_NAME_XY_DIAGRAM, SERVICE_NAME_BUBBLE_DIAGRAM, @@ -114,6 +116,8 @@ tServiceNameMap & lcl_getStaticServiceNameMap() {"com.sun.star.chart.NetDiagram", SERVICE_NAME_NET_DIAGRAM}, {"com.sun.star.chart.FilledNetDiagram", SERVICE_NAME_FILLED_NET_DIAGRAM}, {"com.sun.star.chart.PieDiagram", SERVICE_NAME_PIE_DIAGRAM}, + {"com.sun.star.chart.BarOfPieDiagram", SERVICE_NAME_BAR_OF_PIE_DIAGRAM}, + {"com.sun.star.chart.PieOfPieDiagram", SERVICE_NAME_PIE_OF_PIE_DIAGRAM}, {"com.sun.star.chart.StockDiagram", SERVICE_NAME_STOCK_DIAGRAM}, {"com.sun.star.chart.XYDiagram", SERVICE_NAME_XY_DIAGRAM}, {"com.sun.star.chart.BubbleDiagram", SERVICE_NAME_BUBBLE_DIAGRAM}, @@ -1207,6 +1211,22 @@ uno::Reference< uno::XInterface > SAL_CALL ChartDocumentWrapper::createInstance( bCreateDiagram = true; } break; + case SERVICE_NAME_BAR_OF_PIE_DIAGRAM: + if (xChartTypeManager.is()) + { + xTemplate = xChartTypeManager->createTemplate( + u"com.sun.star.chart2.template.BarOfPie"_ustr); + bCreateDiagram = true; + } + break; + case SERVICE_NAME_PIE_OF_PIE_DIAGRAM: + if (xChartTypeManager.is()) + { + xTemplate = xChartTypeManager->createTemplate( + u"com.sun.star.chart2.template.PieOfPie"_ustr); + bCreateDiagram = true; + } + break; case SERVICE_NAME_STOCK_DIAGRAM: if( xChartTypeManager.is()) { diff --git a/chart2/source/controller/dialogs/tp_DataSource.cxx b/chart2/source/controller/dialogs/tp_DataSource.cxx index 2206e89c7f1f..27b9b2f016dd 100644 --- a/chart2/source/controller/dialogs/tp_DataSource.cxx +++ b/chart2/source/controller/dialogs/tp_DataSource.cxx @@ -183,7 +183,6 @@ DataSourceTabPage::DataSourceTabPage(weld::Container* pPage, weld::DialogControl m_xLB_ROLE->get_height_rows(5)); m_xFT_CAPTION->set_visible(!bHideDescription); - m_aFixedTextRange = m_xFT_RANGE->get_label(); SetPageTitle(SchResId(STR_OBJECT_DATASERIES_PLURAL)); // set handlers @@ -473,15 +472,9 @@ IMPL_LINK_NOARG(DataSourceTabPage, RoleSelectionChangedHdl, weld::ItemView&, voi OUString aSelectedRoleUI = lcl_GetSelectedRole( *m_xLB_ROLE, true ); OUString aSelectedRange = lcl_GetSelectedRolesRange( *m_xLB_ROLE ); - // replace role in fixed text label - static constexpr OUString aReplacementStr( u"%VALUETYPE"_ustr ); - sal_Int32 nIndex = m_aFixedTextRange.indexOf( aReplacementStr ); - if( nIndex != -1 ) - { - m_xFT_RANGE->set_label( - m_aFixedTextRange.replaceAt( - nIndex, aReplacementStr.getLength(), aSelectedRoleUI )); - } + // add the role in fixed text label + m_xFT_RANGE->set_label( + SchResId(STR_RANGE_FOR_VALUETYPE).replaceFirst(u"%VALUETYPE", aSelectedRoleUI)); m_xEDT_RANGE->set_text(aSelectedRange); isValid(); diff --git a/chart2/source/controller/dialogs/tp_DataSource.hxx b/chart2/source/controller/dialogs/tp_DataSource.hxx index 0ecb3dffd2bc..22efd8449d34 100644 --- a/chart2/source/controller/dialogs/tp_DataSource.hxx +++ b/chart2/source/controller/dialogs/tp_DataSource.hxx @@ -121,8 +121,6 @@ private: std::vector> m_aEntries; - OUString m_aFixedTextRange; - ChartTypeTemplateProvider * m_pTemplateProvider; DialogModel & m_rDialogModel; weld::Entry* m_pCurrentRangeChoosingField; diff --git a/chart2/source/model/template/FunnelChartTypeTemplate.cxx b/chart2/source/model/template/FunnelChartTypeTemplate.cxx index 1dff397bccd2..1137eca284aa 100644 --- a/chart2/source/model/template/FunnelChartTypeTemplate.cxx +++ b/chart2/source/model/template/FunnelChartTypeTemplate.cxx @@ -13,12 +13,19 @@ #include #include #include +#include +#include +#include +#include #include #include #include #include +#include #include +#include + #include using namespace ::com::sun::star; @@ -188,6 +195,42 @@ void FunnelChartTypeTemplate::resetStyles2(const rtl::Reference<::chart::Diagram xDiagram->setVertical(false); } +bool FunnelChartTypeTemplate::isSwapXAndY() const +{ + // Ordinate axis is vertical + return true; +} + +void FunnelChartTypeTemplate::createCoordinateSystems( + const rtl::Reference<::chart::Diagram>& xDiagram) +{ + ChartTypeTemplate::createCoordinateSystems(xDiagram); + + xDiagram->setVertical(isSwapXAndY()); +} + +void FunnelChartTypeTemplate::adaptAxes( + const std::vector>& rCoordSys) +{ + // override to hide the horizontal (value) axis + ChartTypeTemplate::adaptAxes(rCoordSys); + + if (rCoordSys.empty()) + return; + + for (rtl::Reference const& xCooSys : rCoordSys) + { + if (!xCooSys.is()) + continue; + + rtl::Reference xAxis = AxisHelper::getAxis(1, 0, xCooSys); + if (!xAxis.is()) + continue; + + xAxis->setPropertyValue(u"Show"_ustr, uno::Any(false)); + } +} + IMPLEMENT_FORWARD_XINTERFACE2(FunnelChartTypeTemplate, ChartTypeTemplate, OPropertySet) IMPLEMENT_FORWARD_XTYPEPROVIDER2(FunnelChartTypeTemplate, ChartTypeTemplate, OPropertySet) diff --git a/chart2/source/model/template/FunnelChartTypeTemplate.hxx b/chart2/source/model/template/FunnelChartTypeTemplate.hxx index ffaefeace151..a6b3ab061472 100644 --- a/chart2/source/model/template/FunnelChartTypeTemplate.hxx +++ b/chart2/source/model/template/FunnelChartTypeTemplate.hxx @@ -45,12 +45,16 @@ protected: ::sal_Int32 nChartTypeGroupIndex, ::sal_Int32 nSeriesIndex, ::sal_Int32 nSeriesCount) override; virtual void resetStyles2(const rtl::Reference<::chart::Diagram>& xDiagram) override; + virtual void createCoordinateSystems(const rtl::Reference<::chart::Diagram>& xDiagram) override; // ____ ChartTypeTemplate ____ virtual sal_Int32 getDimension() const override; virtual rtl::Reference<::chart::ChartType> getChartTypeForIndex(sal_Int32 nChartTypeIndex) override; + virtual bool isSwapXAndY() const override; + virtual void + adaptAxes(const std::vector>& rCoordSys) override; private: sal_Int32 m_nDim; diff --git a/chart2/source/view/charttypes/Splines.cxx b/chart2/source/view/charttypes/Splines.cxx index fe2c67792ad5..ba4fe5a67bc0 100644 --- a/chart2/source/view/charttypes/Splines.cxx +++ b/chart2/source/view/charttypes/Splines.cxx @@ -38,6 +38,75 @@ typedef std::pair< double, double > tPointType; typedef std::vector< tPointType > tPointVecType; typedef tPointVecType::size_type lcl_tSizeType; +bool lcl_CalculateUpperTriangularForm( + tPointVecType& rPointsIn + , std::vector>& rMatN + , std::vector& rShift ) +{ + lcl_tSizeType r = 0; // true row index + lcl_tSizeType c = 0; // true column index + double fDivisor = 1.0; // used for diagonal element + double fEliminate = 1.0; // used for the element, that will become zero + bool bIsSuccessful = true; + const lcl_tSizeType n = rPointsIn.size() - 1; + for (c = 0 ; c <= n && bIsSuccessful; ++c) + { + // search for first non-zero downwards + r = c; + while ( r < n && rMatN[r][c-rShift[r]] == 0 ) + { + ++r; + } + if (rMatN[r][c-rShift[r]] == 0.0) + { + // Matrix N is singular, although this is mathematically impossible + bIsSuccessful = false; + } + else + { + // exchange total row r with total row c if necessary + if (r != c) + { + std::swap( rMatN[r], rMatN[c] ); + std::swap( rPointsIn[r], rPointsIn[c] ); + std::swap( rShift[r], rShift[c] ); + } + + sal_uInt32 p = rMatN[c].size() - 1; + + // divide row c, so that element(c,c) becomes 1 + fDivisor = rMatN[c][c-rShift[c]]; // not zero, see above + for (sal_uInt32 i = 0; i <= p; ++i) + { + rMatN[c][i] /= fDivisor; + } + rPointsIn[c].first /= fDivisor; + rPointsIn[c].second /= fDivisor; + + // eliminate forward, examine row c+1 to n-1 (worst case) + // stop if first non-zero element in row has an higher column as c + // look at nShift for that, elements in nShift are equal or increasing + for ( r = c+1; r < n && rShift[r]<=c ; ++r) + { + fEliminate = rMatN[r][0]; + if (fEliminate != 0.0) // else accidentally zero, nothing to do + { + for (sal_uInt32 i = 1; i <= p; ++i) + { + rMatN[r][i-1] = rMatN[r][i] - fEliminate * rMatN[c][i]; + } + rMatN[r][p]=0; + rPointsIn[r].first -= fEliminate * rPointsIn[c].first; + rPointsIn[r].second -= fEliminate * rPointsIn[c].second; + ++rShift[r]; + } + } + } + } + + return bIsSuccessful; +} + class lcl_SplineCalculation { public: @@ -719,63 +788,9 @@ void SplineCalculator::CalculateBSplines( // Get matrix C of control points from the matrix equation aMatN * C = aPointsIn // aPointsIn is overwritten with C. // Gaussian elimination is possible without pivoting, see reference - lcl_tSizeType r = 0; // true row index - lcl_tSizeType c = 0; // true column index - double fDivisor = 1.0; // used for diagonal element - double fEliminate = 1.0; // used for the element, that will become zero - bool bIsSuccessful = true; - for (c = 0 ; c <= n && bIsSuccessful; ++c) - { - // search for first non-zero downwards - r = c; - while ( r < n && aMatN[r][c-aShift[r]] == 0 ) - { - ++r; - } - if (aMatN[r][c-aShift[r]] == 0.0) - { - // Matrix N is singular, although this is mathematically impossible - bIsSuccessful = false; - } - else - { - // exchange total row r with total row c if necessary - if (r != c) - { - std::swap( aMatN[r], aMatN[c] ); - std::swap( aPointsIn[r], aPointsIn[c] ); - std::swap( aShift[r], aShift[c] ); - } - // divide row c, so that element(c,c) becomes 1 - fDivisor = aMatN[c][c-aShift[c]]; // not zero, see above - for (sal_uInt32 i = 0; i <= p; ++i) - { - aMatN[c][i] /= fDivisor; - } - aPointsIn[c].first /= fDivisor; - aPointsIn[c].second /= fDivisor; + bool bIsSuccessful = lcl_CalculateUpperTriangularForm(aPointsIn, aMatN, aShift); - // eliminate forward, examine row c+1 to n-1 (worst case) - // stop if first non-zero element in row has an higher column as c - // look at nShift for that, elements in nShift are equal or increasing - for ( r = c+1; r < n && aShift[r]<=c ; ++r) - { - fEliminate = aMatN[r][0]; - if (fEliminate != 0.0) // else accidentally zero, nothing to do - { - for (sal_uInt32 i = 1; i <= p; ++i) - { - aMatN[r][i-1] = aMatN[r][i] - fEliminate * aMatN[c][i]; - } - aMatN[r][p]=0; - aPointsIn[r].first -= fEliminate * aPointsIn[c].first; - aPointsIn[r].second -= fEliminate * aPointsIn[c].second; - ++aShift[r]; - } - } - } - }// upper triangle form is reached if( bIsSuccessful) { // eliminate backwards, begin with last column @@ -785,10 +800,11 @@ void SplineCalculator::CalculateBSplines( // diagonal are zero and do not influence other rows. // Full matrix N has semibandwidth < p, therefore element(r,c) is // zero, if abs(r-cc)>=p. abs(r-cc)=cc-r, because r True False - Ran_ge for %VALUETYPE + Ran_ge True EDT_RANGE 0 diff --git a/comphelper/Library_comphelper.mk b/comphelper/Library_comphelper.mk index 4939cda63aba..eb27e52361cf 100644 --- a/comphelper/Library_comphelper.mk +++ b/comphelper/Library_comphelper.mk @@ -117,6 +117,7 @@ $(eval $(call gb_Library_add_exception_objects,comphelper,\ comphelper/source/misc/hash \ comphelper/source/misc/instancelocker \ comphelper/source/misc/interaction \ + comphelper/source/misc/invocationhelper \ comphelper/source/misc/logging \ comphelper/source/misc/lok \ comphelper/source/misc/mimeconfighelper \ diff --git a/comphelper/source/misc/invocationhelper.cxx b/comphelper/source/misc/invocationhelper.cxx new file mode 100644 index 000000000000..90f532d891dd --- /dev/null +++ b/comphelper/source/misc/invocationhelper.cxx @@ -0,0 +1,243 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace comphelper +{ +constexpr sal_Int32 ALLOWED_METHODS + = css::beans::MethodConcept::ALL ^ css::beans::MethodConcept::DANGEROUS; +constexpr sal_Int32 ALLOWED_PROPERTIES + = css::beans::PropertyConcept::ALL ^ css::beans::PropertyConcept::DANGEROUS; + +InvocationHelper::InvocationHelper(const css::uno::Reference& xContext) + : m_xContext(xContext) +{ +} + +css::uno::Reference +InvocationHelper::getIntrospection(css::script::XInvocation* pInvocation) +{ + ensureIntrospection(pInvocation); + + return m_xIntrospectionAccess; +} + +css::uno::Sequence InvocationHelper::prepareInvokeParams( + const css::uno::Sequence& aParams, + const css::uno::Sequence& aParamInfos) +{ + css::uno::Sequence aInvokeParams(aParamInfos.getLength()); + css::uno::Any* pInvokeParams = aInvokeParams.getArray(); + + for (sal_Int32 nParam = 0, nParams = aParamInfos.getLength(); nParam < nParams; ++nParam) + { + const css::reflection::ParamInfo& rParam = aParamInfos[nParam]; + + if (rParam.aMode == css::reflection::ParamMode_OUT) + { + // Initialize default value if the parameter is OUT + rParam.aType->createObject(pInvokeParams[nParam]); + } + else + { + css::uno::Type aDestType(rParam.aType->getTypeClass(), rParam.aType->getName()); + + try + { + pInvokeParams[nParam] = m_xTypeConverter->convertTo(aParams[nParam], aDestType); + } + catch (css::script::CannotConvertException& e) + { + // Add optional parameter index + e.ArgumentIndex = nParam; + throw; + } + } + } + + return aInvokeParams; +} + +css::uno::Any InvocationHelper::invoke(css::script::XInvocation* pInvocation, + const OUString& aFunctionName, + const css::uno::Sequence& aParams, + css::uno::Sequence& aOutParamIndex, + css::uno::Sequence& aOutParam) +{ + // This implementation is heavily inspired by Invocation_Impl::invoke + + ensureIntrospection(pInvocation); + + css::uno::Reference xMethod; + + try + { + xMethod = m_xIntrospectionAccess->getMethod(aFunctionName, ALLOWED_METHODS); + } + catch (css::lang::NoSuchMethodException&) + { + throw css::lang::IllegalArgumentException(u"no such method"_ustr, pInvocation, 0); + } + + css::uno::Sequence aParamInfos = xMethod->getParameterInfos(); + + if (aParams.getLength() != aParamInfos.getLength()) + { + throw css::lang::IllegalArgumentException( + "incorrect number of parameters passed invoking function " + aFunctionName + + ": expected " + OUString::number(aParamInfos.getLength()) + ", got " + + OUString::number(aParams.getLength()), + pInvocation, 1); + } + + ensureTypeConverter(); + + css::uno::Sequence aInvokeParams = prepareInvokeParams(aParams, aParamInfos); + + css::uno::Reference xInvocation(pInvocation); + css::uno::Any aRet = xMethod->invoke(css::uno::Any(xInvocation), aInvokeParams); + + size_t nOutParams = std::count_if( + std::cbegin(aParamInfos), std::cend(aParamInfos), + [](const auto& rParamInfo) { return rParamInfo.aMode != css::reflection::ParamMode_IN; }); + aOutParamIndex.realloc(nOutParams); + aOutParam.realloc(nOutParams); + + sal_Int16* pOutParamIndex = aOutParamIndex.getArray(); + css::uno::Any* pOutParam = aOutParam.getArray(); + + for (sal_Int32 nParam = 0, nOutParam = 0, nParams = aParamInfos.getLength(); nParam < nParams; + ++nParam) + { + if (aParamInfos[nParam].aMode == css::reflection::ParamMode_IN) + continue; + + pOutParamIndex[nOutParam] = nParam; + pOutParam[nOutParam] = aInvokeParams[nParam]; + nOutParam++; + } + + return aRet; +} + +void InvocationHelper::setValue(css::script::XInvocation* pInvocation, + const OUString& aPropertyName, const css::uno::Any& aValue) +{ + ensureIntrospection(pInvocation); + + css::uno::Reference xPropertySet(m_xIntrospectionAccess, + css::uno::UNO_QUERY); + + if (!xPropertySet.is()) + throw css::beans::UnknownPropertyException(aPropertyName); + + css::beans::Property aProp; + + try + { + aProp = m_xIntrospectionAccess->getProperty(aPropertyName, ALLOWED_PROPERTIES); + } + catch (css::container::NoSuchElementException&) + { + throw css::beans::UnknownPropertyException(aPropertyName); + } + + ensureTypeConverter(); + + css::uno::Any aConvertedValue = m_xTypeConverter->convertTo(aValue, aProp.Type); + + try + { + return xPropertySet->setPropertyValue(aPropertyName, aConvertedValue); + } + catch (css::lang::WrappedTargetException& e) + { + throw css::reflection::InvocationTargetException(e.Message, pInvocation, e.TargetException); + } + catch (css::beans::PropertyVetoException& e) + { + throw css::reflection::InvocationTargetException(e.Message, pInvocation, css::uno::Any(e)); + } +} + +css::uno::Any InvocationHelper::getValue(css::script::XInvocation* pInvocation, + const OUString& aPropertyName) +{ + // Make sure the introspection access implements XPropertySet and that the property is one of + // the ones allowed by hasProperty. + + if (!hasProperty(pInvocation, aPropertyName)) + throw css::beans::UnknownPropertyException(aPropertyName); + + css::uno::Reference xPropertySet(m_xIntrospectionAccess, + css::uno::UNO_QUERY_THROW); + + try + { + return xPropertySet->getPropertyValue(aPropertyName); + } + catch (css::lang::WrappedTargetException& e) + { + throw css::reflection::InvocationTargetException(e.Message, pInvocation, e.TargetException); + } +} + +sal_Bool InvocationHelper::hasMethod(css::script::XInvocation* pInvocation, const OUString& aName) +{ + ensureIntrospection(pInvocation); + + return m_xIntrospectionAccess->hasMethod(aName, ALLOWED_METHODS); +} + +sal_Bool InvocationHelper::hasProperty(css::script::XInvocation* pInvocation, const OUString& aName) +{ + ensureIntrospection(pInvocation); + + css::uno::Reference xPropertySet(m_xIntrospectionAccess, + css::uno::UNO_QUERY); + + return xPropertySet.is() && m_xIntrospectionAccess->hasProperty(aName, ALLOWED_PROPERTIES); +} + +void InvocationHelper::ensureTypeConverter() +{ + if (m_xTypeConverter.is()) + return; + + m_xTypeConverter = css::script::Converter::create(m_xContext); +} + +void InvocationHelper::ensureIntrospection(css::script::XInvocation* pInvocation) +{ + if (m_xIntrospectionAccess.is()) + return; + + css::uno::Reference xIntrospection + = css::beans::theIntrospection::get(m_xContext); + + css::uno::Reference xInvocation(pInvocation); + m_xIntrospectionAccess = xIntrospection->inspect(css::uno::Any(xInvocation)); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/compilerplugins/clang/consttobool.cxx b/compilerplugins/clang/consttobool.cxx index bb4fc31911b5..831ef6d300ac 100644 --- a/compilerplugins/clang/consttobool.cxx +++ b/compilerplugins/clang/consttobool.cxx @@ -115,6 +115,26 @@ public: return ret; } + bool TraverseCXXFunctionalCastExpr(CXXFunctionalCastExpr* expr) + { + // The C++ definition of assert in glibc 2.44 checks that its argument is a single + // expression with `1 ? 1 : bool (__VA_ARGS__)`, which re-evaluates the whole argument in a + // branch that is never taken; do not descend into that dead branch to avoid duplicate + // warnings: + if (expr->getType()->isBooleanType()) + { + auto const loc = expr->getBeginLoc(); + if (compiler.getSourceManager().isMacroBodyExpansion(loc) + && Lexer::getImmediateMacroName(loc, compiler.getSourceManager(), + compiler.getLangOpts()) + == "assert") + { + return true; + } + } + return FilteringPlugin::TraverseCXXFunctionalCastExpr(expr); + } + bool VisitImplicitCastExpr(ImplicitCastExpr const* expr) { if (ignoreLocation(expr)) diff --git a/compilerplugins/clang/implicitboolconversion.cxx b/compilerplugins/clang/implicitboolconversion.cxx index a73d3694334b..67c037bf89cb 100644 --- a/compilerplugins/clang/implicitboolconversion.cxx +++ b/compilerplugins/clang/implicitboolconversion.cxx @@ -241,15 +241,7 @@ public: FilteringPlugin(data) {} virtual void run() override - { - std::string fn(handler.getMainFileName()); - loplugin::normalizeDotDotInFilePath(fn); - if (loplugin::hasPathnamePrefix(fn, SRCDIR "/soltools/cpp") - || loplugin::hasPathnamePrefix(fn, SRCDIR "/soltools/mkdepend")) - return; - - TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); - } + { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); } bool TraverseCallExpr(CallExpr * expr); @@ -488,6 +480,18 @@ bool ImplicitBoolConversion::TraverseCXXStaticCastExpr(CXXStaticCastExpr * expr) bool ImplicitBoolConversion::TraverseCXXFunctionalCastExpr( CXXFunctionalCastExpr * expr) { + // The C++ definition of assert in glibc 2.44 checks that its argument is a single expression + // with `1 ? 1 : bool (__VA_ARGS__)`, which re-evaluates the whole argument in a branch that is + // never taken; do not descend into that dead branch to avoid duplicate warnings: + if (expr->getType()->isBooleanType()) { + auto const loc = expr->getBeginLoc(); + if (compiler.getSourceManager().isMacroBodyExpansion(loc) + && Lexer::getImmediateMacroName( + loc, compiler.getSourceManager(), compiler.getLangOpts()) == "assert") + { + return true; + } + } nested.push(std::vector()); bool bRet = RecursiveASTVisitor::TraverseCXXFunctionalCastExpr(expr); assert(!nested.empty()); @@ -721,6 +725,15 @@ bool ImplicitBoolConversion::VisitImplicitCastExpr( } } } + // Ignore the conversion from bool to int in the `1 ? 1 : bool (__VA_ARGS__)` argument check + // of the C++ definition of assert in glibc 2.44: + auto const loc = expr->getBeginLoc(); + if (compiler.getSourceManager().isMacroBodyExpansion(loc) + && Lexer::getImmediateMacroName( + loc, compiler.getSourceManager(), compiler.getLangOpts()) == "assert") + { + return true; + } if (nested.empty()) { reportWarning(expr); } else { @@ -757,6 +770,13 @@ bool ImplicitBoolConversion::VisitImplicitCastExpr( && !calls.empty()) { CallExpr const * call = calls.top(); + // Ignore the conversion from pointer in the `extern _Bool __assert_single_arg (_Bool)` + // check in the C definition of assert in glibc 2.44: + if (auto const fd = call->getDirectCallee()) { + if (fd->getIdentifier() && fd->getName() == "__assert_single_arg") { + return true; + } + } if (std::any_of( call->arg_begin(), call->arg_end(), [expr](Expr const * e) { return expr == e->IgnoreParens(); })) diff --git a/compilerplugins/clang/pointerbool.cxx b/compilerplugins/clang/pointerbool.cxx index f5c2dfd4951b..ccd482f04584 100644 --- a/compilerplugins/clang/pointerbool.cxx +++ b/compilerplugins/clang/pointerbool.cxx @@ -45,6 +45,9 @@ public: bool PreTraverseFunctionDecl(FunctionDecl* decl); bool PostTraverseFunctionDecl(FunctionDecl* decl, bool); bool TraverseFunctionDecl(FunctionDecl* decl); + bool PreTraverseCXXFunctionalCastExpr(CXXFunctionalCastExpr* expr); + bool PostTraverseCXXFunctionalCastExpr(CXXFunctionalCastExpr* expr, bool); + bool TraverseCXXFunctionalCastExpr(CXXFunctionalCastExpr* expr); bool VisitCallExpr(CallExpr const*); private: @@ -76,14 +79,36 @@ bool PointerBool::TraverseFunctionDecl(FunctionDecl* decl) return ret; } +bool PointerBool::PreTraverseCXXFunctionalCastExpr(CXXFunctionalCastExpr* expr) +{ + // The C++ definition of assert in glibc 2.44 checks that its argument is a single expression + // with `1 ? 1 : bool (__VA_ARGS__)`, which re-evaluates the whole argument in a branch that is + // never taken; do not descend into that dead branch to avoid duplicate warnings: + if (!expr->getType()->isBooleanType()) + { + return true; + } + auto const loc = expr->getBeginLoc(); + return !compiler.getSourceManager().isMacroBodyExpansion(loc) + || Lexer::getImmediateMacroName(loc, compiler.getSourceManager(), compiler.getLangOpts()) + != "assert"; +} + +bool PointerBool::PostTraverseCXXFunctionalCastExpr(CXXFunctionalCastExpr*, bool) { return true; } + +bool PointerBool::TraverseCXXFunctionalCastExpr(CXXFunctionalCastExpr* expr) +{ + bool ret = true; + if (PreTraverseCXXFunctionalCastExpr(expr)) + { + ret = FilteringPlugin::TraverseCXXFunctionalCastExpr(expr); + PostTraverseCXXFunctionalCastExpr(expr, ret); + } + return ret; +} + bool PointerBool::VisitCallExpr(CallExpr const* callExpr) { - std::string fn(handler.getMainFileName()); - loplugin::normalizeDotDotInFilePath(fn); - if (loplugin::hasPathnamePrefix(fn, SRCDIR "/soltools/cpp") - || loplugin::hasPathnamePrefix(fn, SRCDIR "/soltools/mkdepend")) - return true; - if (ignoreLocation(callExpr)) return true; // TODO this doesn't currently work, the args and the params don't seem to line up @@ -100,6 +125,11 @@ bool PointerBool::VisitCallExpr(CallExpr const* callExpr) } if (!functionDecl) return true; + // glibc 2.44 checks that the argument to assert is a single expression by passing it to + // "extern _Bool __assert_single_arg (_Bool)" inside a sizeof. A pointer argument to that + // internal check is not a mistake in the calling code: + if (functionDecl->getIdentifier() && functionDecl->getName() == "__assert_single_arg") + return true; unsigned len = std::min(callExpr->getNumArgs(), functionDecl->getNumParams()); for (unsigned i = 0; i < len; ++i) diff --git a/compilerplugins/clang/redundantcast.cxx b/compilerplugins/clang/redundantcast.cxx index 78b1e70e14f2..557e80a6b4ab 100644 --- a/compilerplugins/clang/redundantcast.cxx +++ b/compilerplugins/clang/redundantcast.cxx @@ -132,6 +132,23 @@ public: return ret; } + bool TraverseCXXFunctionalCastExpr(CXXFunctionalCastExpr * expr) { + // The C++ definition of assert in glibc 2.44 checks that its argument is a single + // expression with `1 ? 1 : bool (__VA_ARGS__)`, which re-evaluates the whole argument in a + // branch that is never taken; do not descend into that dead branch to avoid duplicate + // warnings: + if (expr->getType()->isBooleanType()) { + auto const loc = expr->getBeginLoc(); + if (compiler.getSourceManager().isMacroBodyExpansion(loc) + && Lexer::getImmediateMacroName( + loc, compiler.getSourceManager(), compiler.getLangOpts()) == "assert") + { + return true; + } + } + return FilteringRewritePlugin::TraverseCXXFunctionalCastExpr(expr); + } + bool VisitImplicitCastExpr(ImplicitCastExpr const * expr); bool VisitCXXStaticCastExpr(CXXStaticCastExpr const * expr); diff --git a/compilerplugins/clang/test/consttobool.cxx b/compilerplugins/clang/test/consttobool.cxx index 4fe41a8140f2..aa0a824e5a66 100644 --- a/compilerplugins/clang/test/consttobool.cxx +++ b/compilerplugins/clang/test/consttobool.cxx @@ -64,6 +64,8 @@ int main() } // expected-error@+1 {{implicit conversion of constant &"msg"[0] of type 'const char *' to 'bool'; use 'true' instead [loplugin:consttobool]}} assert("msg"); + // expected-error@+1 {{implicit conversion of constant 2 of type 'int' to 'bool'; use 'true' instead [loplugin:consttobool]}} + assert(2); #endif } diff --git a/compilerplugins/clang/test/implicitboolconversion.cxx b/compilerplugins/clang/test/implicitboolconversion.cxx index 122ee363ae68..2bb00ba6c5c4 100644 --- a/compilerplugins/clang/test/implicitboolconversion.cxx +++ b/compilerplugins/clang/test/implicitboolconversion.cxx @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -80,4 +81,13 @@ void f() s8[0] = false; } +void testAssert([[maybe_unused]] bool b, [[maybe_unused]] int n) +{ + assert(b); +#if !defined NDEBUG + // expected-error@+1 {{implicit conversion (IntegralCast) from 'bool' to 'int' [loplugin:implicitboolconversion]}} + assert(n = b); +#endif +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/compilerplugins/clang/test/pointerbool.cxx b/compilerplugins/clang/test/pointerbool.cxx index fcb4a9a31b57..8e32cc0250d9 100644 --- a/compilerplugins/clang/test/pointerbool.cxx +++ b/compilerplugins/clang/test/pointerbool.cxx @@ -7,6 +7,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +#include + #include #include @@ -43,4 +47,16 @@ void test2(int p1) func_bool_via_forward_template(p1); } +// expected-note@+1 {{method here [loplugin:pointerbool]}} +bool func_boolA(bool); + +void testAssert([[maybe_unused]] int* p) +{ + assert(p); +#if !defined NDEBUG + // expected-error@+1 {{possibly unwanted implicit conversion when calling bool param [loplugin:pointerbool]}} + assert(func_boolA(p)); +#endif +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/compilerplugins/clang/test/redundantcast.cxx b/compilerplugins/clang/test/redundantcast.cxx index 47a155c64b37..55a000bf4bd7 100644 --- a/compilerplugins/clang/test/redundantcast.cxx +++ b/compilerplugins/clang/test/redundantcast.cxx @@ -9,6 +9,7 @@ #include +#include #include #include @@ -485,6 +486,14 @@ void testFunctionalCast2() { S1 aTest2(S1_COL); } +void testAssert([[maybe_unused]] int n) { + assert(n > 0); +#if !defined NDEBUG + // expected-error@+1 {{redundant functional cast from 'bool' to 'bool' [loplugin:redundantcast]}} + assert(bool(n > 0)); +#endif +} + int main() { testConstCast(); testStaticCast(); diff --git a/config_host/config_locales.h.in b/config_host/config_locales.h.in index b9b6988cc891..18d3f041dbc5 100644 --- a/config_host/config_locales.h.in +++ b/config_host/config_locales.h.in @@ -38,8 +38,8 @@ #define WITH_LOCALE_hu 0 #define WITH_LOCALE_is 0 #define WITH_LOCALE_ja 0 +#define WITH_LOCALE_kmr_Latn 0 #define WITH_LOCALE_ko 0 -#define WITH_LOCALE_ku 0 #define WITH_LOCALE_ln 0 #define WITH_LOCALE_my 0 #define WITH_LOCALE_ne 0 diff --git a/configure.ac b/configure.ac index 166e261ec39c..2b9a769034cb 100644 --- a/configure.ac +++ b/configure.ac @@ -1108,8 +1108,6 @@ cygwin*|wsl*) DLLPOST=".dll" LINKFLAGSNOUNDEFS= - # bypass check for ninja, it is not in default path, but it can be used nevertheless by default - NINJA="assumed to be available from Visual Studio" if test "$host_cpu" = "aarch64"; then build_skia=yes @@ -1498,6 +1496,7 @@ if test "$_os" != "WINNT"; then # Clang 12.0.1 occasionally SEGVs on some of the test invocations during AC_PROG_CC with -O2): save_CFLAGS=$CFLAGS CFLAGS=-g + ac_cv_prog_cc_c23=no AC_PROG_CC CFLAGS=$save_CFLAGS if test -z "$CC_BASE"; then @@ -4758,6 +4757,10 @@ if test "$_os" = "WINNT"; then PathFormat "$VC_PRODUCT_DIR/../Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin/cmake.exe" CMAKE="$formatted_path" + # Find the ninja that ships with Visual Studio, beside that cmake. + PathFormat "$VC_PRODUCT_DIR/../Common7/IDE/CommonExtensions/Microsoft/CMake/Ninja/ninja.exe" + NINJA="$formatted_path" + # Find the version of devenv.exe PathFormat "$VC_PRODUCT_DIR/../Common7/IDE/devenv.exe" DEVENV="$formatted_path" @@ -10641,9 +10644,9 @@ else fi if test "$COM" = "MSC"; then if test "$MSVC_USE_DEBUG_RUNTIME" = TRUE; then - LIBXML_LIBS="${WORKDIR}/UnpackedTarball/libxml2/Debug/libxml2d.lib" + LIBXML_LIBS="${WORKDIR}/UnpackedTarball/libxml2/libxml2d.lib" else - LIBXML_LIBS="${WORKDIR}/UnpackedTarball/libxml2/Release/libxml2.lib" + LIBXML_LIBS="${WORKDIR}/UnpackedTarball/libxml2/libxml2.lib" fi else LIBXML_LIBS="-L${WORKDIR}/UnpackedTarball/libxml2/.libs -lxml2" @@ -15020,7 +15023,7 @@ if test "$need_meson" = "yes"; then fi fi -if test -z "$NINJA" || test "$NINJA" = 'assumed to be available from Visual Studio'; then +if test -z "$NINJA"; then x_NINJA=[\#] else x_NINJA= diff --git a/connectivity/qa/connectivity/commontools/DBTypeConversion_test.cxx b/connectivity/qa/connectivity/commontools/DBTypeConversion_test.cxx index a79369149a23..9fb608a228fc 100644 --- a/connectivity/qa/connectivity/commontools/DBTypeConversion_test.cxx +++ b/connectivity/qa/connectivity/commontools/DBTypeConversion_test.cxx @@ -52,6 +52,19 @@ public: void test_toDateTime_date_only(); void test_toDateTime_with_nanoseconds(); + // toTimeString / toDateTimeString tests + void test_toTimeString_no_fraction_when_zero_nanoseconds(); + void test_toTimeString_keeps_full_precision(); + void test_toDateTimeString_no_fraction_when_zero_nanoseconds(); + + // fractional-seconds rounding for SQL literal generation + void test_roundTimeFraction_rounds_to_4_digits(); + void test_roundTimeFraction_carries_into_seconds(); + void test_roundTimeFraction_wraps_at_midnight(); + void test_roundDateTimeFraction_carries_into_next_day(); + void test_roundDateTimeFraction_rolls_over_month_end(); + void test_roundTimeFraction_noop_when_no_cap_requested(); + CPPUNIT_TEST_SUITE(DBTypeConversionTest); CPPUNIT_TEST(test_toDate_basic); @@ -66,6 +79,17 @@ public: CPPUNIT_TEST(test_toDateTime_date_only); CPPUNIT_TEST(test_toDateTime_with_nanoseconds); + CPPUNIT_TEST(test_toTimeString_no_fraction_when_zero_nanoseconds); + CPPUNIT_TEST(test_toTimeString_keeps_full_precision); + CPPUNIT_TEST(test_toDateTimeString_no_fraction_when_zero_nanoseconds); + + CPPUNIT_TEST(test_roundTimeFraction_rounds_to_4_digits); + CPPUNIT_TEST(test_roundTimeFraction_carries_into_seconds); + CPPUNIT_TEST(test_roundTimeFraction_wraps_at_midnight); + CPPUNIT_TEST(test_roundDateTimeFraction_carries_into_next_day); + CPPUNIT_TEST(test_roundDateTimeFraction_rolls_over_month_end); + CPPUNIT_TEST(test_roundTimeFraction_noop_when_no_cap_requested); + CPPUNIT_TEST_SUITE_END(); }; @@ -166,6 +190,118 @@ void DBTypeConversionTest::test_toDateTime_with_nanoseconds() CPPUNIT_ASSERT_EQUAL_MESSAGE("NanoSeconds", sal_uInt32(500000000), aDT.NanoSeconds); } +// ----- toTimeString / toDateTimeString --------------------------------------- + +void DBTypeConversionTest::test_toTimeString_no_fraction_when_zero_nanoseconds() +{ + // tdf#153057 (reopened): TIME values must not get trailing ".000000000" + css::util::Time aTime; + aTime.Hours = 0; + aTime.Minutes = 3; + aTime.Seconds = 0; + aTime.NanoSeconds = 0; + CPPUNIT_ASSERT_EQUAL(u"00:03:00"_ustr, DBTypeConversion::toTimeString(aTime)); +} + +void DBTypeConversionTest::test_toTimeString_keeps_full_precision() +{ + css::util::Time aTime; + aTime.Hours = 0; + aTime.Minutes = 3; + aTime.Seconds = 0; + aTime.NanoSeconds = 123456789; + CPPUNIT_ASSERT_EQUAL(u"00:03:00.123456789"_ustr, DBTypeConversion::toTimeString(aTime)); +} + +void DBTypeConversionTest::test_roundTimeFraction_rounds_to_4_digits() +{ + // this is the rounding the Firebird is using + css::util::Time aTime; + aTime.Hours = 0; + aTime.Minutes = 3; + aTime.Seconds = 0; + aTime.NanoSeconds = 123456789; + CPPUNIT_ASSERT(!DBTypeConversion::roundTimeFraction(aTime, 4)); + // 123456789ns rounds to the nearest 1/10000s (123500000ns = .1235). + CPPUNIT_ASSERT_EQUAL(sal_uInt32(123500000), aTime.NanoSeconds); + // toTimeString() trims trailing zeros + CPPUNIT_ASSERT_EQUAL(u"00:03:00.1235"_ustr, DBTypeConversion::toTimeString(aTime)); +} + +void DBTypeConversionTest::test_roundTimeFraction_carries_into_seconds() +{ + css::util::Time aTime; + aTime.Hours = 0; + aTime.Minutes = 3; + aTime.Seconds = 59; + aTime.NanoSeconds = 999960000; // rounds up to a full extra second + CPPUNIT_ASSERT(!DBTypeConversion::roundTimeFraction(aTime, 4)); + CPPUNIT_ASSERT_EQUAL(u"00:04:00"_ustr, DBTypeConversion::toTimeString(aTime)); +} + +void DBTypeConversionTest::test_roundTimeFraction_wraps_at_midnight() +{ + css::util::Time aTime; + aTime.Hours = 23; + aTime.Minutes = 59; + aTime.Seconds = 59; + aTime.NanoSeconds = 999950000; + CPPUNIT_ASSERT(DBTypeConversion::roundTimeFraction(aTime, 4)); + CPPUNIT_ASSERT_EQUAL(u"00:00:00"_ustr, DBTypeConversion::toTimeString(aTime)); +} + +void DBTypeConversionTest::test_roundDateTimeFraction_carries_into_next_day() +{ + css::util::DateTime aDT; + aDT.Year = 2024; + aDT.Month = 7; + aDT.Day = 15; + aDT.Hours = 23; + aDT.Minutes = 59; + aDT.Seconds = 59; + aDT.NanoSeconds = 999950000; + DBTypeConversion::roundDateTimeFraction(aDT, 4); + CPPUNIT_ASSERT_EQUAL(u"2024-07-16 00:00:00"_ustr, DBTypeConversion::toDateTimeString(aDT)); +} + +void DBTypeConversionTest::test_roundDateTimeFraction_rolls_over_month_end() +{ + css::util::DateTime aDT; + aDT.Year = 2024; + aDT.Month = 12; + aDT.Day = 31; + aDT.Hours = 23; + aDT.Minutes = 59; + aDT.Seconds = 59; + aDT.NanoSeconds = 999950000; + DBTypeConversion::roundDateTimeFraction(aDT, 4); + CPPUNIT_ASSERT_EQUAL(u"2025-01-01 00:00:00"_ustr, DBTypeConversion::toDateTimeString(aDT)); +} + +void DBTypeConversionTest::test_roundTimeFraction_noop_when_no_cap_requested() +{ + css::util::Time aTime; + aTime.Hours = 0; + aTime.Minutes = 3; + aTime.Seconds = 0; + aTime.NanoSeconds = 123456789; + CPPUNIT_ASSERT(!DBTypeConversion::roundTimeFraction(aTime, 9)); + CPPUNIT_ASSERT_EQUAL(sal_uInt32(123456789), aTime.NanoSeconds); +} + +void DBTypeConversionTest::test_toDateTimeString_no_fraction_when_zero_nanoseconds() +{ + css::util::DateTime aDT; + aDT.Year = 2024; + aDT.Month = 7; + aDT.Day = 15; + aDT.Hours = 14; + aDT.Minutes = 30; + aDT.Seconds = 0; + aDT.NanoSeconds = 0; + CPPUNIT_ASSERT_EQUAL(u"2024-07-15 14:30:00"_ustr, DBTypeConversion::toDateTimeString(aDT)); +} + CPPUNIT_TEST_SUITE_REGISTRATION(DBTypeConversionTest); } // namespace connectivity::commontools diff --git a/connectivity/registry/firebird/org/openoffice/Office/DataAccess/Drivers.xcu b/connectivity/registry/firebird/org/openoffice/Office/DataAccess/Drivers.xcu index 11b214855bc6..889d8d01cfc3 100644 --- a/connectivity/registry/firebird/org/openoffice/Office/DataAccess/Drivers.xcu +++ b/connectivity/registry/firebird/org/openoffice/Office/DataAccess/Drivers.xcu @@ -75,6 +75,13 @@ + + + + 4 + + @@ -129,6 +136,13 @@ UserPassword + + + + 4 + + diff --git a/connectivity/source/commontools/DateConversion.cxx b/connectivity/source/commontools/DateConversion.cxx index 30e9a323dc12..98c99b7a52cc 100644 --- a/connectivity/source/commontools/DateConversion.cxx +++ b/connectivity/source/commontools/DateConversion.cxx @@ -18,6 +18,7 @@ */ #include +#include #include #include #include @@ -32,6 +33,7 @@ #include #include #include +#include #include #include @@ -48,7 +50,8 @@ using namespace ::com::sun::star::util; using namespace ::com::sun::star::uno; OUString DBTypeConversion::toSQLString(sal_Int32 eType, const Any& _rVal, - const Reference< XTypeConverter >& _rxTypeConverter) + const Reference< XTypeConverter >& _rxTypeConverter, + const Reference< XConnection >& _rxConnection) { OUStringBuffer aRet; if (_rVal.hasValue()) @@ -125,6 +128,10 @@ OUString DBTypeConversion::toSQLString(sal_Int32 eType, const Any& _rVal, // check if this is really a timestamp or only a date if ( bOk ) { + // This is a SQL literal, so cap the fraction to what + // _rxConnection's backend can parse (tdf#153057). + DBTypeConversion::roundDateTimeFraction(aDateTime, + ::dbtools::DatabaseMetaData(_rxConnection).getMaxDateTimeLiteralFractionDigits()); aRet.append("{ts '" + DBTypeConversion::toDateTimeString(aDateTime) + "'}"); @@ -178,6 +185,10 @@ OUString DBTypeConversion::toSQLString(sal_Int32 eType, const Any& _rVal, else bOk = _rVal >>= aTime; OSL_ENSURE( bOk,"DBTypeConversion::toSQLString: _rVal is not time!"); + // See the TIMESTAMP case above. + // Discarding the day-carry return: this is just a Time. + std::ignore = DBTypeConversion::roundTimeFraction(aTime, + ::dbtools::DatabaseMetaData(_rxConnection).getMaxDateTimeLiteralFractionDigits()); aRet.append("{t '" + DBTypeConversion::toTimeString(aTime) + "'}"); diff --git a/connectivity/source/commontools/dbconversion.cxx b/connectivity/source/commontools/dbconversion.cxx index 304bca1c757e..37c97a998bd9 100644 --- a/connectivity/source/commontools/dbconversion.cxx +++ b/connectivity/source/commontools/dbconversion.cxx @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -100,8 +101,18 @@ namespace dbtools ostr.fill('0'); ostr << setw(2) << rTime.Hours << ":" << setw(2) << rTime.Minutes << ":" - << setw(2) << rTime.Seconds << "." - << setw(9) << rTime.NanoSeconds; + << setw(2) << rTime.Seconds; + if (rTime.NanoSeconds) + { + std::ostringstream ostrFraction; + ostrFraction.fill('0'); + ostrFraction << setw(9) << rTime.NanoSeconds; + // Trim trailing zeros if exist, since some backends (e.g. Firebird) + // reject a literal with more fractional digits than they support. + std::string sFraction = ostrFraction.str(); + sFraction.erase(sFraction.find_last_not_of('0') + 1); + ostr << "." << sFraction; + } return OUString::createFromAscii(ostr.str()); } @@ -113,6 +124,43 @@ namespace dbtools return toDateString(aDate) + " " + toTimeString(aTime); } + bool DBTypeConversion::roundTimeFraction(css::util::Time& rTime, sal_Int32 nMaxFractionDigits) + { + // Round the fraction to at most nMaxFractionDigits digits, + // carrying into seconds/minutes/hours as needed. + if (nMaxFractionDigits >= 9 || nMaxFractionDigits < 0) + return false; + + sal_uInt32 nRoundingUnit = 1; + for (sal_Int32 i = 0; i < 9 - nMaxFractionDigits; ++i) + nRoundingUnit *= 10; + + sal_uInt32 nNanoSeconds + = ((rTime.NanoSeconds + nRoundingUnit / 2) / nRoundingUnit) * nRoundingUnit; + bool bCarriedPastMidnight = false; + if (nNanoSeconds >= o3tl::make_unsigned(nanoSecInSec)) + { + nNanoSeconds -= static_cast(nanoSecInSec); + if (++rTime.Seconds >= secInMin) + { + rTime.Seconds -= secInMin; + if (++rTime.Minutes >= minInHour) + { + rTime.Minutes -= minInHour; + // Time has no day of its own to carry into so wrap + // at 24h and report it to the caller. + if (++rTime.Hours >= 24) + { + rTime.Hours -= 24; + bCarriedPastMidnight = true; + } + } + } + } + rTime.NanoSeconds = nNanoSeconds; + return bCarriedPastMidnight; + } + css::util::Date DBTypeConversion::toDate(const sal_Int32 _nVal) { css::util::Date aReturn; @@ -221,6 +269,27 @@ namespace dbtools comphelper::date::convertDaysToDate( nTempDays, _rDate.Day, _rDate.Month, _rDate.Year ); } + void DBTypeConversion::roundDateTimeFraction(css::util::DateTime& rDateTime, sal_Int32 nMaxFractionDigits) + { + css::util::Time aTime(rDateTime.NanoSeconds, rDateTime.Seconds, rDateTime.Minutes, + rDateTime.Hours, rDateTime.IsUTC); + if (roundTimeFraction(aTime, nMaxFractionDigits)) + { + // Unlike a bare Time, a DateTime does have a date to carry into, + // so 23:59:59.99995 becomes the next day at 00:00:00 rather than + // an out-of-range 24:00:00 on the original date. + css::util::Date aDate(rDateTime.Day, rDateTime.Month, rDateTime.Year); + addDays(1, aDate); + rDateTime.Day = aDate.Day; + rDateTime.Month = aDate.Month; + rDateTime.Year = aDate.Year; + } + rDateTime.Hours = aTime.Hours; + rDateTime.Minutes = aTime.Minutes; + rDateTime.Seconds = aTime.Seconds; + rDateTime.NanoSeconds = aTime.NanoSeconds; + } + static void subDays(const sal_Int32 nDays, css::util::Date& _rDate ) { sal_Int64 nTempDays = implRelativeToAbsoluteNull(_rDate); diff --git a/connectivity/source/commontools/dbmetadata.cxx b/connectivity/source/commontools/dbmetadata.cxx index 271c120c383b..e50c813ff15d 100644 --- a/connectivity/source/commontools/dbmetadata.cxx +++ b/connectivity/source/commontools/dbmetadata.cxx @@ -300,6 +300,24 @@ namespace dbtools return doGenerate; } + sal_Int32 DatabaseMetaData::getMaxDateTimeLiteralFractionDigits() const + { + if ( !isConnected() ) + return 4; // fallback for a caller with no connection info + + sal_Int32 nDigits( 9 ); + Any setting; + if ( lcl_getDriverSetting( u"MaxDateTimeLiteralFractionDigits"_ustr, *m_pImpl, setting ) ) + { + if( ! (setting >>= nDigits) ) + { + SAL_WARN("connectivity.commontools", "getMaxDateTimeLiteralFractionDigits: unable to assign MaxDateTimeLiteralFractionDigits"); + nDigits = 4; + } + } + return nDigits; + } + bool DatabaseMetaData::shouldSubstituteParameterNames() const { bool doSubstitute( true ); diff --git a/connectivity/source/commontools/predicateinput.cxx b/connectivity/source/commontools/predicateinput.cxx index b03bda56184a..574521506a1d 100644 --- a/connectivity/source/commontools/predicateinput.cxx +++ b/connectivity/source/commontools/predicateinput.cxx @@ -128,7 +128,7 @@ namespace dbtools std::unique_ptr OPredicateInputController::implPredicateTree(OUString& _rErrorMessage, const OUString& _rStatement, const Reference< XPropertySet > & _rxField) const { - std::unique_ptr pReturn = const_cast< OSQLParser& >( m_aParser ).predicateTree( _rErrorMessage, _rStatement, m_xFormatter, _rxField ); + std::unique_ptr pReturn = const_cast< OSQLParser& >( m_aParser ).predicateTree( _rErrorMessage, _rStatement, m_xFormatter, _rxField, /*bUseRealName*/true, m_xConnection ); if ( !pReturn ) { // is it a text field ? sal_Int32 nType = DataType::OTHER; @@ -149,7 +149,7 @@ namespace dbtools { sQuoted = u"'" + sQuoted.replaceAll(u"'", u"''") + u"'"; } - pReturn = const_cast< OSQLParser& >( m_aParser ).predicateTree( _rErrorMessage, sQuoted, m_xFormatter, _rxField ); + pReturn = const_cast< OSQLParser& >( m_aParser ).predicateTree( _rErrorMessage, sQuoted, m_xFormatter, _rxField, /*bUseRealName*/true, m_xConnection ); } // one more fallback: for numeric fields, and value strings containing a decimal/thousands separator @@ -215,7 +215,7 @@ namespace dbtools sTranslated = sTranslated.replace( nCtxThdSep, nFmtThdSep ); sTranslated = sTranslated.replace( nIntermediate, nFmtDecSep ); - pReturn = const_cast< OSQLParser& >( m_aParser ).predicateTree( _rErrorMessage, sTranslated, m_xFormatter, _rxField ); + pReturn = const_cast< OSQLParser& >( m_aParser ).predicateTree( _rErrorMessage, sTranslated, m_xFormatter, _rxField, /*bUseRealName*/true, m_xConnection ); } } } diff --git a/connectivity/source/drivers/firebird/PreparedStatement.cxx b/connectivity/source/drivers/firebird/PreparedStatement.cxx index 44897cc330cd..c4ab0c0b0760 100644 --- a/connectivity/source/drivers/firebird/PreparedStatement.cxx +++ b/connectivity/source/drivers/firebird/PreparedStatement.cxx @@ -966,9 +966,9 @@ void SAL_CALL OPreparedStatement::setBytes(sal_Int32 nParameterIndex, setParameterNull(nParameterIndex, false); const sal_Int32 nMaxSize = 0xFFFF; const sal_uInt16 nSize = std::min(xBytes.getLength(), nMaxSize); - // 8000 corresponds to value from lcl_addDefaultParameters - // in dbaccess/source/filter/hsqldb/createparser.cxx - if (nSize > 8000) + // sqldata was allocated from sqllen in mallocSQLVAR, so grow it whenever the value is + // longer than the column it is bound to. + if (nSize > pVar->sqllen) { free(pVar->sqldata); pVar->sqldata = static_cast(malloc(sizeof(char) * nSize + 2)); diff --git a/connectivity/source/parse/sqlnode.cxx b/connectivity/source/parse/sqlnode.cxx index 7575010cecf1..51a4a31103b0 100644 --- a/connectivity/source/parse/sqlnode.cxx +++ b/connectivity/source/parse/sqlnode.cxx @@ -57,6 +57,7 @@ #include #include #include +#include #include #include @@ -214,6 +215,14 @@ OUString OSQLParseNode::convertDateString(const SQLParseNodeParameter& rParam, s OUString OSQLParseNode::convertDateTimeString(const SQLParseNodeParameter& rParam, const OUString& rString) { DateTime aDate = DBTypeConversion::toDateTime(rString); + + // round here in case of re-normalization on a rounded value + if (aDate.NanoSeconds) + { + DBTypeConversion::roundDateTimeFraction(aDate, rParam.aMetaData.getMaxDateTimeLiteralFractionDigits()); + return DBTypeConversion::toDateTimeString(aDate); + } + Reference< XNumberFormatsSupplier > xSupplier(rParam.xFormatter->getNumberFormatsSupplier()); Reference< XNumberFormatTypes > xTypes(xSupplier->getNumberFormats(), UNO_QUERY); @@ -226,6 +235,15 @@ OUString OSQLParseNode::convertDateTimeString(const SQLParseNodeParameter& rPara OUString OSQLParseNode::convertTimeString(const SQLParseNodeParameter& rParam, std::u16string_view rString) { css::util::Time aTime = DBTypeConversion::toTime(rString); + + // Same fallback as convertDateTimeString() above + if (aTime.NanoSeconds) + { + // Discarding the day-carry return: only time is relevant here + std::ignore = DBTypeConversion::roundTimeFraction(aTime, rParam.aMetaData.getMaxDateTimeLiteralFractionDigits()); + return DBTypeConversion::toTimeString(aTime); + } + Reference< XNumberFormatsSupplier > xSupplier(rParam.xFormatter->getNumberFormatsSupplier()); Reference< XNumberFormatTypes > xTypes(xSupplier->getNumberFormats(), UNO_QUERY); @@ -1081,6 +1099,7 @@ OSQLParseNode* OSQLParser::buildNode_Date(const double& fValue, sal_Int32 nType) case DataType::TIME: { css::util::Time aTime = DBTypeConversion::toTime(fValue); + std::ignore = DBTypeConversion::roundTimeFraction(aTime, m_aMetaData.getMaxDateTimeLiteralFractionDigits()); OUString aString = DBTypeConversion::toTimeString(aTime); pDateNode->append(new OSQLInternalNode("", SQLNodeType::Keyword, SQL_TOKEN_T)); pDateNode->append(new OSQLInternalNode(aString, SQLNodeType::String)); @@ -1089,6 +1108,7 @@ OSQLParseNode* OSQLParser::buildNode_Date(const double& fValue, sal_Int32 nType) case DataType::TIMESTAMP: { DateTime aDateTime = DBTypeConversion::toDateTime(fValue,DBTypeConversion::getNULLDate(m_xFormatter->getNumberFormatsSupplier())); + DBTypeConversion::roundDateTimeFraction(aDateTime, m_aMetaData.getMaxDateTimeLiteralFractionDigits()); if (aDateTime.Seconds || aDateTime.Minutes || aDateTime.Hours) { OUString aString = DBTypeConversion::toDateTimeString(aDateTime); @@ -1176,7 +1196,8 @@ std::mutex& OSQLParser::getMutex() std::unique_ptr OSQLParser::predicateTree(OUString& rErrorMessage, const OUString& rStatement, const Reference< css::util::XNumberFormatter > & xFormatter, const Reference< XPropertySet > & xField, - bool bUseRealName) + bool bUseRealName, + const Reference< css::sdbc::XConnection >& xConnection) { // Guard the parsing std::unique_lock aGuard(getMutex()); @@ -1187,6 +1208,7 @@ std::unique_ptr OSQLParser::predicateTree(OUString& rErrorMessage // reset the parser m_xField = xField; m_xFormatter = xFormatter; + m_aMetaData = ::dbtools::DatabaseMetaData(xConnection); if (m_xField.is()) { diff --git a/cui/source/inc/cuitabarea.hxx b/cui/source/inc/cuitabarea.hxx index 8c974f98cef2..20cb242cc2d1 100644 --- a/cui/source/inc/cuitabarea.hxx +++ b/cui/source/inc/cuitabarea.hxx @@ -728,8 +728,8 @@ private: DECL_LINK(OnMoreColorsClick, weld::Button&, void); DECL_LINK(SelectPaletteLBHdl, weld::ComboBox&, void); - DECL_LINK(ActivatePaletteColorHdl, const Color&, void); - DECL_LINK(ActivateRecentColorHdl, const Color&, void); + DECL_LINK(ActivatePaletteColorHdl, int, void); + DECL_LINK(ActivateRecentColorHdl, int, void); DECL_LINK( SelectColorModeHdl_Impl, weld::Toggleable&, void ); void UpdateToSelectedColor(const NamedColor& rNamedColor); void ChangeColor(const NamedColor &rNewColor, bool bUpdatePreset = true); diff --git a/cui/source/inc/numpages.hxx b/cui/source/inc/numpages.hxx index 65fc9364d9f1..a2af733f6d23 100644 --- a/cui/source/inc/numpages.hxx +++ b/cui/source/inc/numpages.hxx @@ -210,23 +210,23 @@ class SvxNumOptionsTabPage : public SfxTabPage OUString m_sNumCharFmtName; OUString m_sBulletCharFormatName; - Timer aInvalidateTimer; + Timer m_aInvalidateTimer; - std::unique_ptr pActNum; - std::unique_ptr pSaveNum; + std::unique_ptr m_pActNum; + std::unique_ptr m_pSaveNum; - Size aInitSize[SVX_MAX_NUM]; + Size m_aInitSize[SVX_MAX_NUM]; ImplSVEvent* m_pLevelHdlEvent; - bool bLastWidthModified : 1; - bool bModified : 1; - bool bPreset : 1; - bool bAutomaticCharStyles: 1; - bool bHTMLMode : 1; + bool m_bLastWidthModified : 1; + bool m_bModified : 1; + bool m_bPreset : 1; + bool m_bAutomaticCharStyles : 1; + bool m_bHTMLMode : 1; - std::vector aGrfNames; - vcl::Font aActBulletFont; + std::vector m_aGrfNames; + vcl::Font m_aActBulletFont; enum class NumberType { SHOW_NUMBERING = 0, @@ -234,11 +234,11 @@ class SvxNumOptionsTabPage : public SfxTabPage SHOW_BITMAP = 2, NONE = 0xff }; - NumberType nBullet; + NumberType m_nBullet; - sal_uInt16 nActNumLvl; - TypedWhichId nNumItemId; - MapUnit eCoreUnit; + sal_uInt16 m_nActNumLvl; + TypedWhichId m_nNumItemId; + MapUnit m_eCoreUnit; SvxRatioConnector m_aRatioTop; SvxRatioConnector m_aRatioBottom; @@ -289,7 +289,6 @@ class SvxNumOptionsTabPage : public SfxTabPage DECL_LINK(NumberTypeSelectHdl_Impl, weld::ComboBox&, void); DECL_LINK(LevelHdl_Impl, weld::ItemView&, void); DECL_LINK(LevelHdl, void *, void); - DECL_LINK(PopupActivateHdl_Impl, weld::Toggleable&, void); DECL_LINK(GraphicHdl_Impl, const OUString&, void); DECL_LINK(BulletHdl_Impl, weld::Button&, void); DECL_LINK(SizeHdl_Impl, weld::MetricSpinButton&, void); @@ -306,6 +305,8 @@ class SvxNumOptionsTabPage : public SfxTabPage DECL_LINK(PreviewInvalidateHdl_Impl, Timer*, void); void EditModifyHdl_Impl(const weld::TextWidget*); + void CreateGalleryMenu(); + public: SvxNumOptionsTabPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet); virtual ~SvxNumOptionsTabPage() override; diff --git a/cui/source/inc/paragrph.hxx b/cui/source/inc/paragrph.hxx index af1369fd56dd..222c11e41a51 100644 --- a/cui/source/inc/paragrph.hxx +++ b/cui/source/inc/paragrph.hxx @@ -177,6 +177,9 @@ class SvxParaAlignTabPage : public SfxTabPage std::unique_ptr m_xLetterSpacingMinimum; std::unique_ptr m_xLetterSpacingMaximum; + /// compound-based hyphenation + std::unique_ptr m_xCompoundBased; + /// glyph scaling std::unique_ptr m_xLabelGlyphScaling; std::unique_ptr m_xGlyphScalingMinimum; diff --git a/cui/source/options/optchart.cxx b/cui/source/options/optchart.cxx index d2065f94efc0..a85e6d2e7f2a 100644 --- a/cui/source/options/optchart.cxx +++ b/cui/source/options/optchart.cxx @@ -344,12 +344,13 @@ IMPL_LINK_NOARG( SvxDefaultColorOptPage, SelectPaletteLbHdl, weld::ComboBox&, vo aPaletteManager.ReloadColorSet(m_aColorIconView); } -IMPL_LINK(SvxDefaultColorOptPage, ColorActivatedHdl, const Color&, rColor, void) +IMPL_LINK(SvxDefaultColorOptPage, ColorActivatedHdl, int, nIndex, void) { sal_Int32 nIdx = m_xLbChartColors->get_selected_index(); if (nIdx != -1) { - const XColorEntry aEntry(rColor, m_xLbChartColors->get_selected_text()); + const XColorEntry aEntry(m_aColorIconView.getColor(nIndex), + m_xLbChartColors->get_selected_text()); ModifyColorEntry(aEntry, nIdx); m_SvxChartColorTableUniquePtr->replace(nIdx, aEntry); diff --git a/cui/source/options/optchart.hxx b/cui/source/options/optchart.hxx index d38ea4c161fb..3bb345e05483 100644 --- a/cui/source/options/optchart.hxx +++ b/cui/source/options/optchart.hxx @@ -53,7 +53,7 @@ private: DECL_LINK(ResetToDefaults, weld::Button&, void); DECL_LINK(AddChartColor, weld::Button&, void); DECL_LINK(RemoveChartColor, weld::Button&, void); - DECL_LINK(ColorActivatedHdl, const Color&, void); + DECL_LINK(ColorActivatedHdl, int, void); DECL_LINK(SelectPaletteLbHdl, weld::ComboBox&, void); DECL_LINK(LbChartColorsSelectionChangedHdl, weld::ItemView&, void); diff --git a/cui/source/tabpages/numpages.cxx b/cui/source/tabpages/numpages.cxx index 7ddd3158be15..99e2fc54110a 100644 --- a/cui/source/tabpages/numpages.cxx +++ b/cui/source/tabpages/numpages.cxx @@ -1126,16 +1126,16 @@ IMPL_LINK_NOARG(SvxBitmapPickTabPage, ClickAddBrowseHdl_Impl, weld::Button&, voi SvxNumOptionsTabPage::SvxNumOptionsTabPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet) : SfxTabPage(pPage, pController, u"cui/ui/numberingoptionspage.ui"_ustr, u"NumberingOptionsPage"_ustr, &rSet) - , aInvalidateTimer("cui SvxNumOptionsTabPage aInvalidateTimer") + , m_aInvalidateTimer("cui SvxNumOptionsTabPage aInvalidateTimer") , m_pLevelHdlEvent(nullptr) - , bLastWidthModified(false) - , bModified(false) - , bPreset(false) - , bAutomaticCharStyles(true) - , bHTMLMode(false) - , nBullet(NumberType::NONE) - , nActNumLvl(1) - , nNumItemId(SID_ATTR_NUMBERING_RULE) + , m_bLastWidthModified(false) + , m_bModified(false) + , m_bPreset(false) + , m_bAutomaticCharStyles(true) + , m_bHTMLMode(false) + , m_nBullet(NumberType::NONE) + , m_nActNumLvl(1) + , m_nNumItemId(SID_ATTR_NUMBERING_RULE) , m_aRatioTop(ConnectorType::Top) , m_aRatioBottom(ConnectorType::Bottom) , m_xGrid(m_xBuilder->weld_widget(u"grid2"_ustr)) @@ -1180,7 +1180,7 @@ SvxNumOptionsTabPage::SvxNumOptionsTabPage(weld::Container* pPage, weld::DialogC m_xBulRelSizeMF->set_min(SVX_NUM_REL_SIZE_MIN, FieldUnit::PERCENT); m_xBulRelSizeMF->set_increments(5, 50, FieldUnit::PERCENT); SetExchangeSupport(); - aActBulletFont = lcl_GetDefaultBulletFont(); + m_aActBulletFont = lcl_GetDefaultBulletFont(); // vertical alignment = fill makes the drawingarea expand the associated spinedits so we have to size it here const sal_Int16 aHeight = static_cast(std::max(int(m_xRatioCB->get_preferred_size().getHeight() / 2 @@ -1197,7 +1197,6 @@ SvxNumOptionsTabPage::SvxNumOptionsTabPage(weld::Container* pPage, weld::DialogC m_xBulletPB->connect_clicked(LINK(this, SvxNumOptionsTabPage, BulletHdl_Impl)); m_xFmtLB->connect_changed(LINK(this, SvxNumOptionsTabPage, NumberTypeSelectHdl_Impl)); m_xBitmapMB->connect_selected(LINK(this, SvxNumOptionsTabPage, GraphicHdl_Impl)); - m_xBitmapMB->connect_toggled(LINK(this, SvxNumOptionsTabPage, PopupActivateHdl_Impl)); m_xLevelLB->set_selection_mode(SelectionMode::Multiple); m_xLevelLB->connect_selection_changed(LINK(this, SvxNumOptionsTabPage, LevelHdl_Impl)); m_xCharFmtLB->connect_changed(LINK(this, SvxNumOptionsTabPage, CharFmtHdl_Impl)); @@ -1213,10 +1212,12 @@ SvxNumOptionsTabPage::SvxNumOptionsTabPage(weld::Container* pPage, weld::DialogC m_xSameLevelCB->connect_toggled(LINK(this, SvxNumOptionsTabPage, SameLevelHdl_Impl)); m_xBulRelSizeMF->connect_value_changed(LINK(this,SvxNumOptionsTabPage, BulRelSizeHdl_Impl)); m_xBulColLB->SetSelectHdl(LINK(this, SvxNumOptionsTabPage, BulColorHdl_Impl)); - aInvalidateTimer.SetInvokeHandler(LINK(this, SvxNumOptionsTabPage, PreviewInvalidateHdl_Impl)); - aInvalidateTimer.SetTimeout(50); + m_aInvalidateTimer.SetInvokeHandler( + LINK(this, SvxNumOptionsTabPage, PreviewInvalidateHdl_Impl)); + m_aInvalidateTimer.SetTimeout(50); - eCoreUnit = rSet.GetPool()->GetMetric(rSet.GetPool()->GetWhichIDFromSlotID(SID_ATTR_NUMBERING_RULE)); + m_eCoreUnit + = rSet.GetPool()->GetMetric(rSet.GetPool()->GetWhichIDFromSlotID(SID_ATTR_NUMBERING_RULE)); // Fill ListBox with predefined / translated numbering types. sal_uInt32 nCount = SvxNumberingTypeTable::Count(); @@ -1244,8 +1245,8 @@ SvxNumOptionsTabPage::~SvxNumOptionsTabPage() { m_xPreviewWIN.reset(); m_xBulColLB.reset(); - pActNum.reset(); - pSaveNum.reset(); + m_pActNum.reset(); + m_pSaveNum.reset(); if (m_pLevelHdlEvent) { Application::RemoveUserEvent(m_pLevelHdlEvent); @@ -1277,34 +1278,34 @@ void SvxNumOptionsTabPage::ActivatePage(const SfxItemSet& rSet) if(pExampleSet) { if(const SfxBoolItem* pPresetItem = pExampleSet->GetItemIfSet(SID_PARAM_NUM_PRESET, false)) - bPreset = pPresetItem->GetValue(); + m_bPreset = pPresetItem->GetValue(); if(const SfxUInt16Item* pLevelItem = pExampleSet->GetItemIfSet(SID_PARAM_CUR_NUM_LEVEL, false)) nTmpNumLvl = pLevelItem->GetValue(); } - if(const SvxNumBulletItem* pBulletItem = rSet.GetItemIfSet(nNumItemId, false)) + if (const SvxNumBulletItem* pBulletItem = rSet.GetItemIfSet(m_nNumItemId, false)) { - pSaveNum.reset( new SvxNumRule(pBulletItem->GetNumRule()) ); + m_pSaveNum.reset(new SvxNumRule(pBulletItem->GetNumRule())); } - bModified = (!pActNum->Get( 0 ) || bPreset); - if(*pActNum == *pSaveNum && nActNumLvl == nTmpNumLvl) + m_bModified = (!m_pActNum->Get(0) || m_bPreset); + if (*m_pActNum == *m_pSaveNum && m_nActNumLvl == nTmpNumLvl) return; - nActNumLvl = nTmpNumLvl; + m_nActNumLvl = nTmpNumLvl; sal_uInt16 nMask = 1; m_xLevelLB->unselect_all(); - if (nActNumLvl == SAL_MAX_UINT16) - m_xLevelLB->select(pActNum->GetLevelCount()); - if(nActNumLvl != SAL_MAX_UINT16) + if (m_nActNumLvl == SAL_MAX_UINT16) + m_xLevelLB->select(m_pActNum->GetLevelCount()); + if (m_nActNumLvl != SAL_MAX_UINT16) { - for(sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++) + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) { - if(nActNumLvl & nMask) + if (m_nActNumLvl & nMask) m_xLevelLB->select(i); nMask <<= 1 ; } } - *pActNum = *pSaveNum; + *m_pActNum = *m_pSaveNum; InitControls(); } @@ -1318,14 +1319,14 @@ DeactivateRC SvxNumOptionsTabPage::DeactivatePage(SfxItemSet * _pSet) bool SvxNumOptionsTabPage::FillItemSet( SfxItemSet* rSet ) { - rSet->Put(SfxUInt16Item(SID_PARAM_CUR_NUM_LEVEL, nActNumLvl)); - if(bModified && pActNum) + rSet->Put(SfxUInt16Item(SID_PARAM_CUR_NUM_LEVEL, m_nActNumLvl)); + if (m_bModified && m_pActNum) { - *pSaveNum = *pActNum; - rSet->Put(SvxNumBulletItem( *pSaveNum, nNumItemId )); + *m_pSaveNum = *m_pActNum; + rSet->Put(SvxNumBulletItem(*m_pSaveNum, m_nNumItemId)); rSet->Put(SfxBoolItem(SID_PARAM_NUM_PRESET, false)); } - return bModified; + return m_bModified; }; void SvxNumOptionsTabPage::Reset( const SfxItemSet* rSet ) @@ -1335,25 +1336,25 @@ void SvxNumOptionsTabPage::Reset( const SfxItemSet* rSet ) rSet->GetItemIfSet(SID_ATTR_NUMBERING_RULE, false); if(!pBulletItem) { - nNumItemId = rSet->GetPool()->GetWhichIDFromSlotID(SID_ATTR_NUMBERING_RULE); - pBulletItem = rSet->GetItemIfSet(nNumItemId, false); + m_nNumItemId = rSet->GetPool()->GetWhichIDFromSlotID(SID_ATTR_NUMBERING_RULE); + pBulletItem = rSet->GetItemIfSet(m_nNumItemId, false); if( !pBulletItem ) { - pBulletItem = & rSet->Get( nNumItemId ); + pBulletItem = &rSet->Get(m_nNumItemId); } } DBG_ASSERT(pBulletItem, "no item found!"); - pSaveNum.reset( new SvxNumRule(pBulletItem->GetNumRule()) ); + m_pSaveNum.reset(new SvxNumRule(pBulletItem->GetNumRule())); // insert levels if (!m_xLevelLB->n_children()) { - for(sal_uInt16 i = 1; i <= pSaveNum->GetLevelCount(); i++) + for (sal_uInt16 i = 1; i <= m_pSaveNum->GetLevelCount(); i++) m_xLevelLB->append_text(OUString::number(i)); - if(pSaveNum->GetLevelCount() > 1) + if (m_pSaveNum->GetLevelCount() > 1) { - OUString sEntry = "1 - " + OUString::number( pSaveNum->GetLevelCount() ); + OUString sEntry = "1 - " + OUString::number(m_pSaveNum->GetLevelCount()); m_xLevelLB->append_text(sEntry); m_xLevelLB->select_text(sEntry); } @@ -1365,26 +1366,26 @@ void SvxNumOptionsTabPage::Reset( const SfxItemSet* rSet ) sal_uInt16 nMask = 1; m_xLevelLB->unselect_all(); - if (nActNumLvl == SAL_MAX_UINT16) + if (m_nActNumLvl == SAL_MAX_UINT16) { - m_xLevelLB->select( pSaveNum->GetLevelCount() ); + m_xLevelLB->select(m_pSaveNum->GetLevelCount()); } else { - for(sal_uInt16 i = 0; i < pSaveNum->GetLevelCount(); i++) + for (sal_uInt16 i = 0; i < m_pSaveNum->GetLevelCount(); i++) { - if(nActNumLvl & nMask) + if (m_nActNumLvl & nMask) m_xLevelLB->select( i ); nMask <<= 1 ; } } - if(!pActNum) - pActNum.reset( new SvxNumRule(*pSaveNum) ); - else if(*pSaveNum != *pActNum) - *pActNum = *pSaveNum; - m_aPreviewWIN.SetNumRule(pActNum.get()); - m_xSameLevelCB->set_active(pActNum->IsContinuousNumbering()); + if (!m_pActNum) + m_pActNum.reset(new SvxNumRule(*m_pSaveNum)); + else if (*m_pSaveNum != *m_pActNum) + *m_pActNum = *m_pSaveNum; + m_aPreviewWIN.SetNumRule(m_pActNum.get()); + m_xSameLevelCB->set_active(m_pActNum->IsContinuousNumbering()); const SfxUInt16Item* pHtmlModeItem = rSet->GetItemIfSet( SID_HTML_MODE, false ); @@ -1396,16 +1397,16 @@ void SvxNumOptionsTabPage::Reset( const SfxItemSet* rSet ) if ( pHtmlModeItem ) { sal_uInt16 nHtmlMode = pHtmlModeItem->GetValue(); - bHTMLMode = 0 != (nHtmlMode&HTMLMODE_ON); + m_bHTMLMode = 0 != (nHtmlMode & HTMLMODE_ON); } - bool bCharFmt = pActNum->IsFeatureSupported(SvxNumRuleFlags::CHAR_STYLE); + bool bCharFmt = m_pActNum->IsFeatureSupported(SvxNumRuleFlags::CHAR_STYLE); m_xCharFmtFT->set_visible(bCharFmt); m_xCharFmtLB->set_visible(bCharFmt); - bool bContinuous = pActNum->IsFeatureSupported(SvxNumRuleFlags::CONTINUOUS); + bool bContinuous = m_pActNum->IsFeatureSupported(SvxNumRuleFlags::CONTINUOUS); - bool bAllLevel = bContinuous && !bHTMLMode; + bool bAllLevel = bContinuous && !m_bHTMLMode; m_xAllLevelFT->set_visible(bAllLevel); m_xAllLevelNF->set_visible(bAllLevel); m_xIsLegalCB->set_visible(bAllLevel); @@ -1427,13 +1428,13 @@ void SvxNumOptionsTabPage::Reset( const SfxItemSet* rSet ) } } //one must be enabled - if(!pActNum->IsFeatureSupported(SvxNumRuleFlags::ENABLE_LINKED_BMP)) + if (!m_pActNum->IsFeatureSupported(SvxNumRuleFlags::ENABLE_LINKED_BMP)) { auto nPos = m_xFmtLB->find_id(OUString::number(SVX_NUM_BITMAP|LINK_TOKEN)); if (nPos != -1) m_xFmtLB->remove(nPos); } - else if(!pActNum->IsFeatureSupported(SvxNumRuleFlags::ENABLE_EMBEDDED_BMP)) + else if (!m_pActNum->IsFeatureSupported(SvxNumRuleFlags::ENABLE_EMBEDDED_BMP)) { auto nPos = m_xFmtLB->find_id(OUString::number(SVX_NUM_BITMAP)); if (nPos != -1) @@ -1442,7 +1443,7 @@ void SvxNumOptionsTabPage::Reset( const SfxItemSet* rSet ) // MegaHack: because of a not-fixable 'design mistake/error' in Impress // delete all kinds of numeric enumerations - if(pActNum->IsFeatureSupported(SvxNumRuleFlags::NO_NUMBERS)) + if (m_pActNum->IsFeatureSupported(SvxNumRuleFlags::NO_NUMBERS)) { sal_Int32 nFmtCount = m_xFmtLB->get_count(); for(sal_Int32 i = nFmtCount; i; i--) @@ -1454,7 +1455,7 @@ void SvxNumOptionsTabPage::Reset( const SfxItemSet* rSet ) } InitControls(); - bModified = false; + m_bModified = false; const SvxLanguageItem* pLangItem = rSet->GetItemIfSet(SID_ATTR_CHAR_LANGUAGE, true); if (!pLangItem) pLangItem = rSet->GetItemIfSet(EE_CHAR_LANGUAGE, true); @@ -1487,13 +1488,13 @@ void SvxNumOptionsTabPage::InitControls() sal_uInt16 nLvl = SAL_MAX_UINT16; sal_uInt16 nHighestLevel = 0; - bool bBullColor = pActNum->IsFeatureSupported(SvxNumRuleFlags::BULLET_COLOR); - bool bBullRelSize = pActNum->IsFeatureSupported(SvxNumRuleFlags::BULLET_REL_SIZE); - for(sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++) + bool bBullColor = m_pActNum->IsFeatureSupported(SvxNumRuleFlags::BULLET_COLOR); + bool bBullRelSize = m_pActNum->IsFeatureSupported(SvxNumRuleFlags::BULLET_REL_SIZE); + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) { - if(nActNumLvl & nMask) + if (m_nActNumLvl & nMask) { - aNumFmtArr[i] = &pActNum->GetLevel(i); + aNumFmtArr[i] = &m_pActNum->GetLevel(i); bShowBullet &= aNumFmtArr[i]->GetNumberingType() == SVX_NUM_CHAR_SPECIAL; bShowBitmap &= (aNumFmtArr[i]->GetNumberingType()&(~LINK_TOKEN)) == SVX_NUM_BITMAP; if(SAL_MAX_UINT16 == nLvl) @@ -1558,8 +1559,8 @@ void SvxNumOptionsTabPage::InitControls() if(bSameSize) { - SetMetricValue(*m_xHeightMF, aFirstSize.Height(), eCoreUnit); - SetMetricValue(*m_xWidthMF, aFirstSize.Width(), eCoreUnit); + SetMetricValue(*m_xHeightMF, aFirstSize.Height(), m_eCoreUnit); + SetMetricValue(*m_xWidthMF, aFirstSize.Width(), m_eCoreUnit); } else { @@ -1605,7 +1606,7 @@ void SvxNumOptionsTabPage::InitControls() m_xBulColLB->SetNoSelection(); } m_xStartED->set_value(1); // If this isn't set then changing the bullet type to a numbered type doesn't reset the start level - switch(nBullet) + switch (m_nBullet) { case NumberType::SHOW_NUMBERING: if(bSameStart) @@ -1642,16 +1643,16 @@ void SvxNumOptionsTabPage::InitControls() else m_xCharFmtLB->set_active(-1); - m_aPreviewWIN.SetLevel(nActNumLvl); + m_aPreviewWIN.SetLevel(m_nActNumLvl); m_aPreviewWIN.Invalidate(); } // 0 - Number; 1 - Bullet; 2 - Bitmap void SvxNumOptionsTabPage::SwitchNumberType( NumberType nType ) { - if(nBullet == nType) + if (m_nBullet == nType) return; - nBullet = nType; + m_nBullet = nType; bool bBullet = (nType == NumberType::SHOW_BULLET); bool bBitmap = (nType == NumberType::SHOW_BITMAP); bool bEnableBitmap = (nType == NumberType::SHOW_BITMAP); @@ -1662,14 +1663,14 @@ void SvxNumOptionsTabPage::SwitchNumberType( NumberType nType ) m_xSuffixFT->set_visible(bNumeric); m_xSuffixED->set_visible(bNumeric); - bool bCharFmt = pActNum->IsFeatureSupported(SvxNumRuleFlags::CHAR_STYLE); + bool bCharFmt = m_pActNum->IsFeatureSupported(SvxNumRuleFlags::CHAR_STYLE); m_xCharFmtFT->set_visible(!bBitmap && bCharFmt); m_xCharFmtLB->set_visible(!bBitmap && bCharFmt); // this is rather misusage, as there is no own flag // for complete numeration - bool bAllLevelFeature = pActNum->IsFeatureSupported(SvxNumRuleFlags::CONTINUOUS); - bool bAllLevel = bNumeric && bAllLevelFeature && !bHTMLMode; + bool bAllLevelFeature = m_pActNum->IsFeatureSupported(SvxNumRuleFlags::CONTINUOUS); + bool bAllLevel = bNumeric && bAllLevelFeature && !m_bHTMLMode; m_xAllLevelFT->set_visible(bAllLevel); m_xAllLevelNF->set_visible(bAllLevel); m_xIsLegalCB->set_visible(bAllLevel); @@ -1679,15 +1680,17 @@ void SvxNumOptionsTabPage::SwitchNumberType( NumberType nType ) m_xBulletFT->set_visible(bBullet); m_xBulletPB->set_visible(bBullet); - bool bBullColor = pActNum->IsFeatureSupported(SvxNumRuleFlags::BULLET_COLOR); + bool bBullColor = m_pActNum->IsFeatureSupported(SvxNumRuleFlags::BULLET_COLOR); m_xBulColorFT->set_visible(!bBitmap && bBullColor); m_xBulColLB->set_visible(!bBitmap && bBullColor); - bool bBullResSize = pActNum->IsFeatureSupported(SvxNumRuleFlags::BULLET_REL_SIZE); + bool bBullResSize = m_pActNum->IsFeatureSupported(SvxNumRuleFlags::BULLET_REL_SIZE); m_xBulRelSizeFT->set_visible(!bBitmap && bBullResSize); m_xBulRelSizeMF->set_visible(!bBitmap && bBullResSize); m_xBitmapFT->set_visible(bBitmap); m_xBitmapMB->set_visible(bBitmap); + if (bBitmap) + CreateGalleryMenu(); m_xWidthFT->set_visible(bBitmap); m_xWidthMF->set_visible(bBitmap); @@ -1724,34 +1727,35 @@ IMPL_LINK_NOARG(SvxNumOptionsTabPage, LevelHdl, void*, void) { m_pLevelHdlEvent = nullptr; - sal_uInt16 nSaveNumLvl = nActNumLvl; - nActNumLvl = 0; + sal_uInt16 nSaveNumLvl = m_nActNumLvl; + m_nActNumLvl = 0; std::vector aSelectedRows = m_xLevelLB->get_selected_rows(); - if (std::find(aSelectedRows.begin(), aSelectedRows.end(), pActNum->GetLevelCount()) != aSelectedRows.end() && - (aSelectedRows.size() == 1 || nSaveNumLvl != 0xffff)) + if (std::find(aSelectedRows.begin(), aSelectedRows.end(), m_pActNum->GetLevelCount()) + != aSelectedRows.end() + && (aSelectedRows.size() == 1 || nSaveNumLvl != 0xffff)) { - nActNumLvl = 0xFFFF; - for( sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++ ) - m_xLevelLB->unselect(i); + m_nActNumLvl = 0xFFFF; + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) + m_xLevelLB->unselect(i); } else if (!aSelectedRows.empty()) { sal_uInt16 nMask = 1; - for( sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++ ) + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) { if (std::find(aSelectedRows.begin(), aSelectedRows.end(), i) != aSelectedRows.end()) - nActNumLvl |= nMask; + m_nActNumLvl |= nMask; nMask <<= 1; } - m_xLevelLB->unselect(pActNum->GetLevelCount()); + m_xLevelLB->unselect(m_pActNum->GetLevelCount()); } else { - nActNumLvl = nSaveNumLvl; + m_nActNumLvl = nSaveNumLvl; sal_uInt16 nMask = 1; - for( sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++ ) + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) { - if(nActNumLvl & nMask) + if (m_nActNumLvl & nMask) { m_xLevelLB->select(i); break; @@ -1770,15 +1774,15 @@ IMPL_LINK_NOARG(SvxNumOptionsTabPage, PreviewInvalidateHdl_Impl, Timer *, void) IMPL_LINK(SvxNumOptionsTabPage, AllLevelHdl_Impl, weld::SpinButton&, rBox, void) { sal_uInt16 nMask = 1; - for(sal_uInt16 e = 0; e < pActNum->GetLevelCount(); e++) + for (sal_uInt16 e = 0; e < m_pActNum->GetLevelCount(); e++) { - if(nActNumLvl & nMask) + if (m_nActNumLvl & nMask) { - SvxNumberFormat aNumFmt(pActNum->GetLevel(e)); + SvxNumberFormat aNumFmt(m_pActNum->GetLevel(e)); aNumFmt.SetIncludeUpperLevels(static_cast(std::min(rBox.get_value(), sal_Int64(e + 1))) ); // Set the same prefix/suffix to generate list format with changed IncludedUpperLevels aNumFmt.SetListFormat(aNumFmt.GetPrefix(), aNumFmt.GetSuffix(), e); - pActNum->SetLevel(e, aNumFmt); + m_pActNum->SetLevel(e, aNumFmt); } nMask <<= 1; } @@ -1788,13 +1792,13 @@ IMPL_LINK(SvxNumOptionsTabPage, AllLevelHdl_Impl, weld::SpinButton&, rBox, void) IMPL_LINK(SvxNumOptionsTabPage, IsLegalHdl_Impl, weld::Toggleable&, rBox, void) { bool bSet = rBox.get_active(); - for (sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++) + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) { - if (nActNumLvl & (sal_uInt16(1) << i)) + if (m_nActNumLvl & (sal_uInt16(1) << i)) { - SvxNumberFormat aNumFmt(pActNum->GetLevel(i)); + SvxNumberFormat aNumFmt(m_pActNum->GetLevel(i)); aNumFmt.SetIsLegal(bSet); - pActNum->SetLevel(i, aNumFmt); + m_pActNum->SetLevel(i, aNumFmt); } } SetModified(); @@ -1806,11 +1810,11 @@ IMPL_LINK(SvxNumOptionsTabPage, NumberTypeSelectHdl_Impl, weld::ComboBox&, rBox, bool bShowOrient = false; bool bBmp = false; sal_uInt16 nMask = 1; - for(sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++) + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) { - if(nActNumLvl & nMask) + if (m_nActNumLvl & nMask) { - SvxNumberFormat aNumFmt(pActNum->GetLevel(i)); + SvxNumberFormat aNumFmt(m_pActNum->GetLevel(i)); // PAGEDESC does not exist SvxNumType nNumType = static_cast(rBox.get_active_id().toUInt32()); aNumFmt.SetNumberingType(nNumType); @@ -1822,7 +1826,7 @@ IMPL_LINK(SvxNumOptionsTabPage, NumberTypeSelectHdl_Impl, weld::ComboBox&, rBox, aNumFmt.SetListFormat(u""_ustr, u""_ustr, i); if(!bBmp) aNumFmt.SetGraphic(u""_ustr); - pActNum->SetLevel(i, aNumFmt); + m_pActNum->SetLevel(i, aNumFmt); SwitchNumberType(NumberType::SHOW_BITMAP); bShowOrient = true; } @@ -1831,13 +1835,13 @@ IMPL_LINK(SvxNumOptionsTabPage, NumberTypeSelectHdl_Impl, weld::ComboBox&, rBox, aNumFmt.SetIncludeUpperLevels( 1 ); aNumFmt.SetListFormat(u""_ustr, u""_ustr, i); if( !aNumFmt.GetBulletFont() ) - aNumFmt.SetBulletFont(&aActBulletFont); + aNumFmt.SetBulletFont(&m_aActBulletFont); if( !aNumFmt.GetBulletChar() ) aNumFmt.SetBulletChar( SVX_DEF_BULLET ); - pActNum->SetLevel(i, aNumFmt); + m_pActNum->SetLevel(i, aNumFmt); SwitchNumberType(NumberType::SHOW_BULLET); // allocation of the drawing pattern is automatic - if(bAutomaticCharStyles) + if (m_bAutomaticCharStyles) { sSelectStyle = m_sBulletCharFormatName; } @@ -1847,11 +1851,11 @@ IMPL_LINK(SvxNumOptionsTabPage, NumberTypeSelectHdl_Impl, weld::ComboBox&, rBox, aNumFmt.SetListFormat(m_xPrefixED->get_text(), m_xSuffixED->get_text(), i); SwitchNumberType(NumberType::SHOW_NUMBERING); - pActNum->SetLevel(i, aNumFmt); + m_pActNum->SetLevel(i, aNumFmt); CheckForStartValue_Impl(nNumberingType); // allocation of the drawing pattern is automatic - if(bAutomaticCharStyles) + if (m_bAutomaticCharStyles) { sSelectStyle = m_sNumCharFmtName; } @@ -1859,7 +1863,7 @@ IMPL_LINK(SvxNumOptionsTabPage, NumberTypeSelectHdl_Impl, weld::ComboBox&, rBox, } nMask <<= 1; } - bool bAllLevelFeature = pActNum->IsFeatureSupported(SvxNumRuleFlags::CONTINUOUS); + bool bAllLevelFeature = m_pActNum->IsFeatureSupported(SvxNumRuleFlags::CONTINUOUS); if(bShowOrient && bAllLevelFeature) { m_xOrientFT->show(); @@ -1875,7 +1879,7 @@ IMPL_LINK(SvxNumOptionsTabPage, NumberTypeSelectHdl_Impl, weld::ComboBox&, rBox, { m_xCharFmtLB->set_active_text(sSelectStyle); CharFmtHdl_Impl(*m_xCharFmtLB); - bAutomaticCharStyles = true; + m_bAutomaticCharStyles = true; } } @@ -1896,18 +1900,18 @@ IMPL_LINK(SvxNumOptionsTabPage, OrientHdl_Impl, weld::ComboBox&, rBox, void) nPos ++; // no VERT_NONE sal_uInt16 nMask = 1; - for(sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++) + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) { - if(nActNumLvl & nMask) + if (m_nActNumLvl & nMask) { - SvxNumberFormat aNumFmt(pActNum->GetLevel(i)); + SvxNumberFormat aNumFmt(m_pActNum->GetLevel(i)); if(SVX_NUM_BITMAP == (aNumFmt.GetNumberingType()&(~LINK_TOKEN))) { const SvxBrushItem* pBrushItem = aNumFmt.GetBrush(); const Size& rSize = aNumFmt.GetGraphicSize(); sal_Int16 eOrient = static_cast(nPos); aNumFmt.SetGraphicBrush( pBrushItem, &rSize, &eOrient ); - pActNum->SetLevel(i, aNumFmt); + m_pActNum->SetLevel(i, aNumFmt); } } nMask <<= 1; @@ -1918,11 +1922,11 @@ IMPL_LINK(SvxNumOptionsTabPage, OrientHdl_Impl, weld::ComboBox&, rBox, void) IMPL_LINK(SvxNumOptionsTabPage, SameLevelHdl_Impl, weld::Toggleable&, rBox, void) { bool bSet = rBox.get_active(); - pActNum->SetContinuousNumbering(bSet); + m_pActNum->SetContinuousNumbering(bSet); bool bRepaint = false; - for(sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++) + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) { - SvxNumberFormat aNumFmt(pActNum->GetLevel(i)); + SvxNumberFormat aNumFmt(m_pActNum->GetLevel(i)); if(aNumFmt.GetNumberingType() != SVX_NUM_NUMBER_NONE) { bRepaint = true; @@ -1938,13 +1942,13 @@ IMPL_LINK(SvxNumOptionsTabPage, BulColorHdl_Impl, ColorListBox&, rColorBox, void Color nSetColor = rColorBox.GetSelectEntryColor(); sal_uInt16 nMask = 1; - for(sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++) + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) { - if(nActNumLvl & nMask) + if (m_nActNumLvl & nMask) { - SvxNumberFormat aNumFmt(pActNum->GetLevel(i)); + SvxNumberFormat aNumFmt(m_pActNum->GetLevel(i)); aNumFmt.SetBulletColor(nSetColor); - pActNum->SetLevel(i, aNumFmt); + m_pActNum->SetLevel(i, aNumFmt); } nMask <<= 1; } @@ -1956,13 +1960,13 @@ IMPL_LINK(SvxNumOptionsTabPage, BulRelSizeHdl_Impl, weld::MetricSpinButton&, rFi sal_uInt16 nRelSize = o3tl::sanitizing_cast(rField.get_value(FieldUnit::PERCENT)); sal_uInt16 nMask = 1; - for(sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++) + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) { - if(nActNumLvl & nMask) + if (m_nActNumLvl & nMask) { - SvxNumberFormat aNumFmt(pActNum->GetLevel(i)); + SvxNumberFormat aNumFmt(m_pActNum->GetLevel(i)); aNumFmt.SetBulletRelSize(nRelSize); - pActNum->SetLevel(i, aNumFmt); + m_pActNum->SetLevel(i, aNumFmt); } nMask <<= 1; } @@ -1980,9 +1984,9 @@ IMPL_LINK(SvxNumOptionsTabPage, GraphicHdl_Impl, const OUString&, rIdent, void) if (rIdent.startsWith("gallery", &sNumber)) { auto idx = o3tl::toUInt32(sNumber); - if (idx < aGrfNames.size()) + if (idx < m_aGrfNames.size()) { - aGrfName = aGrfNames[idx]; + aGrfName = m_aGrfNames[idx]; Graphic aGraphic; if(GalleryExplorer::GetGraphicObj( GALLERY_THEME_BULLETS, idx, &aGraphic)) { @@ -2011,14 +2015,14 @@ IMPL_LINK(SvxNumOptionsTabPage, GraphicHdl_Impl, const OUString&, rIdent, void) if(!bSucc) return; - aSize = OutputDevice::LogicToLogic(aSize, MapMode(MapUnit::Map100thMM), MapMode(eCoreUnit)); + aSize = OutputDevice::LogicToLogic(aSize, MapMode(MapUnit::Map100thMM), MapMode(m_eCoreUnit)); sal_uInt16 nMask = 1; - for(sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++) + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) { - if(nActNumLvl & nMask) + if (m_nActNumLvl & nMask) { - SvxNumberFormat aNumFmt(pActNum->GetLevel(i)); + SvxNumberFormat aNumFmt(m_pActNum->GetLevel(i)); aNumFmt.SetCharFormatName(m_sNumCharFmtName); aNumFmt.SetGraphic(aGrfName); @@ -2027,9 +2031,9 @@ IMPL_LINK(SvxNumOptionsTabPage, GraphicHdl_Impl, const OUString&, rIdent, void) // initiate asynchronous loading sal_Int16 eOrient = aNumFmt.GetVertOrient(); aNumFmt.SetGraphicBrush( pBrushItem, &aSize, &eOrient ); - aInitSize[i] = aNumFmt.GetGraphicSize(); + m_aInitSize[i] = aNumFmt.GetGraphicSize(); - pActNum->SetLevel(i, aNumFmt); + m_pActNum->SetLevel(i, aNumFmt); } nMask <<= 1; } @@ -2038,16 +2042,16 @@ IMPL_LINK(SvxNumOptionsTabPage, GraphicHdl_Impl, const OUString&, rIdent, void) m_xHeightFT->set_sensitive(true); m_xWidthMF->set_sensitive(true); m_xHeightMF->set_sensitive(true); - SetMetricValue(*m_xWidthMF, aSize.Width(), eCoreUnit); - SetMetricValue(*m_xHeightMF, aSize.Height(), eCoreUnit); + SetMetricValue(*m_xWidthMF, aSize.Width(), m_eCoreUnit); + SetMetricValue(*m_xHeightMF, aSize.Height(), m_eCoreUnit); m_xOrientFT->set_sensitive(true); m_xOrientLB->set_sensitive(true); SetModified(); //needed due to asynchronous loading of graphics in the SvxBrushItem - aInvalidateTimer.Start(); + m_aInvalidateTimer.Start(); } -IMPL_LINK_NOARG(SvxNumOptionsTabPage, PopupActivateHdl_Impl, weld::Toggleable&, void) +void SvxNumOptionsTabPage::CreateGalleryMenu() { if (m_xGalleryMenu) return; @@ -2055,7 +2059,7 @@ IMPL_LINK_NOARG(SvxNumOptionsTabPage, PopupActivateHdl_Impl, weld::Toggleable&, m_xGalleryMenu = m_xBuilder->weld_menu(u"gallerysubmenu"_ustr); weld::WaitObject aWait(GetFrameWeld()); - if (!GalleryExplorer::FillObjList(GALLERY_THEME_BULLETS, aGrfNames)) + if (!GalleryExplorer::FillObjList(GALLERY_THEME_BULLETS, m_aGrfNames)) return; GalleryExplorer::BeginLocking(GALLERY_THEME_BULLETS); @@ -2064,7 +2068,7 @@ IMPL_LINK_NOARG(SvxNumOptionsTabPage, PopupActivateHdl_Impl, weld::Toggleable&, OUString sGrfName; ScopedVclPtrInstance< VirtualDevice > pVD; size_t i = 0; - for (const auto & grfName : aGrfNames) + for (const auto& grfName : m_aGrfNames) { sGrfName = grfName; OUString sItemId = "gallery" + OUString::number(i); @@ -2110,11 +2114,11 @@ IMPL_LINK_NOARG(SvxNumOptionsTabPage, BulletHdl_Impl, weld::Button&, void) bool bSameBullet = true; sal_UCS4 cBullet = 0; bool bFirst = true; - for(sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++) + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) { - if(nActNumLvl & nMask) + if (m_nActNumLvl & nMask) { - const SvxNumberFormat& rCurFmt = pActNum->GetLevel(i); + const SvxNumberFormat& rCurFmt = m_pActNum->GetLevel(i); if(bFirst) { cBullet = rCurFmt.GetBulletChar(); @@ -2135,24 +2139,24 @@ IMPL_LINK_NOARG(SvxNumOptionsTabPage, BulletHdl_Impl, weld::Button&, void) if (pFmtFont) aMap.SetCharFont(*pFmtFont); else - aMap.SetCharFont(aActBulletFont); + aMap.SetCharFont(m_aActBulletFont); if (bSameBullet) aMap.SetChar(cBullet); if (aMap.run() != RET_OK) return; // change Font Numrules - aActBulletFont = aMap.GetCharFont(); + m_aActBulletFont = aMap.GetCharFont(); sal_uInt16 _nMask = 1; - for(sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++) + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) { - if(nActNumLvl & _nMask) + if (m_nActNumLvl & _nMask) { - SvxNumberFormat aNumFmt(pActNum->GetLevel(i)); - aNumFmt.SetBulletFont(&aActBulletFont); + SvxNumberFormat aNumFmt(m_pActNum->GetLevel(i)); + aNumFmt.SetBulletFont(&m_aActBulletFont); aNumFmt.SetBulletChar(aMap.GetChar()); - pActNum->SetLevel(i, aNumFmt); + m_pActNum->SetLevel(i, aNumFmt); } _nMask <<= 1; } @@ -2163,55 +2167,59 @@ IMPL_LINK_NOARG(SvxNumOptionsTabPage, BulletHdl_Impl, weld::Button&, void) IMPL_LINK( SvxNumOptionsTabPage, SizeHdl_Impl, weld::MetricSpinButton&, rField, void) { bool bWidth = &rField == m_xWidthMF.get(); - bLastWidthModified = bWidth; + m_bLastWidthModified = bWidth; bool bRatio = m_xRatioCB->get_active(); tools::Long nWidthVal = static_cast(m_xWidthMF->denormalize(m_xWidthMF->get_value(FieldUnit::MM_100TH))); tools::Long nHeightVal = static_cast(m_xHeightMF->denormalize(m_xHeightMF->get_value(FieldUnit::MM_100TH))); - nWidthVal = OutputDevice::LogicToLogic( nWidthVal , - MapUnit::Map100thMM, eCoreUnit ); - nHeightVal = OutputDevice::LogicToLogic( nHeightVal, - MapUnit::Map100thMM, eCoreUnit); + nWidthVal = OutputDevice::LogicToLogic(nWidthVal, MapUnit::Map100thMM, m_eCoreUnit); + nHeightVal = OutputDevice::LogicToLogic(nHeightVal, MapUnit::Map100thMM, m_eCoreUnit); double fSizeRatio; bool bRepaint = false; sal_uInt16 nMask = 1; - for(sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++) + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) { - if(nActNumLvl & nMask) + if (m_nActNumLvl & nMask) { - SvxNumberFormat aNumFmt(pActNum->GetLevel(i)); + SvxNumberFormat aNumFmt(m_pActNum->GetLevel(i)); if(SVX_NUM_BITMAP == (aNumFmt.GetNumberingType()&(~LINK_TOKEN))) { Size aSize(aNumFmt.GetGraphicSize() ); Size aSaveSize(aSize); - if (aInitSize[i].Height()) - fSizeRatio = static_cast(aInitSize[i].Width()) / static_cast(aInitSize[i].Height()); + if (m_aInitSize[i].Height()) + fSizeRatio = static_cast(m_aInitSize[i].Width()) + / static_cast(m_aInitSize[i].Height()); else fSizeRatio = double(1); if(bWidth) { - tools::Long nDelta = nWidthVal - aInitSize[i].Width(); + tools::Long nDelta = nWidthVal - m_aInitSize[i].Width(); aSize.setWidth( nWidthVal ); if (bRatio) { - aSize.setHeight( aInitSize[i].Height() + static_cast(static_cast(nDelta) / fSizeRatio) ); - m_xHeightMF->set_value(m_xHeightMF->normalize( - OutputDevice::LogicToLogic( aSize.Height(), eCoreUnit, MapUnit::Map100thMM )), - FieldUnit::MM_100TH); + aSize.setHeight( + m_aInitSize[i].Height() + + static_cast(static_cast(nDelta) / fSizeRatio)); + m_xHeightMF->set_value( + m_xHeightMF->normalize(OutputDevice::LogicToLogic( + aSize.Height(), m_eCoreUnit, MapUnit::Map100thMM)), + FieldUnit::MM_100TH); } } else { - tools::Long nDelta = nHeightVal - aInitSize[i].Height(); + tools::Long nDelta = nHeightVal - m_aInitSize[i].Height(); aSize.setHeight( nHeightVal ); if (bRatio) { - aSize.setWidth( aInitSize[i].Width() + static_cast(static_cast(nDelta) * fSizeRatio) ); - m_xWidthMF->set_value(m_xWidthMF->normalize( - OutputDevice::LogicToLogic( aSize.Width(), eCoreUnit, MapUnit::Map100thMM )), - FieldUnit::MM_100TH); + aSize.setWidth( + m_aInitSize[i].Width() + + static_cast(static_cast(nDelta) * fSizeRatio)); + m_xWidthMF->set_value(m_xWidthMF->normalize(OutputDevice::LogicToLogic( + aSize.Width(), m_eCoreUnit, MapUnit::Map100thMM)), + FieldUnit::MM_100TH); } } const SvxBrushItem* pBrushItem = aNumFmt.GetBrush(); @@ -2219,7 +2227,7 @@ IMPL_LINK( SvxNumOptionsTabPage, SizeHdl_Impl, weld::MetricSpinButton&, rField, if(aSize != aSaveSize) bRepaint = true; aNumFmt.SetGraphicBrush( pBrushItem, &aSize, &eOrient ); - pActNum->SetLevel(i, aNumFmt); + m_pActNum->SetLevel(i, aNumFmt); } } nMask <<= 1; @@ -2232,7 +2240,7 @@ IMPL_LINK(SvxNumOptionsTabPage, RatioHdl_Impl, weld::Toggleable&, rBox, void) m_xCbxScaleImg->set_from_icon_name(m_xRatioCB->get_active() ? RID_SVXBMP_LOCKED : RID_SVXBMP_UNLOCKED); if (rBox.get_active()) { - if (bLastWidthModified) + if (m_bLastWidthModified) SizeHdl_Impl(*m_xWidthMF); else SizeHdl_Impl(*m_xHeightMF); @@ -2241,15 +2249,15 @@ IMPL_LINK(SvxNumOptionsTabPage, RatioHdl_Impl, weld::Toggleable&, rBox, void) IMPL_LINK_NOARG(SvxNumOptionsTabPage, CharFmtHdl_Impl, weld::ComboBox&, void) { - bAutomaticCharStyles = false; + m_bAutomaticCharStyles = false; sal_Int32 nEntryPos = m_xCharFmtLB->get_active(); OUString sEntry = m_xCharFmtLB->get_active_text(); sal_uInt16 nMask = 1; - for(sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++) + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) { - if(nActNumLvl & nMask) + if (m_nActNumLvl & nMask) { - SvxNumberFormat aNumFmt(pActNum->GetLevel(i)); + SvxNumberFormat aNumFmt(m_pActNum->GetLevel(i)); if( 0 == nEntryPos ) aNumFmt.SetCharFormatName(u""_ustr); else @@ -2257,7 +2265,7 @@ IMPL_LINK_NOARG(SvxNumOptionsTabPage, CharFmtHdl_Impl, weld::ComboBox&, void) if(SVX_NUM_BITMAP != (aNumFmt.GetNumberingType()&(~LINK_TOKEN))) aNumFmt.SetCharFormatName(sEntry); } - pActNum->SetLevel(i, aNumFmt); + m_pActNum->SetLevel(i, aNumFmt); } nMask <<= 1; } @@ -2279,16 +2287,16 @@ void SvxNumOptionsTabPage::EditModifyHdl_Impl(const weld::TextWidget* pEdit) bool bPrefixSuffix = (pEdit == m_xPrefixED.get())|| (pEdit == m_xSuffixED.get()); bool bStart = pEdit == m_xStartED.get(); sal_uInt16 nMask = 1; - for(sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++) + for (sal_uInt16 i = 0; i < m_pActNum->GetLevelCount(); i++) { - if(nActNumLvl & nMask) + if (m_nActNumLvl & nMask) { - SvxNumberFormat aNumFmt(pActNum->GetLevel(i)); + SvxNumberFormat aNumFmt(m_pActNum->GetLevel(i)); if (bPrefixSuffix) aNumFmt.SetListFormat(m_xPrefixED->get_text(), m_xSuffixED->get_text(), i); else if(bStart) aNumFmt.SetStart(m_xStartED->get_value()); - pActNum->SetLevel(i, aNumFmt); + m_pActNum->SetLevel(i, aNumFmt); } nMask <<= 1; } @@ -3150,10 +3158,10 @@ void SvxNumPositionTabPage::SetModified() void SvxNumOptionsTabPage::SetModified(bool bRepaint) { - bModified = true; + m_bModified = true; if (bRepaint) { - m_aPreviewWIN.SetLevel(nActNumLvl); + m_aPreviewWIN.SetLevel(m_nActNumLvl); m_aPreviewWIN.Invalidate(); } } diff --git a/cui/source/tabpages/paragrph.cxx b/cui/source/tabpages/paragrph.cxx index 8c4917c1a9c6..a9e246b4efce 100644 --- a/cui/source/tabpages/paragrph.cxx +++ b/cui/source/tabpages/paragrph.cxx @@ -1299,6 +1299,7 @@ SvxParaAlignTabPage::SvxParaAlignTabPage(weld::Container* pPage, weld::DialogCon , m_xLabelLetterSpacing(m_xBuilder->weld_label(u"labelLetterSpacing"_ustr)) , m_xLetterSpacingMinimum(m_xBuilder->weld_metric_spin_button(u"spin_LETTER_SPACING_MIN"_ustr, FieldUnit::PERCENT)) , m_xLetterSpacingMaximum(m_xBuilder->weld_metric_spin_button(u"spin_LETTER_SPACING_MAX"_ustr, FieldUnit::PERCENT)) + , m_xCompoundBased(m_xBuilder->weld_check_button(u"checkCompoundBased"_ustr)) , m_xLabelGlyphScaling(m_xBuilder->weld_label(u"labelGlyphScaling"_ustr)) , m_xGlyphScalingMinimum(m_xBuilder->weld_metric_spin_button(u"spin_GLYPH_SCALING_MIN"_ustr, FieldUnit::PERCENT)) , m_xGlyphScalingMaximum(m_xBuilder->weld_metric_spin_button(u"spin_GLYPH_SCALING_MAX"_ustr, FieldUnit::PERCENT)) @@ -1409,6 +1410,7 @@ bool SvxParaAlignTabPage::FillItemSet( SfxItemSet* rOutSet ) m_xParagraphComposer->get_state_changed_from_saved() || m_xLetterSpacingMinimum->get_value_changed_from_saved() || m_xLetterSpacingMaximum->get_value_changed_from_saved() || + m_xCompoundBased->get_state_changed_from_saved() || m_xGlyphScalingMinimum->get_value_changed_from_saved() || m_xGlyphScalingMaximum->get_value_changed_from_saved(); } @@ -1436,6 +1438,7 @@ bool SvxParaAlignTabPage::FillItemSet( SfxItemSet* rOutSet ) aAdj.SetParagraphComposer( m_xParagraphComposer->get_active() ); aAdj.SetPropLetterSpacingMinimum( m_xLetterSpacingMinimum->get_value(FieldUnit::PERCENT) ); aAdj.SetPropLetterSpacingMaximum( m_xLetterSpacingMaximum->get_value(FieldUnit::PERCENT) ); + aAdj.SetCompoundBased( m_xCompoundBased->get_active() ); aAdj.SetPropScaleWidthMinimum( m_xGlyphScalingMinimum->get_value(FieldUnit::PERCENT) ); aAdj.SetPropScaleWidthMaximum( m_xGlyphScalingMaximum->get_value(FieldUnit::PERCENT) ); rOutSet->Put( aAdj ); @@ -1543,6 +1546,8 @@ void SvxParaAlignTabPage::Reset( const SfxItemSet* rSet ) m_xLetterSpacingMinimum->set_sensitive(true); m_xLetterSpacingMinimum->set_value(rAdj.GetPropLetterSpacingMinimum(), FieldUnit::PERCENT); m_xLetterSpacingMaximum->set_value(rAdj.GetPropLetterSpacingMaximum(), FieldUnit::PERCENT); + m_xCompoundBased->set_sensitive(true); + m_xCompoundBased->set_active(rAdj.GetCompoundBased()); // TODO add GlyphScaling (CharScaleWidth) m_xGlyphScalingMaximum->set_sensitive(true); m_xGlyphScalingMinimum->set_sensitive(true); @@ -1559,10 +1564,10 @@ void SvxParaAlignTabPage::Reset( const SfxItemSet* rSet ) m_xWordSpacingMinimum->set_sensitive(false); m_xWordSpacingMaximum->set_sensitive(false); m_xParagraphComposer->set_sensitive(false); - m_xParagraphComposer->set_sensitive(false); m_xLabelLetterSpacing->set_sensitive(false); m_xLetterSpacingMinimum->set_sensitive(false); m_xLetterSpacingMaximum->set_sensitive(false); + m_xCompoundBased->set_sensitive(false); m_xLabelGlyphScaling->set_sensitive(false); m_xGlyphScalingMinimum->set_sensitive(false); m_xGlyphScalingMaximum->set_sensitive(false); @@ -1664,6 +1669,7 @@ void SvxParaAlignTabPage::Reset( const SfxItemSet* rSet ) m_xParagraphComposer->save_state(); m_xLetterSpacingMinimum->save_value(); m_xLetterSpacingMaximum->save_value(); + m_xCompoundBased->save_state(); m_xGlyphScalingMinimum->save_value(); m_xGlyphScalingMaximum->save_value(); @@ -1690,6 +1696,7 @@ void SvxParaAlignTabPage::ChangesApplied() m_xParagraphComposer->save_state(); m_xLetterSpacingMinimum->save_value(); m_xLetterSpacingMaximum->save_value(); + m_xCompoundBased->save_state(); m_xGlyphScalingMinimum->save_value(); m_xGlyphScalingMaximum->save_value(); } @@ -1711,6 +1718,7 @@ IMPL_LINK_NOARG(SvxParaAlignTabPage, AlignHdl_Impl, weld::Toggleable&, void) // TODO visualize CharKerning with percentage m_xLetterSpacingMinimum->set_sensitive(bJustify); m_xLetterSpacingMaximum->set_sensitive(bJustify); + m_xCompoundBased->set_sensitive(bJustify); m_xLabelGlyphScaling->set_sensitive(bJustify); // TODO visualize CharScaleWidth with percentage m_xGlyphScalingMinimum->set_sensitive(bJustify); diff --git a/cui/source/tabpages/tpcolor.cxx b/cui/source/tabpages/tpcolor.cxx index bfe1892354d1..4d35e7c4a86e 100644 --- a/cui/source/tabpages/tpcolor.cxx +++ b/cui/source/tabpages/tpcolor.cxx @@ -185,7 +185,7 @@ void SvxColorTabPage::ActivatePage( const SfxItemSet& ) if (nSelectedIndex < 0) return; - ActivatePaletteColorHdl(m_aColorIconView.getColor(nSelectedIndex)); + ActivatePaletteColorHdl(nSelectedIndex); } DeactivateRC SvxColorTabPage::DeactivatePage( SfxItemSet* _pSet ) @@ -388,7 +388,7 @@ IMPL_LINK_NOARG(SvxColorTabPage, ClickDeleteHdl_Impl, weld::Button&, void) if (m_aColorIconView.getItemCount() > 0) { m_aColorIconView.select(0); - ActivatePaletteColorHdl(m_aColorIconView.getColor(0)); + ActivatePaletteColorHdl(0); } else { @@ -481,20 +481,16 @@ void SvxColorTabPage::UpdateToSelectedColor(const NamedColor& rNamedColor) ChangeColor(rNamedColor, false); } -IMPL_LINK(SvxColorTabPage, ActivatePaletteColorHdl, const Color&, rColor, void) +IMPL_LINK(SvxColorTabPage, ActivatePaletteColorHdl, int, nIndex, void) { - const int nPos = m_aColorIconView.get_selected_index(); - if (nPos < 0 || m_aColorIconView.getColor(nPos) != rColor) - return; - NamedColor aNamedColor; - aNamedColor.m_aColor = rColor; + aNamedColor.m_aColor = m_aColorIconView.getColor(nIndex); if (maPaletteManager.IsThemePaletteSelected()) { sal_uInt16 nThemeIndex; sal_uInt16 nEffectIndex; - if (PaletteManager::GetThemeAndEffectIndex(nPos, nThemeIndex, nEffectIndex)) + if (PaletteManager::GetThemeAndEffectIndex(nIndex, nThemeIndex, nEffectIndex)) { aNamedColor.m_nThemeIndex = nThemeIndex; maPaletteManager.GetLumModOff(nThemeIndex, nEffectIndex, aNamedColor.m_nLumMod, aNamedColor.m_nLumOff); @@ -516,10 +512,10 @@ IMPL_LINK(SvxColorTabPage, ActivatePaletteColorHdl, const Color&, rColor, void) } } -IMPL_LINK(SvxColorTabPage, ActivateRecentColorHdl, const Color&, rColor, void) +IMPL_LINK(SvxColorTabPage, ActivateRecentColorHdl, int, nIndex, void) { NamedColor aNamedColor; - aNamedColor.m_aColor = rColor; + aNamedColor.m_aColor = m_aRecentIconView.getColor(nIndex); UpdateToSelectedColor(aNamedColor); diff --git a/cui/uiconfig/ui/colorpage.ui b/cui/uiconfig/ui/colorpage.ui index bd350cca6700..ab198cfe06f8 100644 --- a/cui/uiconfig/ui/colorpage.ui +++ b/cui/uiconfig/ui/colorpage.ui @@ -167,7 +167,10 @@ coloriconviewmodel 12 0 + 0 + 0 2 + 2 True @@ -214,7 +217,10 @@ recentcoloriconviewmodel 12 0 + 0 + 0 2 + 2 True diff --git a/cui/uiconfig/ui/numberingoptionspage.ui b/cui/uiconfig/ui/numberingoptionspage.ui index bbd57556425d..b2e4ac9a1ea0 100644 --- a/cui/uiconfig/ui/numberingoptionspage.ui +++ b/cui/uiconfig/ui/numberingoptionspage.ui @@ -164,7 +164,7 @@ True False True - Number: + Type: True numfmtlb 0 @@ -180,7 +180,7 @@ False - Select a numbering scheme for the selected levels. + Select a numbering scheme or a bullet for the selected levels. - - - False - True - 3 - - True @@ -7207,6 +7168,16 @@ True + + + True + .uno:HandleDuplicateRecords + + + False + True + + True diff --git a/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng b/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng index 710efd324ce9..71f4006ce7b5 100644 --- a/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng +++ b/schema/libreoffice/OpenDocument-v1.4+libreoffice-schema.rng @@ -3381,6 +3381,15 @@ xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1. + + + + + + + + + diff --git a/sd/CppunitTest_sd_pdf_import_test.mk b/sd/CppunitTest_sd_pdf_import_test.mk index f3be2355a329..045c1aca8efa 100644 --- a/sd/CppunitTest_sd_pdf_import_test.mk +++ b/sd/CppunitTest_sd_pdf_import_test.mk @@ -36,6 +36,7 @@ $(eval $(call gb_CppunitTest_use_libraries,sd_pdf_import_test, \ cppu \ cppuhelper \ drawinglayer \ + drawinglayercore \ editeng \ for \ forui \ @@ -65,6 +66,10 @@ $(eval $(call gb_CppunitTest_use_libraries,sd_pdf_import_test, \ xo \ )) +$(eval $(call gb_CppunitTest_use_custom_headers,sd_pdf_import_test,\ + officecfg/registry \ +)) + $(eval $(call gb_CppunitTest_use_sdk_api,sd_pdf_import_test)) $(eval $(call gb_CppunitTest_use_ure,sd_pdf_import_test)) $(eval $(call gb_CppunitTest_use_vcl,sd_pdf_import_test)) diff --git a/sd/qa/unit/SdrPdfImportTest.cxx b/sd/qa/unit/SdrPdfImportTest.cxx index 7ea6bb3713d2..62f4d00b4787 100644 --- a/sd/qa/unit/SdrPdfImportTest.cxx +++ b/sd/qa/unit/SdrPdfImportTest.cxx @@ -12,23 +12,88 @@ #include #include #include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include #include #include +#include #include #include #include +#include +#include +#include #include #include #include +#include #include #include using namespace css; +namespace +{ +// Collects the primitives produced for every view object contact during a +// CompleteRedraw, while still forwarding them so the normal render runs. +class PrimitiveCollector : public sdr::contact::ViewObjectContactRedirector +{ +public: + drawinglayer::primitive2d::Primitive2DContainer maPrimitives; + + void createRedirectedPrimitive2DSequence( + const sdr::contact::ViewObjectContact& rOriginal, + const sdr::contact::DisplayInfo& rDisplayInfo, + drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) override + { + drawinglayer::primitive2d::Primitive2DContainer aLocal; + ViewObjectContactRedirector::createRedirectedPrimitive2DSequence(rOriginal, rDisplayInfo, + aLocal); + maPrimitives.append(aLocal); + rVisitor.visit(std::move(aLocal)); + } +}; + +// True when the primitives, decomposed recursively, contain a hairline drawn in +// rColor. A null reference in the tree is skipped rather than dereferenced. +bool containsHairlineOfColor(const drawinglayer::primitive2d::Primitive2DContainer& rPrimitives, + const basegfx::BColor& rColor) +{ + for (const drawinglayer::primitive2d::Primitive2DReference& rReference : rPrimitives) + { + const drawinglayer::primitive2d::BasePrimitive2D* pPrimitive = rReference.get(); + if (!pPrimitive) + continue; + + if (auto* pHairline + = dynamic_cast( + pPrimitive)) + { + if (pHairline->getBColor() == rColor) + return true; + continue; + } + + drawinglayer::primitive2d::Primitive2DContainer aChildren; + pPrimitive->get2DDecomposition(aChildren, drawinglayer::geometry::ViewInformation2D()); + if (containsHairlineOfColor(aChildren, rColor)) + return true; + } + return false; +} +} + class SdrPdfImportTest : public UnoApiTest { public: @@ -319,6 +384,61 @@ CPPUNIT_TEST_FIXTURE(SdrPdfImportTest, testImportThreadedComments) CPPUNIT_ASSERT(!xDave->IsResolved()); } +CPPUNIT_TEST_FIXTURE(SdrPdfImportTest, testPdfHidesTextBoundaries) +{ + // A PDF opened in Draw must not show the text boundary guides, even when the + // "show boundary for margins" setting is on. Each page holds a single imported + // image, so the margins carry no meaning there. + + auto pPdfium = vcl::pdf::PDFiumLibrary::get(); + if (!pPdfium) + return; + + // We need to enable PDFium import (and make sure to disable after the test) + UsePdfium aGuard; + + // Turn the boundary guides on, the way a user who wants them for ordinary + // Draw documents would. + auto xChanges = comphelper::ConfigurationChanges::create(); + officecfg::Office::Draw::Misc::TextObject::ShowBoundary::set(true, xChanges); + xChanges->commit(); + + loadFromFile(u"SimplePDF.pdf"); + auto pImpressDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pImpressDocument); + SdDrawDocument* pDocument = pImpressDocument->GetDoc(); + CPPUNIT_ASSERT(pDocument); + CPPUNIT_ASSERT(pDocument->IsPDFDocument()); + + sd::ViewShell* pViewShell = pImpressDocument->GetDocShell()->GetViewShell(); + CPPUNIT_ASSERT(pViewShell); + SdPage* pPage = pViewShell->GetActualPage(); + CPPUNIT_ASSERT(pPage); + SdrView* pView = pViewShell->GetView(); + CPPUNIT_ASSERT(pView); + + // Render the whole page and gather every primitive sent to the device. + PrimitiveCollector aCollector; + ScopedVclPtrInstance pDevice(DeviceFormat::WITHOUT_ALPHA); + pDevice->SetMapMode(MapMode(MapUnit::Map100thMM)); + pDevice->SetOutputSizePixel(Size(2000, 2000)); + tools::Rectangle aPageRect(Point(), pPage->GetSize()); + pView->CompleteRedraw(pDevice, vcl::Region(aPageRect), &aCollector); + CPPUNIT_ASSERT(!aCollector.maPrimitives.empty()); + + // The page keeps non-zero margins, which is what would make the guide + // appear, so the rendered page is a valid probe for this regression. + CPPUNIT_ASSERT(pPage->GetLeftBorder() || pPage->GetUpperBorder() || pPage->GetRightBorder() + || pPage->GetLowerBorder()); + + // The margin guide is a hairline drawn in the document boundary color. The + // rendered content for a PDF page must not contain one. + const svtools::ColorConfig aColorConfig; + const basegfx::BColor aBoundaryColor + = aColorConfig.GetColorValue(svtools::DOCBOUNDARIES).nColor.getBColor(); + CPPUNIT_ASSERT(!containsHairlineOfColor(aCollector.maPrimitives, aBoundaryColor)); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/qa/unit/TextFittingTest.cxx b/sd/qa/unit/TextFittingTest.cxx index 74bfce98e669..d45d379c579a 100644 --- a/sd/qa/unit/TextFittingTest.cxx +++ b/sd/qa/unit/TextFittingTest.cxx @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -203,6 +205,37 @@ CPPUNIT_TEST_FIXTURE(TextFittingTest, testTestBulletDifferenceViewAndEdit) CPPUNIT_ASSERT_EQUAL(false, pView->IsTextEdit()); } +CPPUNIT_TEST_FIXTURE(TextFittingTest, testStoredFitKeptUntilEdited) +{ + // The first slide stores a fit of 55 percent font with 20 percent less line spacing, though + // its text fits at full size. The stored fit is shown until the text is edited, and then the + // text is fitted afresh. + createSdImpressDoc("pptx/TextFittingStored.pptx"); + auto pXImpressDocument = dynamic_cast(mxComponent.get()); + CPPUNIT_ASSERT(pXImpressDocument); + sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); + auto pBody = DynCastSdrTextObj(pViewShell->GetActualPage()->GetObj(1)); + CPPUNIT_ASSERT(pBody); + + const SdrTextFitToSizeTypeItem& rStored = pBody->GetMergedItem(SDRATTR_TEXT_FITTOSIZE); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.55, rStored.getFontScale(), 1E-4); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.8, rStored.getSpacingScale(), 1E-4); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.55, pBody->GetFontScale(), 1E-4); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.8, pBody->GetSpacingScale(), 1E-4); + + SdrView* pView = pViewShell->GetView(); + Scheduler::ProcessEventsToIdle(); + pView->SdrBeginTextEdit(pBody); + CPPUNIT_ASSERT(pView->IsTextEdit()); + EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); + rEditView.SetSelection(ESelection(0, 0)); + rEditView.InsertText(u"x"_ustr); + pView->SdrEndTextEdit(); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, pBody->GetFontScale(), 1E-4); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, pBody->GetSpacingScale(), 1E-4); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/qa/unit/data/odp/math-color.fodp b/sd/qa/unit/data/odp/math-color.fodp new file mode 100644 index 000000000000..ba1a0d5f5a15 --- /dev/null +++ b/sd/qa/unit/data/odp/math-color.fodp @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + color red { 1 } + + + + + + + + \ No newline at end of file diff --git a/sd/qa/unit/data/pdf/ligature-textbox-fit.pdf b/sd/qa/unit/data/pdf/ligature-textbox-fit.pdf new file mode 100644 index 000000000000..b032e99fe0d5 Binary files /dev/null and b/sd/qa/unit/data/pdf/ligature-textbox-fit.pdf differ diff --git a/sd/qa/unit/data/pptx/TextFittingStored.pptx b/sd/qa/unit/data/pptx/TextFittingStored.pptx new file mode 100644 index 000000000000..358f6a3c245e Binary files /dev/null and b/sd/qa/unit/data/pptx/TextFittingStored.pptx differ diff --git a/sd/qa/unit/export-tests-ooxml2.cxx b/sd/qa/unit/export-tests-ooxml2.cxx index 2cc2cab65b97..631845b71a65 100644 --- a/sd/qa/unit/export-tests-ooxml2.cxx +++ b/sd/qa/unit/export-tests-ooxml2.cxx @@ -363,6 +363,25 @@ CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest2, testMathObject) } } +CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest2, testMathObjectColorOmittedInSlide) +{ + // A colored formula still exports as a slide that binds every prefix it uses. The + // run properties that carry a color live in the w namespace, which a slide does not + // declare, so the color is left out and the run holds nothing but its text. + createSdImpressDoc("odp/math-color.fodp"); + save(TestFilter::PPTX); + + xmlDocUniquePtr pXmlDocContent = parseExport(u"ppt/slides/slide1.xml"_ustr); + assertXPathContent(pXmlDocContent, + "/p:sld/p:cSld/p:spTree/mc:AlternateContent/mc:Choice/p:sp/p:txBody/a:p/" + "a14:m/m:oMath/m:r[1]/m:t", + u"1"); + assertXPath(pXmlDocContent, + "/p:sld/p:cSld/p:spTree/mc:AlternateContent/mc:Choice/p:sp/p:txBody/a:p/" + "a14:m/m:oMath/m:r[1]/*", + 1); +} + CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest2, testMathExportNonBMP) { // Check import / export of math object diff --git a/sd/qa/unit/export-tests.cxx b/sd/qa/unit/export-tests.cxx index 5d6c0ad5df1b..9a59d09c2da2 100644 --- a/sd/qa/unit/export-tests.cxx +++ b/sd/qa/unit/export-tests.cxx @@ -1183,11 +1183,11 @@ CPPUNIT_TEST_FIXTURE(SdExportTest, testExplodedPdfHindi) // ensure the expected content assertXPathContent(pXmlDoc, "/office:document/office:body/office:drawing/draw:page/draw:g/draw:frame[3]/" - "draw:text-box/text:p[@text:style-name='P6'][1]", + "draw:text-box/text:p[@text:style-name='P4'][1]", u"FIRST-YEAR HINDI COURSE"); // ensure the expected font name - assertXPath(pXmlDoc, "/office:document/office:automatic-styles/style:style[@style:name='P6']/" + assertXPath(pXmlDoc, "/office:document/office:automatic-styles/style:style[@style:name='P4']/" "style:text-properties[@style:font-name='AcademyEngravedLetPlain']"); } @@ -2569,6 +2569,33 @@ CPPUNIT_TEST_FIXTURE(SdExportTest, testTableBordersTransparancy) Color(ColorTransparency, aBorderLine.Color)); } +CPPUNIT_TEST_FIXTURE(SdExportTest, testExplodedPdfLigatureTextFit) +{ + auto pPdfium = vcl::pdf::PDFiumLibrary::get(); + if (!pPdfium) + return; + UsePdfium aGuard; + + // An "ſt" ligature is one narrow pdf glyph that expands to two + // characters. Its run must stay on a single line, not wrap with the + // trailing glyph dropped below the box. + loadFromFile(u"pdf/ligature-textbox-fit.pdf"); + + setImportFilterName(TestFilter::FODG); + saveAndReload(TestFilter::FODG, + { + comphelper::makePropertyValue( + u"FilterOptions"_ustr, + u"{\"DecomposePDF\":{\"type\":\"boolean\",\"value\":\"true\"}}"_ustr), + }); + + xmlDocUniquePtr pXml = parseLayout(); + + // Three "ſt" runs, each on one line. A wrapped run adds a duplicate at + // the same x and a lower y, taking the count to six. + assertXPath(pXml, "//textarray[text='ſt']", 3); +} + // CharOpticalSizing should be enabled by default for new documents CPPUNIT_TEST_FIXTURE(SdExportTest, testOpticalSizing1) { diff --git a/sd/source/console/AccessibleObject.cxx b/sd/source/console/AccessibleObject.cxx index 0542890a8c65..437ca6a2035d 100644 --- a/sd/source/console/AccessibleObject.cxx +++ b/sd/source/console/AccessibleObject.cxx @@ -66,10 +66,10 @@ void AccessibleObject::SetWindow ( UpdateStateSet(); } -void AccessibleObject::SetAccessibleParent ( - const Reference& rxAccessibleParent) +void AccessibleObject::SetAccessibleParent( + const rtl::Reference& rpAccessibleParent) { - mxParentAccessible = rxAccessibleParent; + mpParentAccessible = rpAccessibleParent; } void SAL_CALL AccessibleObject::disposing() @@ -105,7 +105,7 @@ Reference SAL_CALL { ensureAlive(); - return mxParentAccessible; + return mpParentAccessible; } sal_Int64 SAL_CALL @@ -114,14 +114,12 @@ sal_Int64 SAL_CALL ensureAlive(); const Reference xThis (this); - if (mxParentAccessible.is()) + if (mpParentAccessible.is()) { - const Reference xContext (mxParentAccessible->getAccessibleContext()); - for (sal_Int64 nIndex = 0, nCount=xContext->getAccessibleChildCount(); - nIndexgetAccessibleChildCount(); + nIndex < nCount; ++nIndex) { - if (xContext->getAccessibleChild(nIndex) == xThis) + if (mpParentAccessible->getAccessibleChild(nIndex) == xThis) return nIndex; } } @@ -174,11 +172,9 @@ lang::Locale SAL_CALL { ensureAlive(); - if (mxParentAccessible.is()) + if (mpParentAccessible.is()) { - Reference xParentContext (mxParentAccessible->getAccessibleContext()); - if (xParentContext.is()) - return xParentContext->getLocale(); + return mpParentAccessible->getLocale(); } return css::lang::Locale(); } @@ -331,7 +327,7 @@ void AccessibleObject::AddChild ( void AccessibleObject::RemoveChild ( const ::rtl::Reference& rpChild) { - rpChild->SetAccessibleParent(Reference()); + rpChild->SetAccessibleParent({}); maChildren.erase(::std::find(maChildren.begin(), maChildren.end(), rpChild)); FireAccessibleEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN, Any(), Any()); } diff --git a/sd/source/console/AccessibleObject.hxx b/sd/source/console/AccessibleObject.hxx index 83ef97a02236..62092b0fbe64 100644 --- a/sd/source/console/AccessibleObject.hxx +++ b/sd/source/console/AccessibleObject.hxx @@ -46,7 +46,7 @@ public: virtual void SetWindow ( const css::uno::Reference& rxContentWindow, const css::uno::Reference& rxBorderWindow); - void SetAccessibleParent (const css::uno::Reference& rxAccessibleParent); + void SetAccessibleParent(const rtl::Reference& rpAccessibleParent); virtual void SAL_CALL disposing() override; @@ -120,7 +120,7 @@ protected: const sal_Int16 mnRole; sal_Int64 mnStateSet; bool mbIsFocused; - css::uno::Reference mxParentAccessible; + rtl::Reference mpParentAccessible; ::std::vector > maChildren; // OAccessible diff --git a/sd/source/console/AccessibleParagraph.cxx b/sd/source/console/AccessibleParagraph.cxx index 37c9afc7c1ee..b3ab04ac0072 100644 --- a/sd/source/console/AccessibleParagraph.cxx +++ b/sd/source/console/AccessibleParagraph.cxx @@ -127,21 +127,15 @@ Reference SAL_CALL rtl::Reference pSet (new AccessibleRelationSet); - if (mxParentAccessible.is()) + if (mpParentAccessible.is()) { - Reference xParentContext (mxParentAccessible->getAccessibleContext()); - if (xParentContext.is()) - { - if (mnParagraphIndex>0) - pSet->AddRelation( - AccessibleRelationType_CONTENT_FLOWS_FROM, - xParentContext->getAccessibleChild(mnParagraphIndex-1)); + if (mnParagraphIndex > 0) + pSet->AddRelation(AccessibleRelationType_CONTENT_FLOWS_FROM, + mpParentAccessible->getAccessibleChild(mnParagraphIndex - 1)); - if (mnParagraphIndexgetAccessibleChildCount()-1) - pSet->AddRelation( - AccessibleRelationType_CONTENT_FLOWS_TO, - xParentContext->getAccessibleChild(mnParagraphIndex+1)); - } + if (mnParagraphIndex < mpParentAccessible->getAccessibleChildCount() - 1) + pSet->AddRelation(AccessibleRelationType_CONTENT_FLOWS_TO, + mpParentAccessible->getAccessibleChild(mnParagraphIndex + 1)); } return pSet; diff --git a/sd/source/console/PresenterAccessibility.cxx b/sd/source/console/PresenterAccessibility.cxx index a25a3da0fbc7..c93c1174c58f 100644 --- a/sd/source/console/PresenterAccessibility.cxx +++ b/sd/source/console/PresenterAccessibility.cxx @@ -96,7 +96,7 @@ PresenterAccessible::PresenterAccessible( if (VclPtr pMainPaneWin = VCLUnoHelper::GetWindow(mxMainWindow)) { pMainPaneWin->SetAccessible(this); - mxAccessibleParent = pMainPaneWin->GetAccessibleParent(); + mpAccessibleParent = pMainPaneWin->GetAccessibleParent(); } } @@ -274,7 +274,7 @@ PresenterAccessible::Create(const rtl::Reference& xPresente pPresenterAcc->UpdateStateSet(); pPresenterAcc->SetWindow(pPresenterAcc->mxMainWindow, nullptr); - pPresenterAcc->SetAccessibleParent(pPresenterAcc->mxAccessibleParent); + pPresenterAcc->SetAccessibleParent(pPresenterAcc->mpAccessibleParent); pPresenterAcc->UpdateAccessibilityHierarchy(); return pPresenterAcc; diff --git a/sd/source/console/PresenterAccessibility.hxx b/sd/source/console/PresenterAccessibility.hxx index 6b210adf1230..cc78bdc5b779 100644 --- a/sd/source/console/PresenterAccessibility.hxx +++ b/sd/source/console/PresenterAccessibility.hxx @@ -76,7 +76,7 @@ private: css::uno::Reference mxNotesBorderWindow; ::rtl::Reference mpAccessiblePreview; ::rtl::Reference mpAccessibleNotes; - css::uno::Reference mxAccessibleParent; + rtl::Reference mpAccessibleParent; void UpdateAccessibilityHierarchy ( const css::uno::Reference& rxPreviewContentWindow, diff --git a/sd/source/ui/accessibility/AccessibleDrawDocumentView.cxx b/sd/source/ui/accessibility/AccessibleDrawDocumentView.cxx index 14d9e512eb2d..e0009093d011 100644 --- a/sd/source/ui/accessibility/AccessibleDrawDocumentView.cxx +++ b/sd/source/ui/accessibility/AccessibleDrawDocumentView.cxx @@ -389,8 +389,8 @@ sal_Bool SAL_CALL AccessibleDrawDocumentView::supportsService(const OUString& rS } //===== XAccessibleGroupPosition ========================================= -uno::Sequence< sal_Int32 > SAL_CALL - AccessibleDrawDocumentView::getGroupPosition( const uno::Any& rAny ) +uno::Sequence SAL_CALL AccessibleDrawDocumentView::getGroupPosition( + const css::uno::Reference& rxAccessible) { SolarMutexGuard g; @@ -400,13 +400,11 @@ uno::Sequence< sal_Int32 > SAL_CALL // [2] the position of the object in the group uno::Sequence< sal_Int32 > aRet( 3 ); //get the xShape of the current selected drawing object - uno::Reference xAccContent; - rAny >>= xAccContent; - if ( !xAccContent.is() ) + if (!rxAccessible.is()) { return aRet; } - AccessibleShape* pAcc = comphelper::getFromUnoTunnel( xAccContent ); + AccessibleShape* pAcc = comphelper::getFromUnoTunnel(rxAccessible); if ( !pAcc ) { return aRet; @@ -458,19 +456,18 @@ uno::Sequence< sal_Int32 > SAL_CALL return aRet; } -OUString AccessibleDrawDocumentView::getObjectLink( const uno::Any& rAny ) +OUString AccessibleDrawDocumentView::getObjectLink( + const css::uno::Reference& rxAccessible) { SolarMutexGuard g; OUString aRet; //get the xShape of the current selected drawing object - uno::Reference xAccContent; - rAny >>= xAccContent; - if ( !xAccContent.is() ) + if (!rxAccessible.is()) { return aRet; } - AccessibleShape* pAcc = comphelper::getFromUnoTunnel( xAccContent ); + AccessibleShape* pAcc = comphelper::getFromUnoTunnel(rxAccessible); if ( !pAcc ) { return aRet; diff --git a/sd/source/ui/accessibility/AccessibleSlideSorterView.cxx b/sd/source/ui/accessibility/AccessibleSlideSorterView.cxx index 7b1d6dc5e069..ada3f5952066 100644 --- a/sd/source/ui/accessibility/AccessibleSlideSorterView.cxx +++ b/sd/source/ui/accessibility/AccessibleSlideSorterView.cxx @@ -78,8 +78,8 @@ public: void RequestUpdateChildren(); void Clear(); sal_Int32 GetVisibleChildCount() const; - AccessibleSlideSorterObject* GetAccessibleChild (sal_Int32 nIndex); - AccessibleSlideSorterObject* GetVisibleChild (sal_Int32 nIndex); + rtl::Reference GetAccessibleChild(sal_Int32 nIndex); + rtl::Reference GetVisibleChild(sal_Int32 nIndex); void ConnectListeners(); void ReleaseListeners(); @@ -143,10 +143,10 @@ void SAL_CALL AccessibleSlideSorterView::disposing() mpImpl.reset(); } -AccessibleSlideSorterObject* AccessibleSlideSorterView::GetAccessibleChildImplementation ( - sal_Int32 nIndex) +rtl::Reference +AccessibleSlideSorterView::GetAccessibleChildImplementation(sal_Int32 nIndex) { - AccessibleSlideSorterObject* pResult = nullptr; + rtl::Reference pResult; ::osl::MutexGuard aGuard (m_aMutex); if (nIndex>=0 && nIndexGetVisibleChildCount()) @@ -180,12 +180,11 @@ Reference SAL_CALL AccessibleSlideSorterView::getAccessibleParent( { ThrowIfDisposed(); const SolarMutexGuard aSolarGuard; - Reference xParent; - if (mpContentWindow != nullptr) - xParent = mpContentWindow->GetAccessibleParent(); + if (!mpContentWindow) + return {}; - return xParent; + return mpContentWindow->GetAccessibleParent(); } sal_Int64 SAL_CALL AccessibleSlideSorterView::getAccessibleIndexInParent() @@ -283,17 +282,16 @@ Reference SAL_CALL AccessibleSlideSorterView::getAccessibleAtPoint (const awt::Point& aPoint) { ThrowIfDisposed(); - Reference xAccessible; + rtl::Reference pAccessible; const SolarMutexGuard aSolarGuard; const Point aTestPoint (aPoint.X, aPoint.Y); ::sd::slidesorter::model::SharedPageDescriptor pHitDescriptor ( mrSlideSorter.GetController().GetPageAt(aTestPoint)); if (pHitDescriptor) - xAccessible = mpImpl->GetAccessibleChild( - (pHitDescriptor->GetPage()->GetPageNum()-1)/2); + pAccessible = mpImpl->GetAccessibleChild((pHitDescriptor->GetPage()->GetPageNum() - 1) / 2); - return xAccessible; + return pAccessible; } awt::Rectangle AccessibleSlideSorterView::implGetBounds() @@ -348,8 +346,8 @@ void SAL_CALL AccessibleSlideSorterView::selectAccessibleChild (sal_Int64 nChild if (nChildIndex < 0 || nChildIndex >= getAccessibleChildCount()) throw lang::IndexOutOfBoundsException(); - AccessibleSlideSorterObject* pChild = mpImpl->GetAccessibleChild(nChildIndex); - if (pChild == nullptr) + rtl::Reference pChild = mpImpl->GetAccessibleChild(nChildIndex); + if (!pChild.is()) throw lang::IndexOutOfBoundsException(); mrSlideSorter.GetController().GetPageSelector().SelectPage(pChild->GetPageNumber()); @@ -364,8 +362,8 @@ sal_Bool SAL_CALL AccessibleSlideSorterView::isAccessibleChildSelected (sal_Int6 if (nChildIndex < 0 || nChildIndex >= getAccessibleChildCount()) throw lang::IndexOutOfBoundsException(); - AccessibleSlideSorterObject* pChild = mpImpl->GetAccessibleChild(nChildIndex); - if (pChild == nullptr) + rtl::Reference pChild = mpImpl->GetAccessibleChild(nChildIndex); + if (!pChild.is()) throw lang::IndexOutOfBoundsException(); bIsSelected = mrSlideSorter.GetController().GetPageSelector().IsPageSelected( @@ -406,7 +404,7 @@ Reference SAL_CALL if (nSelectedChildIndex < 0 || nSelectedChildIndex >= getSelectedAccessibleChildCount()) throw lang::IndexOutOfBoundsException(); - Reference xChild; + rtl::Reference pChild; ::sd::slidesorter::controller::PageSelector& rSelector ( mrSlideSorter.GetController().GetPageSelector()); @@ -417,16 +415,16 @@ Reference SAL_CALL { if (nSelectedCount == nSelectedChildIndex) { - xChild = mpImpl->GetAccessibleChild(i); + pChild = mpImpl->GetAccessibleChild(i); break; } ++nSelectedCount; } - if ( ! xChild.is() ) + if (!pChild.is()) throw lang::IndexOutOfBoundsException(); - return xChild; + return pChild; } void SAL_CALL AccessibleSlideSorterView::deselectAccessibleChild (sal_Int64 nChildIndex) @@ -437,8 +435,8 @@ void SAL_CALL AccessibleSlideSorterView::deselectAccessibleChild (sal_Int64 nChi if (nChildIndex < 0 || nChildIndex >= getAccessibleChildCount()) throw lang::IndexOutOfBoundsException(); - AccessibleSlideSorterObject* pChild = mpImpl->GetAccessibleChild(nChildIndex); - if (pChild == nullptr) + rtl::Reference pChild = mpImpl->GetAccessibleChild(nChildIndex); + if (!pChild.is()) throw lang::IndexOutOfBoundsException(); mrSlideSorter.GetController().GetPageSelector().DeselectPage(pChild->GetPageNumber()); @@ -551,18 +549,18 @@ sal_Int32 AccessibleSlideSorterView::Implementation::GetVisibleChildCount() cons return 0; } -AccessibleSlideSorterObject* AccessibleSlideSorterView::Implementation::GetVisibleChild ( - sal_Int32 nIndex) +rtl::Reference +AccessibleSlideSorterView::Implementation::GetVisibleChild(sal_Int32 nIndex) { assert(nIndex>=0 && nIndex +AccessibleSlideSorterView::Implementation::GetAccessibleChild(sal_Int32 nIndex) { - AccessibleSlideSorterObject* pChild = nullptr; + rtl::Reference pChild; if (nIndex>=0 && o3tl::make_unsigned(nIndex)= 0) { - AccessibleSlideSorterObject* pObject = GetAccessibleChild(mnFocusedIndex); - if (pObject != nullptr) + rtl::Reference pObject = GetAccessibleChild(mnFocusedIndex); + if (pObject.is()) { pObject->FireAccessibleEvent( AccessibleEventId::STATE_CHANGED, @@ -754,8 +752,8 @@ IMPL_LINK_NOARG(AccessibleSlideSorterView::Implementation, FocusChangeListener, } if (nNewFocusedIndex >= 0) { - AccessibleSlideSorterObject* pObject = GetAccessibleChild(nNewFocusedIndex); - if (pObject != nullptr) + rtl::Reference pObject = GetAccessibleChild(nNewFocusedIndex); + if (pObject.is()) { pObject->FireAccessibleEvent( AccessibleEventId::STATE_CHANGED, diff --git a/sd/source/ui/inc/AccessibleDrawDocumentView.hxx b/sd/source/ui/inc/AccessibleDrawDocumentView.hxx index ecf24fc8193d..d935a23e4da9 100644 --- a/sd/source/ui/inc/AccessibleDrawDocumentView.hxx +++ b/sd/source/ui/inc/AccessibleDrawDocumentView.hxx @@ -79,9 +79,10 @@ public: propertyChange (const css::beans::PropertyChangeEvent& rEventObject) override; //===== XAccessibleGroupPosition ========================================= - virtual css::uno::Sequence< sal_Int32 > SAL_CALL - getGroupPosition( const css::uno::Any& rAny ) override; - virtual OUString SAL_CALL getObjectLink( const css::uno::Any& accoject ) override; + virtual css::uno::Sequence SAL_CALL getGroupPosition( + const css::uno::Reference& rxAccessible) override; + virtual OUString SAL_CALL getObjectLink( + const css::uno::Reference& rxAccessible) override; private: diff --git a/sd/source/ui/inc/AccessibleSlideSorterView.hxx b/sd/source/ui/inc/AccessibleSlideSorterView.hxx index 7de5a0a98bef..6951a5b6c0fc 100644 --- a/sd/source/ui/inc/AccessibleSlideSorterView.hxx +++ b/sd/source/ui/inc/AccessibleSlideSorterView.hxx @@ -62,7 +62,7 @@ public: @param nIndex Index of the child for which to return the implementation object. */ - AccessibleSlideSorterObject* GetAccessibleChildImplementation (sal_Int32 nIndex); + rtl::Reference GetAccessibleChildImplementation(sal_Int32 nIndex); //===== XAccessibleContext ============================================== diff --git a/sd/source/ui/slidesorter/controller/SlideSorterController.cxx b/sd/source/ui/slidesorter/controller/SlideSorterController.cxx index 81906396f515..b33c41327b21 100644 --- a/sd/source/ui/slidesorter/controller/SlideSorterController.cxx +++ b/sd/source/ui/slidesorter/controller/SlideSorterController.cxx @@ -868,9 +868,9 @@ void SlideSorterController::PageNameHasChanged (int nPageIndex, const OUString& if (pAccessibleView == nullptr) return; - ::accessibility::AccessibleSlideSorterObject* pChild - = pAccessibleView->GetAccessibleChildImplementation(nPageIndex); - if (pChild == nullptr || pChild->GetPage() == nullptr) + rtl::Reference<::accessibility::AccessibleSlideSorterObject> pChild + = pAccessibleView->GetAccessibleChildImplementation(nPageIndex); + if (!pChild.is() || pChild->GetPage() == nullptr) return; OUString sNewName (pChild->GetPage()->GetName()); diff --git a/sd/source/ui/view/drawview.cxx b/sd/source/ui/view/drawview.cxx index bc02b3460a39..7c3db52c4823 100644 --- a/sd/source/ui/view/drawview.cxx +++ b/sd/source/ui/view/drawview.cxx @@ -510,7 +510,10 @@ void DrawView::CompleteRedraw(OutputDevice* pOutDev, const vcl::Region& rReg, sd else if( pDoc && pDoc->GetDocumentType() == DocumentType::Draw) { // tdf#164605 & tdf#89420 - bool bShowMargin(officecfg::Office::Draw::Misc::TextObject::ShowBoundary::get()); + // A PDF page in Draw is a single imported image, so the text boundary + // guides are pointless there and would show through the background. + bool bShowMargin = !pDoc->IsPDFDocument() + && officecfg::Office::Draw::Misc::TextObject::ShowBoundary::get(); pDoc->SetShowMargin(bShowMargin); } diff --git a/sfx2/inc/DevelopmentToolDockingWindow.hxx b/sfx2/inc/DevelopmentToolDockingWindow.hxx index 909ec48521a0..f05324322861 100644 --- a/sfx2/inc/DevelopmentToolDockingWindow.hxx +++ b/sfx2/inc/DevelopmentToolDockingWindow.hxx @@ -37,7 +37,7 @@ private: std::unique_ptr mpDomToolbar; // Reference to the root object for the current document - css::uno::Reference mxRoot; + css::uno::Reference mxRoot; // Stores the current selected object in the document css::uno::Reference mxCurrentSelection; css::uno::Reference mxSelectionListener; diff --git a/sfx2/inc/strings.hxx b/sfx2/inc/strings.hxx index cc36f7bee90b..6897e27ef92c 100644 --- a/sfx2/inc/strings.hxx +++ b/sfx2/inc/strings.hxx @@ -49,6 +49,7 @@ inline constexpr OUString STR_TEMPLATE_NAME31_DEF = u"Simple"_ustr; inline constexpr OUString STR_TEMPLATE_NAME32_DEF = u"BPMN"_ustr; inline constexpr OUString STR_TEMPLATE_NAME33_DEF = u"Simplified Chinese Normal"_ustr; inline constexpr OUString STR_TEMPLATE_NAME34_DEF = u"Japanese Normal"_ustr; +inline constexpr OUString STR_TEMPLATE_NAME35_DEF = u"MediaWiki"_ustr; inline constexpr OUString CMIS_TYPE_STRING = u"String"_ustr; #define CMIS_TYPE_INTEGER "Integer" diff --git a/sfx2/source/devtools/DevelopmentToolDockingWindow.cxx b/sfx2/source/devtools/DevelopmentToolDockingWindow.cxx index 6101e18746d2..3d979cd75586 100644 --- a/sfx2/source/devtools/DevelopmentToolDockingWindow.cxx +++ b/sfx2/source/devtools/DevelopmentToolDockingWindow.cxx @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/sfx2/source/devtools/DocumentModelTreeHandler.cxx b/sfx2/source/devtools/DocumentModelTreeHandler.cxx index 8c761f6ff90a..594fc0aabaeb 100644 --- a/sfx2/source/devtools/DocumentModelTreeHandler.cxx +++ b/sfx2/source/devtools/DocumentModelTreeHandler.cxx @@ -702,7 +702,7 @@ public: DocumentModelTreeHandler::DocumentModelTreeHandler( std::unique_ptr& pDocumentModelTree, - css::uno::Reference xDocument) + css::uno::Reference xDocument) : mpDocumentModelTree(pDocumentModelTree) , mxDocument(std::move(xDocument)) { diff --git a/sfx2/source/dialog/templdlg.cxx b/sfx2/source/dialog/templdlg.cxx index 0c939acd5b87..29000ab6521e 100644 --- a/sfx2/source/dialog/templdlg.cxx +++ b/sfx2/source/dialog/templdlg.cxx @@ -739,19 +739,22 @@ void SfxCommonTemplateDialog_Impl::FamilySelect(sal_uInt16 nEntry, bool bFillTre m_aStyleList.FamilySelect(nEntry, bFillTreeView); - // update Spotlight checkbox - SfxStyleFamily eFam = m_aStyleList.GetFamilyItem()->GetFamily(); - bool bShowSpotlightCheckbox = m_aStyleList.HasStylesSpotlightFeature() - && (eFam == SfxStyleFamily::Para || eFam == SfxStyleFamily::Char); - mxSpotlightCheckbox->set_visible(bShowSpotlightCheckbox); - if (bShowSpotlightCheckbox) + if (const SfxStyleFamilyItem* pFamilyItem = m_aStyleList.GetFamilyItem()) { - bool bActive = false; - if (eFam == SfxStyleFamily::Para) - bActive = m_aStyleList.IsSpotlightParaStyles(); - else if (eFam == SfxStyleFamily::Char) - bActive = m_aStyleList.IsSpotlightCharStyles(); - mxSpotlightCheckbox->set_active(bActive); + // update Spotlight checkbox + SfxStyleFamily eFam = pFamilyItem->GetFamily(); + bool bShowSpotlightCheckbox = m_aStyleList.HasStylesSpotlightFeature() + && (eFam == SfxStyleFamily::Para || eFam == SfxStyleFamily::Char); + mxSpotlightCheckbox->set_visible(bShowSpotlightCheckbox); + if (bShowSpotlightCheckbox) + { + bool bActive = false; + if (eFam == SfxStyleFamily::Para) + bActive = m_aStyleList.IsSpotlightParaStyles(); + else if (eFam == SfxStyleFamily::Char) + bActive = m_aStyleList.IsSpotlightCharStyles(); + mxSpotlightCheckbox->set_active(bActive); + } } } diff --git a/sfx2/source/doc/doctempl.cxx b/sfx2/source/doc/doctempl.cxx index 3bf4c151bbd5..038569454bca 100644 --- a/sfx2/source/doc/doctempl.cxx +++ b/sfx2/source/doc/doctempl.cxx @@ -501,7 +501,8 @@ OUString SfxDocumentTemplates::ConvertResourceString(const OUString& rString) STR_TEMPLATE_NAME31_DEF, STR_TEMPLATE_NAME32_DEF, STR_TEMPLATE_NAME33_DEF, - STR_TEMPLATE_NAME34_DEF + STR_TEMPLATE_NAME34_DEF, + STR_TEMPLATE_NAME35_DEF }; TranslateId STR_TEMPLATE_NAME[] = @@ -539,7 +540,8 @@ OUString SfxDocumentTemplates::ConvertResourceString(const OUString& rString) STR_TEMPLATE_NAME31, STR_TEMPLATE_NAME32, STR_TEMPLATE_NAME33, - STR_TEMPLATE_NAME34 + STR_TEMPLATE_NAME34, + STR_TEMPLATE_NAME35 }; static_assert(SAL_N_ELEMENTS(aTemplateNames) == SAL_N_ELEMENTS(STR_TEMPLATE_NAME)); diff --git a/sfx2/source/doc/objmisc.cxx b/sfx2/source/doc/objmisc.cxx index b553738ef966..706918d42384 100644 --- a/sfx2/source/doc/objmisc.cxx +++ b/sfx2/source/doc/objmisc.cxx @@ -153,15 +153,6 @@ public: } -sal_uInt16 const aTitleMap_Impl[3][2] = -{ - // local remote - /* SFX_TITLE_CAPTION */ { SFX_TITLE_FILENAME, SFX_TITLE_TITLE }, - /* SFX_TITLE_PICKLIST */ { 32, SFX_TITLE_FULLNAME }, - /* SFX_TITLE_HISTORY */ { 32, SFX_TITLE_FULLNAME } -}; - - bool SfxObjectShell::IsAbortingImport() const { return pImpl->bIsAbortingImport; @@ -808,16 +799,6 @@ OUString SfxObjectShell::GetTitle( sal_uInt16 nMaxLength ) const assert(pMed); const INetURLObject aURL( IsDocShared() ? GetSharedFileURL() : GetMedium()->GetName() ); - if ( nMaxLength > SFX_TITLE_CAPTION && nMaxLength <= SFX_TITLE_HISTORY ) - { - sal_uInt16 nRemote; - if (aURL.GetProtocol() == INetProtocol::File) - nRemote = 0; - else - nRemote = 1; - nMaxLength = aTitleMap_Impl[nMaxLength-SFX_TITLE_CAPTION][nRemote]; - } - // Local file? if ( aURL.GetProtocol() == INetProtocol::File ) { @@ -843,7 +824,7 @@ OUString SfxObjectShell::GetTitle( sal_uInt16 nMaxLength ) const const OUString aName = INetURLObject::decode( aURL.GetBase(), INetURLObject::DecodeMechanism::WithCharset ); return aName.isEmpty() ? aURL.GetURLNoPass() : aName; } - if ( nMaxLength == SFX_TITLE_FULLNAME ) + if (nMaxLength == SFX_TITLE_FULLNAME || nMaxLength == SFX_TITLE_PICKLIST || nMaxLength == SFX_TITLE_HISTORY) return aURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ); // Generate Title from file name if possible diff --git a/sfx2/source/doc/objserv.cxx b/sfx2/source/doc/objserv.cxx index 081237724c03..d03b57105cf9 100644 --- a/sfx2/source/doc/objserv.cxx +++ b/sfx2/source/doc/objserv.cxx @@ -75,6 +75,7 @@ #include #include +#include #include #include #include diff --git a/sfx2/source/doc/objxtor.cxx b/sfx2/source/doc/objxtor.cxx index 78828f06a141..6af7293d6afb 100644 --- a/sfx2/source/doc/objxtor.cxx +++ b/sfx2/source/doc/objxtor.cxx @@ -372,7 +372,7 @@ SfxCloseVetoLock::~SfxCloseVetoLock() if (mpDocShell->Get_Impl()->m_bCloseModelScheduled) { mpDocShell->Get_Impl()->m_bCloseModelScheduled = false; // pass ownership - if (rtl::Reference model = static_cast(mpDocShell->GetBaseModel().get())) + if (rtl::Reference model = mpDocShell->GetBaseModel()) { try { @@ -417,13 +417,13 @@ bool SfxObjectShell::CloseInternal() return false; pImpl->bClosing = true; - Reference< util::XCloseable > xCloseable( GetBaseModel(), UNO_QUERY ); + rtl::Reference pModel = GetBaseModel(); - if ( xCloseable.is() ) + if (pModel.is()) { try { - xCloseable->close( true ); + pModel->close(true); } catch (const Exception&) { @@ -895,11 +895,7 @@ void SfxObjectShell::SetBaseModel( SfxBaseModel* pModel ) } } - -css::uno::Reference< css::frame::XModel3 > SfxObjectShell::GetBaseModel() const -{ - return pImpl->pBaseModel; -} +rtl::Reference SfxObjectShell::GetBaseModel() const { return pImpl->pBaseModel; } void SfxObjectShell::SetAutoStyleFilterIndex(sal_uInt16 nSet) { diff --git a/sfx2/source/sidebar/SidebarController.cxx b/sfx2/source/sidebar/SidebarController.cxx index c4f05f9cd629..c96d45f45d3c 100644 --- a/sfx2/source/sidebar/SidebarController.cxx +++ b/sfx2/source/sidebar/SidebarController.cxx @@ -367,11 +367,10 @@ void SAL_CALL SidebarController::notifyContextChangeEvent (const css::ui::Contex // calling with held // solarmutex + // tdf#104892 - other context changes are batched into a single rebuild + // by the request above; only an application switch needs an immediate one bool bSwitchedApp = maRequestedContext.msApplication != maCurrentContext.msApplication; - // Happens on reattach of sidebar to frame or context change - // LOK performance impact: prevents to switch sidebar on every keypress in multi user case - // Allow when enters embedded OLE (eg. Math formula editor second time) - if (!comphelper::LibreOfficeKit::isActive() || bSwitchedApp) + if (bSwitchedApp) UpdateConfigurations(); } } diff --git a/sfx2/source/view/viewprn.cxx b/sfx2/source/view/viewprn.cxx index f287c215c9c6..543340af6b8e 100644 --- a/sfx2/source/view/viewprn.cxx +++ b/sfx2/source/view/viewprn.cxx @@ -90,7 +90,7 @@ public: const Any& i_rViewProp, const Reference< view::XRenderable >& i_xRender, bool i_bApi, bool i_bDirect, - SfxViewShell* pView, + SfxViewShell& rView, const uno::Sequence< beans::PropertyValue >& rProps ); @@ -109,15 +109,15 @@ SfxPrinterController::SfxPrinterController( const VclPtr& i_rPrinter, const Any& i_rViewProp, const Reference< view::XRenderable >& i_xRender, bool i_bApi, bool i_bDirect, - SfxViewShell* pView, + SfxViewShell& rView, const uno::Sequence< beans::PropertyValue >& rProps ) - : PrinterController(i_rPrinter, pView ? pView->GetFrameWeld() : nullptr) + : PrinterController(i_rPrinter, rView.GetFrameWeld()) , maCompleteSelection(std::move( i_Complete )) , maSelection(std::move( i_Selection )) , mxRenderable( i_xRender ) , mpLastPrinter( nullptr ) - , mpViewShell( pView ) + , mpViewShell(&rView) , mpObjectShell(nullptr) , m_bJobStarted( false ) , m_bOrigStatus( false ) @@ -125,12 +125,9 @@ SfxPrinterController::SfxPrinterController( const VclPtr& i_rPrinter, , m_bApi(i_bApi) , m_bTempPrinter( i_rPrinter ) { - if ( mpViewShell ) - { - StartListening( *mpViewShell ); - mpObjectShell = mpViewShell->GetObjectShell(); - StartListening( *mpObjectShell ); - } + StartListening(*mpViewShell); + mpObjectShell = mpViewShell->GetObjectShell(); + StartListening(*mpObjectShell); // initialize extra ui options if( mxRenderable.is() ) @@ -606,16 +603,7 @@ void SfxViewShell::StartPrint( const uno::Sequence < beans::PropertyValue >& rPr } std::shared_ptr xNewController(std::make_shared( - aPrt, - aComplete, - aSelection, - aViewProp, - GetRenderable(), - bIsAPI, - bIsDirect, - this, - rProps - )); + aPrt, aComplete, aSelection, aViewProp, GetRenderable(), bIsAPI, bIsDirect, *this, rProps)); pImpl->m_xPrinterController = xNewController; // When no JobName was specified via com::sun::star::view::PrintOptions::JobName , @@ -625,7 +613,7 @@ void SfxViewShell::StartPrint( const uno::Sequence < beans::PropertyValue >& rPr { if (SfxObjectShell* pDoc = GetObjectShell()) { - xNewController->setValue(u"JobName"_ustr, Any(pDoc->GetTitle(1))); + xNewController->setValue(u"JobName"_ustr, Any(pDoc->GetTitle(SFX_TITLE_FILENAME))); xNewController->setPrinterModified(mbPrinterSettingsModified); } } diff --git a/shell/source/unix/exec/shellexec.cxx b/shell/source/unix/exec/shellexec.cxx index 07414371f618..d6e7da911a17 100644 --- a/shell/source/unix/exec/shellexec.cxx +++ b/shell/source/unix/exec/shellexec.cxx @@ -229,10 +229,7 @@ void SAL_CALL ShellExec::execute( const OUString& aCommand, const OUString& aPar aBuffer.append("--args "); } #endif - if( nFlags != 42 ) - escapeForShell(aBuffer, OUStringToOString(aParameter, osl_getThreadTextEncoding())); - else - aBuffer.append(OUStringToOString(aParameter, osl_getThreadTextEncoding())); + escapeForShell(aBuffer, OUStringToOString(aParameter, osl_getThreadTextEncoding())); } } diff --git a/slideshow/source/engine/shapes/viewmediashape.cxx b/slideshow/source/engine/shapes/viewmediashape.cxx index fb4d833b4d9e..c40e848d60d5 100644 --- a/slideshow/source/engine/shapes/viewmediashape.cxx +++ b/slideshow/source/engine/shapes/viewmediashape.cxx @@ -40,7 +40,6 @@ #include #include #include -#include #include #include @@ -271,16 +270,9 @@ namespace slideshow::internal bool ViewMediaShape::implMediaLinkAllowed() const { - SdrObject* pObj = SdrObject::getSdrObjectFromXShape(mxShape); - if (!pObj) - return false; - uno::Reference xModelProps( - pObj->getSdrModelFromSdrObject().getUnoModel(), uno::UNO_QUERY); - if (!xModelProps.is()) - return false; - bool bAllow = false; - xModelProps->getPropertyValue(u"AllowLinkUpdate"_ustr) >>= bAllow; - return bAllow; + SdrMediaObj* pObj + = dynamic_cast(SdrObject::getSdrObjectFromXShape(mxShape)); + return pObj && pObj->isLinkAllowed(); } diff --git a/solenv/clang-format/excludelist b/solenv/clang-format/excludelist index c27a50429f40..610f50f09120 100644 --- a/solenv/clang-format/excludelist +++ b/solenv/clang-format/excludelist @@ -13258,7 +13258,6 @@ toolkit/inc/controls/table/AccessibleGridControlHeader.hxx toolkit/inc/controls/table/AccessibleGridControlHeaderCell.hxx toolkit/inc/controls/table/AccessibleGridControlTable.hxx toolkit/inc/controls/table/AccessibleGridControlTableBase.hxx -toolkit/inc/controls/table/defaultinputhandler.hxx toolkit/inc/controls/table/gridtablerenderer.hxx toolkit/inc/controls/table/tablecontrolinterface.hxx toolkit/inc/controls/table/tablemodel.hxx @@ -13326,9 +13325,7 @@ toolkit/source/controls/table/AccessibleGridControlTable.cxx toolkit/source/controls/table/AccessibleGridControlTableBase.cxx toolkit/source/controls/table/cellvalueconversion.cxx toolkit/source/controls/table/cellvalueconversion.hxx -toolkit/source/controls/table/defaultinputhandler.cxx toolkit/source/controls/table/gridtablerenderer.cxx -toolkit/source/controls/table/mousefunction.cxx toolkit/source/controls/table/tabledatawindow.cxx toolkit/source/controls/table/tabledatawindow.hxx toolkit/source/controls/table/tablegeometry.cxx @@ -13346,8 +13343,6 @@ toolkit/source/controls/unocontrolcontainer.cxx toolkit/source/controls/unocontrolcontainermodel.cxx toolkit/source/controls/unocontrolmodel.cxx toolkit/source/controls/unocontrols.cxx -toolkit/source/controls/unocontroltablemodel.cxx -toolkit/source/controls/unocontroltablemodel.hxx toolkit/source/controls/unogridcolumnfacade.cxx toolkit/source/controls/unogridcolumnfacade.hxx toolkit/source/hatchwindow/documentcloser.cxx @@ -14006,6 +14001,8 @@ vcl/inc/opengl/win/WinDeviceInfo.hxx vcl/inc/osx/a11yfactory.h vcl/inc/osx/a11yfocustracker.hxx vcl/inc/osx/a11ywrapper.h +vcl/inc/osx/clipboard.hxx +vcl/inc/osx/DataFlavorMapping.hxx vcl/inc/osx/printview.h vcl/inc/osx/runinmain.hxx vcl/inc/osx/saldata.hxx @@ -14244,7 +14241,6 @@ vcl/ios/iosinst.cxx vcl/ios/salios.cxx vcl/null/printerinfomanager.cxx vcl/osx/DataFlavorMapping.cxx -vcl/osx/DataFlavorMapping.hxx vcl/osx/DragSource.cxx vcl/osx/DragSource.hxx vcl/osx/DragSourceContext.cxx @@ -14261,7 +14257,6 @@ vcl/osx/a11ylistener.cxx vcl/osx/a11yrolehelper.h vcl/osx/a11ytextattributeswrapper.h vcl/osx/clipboard.cxx -vcl/osx/clipboard.hxx vcl/osx/cuidraw.hxx vcl/osx/documentfocuslistener.cxx vcl/osx/documentfocuslistener.hxx diff --git a/solenv/flatpak-external-tarballs.json.in b/solenv/flatpak-external-tarballs.json.in index 6c3e938ddb36..efd6226dc0e3 100644 --- a/solenv/flatpak-external-tarballs.json.in +++ b/solenv/flatpak-external-tarballs.json.in @@ -496,13 +496,6 @@ "dest": "external/tarballs", "dest-filename": "@FONT_LIBRE_HEBREW_TARBALL@" }, - { - "url": "https://dev-www.libreoffice.org/src/@FONT_SCHEHERAZADE_TARBALL@", - "sha256": "@FONT_SCHEHERAZADE_SHA256SUM@", - "type": "file", - "dest": "external/tarballs", - "dest-filename": "@FONT_SCHEHERAZADE_TARBALL@" - }, { "url": "https://dev-www.libreoffice.org/src/@BSH_TARBALL@", "sha256": "@BSH_SHA256SUM@", diff --git a/solenv/sanitizers/ui/svt.suppr b/solenv/sanitizers/ui/svt.suppr index 04fbd9e0db29..bfd567eada9d 100644 --- a/solenv/sanitizers/ui/svt.suppr +++ b/solenv/sanitizers/ui/svt.suppr @@ -25,11 +25,13 @@ svtools/uiconfig/ui/restartdialog.ui://GtkLabel[@id='reason_skia'] orphan-label svtools/uiconfig/ui/restartdialog.ui://GtkLabel[@id='label'] orphan-label svtools/uiconfig/ui/spinfieldcontrol.ui://GtkSpinButton[@id='spinbutton'] no-labelled-by svtools/uiconfig/ui/tabbaredit.ui://GtkEntry[@id='entry'] no-labelled-by +svtools/uiconfig/ui/tabbuttons.ui://GtkButton[@id='list'] button-no-label svtools/uiconfig/ui/tabbuttons.ui://GtkButton[@id='first'] button-no-label svtools/uiconfig/ui/tabbuttons.ui://GtkButton[@id='next'] button-no-label svtools/uiconfig/ui/tabbuttons.ui://GtkButton[@id='prev'] button-no-label svtools/uiconfig/ui/tabbuttons.ui://GtkButton[@id='last'] button-no-label svtools/uiconfig/ui/tabbuttons.ui://GtkButton[@id='add'] button-no-label +svtools/uiconfig/ui/tabbuttonsmirrored.ui://GtkButton[@id='list'] button-no-label svtools/uiconfig/ui/tabbuttonsmirrored.ui://GtkButton[@id='first'] button-no-label svtools/uiconfig/ui/tabbuttonsmirrored.ui://GtkButton[@id='next'] button-no-label svtools/uiconfig/ui/tabbuttonsmirrored.ui://GtkButton[@id='prev'] button-no-label diff --git a/starmath/source/ooxmlexport.cxx b/starmath/source/ooxmlexport.cxx index aed878f57860..878bd3dca654 100644 --- a/starmath/source/ooxmlexport.cxx +++ b/starmath/source/ooxmlexport.cxx @@ -13,6 +13,8 @@ #include #include #include +#include +#include using namespace oox; using namespace oox::core; @@ -79,6 +81,29 @@ void SmOoxmlExport::ConvertFromStarMath( const ::sax_fastparser::FSHelperPtr& se // NOTE: This is still work in progress and unfinished, but it already covers a good // part of the ooxml math stuff. +// The w prefix is bound in a DOCX and nowhere else. A formula in a presentation or a +// spreadsheet goes out inside a slide or a sheet, which never declare it, so a color +// there would leave the prefix unbound. +bool SmOoxmlExport::HasOwnColor( const SmNode* pNode ) const +{ + return drawingml::DOCUMENT_DOCX == m_DocumentType + && pNode->GetFont().GetColor() != COL_AUTO; +} + +// A construct such as a fraction or a pair of brackets draws part of itself, and the +// color of that part travels in the m:ctrlPr of the construct's property element. +void SmOoxmlExport::WriteCtrlPrColor( const SmNode* pNode ) +{ + if( !HasOwnColor( pNode )) + return; + m_pSerializer->startElementNS( XML_m, XML_ctrlPr ); + m_pSerializer->startElementNS( XML_w, XML_rPr ); + m_pSerializer->singleElementNS( XML_w, XML_color, FSNS( XML_w, XML_val ), + msfilter::util::ConvertColorOU( pNode->GetFont().GetColor())); + m_pSerializer->endElementNS( XML_w, XML_rPr ); + m_pSerializer->endElementNS( XML_m, XML_ctrlPr ); +} + void SmOoxmlExport::HandleVerticalStack( const SmNode* pNode, int nLevel ) { m_pSerializer->startElementNS(XML_m, XML_eqArr); @@ -105,11 +130,24 @@ void SmOoxmlExport::HandleText( const SmNode* pNode, int /*nLevel*/) m_pSerializer->singleElementNS(XML_m, XML_nor); m_pSerializer->endElementNS( XML_m, XML_rPr ); } - if (drawingml::DOCUMENT_DOCX == m_DocumentType && ECMA_376_1ST_EDITION == version) - { // HACK: MSOffice2007 does not import characters properly unless this font is explicitly given + const bool bWriteFont + = drawingml::DOCUMENT_DOCX == m_DocumentType && ECMA_376_1ST_EDITION == version; + // A surrounding color command has already reached this node. + const Color aColor = pNode->GetFont().GetColor(); + const bool bWriteColor + = drawingml::DOCUMENT_DOCX == m_DocumentType && aColor != COL_AUTO; + if( bWriteFont || bWriteColor ) + { m_pSerializer->startElementNS(XML_w, XML_rPr); - m_pSerializer->singleElementNS( XML_w, XML_rFonts, FSNS( XML_w, XML_ascii ), "Cambria Math", - FSNS( XML_w, XML_hAnsi ), "Cambria Math" ); + if( bWriteFont ) + // HACK: MSOffice2007 does not import characters properly unless this font is + // explicitly given + m_pSerializer->singleElementNS( XML_w, XML_rFonts, + FSNS( XML_w, XML_ascii ), "Cambria Math", + FSNS( XML_w, XML_hAnsi ), "Cambria Math" ); + if( bWriteColor ) + m_pSerializer->singleElementNS( XML_w, XML_color, FSNS( XML_w, XML_val ), + msfilter::util::ConvertColorOU( aColor )); m_pSerializer->endElementNS( XML_w, XML_rPr ); } m_pSerializer->startElementNS(XML_m, XML_t, FSNS(XML_xml, XML_space), "preserve"); @@ -174,10 +212,12 @@ void SmOoxmlExport::HandleText( const SmNode* pNode, int /*nLevel*/) void SmOoxmlExport::HandleFractions( const SmNode* pNode, int nLevel, const char* type ) { m_pSerializer->startElementNS(XML_m, XML_f); - if( type != nullptr ) + if( type != nullptr || HasOwnColor( pNode )) { m_pSerializer->startElementNS(XML_m, XML_fPr); - m_pSerializer->singleElementNS(XML_m, XML_type, FSNS(XML_m, XML_val), type); + if( type != nullptr ) + m_pSerializer->singleElementNS(XML_m, XML_type, FSNS(XML_m, XML_val), type); + WriteCtrlPrColor( pNode ); m_pSerializer->endElementNS( XML_m, XML_fPr ); } assert( pNode->GetNumSubNodes() == 3 ); @@ -217,6 +257,7 @@ void SmOoxmlExport::HandleAttribute( const SmAttributeNode* pNode, int nLevel ) m_pSerializer->startElementNS(XML_m, XML_accPr); OString value = OUStringToOString(pNode->Attribute()->GetToken().cMathChar, RTL_TEXTENCODING_UTF8 ); m_pSerializer->singleElementNS(XML_m, XML_chr, FSNS(XML_m, XML_val), value); + WriteCtrlPrColor( pNode ); m_pSerializer->endElementNS( XML_m, XML_accPr ); m_pSerializer->startElementNS(XML_m, XML_e); HandleNode( pNode->Body(), nLevel + 1 ); @@ -230,6 +271,7 @@ void SmOoxmlExport::HandleAttribute( const SmAttributeNode* pNode, int nLevel ) m_pSerializer->startElementNS(XML_m, XML_barPr); m_pSerializer->singleElementNS( XML_m, XML_pos, FSNS( XML_m, XML_val ), ( pNode->Attribute()->GetToken().eType == TUNDERLINE ) ? "bot" : "top" ); + WriteCtrlPrColor( pNode ); m_pSerializer->endElementNS( XML_m, XML_barPr ); m_pSerializer->startElementNS(XML_m, XML_e); HandleNode( pNode->Body(), nLevel + 1 ); @@ -244,6 +286,7 @@ void SmOoxmlExport::HandleAttribute( const SmAttributeNode* pNode, int nLevel ) m_pSerializer->singleElementNS(XML_m, XML_hideLeft, FSNS(XML_m, XML_val), "1"); m_pSerializer->singleElementNS(XML_m, XML_hideRight, FSNS(XML_m, XML_val), "1"); m_pSerializer->singleElementNS(XML_m, XML_strikeH, FSNS(XML_m, XML_val), "1"); + WriteCtrlPrColor( pNode ); m_pSerializer->endElementNS( XML_m, XML_borderBoxPr ); m_pSerializer->startElementNS(XML_m, XML_e); HandleNode( pNode->Body(), nLevel + 1 ); @@ -261,6 +304,12 @@ void SmOoxmlExport::HandleRoot( const SmRootNode* pNode, int nLevel ) m_pSerializer->startElementNS(XML_m, XML_rad); if( const SmNode* argument = pNode->Argument()) { + if( HasOwnColor( pNode )) + { + m_pSerializer->startElementNS(XML_m, XML_radPr); + WriteCtrlPrColor( pNode ); + m_pSerializer->endElementNS( XML_m, XML_radPr ); + } m_pSerializer->startElementNS(XML_m, XML_deg); HandleNode( argument, nLevel + 1 ); m_pSerializer->endElementNS( XML_m, XML_deg ); @@ -269,6 +318,7 @@ void SmOoxmlExport::HandleRoot( const SmRootNode* pNode, int nLevel ) { m_pSerializer->startElementNS(XML_m, XML_radPr); m_pSerializer->singleElementNS(XML_m, XML_degHide, FSNS(XML_m, XML_val), "1"); + WriteCtrlPrColor( pNode ); m_pSerializer->endElementNS( XML_m, XML_radPr ); m_pSerializer->singleElementNS(XML_m, XML_deg); // empty but present } @@ -314,6 +364,7 @@ void SmOoxmlExport::HandleOperator( const SmOperNode* pNode, int nLevel ) m_pSerializer->singleElementNS(XML_m, XML_subHide, FSNS(XML_m, XML_val), "1"); if( subsup == nullptr || subsup->GetSubSup( CSUP ) == nullptr ) m_pSerializer->singleElementNS(XML_m, XML_supHide, FSNS(XML_m, XML_val), "1"); + WriteCtrlPrColor( pNode ); m_pSerializer->endElementNS( XML_m, XML_naryPr ); if( subsup == nullptr || subsup->GetSubSup( CSUB ) == nullptr ) m_pSerializer->singleElementNS(XML_m, XML_sub); @@ -537,6 +588,7 @@ void SmOoxmlExport::HandleBrace( const SmBraceNode* pNode, int nLevel ) m_pSerializer->singleElementNS( XML_m, XML_endChr, FSNS( XML_m, XML_val ), mathSymbolToString(pNode->ClosingBrace()) ); + WriteCtrlPrColor( pNode ); m_pSerializer->endElementNS( XML_m, XML_dPr ); if (subnodes.empty()) { @@ -575,6 +627,7 @@ void SmOoxmlExport::HandleVerticalBrace( const SmVerticalBraceNode* pNode, int n FSNS( XML_m, XML_val ), top ? "top" : "bot" ); m_pSerializer->singleElementNS(XML_m, XML_vertJc, FSNS(XML_m, XML_val), top ? "bot" : "top"); + WriteCtrlPrColor( pNode ); m_pSerializer->endElementNS( XML_m, XML_groupChrPr ); m_pSerializer->startElementNS(XML_m, XML_e); HandleNode( pNode->Body(), nLevel + 1 ); diff --git a/starmath/source/ooxmlexport.hxx b/starmath/source/ooxmlexport.hxx index cd2e9d446585..2d0b182e1b99 100644 --- a/starmath/source/ooxmlexport.hxx +++ b/starmath/source/ooxmlexport.hxx @@ -37,6 +37,11 @@ private: void HandleBrace(const SmBraceNode* pNode, int nLevel) override; void HandleVerticalBrace(const SmVerticalBraceNode* pNode, int nLevel) override; void HandleBlank() override; + /// Writes the m:ctrlPr carrying the color of the parts a construct draws itself. + void WriteCtrlPrColor(const SmNode* pNode); + /// True when the color of the node is written out, which needs a color other than + /// the default and a DOCX. + bool HasOwnColor(const SmNode* pNode) const; ::sax_fastparser::FSHelperPtr m_pSerializer; oox::core::OoxmlVersion version; /// needed to determine markup for nested run properties diff --git a/starmath/source/ooxmlimport.cxx b/starmath/source/ooxmlimport.cxx index b821134413bd..4a294c7450e2 100644 --- a/starmath/source/ooxmlimport.cxx +++ b/starmath/source/ooxmlimport.cxx @@ -16,12 +16,15 @@ #include #include +#include #include #include #include #include #include +#include #include +#include using namespace oox::formulaimport; @@ -35,11 +38,123 @@ The primary internal data structure for the formula is the text representation // TODO create IS_OPENING(), IS_CLOSING() instead of doing 'next == OPENING( next )' ? +// True for text that is nothing but math operators. Such text is not an expression on +// its own, so the braces of a color command would make the parser fail on it. +static bool lcl_IsAllMathOperators( std::u16string_view sText ) +{ + if( sText.empty()) + return false; + for( char16_t cChar : sText ) + if( !isSingleCharMathOperator( cChar )) + return false; + return true; +} + +// True when the text stands as an expression of its own, which is what the braces of a +// color command need to hold. Parsing leaves no state behind, so one parser serves every +// run of a formula. +static bool lcl_ParsesAsExpression( SmParser5& rParser, const OUString& rText ) +{ + rParser.ParseExpression( rText ); + return rParser.GetError() == nullptr; +} + +// Wraps the text in a color command. Nothing is added when there is no color, when the +// color is already in effect, or when the text is empty. +static OUString lcl_ApplyColorCommand( const OUString& rText, std::u16string_view sColorCommand, + std::u16string_view sInEffect ) +{ + if( sColorCommand.empty() || sColorCommand == sInEffect || rText.isEmpty()) + return rText; + return OUString::Concat( sColorCommand ) + " {" + rText + "}"; +} + +// A run holds whatever text the document put in it, which need not be an expression. A +// run of operators is written as a literal, because that is an expression and it keeps +// the color. Anything else that does not parse keeps the color off, so that the formula +// reads as it did before the color was carried over at all. +static OUString lcl_ApplyRunColorCommand( SmParser5& rParser, const OUString& rText, + std::u16string_view sColorCommand, std::u16string_view sInEffect ) +{ + if( sColorCommand.empty() || sColorCommand == sInEffect || rText.isEmpty()) + return rText; + if( lcl_IsAllMathOperators( rText )) + return OUString::Concat( sColorCommand ) + " {\"" + rText + "\"}"; + if( !lcl_ParsesAsExpression( rParser, rText )) + return rText; + return OUString::Concat( sColorCommand ) + " {" + rText + "}"; +} + +namespace +{ +// Keeps the color command that applies while the parts of one construct are read, +// and puts the previous one back when the construct is done with. +class ColorInEffect +{ +public: + ColorInEffect( OUString& rInEffect, OUString sColorCommand ) + : m_rInEffect( rInEffect ) + , m_sPrevious( rInEffect ) + , m_sColorCommand( std::move( sColorCommand )) + { + if( !m_sColorCommand.isEmpty()) + m_rInEffect = m_sColorCommand; + } + ~ColorInEffect() { m_rInEffect = m_sPrevious; } + // Wraps the construct in its color command. The command is left off when the color + // is already in effect, so the formula names the color only once. + OUString apply( const OUString& rText ) const + { + return lcl_ApplyColorCommand( rText, m_sColorCommand, m_sPrevious ); + } + +private: + OUString& m_rInEffect; + OUString m_sPrevious; + OUString m_sColorCommand; +}; +} + +// A w:color value is either six hexadecimal digits or the word "auto". Anything else +// gives an empty string back, which leaves the default color in place. Black does the +// same, because the default color follows a dark document theme and an explicit black +// would not. +static OUString lcl_ColorCommandFromOoxmlValue( std::u16string_view sValue ) +{ + if( sValue.size() != 6 ) + return OUString(); + for( char16_t cDigit : sValue ) + if( !rtl::isAsciiHexDigit( cDigit )) + return OUString(); + const sal_uInt32 nColor = o3tl::toUInt32( sValue, 16 ); + if( nColor == 0 ) + return OUString(); + // A color that StarMath names is written with that name, so "color red" comes back + // as it was written. The parser takes these two types bare. Other named colors need + // a keyword in front of the name, so they are written as digits instead. The parser + // reads only uppercase digits, so the value goes out in uppercase. + const SmColorTokenTableEntry aEntry = starmathdatabase::Identify_Color_Parser( nColor ); + if( aEntry.eType == THTMLCOL || aEntry.eType == TMATHMLCOL ) + return "color " + aEntry.aIdent; + return "color hex " + OUString( sValue ).toAsciiUpperCase(); +} + SmOoxmlImport::SmOoxmlImport( oox::formulaimport::XmlStream& s ) : m_rStream( s ) { } +SmOoxmlImport::~SmOoxmlImport() +{ +} + +SmParser5& SmOoxmlImport::getParser() +{ + if( !m_pParser ) + m_pParser.reset( new SmParser5 ); + return *m_pParser; +} + OUString SmOoxmlImport::ConvertToStarMath() { return handleStream(); @@ -164,6 +279,7 @@ OUString SmOoxmlImport::handleAcc() { m_rStream.ensureOpeningTag( M_TOKEN( acc )); sal_Unicode accChr = 0x302; + OUString sColorCommand; if( XmlStream::Tag accPr = m_rStream.checkOpeningTag( M_TOKEN( accPr ))) { if( XmlStream::Tag chr = m_rStream.checkOpeningTag( M_TOKEN( chr ))) @@ -171,8 +287,10 @@ OUString SmOoxmlImport::handleAcc() accChr = chr.attribute( M_TOKEN( val ), accChr ); m_rStream.ensureClosingTag( M_TOKEN( chr )); } + sColorCommand = readCtrlPrColorCommand(); m_rStream.ensureClosingTag( M_TOKEN( accPr )); } + ColorInEffect aColorInEffect( m_sColorCommandInEffect, sColorCommand ); // see aTokenTable in parse.cxx OUString acc; switch( accChr ) @@ -246,13 +364,14 @@ OUString SmOoxmlImport::handleAcc() } OUString e = readOMathArgInElement( M_TOKEN( e )); m_rStream.ensureClosingTag( M_TOKEN( acc )); - return acc + " {" + e + "}"; + return aColorInEffect.apply( acc + " {" + e + "}" ); } OUString SmOoxmlImport::handleBar() { m_rStream.ensureOpeningTag( M_TOKEN( bar )); enum pos_t { top, bot } topbot = bot; + OUString sColorCommand; if( m_rStream.checkOpeningTag( M_TOKEN( barPr ))) { if( XmlStream::Tag pos = m_rStream.checkOpeningTag( M_TOKEN( pos ))) @@ -263,14 +382,16 @@ OUString SmOoxmlImport::handleBar() topbot = bot; m_rStream.ensureClosingTag( M_TOKEN( pos )); } + sColorCommand = readCtrlPrColorCommand(); m_rStream.ensureClosingTag( M_TOKEN( barPr )); } + ColorInEffect aColorInEffect( m_sColorCommandInEffect, sColorCommand ); OUString e = readOMathArgInElement( M_TOKEN( e )); m_rStream.ensureClosingTag( M_TOKEN( bar )); if( topbot == top ) - return "overline {" + e + "}"; + return aColorInEffect.apply( "overline {" + e + "}" ); else - return "underline {" + e + "}"; + return aColorInEffect.apply( "underline {" + e + "}" ); } OUString SmOoxmlImport::handleBox() @@ -288,6 +409,7 @@ OUString SmOoxmlImport::handleBorderBox() { m_rStream.ensureOpeningTag( M_TOKEN( borderBox )); bool isStrikeH = false; + OUString sColorCommand; if( m_rStream.checkOpeningTag( M_TOKEN( borderBoxPr ))) { if( XmlStream::Tag strikeH = m_rStream.checkOpeningTag( M_TOKEN( strikeH ))) @@ -296,14 +418,17 @@ OUString SmOoxmlImport::handleBorderBox() isStrikeH = true; m_rStream.ensureClosingTag( M_TOKEN( strikeH )); } + sColorCommand = readCtrlPrColorCommand(); m_rStream.ensureClosingTag( M_TOKEN( borderBoxPr )); } + ColorInEffect aColorInEffect( m_sColorCommandInEffect, sColorCommand ); OUString e = readOMathArgInElement( M_TOKEN( e )); m_rStream.ensureClosingTag( M_TOKEN( borderBox )); if( isStrikeH ) - return "overstrike {" + e + "}"; - // LO does not seem to implement anything for handling the other cases - return e; + return aColorInEffect.apply( "overstrike {" + e + "}" ); + // LO does not seem to implement anything for handling the other cases. The color + // still belongs on the content, which is all that is left of the border box. + return aColorInEffect.apply( e ); } OUString SmOoxmlImport::handleD() @@ -312,6 +437,7 @@ OUString SmOoxmlImport::handleD() OUString opening = u"("_ustr; OUString closing = u")"_ustr; OUString separator = u"|"_ustr; + OUString sColorCommand; if( XmlStream::Tag dPr = m_rStream.checkOpeningTag( M_TOKEN( dPr ))) { if( XmlStream::Tag begChr = m_rStream.checkOpeningTag( M_TOKEN( begChr ))) @@ -329,8 +455,10 @@ OUString SmOoxmlImport::handleD() closing = endChr.attribute( M_TOKEN( val ), closing ); m_rStream.ensureClosingTag( M_TOKEN( endChr )); } + sColorCommand = readCtrlPrColorCommand(); m_rStream.ensureClosingTag( M_TOKEN( dPr )); } + ColorInEffect aColorInEffect( m_sColorCommandInEffect, sColorCommand ); if( opening == "{" ) opening = "left lbrace "; if( closing == "}" ) @@ -377,7 +505,7 @@ OUString SmOoxmlImport::handleD() } ret.append( closing ); m_rStream.ensureClosingTag( M_TOKEN( d )); - return ret.makeStringAndClear(); + return aColorInEffect.apply( ret.makeStringAndClear() ); } OUString SmOoxmlImport::handleEqArr() @@ -400,6 +528,7 @@ OUString SmOoxmlImport::handleF() { m_rStream.ensureOpeningTag( M_TOKEN( f )); enum operation_t { bar, lin, noBar } operation = bar; + OUString sColorCommand; if( m_rStream.checkOpeningTag( M_TOKEN( fPr ))) { if( XmlStream::Tag type = m_rStream.checkOpeningTag( M_TOKEN( type ))) @@ -412,18 +541,20 @@ OUString SmOoxmlImport::handleF() operation = noBar; m_rStream.ensureClosingTag( M_TOKEN( type )); } + sColorCommand = readCtrlPrColorCommand(); m_rStream.ensureClosingTag( M_TOKEN( fPr )); } + ColorInEffect aColorInEffect( m_sColorCommandInEffect, sColorCommand ); OUString num = readOMathArgInElement( M_TOKEN( num )); OUString den = readOMathArgInElement( M_TOKEN( den )); m_rStream.ensureClosingTag( M_TOKEN( f )); if( operation == bar ) - return "{" + num + "} over {" + den + "}"; + return aColorInEffect.apply( "{" + num + "} over {" + den + "}" ); else if( operation == lin ) - return "{" + num + "} / {" + den + "}"; + return aColorInEffect.apply( "{" + num + "} / {" + den + "}" ); else // noBar { - return "binom {" + num + "} {" + den + "}"; + return aColorInEffect.apply( "binom {" + num + "} {" + den + "}" ); } } @@ -463,6 +594,7 @@ OUString SmOoxmlImport::handleGroupChr() m_rStream.ensureOpeningTag( M_TOKEN( groupChr )); sal_Unicode chr = 0x23df; enum pos_t { top, bot } pos = bot; + OUString sColorCommand; if( m_rStream.checkOpeningTag( M_TOKEN( groupChrPr ))) { if( XmlStream::Tag chrTag = m_rStream.checkOpeningTag( M_TOKEN( chr ))) @@ -476,18 +608,20 @@ OUString SmOoxmlImport::handleGroupChr() pos = top; m_rStream.ensureClosingTag( M_TOKEN( pos )); } + sColorCommand = readCtrlPrColorCommand(); m_rStream.ensureClosingTag( M_TOKEN( groupChrPr )); } + ColorInEffect aColorInEffect( m_sColorCommandInEffect, sColorCommand ); OUString e = readOMathArgInElement( M_TOKEN( e )); m_rStream.ensureClosingTag( M_TOKEN( groupChr )); if( pos == top && chr == u'\x23de') - return "{" + e + "} overbrace { }"; + return aColorInEffect.apply( "{" + e + "} overbrace { }" ); if( pos == bot && chr == u'\x23df') - return "{" + e + "} underbrace { }"; + return aColorInEffect.apply( "{" + e + "} underbrace { }" ); if( pos == top ) - return "{" + e + "} csup {" + OUStringChar( chr ) + "}"; + return aColorInEffect.apply( "{" + e + "} csup {" + OUStringChar( chr ) + "}" ); else - return "{" + e + "} csub {" + OUStringChar( chr ) + "}"; + return aColorInEffect.apply( "{" + e + "} csub {" + OUStringChar( chr ) + "}" ); } OUString SmOoxmlImport::handleM() @@ -519,6 +653,7 @@ OUString SmOoxmlImport::handleNary() sal_Unicode chr = 0x222b; bool subHide = false; bool supHide = false; + OUString sColorCommand; if( m_rStream.checkOpeningTag( M_TOKEN( naryPr ))) { if( XmlStream::Tag chrTag = m_rStream.checkOpeningTag( M_TOKEN( chr ))) @@ -536,8 +671,10 @@ OUString SmOoxmlImport::handleNary() supHide = supHideTag.attribute( M_TOKEN( val ), supHide ); m_rStream.ensureClosingTag( M_TOKEN( supHide )); } + sColorCommand = readCtrlPrColorCommand(); m_rStream.ensureClosingTag( M_TOKEN( naryPr )); } + ColorInEffect aColorInEffect( m_sColorCommandInEffect, sColorCommand ); OUString sub = readOMathArgInElement( M_TOKEN( sub )); OUString sup = readOMathArgInElement( M_TOKEN( sup )); OUString e = readOMathArgInElement( M_TOKEN( e )); @@ -581,7 +718,29 @@ OUString SmOoxmlImport::handleNary() ret += " to {" + sup + "}"; ret += " {" + e + "}"; m_rStream.ensureClosingTag( M_TOKEN( nary )); - return ret; + return aColorInEffect.apply( ret ); +} + +// An m:ctrlPr is the last child of a construct's property element. It carries the +// formatting of the parts the construct draws itself, such as a fraction bar or a +// pair of brackets, and this reads the color out of it. +OUString SmOoxmlImport::readCtrlPrColorCommand() +{ + OUString sRet; + if( m_rStream.checkOpeningTag( M_TOKEN( ctrlPr ))) + { + if( m_rStream.checkOpeningTag( W_TOKEN( rPr ))) + { + if( XmlStream::Tag aColorTag = m_rStream.checkOpeningTag( W_TOKEN( color ))) + { + sRet = lcl_ColorCommandFromOoxmlValue( aColorTag.attribute( W_TOKEN( val ))); + m_rStream.ensureClosingTag( W_TOKEN( color )); + } + m_rStream.ensureClosingTag( W_TOKEN( rPr )); + } + m_rStream.ensureClosingTag( M_TOKEN( ctrlPr )); + } + return sRet; } // NOT complete @@ -591,6 +750,7 @@ OUString SmOoxmlImport::handleR() bool normal = false; bool literal = false; OUString scrString; + OUString sColorCommand; if( XmlStream::Tag rPr = m_rStream.checkOpeningTag( M_TOKEN( rPr ))) { if( XmlStream::Tag litTag = m_rStream.checkOpeningTag( M_TOKEN( lit ))) @@ -616,6 +776,20 @@ OUString SmOoxmlImport::handleR() { switch( m_rStream.currentToken()) { + // A run has two property elements. The math one m:rPr is read above. + // This is the text one w:rPr and it holds the font color. + case OPENING( W_TOKEN( rPr )): + { + m_rStream.ensureOpeningTag( W_TOKEN( rPr )); + if( XmlStream::Tag aColorTag = m_rStream.checkOpeningTag( W_TOKEN( color ))) + { + sColorCommand + = lcl_ColorCommandFromOoxmlValue( aColorTag.attribute( W_TOKEN( val ))); + m_rStream.ensureClosingTag( W_TOKEN( color )); + } + m_rStream.ensureClosingTag( W_TOKEN( rPr )); + break; + } case OPENING( M_TOKEN( t )): { isTagT = true; @@ -637,11 +811,12 @@ OUString SmOoxmlImport::handleR() } } m_rStream.ensureClosingTag( M_TOKEN( r )); + OUString sRet; if (scrString.isEmpty() && (normal || literal || isTagT)) - { - return encloseOrEscapeLiteral(text.makeStringAndClear(), normal || literal); - } - return text.makeStringAndClear(); + sRet = encloseOrEscapeLiteral(text.makeStringAndClear(), normal || literal); + else + sRet = text.makeStringAndClear(); + return lcl_ApplyRunColorCommand( getParser(), sRet, sColorCommand, m_sColorCommandInEffect ); } OUString SmOoxmlImport::handleSetString(const OUString& setOUstring) @@ -664,6 +839,7 @@ OUString SmOoxmlImport::handleRad() { m_rStream.ensureOpeningTag( M_TOKEN( rad )); bool degHide = false; + OUString sColorCommand; if( m_rStream.checkOpeningTag( M_TOKEN( radPr ))) { if( XmlStream::Tag degHideTag = m_rStream.checkOpeningTag( M_TOKEN( degHide ))) @@ -671,15 +847,17 @@ OUString SmOoxmlImport::handleRad() degHide = degHideTag.attribute( M_TOKEN( val ), degHide ); m_rStream.ensureClosingTag( M_TOKEN( degHide )); } + sColorCommand = readCtrlPrColorCommand(); m_rStream.ensureClosingTag( M_TOKEN( radPr )); } + ColorInEffect aColorInEffect( m_sColorCommandInEffect, sColorCommand ); OUString deg = readOMathArgInElement( M_TOKEN( deg )); OUString e = readOMathArgInElement( M_TOKEN( e )); m_rStream.ensureClosingTag( M_TOKEN( rad )); if( degHide ) - return "sqrt {" + e + "}"; + return aColorInEffect.apply( "sqrt {" + e + "}" ); else - return "nroot {" + deg + "} {" + e + "}"; + return aColorInEffect.apply( "nroot {" + deg + "} {" + e + "}" ); } OUString SmOoxmlImport::handleSpre() diff --git a/starmath/source/ooxmlimport.hxx b/starmath/source/ooxmlimport.hxx index e3621fdc0dec..9c1e64ab5391 100644 --- a/starmath/source/ooxmlimport.hxx +++ b/starmath/source/ooxmlimport.hxx @@ -11,6 +11,9 @@ #include +#include + +class SmParser5; namespace oox::formulaimport { class XmlStream; } /** Class implementing reading of formulas from OOXML. The toplevel element is expected @@ -20,8 +23,11 @@ class SmOoxmlImport { public: explicit SmOoxmlImport( oox::formulaimport::XmlStream& stream ); + ~SmOoxmlImport(); OUString ConvertToStarMath(); private: + /// The parser that tells whether a run's text stands as an expression of its own. + SmParser5& getParser(); OUString handleStream(); OUString handleAcc(); OUString handleBar(); @@ -42,11 +48,18 @@ private: OUString handleSsub(); OUString handleSsubsup(); OUString handleSsup(); + OUString readCtrlPrColorCommand(); OUString readOMathArg( int stoptoken ); OUString readOMathArgInElement( int token ); static OUString handleSetString(const OUString& setOUstring); oox::formulaimport::XmlStream& m_rStream; + // The color command that applies to the part of the formula being read now, or + // an empty string for the default color. + OUString m_sColorCommandInEffect; + // Built on first use, because constructing one creates a character classification + // service, and most formulas never need it. + std::unique_ptr m_pParser; }; /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/crypto/cryptosign.cxx b/svl/source/crypto/cryptosign.cxx index b245de24f103..f9e23c54d89a 100644 --- a/svl/source/crypto/cryptosign.cxx +++ b/svl/source/crypto/cryptosign.cxx @@ -2203,9 +2203,18 @@ bool Signing::Verify(SvStream& rStream, #if USE_CRYPTO_ANY std::vector buffer; + const sal_uInt64 nStreamSize = rStream.TellEnd(); + // Copy the byte ranges into a single buffer. for (const auto& rByteRange : aByteRanges) { + // A signature covers a part of the file, so both ends of every range are inside it. + if (rByteRange.first > nStreamSize || rByteRange.second > nStreamSize - rByteRange.first) + { + SAL_WARN("svl.crypto", "Verify: byte range is outside the stream"); + return false; + } + rStream.Seek(rByteRange.first); const size_t size = buffer.size(); buffer.resize(size + rByteRange.second); diff --git a/svl/source/numbers/numfmuno.cxx b/svl/source/numbers/numfmuno.cxx index 68469c4e9fc4..e6f295d72ad1 100644 --- a/svl/source/numbers/numfmuno.cxx +++ b/svl/source/numbers/numfmuno.cxx @@ -38,6 +38,7 @@ #include #include #include +#include using namespace com::sun::star; @@ -429,7 +430,7 @@ sal_Int32 SAL_CALL SvNumberFormatsObj::queryKey( const OUString& aFormat, const sal_Int32 nLen = aFormat.getLength(); OUStringBuffer aBuf(0); sal_Unicode* p = aBuf.appendUninitialized( nLen + 1); - memcpy( p, aFormat.getStr(), (nLen + 1) * sizeof(sal_Unicode)); // including 0-char + std::copy_n( aFormat.getStr(), nLen + 1, p ); // including 0-char aBuf.setLength( nLen); assert(p == aBuf.getStr()); sal_Unicode const * const pStop = p + aBuf.getLength(); diff --git a/svtools/source/config/fontsubstconfig.cxx b/svtools/source/config/fontsubstconfig.cxx index 62f6043f1927..7dbb5c0d7cf6 100644 --- a/svtools/source/config/fontsubstconfig.cxx +++ b/svtools/source/config/fontsubstconfig.cxx @@ -151,6 +151,20 @@ void ApplyFontSubstitutionsToVcl() OutputDevice::EndFontSubstitution(); } +bool GetFontSubstitute(std::u16string_view sFontName, OUString& rFontName) +{ + std::vector aFontSubs = svtools::GetFontSubstitutions(); + for (auto const & sub: aFontSubs) + { + if (sub.sFont == sFontName) + { + rFontName = sub.sReplaceBy; + return true; + } + } + return false; +} + } // namespace svtools /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/accessibleruler.cxx b/svtools/source/control/accessibleruler.cxx index f3c825c38879..4e54dea27609 100644 --- a/svtools/source/control/accessibleruler.cxx +++ b/svtools/source/control/accessibleruler.cxx @@ -37,11 +37,10 @@ using namespace ::com::sun::star; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::accessibility; - -SvtRulerAccessible::SvtRulerAccessible(uno::Reference xParent, Ruler& rRepr, - OUString aName) +SvtRulerAccessible::SvtRulerAccessible(const rtl::Reference& rpParent, + Ruler& rRepr, OUString aName) : msName(std::move(aName)) - , mxParent(std::move(xParent)) + , mpParent(rpParent) , mpRepr(&rRepr) { } @@ -73,7 +72,7 @@ uno::Reference< XAccessible > SAL_CALL SvtRulerAccessible::getAccessibleChild( s uno::Reference< XAccessible > SAL_CALL SvtRulerAccessible::getAccessibleParent() { SolarMutexGuard aSolarGuard; - return mxParent; + return mpParent; } sal_Int16 SAL_CALL SvtRulerAccessible::getAccessibleRole() @@ -160,7 +159,7 @@ void SAL_CALL SvtRulerAccessible::disposing() comphelper::OAccessible::disposing(); - mxParent.clear(); + mpParent.clear(); } awt::Rectangle SvtRulerAccessible::implGetBounds() diff --git a/svtools/source/control/accessibleruler.hxx b/svtools/source/control/accessibleruler.hxx index 7ee0baef4964..c1309a1513d1 100644 --- a/svtools/source/control/accessibleruler.hxx +++ b/svtools/source/control/accessibleruler.hxx @@ -36,8 +36,8 @@ class Ruler; class SvtRulerAccessible final : public comphelper::OAccessible { public: - SvtRulerAccessible( - css::uno::Reference< css::accessibility::XAccessible> xParent, Ruler& rRepresentation, OUString aName ); + SvtRulerAccessible(const rtl::Reference& rpParent, + Ruler& rRepresentation, OUString aName); //===== XAccessibleComponent ============================================ @@ -88,8 +88,7 @@ private: OUString msName; /// Reference to the parent object. - css::uno::Reference< css::accessibility::XAccessible > - mxParent; + rtl::Reference mpParent; /// pointer to internal representation VclPtr mpRepr; diff --git a/svtools/source/control/accessibletabbarpage.cxx b/svtools/source/control/accessibletabbarpage.cxx index 9c705a2f0d2b..bd455bae2254 100644 --- a/svtools/source/control/accessibletabbarpage.cxx +++ b/svtools/source/control/accessibletabbarpage.cxx @@ -43,10 +43,10 @@ using namespace ::com::sun::star; using namespace ::comphelper; AccessibleTabBarPage::AccessibleTabBarPage(TabBar* pTabBar, sal_uInt16 nPageId, - const Reference& rxParent) + const rtl::Reference& rpParent) : AccessibleTabBarBase(pTabBar) , m_nPageId(nPageId) - , m_xParent(rxParent) + , m_pParent(rpParent) { m_bShowing = IsShowing(); m_bSelected = IsSelected(); @@ -212,7 +212,7 @@ Reference< XAccessible > AccessibleTabBarPage::getAccessibleParent( ) { OExternalLockGuard aGuard( this ); - return m_xParent; + return m_pParent; } diff --git a/svtools/source/control/accessibletabbarpage.hxx b/svtools/source/control/accessibletabbarpage.hxx index 6ed7aa017987..c2e51b2d04c9 100644 --- a/svtools/source/control/accessibletabbarpage.hxx +++ b/svtools/source/control/accessibletabbarpage.hxx @@ -37,7 +37,7 @@ private: bool m_bSelected; OUString m_sPageText; - css::uno::Reference< css::accessibility::XAccessible > m_xParent; + rtl::Reference m_pParent; bool IsEnabled(); bool IsShowing() const; @@ -56,8 +56,8 @@ private: virtual void SAL_CALL disposing() override; public: - AccessibleTabBarPage( TabBar* pTabBar, sal_uInt16 nPageId, - const css::uno::Reference< css::accessibility::XAccessible >& rxParent ); + AccessibleTabBarPage(TabBar* pTabBar, sal_uInt16 nPageId, + const rtl::Reference& rpParent); // XAccessibleContext virtual sal_Int64 SAL_CALL getAccessibleChildCount( ) override; diff --git a/svtools/source/control/accessibletabbarpagelist.cxx b/svtools/source/control/accessibletabbarpagelist.cxx index 8488006577ca..24f4f3178254 100644 --- a/svtools/source/control/accessibletabbarpagelist.cxx +++ b/svtools/source/control/accessibletabbarpagelist.cxx @@ -369,11 +369,10 @@ Reference< XAccessible > AccessibleTabBarPageList::getAccessibleParent( ) { OExternalLockGuard aGuard( this ); - Reference< XAccessible > xParent; if ( m_pTabBar ) - xParent = m_pTabBar->GetAccessible(); + return m_pTabBar->GetAccessible(); - return xParent; + return {}; } @@ -560,18 +559,13 @@ Reference< XAccessible > AccessibleTabBarPageList::getSelectedAccessibleChild( s if ( nSelectedChildIndex < 0 || nSelectedChildIndex >= getSelectedAccessibleChildCount() ) throw IndexOutOfBoundsException(); - Reference< XAccessible > xChild; - for ( sal_Int64 i = 0, j = 0, nCount = getAccessibleChildCount(); i < nCount; i++ ) { if ( isAccessibleChildSelected( i ) && ( j++ == nSelectedChildIndex ) ) - { - xChild = getAccessibleChild( i ); - break; - } + return getAccessibleChild(i); } - return xChild; + return {}; } diff --git a/svtools/source/control/ruler.cxx b/svtools/source/control/ruler.cxx index 75c46a45814d..261e7f59d9f8 100644 --- a/svtools/source/control/ruler.cxx +++ b/svtools/source/control/ruler.cxx @@ -2764,8 +2764,8 @@ void Ruler::DrawTicks() rtl::Reference Ruler::CreateAccessible() { - uno::Reference xAccParent = GetAccessibleParent(); - if( xAccParent.is() ) + rtl::Reference pAccParent = GetAccessibleParent(); + if (pAccParent.is()) { OUString aStr; if ( mnWinStyle & WB_HORZ ) @@ -2776,7 +2776,7 @@ rtl::Reference Ruler::CreateAccessible() { aStr = SvtResId(STR_SVT_ACC_RULER_VERT_NAME); } - return new SvtRulerAccessible(xAccParent, *this, aStr); + return new SvtRulerAccessible(pAccParent, *this, aStr); } else return {}; diff --git a/svtools/source/control/tabbar.cxx b/svtools/source/control/tabbar.cxx index 94bed1616552..1399762f7493 100644 --- a/svtools/source/control/tabbar.cxx +++ b/svtools/source/control/tabbar.cxx @@ -440,6 +440,7 @@ namespace { class TabButtons final : public InterimItemWindow { public: + std::unique_ptr m_xListButton; std::unique_ptr m_xFirstButton; std::unique_ptr m_xPrevButton; std::unique_ptr m_xNextButton; @@ -454,6 +455,7 @@ public: pParent->IsMirrored() ? u"svt/ui/tabbuttonsmirrored.ui"_ustr : u"svt/ui/tabbuttons.ui"_ustr, u"TabButtons"_ustr) + , m_xListButton(m_xBuilder->weld_button(u"list"_ustr)) , m_xFirstButton(m_xBuilder->weld_button(u"first"_ustr)) , m_xPrevButton(m_xBuilder->weld_button(u"prev"_ustr)) , m_xNextButton(m_xBuilder->weld_button(u"next"_ustr)) @@ -464,7 +466,8 @@ public: SetPaintTransparent(false); SetBackground(rStyleSettings.GetFaceColor()); - m_xFirstButton->set_accessible_name(SvtResId(STR_TABBAR_PUSHBUTTON_MOVET0HOME)); + m_xListButton->set_accessible_name(SvtResId(STR_TABBAR_PUSHBUTTON_SHEETSLIST)); + m_xFirstButton->set_accessible_name(SvtResId(STR_TABBAR_PUSHBUTTON_MOVETOHOME)); m_xPrevButton->set_accessible_name(SvtResId(STR_TABBAR_PUSHBUTTON_MOVELEFT)); m_xNextButton->set_accessible_name(SvtResId(STR_TABBAR_PUSHBUTTON_MOVERIGHT)); m_xLastButton->set_accessible_name(SvtResId(STR_TABBAR_PUSHBUTTON_MOVETOEND)); @@ -472,6 +475,7 @@ public: if (bSheets) { + m_xListButton->set_tooltip_text(SvtResId(STR_TABBAR_HINT_SHEETSLIST_SHEETS)); m_xFirstButton->set_tooltip_text(SvtResId(STR_TABBAR_HINT_MOVETOHOME_SHEETS)); m_xPrevButton->set_tooltip_text(SvtResId(STR_TABBAR_HINT_MOVELEFT_SHEETS)); m_xNextButton->set_tooltip_text(SvtResId(STR_TABBAR_HINT_MOVERIGHT_SHEETS)); @@ -484,6 +488,7 @@ public: { if (m_xFirstButton->get_preferred_size() == Size(nHeight, nHeight)) return; + m_xListButton->set_size_request(nHeight, nHeight); m_xFirstButton->set_size_request(nHeight, nHeight); m_xPrevButton->set_size_request(nHeight, nHeight); m_xNextButton->set_size_request(nHeight, nHeight); @@ -502,6 +507,7 @@ public: m_xNextButton.reset(); m_xPrevButton.reset(); m_xFirstButton.reset(); + m_xListButton.reset(); InterimItemWindow::dispose(); } }; @@ -785,6 +791,11 @@ IMPL_LINK(TabBar, MousePressHdl, const MouseEvent&, rMouseEvent, bool) return false; } +IMPL_LINK_NOARG( TabBar, ImplListClickHandler, weld::Button&, void ) +{ + maScrollAreaContextHdl.Call( CommandEvent( Point( 0, 0 ), CommandEventId::ContextMenu, true ) ); +} + void TabBar::ImplInitControls() { if (mnWinStyle & WB_SIZEABLE) @@ -803,13 +814,19 @@ void TabBar::ImplInitControls() mpImpl->mxButtonBox.disposeAndReset(VclPtr::Create(this, mbSheets)); Link aContextLink = LINK( this, TabBar, ContextMenuHdl ); + Link aBtnContextLink = LINK(this, TabBar, MousePressHdl); if (mnWinStyle & WB_INSERTTAB) { Link aLink = LINK(this, TabBar, ImplAddClickHandler); mpImpl->mxButtonBox->m_xAddRepeater = std::make_shared( - *mpImpl->mxButtonBox->m_xAddButton, aLink, aContextLink); + *mpImpl->mxButtonBox->m_xAddButton, aLink ); mpImpl->mxButtonBox->m_xAddButton->show(); + + aLink = LINK( this, TabBar, ImplListClickHandler ); + mpImpl->mxButtonBox->m_xListButton->connect_clicked( aLink ); + mpImpl->mxButtonBox->m_xListButton->connect_mouse_press( aBtnContextLink ); + mpImpl->mxButtonBox->m_xListButton->show(); } Link aLink = LINK( this, TabBar, ImplClickHdl ); @@ -826,8 +843,6 @@ void TabBar::ImplInitControls() if (mnWinStyle & WB_SCROLL) { - Link aBtnContextLink = LINK(this, TabBar, MousePressHdl); - mpImpl->mxButtonBox->m_xFirstButton->connect_clicked(aLink); mpImpl->mxButtonBox->m_xFirstButton->connect_mouse_press(aBtnContextLink); mpImpl->mxButtonBox->m_xFirstButton->show(); @@ -1447,6 +1462,7 @@ void TabBar::StateChanged(StateChangedType nType) mpImpl->mpSizer->EnableRTL(bIsRTLEnabled); if (mpImpl->mxButtonBox) { + mpImpl->mxButtonBox->m_xListButton->set_direction(bIsRTLEnabled); mpImpl->mxButtonBox->m_xFirstButton->set_direction(bIsRTLEnabled); mpImpl->mxButtonBox->m_xPrevButton->set_direction(bIsRTLEnabled); mpImpl->mxButtonBox->m_xNextButton->set_direction(bIsRTLEnabled); diff --git a/svtools/source/control/valueacc.cxx b/svtools/source/control/valueacc.cxx index 0ec2d1900560..399887e17c19 100644 --- a/svtools/source/control/valueacc.cxx +++ b/svtools/source/control/valueacc.cxx @@ -218,8 +218,7 @@ lang::Locale SAL_CALL ValueItemAcc::getLocale() uno::Reference< accessibility::XAccessible > SAL_CALL ValueItemAcc::getAccessibleAtPoint( const awt::Point& ) { - uno::Reference< accessibility::XAccessible > xRet; - return xRet; + return {}; } awt::Rectangle ValueItemAcc::implGetBounds() diff --git a/svtools/uiconfig/ui/tabbuttons.ui b/svtools/uiconfig/ui/tabbuttons.ui index 316813491abe..3c3ea58f0f94 100644 --- a/svtools/uiconfig/ui/tabbuttons.ui +++ b/svtools/uiconfig/ui/tabbuttons.ui @@ -32,6 +32,12 @@ go-last 1 + + True + False + open-menu-symbolic + 1 + True False @@ -39,6 +45,25 @@ 6 True True + + + True + False + True + True + image6 + none + True + + + + False + True + 0 + + True @@ -55,7 +80,7 @@ False True - 0 + 1 @@ -74,7 +99,7 @@ False True - 1 + 2 @@ -93,7 +118,7 @@ False True - 2 + 3 @@ -112,7 +137,7 @@ False True - 3 + 4 @@ -132,7 +157,7 @@ False True - 4 + 5 diff --git a/svtools/uiconfig/ui/tabbuttonsmirrored.ui b/svtools/uiconfig/ui/tabbuttonsmirrored.ui index 61e70d1e906e..56350792355d 100644 --- a/svtools/uiconfig/ui/tabbuttonsmirrored.ui +++ b/svtools/uiconfig/ui/tabbuttonsmirrored.ui @@ -32,6 +32,12 @@ go-last 1 + + True + False + open-menu-symbolic + 1 + True False @@ -135,5 +141,24 @@ 4 + + + True + False + True + True + image6 + none + True + + + + False + True + 5 + + diff --git a/svx/sdi/svx.sdi b/svx/sdi/svx.sdi index 4eeccdcb6f2c..42a7fe6873f6 100644 --- a/svx/sdi/svx.sdi +++ b/svx/sdi/svx.sdi @@ -6198,7 +6198,7 @@ SvxOrphansItem Orphan SID_ATTR_PARA_ORPHANS SfxVoidItem OutlineBullet SID_OUTLINE_BULLET -(SfxStringItem Page FN_PARAM_1) +(SfxStringItem Page FN_PARAM_1, SfxBoolItem Bullet FN_PARAM_2) [ AutoUpdate = FALSE, FastCall = FALSE, diff --git a/svx/sdi/svxitems.sdi b/svx/sdi/svxitems.sdi index a93e07985957..9b515767c521 100644 --- a/svx/sdi/svxitems.sdi +++ b/svx/sdi/svxitems.sdi @@ -65,6 +65,7 @@ struct SvxAdjustStruct INT16 ScaleWidthMinimum MID_SCALE_WIDTH_MIN; INT16 ScaleWidthMaximum MID_SCALE_WIDTH_MAX; BOOL ParagraphComposer MID_PARAGRAPH_COMPOSER; + BOOL CompoundBased MID_COMPOUND_BASED; }; item SvxAdjustStruct SvxAdjustItem; diff --git a/svx/source/accessibility/AccessibleShape.cxx b/svx/source/accessibility/AccessibleShape.cxx index 291f4ef3c53b..de77d6301c99 100644 --- a/svx/source/accessibility/AccessibleShape.cxx +++ b/svx/source/accessibility/AccessibleShape.cxx @@ -993,8 +993,8 @@ struct XShapePosCompareHelper //end of group position // XAccessibleGroupPosition -uno::Sequence< sal_Int32 > SAL_CALL -AccessibleShape::getGroupPosition( const uno::Any& ) +uno::Sequence SAL_CALL +AccessibleShape::getGroupPosition(const css::uno::Reference&) { // we will return the: // [0] group level @@ -1033,7 +1033,7 @@ AccessibleShape::getGroupPosition( const uno::Any& ) Reference< XAccessibleGroupPosition > xGroupPosition( xParent,uno::UNO_QUERY ); if ( xGroupPosition.is() ) { - aRet = xGroupPosition->getGroupPosition( uno::Any( getAccessibleContext() ) ); + aRet = xGroupPosition->getGroupPosition(this); } return aRet; } @@ -1084,7 +1084,7 @@ AccessibleShape::getGroupPosition( const uno::Any& ) return aRet; } -OUString AccessibleShape::getObjectLink( const uno::Any& ) +OUString AccessibleShape::getObjectLink(const css::uno::Reference&) { OUString aRet; @@ -1098,7 +1098,7 @@ OUString AccessibleShape::getObjectLink( const uno::Any& ) Reference< XAccessibleGroupPosition > xGroupPosition( maShapeTreeInfo.GetDocumentWindow(), uno::UNO_QUERY ); if (xGroupPosition.is()) { - aRet = xGroupPosition->getObjectLink( uno::Any( getAccessibleContext() ) ); + aRet = xGroupPosition->getObjectLink(this); } } return aRet; diff --git a/svx/source/accessibility/GraphCtlAccessibleContext.cxx b/svx/source/accessibility/GraphCtlAccessibleContext.cxx index 486f2f4ee082..db1fcb16e2ec 100644 --- a/svx/source/accessibility/GraphCtlAccessibleContext.cxx +++ b/svx/source/accessibility/GraphCtlAccessibleContext.cxx @@ -460,10 +460,9 @@ void SAL_CALL SvxGraphCtrlAccessibleContext::disposing() { for (const auto& rEntry : mxShapes) { - rtl::Reference pAcc(rEntry.second); - Reference< XComponent > xComp( pAcc.get(), UNO_QUERY ); - if( xComp.is() ) - xComp->dispose(); + rtl::Reference<::accessibility::AccessibleShape> pAcc(rEntry.second); + if (pAcc.is()) + pAcc->dispose(); } mxShapes.clear(); diff --git a/svx/source/accessibility/svxrectctaccessiblecontext.cxx b/svx/source/accessibility/svxrectctaccessiblecontext.cxx index 33a0ed99bd8e..81d62dc3c3e9 100644 --- a/svx/source/accessibility/svxrectctaccessiblecontext.cxx +++ b/svx/source/accessibility/svxrectctaccessiblecontext.cxx @@ -116,14 +116,12 @@ Reference< XAccessible > SAL_CALL SvxRectCtlAccessibleContext::getAccessibleAtPo { ::osl::MutexGuard aGuard( m_aMutex ); - Reference< XAccessible > xRet; - tools::Long nChild = mpRepr ? PointToIndex(mpRepr->GetApproxRPFromPixPt(rPoint)) : NOCHILDSELECTED; if (nChild != NOCHILDSELECTED) - xRet = getAccessibleChild( nChild ); + return getAccessibleChild(nChild); - return xRet; + return {}; } // XAccessibleContext @@ -390,14 +388,11 @@ awt::Rectangle SvxRectCtlAccessibleContext::implGetBounds() } SvxRectCtlChildAccessibleContext::SvxRectCtlChildAccessibleContext( - const Reference& rxParent, - OUString aName, - OUString aDescription, - const tools::Rectangle& rBoundingBox, - tools::Long nIndexInParent ) + const rtl::Reference& rpParent, OUString aName, OUString aDescription, + const tools::Rectangle& rBoundingBox, tools::Long nIndexInParent) : msDescription(std::move( aDescription )) , msName(std::move( aName )) - , mxParent(rxParent) + , mxParent(rpParent) , maBoundingBox( rBoundingBox ) , mnIndexInParent( nIndexInParent ) , mbIsChecked( false ) diff --git a/svx/source/fmcomp/gridcell.cxx b/svx/source/fmcomp/gridcell.cxx index 3e7c09c44222..94af71e70f96 100644 --- a/svx/source/fmcomp/gridcell.cxx +++ b/svx/source/fmcomp/gridcell.cxx @@ -2960,17 +2960,17 @@ bool DbFilterField::commitControl() OUString aErrorMsg; Reference< XNumberFormatter > xNumberFormatter(m_rColumn.GetParent().getNumberFormatter()); - std::unique_ptr< OSQLParseNode > pParseNode = predicateTree(aErrorMsg, aNewText,xNumberFormatter, m_rColumn.GetField()); + Reference< XRowSet > xDataSourceRowSet( + Reference< XInterface >(*m_rColumn.GetParent().getDataSource()), UNO_QUERY); + Reference< XConnection > xConnection(getConnection(xDataSourceRowSet)); + + std::unique_ptr< OSQLParseNode > pParseNode = predicateTree(aErrorMsg, aNewText,xNumberFormatter, m_rColumn.GetField(), xConnection); if (pParseNode != nullptr) { OUString aPreparedText; css::lang::Locale aAppLocale = Application::GetSettings().GetUILanguageTag().getLocale(); - Reference< XRowSet > xDataSourceRowSet( - Reference< XInterface >(*m_rColumn.GetParent().getDataSource()), UNO_QUERY); - Reference< XConnection > xConnection(getConnection(xDataSourceRowSet)); - pParseNode->parseNodeToPredicateStr(aPreparedText, xConnection, xNumberFormatter, diff --git a/svx/source/form/filtnav.cxx b/svx/source/form/filtnav.cxx index d5a94129c184..1759a9b25f66 100644 --- a/svx/source/form/filtnav.cxx +++ b/svx/source/form/filtnav.cxx @@ -792,7 +792,7 @@ bool FmFilterModel::ValidateText(FmFilterItem const * pItem, OUString& rText, OU // parse the given text as filter predicate OUString aErr, aTxt( rText ); - std::unique_ptr< OSQLParseNode > pParseNode = predicateTree( aErr, aTxt, xFormatter, xField ); + std::unique_ptr< OSQLParseNode > pParseNode = predicateTree( aErr, aTxt, xFormatter, xField, xConnection ); rErrorMsg = aErr; rText = aTxt; if ( pParseNode != nullptr ) diff --git a/svx/source/form/formcontroller.cxx b/svx/source/form/formcontroller.cxx index 160cae0cd2f9..eb6bada9ea15 100644 --- a/svx/source/form/formcontroller.cxx +++ b/svx/source/form/formcontroller.cxx @@ -793,7 +793,7 @@ void FormController::getFastPropertyValue( Any& rValue, sal_Int32 nHandle ) cons OUString sErrorMsg; const std::unique_ptr< OSQLParseNode > pParseNode = - predicateTree( sErrorMsg, sFilterValue, xFormatter, xField ); + predicateTree( sErrorMsg, sFilterValue, xFormatter, xField, xConnection ); OSL_ENSURE( pParseNode != nullptr, "FormController::getFastPropertyValue: could not parse the field value predicate!" ); if ( pParseNode != nullptr ) { @@ -3089,7 +3089,7 @@ void FormController::setFilter(::std::vector& rFieldInfos) { OUString sPredicate,sErrorMsg; rRefValue.Value >>= sPredicate; - std::unique_ptr< OSQLParseNode > pParseNode = predicateTree(sErrorMsg, sPredicate, xFormatter, xField); + std::unique_ptr< OSQLParseNode > pParseNode = predicateTree(sErrorMsg, sPredicate, xFormatter, xField, xConnection); if ( pParseNode != nullptr ) { OUString sCriteria; diff --git a/svx/source/form/sqlparserclient.cxx b/svx/source/form/sqlparserclient.cxx index eb1606275237..c618c1b49276 100644 --- a/svx/source/form/sqlparserclient.cxx +++ b/svx/source/form/sqlparserclient.cxx @@ -36,10 +36,12 @@ namespace svxform OUString& _rErrorMessage, const OUString& _rStatement, const css::uno::Reference< css::util::XNumberFormatter >& _rxFormatter, - const css::uno::Reference< css::beans::XPropertySet >& _rxField + const css::uno::Reference< css::beans::XPropertySet >& _rxField, + const css::uno::Reference< css::sdbc::XConnection >& _rxConnection ) const { - return m_pParser->predicateTree(_rErrorMessage, _rStatement, _rxFormatter, _rxField); + return m_pParser->predicateTree(_rErrorMessage, _rStatement, _rxFormatter, _rxField, + /*bUseRealName*/true, _rxConnection); } } diff --git a/svx/source/inc/sqlparserclient.hxx b/svx/source/inc/sqlparserclient.hxx index a14ea367115f..03de84b4c2e7 100644 --- a/svx/source/inc/sqlparserclient.hxx +++ b/svx/source/inc/sqlparserclient.hxx @@ -34,6 +34,9 @@ namespace com::sun::star { namespace beans { class XPropertySet; } + namespace sdbc { + class XConnection; + } } namespace connectivity { class OSQLParser; @@ -56,7 +59,9 @@ namespace svxform OUString& _rErrorMessage, const OUString& _rStatement, const css::uno::Reference< css::util::XNumberFormatter >& _rxFormatter, - const css::uno::Reference< css::beans::XPropertySet >& _rxField + const css::uno::Reference< css::beans::XPropertySet >& _rxField, + const css::uno::Reference< css::sdbc::XConnection >& _rxConnection + = css::uno::Reference< css::sdbc::XConnection >() ) const; }; diff --git a/svx/source/inc/svdpdf.hxx b/svx/source/inc/svdpdf.hxx index c6dad1b4df4e..ad8ee3f7fbaa 100644 --- a/svx/source/inc/svdpdf.hxx +++ b/svx/source/inc/svdpdf.hxx @@ -130,10 +130,8 @@ class ImpSdrPdfImport final std::shared_ptr mpPDFium; - Color getStrokeColor(std::unique_ptr const& pPageObject, - std::unique_ptr const& pPage); - Color getFillColor(std::unique_ptr const& pPageObject, - std::unique_ptr const& pPage); + Color getStrokeColor(std::unique_ptr const& pPageObject); + Color getFillColor(std::unique_ptr const& pPageObject); void ImportPdfObject(std::unique_ptr const& pPageObject, std::unique_ptr const& pPage, @@ -141,16 +139,11 @@ class ImpSdrPdfImport final int nPageObjectIndex); void ImportForm(std::unique_ptr const& pPageObject, std::unique_ptr const& pPage, - std::unique_ptr const& pTextPage, - int nPageObjectIndex); - void ImportImage(std::unique_ptr const& pPageObject, - int nPageObjectIndex); - void ImportPath(std::unique_ptr const& pPageObject, - std::unique_ptr const& pPage, int nPageObjectIndex); + std::unique_ptr const& pTextPage); + void ImportImage(std::unique_ptr const& pPageObject); + void ImportPath(std::unique_ptr const& pPageObject); void ImportText(std::unique_ptr const& pPageObject, - std::unique_ptr const& pPage, - std::unique_ptr const& pTextPage, - int nPageObjectIndex); + std::unique_ptr const& pTextPage); void InsertTextObject(const Point& rPos, const Size& rSize, const OUString& rStr, bool bInvisible); diff --git a/svx/source/inc/svxrectctaccessiblecontext.hxx b/svx/source/inc/svxrectctaccessiblecontext.hxx index 9031cba259bd..9ba09dbce1b5 100644 --- a/svx/source/inc/svxrectctaccessiblecontext.hxx +++ b/svx/source/inc/svxrectctaccessiblecontext.hxx @@ -123,11 +123,10 @@ class SvxRectCtlChildAccessibleContext final css::accessibility::XAccessibleAction> { public: - SvxRectCtlChildAccessibleContext( - const css::uno::Reference< css::accessibility::XAccessible>& rxParent, - OUString aName, OUString aDescription, - const tools::Rectangle& rBoundingBox, - tools::Long nIndexInParent ); + SvxRectCtlChildAccessibleContext(const rtl::Reference& rpParent, + OUString aName, OUString aDescription, + const tools::Rectangle& rBoundingBox, + tools::Long nIndexInParent); // XAccessibleComponent virtual void SAL_CALL grabFocus() override; diff --git a/svx/source/sdr/contact/viewobjectcontact.cxx b/svx/source/sdr/contact/viewobjectcontact.cxx index 72ad4d56d0a5..5b69a6995db5 100644 --- a/svx/source/sdr/contact/viewobjectcontact.cxx +++ b/svx/source/sdr/contact/viewobjectcontact.cxx @@ -452,16 +452,15 @@ void ViewObjectContact::createStructureTag(drawinglayer::primitive2d::Primitive2 } else { - // page backgrounds etc should be tagged as artifacts: - rNewPrimitiveSequence = drawinglayer::primitive2d::Primitive2DContainer { - new drawinglayer::primitive2d::StructureTagPrimitive2D( - // lies to force silly VclMetafileProcessor2D to emit NonStructElement - vcl::pdf::StructElement::Division, - true, - true, - true, // Decorative - std::move(rNewPrimitiveSequence)) - }; + // page decoration, and anything the user marked decorative, is an artifact + rNewPrimitiveSequence = drawinglayer::primitive2d::Primitive2DContainer{ + new drawinglayer::primitive2d::StructureTagPrimitive2D( + vcl::pdf::StructElement::NonStructElement, + nullptr == pSdrObj, // Background + false, // Image + true, // Decorative + std::move(rNewPrimitiveSequence)) + }; } } } diff --git a/svx/source/sdr/contact/viewobjectcontactofmasterpagedescriptor.cxx b/svx/source/sdr/contact/viewobjectcontactofmasterpagedescriptor.cxx index 2cd4b64c2fe8..c46c1e831c26 100644 --- a/svx/source/sdr/contact/viewobjectcontactofmasterpagedescriptor.cxx +++ b/svx/source/sdr/contact/viewobjectcontactofmasterpagedescriptor.cxx @@ -89,7 +89,11 @@ namespace sdr::contact if (!bHideBackground && !GetObjectContact().isDrawModeGray() && !GetObjectContact().isDrawModeHighContrast()) { // if visible, create the default background primitive sequence - static_cast< ViewContactOfMasterPageDescriptor& >(GetViewContact()).getViewIndependentPrimitive2DContainer(rVisitor); + drawinglayer::primitive2d::Primitive2DContainer aBackground; + static_cast(GetViewContact()) + .getViewIndependentPrimitive2DContainer(aBackground); + createStructureTag(aBackground); // decoration, not content + rVisitor.visit(std::move(aBackground)); } } diff --git a/svx/source/svdraw/svdotxed.cxx b/svx/source/svdraw/svdotxed.cxx index b3fac1d95bb1..779effae035c 100644 --- a/svx/source/svdraw/svdotxed.cxx +++ b/svx/source/svdraw/svdotxed.cxx @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include #include @@ -287,6 +289,18 @@ void SdrTextObj::EndTextEdit(SdrOutliner& rOutl) SetOutlinerParaObject(std::move(pNewText)); } + // The fit stored with the shape belongs to the text before the edit, so fit afresh. + if (IsAutoFit()) + { + SdrTextFitToSizeTypeItem aItem(GetObjectItem(SDRATTR_TEXT_FITTOSIZE)); + if (aItem.getFontScale() != 0.0 || aItem.getSpacingScale() != 0.0) + { + aItem.setFontScale(0.0); + aItem.setSpacingScale(0.0); + SetObjectItem(aItem); + } + } + if (isAnnotationObject()) { auto xTextAPI(getAnnotationData()->mxAnnotation->getTextApiObject()); diff --git a/svx/source/svdraw/svdpdf.cxx b/svx/source/svdraw/svdpdf.cxx index d34450d4851c..6f8f81b806b8 100644 --- a/svx/source/svdraw/svdpdf.cxx +++ b/svx/source/svdraw/svdpdf.cxx @@ -796,19 +796,19 @@ void ImpSdrPdfImport::ImportPdfObject( switch (ePageObjectType) { case vcl::pdf::PDFPageObjectType::Text: - ImportText(pPageObject, pPage, pTextPage, nPageObjectIndex); + ImportText(pPageObject, pTextPage); break; case vcl::pdf::PDFPageObjectType::Path: - ImportPath(pPageObject, pPage, nPageObjectIndex); + ImportPath(pPageObject); break; case vcl::pdf::PDFPageObjectType::Image: - ImportImage(pPageObject, nPageObjectIndex); + ImportImage(pPageObject); break; case vcl::pdf::PDFPageObjectType::Shading: SAL_WARN("sd.filter", "Got page object SHADING: " << nPageObjectIndex); break; case vcl::pdf::PDFPageObjectType::Form: - ImportForm(pPageObject, pPage, pTextPage, nPageObjectIndex); + ImportForm(pPageObject, pPage, pTextPage); break; default: SAL_WARN("sd.filter", "Unknown PDF page object #" << nPageObjectIndex << " of type: " @@ -819,8 +819,7 @@ void ImpSdrPdfImport::ImportPdfObject( void ImpSdrPdfImport::ImportForm(std::unique_ptr const& pPageObject, std::unique_ptr const& pPage, - std::unique_ptr const& pTextPage, - int /*nPageObjectIndex*/) + std::unique_ptr const& pTextPage) { // Get the form matrix to perform correct translation/scaling of the form sub-objects. const basegfx::B2DHomMatrix aOldMatrix = maCurrentMatrix; @@ -1734,11 +1733,10 @@ EmbeddedFontInfo ImpSdrPdfImport::convertToOTF(std::u16string_view prefix, SubSe // There isn't, as far as I know, a way to stroke with a pattern at the moment, // so extract some sensible color if this is a stroke pattern Color ImpSdrPdfImport::getStrokeColor( - std::unique_ptr const& pPageObject, - std::unique_ptr const& pPage) + std::unique_ptr const& pPageObject) { if (std::unique_ptr bitmap - = pPageObject->getRenderedStrokePattern(*mpPdfDocument, *pPage)) + = pPageObject->getRenderedStrokePattern(*mpPdfDocument)) { Bitmap aBitmap(bitmap->createBitmapFromBuffer()); if (!aBitmap.IsEmpty()) @@ -1750,11 +1748,10 @@ Color ImpSdrPdfImport::getStrokeColor( // Typically for a fill pattern you want to use some pattern fill equivalent // but if that's not possible then this fallback can be useful -Color ImpSdrPdfImport::getFillColor(std::unique_ptr const& pPageObject, - std::unique_ptr const& pPage) +Color ImpSdrPdfImport::getFillColor(std::unique_ptr const& pPageObject) { if (std::unique_ptr bitmap - = pPageObject->getRenderedFillPattern(*mpPdfDocument, *pPage)) + = pPageObject->getRenderedFillPattern(*mpPdfDocument)) { Bitmap aBitmap(bitmap->createBitmapFromBuffer()); if (!aBitmap.IsEmpty()) @@ -1782,9 +1779,7 @@ static bool AllowRect(const tools::Rectangle& rRect) } void ImpSdrPdfImport::ImportText(std::unique_ptr const& pPageObject, - std::unique_ptr const& pPage, - std::unique_ptr const& pTextPage, - int /*nPageObjectIndex*/) + std::unique_ptr const& pTextPage) { basegfx::B2DRectangle aTextRect = pPageObject->getBounds(); basegfx::B2DHomMatrix aMatrix = pPageObject->getMatrix(); @@ -1889,8 +1884,7 @@ void ImpSdrPdfImport::ImportText(std::unique_ptr con } if (bUse) { - Color aColor - = bFill ? getFillColor(pPageObject, pPage) : getStrokeColor(pPageObject, pPage); + Color aColor = bFill ? getFillColor(pPageObject) : getStrokeColor(pPageObject); if (aColor != COL_TRANSPARENT) aTextColor = aColor.GetRGBColor(); } @@ -1947,8 +1941,7 @@ void ImpSdrPdfImport::InsertTextObject(const Point& rPos, const Size& rSize, con if (nChars > 1 && nVclInkedWidth > 0 && nPdfInkedWidth > 0) { const tools::Long nKern = (nPdfInkedWidth - nVclInkedWidth) / (nChars - 1); - nExtraKerning - = static_cast(std::clamp(nKern, SAL_MIN_INT16, SAL_MAX_INT16)); + nExtraKerning = static_cast(std::clamp(nKern, 0, SAL_MAX_INT16)); } // As per ImpEditEngine::CalcParaWidth the width of the text box has to be 1 unit wider than the text @@ -2043,8 +2036,7 @@ ImpSdrPdfImport::GetClip(const std::unique_ptr& pClipP return aClipPolyPoly; } -void ImpSdrPdfImport::ImportImage(std::unique_ptr const& pPageObject, - int /*nPageObjectIndex*/) +void ImpSdrPdfImport::ImportImage(std::unique_ptr const& pPageObject) { std::unique_ptr bitmap = pPageObject->getImageBitmap(); if (!bitmap) @@ -2184,9 +2176,7 @@ void ImpSdrPdfImport::appendSegmentsToPolyPoly( } } -void ImpSdrPdfImport::ImportPath(std::unique_ptr const& pPageObject, - std::unique_ptr const& pPage, - int /*nPageObjectIndex*/) +void ImpSdrPdfImport::ImportPath(std::unique_ptr const& pPageObject) { auto aPathMatrix = pPageObject->getMatrix(); @@ -2232,7 +2222,7 @@ void ImpSdrPdfImport::ImportPath(std::unique_ptr con } if (std::unique_ptr bitmap - = pPageObject->getRenderedFillPattern(*mpPdfDocument, *pPage)) + = pPageObject->getRenderedFillPattern(*mpPdfDocument)) { moFillPattern = bitmap->createBitmapFromBuffer(); moFillColor.reset(); @@ -2244,7 +2234,7 @@ void ImpSdrPdfImport::ImportPath(std::unique_ptr con } if (bStroke) - mpVD->SetLineColor(getStrokeColor(pPageObject, pPage)); + mpVD->SetLineColor(getStrokeColor(pPageObject)); else mpVD->SetLineColor(COL_TRANSPARENT); diff --git a/svx/source/tbxctrls/ColorIconView.cxx b/svx/source/tbxctrls/ColorIconView.cxx index c49d8cad48db..031f647ca5bc 100644 --- a/svx/source/tbxctrls/ColorIconView.cxx +++ b/svx/source/tbxctrls/ColorIconView.cxx @@ -68,8 +68,7 @@ void ColorIconView::set_help_id(const OUString& rName) { m_pIconView->set_help_i IMPL_LINK(ColorIconView, ItemActivatedHdl, const weld::TreeIter&, rIter, bool) { - Color aColor = getColor(m_pIconView->get_iter_index_in_parent(rIter)); - m_aColorActivatedHdl.Call(aColor); + m_aColorActivatedHdl.Call(m_pIconView->get_iter_index_in_parent(rIter)); return true; } diff --git a/svx/source/tbxctrls/StylesPreviewWindow.cxx b/svx/source/tbxctrls/StylesPreviewWindow.cxx index 178d2cf4c67d..3afdc6d9b8bb 100644 --- a/svx/source/tbxctrls/StylesPreviewWindow.cxx +++ b/svx/source/tbxctrls/StylesPreviewWindow.cxx @@ -345,6 +345,7 @@ void StyleItemController::DrawEntry(vcl::RenderContext& rRenderContext) if (pFontItem && pFontHeightItem) { Size aFontSize(0, pFontHeightItem->GetHeight()); + aFontSize /= rRenderContext.GetDPIScaleFactor(); Size aPixelSize(rRenderContext.LogicToPixel(aFontSize, MapMode(pShell->GetMapUnit()))); SvxFont aFont = GetFontFromItems(pFontItem, aPixelSize, pItemSet); diff --git a/svx/source/tbxctrls/bulletsnumbering.cxx b/svx/source/tbxctrls/bulletsnumbering.cxx index 49e1e596750e..546838a3a081 100644 --- a/svx/source/tbxctrls/bulletsnumbering.cxx +++ b/svx/source/tbxctrls/bulletsnumbering.cxx @@ -251,8 +251,10 @@ void NumberingPopup::GrabFocus() IMPL_LINK_NOARG(NumberingPopup, VSButtonClickSetHdl, weld::Button&, void) { - auto aArgs( comphelper::InitPropertySequence( { { "Page", css::uno::Any( u"customize"_ustr ) } } ) ); - mrController.dispatchCommand( u".uno:OutlineBullet"_ustr, aArgs ); + auto aArgs(comphelper::InitPropertySequence( + { { "Page", css::uno::Any(u"customize"_ustr) }, + { "Bullet", css::uno::Any(mePageType == NumberingPageType::BULLET) } })); + mrController.dispatchCommand(u".uno:OutlineBullet"_ustr, aArgs); mrController.EndPopupMode(); } diff --git a/svx/source/tbxctrls/tbcontrl.cxx b/svx/source/tbxctrls/tbcontrl.cxx index cfb2741eea7f..d6b2851ea856 100644 --- a/svx/source/tbxctrls/tbcontrl.cxx +++ b/svx/source/tbxctrls/tbcontrl.cxx @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -1781,12 +1782,29 @@ void SvxFontNameBox_Base::CheckAndMarkUnknownFont() } else { - if( font.GetItalicMaybeAskConfig() != ITALIC_NORMAL ) + font.SetItalic(ITALIC_NORMAL); + m_xWidget->set_entry_font(font); + OUString sSubstitute; + const bool bHasSubstitute = svtools::GetFontSubstitute(fontname, sSubstitute); + // no nested replacements: if 'Foo' is substituted by 'Bar' and 'Bar' by some existing font + // this font won't be used + if (bHasSubstitute && CheckFontIsAvailable(sSubstitute)) + { + m_xWidget->set_entry_message_type(weld::EntryMessageType::Info); + OUString sTip = SvxResId(RID_SVXSTR_CHARFONTNAME_NOTAVAILABLE); + sTip = sTip.replaceAll("%1", fontname); + sTip = sTip.replaceAll("%2", sSubstitute); + m_xWidget->set_tooltip_text(sTip); + } + else { - font.SetItalic( ITALIC_NORMAL ); - m_xWidget->set_entry_font(font); m_xWidget->set_entry_message_type(weld::EntryMessageType::Warning); - m_xWidget->set_tooltip_text(SvxResId(RID_SVXSTR_CHARFONTNAME_NOTAVAILABLE)); + OUString sTip = SvxResId(RID_SVXSTR_CHARFONTNAME_NOTAVAILABLE_NOSUBST); + const OUString sReplaceBy + = Application::GetDefaultDevice()->GetFontReplacement(fontname); + sTip = sTip.replaceAll("%1", fontname); + sTip = sTip.replaceAll("%2", sReplaceBy); + m_xWidget->set_tooltip_text(sTip); } } mbCheckingUnknownFont = false; @@ -2131,11 +2149,9 @@ ColorWindow::~ColorWindow() { } -NamedColor ColorWindow::GetSelectEntryColor(const ColorIconView& rColorIconView) +NamedColor ColorWindow::GetEntryColor(const ColorIconView& rColorIconView, int nIndex) { - Color aColor = rColorIconView.getColor(rColorIconView.get_selected_index()); - const OUString sColorName = rColorIconView.getColorName(rColorIconView.get_selected_index()); - return { aColor, sColorName }; + return { rColorIconView.getColor(nIndex), rColorIconView.getColorName(nIndex) }; } namespace @@ -2189,27 +2205,27 @@ namespace NamedColor ColorWindow::GetSelectEntryColor() const { if (maColorIconView.get_selected_index() != -1) - return GetSelectEntryColor(maColorIconView); + return GetEntryColor(maColorIconView, maColorIconView.get_selected_index()); if (maRecentColorIconView.get_selected_index() != -1) - return GetSelectEntryColor(maRecentColorIconView); + return GetEntryColor(maRecentColorIconView, maRecentColorIconView.get_selected_index()); if (mxButtonNoneColor.get() == mpDefaultButton) return GetNoneColor(); return GetAutoColor(); } -IMPL_LINK_NOARG(ColorWindow, ColorSelectHdl, const Color&, void) +IMPL_LINK(ColorWindow, ColorSelectHdl, int, nIndex, void) { - ApplySelectedColor(maColorIconView); + ApplyColor(maColorIconView, nIndex); } -IMPL_LINK_NOARG(ColorWindow, RecentColorSelectHdl, const Color&, void) +IMPL_LINK(ColorWindow, RecentColorSelectHdl, int, nIndex, void) { - ApplySelectedColor(maRecentColorIconView); + ApplyColor(maRecentColorIconView, nIndex); } -void ColorWindow::ApplySelectedColor(ColorIconView& rColorIconView) +void ColorWindow::ApplyColor(ColorIconView& rColorIconView, int nIndex) { - NamedColor aNamedColor = GetSelectEntryColor(rColorIconView); + NamedColor aNamedColor = GetEntryColor(rColorIconView, nIndex); if (&rColorIconView != &maRecentColorIconView) { @@ -2229,10 +2245,9 @@ void ColorWindow::ApplySelectedColor(ColorIconView& rColorIconView) if (bThemePaletteSelected) { - const sal_uInt16 nSelectedItemPos = rColorIconView.get_selected_index(); sal_uInt16 nThemeIndex; sal_uInt16 nEffectIndex; - if (PaletteManager::GetThemeAndEffectIndex(nSelectedItemPos, nThemeIndex, nEffectIndex)) + if (PaletteManager::GetThemeAndEffectIndex(nIndex, nThemeIndex, nEffectIndex)) { aNamedColor.m_nThemeIndex = nThemeIndex; mxPaletteManager->GetLumModOff(nThemeIndex, nEffectIndex, aNamedColor.m_nLumMod, aNamedColor.m_nLumOff); diff --git a/svx/source/unodraw/unoshape.cxx b/svx/source/unodraw/unoshape.cxx index 2a412c6f6f59..61f59f2b8863 100644 --- a/svx/source/unodraw/unoshape.cxx +++ b/svx/source/unodraw/unoshape.cxx @@ -2341,26 +2341,27 @@ bool SvxShape::setPropertyValueImpl( const OUString&, const SfxItemPropertyMapEn } case OWN_ATTR_TEXTFITTOSIZE_FONT_SCALE: - { - double fScale = 0.0; - if (rValue >>= fScale) - { - SdrTextFitToSizeTypeItem aItem(pSdrObject->GetMergedItem(SDRATTR_TEXT_FITTOSIZE)); - aItem.setFontScale(fScale / 100.0); - pSdrObject->SetMergedItem(aItem); - return true; - } - break; - } - case OWN_ATTR_TEXTFITTOSIZE_SPACING_SCALE: { double fScale = 0.0; if (rValue >>= fScale) { - SdrTextFitToSizeTypeItem aItem(pSdrObject->GetMergedItem(SDRATTR_TEXT_FITTOSIZE)); - aItem.setSpacingScale(fScale / 100.0); - pSdrObject->SetMergedItem(aItem); + // A multi-property call may already hold the fit item to apply at the end. + std::optional& rCollected = mpImpl->moItemSet; + const bool bCollected = mbIsMultiPropertyCall && rCollected + && rCollected->GetItemState(SDRATTR_TEXT_FITTOSIZE) + == SfxItemState::SET; + SdrTextFitToSizeTypeItem aItem( + bCollected ? rCollected->Get(SDRATTR_TEXT_FITTOSIZE) + : pSdrObject->GetMergedItem(SDRATTR_TEXT_FITTOSIZE)); + if (pProperty->nWID == OWN_ATTR_TEXTFITTOSIZE_FONT_SCALE) + aItem.setFontScale(fScale / 100.0); + else + aItem.setSpacingScale(fScale / 100.0); + if (bCollected) + rCollected->Put(aItem); + else + pSdrObject->SetMergedItem(aItem); return true; } break; diff --git a/svx/uiconfig/ui/colorwindow.ui b/svx/uiconfig/ui/colorwindow.ui index 7ee0bebc5fd9..5c51bceccf7c 100644 --- a/svx/uiconfig/ui/colorwindow.ui +++ b/svx/uiconfig/ui/colorwindow.ui @@ -140,7 +140,10 @@ coloriconview_model 12 0 + 0 + 0 2 + 2 True @@ -185,7 +188,10 @@ recent_coloriconview_model 12 0 + 0 + 0 2 + 2 True diff --git a/sw/CppunitTest_sw_core_text.mk b/sw/CppunitTest_sw_core_text.mk index 38cd6882b0ee..78d7e850cf3b 100644 --- a/sw/CppunitTest_sw_core_text.mk +++ b/sw/CppunitTest_sw_core_text.mk @@ -14,6 +14,7 @@ $(eval $(call gb_CppunitTest_CppunitTest,sw_core_text)) $(eval $(call gb_CppunitTest_use_common_precompiled_header,sw_core_text)) $(eval $(call gb_CppunitTest_add_exception_objects,sw_core_text, \ + sw/qa/core/text/EnhancedPDFExportHelper \ sw/qa/core/text/frmform \ sw/qa/core/text/itratr \ sw/qa/core/text/itrpaint \ diff --git a/sw/inc/EnhancedPDFExportHelper.hxx b/sw/inc/EnhancedPDFExportHelper.hxx index 229e8c6df0e9..9fbbf8428e23 100644 --- a/sw/inc/EnhancedPDFExportHelper.hxx +++ b/sw/inc/EnhancedPDFExportHelper.hxx @@ -20,6 +20,7 @@ #pragma once #include +#include #include #include "swrect.hxx" #include "swtypes.hxx" @@ -32,6 +33,7 @@ namespace vcl class PDFExtOutDevData; } class OutputDevice; +class SvxLanguageItem; class SwFrame; class SwLinePortion; class SwPageFrame; @@ -41,6 +43,7 @@ class SwEditShell; class StringRangeEnumerator; class SwTextAttr; class SwTextNode; +struct SwPosition; class SwTextPaintInfo; class SwTextFrame; @@ -137,9 +140,8 @@ class SwTaggedPDFHelper { private: - // This will be incremented for each BeginTag() call. - // It denotes the number of tags to close during EndStructureElements(); - sal_uInt8 m_nEndStructureElement; + // opened by this helper, innermost last + std::vector m_aOpenedTags; // If an already existing tag is reopened for follows of flow frames, // this value stores the tag id which has to be restored. @@ -155,6 +157,10 @@ class SwTaggedPDFHelper sal_Int32 BeginTagImpl(void const* pKey,vcl::pdf::StructElement aTagRole, const OUString& rTagName); void BeginTag(vcl::pdf::StructElement aTagRole, const OUString& rTagName); void EndTag(); + void DeferTag(); + bool IsDeferredTagCurrent() const; + void EndDeferredTag(); + void EndDeferredTags(); void SetAttributes(vcl::pdf::StructElement eType); @@ -164,10 +170,19 @@ class SwTaggedPDFHelper void BeginInlineStructureElements(); void EndStructureElements(); + // what an open link or span leaves for this portion to do + enum class Continuation + { + None, // nothing is open that covers it + Whole, // the open tag covers it, so no new tag + SpanInLink // the link goes on, but its properties do not + }; + void EndCurrentAll(); void EndCurrentSpan(); void CreateCurrentSpan(SwTextPaintInfo const& rInf, OUString const& rStyleName); - bool CheckContinueSpan(SwTextPaintInfo const& rInf, std::u16string_view rStyleName, SwTextAttr const* pInetFormatAttr); + Continuation CheckContinuation(SwTextPaintInfo const& rInf, OUString const& rStyleName, + SwTextAttr const* pInetFormatAttr); bool CheckReopenTag(); void CheckRestoreTag() const; @@ -180,8 +195,6 @@ class SwTaggedPDFHelper SwTaggedPDFHelper( const Num_Info* pNumInfo, const Frame_Info* pFrameInfo, const Por_Info* pPorInfo, OutputDevice const & rOut ); ~SwTaggedPDFHelper(); - - static void EndCurrentLink(OutputDevice const&); }; /* @@ -217,11 +230,19 @@ class SwEnhancedPDFExportHelper const SwPrintData& mrPrintData; - void EnhancedPDFExport(LanguageType const eLanguageDefault); + void EnhancedPDFExport(LanguageType const eLanguageDefault, + const TypedWhichId nLanguageWhich); /// Exports bibliography entry links. void ExportAuthorityEntryLinks(); + /// Creates a destination, and remembers the node it points at, to name it once tagging is done. + /// The target is either the node the cursor jumped to since rBeforeJump, or one known outright. + sal_Int32 CreateDestination(const SwPageFrame* pCurrPage, const SwRect& rRect, + sal_Int32 nPageNum, const SwPosition& rBeforeJump); + sal_Int32 CreateDestination(const SwPageFrame* pCurrPage, const SwRect& rRect, + sal_Int32 nPageNum, const SwTextNode* pTarget); + sal_Int32 CalcOutputPageNum( const SwRect& rRect ) const; std::vector< sal_Int32 > CalcOutputPageNums( const SwRect& rRect ) const; diff --git a/sw/inc/IDocumentSettingAccess.hxx b/sw/inc/IDocumentSettingAccess.hxx index 407f0ec43c14..8e99f7be4d59 100644 --- a/sw/inc/IDocumentSettingAccess.hxx +++ b/sw/inc/IDocumentSettingAccess.hxx @@ -71,6 +71,7 @@ enum class DocumentSettingId MS_WORD_COMP_TRAILING_BLANKS, // tdf#128197 MS Word in some modes can have line height based on shape height, not on font MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY, + MS_WORD_COMP_MIN_TEXT_WRAP_GAP, UNIX_FORCE_ZERO_EXT_LEADING, TABS_RELATIVE_TO_INDENT, PROTECT_FORM, diff --git a/sw/inc/SwStyleNameMapper.hxx b/sw/inc/SwStyleNameMapper.hxx index 4cde545b0b0f..af245a3c3d91 100644 --- a/sw/inc/SwStyleNameMapper.hxx +++ b/sw/inc/SwStyleNameMapper.hxx @@ -92,7 +92,7 @@ class SwStyleNameMapper final public: // This gets the UI Name from the programmatic name SAL_WARN_UNUSED_RESULT - static UIName GetUIName(const ProgName& rName, SwGetPoolIdFromName); + SW_DLLPUBLIC static UIName GetUIName(const ProgName& rName, SwGetPoolIdFromName); static void FillUIName(const ProgName& rName, UIName& rFillName, SwGetPoolIdFromName); diff --git a/sw/inc/inspectorproperties.hrc b/sw/inc/inspectorproperties.hrc index deff9bca1a17..a125e88f040a 100644 --- a/sw/inc/inspectorproperties.hrc +++ b/sw/inc/inspectorproperties.hrc @@ -199,6 +199,7 @@ #define RID_PARA_BOTTOM_MARGIN_RELATIVE NC_("RID_ATTRIBUTE_NAMES_MAP", "Para Bottom Margin Relative") #define RID_PARA_CHAPTER_NUMBERING_LEVEL NC_("RID_ATTRIBUTE_NAMES_MAP", "Para Chapter Numbering Level") #define RID_PARA_COMPOSER NC_("RID_ATTRIBUTE_NAMES_MAP", "Para Composer") +#define RID_PARA_COMPOUND_BASED_HYPHENATION NC_("RID_ATTRIBUTE_NAMES_MAP", "Para Compound-based Hyphenation") #define RID_PARA_CONDITIONAL_STYLE_NAME NC_("RID_ATTRIBUTE_NAMES_MAP", "Para Conditional Style Name") #define RID_PARA_CONTEXT_MARGIN NC_("RID_ATTRIBUTE_NAMES_MAP", "Para Context Margin") #define RID_PARA_EXPAND_SINGLE_WORD NC_("RID_ATTRIBUTE_NAMES_MAP", "Para Expand Single Word") diff --git a/sw/inc/strings.hrc b/sw/inc/strings.hrc index e33b790d16af..ff58bf2ac173 100644 --- a/sw/inc/strings.hrc +++ b/sw/inc/strings.hrc @@ -685,6 +685,7 @@ #define STR_PRINTOPTUI_PRINTALLPAGES NC_("STR_PRINTOPTUI_PRINTALLPAGES", "~All Pages") #define STR_PRINTOPTUI_PRINTPAGES NC_("STR_PRINTOPTUI_PRINTPAGES", "Pa~ges:") #define STR_PRINTOPTUI_PRINTSELECTION NC_("STR_PRINTOPTUI_PRINTSELECTION", "~Selection") +#define STR_PRINTOPTUI_PRINTCURRENTPAGE NC_("STR_PRINTOPTUI_PRINTCURRENTPAGE", "Current ~Page") #define STR_PRINTOPTUI_PLACE_MARGINS NC_("STR_PRINTOPTUI_PLACE_MARGINS", "Place in margins") #define STR_FORMULA_CALC NC_("STR_FORMULA_CALC", "Functions") diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx index f82f78f15fa8..4f379e1a6e6a 100644 --- a/sw/inc/unoprnms.hxx +++ b/sw/inc/unoprnms.hxx @@ -519,6 +519,7 @@ inline constexpr OUString UNO_NAME_TEXT_USER_DEFINED_ATTRIBUTES = u"TextUserDefi inline constexpr OUString UNO_NAME_FILE_PATH = u"FilePath"_ustr; inline constexpr OUString UNO_NAME_PARA_CHAPTER_NUMBERING_LEVEL = u"ParaChapterNumberingLevel"_ustr; inline constexpr OUString UNO_NAME_PARA_COMPOSER = u"ParaComposer"_ustr; +inline constexpr OUString UNO_NAME_PARA_IS_COMPOUND_BASED = u"ParaIsCompoundBased"_ustr; inline constexpr OUString UNO_NAME_PARA_CONDITIONAL_STYLE_NAME = u"ParaConditionalStyleName"_ustr; inline constexpr OUString UNO_NAME_CHAPTER_NUMBERING_LEVEL = u"ChapterNumberingLevel"_ustr; inline constexpr OUString UNO_NAME_NUMBERING_SEPARATOR = u"NumberingSeparator"_ustr; diff --git a/sw/qa/core/docnode/data/ofz515655013.doc b/sw/qa/core/docnode/data/ofz515655013.doc new file mode 100644 index 000000000000..6b43d1c2a299 Binary files /dev/null and b/sw/qa/core/docnode/data/ofz515655013.doc differ diff --git a/sw/qa/core/docnode/docnode.cxx b/sw/qa/core/docnode/docnode.cxx index 03b53de11f47..4f1af31c66c1 100644 --- a/sw/qa/core/docnode/docnode.cxx +++ b/sw/qa/core/docnode/docnode.cxx @@ -13,6 +13,8 @@ #include #include #include +#include +#include /// Covers sw/source/core/docnode/ fixes. class Test : public SwModelTestBase @@ -66,6 +68,37 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf156267) CPPUNIT_ASSERT_EQUAL(1, getPages()); } +CPPUNIT_TEST_FIXTURE(Test, testTableBoxKeepsItsCellSection) +{ + // Given a document whose tracked deletion carries on across a cell mark, so applying it at + // import empties one cell of the table: + createSwDoc("ofz515655013.doc"); + + // Then every box of the table still has a cell section of its own: + SwDoc* pDoc = getSwDoc(); + SwNodes& rNodes = pDoc->GetNodes(); + const SwTableNode* pTableNode = nullptr; + for (SwNodeOffset nNode(0); nNode < rNodes.Count(); ++nNode) + { + if (rNodes[nNode]->IsTableNode()) + { + pTableNode = rNodes[nNode]->GetTableNode(); + break; + } + } + CPPUNIT_ASSERT(pTableNode); + int nCellSections = 0; + for (SwNodeOffset nNode = pTableNode->GetIndex(); nNode < pTableNode->EndOfSectionIndex(); + ++nNode) + { + const SwStartNode* pStartNode = rNodes[nNode]->GetStartNode(); + if (pStartNode && pStartNode->GetStartNodeType() == SwTableBoxStartNode) + ++nCellSections; + } + CPPUNIT_ASSERT_EQUAL(static_cast(6), pTableNode->GetTable().GetTabSortBoxes().size()); + CPPUNIT_ASSERT_EQUAL(6, nCellSections); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/qa/core/layout/data/inline-endnote-and-columns.doc b/sw/qa/core/layout/data/inline-endnote-and-columns.doc new file mode 100644 index 000000000000..2053df31bb24 Binary files /dev/null and b/sw/qa/core/layout/data/inline-endnote-and-columns.doc differ diff --git a/sw/qa/core/layout/ftnfrm.cxx b/sw/qa/core/layout/ftnfrm.cxx index 0bd0b2c5d077..12434415569e 100644 --- a/sw/qa/core/layout/ftnfrm.cxx +++ b/sw/qa/core/layout/ftnfrm.cxx @@ -118,6 +118,22 @@ CPPUNIT_TEST_FIXTURE(Test, testInlineEndnoteAndSection) CPPUNIT_ASSERT_EQUAL(2, nToplevelSections); } +CPPUNIT_TEST_FIXTURE(Test, testInlineEndnoteAndColumns) +{ + // Given a DOC file with an endnote and a page style with two columns, ContinuousEndnotes is + // true: + createSwDoc("inline-endnote-and-columns.doc"); + + // When laying out that document: + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + + // Then make sure the endnote section is inside a column of the body text: + // Without the accompanying fix in place, this test would have failed with an assertion in + // SwFrame::GetNextSctLeaf(), the endnote section was a sibling of the columns. + assertXPath(pXmlDoc, "/root/page/body/column/body/section", 1); + assertXPath(pXmlDoc, "/root/page/body/section", 0); +} + CPPUNIT_TEST_FIXTURE(Test, testInlineEndnotePosition) { // Given a document, ContinuousEndnotes is true: diff --git a/sw/qa/core/text/EnhancedPDFExportHelper.cxx b/sw/qa/core/text/EnhancedPDFExportHelper.cxx new file mode 100644 index 000000000000..f95aa27e6a1f --- /dev/null +++ b/sw/qa/core/text/EnhancedPDFExportHelper.cxx @@ -0,0 +1,403 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * Copyright the LibreOffice contributors. + * + * SPDX-License-Identifier: MPL-2.0 + * + * 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/. + */ + +#include + +#include +#include +#include + +#include +#include +#include + +namespace +{ +/// Covers sw/source/core/text/EnhancedPDFExportHelper.cxx fixes. +class Test : public SwModelTestBase +{ +public: + Test() + : SwModelTestBase(u"/sw/qa/core/text/data/"_ustr) + { + } +}; + +CPPUNIT_TEST_FIXTURE(Test, testTdf171022) +{ + createSwDoc("structure-destinations.fodt"); + + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"UseTaggedPDF"_ustr, true), + comphelper::makePropertyValue(u"SelectPdfVersion"_ustr, + sal_Int32(20)) }; + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + vcl::filter::PDFDocument aDocument; + CPPUNIT_ASSERT(aDocument.Read(*maTempFile.GetStream(StreamMode::READ))); + + // the outline item, the footnote's two links, the shape's link and the citation's + std::vector aOutlineTypes; + std::vector aLinkTypes; + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pAction = dynamic_cast(pObject->Lookup("A"_ostr)); + if (!pAction) + continue; + auto pType = dynamic_cast(pAction->LookupElement("S"_ostr)); + if (!pType || pType->GetValue() != "GoTo") + continue; + // ISO 14289-2 8.8: the destination inside the document is a structure destination, so + // it names the element and not only the page. veraPDF passes a structure destination + // that points at nothing, so check what it points at. + auto pPage = dynamic_cast(pAction->LookupElement("D"_ostr)); + auto pStructure + = dynamic_cast(pAction->LookupElement("SD"_ostr)); + CPPUNIT_ASSERT(pPage); + CPPUNIT_ASSERT(pStructure); + CPPUNIT_ASSERT(!pPage->GetElements().empty()); + CPPUNIT_ASSERT(!pStructure->GetElements().empty()); + + auto pPageRef = dynamic_cast(pPage->GetElements()[0]); + auto pStructureRef + = dynamic_cast(pStructure->GetElements()[0]); + CPPUNIT_ASSERT(pPageRef); + CPPUNIT_ASSERT(pStructureRef); + + CPPUNIT_ASSERT(pPageRef->LookupObject()); + CPPUNIT_ASSERT(pStructureRef->LookupObject()); + auto pPageType = dynamic_cast( + pPageRef->LookupObject()->Lookup("Type"_ostr)); + auto pStructureType = dynamic_cast( + pStructureRef->LookupObject()->Lookup("Type"_ostr)); + CPPUNIT_ASSERT(pPageType); + CPPUNIT_ASSERT(pStructureType); + CPPUNIT_ASSERT_EQUAL("Page"_ostr, pPageType->GetValue()); + CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pStructureType->GetValue()); + + // which element it is, not merely that it is one: a jump that leaves the cursor behind + // names the paragraph the link sits in, and that reads as a plausible structure + // destination to a validator + auto pStructureS = dynamic_cast( + pStructureRef->LookupObject()->Lookup("S"_ostr)); + CPPUNIT_ASSERT(pStructureS); + // an outline item has a Title, a link annotation does not + if (pObject->Lookup("Title"_ostr)) + aOutlineTypes.push_back(pStructureS->GetValue()); + else + aLinkTypes.push_back(pStructureS->GetValue()); + } + + // the outline item names the heading it was made from + CPPUNIT_ASSERT_EQUAL(size_t(1), aOutlineTypes.size()); + CPPUNIT_ASSERT_EQUAL("H1"_ostr, aOutlineTypes[0]); + + // the shape's bookmark sits at the document's first position on purpose: the cursor is + // already there, and nothing moves + std::sort(aLinkTypes.begin(), aLinkTypes.end()); + CPPUNIT_ASSERT_EQUAL(size_t(4), aLinkTypes.size()); + CPPUNIT_ASSERT_EQUAL("Bibliography 1"_ostr, aLinkTypes[0]); + CPPUNIT_ASSERT_EQUAL("Footnote"_ostr, aLinkTypes[1]); + CPPUNIT_ASSERT_EQUAL("H1"_ostr, aLinkTypes[2]); + CPPUNIT_ASSERT_EQUAL("Standard"_ostr, aLinkTypes[3]); +} + +CPPUNIT_TEST_FIXTURE(Test, testFootnoteNoteType) +{ + createSwDoc("footnote-tag.fodt"); + + // the type of the one element a footnote frame opens + const auto aFootnoteType = [](vcl::filter::PDFDocument& rDocument) -> OString { + for (const auto& rDocElement : rDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pType = dynamic_cast(pObject->Lookup("S"_ostr)); + if (pType && (pType->GetValue() == "Note" || pType->GetValue() == "FENote")) + return pType->GetValue(); + } + return OString(); + }; + + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue( + u"FilterData"_ustr, + uno::Sequence{ + comphelper::makePropertyValue(u"UseTaggedPDF"_ustr, true), + comphelper::makePropertyValue(u"SelectPdfVersion"_ustr, sal_Int32(20)) }) }); + + vcl::filter::PDFDocument aPDF20; + CPPUNIT_ASSERT(aPDF20.Read(*maTempFile.GetStream(StreamMode::READ))); + + // Without the fix this was Note, which ISO 14289-2 8.2.5.14 takes only where a role map + // sends it to the PDF 2.0 namespace + CPPUNIT_ASSERT_EQUAL("FENote"_ostr, aFootnoteType(aPDF20)); + + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue( + u"FilterData"_ustr, + uno::Sequence{ comphelper::makePropertyValue(u"UseTaggedPDF"_ustr, true) }) }); + + vcl::filter::PDFDocument aPDF17; + maTempFile.CloseStream(); + CPPUNIT_ASSERT(aPDF17.Read(*maTempFile.GetStream(StreamMode::READ))); + + // the only footnote type ISO 32000-1 has, FENote being a PDF 2.0 addition + CPPUNIT_ASSERT_EQUAL("Note"_ostr, aFootnoteType(aPDF17)); +} + +CPPUNIT_TEST_FIXTURE(Test, testTOCItemRef) +{ + createSwDoc("toc-structure-ref.fodt"); + + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"UseTaggedPDF"_ustr, true), + comphelper::makePropertyValue(u"SelectPdfVersion"_ustr, + sal_Int32(20)) }; + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + vcl::filter::PDFDocument aDocument; + CPPUNIT_ASSERT(aDocument.Read(*maTempFile.GetStream(StreamMode::READ))); + + // Without the fix a TOC item had no Ref at all, and ISO 14289-2 8.2.5.8 asks each of them to + // name the element its entry reaches + OStringBuffer aTargets; + std::unordered_set aSeen; + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + + auto pType = dynamic_cast(pObject->Lookup("S"_ostr)); + if (!pType || pType->GetValue() != "TOCI") + continue; + + auto pRefs = dynamic_cast(pObject->Lookup("Ref"_ostr)); + CPPUNIT_ASSERT(pRefs); + CPPUNIT_ASSERT_EQUAL(size_t(1), pRefs->GetElements().size()); + + auto pRef = dynamic_cast(pRefs->GetElements()[0]); + CPPUNIT_ASSERT(pRef); + CPPUNIT_ASSERT(pRef->LookupObject()); + // the heading of this entry, not the one the entry before it reached + CPPUNIT_ASSERT(aSeen.insert(pRef->GetObjectValue()).second); + + auto pTargetType + = dynamic_cast(pRef->LookupObject()->Lookup("S"_ostr)); + CPPUNIT_ASSERT(pTargetType); + aTargets.append(pTargetType->GetValue() + " "); + } + + // one item per heading, each naming its own + CPPUNIT_ASSERT_EQUAL("H1 H2 "_ostr, aTargets.makeStringAndClear()); +} + +CPPUNIT_TEST_FIXTURE(Test, testFormulaAltFromSource) +{ + createSwDoc("formula-alt.fodt"); + // a formula reaches the export loaded, not running, in every document but a freshly typed one + saveAndReload(TestFilter::ODT); + + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"UseTaggedPDF"_ustr, true) }; + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + vcl::filter::PDFDocument aDocument; + maTempFile.CloseStream(); + CPPUNIT_ASSERT(aDocument.Read(*maTempFile.GetStream(StreamMode::READ))); + + OUStringBuffer aAlts; + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + + auto pType = dynamic_cast(pObject->Lookup("S"_ostr)); + if (!pType || pType->GetValue() != "Formula") + continue; + + // Without the fix a formula nobody had described carried no Alt, which the PDF/UA + // checkers count against the file + auto pAlt = dynamic_cast(pObject->Lookup("Alt"_ostr)); + CPPUNIT_ASSERT(pAlt); + aAlts.append(vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAlt) + "|"); + } + + // the source where nobody described the formula, and the title where somebody did + CPPUNIT_ASSERT_EQUAL(u"E=mc^2|Half of a|"_ustr, aAlts.makeStringAndClear()); +} + +CPPUNIT_TEST_FIXTURE(Test, testFormulaPlacement) +{ + createSwDoc("formula-placement.fodt"); + + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"UseTaggedPDF"_ustr, true) }; + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + vcl::filter::PDFDocument aDocument; + CPPUNIT_ASSERT(aDocument.Read(*maTempFile.GetStream(StreamMode::READ))); + + OString aInALine; + OString aInAParagraph; + OString aOnThePage; + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + + auto pType = dynamic_cast(pObject->Lookup("S"_ostr)); + if (!pType || pType->GetValue() != "Formula") + continue; + + auto pAttributes + = dynamic_cast(pObject->Lookup("A"_ostr)); + CPPUNIT_ASSERT(pAttributes); + auto pPlacement = dynamic_cast( + pAttributes->LookupElement("Placement"_ostr)); + CPPUNIT_ASSERT(pPlacement); + + // each formula names its own source, so the three are told apart by it + auto pAlt = dynamic_cast(pObject->Lookup("Alt"_ostr)); + CPPUNIT_ASSERT(pAlt); + const OUString aSource(vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAlt)); + if (aSource == u"E=mc^2") + aInALine = pPlacement->GetValue(); + else if (aSource == u"a over 2") + aInAParagraph = pPlacement->GetValue(); + else if (aSource == u"b+1") + aOnThePage = pPlacement->GetValue(); + } + + // Without the fix these were Block, which makes a block level element of a Formula that the + // export tags inside a paragraph, and PAC 2026 calls such a Formula inappropriate + CPPUNIT_ASSERT_EQUAL("Inline"_ostr, aInALine); + CPPUNIT_ASSERT_EQUAL("Inline"_ostr, aInAParagraph); + // one anchored to the page is tagged under the document, where it is a block of its own + CPPUNIT_ASSERT_EQUAL("Block"_ostr, aOnThePage); +} + +CPPUNIT_TEST_FIXTURE(Test, testLayoutAttributeUnits) +{ + createSwDoc("layout-attribute-units.fodt"); + + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"UseTaggedPDF"_ustr, true) }; + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + vcl::filter::PDFDocument aDocument; + maTempFile.CloseStream(); + CPPUNIT_ASSERT(aDocument.Read(*maTempFile.GetStream(StreamMode::READ))); + + // the indented paragraph, the one whose first line hangs, and the first data cell; a + // header cell carries an array of two dictionaries instead, the layout one and the table one + vcl::filter::PDFDictionaryElement* pIndented = nullptr; + vcl::filter::PDFDictionaryElement* pHanging = nullptr; + vcl::filter::PDFDictionaryElement* pCell = nullptr; + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + + auto pType = dynamic_cast(pObject->Lookup("S"_ostr)); + auto pAttributes + = dynamic_cast(pObject->Lookup("A"_ostr)); + if (!pType || !pAttributes) + continue; + + // of the paragraphs only the indented one has a right margin; the table has one too, + // which is why the type is asked for as well + if (!pIndented && pType->GetValue() == "Standard" + && pAttributes->LookupElement("EndIndent"_ostr)) + pIndented = pAttributes; + else if (!pHanging && pType->GetValue() == "Standard" + && pAttributes->LookupElement("TextIndent"_ostr)) + pHanging = pAttributes; + else if (!pCell && pType->GetValue() == "TD") + pCell = pAttributes; + } + CPPUNIT_ASSERT(pIndented); + CPPUNIT_ASSERT(pHanging); + CPPUNIT_ASSERT(pCell); + + const auto getLength = [](vcl::filter::PDFDictionaryElement* pAttributes, const OString& rKey) { + auto pNumber + = dynamic_cast(pAttributes->LookupElement(rKey)); + CPPUNIT_ASSERT(pNumber); + return pNumber->GetValue(); + }; + + // Without the fix these carried the twips the layout works in, where ISO 32000-2 14.8.5.4 + // asks for default user space units - a fiftieth of the length they name. + // 0.5in, 0.4in and 0.25in of indent, and 0.2in above the paragraph + CPPUNIT_ASSERT_DOUBLES_EQUAL(36.0, getLength(pIndented, "StartIndent"_ostr), 0.01); + CPPUNIT_ASSERT_DOUBLES_EQUAL(28.8, getLength(pIndented, "EndIndent"_ostr), 0.01); + CPPUNIT_ASSERT_DOUBLES_EQUAL(18.0, getLength(pIndented, "TextIndent"_ostr), 0.01); + CPPUNIT_ASSERT_DOUBLES_EQUAL(14.4, getLength(pIndented, "SpaceBefore"_ostr), 0.01); + // a hanging first line, the one length that is negative + CPPUNIT_ASSERT_DOUBLES_EQUAL(-18.0, getLength(pHanging, "TextIndent"_ostr), 0.01); + // the 1.5in column the cell sits in + CPPUNIT_ASSERT_DOUBLES_EQUAL(108.0, getLength(pCell, "Width"_ostr), 0.01); +} + +CPPUNIT_TEST_FIXTURE(Test, testParagraphLanguage) +{ + createSwDoc("paragraph-language.fodt"); + + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"UseTaggedPDF"_ustr, true) }; + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + vcl::filter::PDFDocument aDocument; + maTempFile.CloseStream(); + CPPUNIT_ASSERT(aDocument.Read(*maTempFile.GetStream(StreamMode::READ))); + + // every structure element that names a language, sorted: they are emitted by object and + // not by document order + std::vector aLanguages; + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + + auto pType = dynamic_cast(pObject->Lookup("S"_ostr)); + auto pLang + = dynamic_cast(pObject->Lookup("Lang"_ostr)); + if (!pType || !pLang) + continue; + + aLanguages.push_back(pType->GetValue() + "=" + pLang->GetValue()); + } + std::sort(aLanguages.begin(), aLanguages.end()); + + CPPUNIT_ASSERT_EQUAL(size_t(5), aLanguages.size()); + // the German words in an English paragraph, and the text of the paragraph holding a frame, + // which says nothing itself because what is anchored there hangs under it + CPPUNIT_ASSERT_EQUAL("Span=de-DE"_ostr, aLanguages[0]); + CPPUNIT_ASSERT_EQUAL("Span=de-DE"_ostr, aLanguages[1]); + // Without the fix this was de-DE: the run differs from its paragraph, not from the document + CPPUNIT_ASSERT_EQUAL("Span=en-US"_ostr, aLanguages[2]); + // and without it neither German paragraph named a language at all + CPPUNIT_ASSERT_EQUAL("Standard=de-DE"_ostr, aLanguages[3]); + CPPUNIT_ASSERT_EQUAL("Standard=de-DE"_ostr, aLanguages[4]); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/qa/core/text/data/footnote-tag.fodt b/sw/qa/core/text/data/footnote-tag.fodt new file mode 100644 index 000000000000..a1970276d093 --- /dev/null +++ b/sw/qa/core/text/data/footnote-tag.fodt @@ -0,0 +1,21 @@ + + + Footnote tag + + + + + + + + + + + + + + + A paragraph with a note1What the note says. and more text after it. + + + diff --git a/sw/qa/core/text/data/formula-alt.fodt b/sw/qa/core/text/data/formula-alt.fodt new file mode 100644 index 000000000000..d040e95b3c55 --- /dev/null +++ b/sw/qa/core/text/data/formula-alt.fodt @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + The mass energy equivalence is E=mc2E=mc^2 in a paragraph. + A described one is a2a over 2Half of a here. + + + diff --git a/sw/qa/core/text/data/formula-placement.fodt b/sw/qa/core/text/data/formula-placement.fodt new file mode 100644 index 000000000000..a05033f7e5cb --- /dev/null +++ b/sw/qa/core/text/data/formula-placement.fodt @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + The mass energy equivalence is E=mc2E=mc^2 in a paragraph. + One on its own follows.a2a over 2 + One on the page follows.b+1b+1 + + + diff --git a/sw/qa/core/text/data/layout-attribute-units.fodt b/sw/qa/core/text/data/layout-attribute-units.fodt new file mode 100644 index 000000000000..34dce3407f63 --- /dev/null +++ b/sw/qa/core/text/data/layout-attribute-units.fodt @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A first paragraph, so the next one keeps its top margin. + An indented paragraph. + A paragraph whose first line hangs. + + + + + Name + Value + + + + One + 1 + + + + + diff --git a/sw/qa/core/text/data/min-wrap-gap.docx b/sw/qa/core/text/data/min-wrap-gap.docx new file mode 100644 index 000000000000..792f822df50b Binary files /dev/null and b/sw/qa/core/text/data/min-wrap-gap.docx differ diff --git a/sw/qa/core/text/data/paragraph-language.fodt b/sw/qa/core/text/data/paragraph-language.fodt new file mode 100644 index 000000000000..ad7c53e52e12 --- /dev/null +++ b/sw/qa/core/text/data/paragraph-language.fodt @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A paragraph in the language of the document. + Ein Absatz, der ganz und gar deutsch ist. + English with ein paar deutsche Worte in it. + Ein deutscher Absatz mit a few English words darin. + Ein deutscher Absatz mit einem Rahmen.Text inside the frame. + + + diff --git a/sw/qa/core/text/data/structure-destinations.fodt b/sw/qa/core/text/data/structure-destinations.fodt new file mode 100644 index 000000000000..2aa91f366601 --- /dev/null +++ b/sw/qa/core/text/data/structure-destinations.fodt @@ -0,0 +1,47 @@ + + + Structure destinations + + + + + + + + + + + + + + + + + + + + + + + + + + + + A heading + Jump to the heading + Body text with a footnote.1The footnote body. + A citation [AAA] + + + + + + + + AAA + + + + + diff --git a/sw/qa/core/text/data/tdf171555.fodt b/sw/qa/core/text/data/tdf171555.fodt new file mode 100644 index 000000000000..9376755c4316 --- /dev/null +++ b/sw/qa/core/text/data/tdf171555.fodt @@ -0,0 +1,38 @@ + + + + a‑b + + + + + + + + + + + + + + + + + + + + + + + + + + ‑ + - + + mm‑nn + a b + X(1)Y + + + diff --git a/sw/qa/core/text/data/toc-structure-ref.fodt b/sw/qa/core/text/data/toc-structure-ref.fodt new file mode 100644 index 000000000000..6655646c97c5 --- /dev/null +++ b/sw/qa/core/text/data/toc-structure-ref.fodt @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + Table of Contents + First heading1 + Second heading1 + + + First heading + Text under the first heading. + Second heading + Text under the second heading. + + + diff --git a/sw/qa/core/text/text.cxx b/sw/qa/core/text/text.cxx index 58093261e621..c13cfde1ead2 100644 --- a/sw/qa/core/text/text.cxx +++ b/sw/qa/core/text/text.cxx @@ -1871,6 +1871,41 @@ CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testTdf136663_Korean_No_Extra_Spacing) "//body/txt[4]/SwParaPortion/SwLineLayout/child::*[@type='PortionType::Kern']", 0); } +CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testTdf171555NonBreakingHyphen) +{ + // Given paragraphs holding a non-breaking hyphen, on its own and inside a field, in a font + // that has no glyph for U+2011: + createSwDoc("tdf171555.fodt"); + + // When painting them: + std::shared_ptr xMetaFile = getSwDocShell()->GetPreviewMetaFile(); + CPPUNIT_ASSERT(xMetaFile); + + OUStringBuffer aPainted; + for (size_t nAction = 0; nAction < xMetaFile->GetActionSize(); ++nAction) + { + MetaAction* pAction = xMetaFile->GetAction(nAction); + if (pAction->GetType() != MetaActionType::TEXTARRAY) + continue; + + auto pTextArray = static_cast(pAction); + aPainted.append( + pTextArray->GetText().subView(pTextArray->GetIndex(), pTextArray->GetLen())); + } + + // Then the plain hyphen the portion stands for reaches the font, not U+2011 itself, which + // Carlito has no glyph for; inside the field the hyphen was not painted at all. A no-break + // space stands for itself and still reaches the font as U+00A0. Joining the runs keeps this + // independent of where the portions and the narrow column's lines split, while still + // catching a character painted twice: + CPPUNIT_ASSERT_EQUAL(u"--a-bmm-nna\u00A0bX((1))Y"_ustr, aPainted.makeStringAndClear()); + + // ... and the hyphen is as wide as the one the second paragraph spells out directly + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + CPPUNIT_ASSERT_EQUAL(getXPath(pXmlDoc, "//txt[2]/SwParaPortion/SwLineLayout", "width"), + getXPath(pXmlDoc, "//txt[1]/SwParaPortion/SwLineLayout", "width")); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/qa/core/text/txtfly.cxx b/sw/qa/core/text/txtfly.cxx index 0160258ebc47..4d6baea13470 100644 --- a/sw/qa/core/text/txtfly.cxx +++ b/sw/qa/core/text/txtfly.cxx @@ -29,6 +29,25 @@ public: } }; +CPPUNIT_TEST_FIXTURE(Test, testMinWrapGap) +{ + // Given a document with a square-wrapped shape + createSwDoc("min-wrap-gap.docx"); + + // When layouting that document + calcLayout(); + + // Then make sure the line starts with the fly portion + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + // Without the fix, this test would have failed with: + // - Expected: PortionType::Fly + // - Actual: PortionType::Text + assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/SwParaPortion/SwLineLayout/*[1]", "type", + u"PortionType::Fly"); + // The paragraph below the shape should not be affected + assertXPath(pXmlDoc, "/root/page[1]/body/txt[5]/SwParaPortion/SwLineLayout/SwFixPortion", 0); +} + CPPUNIT_TEST_FIXTURE(Test, testClearingBreakWrapThrough) { // Given a document with a clearing break, then a shape in the next paragraph: diff --git a/sw/qa/extras/layout/data/tdf119908_smart_hyphenation_nl.odt b/sw/qa/extras/layout/data/tdf119908_smart_hyphenation_nl.odt new file mode 100644 index 000000000000..04a82426cff3 Binary files /dev/null and b/sw/qa/extras/layout/data/tdf119908_smart_hyphenation_nl.odt differ diff --git a/sw/qa/extras/layout/data/tdf173348-space-only-line.fodt b/sw/qa/extras/layout/data/tdf173348-space-only-line.fodt new file mode 100644 index 000000000000..c01aec20065c --- /dev/null +++ b/sw/qa/extras/layout/data/tdf173348-space-only-line.fodt @@ -0,0 +1,177 @@ + + + LibreOfficeDev/27.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/643aab12aa0946fd268eb99094a98b3ad46ece1bGl Variable form ODUMP Binder.pdf2026-09-18T08:43:28.5996052441P0D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CG 11 11 11 11 + + + + \ No newline at end of file diff --git a/sw/qa/extras/layout/data/tdf173389_smart_hyphenation_hu.odt b/sw/qa/extras/layout/data/tdf173389_smart_hyphenation_hu.odt new file mode 100644 index 000000000000..9ece0b297d86 Binary files /dev/null and b/sw/qa/extras/layout/data/tdf173389_smart_hyphenation_hu.odt differ diff --git a/sw/qa/extras/layout/data/tdf173389_smart_hyphenation_nl.odt b/sw/qa/extras/layout/data/tdf173389_smart_hyphenation_nl.odt new file mode 100644 index 000000000000..9edd6bd190f7 Binary files /dev/null and b/sw/qa/extras/layout/data/tdf173389_smart_hyphenation_nl.odt differ diff --git a/sw/qa/extras/layout/layout3.cxx b/sw/qa/extras/layout/layout3.cxx index 97cf32dfd060..51e0d1e54499 100644 --- a/sw/qa/extras/layout/layout3.cxx +++ b/sw/qa/extras/layout/layout3.cxx @@ -851,7 +851,7 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf172674_less_hyphenation) assertXPath(pXmlDoc, "/root/page[1]/body/txt/SwParaPortion/SwLineLayout/SwHyphPortion", 3); } -// check smart hyphenation and compound-based smart hyphenation +// check smart hyphenation and disabled compound-based smart hyphenation CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf119908_smart_hyphenation_hu) { uno::Reference xHyphenator = LinguMgr::GetHyphenator(); @@ -1030,6 +1030,520 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf119908_smart_hyphenation_hu) pViewShell->Reformat(); pXmlDoc = parseLayoutDump(); + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[2]/SwParaPortion/SwLineLayout[1]", "portion", + u"Senkit sem lehet rabszolgaságban vagy szolgaságban tartani; a rabszolgaság és a rab"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]", "portion", + u"Senkit sem lehet kínvallatásnak, avagy kegyetlen, embertelen vagy lealacsonyító bá"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout[1]", "portion", + u"A törvény előtt mindenki egyenlő, és minden megkülönböztetés nélkül joga van a tör"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout[3]", "portion", + u"sértő minden megkülönböztetéssel és minden ilyen megkülönböztetésre irányuló fel"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout[3]/SwHyphPortion", + 1); + + // no compound-based "smart" hyphenation of the compound "jog|orvoslat" + // This is hyphenated as "jogor-voslat", not "jog-orvoslat" + + if (bHunspell && aTests[0]) + { + assertXPath(pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[2]", "portion", + u"alapvető jogokat sértő eljárások ellen az illetékes hazai bíróságokhoz " + u"tényleges jogor"); + } + assertXPath(pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[2]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[14]/SwParaPortion/SwLineLayout[2]", "portion", + u"bíróság méltányosan és nyilvánosan tárgyalja, s ez határozzon egyrészt jogai és köte"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[14]/SwParaPortion/SwLineLayout[2]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[16]/SwParaPortion/SwLineLayout[1]", "portion", + u"Minden büntetendő cselekménnyel vádolt személyt ártatlannak kell vélelmezni mind"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[16]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[16]/SwParaPortion/SwLineLayout[2]", "portion", + u"addig, amíg bűnösségét nyilvánosan lefolytatott perben, a védelméhez szükséges vala"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[16]/SwParaPortion/SwLineLayout[2]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[17]/SwParaPortion/SwLineLayout[1]", "portion", + u"Senkit sem szabad elítélni oly cselekményért vagy mulasztásért, amely elkövetése pil"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[17]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[17]/SwParaPortion/SwLineLayout[3]", "portion", + u"Ugyancsak nem szabad súlyosabb büntetést kiszabni, mint amely a büntetendő cselek"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[17]/SwParaPortion/SwLineLayout[3]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[19]/SwParaPortion/SwLineLayout[1]", "portion", + u"Senkinek magánéletébe, családi ügyeibe, otthonába vagy levelezésébe nem szabad ön"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[19]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + // "smart" hyphenation: disabled hyphenation of "minden" by shrinking + // This was "Min-den" + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[19]/SwParaPortion/SwLineLayout[2]", "portion", + u"kényesen beavatkozni, sem pedig becsületében vagy jó hírnevében megsérteni. Minden "); + + assertXPath(pXmlDoc, "/root/page[1]/body/txt[21]/SwParaPortion/SwLineLayout[1]", "portion", + u"Az államon belül minden személynek joga van szabadon mozogni és lakóhelyét sza"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[21]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + // no compound-based "smart" hyphenation of the compound "vala|mint" + // This is hyphenated as "va-lamint" without enabled compound-based "smart" hyphenation + + if (bHunspell && aTests[1]) + { + assertXPath(pXmlDoc, "/root/page[1]/body/txt[22]/SwParaPortion/SwLineLayout[1]", "portion", + u"Minden személynek joga van minden országot, ideértve saját hazáját is, " + u"elhagyni, va"); + } + assertXPath(pXmlDoc, "/root/page[1]/body/txt[22]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath(pXmlDoc, "/root/page[1]/body/txt/SwParaPortion/SwLineLayout/SwHyphPortion", 13); + + // delete page 2 to update hyphenation on the next page + + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 2297, /*bBasicCall=*/false); + pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 2297, /*bBasicCall=*/false); + pWrtShell->Delete(); + + // 10 hyphenations on page 3 + + pViewShell->Reformat(); + pXmlDoc = parseLayoutDump(); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[1]", "portion", + u"Erre a jogra nem lehet hivatkozni közönséges bűncselekmény miatti, kellőképpen meg"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[2]", "portion", + u"alapozott üldözés, sem pedig az Egyesült Nemzetek céljaival és elveivel ellentétes te"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[2]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]", "portion", + u"Senkit sem lehet sem állampolgárságától, sem állampolgársága megváltoztatásának jo"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[6]/SwParaPortion/SwLineLayout[1]", "portion", + u"Mind a férfinak, mind a nőnek a nagykorúság elérésétől kezdve joga van fajon, nem"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[6]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[6]/SwParaPortion/SwLineLayout[2]", "portion", + u"zetiségen vagy valláson alapuló korlátozás nélkül házasságot kötni és családot alapí"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[6]/SwParaPortion/SwLineLayout[2]/SwHyphPortion", + 1); + + assertXPath(pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[1]", "portion", + u"Minden személynek joga van a tulajdonhoz, mind egyénileg, mind másokkal együtte"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + // "smart" hyphenation: disabled hyphenation of "valamint" by shifting + // This was "va-lamint" + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[2]", "portion", + u"a jog magában foglalja a vallás és a meggyőződés megváltoztatásának szabadságát, "); + + assertXPath(pXmlDoc, "/root/page[1]/body/txt[15]/SwParaPortion/SwLineLayout[1]", "portion", + u"Minden személynek joga van a vélemény és a kifejezés szabadságához, amely magá"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[15]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[15]/SwParaPortion/SwLineLayout[2]", "portion", + u"ban foglalja azt a jogot, hogy véleménye miatt ne szenvedjen zaklatást, és hogy hatá"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[15]/SwParaPortion/SwLineLayout[2]/SwHyphPortion", + 1); + + // "smart" hyphenation: disabled hyphenation of "információkat" by shrinking + // This was "információ-kat" + + assertXPath(pXmlDoc, "/root/page[1]/body/txt[15]/SwParaPortion/SwLineLayout[3]", "portion", + u"rokra való tekintet nélkül kutathasson, tájékozódhasson és terjeszthessen " + u"információkat "); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[22]/SwParaPortion/SwLineLayout[2]", "portion", + u"rendszeres, az általános és egyenlő szavazati jogot és a titkos szavazást biztosító vá"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[22]/SwParaPortion/SwLineLayout[2]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[24]/SwParaPortion/SwLineLayout[1]", "portion", + u"Minden személynek mint a társadalom tagjának joga van a szociális biztonsághoz, to"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[24]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath(pXmlDoc, "/root/page[1]/body/txt/SwParaPortion/SwLineLayout/SwHyphPortion", 10); + + // delete page 3 to update hyphenation on the next page + + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 2566, /*bBasicCall=*/false); + pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 2566, /*bBasicCall=*/false); + pWrtShell->Delete(); + + // 12 hyphenations on page 4 + + pViewShell->Reformat(); + pXmlDoc = parseLayoutDump(); + + assertXPath(pXmlDoc, "/root/page[1]/body/txt[3]/SwParaPortion/SwLineLayout[1]", "portion", + u"Minden személynek joga van a munkához, a munka szabad megválasztásához, a mél"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[3]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath(pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]", "portion", + u"Mindenkinek joga van egyenlő munkáért egyenlő bért kapni, bárminemű megkülön"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout[1]", "portion", + u"Minden személynek joga van a pihenéshez, a szabadidőhöz, beleértve a munkaidő ész"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[1]", "portion", + u"Minden személynek joga van saját maga és családja egészségének és jólétének bizto"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[3]", "portion", + u"ellátáshoz, valamint a szükséges szociális szolgáltatásokhoz, továbbá joga van a szoci"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[10]/SwParaPortion/SwLineLayout[3]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[11]/SwParaPortion/SwLineLayout[2]", "portion", + u"gyermek, akár házasságból, akár házasságon kívül született, azonos szociális védelem"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[11]/SwParaPortion/SwLineLayout[2]/SwHyphPortion", + 1); + + // no compound-based "smart" hyphenation of the compound "alap|fokon" + // This is hyphenated as "alapfo-kon", not "alap-fokon" + + if (bHunspell && aTests[2]) + { + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[1]", "portion", + u"Minden személynek joga van az oktatáshoz. Az oktatásnak legalábbis elemi és alapfo"); + } + assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[14]/SwParaPortion/SwLineLayout[1]", "portion", + u"A nevelésnek az emberi személyiség teljes kibontakoztatására, valamint az emberi jo"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[14]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[14]/SwParaPortion/SwLineLayout[4]", "portion", + u"közötti megértést, türelmet és barátságot, valamint támogatnia kell az Egyesült Nem"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[14]/SwParaPortion/SwLineLayout[4]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[15]/SwParaPortion/SwLineLayout[1]", "portion", + u"A szülőket elsőbbségi jog illeti meg a gyermekeiknek adandó oktatás megválasztásá"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[15]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + // no compound-based "smart" hyphenation of the compound "jó|téteményekben" + // This is not hyphenated, when compound-based smart hyphenation is disabled + + if (bHunspell && aTests[3]) + { + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[17]/SwParaPortion/SwLineLayout[2]", "portion", + u"a művészetek élvezéséhez, valamint a tudomány haladásában és az abból származó "); + assertXPath(pXmlDoc, + "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[2]/SwHyphPortion", 0); + } + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[18]/SwParaPortion/SwLineLayout[1]", "portion", + u"Mindenkinek joga van minden általa alkotott tudományos, irodalmi és művészeti ter"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[18]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath(pXmlDoc, "/root/page[1]/body/txt/SwParaPortion/SwLineLayout/SwHyphPortion", 12); + + // delete page 4 to update hyphenation on the next page + + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 2766, /*bBasicCall=*/false); + pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 2766, /*bBasicCall=*/false); + pWrtShell->Delete(); + + // 5 hyphenations on page 5 + + pViewShell->Reformat(); + pXmlDoc = parseLayoutDump(); + + assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[1]", "portion", + u"Minden személynek joga van ahhoz, hogy mind a társadalmi, mind a nemzetközi vi"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[2]", "portion", + u"szonyok tekintetében olyan rendszer uralkodjék, amelyben a jelen Nyilatkozatban ki"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[2]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]", "portion", + u"Jogainak gyakorlásában és szabadságainak élvezetében mindenki csak olyan korláto"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + // no compound-based "smart" hyphenation of the compound "el|ismerésének" + // This is not hyphenated by the plain, i.e. not compound-based "smart" hyphenation. + + if (bHunspell && aTests[4]) + { + assertXPath(pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[2]", "portion", + u"zásoknak lehet alávetve, amelyeket a törvény kizárólag mások jogai és " + u"szabadságai "); + assertXPath(pXmlDoc, + "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[2]/SwHyphPortion", 0); + } + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[5]/SwParaPortion/SwLineLayout[1]", "portion", + u"Ezeket a jogokat és szabadságokat semmi esetre sem lehet az Egyesült Nemzetek cél"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[5]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + // "smart" hyphenation: disabled hyphenation of "állam" by shifting + // This was "ál-lam" + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[7]/SwParaPortion/SwLineLayout[1]", "portion", + u"A jelen Nyilatkozat egyetlen rendelkezése sem értelmezhető úgy, hogy az valamely "); + + if (bHunspell && aTests[4]) + assertXPath(pXmlDoc, "/root/page[1]/body/txt/SwParaPortion/SwLineLayout/SwHyphPortion", 5); +} + +// check smart hyphenation and compound-based smart hyphenation +CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf173389_smart_hyphenation_hu) +{ + uno::Reference xHyphenator = LinguMgr::GetHyphenator(); + if (!xHyphenator->hasLocale(lang::Locale(u"hu"_ustr, u"HU"_ustr, OUString()))) + return; + + // compound-based hyphenation needs Hunspell morphological analysis + // (and an up-to-date Hungarian dictionary for full testing, which + // contains compound splitting information. E.g. dictionary version 1.7 + // didn't contain data for the words "valamint" and "elismerésének") + uno::Reference xSpell = LinguMgr::GetSpellChecker(); + LanguageType eLang + = LanguageTag::convertToLanguageType(lang::Locale(u"hu"_ustr, u"HU"_ustr, OUString())); + if (!xSpell.is() || !xSpell->hasLanguage(static_cast(eLang))) + return; + + std::vector> aCompound = { + { u"jogorvoslatért"_ustr, "hy:3" }, { u"valamint"_ustr, "hy:4" }, + { u"alapfokon"_ustr, "pa:fokon" }, { u"jótéteményekben"_ustr, "hy:2" }, + { u"elismerésének"_ustr, "hy:2" }, + }; + + // tests supported by the actual spelling dictionary + bool aTests[5]; + + // check Hunspell-based morphological analysis & hyphenation + bool bHunspell = xSpell->isValid("", static_cast(eLang), + uno::Sequence()); + if (bHunspell) + { + // check dictionary support of compound splitting + for (size_t i = 0; i < aCompound.size(); ++i) + { + aTests[i] = false; + // get morphological analysis of the test word + uno::Reference xTmpRes = xSpell->spell( + "" + aCompound[i].first + "", + static_cast(eLang), uno::Sequence()); + if (xTmpRes.is()) + { + uno::Sequence seq = xTmpRes->getAlternatives(); + if (seq.hasElements()) + { + sal_Int32 nEndOfFirstAnalysis = seq[0].indexOf(""); + // use only the first analysis, like the lingucomponent implementation + OUString morph(seq[0].copy(0, nEndOfFirstAnalysis)); + + // check compound data + aTests[i] = morph.indexOf(aCompound[i].second) > -1; + } + } + } + } + + createSwDoc("tdf173389_smart_hyphenation_hu.odt"); + // Ensure that all text portions are calculated before testing. + SwViewShell* pViewShell = getSwDoc()->getIDocumentLayoutAccess().GetCurrentViewShell(); + + CPPUNIT_ASSERT(pViewShell); + pViewShell->Reformat(); + + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + + // 53 hyphenations on 5 pages using compound-based smart hyphenation + // This was 67 without smart hyphenation and 53 without compound-based smart hyphenation + // (using only the plain or interoperable smart hyphenation). + + // 13 hyphenations on page 1 + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[3]/SwParaPortion/SwLineLayout[1]", "portion", + u"Tekintettel arra, hogy az emberiség családja minden egyes tagja méltóságának, vala"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[3]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + // "smart" hyphenation: disabled hyphenation of "emberiség" by shrinking + // This was "emberi-ség" + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[4]/SwParaPortion/SwLineLayout[1]", "portion", + u"Tekintettel arra, hogy az emberi jogok el nem ismerése és semmibevevése az emberiség "); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[5]/SwParaPortion/SwLineLayout[1]", "portion", + u"Tekintettel annak fontosságára, hogy az emberi jogokat a jog uralma védelmezze, ne"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[5]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[5]/SwParaPortion/SwLineLayout[2]", "portion", + u"hogy az ember végső szükségében a zsarnokság és az elnyomás elleni lázadásra kény"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[5]/SwParaPortion/SwLineLayout[2]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[6]/SwParaPortion/SwLineLayout[1]", "portion", + u"Tekintettel arra, hogy igen lényeges a nemzetek közötti baráti kapcsolatok kifejlődésé"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[6]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + // "smart" hyphenation: disabled hyphenation of "tettek" by shrinking + // This was "tet-tek" + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[7]/SwParaPortion/SwLineLayout[1]", "portion", + u"Tekintettel arra, hogy az Alapokmányban az Egyesült Nemzetek népei újból hitet tettek "); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[7]/SwParaPortion/SwLineLayout[3]", "portion", + u"egyenjogúsága mellett, valamint kinyilvánították azt az elhatározásukat, hogy elősegí"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[7]/SwParaPortion/SwLineLayout[3]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout[1]", "portion", + u"Tekintettel arra, hogy a tagállamok kötelezték magukat arra, hogy az Egyesült Nemze"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[8]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath(pXmlDoc, "/root/page[1]/body/txt[9]/SwParaPortion/SwLineLayout[1]", "portion", + u"A KÖZGYŰLÉS kinyilvánítja az EMBERI JOGOK EGYETEMES NYILATKOZA"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[9]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + // "smart" hyphenation: disabled hyphenation of "törekednie" by shifting + // This was "tö-rekednie" + + assertXPath(pXmlDoc, "/root/page[1]/body/txt[9]/SwParaPortion/SwLineLayout[2]", "portion", + u"TÁT, mint azt a közös eszményt, amelynek elérésére minden népnek és nemzetnek "); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[11]/SwParaPortion/SwLineLayout[1]", "portion", + u"Minden emberi lény szabadnak születik, és egyenlő méltósága és joga van. Az embe"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[11]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[2]", "portion", + u"nyelvre, vallásra, politikai vagy más véleményre, nemzeti vagy társadalmi szárma"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[2]/SwHyphPortion", + 1); + + assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[3]", "portion", + u"zásra, vagyonra, születésre vagy más körülményre vonatkozó mindennemű megkülön"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[3]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[4]", "portion", + u"böztetés nélkül mindenkit megilletnek. Ezenfelül nem lehet semmiféle megkülönböz"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[4]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[5]", "portion", + u"tetést tenni annak az országnak vagy területnek a politikai, jogi vagy nemzetközi hely"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[5]/SwHyphPortion", + 1); + + assertXPath( + pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[7]", "portion", + u"terület független, gyámság alatt áll, nem autonóm vagy szuverenitása bármely vonat"); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/SwParaPortion/SwLineLayout[7]/SwHyphPortion", + 1); + + assertXPath(pXmlDoc, "/root/page[1]/body/txt/SwParaPortion/SwLineLayout/SwHyphPortion", 13); + + // delete page 1 to update hyphenation on the next page + + SwWrtShell* const pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 2868, /*bBasicCall=*/false); + pWrtShell->Delete(); + + // 13 hyphenations on page 2 + + pViewShell->Reformat(); + pXmlDoc = parseLayoutDump(); + assertXPath( pXmlDoc, "/root/page[1]/body/txt[2]/SwParaPortion/SwLineLayout[1]", "portion", u"Senkit sem lehet rabszolgaságban vagy szolgaságban tartani; a rabszolgaság és a rab"); @@ -1365,6 +1879,212 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf119908_smart_hyphenation_hu) assertXPath(pXmlDoc, "/root/page[1]/body/txt/SwParaPortion/SwLineLayout/SwHyphPortion", 5); } +// check smart hyphenation and disabled compound-based smart hyphenation +CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf119908_smart_hyphenation_nl) +{ + uno::Reference xHyphenator = LinguMgr::GetHyphenator(); + if (!xHyphenator->hasLocale(lang::Locale(u"nl"_ustr, u"NL"_ustr, OUString()))) + return; + + // compound-based hyphenation needs fixed Hunspell morphological analysis + uno::Reference xSpell = LinguMgr::GetSpellChecker(); + LanguageType eLang + = LanguageTag::convertToLanguageType(lang::Locale(u"nl"_ustr, u"NL"_ustr, OUString())); + if (!xSpell.is() || !xSpell->hasLanguage(static_cast(eLang))) + return; + + std::vector> aCompound = { + { u"arbeidsvoorwaarden"_ustr, "pa:arbeids" }, + { u"doeleinden"_ustr, "pa:doel" }, + }; + + // tests supported by the actual spelling dictionary + bool aTests[2]; + + // check Hunspell-based morphological analysis & hyphenation + bool bHunspell = xSpell->isValid("", static_cast(eLang), + uno::Sequence()); + if (bHunspell) + { + // check dictionary support of compound splitting + for (size_t i = 0; i < aCompound.size(); ++i) + { + aTests[i] = false; + // get morphological analysis of the test word + uno::Reference xTmpRes = xSpell->spell( + "" + aCompound[i].first + "", + static_cast(eLang), uno::Sequence()); + if (xTmpRes.is()) + { + uno::Sequence seq = xTmpRes->getAlternatives(); + if (seq.hasElements()) + { + sal_Int32 nEndOfFirstAnalysis = seq[0].indexOf(""); + // use only the first analysis, like the lingucomponent implementation + OUString morph(seq[0].copy(0, nEndOfFirstAnalysis)); + + // check compound data + aTests[i] = morph.indexOf(aCompound[i].second) > -1; + } + } + } + } + + createSwDoc("tdf119908_smart_hyphenation_nl.odt"); + // Ensure that all text portions are calculated before testing. + SwViewShell* pViewShell = getSwDoc()->getIDocumentLayoutAccess().GetCurrentViewShell(); + + CPPUNIT_ASSERT(pViewShell); + pViewShell->Reformat(); + + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + + // delete page 1-2 to update hyphenation on the next page + + SwWrtShell* const pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 6151, /*bBasicCall=*/false); + pWrtShell->Delete(); + + pViewShell->Reformat(); + pXmlDoc = parseLayoutDump(); + + // no compound-based "smart" hyphenation of the compound "arbeids|voorwaarden" + // This is hyphenated as "ar-beidsvoorwaarden" previously, not "arbeids-voorwaarden" + + if (bHunspell && aTests[0]) + { + assertXPath(pXmlDoc, "/root/page[1]/body/txt[25]/SwParaPortion/SwLineLayout[1]", "portion", + u"Een ieder heeft recht op arbeid, op vrije keuze van beroep, op rechtmatige " + u"en gunstige ar"); + } + assertXPath(pXmlDoc, "/root/page[1]/body/txt[25]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + // delete page 3 and page 4 to update hyphenation on the last page + + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 6177, /*bBasicCall=*/false); + pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 6177, /*bBasicCall=*/false); + pWrtShell->Delete(); + + pViewShell->Reformat(); + pXmlDoc = parseLayoutDump(); + + // compound-based "smart" hyphenation of the compound "doel|einden" + // This is hyphenated as "doelein-den" previously, not "doel-einden" + + if (bHunspell && aTests[1]) + { + assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[1]", "portion", + u"Deze rechten en vrijheden mogen in geen geval worden uitgeoefend in strijd " + u"met de doelein"); + } + assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); +} + +// check smart hyphenation and compound-based smart hyphenation +CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf173389_smart_hyphenation_nl) +{ + uno::Reference xHyphenator = LinguMgr::GetHyphenator(); + if (!xHyphenator->hasLocale(lang::Locale(u"nl"_ustr, u"NL"_ustr, OUString()))) + return; + + // compound-based hyphenation needs fixed Hunspell morphological analysis + uno::Reference xSpell = LinguMgr::GetSpellChecker(); + LanguageType eLang + = LanguageTag::convertToLanguageType(lang::Locale(u"nl"_ustr, u"NL"_ustr, OUString())); + if (!xSpell.is() || !xSpell->hasLanguage(static_cast(eLang))) + return; + + std::vector> aCompound = { + { u"arbeidsvoorwaarden"_ustr, "pa:arbeids" }, + { u"doeleinden"_ustr, "pa:doel" }, + }; + + // tests supported by the actual spelling dictionary + bool aTests[2]; + + // check Hunspell-based morphological analysis & hyphenation + bool bHunspell = xSpell->isValid("", static_cast(eLang), + uno::Sequence()); + if (bHunspell) + { + // check dictionary support of compound splitting + for (size_t i = 0; i < aCompound.size(); ++i) + { + aTests[i] = false; + // get morphological analysis of the test word + uno::Reference xTmpRes = xSpell->spell( + "" + aCompound[i].first + "", + static_cast(eLang), uno::Sequence()); + if (xTmpRes.is()) + { + uno::Sequence seq = xTmpRes->getAlternatives(); + if (seq.hasElements()) + { + sal_Int32 nEndOfFirstAnalysis = seq[0].indexOf(""); + // use only the first analysis, like the lingucomponent implementation + OUString morph(seq[0].copy(0, nEndOfFirstAnalysis)); + + // check compound data + aTests[i] = morph.indexOf(aCompound[i].second) > -1; + } + } + } + } + + createSwDoc("tdf173389_smart_hyphenation_nl.odt"); + // Ensure that all text portions are calculated before testing. + SwViewShell* pViewShell = getSwDoc()->getIDocumentLayoutAccess().GetCurrentViewShell(); + + CPPUNIT_ASSERT(pViewShell); + pViewShell->Reformat(); + + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + + // delete page 1-2 to update hyphenation on the next page + + SwWrtShell* const pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 6151, /*bBasicCall=*/false); + pWrtShell->Delete(); + + pViewShell->Reformat(); + pXmlDoc = parseLayoutDump(); + + // compound-based "smart" hyphenation of the compound "arbeids|voorwaarden" + // This was hyphenated as "ar-beidsvoorwaarden" previously, now "arbeids-voorwaarden" + + if (bHunspell && aTests[0]) + { + assertXPath(pXmlDoc, "/root/page[1]/body/txt[25]/SwParaPortion/SwLineLayout[1]", "portion", + u"Een ieder heeft recht op arbeid, op vrije keuze van beroep, op rechtmatige " + u"en gunstige arbeids"); + } + assertXPath(pXmlDoc, "/root/page[1]/body/txt[25]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); + + // delete page 3 and page 4 to update hyphenation on the last page + + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 6177, /*bBasicCall=*/false); + pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 6177, /*bBasicCall=*/false); + pWrtShell->Delete(); + + pViewShell->Reformat(); + pXmlDoc = parseLayoutDump(); + + // compound-based "smart" hyphenation of the compound "doel|einden" + // This was hyphenated as "doelein-den" previously, now "doel-einden" + + if (bHunspell && aTests[1]) + { + assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[1]", "portion", + u"Deze rechten en vrijheden mogen in geen geval worden uitgeoefend in strijd " + u"met de doel"); + } + assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout[1]/SwHyphPortion", + 1); +} + CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf158333) { createSwDoc("tdf130088.docx"); diff --git a/sw/qa/extras/layout/layout6.cxx b/sw/qa/extras/layout/layout6.cxx index 72a69f81cbbd..2332906853e6 100644 --- a/sw/qa/extras/layout/layout6.cxx +++ b/sw/qa/extras/layout/layout6.cxx @@ -2736,6 +2736,17 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testCool15691_headerTextWrapsAroundImageLa } } +CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testTdf173348SpaceOnlyLine) +{ + // Test fix for regression in line breaking after a whitespace-only line. + createSwDoc("tdf173348-space-only-line.fodt"); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + CPPUNIT_ASSERT(pXmlDoc); + + // Without the fix, the C would be moved to the previous column + assertXPath(pXmlDoc, "//column[2]//SwLineLayout", "portion", u"CG 11 11 11 11"); +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/qa/extras/odfimport/data/fieldmark-below-nested-table.fodt b/sw/qa/extras/odfimport/data/fieldmark-below-nested-table.fodt new file mode 100644 index 000000000000..3ab8a7f83a0a --- /dev/null +++ b/sw/qa/extras/odfimport/data/fieldmark-below-nested-table.fodt @@ -0,0 +1,28 @@ + + + + + + + + + + + + + inner cell text + + + + beforeresultafter + + + + body + + + diff --git a/sw/qa/extras/odfimport/odfimport.cxx b/sw/qa/extras/odfimport/odfimport.cxx index 985a43547b52..5d46782fcfe7 100644 --- a/sw/qa/extras/odfimport/odfimport.cxx +++ b/sw/qa/extras/odfimport/odfimport.cxx @@ -53,6 +53,7 @@ #include #include +#include #include #include #include @@ -1864,6 +1865,16 @@ CPPUNIT_TEST_FIXTURE(Test, testHorizontalRuleOldOdf) CPPUNIT_ASSERT(!getProperty(getShape(1), u"HorizontalRule"_ustr)); } +CPPUNIT_TEST_FIXTURE(Test, testFieldmarkBelowNestedTable) +{ + // a fieldmark in a table cell whose first content is a nested table is imported, and the + // document it is in loads + createSwDoc("fieldmark-below-nested-table.fodt"); + + IDocumentMarkAccess& rMarkAccess(*getSwDoc()->getIDocumentMarkAccess()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), rMarkAccess.getFieldmarksCount()); +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/qa/extras/ooxmlexport/data/math-color-ctrlpr.docx b/sw/qa/extras/ooxmlexport/data/math-color-ctrlpr.docx new file mode 100644 index 000000000000..1905997f0aa0 Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/math-color-ctrlpr.docx differ diff --git a/sw/qa/extras/ooxmlexport/data/math-color.docx b/sw/qa/extras/ooxmlexport/data/math-color.docx new file mode 100644 index 000000000000..71c47f16a3f0 Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/math-color.docx differ diff --git a/sw/qa/extras/ooxmlexport/data/table-style-jc.docx b/sw/qa/extras/ooxmlexport/data/table-style-jc.docx new file mode 100644 index 000000000000..22ddb3d93c07 Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/table-style-jc.docx differ diff --git a/sw/qa/extras/ooxmlexport/data/tdf109308_dropCap.fodt b/sw/qa/extras/ooxmlexport/data/tdf109308_dropCap.fodt new file mode 100644 index 000000000000..ce6a45ef38d1 --- /dev/null +++ b/sw/qa/extras/ooxmlexport/data/tdf109308_dropCap.fodt @@ -0,0 +1,15 @@ + + + + + + + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum consequat mi quis pretium semper. Proin luctus orci ac neque venenatis, quis commodo dolor posuere. Curabitur dignissim sapien quis cursus egestas. Donec blandit auctor arcu, nec pellentesque eros molestie eget. + + + diff --git a/sw/qa/extras/ooxmlexport/data/tdf109308_dropCapHyperlink.fodt b/sw/qa/extras/ooxmlexport/data/tdf109308_dropCapHyperlink.fodt new file mode 100644 index 000000000000..b29d21e7a422 --- /dev/null +++ b/sw/qa/extras/ooxmlexport/data/tdf109308_dropCapHyperlink.fodt @@ -0,0 +1,15 @@ + + + + + + + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum consequat mi quis pretium semper. Proin luctus orci ac neque venenatis, quis commodo dolor posuere. Curabitur dignissim sapien quis cursus egestas. Donec blandit auctor arcu, nec pellentesque eros molestie eget. + + + diff --git a/sw/qa/extras/ooxmlexport/data/tdf109308_dropCapNumbered.docx b/sw/qa/extras/ooxmlexport/data/tdf109308_dropCapNumbered.docx new file mode 100644 index 000000000000..6fb3c0831a99 Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf109308_dropCapNumbered.docx differ diff --git a/sw/qa/extras/ooxmlexport/data/tdf109308_dropCapRuby.fodt b/sw/qa/extras/ooxmlexport/data/tdf109308_dropCapRuby.fodt new file mode 100644 index 000000000000..c672d1e67f12 --- /dev/null +++ b/sw/qa/extras/ooxmlexport/data/tdf109308_dropCapRuby.fodt @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum consequat mi quis pretium semper. Proin luctus orci ac neque venenatis, quis commodo dolor posuere. Curabitur dignissim sapien quis cursus egestas. Donec blandit auctor arcu, nec pellentesque eros molestie eget.ruby + + + diff --git a/sw/qa/extras/ooxmlexport/data/tdf109308_dropCapSdt.docx b/sw/qa/extras/ooxmlexport/data/tdf109308_dropCapSdt.docx new file mode 100644 index 000000000000..d6371cb4445b Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf109308_dropCapSdt.docx differ diff --git a/sw/qa/extras/ooxmlexport/data/tdf158349_SDTstreamStateStack.docx b/sw/qa/extras/ooxmlexport/data/tdf158349_SDTstreamStateStack.docx new file mode 100644 index 000000000000..fecdfe74d315 Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf158349_SDTstreamStateStack.docx differ diff --git a/sw/qa/extras/ooxmlexport/data/tdf163868_headerAsFramePr.docx b/sw/qa/extras/ooxmlexport/data/tdf163868_headerAsFramePr.docx new file mode 100644 index 000000000000..83adf4aa730e Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf163868_headerAsFramePr.docx differ diff --git a/sw/qa/extras/ooxmlexport/data/tdf169024_verticalTopFrame.odt b/sw/qa/extras/ooxmlexport/data/tdf169024_verticalTopFrame.odt new file mode 100644 index 000000000000..620209a3b719 Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf169024_verticalTopFrame.odt differ diff --git a/sw/qa/extras/ooxmlexport/data/tdf79738_footerLineNumbering.docx b/sw/qa/extras/ooxmlexport/data/tdf79738_footerLineNumbering.docx new file mode 100644 index 000000000000..5837fbc5e68d Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf79738_footerLineNumbering.docx differ diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport19.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport19.cxx index b69b40fa3166..4607f172ecaf 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport19.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport19.cxx @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -476,6 +477,25 @@ DECLARE_OOXMLEXPORT_TEST(testTdf92124, "tdf92124.docx") CPPUNIT_ASSERT(aSuffix.isEmpty()); } +CPPUNIT_TEST_FIXTURE(Test, testTdf169024_verticalTopFrame) +{ + // Given an SW frame anchored TO_CHAR, vertically aligned above (top) the Line of Text + // gets exported as 'bottom' in DOCX, and import maps 'bottom' to top of Line of Text again + + auto verify = [this]() { + // To Character anchored Line of Text need to be LINE_TOP in order for the GUI to pick it up + CPPUNIT_ASSERT_EQUAL(text::RelOrientation::TEXT_LINE, + getProperty(getShape(1), u"VertOrientRelation"_ustr)); + CPPUNIT_ASSERT_EQUAL(text::VertOrientation::LINE_TOP, + getProperty(getShape(1), u"VertOrient"_ustr)); + }; + + createSwDoc("tdf169024_verticalTopFrame.odt"); + verify(); + saveAndReload(TestFilter::DOCX_2007); + verify(); +} + DECLARE_OOXMLEXPORT_TEST(testTdf90153, "tdf90153.docx") { // This was at-para, so the line-level VertOrientRelation was lost, resulting in an incorrect vertical position. @@ -483,6 +503,18 @@ DECLARE_OOXMLEXPORT_TEST(testTdf90153, "tdf90153.docx") getProperty(getShape(1), u"AnchorType"_ustr)); } +CPPUNIT_TEST_FIXTURE(Test, testTdf169024) +{ + createSwDoc("tdf90153.docx"); + //saveAndReload(TestFilter::DOCX); + + // To Character anchored Line of Text need to be LINE_BOTTOM in order for the GUI to pick it up + CPPUNIT_ASSERT_EQUAL(text::RelOrientation::TEXT_LINE, + getProperty(getShape(1), u"VertOrientRelation"_ustr)); + CPPUNIT_ASSERT_EQUAL(text::VertOrientation::LINE_BOTTOM, + getProperty(getShape(1), u"VertOrient"_ustr)); +} + DECLARE_OOXMLEXPORT_TEST(testTdf93919, "tdf93919.docx") { // This was 0, left margin was not inherited from the list style. diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport2.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport2.cxx index 97c439d97e3f..1f6e72c1f8d0 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport2.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport2.cxx @@ -325,6 +325,75 @@ DECLARE_OOXMLEXPORT_TEST(testMathAccents, "math-accents.docx") getFormula( getRun( getParagraph( 1 ), 1 ))); } +CPPUNIT_TEST_FIXTURE(Test, testMathColor) +{ + createSwDoc("math-color.docx"); + + // A font color on part of a formula becomes a color command around that part. + // FF0000 is a color StarMath names, 00B050 is not, so one comes back as a name + // and the other as hexadecimal digits. + CHECK_FORMULA(u"color red {x} + color hex 00B050 {y}"_ustr, + getFormula(getRun(getParagraph(1), 1))); + // An operator on its own cannot stand as an expression, so it goes in as a literal + // and the formula still parses + CHECK_FORMULA(u"a color blue {\"+\"} b"_ustr, getFormula(getRun(getParagraph(2), 1))); + // A color of auto is the default color, so the formula stays plain + CHECK_FORMULA(u"c"_ustr, getFormula(getRun(getParagraph(3), 1))); + // The color is found wherever the run properties keep it, not only when it comes + // first. This run puts several other properties in front of it. + CHECK_FORMULA(u"color yellow {z}"_ustr, getFormula(getRun(getParagraph(4), 1))); + // The parser reads only uppercase hexadecimal digits, so a value written in + // lowercase comes back in uppercase and stays one command + CHECK_FORMULA(u"color hex 00B050 {w}"_ustr, getFormula(getRun(getParagraph(5), 1))); + // and a run of several operators is no more an expression than a single one + CHECK_FORMULA(u"a color blue {\"+-\"} b"_ustr, getFormula(getRun(getParagraph(6), 1))); + // A run that is not an expression on its own keeps no color command at all, so the + // formula reads the way it did before any color was carried over + CHECK_FORMULA(u"a+ b"_ustr, getFormula(getRun(getParagraph(7), 1))); + + saveAndReload(TestFilter::DOCX); + xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); + + // Each color goes back on the run it belongs to, and the run between them + // keeps the default color + assertXPath(pXmlDoc, "//w:p[1]//m:oMath/m:r[1]/w:rPr/w:color", "val", u"FF0000"); + assertXPath(pXmlDoc, "//w:p[1]//m:oMath/m:r[2]/w:rPr/w:color", 0); + assertXPath(pXmlDoc, "//w:p[1]//m:oMath/m:r[3]/w:rPr/w:color", "val", u"00B050"); + assertXPath(pXmlDoc, "//w:p[3]//m:oMath/m:r[1]/w:rPr/w:color", 0); + assertXPath(pXmlDoc, "//w:p[4]//m:oMath/m:r[1]/w:rPr/w:color", "val", u"FFFF00"); + assertXPath(pXmlDoc, "//w:p[5]//m:oMath/m:r[1]/w:rPr/w:color", "val", u"00B050"); + assertXPath(pXmlDoc, "//w:p[6]//m:oMath/m:r[2]/w:rPr/w:color", "val", u"0000FF"); + assertXPath(pXmlDoc, "//w:p[7]//m:oMath/m:r[1]/w:rPr/w:color", 0); +} + +CPPUNIT_TEST_FIXTURE(Test, testMathFractionBarColor) +{ + createSwDoc("math-color-ctrlpr.docx"); + + // A fraction bar and a root sign are drawn by the construct itself, and their color + // travels in the m:ctrlPr. One command around the whole construct colors the part it + // draws as well as the terms inside it. + CHECK_FORMULA(u"color red {{x} over {2}}"_ustr, getFormula(getRun(getParagraph(1), 1))); + CHECK_FORMULA(u"color blue {sqrt {y}}"_ustr, getFormula(getRun(getParagraph(2), 1))); + // An m:ctrlPr that names no color leaves the fraction at the default color + CHECK_FORMULA(u"{a} over {b}"_ustr, getFormula(getRun(getParagraph(3), 1))); + // A border box that draws no line of its own is dropped, and the color it named + // stays on the content that is left + CHECK_FORMULA(u"color red {c}"_ustr, getFormula(getRun(getParagraph(4), 1))); + + saveAndReload(TestFilter::DOCX); + xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); + + // The color goes back on the m:ctrlPr, so the bar and the root sign keep it + assertXPath(pXmlDoc, "//w:p[1]//m:f/m:fPr/m:ctrlPr/w:rPr/w:color", "val", u"FF0000"); + assertXPath(pXmlDoc, "//w:p[1]//m:f/m:num/m:r/w:rPr/w:color", "val", u"FF0000"); + assertXPath(pXmlDoc, "//w:p[1]//m:f/m:den/m:r/w:rPr/w:color", "val", u"FF0000"); + assertXPath(pXmlDoc, "//w:p[2]//m:rad/m:radPr/m:ctrlPr/w:rPr/w:color", "val", u"0000FF"); + // and a construct at the default color says nothing about it + assertXPath(pXmlDoc, "//w:p[3]//m:f/m:fPr/m:ctrlPr/w:rPr/w:color", 0); + assertXPath(pXmlDoc, "//w:p[4]//m:oMath/m:r[1]/w:rPr/w:color", "val", u"FF0000"); +} + DECLARE_OOXMLEXPORT_TEST(testMathD, "math-d.docx") { CHECK_FORMULA( u"left (x mline y mline z right )"_ustr, getFormula( getRun( getParagraph( 1 ), 1 ))); diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport27.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport27.cxx index 0f95241d4078..a7a99ac3761c 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport27.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport27.cxx @@ -13,9 +13,12 @@ #include #include #include +#include +#include #include #include #include +#include #include #include @@ -45,6 +48,18 @@ DECLARE_OOXMLEXPORT_TEST(testTdf38575_fullWidthLine, "tdf38575_fullWidthLine.doc CPPUNIT_ASSERT_EQUAL(4, getPages()); } +DECLARE_OOXMLEXPORT_TEST(testTdf79738_footerLineNumbering, "tdf79738_footerLineNumbering.docx") +{ + uno::Reference xStylesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference xStyleFamilies = xStylesSupplier->getStyleFamilies(); + uno::Reference xStyles; + xStyleFamilies->getByName(u"ParagraphStyles"_ustr) >>= xStyles; + uno::Reference xHeader(xStyles->getByName(u"Header"_ustr), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(false, xHeader->getPropertyValue(u"ParaLineNumberCount"_ustr).get()); + uno::Reference xFooter(xStyles->getByName(u"Footer"_ustr), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(false, xFooter->getPropertyValue(u"ParaLineNumberCount"_ustr).get()); +} + CPPUNIT_TEST_FIXTURE(Test, testTdf124398_groupshapeChart) { // given a document with grouped chart and textbox @@ -68,6 +83,11 @@ DECLARE_OOXMLEXPORT_TEST(testTdf138027_pageBreakAfterShape, "tdf138027_pageBreak assertXPath(pDump, "/root/page[1]/sorted_objs/fly", 2); } +DECLARE_OOXMLEXPORT_TEST(testTdf158349_SDTstreamStateStack, "tdf158349_SDTstreamStateStack.docx") +{ + // should load and reload without reporting a corrupt document +} + CPPUNIT_TEST_FIXTURE(Test, testTdf168607_tabstopZero) { // Given a 1 page document, where the default tab-stop length is zero @@ -93,6 +113,16 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf163178_columnbreakInTable) assertXPath(pXmlDoc, "//w:br", 0); } +CPPUNIT_TEST_FIXTURE(Test, testTdf163868_headerAsFramePr) +{ + // given a plain text document with a framePr'd paragraph in the header + createSwDoc("tdf163868_headerAsFramePr.docx"); + + saveAndReload(TestFilter::DOCX); + + CPPUNIT_ASSERT(getShapes()); // at least one text frame is seen +} + CPPUNIT_TEST_FIXTURE(Test, testTdf171527_flyInFramePr) { // given a document with a framePr'd image anchoring a drawing shape @@ -227,6 +257,112 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf146973_perFieldDateLocale) assertXPath(pXmlDoc, "(//w:r[w:instrText])[2]/w:rPr/w:lang", "val", u"uk-UA"); } +CPPUNIT_TEST_FIXTURE(Test, testTdf109308_dropCap) +{ + // Given a paragraph starting with a drop cap three lines tall + createSwDoc("tdf109308_dropCap.fodt"); + save(TestFilter::DOCX); + + // Word wants the initial in a framed paragraph of its own, followed by a paragraph + // with the rest of the text. Without the fix the drop cap was not written at all. + xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); + assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:pPr/w:framePr", "dropCap", u"drop"); + assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:pPr/w:framePr", "lines", u"3"); + assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:pPr/w:framePr", "hSpace", u"283"); + assertXPathContent(pXmlDoc, "/w:document/w:body/w:p[1]/w:r/w:t", u"L"); + + // Word does not enlarge the initial itself, so the size of it has to be written out: + // in half-points, well above the 24 of the 12pt body text + assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:pPr/w:spacing", "lineRule", u"exact"); + CPPUNIT_ASSERT_GREATER( + sal_Int32(24), + getXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:r/w:rPr/w:sz", "val").toInt32()); + + // and the text the initial was taken from stays in the paragraph that follows + CPPUNIT_ASSERT( + getXPathContent(pXmlDoc, "/w:document/w:body/w:p[2]/w:r/w:t").startsWith("orem ipsum")); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf109308_dropCapRoundtrip) +{ + createSwDoc("tdf109308_dropCap.fodt"); + saveAndReload(TestFilter::DOCX); + + // The two paragraphs Word needs have to come back as the single one they were + CPPUNIT_ASSERT_EQUAL(1, getParagraphs()); + + uno::Reference xParagraph(getParagraph(1), uno::UNO_QUERY); + auto aDropCap = getProperty(xParagraph, u"DropCapFormat"_ustr); + CPPUNIT_ASSERT_EQUAL(sal_Int8(3), aDropCap.Lines); + CPPUNIT_ASSERT_EQUAL(sal_Int8(1), aDropCap.Count); + CPPUNIT_ASSERT_EQUAL(sal_Int16(499), aDropCap.Distance); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf109308_dropCapHyperlink) +{ + // Given a paragraph with a drop cap whose whole text is one hyperlink + createSwDoc("tdf109308_dropCapHyperlink.fodt"); + save(TestFilter::DOCX); + + // The initial cannot be given a paragraph of its own here, because the hyperlink + // wraps the runs of both paragraphs and the first paragraph would have to end inside + // it. Without the fix the file was written that way and could not be opened again. + xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); + assertXPath(pXmlDoc, "/w:document/w:body/w:p", 1); + assertXPath(pXmlDoc, "//w:framePr", 0); + assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:hyperlink", 1); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf109308_dropCapRuby) +{ + // Given a paragraph with a drop cap whose whole text carries a ruby + createSwDoc("tdf109308_dropCapRuby.fodt"); + save(TestFilter::DOCX); + + // The initial cannot be given a paragraph of its own here either, because the ruby + // wraps the runs of both paragraphs and the first paragraph would have to end between + // the ruby base and the end of the ruby, which is a file that does not open. + xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); + assertXPath(pXmlDoc, "/w:document/w:body/w:p", 1); + assertXPath(pXmlDoc, "//w:framePr", 0); + assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:r/w:ruby", 1); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf109308_dropCapSdt) +{ + // Given a paragraph with a drop cap whose initial sits in a structured document tag + // that the document model cannot show, so it is only known from a grab bag + createSwDoc("tdf109308_dropCapSdt.docx"); + save(TestFilter::DOCX); + + // The tag wraps the runs, so the paragraph cannot be split for the initial. The size + // of a drop cap has to be given up with the frame, or the file would open with a large + // letter in the middle of a sentence. + xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); + assertXPath(pXmlDoc, "//w:framePr", 0); + assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]//w:sdt", 1); + assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]//w:r[1]/w:rPr/w:sz", 0); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf109308_dropCapNumbered) +{ + // Given a numbered paragraph that starts with a drop cap, where the list comes from + // the paragraph style + createSwDoc("tdf109308_dropCapNumbered.docx"); + save(TestFilter::DOCX); + + xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); + assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:pPr/w:framePr", "dropCap", u"drop"); + + // The paragraph that frames the initial carries the paragraph style, so it has to be + // taken out of the list again, or the number would be shown twice. Without the fix + // nothing did that and the initial got a number of its own. + assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:pPr/w:numPr/w:numId", "val", u"0"); + + // The number belongs in front of the text, which is in the paragraph that follows + assertXPath(pXmlDoc, "/w:document/w:body/w:p[2]/w:pPr/w:numPr/w:numId", "val", u"1"); +} + CPPUNIT_TEST_FIXTURE(Test, testTdf146973_rtlDateLocale) { // The runs of this Arabic date field only set a complex script language (w:lang w:bidi), @@ -257,6 +393,32 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf146973_rtlDateLocale) assertXPathNoAttribute(pXmlDoc, "(//w:r[w:instrText])[1]/w:rPr/w:lang", "val"); } +CPPUNIT_TEST_FIXTURE(Test, testTableAlignmentFromTableStyle) +{ + // The first table takes its alignment from the w:jc of its table style, the second one + // overrides the w:jc of its style with a direct one. + auto verify = [this]() { + uno::Reference xSupplier(mxComponent, uno::UNO_QUERY_THROW); + uno::Reference xTables = xSupplier->getTextTables(); + + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 2 + // - Actual : 7 + // i.e. the table was left aligned, the w:jc of the table style having been dropped. + CPPUNIT_ASSERT_EQUAL( + text::HoriOrientation::CENTER, + getProperty(xTables->getByName(u"Table1"_ustr), u"HoriOrient"_ustr)); + CPPUNIT_ASSERT_EQUAL( + text::HoriOrientation::LEFT_AND_WIDTH, + getProperty(xTables->getByName(u"Table2"_ustr), u"HoriOrient"_ustr)); + }; + + createSwDoc("table-style-jc.docx"); + verify(); + saveAndReload(TestFilter::DOCX); + verify(); +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/qa/extras/uiwriter/uiwriter11.cxx b/sw/qa/extras/uiwriter/uiwriter11.cxx index fc0a021fd89e..03f9813a3020 100644 --- a/sw/qa/extras/uiwriter/uiwriter11.cxx +++ b/sw/qa/extras/uiwriter/uiwriter11.cxx @@ -45,6 +45,15 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace { @@ -1240,6 +1249,51 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest11, testNavigateByHyperlinkOrder) } } +CPPUNIT_TEST_FIXTURE(SwUiWriterTest11, testTdf172128_sharedStyleSheetCache) +{ + // tdf#172128: SwDocStyleSheetPool::Find() reuses one sheet. Loading Heading 1 + // into that sheet, then finding Default Paragraph Style, must not leave + // Heading 1's direct attributes (e.g. color) SET on the shared ItemSet — + // otherwise ODF export writes them onto unrelated parent styles. + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + auto& rStyles = pDoc->getIDocumentStylePoolAccess(); + + SwTextFormatColl* pHeading1 = rStyles.GetTextCollFromPool(SwPoolFormatId::COLL_HEADLINE1); + CPPUNIT_ASSERT(pHeading1); + pDoc->SetAttr(SvxColorItem(COL_RED, RES_CHRATR_COLOR), *pHeading1); + + SwTextFormatColl* pStandard = rStyles.GetTextCollFromPool(SwPoolFormatId::COLL_STANDARD); + CPPUNIT_ASSERT(pStandard); + // Ensure Standard does not already carry a direct color override + pStandard->ResetFormatAttr(RES_CHRATR_COLOR); + + SfxStyleSheetBasePool* pPool = getSwDocShell()->GetStyleSheetPool(); + CPPUNIT_ASSERT(pPool); + + const OUString aHeadingName = pHeading1->GetName().toString(); + const OUString aStandardName = pStandard->GetName().toString(); + + SfxStyleSheetBase* pSheet = pPool->Find(aHeadingName, SfxStyleFamily::Para); + CPPUNIT_ASSERT(pSheet); + CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, + pSheet->GetItemSet().GetItemState(RES_CHRATR_COLOR, false)); + + // Property-chip path used to Find(parent) here and pollute the shared sheet + rtl::Reference xTmp( + new SwDocStyleSheet(*static_cast(pSheet))); + (void)xTmp->GetItemPresentation(MapUnit::MapTwip); + + pSheet = pPool->Find(aStandardName, SfxStyleFamily::Para); + CPPUNIT_ASSERT(pSheet); + // Without the fix, residual Heading 1 color remained SET on this ItemSet + CPPUNIT_ASSERT_MESSAGE( + "Shared style sheet cache leaked Heading 1 color onto Default Paragraph Style", + pSheet->GetItemSet().GetItemState(RES_CHRATR_COLOR, false) != SfxItemState::SET); + + CPPUNIT_ASSERT(pStandard->GetItemState(RES_CHRATR_COLOR, false) != SfxItemState::SET); +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/qa/extras/ww8export/data/tdf173671_textlineTop.doc b/sw/qa/extras/ww8export/data/tdf173671_textlineTop.doc new file mode 100644 index 000000000000..7b5e958757cd Binary files /dev/null and b/sw/qa/extras/ww8export/data/tdf173671_textlineTop.doc differ diff --git a/sw/qa/extras/ww8export/data/tdf79738_footerLineNumbering.doc b/sw/qa/extras/ww8export/data/tdf79738_footerLineNumbering.doc new file mode 100644 index 000000000000..4c9a09ced08f Binary files /dev/null and b/sw/qa/extras/ww8export/data/tdf79738_footerLineNumbering.doc differ diff --git a/sw/qa/extras/ww8export/ww8export4.cxx b/sw/qa/extras/ww8export/ww8export4.cxx index 9fb760cc2db1..deef3f2c1fa1 100644 --- a/sw/qa/extras/ww8export/ww8export4.cxx +++ b/sw/qa/extras/ww8export/ww8export4.cxx @@ -73,6 +73,18 @@ DECLARE_WW8EXPORT_TEST(testTdf72511_editengLRSpace, "tdf72511_editengLRSpace.doc getProperty(getParagraph(1), u"ParaLeftMargin"_ustr)); } +DECLARE_WW8EXPORT_TEST(testTdf79738_footerLineNumbering, "tdf79738_footerLineNumbering.doc") +{ + uno::Reference xStylesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference xStyleFamilies = xStylesSupplier->getStyleFamilies(); + uno::Reference xStyles; + xStyleFamilies->getByName(u"ParagraphStyles"_ustr) >>= xStyles; + uno::Reference xHeader(xStyles->getByName(u"Header"_ustr), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(false, xHeader->getPropertyValue(u"ParaLineNumberCount"_ustr).get()); + uno::Reference xFooter(xStyles->getByName(u"Footer"_ustr), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(false, xFooter->getPropertyValue(u"ParaLineNumberCount"_ustr).get()); +} + DECLARE_WW8EXPORT_TEST(testTdf160049_anchorMargin, "tdf160049_anchorMargin.doc") { // given a document with a LEFT "column/text" anchored image @@ -268,6 +280,15 @@ CPPUNIT_TEST_FIXTURE(Test, testEndnotesAtSectEndDOC) CPPUNIT_ASSERT(pFormat->GetEndAtTextEnd().IsAtEnd()); } +DECLARE_WW8EXPORT_TEST(testTdf173671_textlineTop, "tdf173671_textlineTop.doc") +{ + const auto xShape = getShape(1); + CPPUNIT_ASSERT_EQUAL(css::text::RelOrientation::TEXT_LINE, + getProperty(xShape, u"VertOrientRelation"_ustr)); + CPPUNIT_ASSERT_EQUAL(css::text::VertOrientation::LINE_TOP, + getProperty(xShape, u"VertOrient"_ustr)); +} + DECLARE_WW8EXPORT_TEST(testTdf90408, "tdf90408.doc") { uno::Reference xRun(getRun(getParagraph(1), 1), uno::UNO_QUERY_THROW); diff --git a/sw/qa/uitest/styleInspector/styleInspector.py b/sw/qa/uitest/styleInspector/styleInspector.py index 3995c8be557f..1f808c878835 100644 --- a/sw/qa/uitest/styleInspector/styleInspector.py +++ b/sw/qa/uitest/styleInspector/styleInspector.py @@ -26,7 +26,7 @@ class styleNavigator(UITestCase): # The cursor is on text without formatting and default style self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(160, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(161, len(xListBox.getChild('0').getChild('0').getChildren())) self.assertEqual(0, len(xListBox.getChild('1').getChildren())) self.assertEqual(0, len(xListBox.getChild('2').getChildren())) self.assertEqual(0, len(xListBox.getChild('3').getChildren())) @@ -36,7 +36,7 @@ class styleNavigator(UITestCase): # The cursor is on text with direct formatting self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(160, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(161, len(xListBox.getChild('0').getChild('0').getChildren())) self.assertEqual(0, len(xListBox.getChild('1').getChildren())) self.assertEqual(0, len(xListBox.getChild('2').getChildren())) @@ -54,7 +54,7 @@ class styleNavigator(UITestCase): # The cursor is on text with paragraph direct formatting self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(160, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(161, len(xListBox.getChild('0').getChild('0').getChildren())) xParDirFormatting = xListBox.getChild('1') self.assertEqual(7, len(xParDirFormatting.getChildren())) @@ -75,13 +75,13 @@ class styleNavigator(UITestCase): xParStyle = xListBox.getChild('0') self.assertEqual(3, len(xParStyle.getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xParStyle.getChild('0'))['Text']) - self.assertEqual(160, len(xParStyle.getChild('0').getChildren())) + self.assertEqual(161, len(xParStyle.getChild('0').getChildren())) self.assertEqual("Heading\t", get_state_as_dict(xParStyle.getChild('1'))['Text']) self.assertEqual(28, len(xParStyle.getChild('1').getChildren())) xTitleStyle = xParStyle.getChild('2') self.assertEqual("Title\t", get_state_as_dict(xTitleStyle)['Text']) - self.assertEqual(24, len(xTitleStyle.getChildren())) + self.assertEqual(25, len(xTitleStyle.getChildren())) self.assertEqual("Char Difference Height\t0", get_state_as_dict(xTitleStyle.getChild('0'))['Text']) self.assertEqual("Char Difference Height Asian\t0", get_state_as_dict(xTitleStyle.getChild('1'))['Text']) self.assertEqual("Char Difference Height Complex\t0", get_state_as_dict(xTitleStyle.getChild('2'))['Text']) @@ -97,15 +97,16 @@ class styleNavigator(UITestCase): self.assertEqual("Follow Style\tText body", get_state_as_dict(xTitleStyle.getChild('12'))['Text']) self.assertEqual("Para Adjust\t3", get_state_as_dict(xTitleStyle.getChild('13'))['Text']) self.assertEqual("Para Composer\tFalse", get_state_as_dict(xTitleStyle.getChild('14'))['Text']) - self.assertEqual("Para Expand Single Word\tFalse", get_state_as_dict(xTitleStyle.getChild('15'))['Text']) - self.assertEqual("Para Last Line Adjust\t0", get_state_as_dict(xTitleStyle.getChild('16'))['Text']) - self.assertEqual("Para Letter Spacing Maximum\t0", get_state_as_dict(xTitleStyle.getChild('17'))['Text']) - self.assertEqual("Para Letter Spacing Minimum\t0", get_state_as_dict(xTitleStyle.getChild('18'))['Text']) - self.assertEqual("Para Scale Width Maximum\t100", get_state_as_dict(xTitleStyle.getChild('19'))['Text']) - self.assertEqual("Para Scale Width Minimum\t100", get_state_as_dict(xTitleStyle.getChild('20'))['Text']) - self.assertEqual("Para Word Spacing\t100", get_state_as_dict(xTitleStyle.getChild('21'))['Text']) - self.assertEqual("Para Word Spacing Maximum\t100", get_state_as_dict(xTitleStyle.getChild('22'))['Text']) - self.assertEqual("Para Word Spacing Minimum\t100", get_state_as_dict(xTitleStyle.getChild('23'))['Text']) + self.assertEqual("Para Compound-based Hyphenation\tFalse", get_state_as_dict(xTitleStyle.getChild('15'))['Text']) + self.assertEqual("Para Expand Single Word\tFalse", get_state_as_dict(xTitleStyle.getChild('16'))['Text']) + self.assertEqual("Para Last Line Adjust\t0", get_state_as_dict(xTitleStyle.getChild('17'))['Text']) + self.assertEqual("Para Letter Spacing Maximum\t0", get_state_as_dict(xTitleStyle.getChild('18'))['Text']) + self.assertEqual("Para Letter Spacing Minimum\t0", get_state_as_dict(xTitleStyle.getChild('19'))['Text']) + self.assertEqual("Para Scale Width Maximum\t100", get_state_as_dict(xTitleStyle.getChild('20'))['Text']) + self.assertEqual("Para Scale Width Minimum\t100", get_state_as_dict(xTitleStyle.getChild('21'))['Text']) + self.assertEqual("Para Word Spacing\t100", get_state_as_dict(xTitleStyle.getChild('22'))['Text']) + self.assertEqual("Para Word Spacing Maximum\t100", get_state_as_dict(xTitleStyle.getChild('23'))['Text']) + self.assertEqual("Para Word Spacing Minimum\t100", get_state_as_dict(xTitleStyle.getChild('24'))['Text']) self.assertEqual(0, len(xListBox.getChild('1').getChildren())) self.assertEqual(0, len(xListBox.getChild('2').getChildren())) @@ -117,7 +118,7 @@ class styleNavigator(UITestCase): xParStyle = xListBox.getChild('0') self.assertEqual(3, len(xParStyle.getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xParStyle.getChild('0'))['Text']) - self.assertEqual(160, len(xParStyle.getChild('0').getChildren())) + self.assertEqual(161, len(xParStyle.getChild('0').getChildren())) self.assertEqual("Body Text\t", get_state_as_dict(xParStyle.getChild('1'))['Text']) self.assertEqual(6, len(xParStyle.getChild('1').getChildren())) @@ -152,7 +153,7 @@ class styleNavigator(UITestCase): # The cursor is on text without metadata self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(160, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(161, len(xListBox.getChild('0').getChild('0').getChildren())) self.assertEqual(0, len(xListBox.getChild('1').getChildren())) self.assertEqual(0, len(xListBox.getChild('2').getChildren())) self.assertEqual(0, len(xListBox.getChild('3').getChildren())) @@ -162,7 +163,7 @@ class styleNavigator(UITestCase): # The cursor is on text with paragraph metadata showed under direct paragraph formatting self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(160, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(161, len(xListBox.getChild('0').getChild('0').getChildren())) xParDirFormatting = xListBox.getChild('1') self.assertEqual(1, len(xParDirFormatting.getChildren())) @@ -215,7 +216,7 @@ class styleNavigator(UITestCase): # The cursor is on text without metadata self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(160, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(161, len(xListBox.getChild('0').getChild('0').getChildren())) self.assertEqual(0, len(xListBox.getChild('1').getChildren())) self.assertEqual(0, len(xListBox.getChild('2').getChildren())) self.assertEqual(0, len(xListBox.getChild('3').getChildren())) @@ -225,7 +226,7 @@ class styleNavigator(UITestCase): # The cursor is on text with paragraph metadata showed under direct paragraph formatting self.assertEqual(1, len(xListBox.getChild('1').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('1').getChild('0'))['Text']) - self.assertEqual(160, len(xListBox.getChild('1').getChild('0').getChildren())) + self.assertEqual(161, len(xListBox.getChild('1').getChild('0').getChildren())) # Outer bookmark xBookmarkFormatting = xListBox.getChild('0') @@ -272,7 +273,7 @@ class styleNavigator(UITestCase): # The cursor is on text without metadata self.assertEqual(1, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) - self.assertEqual(160, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(161, len(xListBox.getChild('0').getChild('0').getChildren())) self.assertEqual(0, len(xListBox.getChild('1').getChildren())) self.assertEqual(0, len(xListBox.getChild('2').getChildren())) diff --git a/sw/qa/uitest/styleInspector/tdf137513.py b/sw/qa/uitest/styleInspector/tdf137513.py index 0599e4dc1007..33a597b41988 100644 --- a/sw/qa/uitest/styleInspector/tdf137513.py +++ b/sw/qa/uitest/styleInspector/tdf137513.py @@ -35,7 +35,7 @@ class tdf137513(UITestCase): self.assertEqual(2, len(xListBox.getChild('0').getChildren())) self.assertEqual("Default Paragraph Style\t", get_state_as_dict(xListBox.getChild('0').getChild('0'))['Text']) self.assertEqual("Table Contents\t", get_state_as_dict(xListBox.getChild('0').getChild('1'))['Text']) - self.assertEqual(160, len(xListBox.getChild('0').getChild('0').getChildren())) + self.assertEqual(161, len(xListBox.getChild('0').getChild('0').getChildren())) xTableContent = xListBox.getChild('0').getChild('1') self.assertEqual(5, len(xTableContent.getChildren())) diff --git a/sw/qa/uitest/uibase/shells/shells.py b/sw/qa/uitest/uibase/shells/shells.py index a1de003f155b..65923948e6de 100644 --- a/sw/qa/uitest/uibase/shells/shells.py +++ b/sw/qa/uitest/uibase/shells/shells.py @@ -10,6 +10,7 @@ from uitest.framework import UITestCase from uitest.uihelper.common import get_state_as_dict from uitest.uihelper.common import get_url_for_data_file +from libreoffice.uno.propertyvalue import mkPropertyValues import time @@ -29,4 +30,51 @@ class TestSwGrfShell(UITestCase): # i.e. the percent width of the image was wrong. self.assertEqual(get_state_as_dict(widthField)["Text"], "80%") +class TestSwTextShell(UITestCase): + def testOutlineBulletFromBulletDropdown(self): + # Given a doc with no list at cursor + with self.ui_test.create_doc_in_start_center("writer"): + # When opening b&n dialog with "Customize" btn: + with self.ui_test.execute_blocking_action(self.xUITest.executeCommandWithParameters, + args=(".uno:OutlineBullet", mkPropertyValues({"Page": "customize", "Bullet": True})), + close_button="cancel") as dialog: + level = dialog.getChild('levellb') + format = dialog.getChild('numfmtlb') + + # Then make sure the customize page displays Bullet as the selected format + self.assertEqual("Bullet", get_state_as_dict(format)["SelectEntryText"]) + xToolkit = self.xContext.ServiceManager.createInstance('com.sun.star.awt.Toolkit') + for nLevel in range(1, 10): + # The level list allows multiple selection, so select the next + # level before dropping the previous one, and let the posted + # level handler run before reading the format back. + level.getChild(str(nLevel)).executeAction("SELECT", tuple()) + level.getChild(str(nLevel - 1)).executeAction("DESELECT", tuple()) + xToolkit.waitUntilAllIdlesDispatched() + self.assertEqual(str(nLevel+1), get_state_as_dict(level)["SelectEntryText"]) + self.assertEqual("Bullet", get_state_as_dict(format)["SelectEntryText"]) + + def testOutlineBulletFromNumberDropdown(self): + # Given a doc with no list at cursor + with self.ui_test.create_doc_in_start_center("writer"): + # When opening b&n dialog with "Customize" btn: + with self.ui_test.execute_blocking_action(self.xUITest.executeCommandWithParameters, + args=(".uno:OutlineBullet", mkPropertyValues({"Page": "customize", "Bullet": False})), + close_button="cancel") as dialog: + level = dialog.getChild('levellb') + format = dialog.getChild('numfmtlb') + + # Then make sure the customize page displays Number as the selected format + self.assertEqual("1, 2, 3, ...", get_state_as_dict(format)["SelectEntryText"]) + xToolkit = self.xContext.ServiceManager.createInstance('com.sun.star.awt.Toolkit') + for nLevel in range(1, 10): + # The level list allows multiple selection, so select the next + # level before dropping the previous one, and let the posted + # level handler run before reading the format back. + level.getChild(str(nLevel)).executeAction("SELECT", tuple()) + level.getChild(str(nLevel - 1)).executeAction("DESELECT", tuple()) + xToolkit.waitUntilAllIdlesDispatched() + self.assertEqual(str(nLevel+1), get_state_as_dict(level)["SelectEntryText"]) + self.assertEqual("1, 2, 3, ...", get_state_as_dict(format)["SelectEntryText"]) + # vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/sw/qa/writerfilter/dmapper/GraphicImport.cxx b/sw/qa/writerfilter/dmapper/GraphicImport.cxx index 7b729abb4744..b9d2b44a34a5 100644 --- a/sw/qa/writerfilter/dmapper/GraphicImport.cxx +++ b/sw/qa/writerfilter/dmapper/GraphicImport.cxx @@ -331,7 +331,7 @@ CPPUNIT_TEST_FIXTURE(Test, testTextboxTextlineTop) sal_Int16 nActualOrient{}; CPPUNIT_ASSERT(xShape->getPropertyValue(u"VertOrient"_ustr) >>= nActualOrient); - sal_Int16 nExpectedOrient = text::VertOrientation::BOTTOM; + sal_Int16 nExpectedOrient = text::VertOrientation::LINE_BOTTOM; CPPUNIT_ASSERT_EQUAL(nExpectedOrient, nActualOrient); } diff --git a/sw/qa/writerfilter/rtftok/data/shape-in-header-table-row.rtf b/sw/qa/writerfilter/rtftok/data/shape-in-header-table-row.rtf new file mode 100644 index 000000000000..188f6f4015d8 --- /dev/null +++ b/sw/qa/writerfilter/rtftok/data/shape-in-header-table-row.rtf @@ -0,0 +1 @@ +{\rtf1{\header\pard\intbl{\shp{\*\shpinst{\shptxt A{\par}}}}\cell\row}\par} diff --git a/sw/qa/writerfilter/rtftok/rtfdocumentimpl.cxx b/sw/qa/writerfilter/rtftok/rtfdocumentimpl.cxx index d89ffe382206..23c1c8fda9c2 100644 --- a/sw/qa/writerfilter/rtftok/rtfdocumentimpl.cxx +++ b/sw/qa/writerfilter/rtftok/rtfdocumentimpl.cxx @@ -134,6 +134,20 @@ CPPUNIT_TEST_FIXTURE(Test, testImageSizeInShapeText) // i.e. the size of the icon was inherited from the containing shape, losing the own size. CPPUNIT_ASSERT_EQUAL(nExpected, rImageSize.GetWidth()); } + +CPPUNIT_TEST_FIXTURE(Test, testShapeInHeaderTableRow) +{ + // Given a header with a shape in a table row, and a paragraph mark inside the shape text: + // When loading this document: + createSwDoc("shape-in-header-table-row.rtf"); + + // Then make sure the document loads and the shape is there: + // Without the accompanying fix in place, this test would have failed with an assertion + // failure in DomainMapper_Impl::substream(), because the header left the context stack one + // level deeper than it found it. + SwDoc* pDoc = getSwDoc(); + CPPUNIT_ASSERT_EQUAL(static_cast(1), pDoc->GetSpzFrameFormats()->size()); +} } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/attr/calbck.cxx b/sw/source/core/attr/calbck.cxx index 06200a7c0f6a..75531f673eb3 100644 --- a/sw/source/core/attr/calbck.cxx +++ b/sw/source/core/attr/calbck.cxx @@ -307,6 +307,7 @@ void sw::ClientNotifyAttrChg(SwModify& rModify, const SwAttrSet& aSet, SwAttrSet } template class sw::ClientBase; +template class sw::ClientBase; template class sw::ClientBase; template bool SwModify::HasOnlySpecificWriterListeners() const; /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentSettingManager.cxx b/sw/source/core/doc/DocumentSettingManager.cxx index 144e02f4b2a4..638c1bdee75d 100644 --- a/sw/source/core/doc/DocumentSettingManager.cxx +++ b/sw/source/core/doc/DocumentSettingManager.cxx @@ -80,6 +80,7 @@ sw::DocumentSettingManager::DocumentSettingManager(SwDoc &rDoc) mbTabRelativeToIndent(true), mbProtectForm(false), // i#78591# mbMsWordCompTrailingBlanks(false), // tdf#104349 tdf#104668 + mbMsWordCompMinTextWrapGap(false), mbMsWordCompMinLineHeightByFly(false), mbInvertBorderSpacing (false), mbCollapseEmptyCellPara(true), @@ -221,6 +222,7 @@ bool sw::DocumentSettingManager::get(/*[in]*/ DocumentSettingId id) const case DocumentSettingId::PROTECT_FORM: return mbProtectForm; // tdf#104349 tdf#104668 case DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS: return mbMsWordCompTrailingBlanks; + case DocumentSettingId::MS_WORD_COMP_MIN_TEXT_WRAP_GAP: return mbMsWordCompMinTextWrapGap; case DocumentSettingId::MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY: return mbMsWordCompMinLineHeightByFly; // #i89181# case DocumentSettingId::TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST: return mbTabAtLeftIndentForParagraphsInList; @@ -416,6 +418,10 @@ void sw::DocumentSettingManager::set(/*[in]*/ DocumentSettingId id, /*[in]*/ boo mbMsWordCompTrailingBlanks = value; break; + case DocumentSettingId::MS_WORD_COMP_MIN_TEXT_WRAP_GAP: + mbMsWordCompMinTextWrapGap = value; + break; + case DocumentSettingId::MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY: mbMsWordCompMinLineHeightByFly = value; break; @@ -806,6 +812,7 @@ void sw::DocumentSettingManager::ReplaceCompatibilityOptions(const DocumentSetti mbTabRelativeToIndent = rSource.mbTabRelativeToIndent; mbProtectForm = rSource.mbProtectForm; mbMsWordCompTrailingBlanks = rSource.mbMsWordCompTrailingBlanks; + mbMsWordCompMinTextWrapGap = rSource.mbMsWordCompMinTextWrapGap; mbMsWordCompMinLineHeightByFly = rSource.mbMsWordCompMinLineHeightByFly; mbInvertBorderSpacing = rSource.mbInvertBorderSpacing; mbCollapseEmptyCellPara = rSource.mbCollapseEmptyCellPara; @@ -1068,6 +1075,11 @@ void sw::DocumentSettingManager::dumpAsXml(xmlTextWriterPtr pWriter) const BAD_CAST(OString::boolean(mbMsWordCompTrailingBlanks).getStr())); (void)xmlTextWriterEndElement(pWriter); + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbMsWordCompMinTextWrapGap")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbMsWordCompMinTextWrapGap).getStr())); + (void)xmlTextWriterEndElement(pWriter); + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbMsWordCompMinLineHeightByFly")); (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::boolean(mbMsWordCompMinLineHeightByFly).getStr())); diff --git a/sw/source/core/doc/doc.cxx b/sw/source/core/doc/doc.cxx index 940c3af70a3b..db01cdbffbfc 100644 --- a/sw/source/core/doc/doc.cxx +++ b/sw/source/core/doc/doc.cxx @@ -743,7 +743,7 @@ void SwDoc::CalculatePagesForPrinting( // 0 -> print all pages (default if aPageRange is empty) // 1 -> print range according to PageRange // 2 -> print selection - if (1 == nContent) + if (1 == nContent || 3 == nContent) aPageRange = rOptions.getStringValue( "PageRange" ); if (2 == nContent) diff --git a/sw/source/core/doc/doclay.cxx b/sw/source/core/doc/doclay.cxx index 8ed6f575dfb6..8ca094029a3b 100644 --- a/sw/source/core/doc/doclay.cxx +++ b/sw/source/core/doc/doclay.cxx @@ -1399,7 +1399,7 @@ UIName SwDoc::GetUniqueShapeName() const UIName SwDoc::GetUniqueDrawObjectName() const { - return lcl_GetUniqueFlyName(*this, TranslateId(nullptr, "DrawObject"), RES_DRAWFRMFMT); + return lcl_GetUniqueFlyName(*this, TranslateId(nullptr, "DrawObject "), RES_DRAWFRMFMT); } const SwFlyFrameFormat* SwDoc::FindFlyByName( const UIName& rName, SwNodeType nNdTyp ) const diff --git a/sw/source/core/docnode/nodes.cxx b/sw/source/core/docnode/nodes.cxx index f6c23ed2737c..f776ef7c52c9 100644 --- a/sw/source/core/docnode/nodes.cxx +++ b/sw/source/core/docnode/nodes.cxx @@ -1073,6 +1073,22 @@ void SwNodes::SectionUpDown( const SwNodeIndex & aStart, const SwNodeIndex & aEn } } +namespace +{ + // Nothing updates the pointer from a SwTableBox to its cell's start node, so an emptied cell + // keeps its start and end node while the table that owns the cell stays. nLastSurvivingNode is + // the index of the last node that this deletion leaves in place, so a table at or below it + // stays and a table above it goes. + bool IsStartNodeOfSurvivingTableBox(const SwNode* pNode, SwNodeOffset nLastSurvivingNode) + { + const SwStartNode* pStartNode = pNode->GetStartNode(); + if (!pStartNode || pStartNode->GetStartNodeType() != SwTableBoxStartNode) + return false; + const SwTableNode* pTableNode = pStartNode->FindTableNode(); + return pTableNode && pTableNode->GetIndex() <= nLastSurvivingNode; + } +} + void SwNodes::Delete(const SwNodeIndex &rIndex, SwNodeOffset nNodes) { Delete(rIndex.GetNode(), nNodes); @@ -1196,7 +1212,8 @@ void SwNodes::Delete(const SwNode &rIndex, SwNodeOffset nNodes) --aRg.aEnd; while( pTmpNode && ( pCurrentNode = &aRg.aEnd.GetNode())->GetStartNode() && - pCurrentNode->StartOfSectionIndex() ) + pCurrentNode->StartOfSectionIndex() && + !IsStartNodeOfSurvivingTableBox(pCurrentNode, aRg.aStart.GetIndex()) ) { // remove end and start node DelNodes( aRg.aEnd, SwNodeOffset(2) ); @@ -1235,7 +1252,9 @@ void SwNodes::Delete(const SwNode &rIndex, SwNodeOffset nNodes) // delete all empty start/end node pairs while( aRg.aEnd.GetNode().GetEndNode() && ( pCurrentNode = &aRg.aStart.GetNode())->GetStartNode() && - pCurrentNode->StartOfSectionIndex() ) + pCurrentNode->StartOfSectionIndex() && + !IsStartNodeOfSurvivingTableBox(pCurrentNode, + aRg.aStart.GetIndex() - SwNodeOffset(1)) ) // but none of the holy 5. (???) { DelNodes( aRg.aStart, SwNodeOffset(2) ); // delete start and end node diff --git a/sw/source/core/fields/expfld.cxx b/sw/source/core/fields/expfld.cxx index f060817e1876..ddce3bc788f8 100644 --- a/sw/source/core/fields/expfld.cxx +++ b/sw/source/core/fields/expfld.cxx @@ -229,9 +229,9 @@ const SwTextNode* GetBodyTextNode( const SwDoc& rDoc, SwPosition& rPos, pTab->GetTable()->GetRowsToRepeat() > 0 && pTab->IsInHeadline( *pContentFrame ) ) { - // take the next line + // take the next line, when the follow has one const SwLayoutFrame* pRow = pTab->GetFirstNonHeadlineRow(); - pContentFrame = pRow->ContainsContent(); + pContentFrame = pRow ? pRow->ContainsContent() : nullptr; } } else diff --git a/sw/source/core/inc/DocumentSettingManager.hxx b/sw/source/core/inc/DocumentSettingManager.hxx index 3c25b97ed2fb..37d007e255b0 100644 --- a/sw/source/core/inc/DocumentSettingManager.hxx +++ b/sw/source/core/inc/DocumentSettingManager.hxx @@ -143,6 +143,7 @@ class DocumentSettingManager final : bool mbTabRelativeToIndent : 1; // #i24363# tab stops relative to indent bool mbProtectForm : 1; bool mbMsWordCompTrailingBlanks : 1; // tdf#104349 tdf#104668 + bool mbMsWordCompMinTextWrapGap : 1; bool mbMsWordCompMinLineHeightByFly : 1; bool mbInvertBorderSpacing : 1; bool mbCollapseEmptyCellPara : 1; diff --git a/sw/source/core/inc/UndoAttribute.hxx b/sw/source/core/inc/UndoAttribute.hxx index 8aab4092c027..65d8ae09bae6 100644 --- a/sw/source/core/inc/UndoAttribute.hxx +++ b/sw/source/core/inc/UndoAttribute.hxx @@ -163,7 +163,7 @@ public: }; // helper class to receive changed attribute sets -class SwUndoFormatAttrHelper final : public SwClient +class SwUndoFormatAttrHelper final : public sw::ClientBase { SwFormat& m_rFormat; std::unique_ptr m_pUndo; diff --git a/sw/source/core/inc/pagefrm.hxx b/sw/source/core/inc/pagefrm.hxx index 0343c93d1639..79e4d71e815d 100644 --- a/sw/source/core/inc/pagefrm.hxx +++ b/sw/source/core/inc/pagefrm.hxx @@ -218,6 +218,8 @@ public: void SetFootnotePage( bool b ) { m_bFootnotePage = b; } void SetEndNotePage( bool b ) { m_bEndNotePage = b; } SwSectionFrame* GetEndNoteSection(); + /// The frame an endnote section at the end of the document is a lower of. + SwLayoutFrame* GetEndNoteSectionUpper(); sal_uInt16 GetPhyPageNum() const { return m_nPhyPageNum;} void SetPhyPageNum( sal_uInt16 nNum ) { m_nPhyPageNum = nNum;} diff --git a/sw/source/core/inc/swfont.hxx b/sw/source/core/inc/swfont.hxx index 4816099fbdd4..1a4fb6d2103b 100644 --- a/sw/source/core/inc/swfont.hxx +++ b/sw/source/core/inc/swfont.hxx @@ -39,6 +39,7 @@ class SwDoCapitals; // DoCapitals class SwScriptInfo; // GetTextSize_ class SwViewShell; class IDocumentSettingAccess; +class SvxBoxItem; enum class SvxShadowItemSide; const sal_Unicode CH_BLANK = ' '; // ' ' blank spaces @@ -354,6 +355,8 @@ public: void Invalidate() { m_bFontChg = m_bOrgChg = true; } + void SetBorder(const SvxBoxItem* pBoxItem); + void ClearBorders(); void SetTopBorder( const editeng::SvxBorderLine* pTopBorder ); void SetBottomBorder( const editeng::SvxBorderLine* pBottomBorder ); void SetRightBorder( const editeng::SvxBorderLine* pRightBorder ); @@ -378,6 +381,10 @@ public: void SetBottomBorderDist( const sal_uInt16 nBottomDist ); void SetRightBorderDist( const sal_uInt16 nRightDist ); void SetLeftBorderDist( const sal_uInt16 nLeftDist ); + void SetBorderDist(const sal_uInt16 nTopDist, + const sal_uInt16 nBottomDist, + const sal_uInt16 nRightDist, + const sal_uInt16 nLeftDist); sal_uInt16 GetTopBorderDist() const { return m_nTopBorderDist; } sal_uInt16 GetBottomBorderDist() const { return m_nBottomBorderDist; } @@ -908,6 +915,14 @@ inline void SwFont::SetLeftBorderDist( const sal_uInt16 nLeftDist ) m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr; } +inline void SwFont::SetBorderDist(const sal_uInt16 nTopDist, const sal_uInt16 nBottomDist, const sal_uInt16 nRightDist, const sal_uInt16 nLeftDist) +{ + SetTopBorderDist(nTopDist); + SetBottomBorderDist(nBottomDist); + SetRightBorderDist(nRightDist); + SetLeftBorderDist(nLeftDist); +} + inline sal_uInt16 SwFont::GetTopBorderSpace() const { sal_uInt16 nRet = 0; diff --git a/sw/source/core/layout/findfrm.cxx b/sw/source/core/layout/findfrm.cxx index 974774c941c7..92debcf4216f 100644 --- a/sw/source/core/layout/findfrm.cxx +++ b/sw/source/core/layout/findfrm.cxx @@ -66,7 +66,7 @@ SwContentFrame *SwPageFrame::FindLastBodyContent() return pRet; } -SwSectionFrame* SwPageFrame::GetEndNoteSection() +SwLayoutFrame* SwPageFrame::GetEndNoteSectionUpper() { SwLayoutFrame* pBody = FindBodyCont(); if (!pBody) @@ -75,6 +75,27 @@ SwSectionFrame* SwPageFrame::GetEndNoteSection() } SwFrame* pLast = pBody->GetLastLower(); + if (!pLast || !pLast->IsColumnFrame()) + { + return pBody; + } + + // A body frame with columns keeps its text in the column bodies, so the body text ends at + // the end of the last column's body. + SwFrame* pColumnBody = static_cast(pLast)->Lower(); + assert(pColumnBody && pColumnBody->IsBodyFrame() && "column frame without a body frame"); + return static_cast(pColumnBody); +} + +SwSectionFrame* SwPageFrame::GetEndNoteSection() +{ + SwLayoutFrame* pUpper = GetEndNoteSectionUpper(); + if (!pUpper) + { + return nullptr; + } + + SwFrame* pLast = pUpper->GetLastLower(); if (!pLast || !pLast->IsSctFrame()) { return nullptr; diff --git a/sw/source/core/layout/fly.cxx b/sw/source/core/layout/fly.cxx index a47e8548b44b..2f7637342431 100644 --- a/sw/source/core/layout/fly.cxx +++ b/sw/source/core/layout/fly.cxx @@ -3593,7 +3593,7 @@ const SwFormatAnchor* SwFlyFrame::GetAnchorFromPoolItem(const SfxPoolItem& rItem switch(rItem.Which()) { case RES_ANCHOR: - return static_cast(&rItem); + return &rItem.StaticWhichCast(RES_ANCHOR); default: return nullptr; } diff --git a/sw/source/core/layout/ftnfrm.cxx b/sw/source/core/layout/ftnfrm.cxx index efee0677e40e..ac15ec9f9bd5 100644 --- a/sw/source/core/layout/ftnfrm.cxx +++ b/sw/source/core/layout/ftnfrm.cxx @@ -1658,7 +1658,7 @@ void SwFootnoteBossFrame::AppendFootnote( SwContentFrame *pRef, SwTextFootnote * { SwSection* pSwSection = rDoc.GetEndNoteInfo().GetSwSection(rDoc); pEndnoteSection = new SwSectionFrame(*pSwSection, pPage); - SwLayoutFrame* pParent = pPage->FindBodyCont(); + SwLayoutFrame* pParent = pPage->GetEndNoteSectionUpper(); SwFrame* pBefore = pPage->FindLastBodyContent(); while (pBefore) { diff --git a/sw/source/core/text/EnhancedPDFExportHelper.cxx b/sw/source/core/text/EnhancedPDFExportHelper.cxx index 6f8dae771534..bd2c58adbf5d 100644 --- a/sw/source/core/text/EnhancedPDFExportHelper.cxx +++ b/sw/source/core/text/EnhancedPDFExportHelper.cxx @@ -19,13 +19,19 @@ #include +#include +#include + +#include #include #include #include #include #include +#include #include +#include #include #include #include @@ -149,8 +155,12 @@ struct SwEnhancedPDFState NumListIdMap m_NumListIdMap; NumListBodyIdMap m_NumListBodyIdMap; FrameTagSet m_FrameTagSet; + std::unordered_map m_DestNodeMap; + std::unordered_map m_NodeTagIdMap; LanguageType m_eLanguageDefault; + // the language item the application's script uses, which the default was read with + TypedWhichId m_nLanguageWhich; struct Span { @@ -162,13 +172,32 @@ struct SwEnhancedPDFState SwFontScript nScript; LanguageType nLang; OUString StyleName; + + bool operator==(const Span&) const = default; }; - ::std::optional m_oCurrentSpan; - ::std::optional m_oCurrentLink; + struct Link + { + const SwTextAttr* pAttr; + sal_Int32 nStructElement; + // the annotations already nested in it + std::vector AnnotIds; + // the properties the Link's own tag carries + Span aSpan; + }; - SwEnhancedPDFState(LanguageType const eLanguageDefault) + /// the paragraph of every TOC item, whose link says what the item refers to, and the item + std::vector> m_TOCItems; + + ::std::optional m_oCurrentSpan; + std::optional m_oCurrentLink; + // left open for a following portion, innermost last + std::vector m_DeferredTags; + + SwEnhancedPDFState(LanguageType const eLanguageDefault, + const TypedWhichId nLanguageWhich) : m_eLanguageDefault(eLanguageDefault) + , m_nLanguageWhich(nLanguageWhich) { } }; @@ -209,7 +238,6 @@ constexpr OUStringLiteral aBlockQuoteString = u"BlockQuote"; constexpr OUString aCaptionString = u"Caption"_ustr; constexpr OUStringLiteral aIndexString = u"Index"; constexpr OUStringLiteral aTOCString = u"TOC"; -constexpr OUStringLiteral aTOCIString = u"TOCI"; constexpr OUStringLiteral aTableString = u"Table"; constexpr OUStringLiteral aTRString = u"TR"; constexpr OUStringLiteral aTDString = u"TD"; @@ -221,10 +249,9 @@ constexpr OUStringLiteral aCodeString = u"Code"; constexpr OUStringLiteral aFigureString = u"Figure"; constexpr OUStringLiteral aFormulaString = u"Formula"; constexpr OUString aLinkString = u"Link"_ustr; -constexpr OUStringLiteral aNoteString = u"Note"; constexpr OUStringLiteral aAnnotString = u"Annot"; -// returns true if first paragraph in cell frame has 'table heading' style +// returns true if first paragraph in cell frame has 'table heading' style, or one derived from it bool lcl_IsHeadlineCell( const SwCellFrame& rCellFrame ) { bool bRet = false; @@ -233,11 +260,14 @@ bool lcl_IsHeadlineCell( const SwCellFrame& rCellFrame ) if ( pCnt && pCnt->IsTextFrame() ) { SwTextNode const*const pTextNode = static_cast(pCnt)->GetTextNodeForParaProps(); - const SwFormat* pTextFormat = pTextNode->GetFormatColl(); - - ProgName sStyleName; - SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl ); - bRet = sStyleName.toString() == aTableHeadingName; + for (const SwFormat* pFormat = pTextNode->GetFormatColl(); pFormat && !bRet; + pFormat = pFormat->DerivedFrom()) + { + ProgName sStyleName; + SwStyleNameMapper::FillProgName(pFormat->GetName(), sStyleName, + SwGetPoolIdFromName::TxtColl); + bRet = sStyleName == aTableHeadingName; + } } // tdf#153935 wild guessing for 1st row based on table autoformat @@ -423,6 +453,109 @@ bool lcl_HasPreviousParaSameNumRule(SwTextFrame const& rTextFrame, const SwTextN return bRet; } +// a jump that selects an object, or finds nothing, moves the cursor nowhere and names no node +const SwTextNode* lcl_JumpedToNode(const SwEditShell& rSh, const SwPosition& rBeforeJump) +{ + const SwPosition& rPoint = *rSh.GetCursor_()->GetPoint(); + return rPoint == rBeforeJump ? nullptr : rPoint.GetNode().GetTextNode(); +} + +// the language a paragraph declares for itself, as against the one a run of text sets +LanguageType lcl_GetParagraphLanguage(const SwTextFrame& rFrame, + const TypedWhichId nWhich) +{ + // what is anchored here is tagged inside the paragraph's element and would inherit a + // language put there; the node, not the frame, because a split paragraph is one element + if (!rFrame.GetTextNodeFirst()->GetAnchoredFlys().empty()) + return LANGUAGE_DONTKNOW; + return rFrame.GetTextNodeForParaProps()->GetSwAttrSet().Get(nWhich).GetLanguage(); +} + +// the language a run of text is measured against +LanguageType lcl_GetEnclosingLanguage(const SwTextPaintInfo& rInf, const SwEnhancedPDFState& rState) +{ + const LanguageType nParagraph( + lcl_GetParagraphLanguage(*rInf.GetTextFrame(), rState.m_nLanguageWhich)); + return LANGUAGE_DONTKNOW == nParagraph ? rState.m_eLanguageDefault : nParagraph; +} + +// the StarMath source of the formula a frame holds, empty for anything else +OUString lcl_GetFormulaSource(const SwFlyFrame& rFly) +{ + const SwFrame* pLower = rFly.Lower(); + if (!pLower || !pLower->IsNoTextFrame()) + return OUString(); + + const SwContentNode* pNode = static_cast(pLower)->GetNode(); + SwOLENode* pOLENd = pNode ? const_cast(pNode->GetOLENode()) : nullptr; + if (!pOLENd) + return OUString(); + + const uno::Reference xObj(pOLENd->GetOLEObj().GetOleRef()); + if (!xObj.is() || !SotExchange::IsMath(SvGlobalName(xObj->getClassID()))) + return OUString(); + + OUString aSource; + try + { + // a loaded object has no component to read the source from + const bool bWasLoaded(xObj->getCurrentState() == embed::EmbedStates::LOADED); + if (!svt::EmbeddedObjectRef::TryRunningState(xObj)) + return OUString(); + + // reading it ran the object, and an export leaves the document as it found it + const comphelper::ScopeGuard aRestore([&xObj, bWasLoaded] { + if (bWasLoaded) + xObj->changeState(embed::EmbedStates::LOADED); + }); + + const auto xProps(xObj->getComponent().query()); + if (xProps.is()) + xProps->getPropertyValue(u"Formula"_ustr) >>= aSource; + } + catch (const uno::Exception&) + { + } + return aSource; +} + +// the node a table of contents entry links to, which the mark in its URL names +const SwTextNode* lcl_GetLinkedNode(const SwDoc& rDoc, const SwTextNode& rEntry) +{ + if (!rEntry.HasHints()) + return nullptr; + + const IDocumentMarkAccess& rMarks = *rDoc.getIDocumentMarkAccess(); + const SwpHints& rHints = rEntry.GetSwpHints(); + for (size_t i = 0; i < rHints.Count(); ++i) + { + const SwTextAttr& rHint = *rHints.Get(i); + if (rHint.Which() != RES_TXTATR_INETFMT) + continue; + + const OUString aURL(INetURLObject::decode(rHint.GetINetFormat().GetValue(), + INetURLObject::DecodeMechanism::WithCharset)); + if (!aURL.startsWith("#")) + continue; + + // an entry for a heading names the mark and nothing else; one for a table or a frame + // names its kind after a separator, and reaches no node this can tag + const auto ppMark = rMarks.findMark(SwMarkName(aURL.copy(1))); + if (ppMark != rMarks.getAllMarksEnd()) + return (*ppMark)->GetMarkStart().GetNode().GetTextNode(); + } + return nullptr; +} + +// a destination is made before its target is tagged, so remember the node +void lcl_RememberDestNode(vcl::PDFExtOutDevData& rPDFExtOutDevData, const SwTextNode* pTextNode, + const sal_Int32 nDestId) +{ + SwEnhancedPDFState* pState = rPDFExtOutDevData.GetSwPDFState(); + if (pState && nDestId >= 0 && pTextNode) + pState->m_DestNodeMap.emplace(nDestId, pTextNode); +} + bool lcl_TryMoveToNonHiddenField(SwEditShell& rShell, const SwTextNode& rNd, const SwFormatField& rField) { // 1. Check if the whole paragraph is hidden @@ -482,8 +615,7 @@ SwTaggedPDFHelper::SwTaggedPDFHelper( const Num_Info* pNumInfo, const Frame_Info* pFrameInfo, const Por_Info* pPorInfo, OutputDevice const & rOut ) - : m_nEndStructureElement( 0 ), - m_nRestoreCurrentTag( -1 ), + : m_nRestoreCurrentTag( -1 ), mpNumInfo( pNumInfo ), mpFrameInfo( pFrameInfo ), mpPorInfo( pPorInfo ) @@ -623,7 +755,7 @@ void SwTaggedPDFHelper::OpenTagImpl(void const*const pKey) { sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(pKey); mpPDFExtOutDevData->BeginStructureElement(id); - ++m_nEndStructureElement; + m_aOpenedTags.push_back(id); #if OSL_DEBUG_LEVEL > 1 aStructStack.push_back( 99 ); @@ -637,7 +769,7 @@ sal_Int32 SwTaggedPDFHelper::BeginTagImpl(void const*const pKey, const sal_Int32 nId = mpPDFExtOutDevData->EnsureStructureElement(pKey); mpPDFExtOutDevData->InitStructureElement(nId, eType, rString); mpPDFExtOutDevData->BeginStructureElement(nId); - ++m_nEndStructureElement; + m_aOpenedTags.push_back(nId); #if OSL_DEBUG_LEVEL > 1 aStructStack.push_back( o3tl::narrowing(eType) ); @@ -676,6 +808,16 @@ void SwTaggedPDFHelper::BeginTag(vcl::pdf::StructElement eType, const OUString& sal_Int32 const nId = BeginTagImpl(pKey, eType, rString); + // which tag a destination pointing at this node names + if (mpFrameInfo && mpFrameInfo->mrFrame.IsTextFrame()) + { + const SwTextFrame& rTextFrame(static_cast(mpFrameInfo->mrFrame)); + if (const SwTextNode* pTextNode = rTextFrame.GetTextNodeFirst()) + { + mpPDFExtOutDevData->GetSwPDFState()->m_NodeTagIdMap.emplace(pTextNode, nId); + } + } + // Store the id of the current structure element if // - it is a list structure element // - it is a list body element with children @@ -714,20 +856,99 @@ void SwTaggedPDFHelper::EndTag() #endif } +// let a following portion merge into or close it +void SwTaggedPDFHelper::DeferTag() +{ + assert(!m_aOpenedTags.empty()); + assert(mpPDFExtOutDevData->GetSwPDFState()->m_DeferredTags.size() <= 1); // a span in a link + assert(mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan + || mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink); + mpPDFExtOutDevData->GetSwPDFState()->m_DeferredTags.push_back(m_aOpenedTags.back()); + m_aOpenedTags.pop_back(); +} + +bool SwTaggedPDFHelper::IsDeferredTagCurrent() const +{ + const auto& rDeferredTags(mpPDFExtOutDevData->GetSwPDFState()->m_DeferredTags); + return !rDeferredTags.empty() + && rDeferredTags.back() == mpPDFExtOutDevData->GetCurrentStructureElement(); +} + +void SwTaggedPDFHelper::EndDeferredTag() +{ + assert(IsDeferredTagCurrent()); + auto& rState(*mpPDFExtOutDevData->GetSwPDFState()); + // one deferred tag per open span or link + assert(rState.m_DeferredTags.size() + == (rState.m_oCurrentSpan ? 1u : 0u) + (rState.m_oCurrentLink ? 1u : 0u)); + // a span is inside the link, so it is the innermost of the two + if (rState.m_oCurrentSpan) + { + rState.m_oCurrentSpan.reset(); + } + else + { + rState.m_oCurrentLink.reset(); + } + rState.m_DeferredTags.pop_back(); + EndTag(); +} + +// an outer one is not this helper's to close; it outlives the ruby or the fly +void SwTaggedPDFHelper::EndDeferredTags() +{ + while (IsDeferredTagCurrent()) + { + EndDeferredTag(); + } +} + namespace { // link the link annotation to the link structured element void LinkLinkLink(vcl::PDFExtOutDevData & rPDFExtOutDevData, SwRect const& rRect) { + const sal_Int32 nCurrentSE(rPDFExtOutDevData.GetCurrentStructureElement()); + auto& roCurrentLink(rPDFExtOutDevData.GetSwPDFState()->m_oCurrentLink); + if (roCurrentLink && roCurrentLink->nStructElement == -1) + { + // the first call after opening a link is for the link's own SE + roCurrentLink->nStructElement = nCurrentSE; + } + const LinkIdMap& rLinkIdMap(rPDFExtOutDevData.GetSwPDFState()->m_LinkIdMap); const Point aCenter = rRect.Center(); auto aIter = std::find_if(rLinkIdMap.begin(), rLinkIdMap.end(), [&aCenter](const IdMapEntry& rEntry) { return rEntry.first.Contains(aCenter); }); - if (aIter != rLinkIdMap.end()) + if (aIter == rLinkIdMap.end()) + return; + + sal_Int32 nLinkId = (*aIter).second; + // a Link of its own, for a footnote or a fly, is not the link being subdivided + const auto& rDeferredTags(rPDFExtOutDevData.GetSwPDFState()->m_DeferredTags); + const bool bInSpanOfLink(roCurrentLink && rPDFExtOutDevData.GetSwPDFState()->m_oCurrentSpan + && !rDeferredTags.empty() && rDeferredTags.back() == nCurrentSE); + if (roCurrentLink && (roCurrentLink->nStructElement == nCurrentSE || bInSpanOfLink)) { - sal_Int32 nLinkId = (*aIter).second; - rPDFExtOutDevData.SetStructureAttributeNumerical(vcl::pdf::PDFWriter::LinkAnnotation, nLinkId); + // every portion of a line finds that line's annotation again + if (std::find(roCurrentLink->AnnotIds.begin(), roCurrentLink->AnnotIds.end(), nLinkId) + != roCurrentLink->AnnotIds.end()) + { + return; + } + roCurrentLink->AnnotIds.push_back(nLinkId); + if (bInSpanOfLink) + { + // vcl accepts the attribute on a Link, so set it on the link + rPDFExtOutDevData.SetCurrentStructureElement(roCurrentLink->nStructElement); + rPDFExtOutDevData.SetStructureAttributeNumerical( + vcl::pdf::PDFWriter::LinkAnnotation, nLinkId); + rPDFExtOutDevData.SetCurrentStructureElement(nCurrentSE); + return; + } } + rPDFExtOutDevData.SetStructureAttributeNumerical(vcl::pdf::PDFWriter::LinkAnnotation, + nLinkId); } } @@ -758,6 +979,7 @@ void SwTaggedPDFHelper::SetAttributes(vcl::pdf::StructElement eType) bool bBox = false; bool bRowSpan = false; bool bAltText = false; + bool bLanguage = false; // Check which attributes to set: @@ -767,7 +989,7 @@ void SwTaggedPDFHelper::SetAttributes(vcl::pdf::StructElement eType) bWritingMode = true; break; - case vcl::pdf::StructElement::Note: + case vcl::pdf::StructElement::FENote: bPlacement = true; break; @@ -822,7 +1044,8 @@ void SwTaggedPDFHelper::SetAttributes(vcl::pdf::StructElement eType) bStartIndent = bEndIndent = bTextIndent = - bTextAlign = true; + bTextAlign = + bLanguage = true; break; case vcl::pdf::StructElement::Formula: @@ -862,20 +1085,27 @@ void SwTaggedPDFHelper::SetAttributes(vcl::pdf::StructElement eType) if ( bPlacement ) { - bool bIsFigureInline = false; + bool bIsInline = vcl::pdf::StructElement::TableHeader == eType + || vcl::pdf::StructElement::TableData == eType; if (vcl::pdf::StructElement::Figure == eType) { const SwFrame* pKeyFrame = static_cast(*pFrame).GetAnchorFrame(); if (const SwLayoutFrame* pUpperFrame = pKeyFrame->GetUpper()) if (pUpperFrame->GetType() == SwFrameType::Body) - bIsFigureInline = true; + bIsInline = true; + } + else if (vcl::pdf::StructElement::Formula == eType) + { + // a formula anchored in content is tagged inside its anchor paragraph, as + // character by painting in it and otherwise through CheckReopenTag + const RndStdIds eAnchorId( + static_cast(*pFrame).GetFormat()->GetAnchor().GetAnchorId()); + bIsInline = RndStdIds::FLY_AS_CHAR == eAnchorId + || RndStdIds::FLY_AT_PARA == eAnchorId + || RndStdIds::FLY_AT_CHAR == eAnchorId; } - eVal = vcl::pdf::StructElement::TableHeader == eType - || vcl::pdf::StructElement::TableData == eType - || bIsFigureInline - ? vcl::pdf::PDFWriter::Inline - : vcl::pdf::PDFWriter::Block; + eVal = bIsInline ? vcl::pdf::PDFWriter::Inline : vcl::pdf::PDFWriter::Block; mpPDFExtOutDevData->SetStructureAttribute( vcl::pdf::PDFWriter::Placement, eVal ); } @@ -930,6 +1160,18 @@ void SwTaggedPDFHelper::SetAttributes(vcl::pdf::StructElement eType) mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::pdf::PDFWriter::TextIndent, nVal ); } + if (bLanguage && pFrame->IsTextFrame()) + { + const SwEnhancedPDFState& rState(*mpPDFExtOutDevData->GetSwPDFState()); + const LanguageType nLanguage(lcl_GetParagraphLanguage( + static_cast(*pFrame), rState.m_nLanguageWhich)); + if (LANGUAGE_DONTKNOW != nLanguage && rState.m_eLanguageDefault != nLanguage) + { + mpPDFExtOutDevData->SetStructureAttributeNumerical( + vcl::pdf::PDFWriter::Language, static_cast(nLanguage)); + } + } + if ( bTextAlign ) { OSL_ENSURE( pFrame->IsTextFrame(), "Frame type <-> tag attribute mismatch" ); @@ -958,11 +1200,17 @@ void SwTaggedPDFHelper::SetAttributes(vcl::pdf::StructElement eType) // text here again. if (bAltText) { - SwFlyFrameFormat const& rFly(*static_cast(pFrame)->GetFormat()); + SwFlyFrame const& rFlyFrame(*static_cast(pFrame)); + SwFlyFrameFormat const& rFly(*rFlyFrame.GetFormat()); OUString const sep( (rFly.GetObjTitle().isEmpty() || rFly.GetObjDescription().isEmpty()) ? OUString() : u" - "_ustr); - OUString const altText(rFly.GetObjTitle() + sep + rFly.GetObjDescription()); + OUString altText(rFly.GetObjTitle() + sep + rFly.GetObjDescription()); + // a formula nobody described names its own source + if (altText.isEmpty() && eType == vcl::pdf::StructElement::Formula) + { + altText = lcl_GetFormulaSource(rFlyFrame); + } if (!altText.isEmpty()) { mpPDFExtOutDevData->SetAlternateText(altText); @@ -1167,9 +1415,10 @@ void SwTaggedPDFHelper::SetAttributes(vcl::pdf::StructElement eType) { const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage(); - const LanguageType nDefaultLang(mpPDFExtOutDevData->GetSwPDFState()->m_eLanguageDefault); + const LanguageType nEnclosingLanguage( + lcl_GetEnclosingLanguage(rInf, *mpPDFExtOutDevData->GetSwPDFState())); - if ( nDefaultLang != nCurrentLanguage ) + if (nEnclosingLanguage != nCurrentLanguage) mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::pdf::PDFWriter::Language, static_cast(nCurrentLanguage) ); } @@ -1444,12 +1693,9 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() case SwFrameType::Footnote: - // Footnote frame: Note + // Footnote frame: FENote, which the writer names Note below PDF 2.0 - // Note: vcl::pdf::PDFWriter::Note is actually a ILSE. Nevertheless - // we treat it like a grouping element! - nPDFType = sal_uInt16(vcl::pdf::StructElement::Note); - aPDFType = aNoteString; + nPDFType = sal_uInt16(vcl::pdf::StructElement::FENote); break; case SwFrameType::Section : @@ -1695,9 +1941,8 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() break; } - // PDF/UA allows unlimited headings, but PDF only up to H6 - // ... and apparently the extra H7.. must be declared in - // RoleMap, or veraPDF complains. + // PDF/UA allows unlimited headings, but PDF 1.7 only up to H6, so the + // extra H7.. are declared in RoleMap there; PDF 2.0 has them itself. nRealLevel = std::min(nRealLevel, 5); nPDFType = o3tl::narrowing(sal_uInt16(vcl::pdf::StructElement::H1) + nRealLevel); } @@ -1715,7 +1960,13 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() if ( pTOXBase && TOX_INDEX != pTOXBase->GetType() ) { // Special case: Open additional TOCI tag: - BeginTagImpl(nullptr, vcl::pdf::StructElement::TOCI, aTOCIString); + const sal_Int32 nTOCI(BeginTagImpl( + nullptr, vcl::pdf::StructElement::TOCI, u"TOCI"_ustr)); + if (const SwTextNode* pEntryNode = rTextFrame.GetTextNodeFirst()) + { + mpPDFExtOutDevData->GetSwPDFState()->m_TOCItems.emplace_back( + pEntryNode, nTOCI); + } } } } @@ -1876,122 +2127,106 @@ void SwTaggedPDFHelper::EndStructureElements() { if (mpFrameInfo != nullptr) { - if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan) - { // close span at end of paragraph - mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset(); - ++m_nEndStructureElement; - } - if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink) - { // close link at end of paragraph - mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset(); - ++m_nEndStructureElement; - } + EndDeferredTags(); // a span or link of this frame ends with it } - while ( m_nEndStructureElement > 0 ) + while (!m_aOpenedTags.empty()) { + EndDeferredTags(); // one deferred inside this tag ends with it + assert(m_aOpenedTags.back() == mpPDFExtOutDevData->GetCurrentStructureElement()); + m_aOpenedTags.pop_back(); EndTag(); - --m_nEndStructureElement; } CheckRestoreTag(); } -void SwTaggedPDFHelper::EndCurrentLink(OutputDevice const& rOut) -{ - vcl::PDFExtOutDevData *const pPDFExtOutDevData( - dynamic_cast(rOut.GetExtOutDevData())); - if (pPDFExtOutDevData && pPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink) - { - pPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset(); - pPDFExtOutDevData->EndStructureElement(); -#if OSL_DEBUG_LEVEL > 1 - aStructStack.pop_back(); -#endif - } -} - void SwTaggedPDFHelper::EndCurrentAll() { - if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan) + // a ruby is content of the link, so only the span is closed + if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan && IsDeferredTagCurrent()) { - mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset(); - } - if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink) - { - mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset(); + EndCurrentSpan(); } } void SwTaggedPDFHelper::EndCurrentSpan() { - mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset(); - EndTag(); // close span + EndDeferredTag(); +} + +namespace +{ +// the properties a Span tag would carry for this portion +SwEnhancedPDFState::Span MakeSpan(const SwTextPaintInfo& rInf, const OUString& rStyleName) +{ + return { .eUnderline = rInf.GetFont()->GetUnderline(), + .eOverline = rInf.GetFont()->GetOverline(), + .eStrikeout = rInf.GetFont()->GetStrikeout(), + .eFontEmphasis = rInf.GetFont()->GetEmphasisMark(), + .nEscapement = rInf.GetFont()->GetEscapement(), + .nScript = rInf.GetFont()->GetActual(), + .nLang = rInf.GetFont()->GetLanguage(), + .StyleName = rStyleName }; +} } void SwTaggedPDFHelper::CreateCurrentSpan( SwTextPaintInfo const& rInf, OUString const& rStyleName) { assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan); - mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.emplace( - SwEnhancedPDFState::Span{ - rInf.GetFont()->GetUnderline(), - rInf.GetFont()->GetOverline(), - rInf.GetFont()->GetStrikeout(), - rInf.GetFont()->GetEmphasisMark(), - rInf.GetFont()->GetEscapement(), - rInf.GetFont()->GetActual(), - rInf.GetFont()->GetLanguage(), - rStyleName}); - // leave it open to let next portion decide to merge or close - --m_nEndStructureElement; + mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.emplace(MakeSpan(rInf, rStyleName)); } -bool SwTaggedPDFHelper::CheckContinueSpan( - SwTextPaintInfo const& rInf, std::u16string_view const rStyleName, - SwTextAttr const*const pInetFormatAttr) +SwTaggedPDFHelper::Continuation +SwTaggedPDFHelper::CheckContinuation(SwTextPaintInfo const& rInf, OUString const& rStyleName, + SwTextAttr const* const pInetFormatAttr) { - // for now, don't create span inside of link - this should be very rare - // situation and it looks complicated to implement. - assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan - || !mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink); - if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink) + auto& rState(*mpPDFExtOutDevData->GetSwPDFState()); + if (rState.m_oCurrentLink) { - if (pInetFormatAttr && pInetFormatAttr == *mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink) + if (!pInetFormatAttr || pInetFormatAttr != rState.m_oCurrentLink->pAttr) { - return true; + // the span is inside the link, so it closes first + if (rState.m_oCurrentSpan && IsDeferredTagCurrent()) + { + EndCurrentSpan(); + } + if (IsDeferredTagCurrent()) + { + EndDeferredTag(); + } + return Continuation::None; } - else + // the link goes on; a property change subdivides it instead of splitting it + const SwEnhancedPDFState::Span aSpan(MakeSpan(rInf, rStyleName)); + if (rState.m_oCurrentSpan) { - mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset(); - EndTag(); - return false; + if (aSpan == *rState.m_oCurrentSpan) + { + return Continuation::Whole; + } + if (IsDeferredTagCurrent()) + { + EndCurrentSpan(); + } } + return aSpan == rState.m_oCurrentLink->aSpan ? Continuation::Whole + : Continuation::SpanInLink; } - if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan && pInetFormatAttr) + if (!rState.m_oCurrentSpan) { - EndCurrentSpan(); - return false; + return Continuation::None; } - - if (!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan) - return false; - - SwEnhancedPDFState::Span const& rCurrent(*mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan); - - bool const ret(rCurrent.eUnderline == rInf.GetFont()->GetUnderline() - && rCurrent.eOverline == rInf.GetFont()->GetOverline() - && rCurrent.eStrikeout == rInf.GetFont()->GetStrikeout() - && rCurrent.eFontEmphasis == rInf.GetFont()->GetEmphasisMark() - && rCurrent.nEscapement == rInf.GetFont()->GetEscapement() - && rCurrent.nScript == rInf.GetFont()->GetActual() - && rCurrent.nLang == rInf.GetFont()->GetLanguage() - && rCurrent.StyleName == rStyleName); - if (!ret) + if (!pInetFormatAttr && MakeSpan(rInf, rStyleName) == *rState.m_oCurrentSpan) + { + return Continuation::Whole; + } + if (IsDeferredTagCurrent()) // a fly in the span leaves it to the paragraph { EndCurrentSpan(); } - return ret; + return Continuation::None; } void SwTaggedPDFHelper::BeginInlineStructureElements() @@ -2022,11 +2257,18 @@ void SwTaggedPDFHelper::BeginInlineStructureElements() SwStyleNameMapper::FillProgName( pCharFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl ); } + const OUString aStyleName(sStyleName.toString()); // note: ILSE may be nested, so only end the span if needed to start new one - bool const isContinueSpan(CheckContinueSpan(rInf, sStyleName.toString(), pInetFormatAttr)); + const Continuation eContinuation(CheckContinuation(rInf, aStyleName, pInetFormatAttr)); + const sal_Int32 nParentSE(mpPDFExtOutDevData->GetCurrentStructureElement()); + const auto& rDeferredTags(mpPDFExtOutDevData->GetSwPDFState()->m_DeferredTags); + // a span may be deferred inside its link, but not inside a ruby + const bool bCanDefer(rDeferredTags.empty() || rDeferredTags.back() == nParentSE); sal_uInt16 nPDFType = USHRT_MAX; OUString aPDFType; + bool bDeferTag(false); + bool bDeferSpan(false); switch ( pPor->GetWhichPor() ) { @@ -2047,17 +2289,6 @@ void SwTaggedPDFHelper::BeginInlineStructureElements() aPDFType = aSpanString; break; - case PortionType::Fly: - // if a link is split by a fly overlap, then there will be multiple - // annotations for the link, and hence there must be multiple SEs, - // so every annotation has its own SE. - if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink) - { - mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset(); - EndTag(); - } - break; - case PortionType::Lay : case PortionType::Text : case PortionType::Para : @@ -2069,60 +2300,78 @@ void SwTaggedPDFHelper::BeginInlineStructureElements() // Check for Link: if( pInetFormatAttr ) { - if (!isContinueSpan) + if (eContinuation == Continuation::None) { nPDFType = sal_uInt16(vcl::pdf::StructElement::Link); aPDFType = aLinkString; - assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink); - mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.emplace(pInetFormatAttr); - // leave it open to let next portion decide to merge or close - --m_nEndStructureElement; + if (bCanDefer) + { + assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink); + mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.emplace( + SwEnhancedPDFState::Link{ + pInetFormatAttr, -1, {}, MakeSpan(rInf, aStyleName) }); + bDeferTag = true; + } + } + else + { + // attach this portion's annotation + SwRect aPorRect; + rInf.CalcRect(*pPor, &aPorRect); + LinkLinkLink(*mpPDFExtOutDevData, aPorRect); + if (eContinuation == Continuation::SpanInLink) + { + nPDFType = sal_uInt16(vcl::pdf::StructElement::Span); + aPDFType = aSpanString; + bDeferSpan = bCanDefer; + } } } // Emphasis else if (sStyleName == constEmphasisStyleName) { - if (!isContinueSpan) + if (eContinuation == Continuation::None) { nPDFType = sal_uInt16(vcl::pdf::StructElement::Emphasis); aPDFType = constEmphasisStyleName; - CreateCurrentSpan(rInf, sStyleName.toString()); + bDeferSpan = bCanDefer; } } // Strong else if (sStyleName == constStrongEmphasisStyleName) { - if (!isContinueSpan) + if (eContinuation == Continuation::None) { nPDFType = sal_uInt16(vcl::pdf::StructElement::Strong); aPDFType = constStrongEmphasisStyleName; - CreateCurrentSpan(rInf, sStyleName.toString()); + bDeferSpan = bCanDefer; } } // Check for Quote/Code character style: else if (sStyleName == aQuotation) { - if (!isContinueSpan) + if (eContinuation == Continuation::None) { nPDFType = sal_uInt16(vcl::pdf::StructElement::Quote); aPDFType = aQuoteString; - CreateCurrentSpan(rInf, sStyleName.toString()); + bDeferSpan = bCanDefer; } } else if (sStyleName == aSourceText) { - if (!isContinueSpan) + if (eContinuation == Continuation::None) { nPDFType = sal_uInt16(vcl::pdf::StructElement::Code); aPDFType = aCodeString; - CreateCurrentSpan(rInf, sStyleName.toString()); + bDeferSpan = bCanDefer; } } - else if (!isContinueSpan) + else if (eContinuation == Continuation::None) { const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage(); const SwFontScript nFont = rInf.GetFont()->GetActual(); - const LanguageType nDefaultLang(mpPDFExtOutDevData->GetSwPDFState()->m_eLanguageDefault); + const LanguageType nEnclosingLanguage( + lcl_GetEnclosingLanguage(rInf, *mpPDFExtOutDevData->GetSwPDFState())); if ( LINESTYLE_NONE != rInf.GetFont()->GetUnderline() || LINESTYLE_NONE != rInf.GetFont()->GetOverline() || @@ -2130,7 +2379,7 @@ void SwTaggedPDFHelper::BeginInlineStructureElements() FontEmphasisMark::NONE != rInf.GetFont()->GetEmphasisMark() || 0 != rInf.GetFont()->GetEscapement() || SwFontScript::Latin != nFont || - nCurrentLanguage != nDefaultLang || + nCurrentLanguage != nEnclosingLanguage || !sStyleName.isEmpty()) { nPDFType = sal_uInt16(vcl::pdf::StructElement::Span); @@ -2138,7 +2387,7 @@ void SwTaggedPDFHelper::BeginInlineStructureElements() aPDFType = sStyleName.toString(); else aPDFType = aSpanString; - CreateCurrentSpan(rInf, sStyleName.toString()); + bDeferSpan = bCanDefer; } } } @@ -2220,7 +2469,7 @@ void SwTaggedPDFHelper::BeginInlineStructureElements() // for FootnoteNum, is called twice: outer generates Lbl, inner Link case PortionType::FootnoteNum: - assert(!isContinueSpan); // is at start + assert(eContinuation == Continuation::None); // is at start if (mpPorInfo->m_Mode == 0) { // tdf#152218 link both directions nPDFType = sal_uInt16(vcl::pdf::StructElement::Link); @@ -2231,7 +2480,7 @@ void SwTaggedPDFHelper::BeginInlineStructureElements() case PortionType::Number: case PortionType::Bullet: case PortionType::GrfNum: - assert(!isContinueSpan); // is at start + assert(eContinuation == Continuation::None); // is at start if (mpPorInfo->m_Mode == 1) { // only works for multiple lines via wrapper from PaintSwFrame nPDFType = sal_uInt16(vcl::pdf::StructElement::LILabel); @@ -2251,6 +2500,15 @@ void SwTaggedPDFHelper::BeginInlineStructureElements() if ( USHRT_MAX != nPDFType ) { BeginTag( static_cast(nPDFType), aPDFType ); + if (bDeferSpan) + { + CreateCurrentSpan(rInf, aStyleName); + bDeferTag = true; + } + if (bDeferTag) + { + DeferTag(); + } } } @@ -2301,7 +2559,7 @@ SwEnhancedPDFExportHelper::SwEnhancedPDFExportHelper( SwEditShell& rSh, const SvxLanguageItem& rLangItem = mrSh.GetDoc()->GetDefault( nLangRes ); auto const eLanguageDefault = rLangItem.GetLanguage(); - EnhancedPDFExport(eLanguageDefault); + EnhancedPDFExport(eLanguageDefault, nLangRes); } SwEnhancedPDFExportHelper::~SwEnhancedPDFExportHelper() @@ -2343,7 +2601,29 @@ tools::Rectangle SwEnhancedPDFExportHelper::MapSwRectToPDFRect(const SwPageFrame return aRect; } -void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDefault) +sal_Int32 SwEnhancedPDFExportHelper::CreateDestination(const SwPageFrame* pCurrPage, + const SwRect& rRect, + const sal_Int32 nPageNum, + const SwPosition& rBeforeJump) +{ + return CreateDestination(pCurrPage, rRect, nPageNum, lcl_JumpedToNode(mrSh, rBeforeJump)); +} + +sal_Int32 SwEnhancedPDFExportHelper::CreateDestination(const SwPageFrame* pCurrPage, + const SwRect& rRect, + const sal_Int32 nPageNum, + const SwTextNode* pTarget) +{ + assert(dynamic_cast(mrOut.GetExtOutDevData())); + auto& rPDFExtOutDevData = *static_cast(mrOut.GetExtOutDevData()); + const sal_Int32 nDestId + = rPDFExtOutDevData.CreateDest(SwRectToPDFRect(pCurrPage, rRect.SVRect()), nPageNum); + lcl_RememberDestNode(rPDFExtOutDevData, pTarget, nDestId); + return nDestId; +} + +void SwEnhancedPDFExportHelper::EnhancedPDFExport( + LanguageType const eLanguageDefault, const TypedWhichId nLanguageWhich) { vcl::PDFExtOutDevData* pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData*>( mrOut.GetExtOutDevData() ); @@ -2374,7 +2654,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe if ( !mbEditEngineOnly ) { assert(pPDFExtOutDevData->GetSwPDFState() == nullptr); - pPDFExtOutDevData->SetSwPDFState(new SwEnhancedPDFState(eLanguageDefault)); + pPDFExtOutDevData->SetSwPDFState(new SwEnhancedPDFState(eLanguageDefault, nLanguageWhich)); // POSTITS @@ -2497,6 +2777,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe { aURL = aURL.copy( 1 ); mrSh.SwCursorShell::ClearMark(); + const SwPosition aBeforeJump(*mrSh.GetCursor_()->GetPoint()); if (! JumpToSwMark( &mrSh, SwMarkName(aURL) )) { continue; // target deleted @@ -2514,8 +2795,8 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe // Destination Export if ( -1 != nDestPageNum ) { - tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); - nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + nDestId = CreateDestination(pCurrPage, rDestRect, nDestPageNum, + aBeforeJump); } } @@ -2585,6 +2866,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe { aURL = aURL.copy( 1 ); mrSh.SwCursorShell::ClearMark(); + const SwPosition aBeforeJump(*mrSh.GetCursor_()->GetPoint()); if (! JumpToSwMark( &mrSh, SwMarkName(aURL) )) { continue; // target deleted @@ -2601,8 +2883,8 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe // Destination Export if ( -1 != nDestPageNum ) { - tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); - nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + nDestId + = CreateDestination(pCurrPage, rDestRect, nDestPageNum, aBeforeJump); } } @@ -2735,6 +3017,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe // Destination Rectangle const SwGetRefField* pField = static_cast(pFormatField->GetField()); const SwMarkName& rRefName = pField->GetSetRefName(); + const SwPosition aBeforeJump(*mrSh.GetCursor_()->GetPoint()); mrSh.GotoRefMark( rRefName, pField->GetSubType(), pField->GetSeqNo(), pField->GetFlags() ); const SwRect& rDestRect = mrSh.GetCharRect(); @@ -2746,8 +3029,8 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe if ( -1 != nDestPageNum ) { // Destination Export - tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); - const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + const sal_Int32 nDestId + = CreateDestination(pCurrPage, rDestRect, nDestPageNum, aBeforeJump); // #i44368# Links in Header/Footer const bool bHeaderFooter = pDoc->IsInHeaderFooter( *pTNd ); @@ -2765,7 +3048,8 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe for (sal_Int32 aLinkPageNum : aLinkPageNums) { // Link Export - aRect = SwRectToPDFRect(pCurrPage, rLinkRect.SVRect()); + const tools::Rectangle aRect( + SwRectToPDFRect(pCurrPage, rLinkRect.SVRect())); const sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, rRefName.toString(), aLinkPageNum); @@ -2829,6 +3113,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe const SwRect aLinkRect( aTmp[ 0 ] ); // Goto footnote text: + const SwPosition aBeforeFootnote(*mrSh.GetCursor_()->GetPoint()); if ( mrSh.GotoFootnoteText() ) { // Destination Rectangle @@ -2837,8 +3122,6 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe if ( -1 != nDestPageNum ) { const SwPageFrame* pCurrPage = static_cast( mrSh.GetLayout()->Lower() ); - // Destination PageNum - tools::Rectangle aRect = SwRectToPDFRect(pCurrPage, rDestRect.SVRect()); // Back link rectangle calculation const SwPageFrame* fnBodyPage = pCurrPage->getRootFrame()->GetPageByPageNum(nDestPageNum+1); SwRect fnSymbolRect; @@ -2865,16 +3148,19 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe // Export back link const sal_Int32 nBackLinkId = pPDFExtOutDevData->CreateLink(aFootnoteSymbolRect, numStrRef, nDestPageNum); // Destination Export - const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + const sal_Int32 nDestId + = CreateDestination(pCurrPage, rDestRect, nDestPageNum, aBeforeFootnote); + const SwPosition aBeforeAnchor(*mrSh.GetCursor_()->GetPoint()); mrSh.GotoFootnoteAnchor(); // Link PageNums sal_Int32 aLinkPageNum = CalcOutputPageNum( aLinkRect ); pCurrPage = static_cast( mrSh.GetLayout()->Lower() ); // Link Export - aRect = SwRectToPDFRect(pCurrPage, aLinkRect.SVRect()); + const tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, aLinkRect.SVRect())); const sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, numStrSymbol, aLinkPageNum); // Back link destination Export - const sal_Int32 nBackDestId = pPDFExtOutDevData->CreateDest(aRect, aLinkPageNum); + const sal_Int32 nBackDestId + = CreateDestination(pCurrPage, aLinkRect, aLinkPageNum, aBeforeAnchor); // Store link info for tagged pdf output: const IdMapEntry aLinkEntry( aLinkRect, nLinkId ); pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry); @@ -2939,6 +3225,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe const sal_Int32 nParent = aOutlineStack.top().second; // Destination rectangle + const SwPosition aBeforeJump(*mrSh.GetCursor_()->GetPoint()); mrSh.GotoOutline(i); const SwRect& rDestRect = mrSh.GetCharRect(); @@ -2951,9 +3238,8 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe if ( -1 != nDestPageNum ) { // Destination Export - tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); - const sal_Int32 nDestId = - pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + const sal_Int32 nDestId + = CreateDestination(pCurrPage, rDestRect, nDestPageNum, aBeforeJump); // Outline entry text const OUString aEntry = mrSh.getIDocumentOutlineNodesAccess()->getOutlineText( @@ -3080,7 +3366,8 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe if ( bInternal ) { aBookmarkName = aBookmarkName.copy( 1 ); - JumpToSwMark( &mrSh, SwMarkName(aBookmarkName) ); + const SwPosition aBeforeJump(*mrSh.GetCursor_()->GetPoint()); + const bool bJumped = JumpToSwMark(&mrSh, SwMarkName(aBookmarkName)); // Destination Rectangle const SwRect& rDestRect = mrSh.GetCharRect(); @@ -3093,18 +3380,32 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe if ( -1 != nDestPageNum ) { - tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); if ( rBookmark.nLinkId != -1 ) { + const SwTextNode* pTarget = lcl_JumpedToNode(mrSh, aBeforeJump); + if (!pTarget && bJumped) + { + // this pass can begin with the cursor on the target already, where a + // successful jump shows no move, and the mark names its own node + const IDocumentMarkAccess& rMarks = *pDoc->getIDocumentMarkAccess(); + const auto ppMark = rMarks.findMark(SwMarkName(INetURLObject::decode( + aBookmarkName, INetURLObject::DecodeMechanism::WithCharset))); + if (ppMark != rMarks.getAllMarksEnd()) + pTarget = (*ppMark)->GetMarkStart().GetNode().GetTextNode(); + } + // Destination Export - const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + const sal_Int32 nDestId + = CreateDestination(pCurrPage, rDestRect, nDestPageNum, pTarget); // Connect Link and Destination: pPDFExtOutDevData->SetLinkDest( rBookmark.nLinkId, nDestId ); } else { - pPDFExtOutDevData->DescribeRegisteredDest(rBookmark.nDestId, aRect, nDestPageNum); + pPDFExtOutDevData->DescribeRegisteredDest( + rBookmark.nDestId, SwRectToPDFRect(pCurrPage, rDestRect.SVRect()), + nDestPageNum); } } } @@ -3113,6 +3414,33 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe } rBookmarks.clear(); assert(pPDFExtOutDevData->GetSwPDFState()); + assert(pPDFExtOutDevData->GetSwPDFState()->m_DeferredTags.empty()); + + // everything is tagged now, so each destination can name its element + SwEnhancedPDFState& rState(*pPDFExtOutDevData->GetSwPDFState()); + for (const auto& rDest : rState.m_DestNodeMap) + { + const auto it(rState.m_NodeTagIdMap.find(rDest.second)); + if (it != rState.m_NodeTagIdMap.end()) + { + pPDFExtOutDevData->SetDestStructureElement(rDest.first, it->second); + } + } + + // ISO 14289-2 8.2.5.8: a TOC item names the heading its entry links to + for (const auto& [pEntry, nItem] : rState.m_TOCItems) + { + const SwTextNode* pLinked = lcl_GetLinkedNode(*pDoc, *pEntry); + if (!pLinked) + continue; + + const auto it(rState.m_NodeTagIdMap.find(pLinked)); + if (it != rState.m_NodeTagIdMap.end()) + { + pPDFExtOutDevData->AddStructureRef(nItem, it->second); + } + } + delete pPDFExtOutDevData->GetSwPDFState(); pPDFExtOutDevData->SetSwPDFState(nullptr); } @@ -3161,9 +3489,10 @@ void SwEnhancedPDFExportHelper::ExportAuthorityEntryLinks() // 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(&rCurrentNode)->GetText(); + auto& rTextNode = static_cast(rCurrentNode); + const sal_Int32 nDestId + = CreateDestination(pCurrPage, rDestRect, nDestPageNum, &rTextNode); + const OUString* vNodeText = &rTextNode.GetText(); vDestinations.emplace_back(pIteratedTOX, vNodeText, nDestId); } } diff --git a/sw/source/core/text/guess.cxx b/sw/source/core/text/guess.cxx index cb150b56b8d7..ff1ccc70e2f3 100644 --- a/sw/source/core/text/guess.cxx +++ b/sw/source/core/text/guess.cxx @@ -817,6 +817,7 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, // If a field has been expanded, we do not want to delete any // blanks inside the field portion. This would cause an unwanted // underflow + TextFrameIndex nOriginalX = m_nBreakPos; TextFrameIndex nX = m_nBreakPos; while (nX > rInf.GetLineStart() && (CH_TXTATR_BREAKWORD != cFieldChr || nX > rInf.GetIdx()) @@ -826,6 +827,13 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, m_nBreakPos = nX; } + // tdf#173348: Do not delete blanks at the end of a line which only + // contains blanks. This also causes unwanted underflow. + if (m_nBreakPos == rInf.GetLineStart()) + { + m_nBreakPos = nOriginalX; + } + if( m_nBreakPos > rInf.GetIdx() ) nPorLen = m_nBreakPos - rInf.GetIdx(); } diff --git a/sw/source/core/text/itrpaint.cxx b/sw/source/core/text/itrpaint.cxx index 9b420a0d70d4..b8ac8567edaa 100644 --- a/sw/source/core/text/itrpaint.cxx +++ b/sw/source/core/text/itrpaint.cxx @@ -263,8 +263,6 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, roTaggedParagraph.emplace(nullptr, &aFrameInfo, nullptr, *GetInfo().GetOut()); } - SwTaggedPDFHelper::EndCurrentLink(*GetInfo().GetOut()); - // Optimization! SwTwips nMaxRight = std::min( rPaint.Right(), Right() ); const SwTwips nTmpLeft = GetInfo().X(); diff --git a/sw/source/core/text/porexp.cxx b/sw/source/core/text/porexp.cxx index eb6dd2719fe6..cbc1c9cf1bb6 100644 --- a/sw/source/core/text/porexp.cxx +++ b/sw/source/core/text/porexp.cxx @@ -204,7 +204,16 @@ bool SwBlankPortion::Format( SwTextFormatInfo &rInf ) oTextSlot.emplace(&rInf, this, true, false); } - const bool bFull = rInf.IsUnderflow() || SwTextPortion::Format(rInf); + bool bFull = rInf.IsUnderflow(); + if (!bFull) + { + // format the character that will be painted + std::optional oShownChar; + if (!oTextSlot && ShowsOtherChar(rInf)) + oShownChar.emplace(&rInf, this, true, false); + bFull = SwTextPortion::Format(rInf); + } + if( bFull && MayUnderflow( rInf, rInf.GetIdx(), rInf.IsUnderflow() ) ) { Truncate(); @@ -215,12 +224,38 @@ bool SwBlankPortion::Format( SwTextFormatInfo &rInf ) return bFull; } +bool SwBlankPortion::ShowsOtherChar(const SwTextSizeInfo& rInf) const +{ + // a bracket of SwDoubleLinePortion and a field's hook char stand for no model character + return m_bMulti || !GetLen() || m_cChar != rInf.GetChar(rInf.GetIdx()); +} + +SwPositiveSize SwBlankPortion::GetTextSize(const SwTextSizeInfo& rInf) const +{ + std::optional oShownChar; + if (ShowsOtherChar(rInf)) + oShownChar.emplace(&rInf, this, true, false); + return SwTextPortion::GetTextSize(rInf); +} + void SwBlankPortion::Paint( const SwTextPaintInfo &rInf ) const { // Draw field shade (can be disabled individually) if (!m_bMulti) // No gray background for multiportion brackets rInf.DrawViewOpt(*this, PortionType::Blank); - SwTextPortion::Paint(rInf); + + // a field's hook char has no length, but a width and a character to draw + if (GetLen() || Width()) + { + PaintDecorations(rInf); + + std::optional oShownChar; + if (ShowsOtherChar(rInf)) + oShownChar.emplace(&rInf, this, true, true); + + // the slot remaps the other two lists, but not the wrong one + PaintText(rInf, !oShownChar && rInf.GetpWrongList() != nullptr); + } if (rInf.GetOpt().IsViewMetaChars() && rInf.GetOpt().IsHardBlank()) { diff --git a/sw/source/core/text/porexp.hxx b/sw/source/core/text/porexp.hxx index 22b1347f78c3..0eea38e2ecde 100644 --- a/sw/source/core/text/porexp.hxx +++ b/sw/source/core/text/porexp.hxx @@ -43,6 +43,10 @@ class SwBlankPortion final : public SwTextPortion { sal_Unicode m_cChar; bool m_bMulti; // For multiportion brackets + + /// Whether the shown character differs from the model one: a hyphen shows a plain '-' + bool ShowsOtherChar(const SwTextSizeInfo& rInf) const; + public: SwBlankPortion( sal_Unicode cCh, bool bMult = false ) : m_cChar( cCh ), m_bMulti( bMult ) @@ -52,6 +56,7 @@ public: virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override; virtual void FormatEOL( SwTextFormatInfo &rInf ) override; virtual bool Format( SwTextFormatInfo &rInf ) override; + virtual SwPositiveSize GetTextSize(const SwTextSizeInfo& rInf) const override; virtual void Paint( const SwTextPaintInfo &rInf ) const override; static sal_uInt16 MayUnderflow(const SwTextFormatInfo &rInf, TextFrameIndex nIdx, bool bUnderflow ); diff --git a/sw/source/core/text/portxt.cxx b/sw/source/core/text/portxt.cxx index 2432bf6f023b..1d0f6b54f133 100644 --- a/sw/source/core/text/portxt.cxx +++ b/sw/source/core/text/portxt.cxx @@ -325,10 +325,37 @@ bool SwTextPortion::IsCompoundSplit( SwTextFormatInfo &rInf, const SwTextGuess & LanguageType aLang = rInf.GetTextFrame()->GetLangOfChar(rInf.GetIdx(), 1, true); - // Hungarian-only yet - if ( LANGUAGE_HUNGARIAN != aLang ) + // is compound-based "smart" hyphenation enabled? + SvxAdjustItem aAdjustItem = + rInf.GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust(); + if ( !aAdjustItem.GetCompoundBased() ) return false; + // need Hunspell dictionaries with compound-based heuristics + // TODO update the list according to the dictionary update + if ( LANGUAGE_ICELANDIC != aLang && + LANGUAGE_DANISH != aLang && + LANGUAGE_DUTCH != aLang && + LANGUAGE_DUTCH_BELGIAN != aLang && + LANGUAGE_ESTONIAN != aLang && + LANGUAGE_FINNISH != aLang && + LANGUAGE_LATVIAN != aLang && + LANGUAGE_GERMAN != aLang && + LANGUAGE_GERMAN_AUSTRIAN != aLang && + LANGUAGE_GERMAN_LIECHTENSTEIN != aLang && + LANGUAGE_GERMAN_LUXEMBOURG != aLang && + LANGUAGE_GERMAN_SWISS != aLang && + LANGUAGE_HUNGARIAN != aLang && + LANGUAGE_MONGOLIAN_CYRILLIC_MONGOLIA != aLang && + LANGUAGE_NORWEGIAN_BOKMAL != aLang && + LANGUAGE_NORWEGIAN_NYNORSK != aLang && + LANGUAGE_SWEDISH != aLang && + LANGUAGE_SWEDISH_FINLAND != aLang && + LANGUAGE_USER_ESPERANTO != aLang ) + { + return false; + } + LanguageTag aLanguageTag(aLang); uno::Reference< linguistic2::XHyphenator > xHyph; @@ -733,6 +760,7 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) // perhaps there is a compound splitting in the same word before the plain hyphenation bool bPossibleCompoundSplit = bOrigHyphenated3 && + aAdjustItem.GetCompoundBased() && pGuess3->HyphWord()->getHyphenationPos() < pGuess2->HyphWord()->getHyphenationPos() && pGuess3->HyphWord()->getWord().equals(pGuess2->HyphWord()->getWord()); @@ -1279,31 +1307,35 @@ SwPositiveSize SwTextPortion::GetTextSize( const SwTextSizeInfo &rInf ) const return aSize; } +void SwTextPortion::PaintDecorations(const SwTextPaintInfo& rInf) const +{ + rInf.DrawBackBrush(*this); + rInf.DrawBorder(*this); + + rInf.DrawCSDFHighlighting(*this); + + // do we have to repaint a post it portion? + if (rInf.OnWin() && mpNextPortion && !mpNextPortion->Width()) + mpNextPortion->PrePaint(rInf, this); +} + +void SwTextPortion::PaintText(const SwTextPaintInfo& rInf, const bool bWrong) const +{ + const bool bGrammarCheck = rInf.GetGrammarCheckList() != nullptr; + const bool bSmartTags = rInf.GetSmartTags() != nullptr; + + if (bWrong || bSmartTags || bGrammarCheck) + rInf.DrawMarkedText(*this, rInf.GetLen(), bWrong, bSmartTags, bGrammarCheck); + else + rInf.DrawText(*this, rInf.GetLen()); +} + void SwTextPortion::Paint( const SwTextPaintInfo &rInf ) const { if( GetLen() ) { - rInf.DrawBackBrush( *this ); - rInf.DrawBorder( *this ); - - rInf.DrawCSDFHighlighting(*this); - - // do we have to repaint a post it portion? - if( rInf.OnWin() && mpNextPortion && !mpNextPortion->Width() ) - mpNextPortion->PrePaint( rInf, this ); - - auto const* pWrongList = rInf.GetpWrongList(); - auto const* pGrammarCheckList = rInf.GetGrammarCheckList(); - auto const* pSmarttags = rInf.GetSmartTags(); - - const bool bWrong = nullptr != pWrongList; - const bool bGrammarCheck = nullptr != pGrammarCheckList; - const bool bSmartTags = nullptr != pSmarttags; - - if ( bWrong || bSmartTags || bGrammarCheck ) - rInf.DrawMarkedText( *this, rInf.GetLen(), bWrong, bSmartTags, bGrammarCheck ); - else - rInf.DrawText( *this, rInf.GetLen() ); + PaintDecorations(rInf); + PaintText(rInf, rInf.GetpWrongList() != nullptr); } } diff --git a/sw/source/core/text/portxt.hxx b/sw/source/core/text/portxt.hxx index a1c5aed4f50a..8dcfc6fae755 100644 --- a/sw/source/core/text/portxt.hxx +++ b/sw/source/core/text/portxt.hxx @@ -36,6 +36,12 @@ class SwTextPortion : public SwLinePortion const sal_Int32 nSpaces, const sal_Int16 nWidthOf10Spaces ); bool Format_( SwTextFormatInfo &rInf ); +protected: + /// Paints what surrounds the text; reads the index, so never under a SwTextSlot + void PaintDecorations(const SwTextPaintInfo& rInf) const; + /// Paints the text; pass false under a SwTextSlot, which does not remap the wrong list + void PaintText(const SwTextPaintInfo& rInf, bool bWrong) const; + public: SwTextPortion(){ SetWhichPor( PortionType::Text ); } static SwTextPortion * CopyLinePortion(const SwLinePortion &rPortion); diff --git a/sw/source/core/text/txtfly.cxx b/sw/source/core/text/txtfly.cxx index 2b848d912263..cc24d1c2ddc9 100644 --- a/sw/source/core/text/txtfly.cxx +++ b/sw/source/core/text/txtfly.cxx @@ -1479,6 +1479,10 @@ SwRect SwTextFly::AnchoredObjToRect( const SwAnchoredObject* pAnchoredObj, // Wrap on both sides up to a frame width of 1.5cm #define FRAME_MAX 850 +// Word gives up a side narrower than 1/4 inch, measured from the column edge, when the +// other side has that much room. With both sides narrow it wraps on both. +#define WORD_TEXT_MIN 360 + css::text::WrapTextMode SwTextFly::GetSurroundForTextWrap( const SwAnchoredObject* pAnchoredObj ) const { const SwFrameFormat* pFormat = pAnchoredObj->GetFrameFormat(); @@ -1553,6 +1557,25 @@ css::text::WrapTextMode SwTextFly::GetSurroundForTextWrap( const SwAnchoredObjec } } + // Ask the format rather than eSurroundForTextWrap, because an optimal wrap + // which the branch above turned into a parallel one measures its sides + // against the paragraph and keeps that result. + if (css::text::WrapTextMode_PARALLEL == rFlyFormat.GetSurround() + && GetMaster()->GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::MS_WORD_COMP_MIN_TEXT_WRAP_GAP)) + { + SwRectFnSet aRectFnSet(*m_pCurrFrame); + const SwRect& rObj(pAnchoredObj->GetObjRectWithSpaces()); + const SwRect& rCol(m_pCurrFrame->getFrameArea()); + const tools::Long nLeft = aRectFnSet.GetLeft(rObj) - aRectFnSet.GetLeft(rCol); + const tools::Long nRight = aRectFnSet.GetRight(rCol) - aRectFnSet.GetRight(rObj); + // If neither side has enough space, keep the wrap mode as PARALLEL + if (nLeft < WORD_TEXT_MIN && nRight >= WORD_TEXT_MIN) + eSurroundForTextWrap = css::text::WrapTextMode_RIGHT; + else if (nRight < WORD_TEXT_MIN && nLeft >= WORD_TEXT_MIN) + eSurroundForTextWrap = css::text::WrapTextMode_LEFT; + } + return eSurroundForTextWrap; } diff --git a/sw/source/core/txtnode/swfont.cxx b/sw/source/core/txtnode/swfont.cxx index 2d4428790f84..77f09a754a60 100644 --- a/sw/source/core/txtnode/swfont.cxx +++ b/sw/source/core/txtnode/swfont.cxx @@ -71,6 +71,23 @@ void SwFont::SetBackColor( std::optional xNewColor ) m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr; } +void SwFont::SetBorder(const SvxBoxItem* pBoxItem) +{ + if(!pBoxItem) return; + SetTopBorder(pBoxItem->GetTop()); + SetBottomBorder(pBoxItem->GetBottom()); + SetRightBorder(pBoxItem->GetRight()); + SetLeftBorder(pBoxItem->GetLeft()); +} + +void SwFont::ClearBorders() +{ + SetTopBorder(nullptr); + SetBottomBorder(nullptr); + SetRightBorder(nullptr); + SetLeftBorder(nullptr); +} + void SwFont::SetTopBorder( const editeng::SvxBorderLine* pTopBorder ) { if( pTopBorder ) @@ -648,14 +665,11 @@ void SwFont::SetDiffFnt( const SfxItemSet *pAttrSet, SetHighlightColor(pItem->GetColor()); if( const SvxBoxItem* pBoxItem = pAttrSet->GetItemIfSet( RES_CHRATR_BOX ) ) { - SetTopBorder(pBoxItem->GetTop()); - SetBottomBorder(pBoxItem->GetBottom()); - SetRightBorder(pBoxItem->GetRight()); - SetLeftBorder(pBoxItem->GetLeft()); - SetTopBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::TOP)); - SetBottomBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::BOTTOM)); - SetRightBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::RIGHT)); - SetLeftBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::LEFT)); + SetBorder(pBoxItem); + SetBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::TOP), + pBoxItem->GetDistance(SvxBoxItemLine::BOTTOM), + pBoxItem->GetDistance(SvxBoxItemLine::RIGHT), + pBoxItem->GetDistance(SvxBoxItemLine::LEFT)); } if( const SvxShadowItem* pShadowItem = pAttrSet->GetItemIfSet( RES_CHRATR_SHADOW ) ) { @@ -818,25 +832,16 @@ SwFont::SwFont( const SwAttrSet* pAttrSet, SetHighlightColor(COL_TRANSPARENT); if( const SvxBoxItem* pBoxItem = pAttrSet->GetItemIfSet( RES_CHRATR_BOX ) ) { - SetTopBorder(pBoxItem->GetTop()); - SetBottomBorder(pBoxItem->GetBottom()); - SetRightBorder(pBoxItem->GetRight()); - SetLeftBorder(pBoxItem->GetLeft()); - SetTopBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::TOP)); - SetBottomBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::BOTTOM)); - SetRightBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::RIGHT)); - SetLeftBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::LEFT)); + SetBorder(pBoxItem); + SetBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::TOP), + pBoxItem->GetDistance(SvxBoxItemLine::BOTTOM), + pBoxItem->GetDistance(SvxBoxItemLine::RIGHT), + pBoxItem->GetDistance(SvxBoxItemLine::LEFT)); } else { - SetTopBorder(nullptr); - SetBottomBorder(nullptr); - SetRightBorder(nullptr); - SetLeftBorder(nullptr); - SetTopBorderDist(0); - SetBottomBorderDist(0); - SetRightBorderDist(0); - SetLeftBorderDist(0); + ClearBorders(); + SetBorderDist(0,0,0,0); } if( const SvxShadowItem* pShadowItem = pAttrSet->GetItemIfSet( RES_CHRATR_SHADOW ) ) diff --git a/sw/source/core/undo/unattr.cxx b/sw/source/core/undo/unattr.cxx index e0ae8801f9a9..2594041cb4af 100644 --- a/sw/source/core/undo/unattr.cxx +++ b/sw/source/core/undo/unattr.cxx @@ -66,7 +66,7 @@ SwUndoFormatAttrHelper::SwUndoFormatAttrHelper(SwFormat& rFormat, bool bSvDrwPt) - : SwClient(&rFormat) + : sw::ClientBase(&rFormat) , m_rFormat(rFormat) , m_bSaveDrawPt(bSvDrwPt) { diff --git a/sw/source/core/unocore/unomapproperties.hxx b/sw/source/core/unocore/unomapproperties.hxx index 0ec513ad13f0..4d18da466b02 100644 --- a/sw/source/core/unocore/unomapproperties.hxx +++ b/sw/source/core/unocore/unomapproperties.hxx @@ -198,6 +198,7 @@ { UNO_NAME_PARA_SCALE_WIDTH_MIN, RES_PARATR_ADJUST, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, MID_SCALE_WIDTH_MIN }, \ { UNO_NAME_PARA_SCALE_WIDTH_MAX, RES_PARATR_ADJUST, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, MID_SCALE_WIDTH_MAX }, \ { UNO_NAME_PARA_COMPOSER, RES_PARATR_ADJUST, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, MID_PARAGRAPH_COMPOSER }, \ + { UNO_NAME_PARA_IS_COMPOUND_BASED, RES_PARATR_ADJUST, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, MID_COMPOUND_BASED }, \ { UNO_NAME_PARA_LINE_NUMBER_COUNT, RES_LINENUMBER, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, MID_LINENUMBER_COUNT }, \ { UNO_NAME_PARA_LINE_NUMBER_START_VALUE, RES_LINENUMBER, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, MID_LINENUMBER_STARTVALUE }, \ { UNO_NAME_PARA_LINE_SPACING, RES_PARATR_LINESPACING, cppu::UnoType::get(), PropertyAttribute::MAYBEVOID, CONVERT_TWIPS }, \ @@ -475,6 +476,7 @@ { UNO_NAME_PARA_SCALE_WIDTH_MIN, RES_PARATR_ADJUST, cppu::UnoType::get(), PROPERTY_NONE, MID_SCALE_WIDTH_MIN},\ { UNO_NAME_PARA_SCALE_WIDTH_MAX, RES_PARATR_ADJUST, cppu::UnoType::get(), PROPERTY_NONE, MID_SCALE_WIDTH_MAX},\ { UNO_NAME_PARA_COMPOSER, RES_PARATR_ADJUST, cppu::UnoType::get(), PROPERTY_NONE, MID_PARAGRAPH_COMPOSER},\ + { UNO_NAME_PARA_IS_COMPOUND_BASED, RES_PARATR_ADJUST, cppu::UnoType::get(), PROPERTY_NONE, MID_COMPOUND_BASED},\ { UNO_NAME_PARA_LINE_NUMBER_COUNT, RES_LINENUMBER, cppu::UnoType::get(), PROPERTY_NONE ,MID_LINENUMBER_COUNT },\ { UNO_NAME_PARA_LINE_NUMBER_START_VALUE, RES_LINENUMBER, cppu::UnoType::get(), PROPERTY_NONE ,MID_LINENUMBER_STARTVALUE},\ { UNO_NAME_PARA_LINE_SPACING, RES_PARATR_LINESPACING, cppu::UnoType::get(),PROPERTY_NONE, CONVERT_TWIPS},\ diff --git a/sw/source/core/unocore/unoobj2.cxx b/sw/source/core/unocore/unoobj2.cxx index e8f019679881..dec3c4d75932 100644 --- a/sw/source/core/unocore/unoobj2.cxx +++ b/sw/source/core/unocore/unoobj2.cxx @@ -1655,7 +1655,7 @@ struct SwXTextRangesImpl final : public SwXTextRanges OWeakObject::release(); } virtual SwUnoCursor* GetCursor() override - { return &(*m_pUnoCursor); }; + { return m_pUnoCursor.get(); }; void MakeRanges(); std::vector< rtl::Reference > m_Ranges; sw::UnoCursorPointer m_pUnoCursor; diff --git a/sw/source/core/unocore/unotext.cxx b/sw/source/core/unocore/unotext.cxx index ebf28f153ae9..911ffdb5fc4f 100644 --- a/sw/source/core/unocore/unotext.cxx +++ b/sw/source/core/unocore/unotext.cxx @@ -881,9 +881,11 @@ SwXText::setString(const OUString& rString) // Only one of the pointers has to be set! bool SwXText::CheckForOwnMember(const SwPaM & rPaM) { - const rtl::Reference< SwXTextCursor > xOwnCursor(createXTextCursor()); - const SwStartNode* pOwnStartNode = - xOwnCursor->GetPaM()->GetPointNode().StartOfSectionNode(); + const SwStartNode* pOwnStartNode = GetStartNode(); + if (!pOwnStartNode) + { + return false; + } SwStartNodeType eSearchNodeType = SwNormalStartNode; switch (m_eType) { diff --git a/sw/source/core/view/printdata.cxx b/sw/source/core/view/printdata.cxx index 4cc427af5b7e..ddcbf759143a 100644 --- a/sw/source/core/view/printdata.cxx +++ b/sw/source/core/view/printdata.cxx @@ -272,12 +272,15 @@ SwPrintUIOptions::SwPrintUIOptions( static constexpr OUString aPrintRangeName( u"PrintContent"_ustr ); uno::Sequence< OUString > aChoices{ SwResId( STR_PRINTOPTUI_PRINTALLPAGES ), SwResId( STR_PRINTOPTUI_PRINTPAGES ), - SwResId( STR_PRINTOPTUI_PRINTSELECTION ) }; - uno::Sequence< sal_Bool > aChoicesDisabled{ false, false, !bHasSelection }; + SwResId( STR_PRINTOPTUI_PRINTSELECTION ), + SwResId( STR_PRINTOPTUI_PRINTCURRENTPAGE ) }; + uno::Sequence< sal_Bool > aChoicesDisabled{ false, false, !bHasSelection, false }; uno::Sequence< OUString > aHelpIds{ u".HelpID:vcl:PrintDialog:PrintContent:RadioButton:0"_ustr, u".HelpID:vcl:PrintDialog:PrintContent:RadioButton:1"_ustr, - u".HelpID:vcl:PrintDialog:PrintContent:RadioButton:2"_ustr }; - uno::Sequence< OUString > aWidgetIds{ u"rbAllPages"_ustr, u"rbRangePages"_ustr, u"rbRangeSelection"_ustr }; + u".HelpID:vcl:PrintDialog:PrintContent:RadioButton:2"_ustr, + u".HelpID:vcl:PrintDialog:PrintContent:RadioButton:3"_ustr + }; + uno::Sequence< OUString > aWidgetIds{ u"rbAllPages"_ustr, u"rbRangePages"_ustr, u"rbRangeSelection"_ustr, u"rbCurrentPage"_ustr }; m_aUIProperties[nIdx++].Value = setChoiceRadiosControlOpt(aWidgetIds, OUString(), aHelpIds, aPrintRangeName, aChoices, 0 /* always default to 'All pages' */, diff --git a/sw/source/filter/md/mdcallbcks.cxx b/sw/source/filter/md/mdcallbcks.cxx index 8589579e1254..db74a16d5706 100644 --- a/sw/source/filter/md/mdcallbcks.cxx +++ b/sw/source/filter/md/mdcallbcks.cxx @@ -83,6 +83,11 @@ int SwMarkdownParser::enter_block_callback(MD_BLOCKTYPE type, void* detail, void parser->StartTable(nRow, nCol); break; } + case MD_BLOCK_ADMONITION: + case MD_BLOCK_BLANK: + case MD_BLOCK_FOOTNOTE_DEF: + case MD_BLOCK_FOOTNOTE_DEF_SECTION: + break; case MD_BLOCK_THEAD: case MD_BLOCK_TBODY: break; @@ -137,6 +142,11 @@ int SwMarkdownParser::leave_block_callback(MD_BLOCKTYPE type, void* /*detail*/, case MD_BLOCK_TABLE: parser->EndTable(); break; + case MD_BLOCK_ADMONITION: + case MD_BLOCK_BLANK: + case MD_BLOCK_FOOTNOTE_DEF: + case MD_BLOCK_FOOTNOTE_DEF_SECTION: + break; case MD_BLOCK_THEAD: case MD_BLOCK_TBODY: break; @@ -216,6 +226,13 @@ int SwMarkdownParser::enter_span_callback(MD_SPANTYPE type, void* detail, void* case MD_SPAN_LATEXMATH: case MD_SPAN_LATEXMATH_DISPLAY: break; + case MD_SPAN_FOOTNOTE_REF: + case MD_SPAN_INS: + case MD_SPAN_MARK: + case MD_SPAN_SPOILER: + case MD_SPAN_SUPERSCRIPT: + case MD_SPAN_SUBSCRIPT: + break; case MD_SPAN_WIKILINK: { const MD_SPAN_WIKILINK_DETAIL* pWikilink @@ -257,6 +274,13 @@ int SwMarkdownParser::leave_span_callback(MD_SPANTYPE type, void* /*detail*/, vo case MD_SPAN_WIKILINK: case MD_SPAN_U: break; + case MD_SPAN_FOOTNOTE_REF: + case MD_SPAN_INS: + case MD_SPAN_MARK: + case MD_SPAN_SPOILER: + case MD_SPAN_SUBSCRIPT: + case MD_SPAN_SUPERSCRIPT: + break; } return 0; } diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 4782ca73759c..e9ac9cfedd3b 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -697,13 +697,13 @@ OString DocxAttributeOutput::convertToOOXMLVertOrient(sal_Int16 nOrient) case text::VertOrientation::LINE_CENTER: return "center"_ostr; case text::VertOrientation::BOTTOM: + case text::VertOrientation::CHAR_BOTTOM: // approximation - nothing compares exactly + case text::VertOrientation::LINE_TOP: // TEXT_LINE inverts the DOCX meaning of 'bottom' return "bottom"_ostr; - case text::VertOrientation::LINE_BOTTOM: - return "outside"_ostr; case text::VertOrientation::TOP: + case text::VertOrientation::CHAR_TOP: + case text::VertOrientation::LINE_BOTTOM: // TEXT_LINE inverts the DOCX meaning of 'top' return "top"_ostr; - case text::VertOrientation::LINE_TOP: - return "inside"_ostr; default: return OString(); } @@ -2280,6 +2280,20 @@ void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, sal_In DoWriteBookmarksStart(m_rFinalBookmarksStart); DoWriteBookmarksEnd(m_rFinalBookmarksEnd); // Write all final bookmarks DoWriteBookmarkEndIfExist(nPos); + + // The run that just ended is the initial of a drop cap: give it its own paragraph. + if (m_oDropCap) + { + // A hyperlink or a structured document tag that is still open here wraps the runs + // of both paragraphs, so the end of the first paragraph would land inside it and + // the file would not open at all. What can be seen from the document is already + // left out in FormatDrop(), and a structured document tag that was kept in a grab + // bag in WriteCollectedRunProperties(), so this is only a backstop. + if (m_nHyperLinkCount.back() > 0 || m_aRunSdt.m_bStartedSdt) + m_oDropCap.reset(); + else + SplitParagraphAtDropCap(); + } } void DocxAttributeOutput::DoWriteBookmarkTagStart(const OUString& bookmarkName) @@ -3477,6 +3491,8 @@ void DocxAttributeOutput::InitCollectedRunProperties() m_pCharLangAttrList = nullptr; m_oFontSize.reset(); m_bCharPostureWritten = false; + m_bCharPositionWritten = false; + m_bCharStyleWritten = false; // Write the elements in the spec order static const sal_Int32 aOrder[] = @@ -3735,6 +3751,32 @@ void DocxAttributeOutput::WriteCollectedRunProperties() m_pSerializer->singleElementNS( XML_w, XML_rFonts, detachFrom( m_pFontsAttrList ) ); } + if (m_oDropCap && !m_oDropCap->bSizeWritten) + { + if (m_aRunSdt.m_oSdtPrToken.has_value()) + { + // A structured document tag begins on this run, and it wraps the runs of both + // paragraphs, so EndRun() will not be able to split the paragraph. Give up the + // drop cap here, while the size can still be left out: an initial written large + // without a frame around it reads as a stray letter in the middle of a + // sentence, which is worse to look at than a paragraph that lost its drop cap. + m_oDropCap.reset(); + } + else + { + // This run is the initial of a drop cap, which Word only renders large if the + // size and the offset of the baseline are spelled out for it. + m_oFontSize = m_oDropCap->nFontSize; + if (!m_bCharPositionWritten) + m_pSerializer->singleElementNS(XML_w, XML_position, FSNS(XML_w, XML_val), + OString::number(m_oDropCap->nPosition)); + if (!m_bCharStyleWritten && !m_oDropCap->aCharStyleId.isEmpty()) + m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), + m_oDropCap->aCharStyleId); + m_oDropCap->bSizeWritten = true; + } + } + if ( m_oFontSize ) { m_pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val), OString::number(*m_oFontSize)); @@ -4671,9 +4713,130 @@ void DocxAttributeOutput::EndRedline(const SwRedlineData* pRedlineData, bool bPa EndRedline(pRedlineData->Next()); } -void DocxAttributeOutput::FormatDrop( const SwTextNode& /*rNode*/, const SwFormatDrop& /*rSwFormatDrop*/, sal_uInt16 /*nStyle*/, ww8::WW8TableNodeInfo::Pointer_t /*pTextNodeInfo*/, ww8::WW8TableNodeInfoInner::Pointer_t ) +void DocxAttributeOutput::FormatDrop( const SwTextNode& rNode, const SwFormatDrop& rSwFormatDrop, sal_uInt16 nStyle, ww8::WW8TableNodeInfo::Pointer_t /*pTextNodeInfo*/, ww8::WW8TableNodeInfoInner::Pointer_t ) { - SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::FormatDrop( const SwTextNode& rNode, const SwFormatDrop& rSwFormatDrop, sal_uInt16 nStyle )" ); + // Word frames the initial in a paragraph of its own, followed by a paragraph with + // the rest of the text, so remember what it takes to write that frame. The split + // itself has to wait until the initial's run is closed, in EndRun(). + const sal_Int32 nDropLen + = rSwFormatDrop.GetWholeWord() ? rNode.GetDropLen(0) : rSwFormatDrop.GetChars(); + if (nDropLen <= 0 || nDropLen >= rNode.GetText().getLength()) + { + // Nothing would be left for the second paragraph, and reading such a file back + // would take the following paragraph's text into the frame instead. + return; + } + + // A hyperlink, a content control or a ruby that begins in the initial and reaches past + // it wraps runs of both paragraphs, so the end of the first paragraph would land inside + // it and the file would not open at all. Leave the drop cap out in that case. + if (const SwpHints* pHints = rNode.GetpSwpHints()) + { + for (size_t i = 0; i < pHints->Count(); ++i) + { + const SwTextAttr* pHint = pHints->Get(i); + if (pHint->Which() != RES_TXTATR_INETFMT + && pHint->Which() != RES_TXTATR_CONTENTCONTROL + && pHint->Which() != RES_TXTATR_CJK_RUBY) + continue; + const sal_Int32* pEnd = pHint->End(); + if (pHint->GetStart() < nDropLen && pEnd && *pEnd > nDropLen) + return; + } + } + + // Word does not enlarge the initial on its own, it expects the sizes that the + // layout came up with. + int nFontHeight = 0; + int nDropHeight = 0; + int nDropDescent = 0; + rNode.GetDropSize(nFontHeight, nDropHeight, nDropDescent); + if (nDropHeight <= 0) + { + // GetDropSize guesses the same sizes itself when the layout has not measured an + // initial, but it can still hand back a height of zero, and a line height of + // zero together with the line rule "exact" would make the line collapse. + nFontHeight = rNode.GetSwAttrSet().Get(RES_CHRATR_FONTSIZE).GetHeight(); + nDropHeight = rSwFormatDrop.GetLines() * nFontHeight; + nDropDescent = nFontHeight / 5; + } + + DropCapInfo aDropCap; + aDropCap.aStyleId = m_rExport.m_pStyles->GetStyleId(nStyle); + // The frame paragraph carries the paragraph style, so a list that comes with the + // style would put a number in front of the initial as well as in front of the text. + const SwTextFormatColl* pTextColl = rNode.GetTextColl(); + aDropCap.bStyleIsNumbered = pTextColl && !pTextColl->GetNumRule().GetValue().isEmpty(); + if (const SwCharFormat* pCharFormat = rSwFormatDrop.GetCharFormat()) + aDropCap.aCharStyleId = m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pCharFormat)); + aDropCap.nLines = rSwFormatDrop.GetLines(); + aDropCap.nDistance = rSwFormatDrop.GetDistance(); + aDropCap.nLineHeight = nDropHeight; + aDropCap.nFontSize = nFontHeight / 10; + aDropCap.nPosition = -((aDropCap.nLines - 1) * nDropDescent) / 10; + m_oDropCap = aDropCap; +} + +void DocxAttributeOutput::SplitParagraphAtDropCap() +{ + const DropCapInfo& rDropCap = *m_oDropCap; + + // The initial's run is in this mark already, so the properties of the paragraph + // framing it have to be prepended. + m_pSerializer->mark(Tag_DropCapParagraphProperties); + m_pSerializer->startElementNS(XML_w, XML_pPr); + if (!rDropCap.aStyleId.isEmpty()) + m_pSerializer->singleElementNS(XML_w, XML_pStyle, FSNS(XML_w, XML_val), rDropCap.aStyleId); + + rtl::Reference pFramePrAttrList + = sax_fastparser::FastSerializerHelper::createAttrList(); + pFramePrAttrList->add(FSNS(XML_w, XML_dropCap), "drop"); + pFramePrAttrList->add(FSNS(XML_w, XML_lines), OString::number(rDropCap.nLines)); + if (rDropCap.nDistance) + pFramePrAttrList->add(FSNS(XML_w, XML_hSpace), OString::number(rDropCap.nDistance)); + pFramePrAttrList->add(FSNS(XML_w, XML_wrap), "around"); + pFramePrAttrList->add(FSNS(XML_w, XML_vAnchor), "text"); + pFramePrAttrList->add(FSNS(XML_w, XML_hAnchor), "text"); + m_pSerializer->singleElementNS(XML_w, XML_framePr, pFramePrAttrList); + + if (rDropCap.bStyleIsNumbered) + { + // Take the frame paragraph out of the list again: the number belongs in front of + // the text, which is in the paragraph that follows. A numbering id of zero is how + // OOXML says that a paragraph is in no list at all. + m_pSerializer->startElementNS(XML_w, XML_numPr); + m_pSerializer->singleElementNS(XML_w, XML_ilvl, FSNS(XML_w, XML_val), "0"); + m_pSerializer->singleElementNS(XML_w, XML_numId, FSNS(XML_w, XML_val), "0"); + m_pSerializer->endElementNS(XML_w, XML_numPr); + } + + m_pSerializer->singleElementNS(XML_w, XML_spacing, + FSNS(XML_w, XML_line), OString::number(rDropCap.nLineHeight), + FSNS(XML_w, XML_lineRule), "exact"); + + m_pSerializer->startElementNS(XML_w, XML_rPr); + if (!rDropCap.aCharStyleId.isEmpty()) + m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), + rDropCap.aCharStyleId); + m_pSerializer->singleElementNS(XML_w, XML_position, FSNS(XML_w, XML_val), + OString::number(rDropCap.nPosition)); + m_pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val), + OString::number(rDropCap.nFontSize)); + m_pSerializer->endElementNS(XML_w, XML_rPr); + + m_pSerializer->endElementNS(XML_w, XML_pPr); + m_pSerializer->mergeTopMarks(Tag_DropCapParagraphProperties, + sax_fastparser::MergeMarks::PREPEND); + + // Close the initial's paragraph and open the one that takes the rest of the text. + // The paragraph properties are written at the end of the text node and prepended, + // so they land in this second paragraph, where they belong. + m_pSerializer->endElementNS(XML_w, XML_p); + m_pSerializer->mergeTopMarks(Tag_StartParagraph_2); + m_pSerializer->startElementNS(XML_w, XML_p); + m_pSerializer->mark(Tag_StartParagraph_2); + + m_oDropCap.reset(); } void DocxAttributeOutput::ParagraphStyle( sal_uInt16 nStyle ) @@ -8429,6 +8592,7 @@ void DocxAttributeOutput::CharEscapement( const SvxEscapementItem& rEscapement ) float fHeight = rItem.GetHeight(); OString sPos = OString::number( round(( fHeight * nEsc ) / 1000) ); m_pSerializer->singleElementNS(XML_w, XML_position, FSNS(XML_w, XML_val), sPos); + m_bCharPositionWritten = true; if( ( 100 != nProp || sIss.match( "baseline" ) ) && !m_oFontSize ) { @@ -8851,7 +9015,10 @@ void DocxAttributeOutput::TextINetFormat( const SwFormatINetFormat& rLink ) { OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pFormat))); if (!aStyleId.equalsIgnoreAsciiCase("DefaultStyle")) + { m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId); + m_bCharStyleWritten = true; + } } } @@ -8860,6 +9027,7 @@ void DocxAttributeOutput::TextCharFormat( const SwFormatCharFormat& rCharFormat OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(rCharFormat.GetCharFormat()))); m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId); + m_bCharStyleWritten = true; } void DocxAttributeOutput::RefField( const SwField& rField, const OUString& rRef ) @@ -10992,6 +11160,8 @@ DocxAttributeOutput::DocxAttributeOutput( DocxExport &rExport, const FSHelperPtr m_pSerializer( pSerializer ), m_rDrawingML( *pDrawingML ), m_bCharPostureWritten(false), + m_bCharPositionWritten(false), + m_bCharStyleWritten(false), m_bEndCharSdt(false), m_endPageRef( false ), m_pFootnotesList( new ::docx::FootnotesList() ), diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx b/sw/source/filter/ww8/docxattributeoutput.hxx index 0a21f83fe1e3..61846eedfb7e 100644 --- a/sw/source/filter/ww8/docxattributeoutput.hxx +++ b/sw/source/filter/ww8/docxattributeoutput.hxx @@ -493,6 +493,13 @@ private: /// @see InitCollectedRunproperties(), WriteCollectedParagraphProperties() void WriteCollectedRunProperties(); + /// Close the paragraph that holds the drop cap, and open one for the rest of the text. + /// + /// Writer keeps a drop cap as an attribute of the paragraph it starts, while + /// Word gives the initial a framed paragraph of its own, so the paragraph has + /// to be split where the initial ends. + void SplitParagraphAtDropCap(); + /// Output graphic fly frames or replacement graphics for OLE nodes. /// /// For graphic frames, just use the first two parameters, for OLE @@ -859,6 +866,25 @@ private: rtl::Reference m_pHyperlinkAttrList; std::optional m_oFontSize; bool m_bCharPostureWritten; + /// Whether the run already has a w:position, so that a drop cap must not add one. + bool m_bCharPositionWritten; + /// Whether the run already has a w:rStyle, so that a drop cap must not add one. + bool m_bCharStyleWritten; + + /// The drop cap that is waiting for a paragraph of its own. + struct DropCapInfo + { + OString aStyleId; ///< style of the paragraph the initial starts + OString aCharStyleId; ///< character style Writer gives the initial + sal_Int32 nLines; ///< how many lines tall the initial is + sal_Int32 nDistance; ///< space between the initial and the text, in twips + sal_Int32 nLineHeight; ///< height of the initial's line, in twips + sal_Int32 nFontSize; ///< size of the initial, in half-points + sal_Int32 nPosition; ///< how far the initial is lowered, in half-points + bool bStyleIsNumbered = false; ///< whether the paragraph style brings a list with it + bool bSizeWritten = false; ///< whether a run already got the size of the initial + }; + std::optional m_oDropCap; std::shared_ptr m_pContentControl; /// If the current SDT around runs should be ended before the current run. bool m_bEndCharSdt; @@ -1213,6 +1239,7 @@ public: static const sal_Int32 Tag_TableDefinition = 14; static const sal_Int32 Tag_OutputFlyFrame = 15; static const sal_Int32 Tag_StartSection = 16; + static const sal_Int32 Tag_DropCapParagraphProperties = 17; }; /** diff --git a/sw/source/filter/ww8/wrtw8esh.cxx b/sw/source/filter/ww8/wrtw8esh.cxx index 9b8a465b73b5..163976c972f0 100644 --- a/sw/source/filter/ww8/wrtw8esh.cxx +++ b/sw/source/filter/ww8/wrtw8esh.cxx @@ -2574,7 +2574,7 @@ void WinwordAnchoring::SetAnchoring(const SwFrameFormat& rFormat) // #i30669# - convert the positioning attributes. // Most positions are converted, if layout information exists. - const bool bPosConverted = ConvertPosition( rHoriOri, rVertOri, rFormat ); + ConvertPosition(rHoriOri, rVertOri, rFormat); const sal_Int16 eHOri = rHoriOri.GetHoriOrient(); const sal_Int16 eVOri = rVertOri.GetVertOrient(); // #i22673# @@ -2610,9 +2610,8 @@ void WinwordAnchoring::SetAnchoring(const SwFrameFormat& rFormat) // #i22673# // When adjustment is vertically relative to line or to char // bottom becomes top and vice versa - const bool bVertSwap = !bPosConverted && - ( (eVRel == text::RelOrientation::CHAR) || - (eVRel == text::RelOrientation::TEXT_LINE) ); + const bool bVertSwap + = eVRel == text::RelOrientation::CHAR || eVRel == text::RelOrientation::TEXT_LINE; switch (eVOri) { default: diff --git a/sw/source/filter/ww8/ww8par.cxx b/sw/source/filter/ww8/ww8par.cxx index 7d5ba5770f58..4eca952761c5 100644 --- a/sw/source/filter/ww8/ww8par.cxx +++ b/sw/source/filter/ww8/ww8par.cxx @@ -1954,6 +1954,8 @@ void SwWW8ImplReader::ImportDop() DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING, true); m_rDoc.getIDocumentSettingAccess().set( DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS, true); + m_rDoc.getIDocumentSettingAccess().set( + DocumentSettingId::MS_WORD_COMP_MIN_TEXT_WRAP_GAP, true); // tdf#128195 m_rDoc.getIDocumentSettingAccess().set( DocumentSettingId::HEADER_SPACING_BELOW_LAST_PARA, true); diff --git a/sw/source/filter/ww8/ww8par6.cxx b/sw/source/filter/ww8/ww8par6.cxx index e18172533706..63a1d03d93c4 100644 --- a/sw/source/filter/ww8/ww8par6.cxx +++ b/sw/source/filter/ww8/ww8par6.cxx @@ -92,6 +92,7 @@ #include #include #include +#include #include #include #include "writerhelper.hxx" @@ -805,6 +806,20 @@ void SwWW8ImplReader::HandleLineNumbering(const wwSection &rSection) aInfo.SetNumType( aNumType ); m_rDoc.SetLineNumberInfo( aInfo ); + + // MS Word never shows line numbering in headers or footers - turn off in LO default styles + SwFormatLineNumber aNoLineNumbering; + aNoLineNumbering.SetCountLines(false); + UIName aUIName + = SwStyleNameMapper::GetUIName(ProgName("Header"), SwGetPoolIdFromName::TxtColl); + SwTextFormatColl* pStyle = m_rDoc.FindTextFormatCollByName(aUIName); + if (pStyle) + pStyle->SetFormatAttr(aNoLineNumbering); + aUIName = SwStyleNameMapper::GetUIName(ProgName("Footer"), SwGetPoolIdFromName::TxtColl); + pStyle = m_rDoc.FindTextFormatCollByName(aUIName); + if (pStyle) + pStyle->SetFormatAttr(aNoLineNumbering); + m_bNoLnNumYet = false; } diff --git a/sw/source/filter/ww8/ww8toolbar.cxx b/sw/source/filter/ww8/ww8toolbar.cxx index 793870754022..3de1a51d5cef 100644 --- a/sw/source/filter/ww8/ww8toolbar.cxx +++ b/sw/source/filter/ww8/ww8toolbar.cxx @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include diff --git a/sw/source/ui/dbui/mmresultdialogs.cxx b/sw/source/ui/dbui/mmresultdialogs.cxx index 686963e742bf..44f5b38baa2e 100644 --- a/sw/source/ui/dbui/mmresultdialogs.cxx +++ b/sw/source/ui/dbui/mmresultdialogs.cxx @@ -595,11 +595,11 @@ IMPL_LINK_NOARG(SwMMResultSaveDialog, SaveOutputHdl_Impl, weld::Button&, void) { uno::Sequence< beans::PropertyValue > aValues { comphelper::makePropertyValue(u"FilterName"_ustr, sFilter) }; - uno::Reference< frame::XStorable > xStore( pTargetView->GetDocShell()->GetModel(), uno::UNO_QUERY); + rtl::Reference pModel = pTargetView->GetDocShell()->GetBaseModel(); ErrCode nErrorCode = ERRCODE_NONE; try { - xStore->storeToURL( sPath, aValues ); + pModel->storeToURL(sPath, aValues); } catch (const task::ErrorCodeIOException& rErrorEx) { @@ -629,11 +629,11 @@ IMPL_LINK_NOARG(SwMMResultSaveDialog, SaveOutputHdl_Impl, weld::Button&, void) pValues[0].Name = "FilterName"; pValues[0].Value <<= pSfxFlt->GetFilterName(); - uno::Reference< frame::XStorable > xStore( pTargetView->GetDocShell()->GetModel(), uno::UNO_QUERY); + rtl::Reference pModel = pTargetView->GetDocShell()->GetBaseModel(); ErrCode nErrorCode = ERRCODE_NONE; try { - xStore->storeToURL( sTargetTempURL, aValues ); + pModel->storeToURL(sTargetTempURL, aValues); } catch (const task::ErrorCodeIOException& rErrorEx) { @@ -717,8 +717,8 @@ IMPL_LINK_NOARG(SwMMResultSaveDialog, SaveOutputHdl_Impl, weld::Button&, void) try { pValues[0].Value <<= sFilter; - uno::Reference< frame::XStorable > xTempStore( xTempDocShell->GetModel(), uno::UNO_QUERY); - xTempStore->storeToURL( sOutPath, aValues ); + rtl::Reference pTempModel = xTempDocShell->GetBaseModel(); + pTempModel->storeToURL(sOutPath, aValues); } catch (const uno::Exception&) { @@ -1111,8 +1111,8 @@ IMPL_LINK_NOARG(SwMMResultEmailDialog, SendDocumentsHdl_Impl, weld::Button&, voi uno::Sequence< beans::PropertyValue > aValues { comphelper::makePropertyValue(u"FilterName"_ustr, pTargetSfxFlt->GetFilterName()) }; - uno::Reference< frame::XStorable > xStore( pTargetView->GetDocShell()->GetModel(), uno::UNO_QUERY); - xStore->storeToURL( sTargetTempURL, aValues ); + rtl::Reference pModel = pTargetView->GetDocShell()->GetBaseModel(); + pModel->storeToURL(sTargetTempURL, aValues); //create the send dialog std::shared_ptr xDlg = std::make_shared(Application::GetDefDialogParent(), *xConfigItem); @@ -1191,8 +1191,8 @@ IMPL_LINK_NOARG(SwMMResultEmailDialog, SendDocumentsHdl_Impl, weld::Button&, voi pFilterValues[nOpt].Value <<= sPassword; } - uno::Reference< frame::XStorable > xTempStore( pTempView->GetDocShell()->GetModel(), uno::UNO_QUERY); - xTempStore->storeToURL( aName.GetValue(), aFilterValues ); + rtl::Reference pTempModel = pTempView->GetDocShell()->GetBaseModel(); + pTempModel->storeToURL(aName.GetValue(), aFilterValues); } xTempDocShell->DoClose(); diff --git a/sw/source/ui/dialog/ascfldlg.cxx b/sw/source/ui/dialog/ascfldlg.cxx index a1871a765473..54e3fde3a3f6 100644 --- a/sw/source/ui/dialog/ascfldlg.cxx +++ b/sw/source/ui/dialog/ascfldlg.cxx @@ -62,7 +62,7 @@ SwAsciiFilterDlg::SwAsciiFilterDlg( weld::Window* pParent, SwDocShell& rDocSh, SvStream* pStream ) : SfxDialogController(pParent, u"modules/swriter/ui/asciifilterdialog.ui"_ustr, u"AsciiFilterDialog"_ustr) , m_bSaveLineStatus(true) - , m_xCharSetLB(new SvxTextEncodingBox(m_xBuilder->weld_combo_box(u"charset"_ustr))) + , m_xEncodingLB(new SvxTextEncodingBox(m_xBuilder->weld_combo_box(u"encoding"_ustr))) , m_xFontFT(m_xBuilder->weld_label(u"fontft"_ustr)) , m_xFontLB(m_xBuilder->weld_combo_box(u"font"_ustr)) , m_xLanguageFT(m_xBuilder->weld_label(u"languageft"_ustr)) @@ -111,7 +111,7 @@ SwAsciiFilterDlg::SwAsciiFilterDlg( weld::Window* pParent, SwDocShell& rDocSh, aOpt.ReadUserData(sAsciiOptions); } - // read the first chars and check the charset, (language - with L&H) + // read the first chars and check the encoding, (language - with L&H) if( pStream ) { char aBuffer[ 4098 ]; @@ -250,10 +250,10 @@ SwAsciiFilterDlg::SwAsciiFilterDlg( weld::Window* pParent, SwDocShell& rDocSh, } // initialize character set - m_xCharSetLB->FillFromTextEncodingTable( pStream != nullptr ); - m_xCharSetLB->SelectTextEncoding( aOpt.GetCharSet() ); + m_xEncodingLB->FillFromTextEncodingTable( pStream != nullptr ); + m_xEncodingLB->SelectTextEncoding( aOpt.GetCharSet() ); - m_xCharSetLB->connect_changed( LINK( this, SwAsciiFilterDlg, CharSetSelHdl )); + m_xEncodingLB->connect_changed( LINK( this, SwAsciiFilterDlg, EncodingSelHdl )); m_xCRLF_RB->connect_toggled( LINK( this, SwAsciiFilterDlg, LineEndHdl )); m_xLF_RB->connect_toggled( LINK( this, SwAsciiFilterDlg, LineEndHdl )); m_xCR_RB->connect_toggled( LINK( this, SwAsciiFilterDlg, LineEndHdl )); @@ -275,7 +275,7 @@ SwAsciiFilterDlg::~SwAsciiFilterDlg() void SwAsciiFilterDlg::FillOptions( SwAsciiOptions& rOptions ) { - sal_uLong nCCode = m_xCharSetLB->GetSelectTextEncoding(); + sal_uLong nCCode = m_xEncodingLB->GetSelectTextEncoding(); OUString sFont; LanguageType nLng = LANGUAGE_SYSTEM; if (m_xFontLB->get_visible()) @@ -354,7 +354,7 @@ void SwAsciiFilterDlg::UpdateIncludeBOMSensitiveState() if (!m_xIncludeBOM_CB->get_visible()) return; - switch (m_xCharSetLB->GetSelectTextEncoding()) + switch (m_xEncodingLB->GetSelectTextEncoding()) { case RTL_TEXTENCODING_UTF8: case RTL_TEXTENCODING_UCS2: @@ -366,7 +366,7 @@ void SwAsciiFilterDlg::UpdateIncludeBOMSensitiveState() } } -IMPL_LINK_NOARG(SwAsciiFilterDlg, CharSetSelHdl, weld::ComboBox&, void) +IMPL_LINK_NOARG(SwAsciiFilterDlg, EncodingSelHdl, weld::ComboBox&, void) { LineEnd eOldEnd = GetCRLF(); std::optional eEnd; @@ -375,7 +375,7 @@ IMPL_LINK_NOARG(SwAsciiFilterDlg, CharSetSelHdl, weld::ComboBox&, void) : LANGUAGE_SYSTEM, nOldLng = nLng; - rtl_TextEncoding nChrSet = m_xCharSetLB->GetSelectTextEncoding(); + rtl_TextEncoding nChrSet = m_xEncodingLB->GetSelectTextEncoding(); if( nChrSet == osl_getThreadTextEncoding() ) eEnd = GetSystemLineEnd(); else diff --git a/sw/source/uibase/app/docsh.cxx b/sw/source/uibase/app/docsh.cxx index 078500d645b0..611cef415c28 100644 --- a/sw/source/uibase/app/docsh.cxx +++ b/sw/source/uibase/app/docsh.cxx @@ -208,6 +208,10 @@ bool SwDocShell::ConvertFrom( SfxMedium& rMedium ) return false; // #129881# return if no reader is found rtl::Reference pStg=pRead->getSotStorageRef(); // #i45333# save sot storage ref in case of recursive calls + // Reading a foreign format starts from the same state as reading our own one, so link updates + // are off until the load settles them. + getEmbeddedObjectContainer().setUserAllowsLinkUpdate(false); + m_xDoc->setDocAccTitle(OUString()); if (const auto pFrame1 = SfxViewFrame::GetFirst(this)) { diff --git a/sw/source/uibase/app/docstyle.cxx b/sw/source/uibase/app/docstyle.cxx index 2f4bf39a038e..53fe33094293 100644 --- a/sw/source/uibase/app/docstyle.cxx +++ b/sw/source/uibase/app/docstyle.cxx @@ -1210,21 +1210,14 @@ std::vector> SwDocStyleSheet::GetItemPresentatio std::vector> aResult; IntlWrapper aIntlWrapper(SvtSysLocale().GetUILanguageTag()); - // Get parent item set for comparison - const SfxItemSet* pParentSet = nullptr; - SfxStyleSheetBase* pParentStyle = nullptr; - if (!GetParent().isEmpty()) - { - pParentStyle = m_pPool->Find(GetParent(), nFamily); - if (pParentStyle) - pParentSet = &pParentStyle->GetItemSet(); - } - if (SfxStyleFamily::Page == nFamily) { if (!pSet) GetItemSet(); + // tdf#172128: use the ItemSet parent instead of the shared object returned by Find(parent) + const SfxItemSet* pParentSet = pSet->GetParent(); + SfxItemIter aIter(*pSet); for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) @@ -1308,6 +1301,7 @@ std::vector> SwDocStyleSheet::GetItemPresentatio GetItemSet(); const SfxItemSet* pCheckSet = pWorkingSet ? pWorkingSet : pSet; + const SfxItemSet* pParentSet = pCheckSet->GetParent(); const drawing::FillStyle eFillStyle(pCheckSet->Get(XATTR_FILLSTYLE).GetValue()); const bool bUseFloatTransparence(pCheckSet->Get(XATTR_FILLFLOATTRANSPARENCE).IsEnabled()); @@ -2714,6 +2708,12 @@ void SwDocStyleSheet::SetPhysical(bool bPhys) m_pColl = nullptr; m_pFrameFormat = nullptr; m_pDesc = nullptr; + // tdf#172128: SwDocStyleSheetPool::Find() reuses one sheet object. Clear the + // cached ItemSet so attributes from the previously represented style cannot + // leak into the next style (and then be exported as direct overrides). + m_aCoreSet.ClearItem(); + m_aCoreSet.SetParent(nullptr); + pSet = nullptr; } } diff --git a/sw/source/uibase/dbui/dbmgr.cxx b/sw/source/uibase/dbui/dbmgr.cxx index 97da302f9843..4ccdbe23f442 100644 --- a/sw/source/uibase/dbui/dbmgr.cxx +++ b/sw/source/uibase/dbui/dbmgr.cxx @@ -1207,8 +1207,7 @@ bool SwDBManager::MergeMailFiles(SwWrtShell& rSourceShell, else { xProgressDlg = std::make_shared(pSourceWindow->GetFrameWeld()); - static_cast(xProgressDlg.get())->set_title( - pSourceDocSh->GetTitle(22)); + xProgressDlg->set_title(pSourceDocSh->GetTitle(22)); } weld::DialogController::runAsync(xProgressDlg, [this, &xProgressDlg](sal_Int32 nResult){ if (nResult == RET_CANCEL) @@ -1364,8 +1363,9 @@ bool SwDBManager::MergeMailFiles(SwWrtShell& rSourceShell, static_cast(xProgressDlg.get())->SetCurrentPosition(nDocNo); else { PrintMonitor *pPrintMonDlg = static_cast(xProgressDlg.get()); - pPrintMonDlg->m_xPrinter->set_label(bNeedsTempFiles - ? aTempFileURL->GetBase() : pSourceDocSh->GetTitle( 2)); + pPrintMonDlg->m_xPrinter->set_label( + bNeedsTempFiles ? aTempFileURL->GetBase() + : pSourceDocSh->GetTitle(SFX_TITLE_FULLNAME)); OUString sStat = SwResId(STR_STATSTR_LETTER) + " " + OUString::number( nDocNo ); pPrintMonDlg->m_xPrintInfo->set_label(sStat); } diff --git a/sw/source/uibase/dbui/mmconfigitem.cxx b/sw/source/uibase/dbui/mmconfigitem.cxx index 7795d45de6c9..efb980f59663 100644 --- a/sw/source/uibase/dbui/mmconfigitem.cxx +++ b/sw/source/uibase/dbui/mmconfigitem.cxx @@ -62,12 +62,12 @@ const char cDataSourceName[] = "DataSource/DataSourceName"; const char cDataTableName[] = "DataSource/DataTableName" ; const char cDataCommandType[] = "DataSource/DataCommandType"; -#define SECURE_PORT 587 -#define DEFAULT_PORT 25 -#define POP_PORT 110 -#define POP_SECURE_PORT 995 -#define IMAP_PORT 143 -#define IMAP_SECURE_PORT 993 +constexpr sal_Int16 SECURE_PORT = 587; +constexpr sal_Int16 DEFAULT_PORT = 25; +constexpr sal_Int16 POP_PORT = 110; +constexpr sal_Int16 POP_SECURE_PORT = 995; +constexpr sal_Int16 IMAP_PORT = 143; +constexpr sal_Int16 IMAP_SECURE_PORT = 993; namespace { @@ -93,7 +93,7 @@ class SwMailMergeConfigItem_Impl : public utl::ConfigItem Reference< XDataSource> m_xSource; SharedConnection m_xConnection; Reference< XColumnsSupplier> m_xColumnsSupplier; - Reference< XResultSet> m_xResultSet; + Reference m_xResultSet; SwDBData m_aDBData; OUString m_sFilter; sal_Int32 m_nResultSetCursorPos; @@ -838,7 +838,7 @@ void SwMailMergeConfigItem::SetCurrentDBData( const SwDBData& rDBData) } } -Reference< XResultSet> const & SwMailMergeConfigItem::GetResultSet() const +Reference const& SwMailMergeConfigItem::GetResultSet() const { if(!m_pImpl->m_xConnection.is() && !m_pImpl->m_aDBData.sDataSource.isEmpty()) { @@ -911,8 +911,8 @@ void SwMailMergeConfigItem::SetFilter(OUString const & rFilter) { xRowProperties->setPropertyValue(u"ApplyFilter"_ustr, Any(!m_pImpl->m_sFilter.isEmpty())); xRowProperties->setPropertyValue(u"Filter"_ustr, Any(m_pImpl->m_sFilter)); - uno::Reference xRowSet( m_pImpl->m_xResultSet, UNO_QUERY_THROW ); - xRowSet->execute(); + assert(m_pImpl->m_xResultSet.is()); + m_pImpl->m_xResultSet->execute(); } catch (const Exception&) { diff --git a/sw/source/uibase/inc/ascfldlg.hxx b/sw/source/uibase/inc/ascfldlg.hxx index fe941fb0395b..82955a53f1cd 100644 --- a/sw/source/uibase/inc/ascfldlg.hxx +++ b/sw/source/uibase/inc/ascfldlg.hxx @@ -35,7 +35,7 @@ class SwAsciiFilterDlg final : public SfxDialogController bool m_bSaveLineStatus; OUString m_sExtraData; - std::unique_ptr m_xCharSetLB; + std::unique_ptr m_xEncodingLB; std::unique_ptr m_xFontFT; std::unique_ptr m_xFontLB; std::unique_ptr m_xLanguageFT; @@ -45,7 +45,7 @@ class SwAsciiFilterDlg final : public SfxDialogController std::unique_ptr m_xLF_RB; std::unique_ptr m_xIncludeBOM_CB; - DECL_LINK(CharSetSelHdl, weld::ComboBox&, void); + DECL_LINK(EncodingSelHdl, weld::ComboBox&, void); DECL_LINK(LineEndHdl, weld::Toggleable&, void); void SetCRLF(LineEnd eEnd); LineEnd GetCRLF() const; diff --git a/sw/source/uibase/inc/conttree.hxx b/sw/source/uibase/inc/conttree.hxx index 2a79c2c1ce3f..301d5312c07b 100644 --- a/sw/source/uibase/inc/conttree.hxx +++ b/sw/source/uibase/inc/conttree.hxx @@ -92,7 +92,7 @@ class SwContentTree final : public SfxListener std::unique_ptr m_xTreeView; SwContentTreeDropTarget m_aDropTargetHelper; - SwNavigationPI* m_pDialog; + SwNavigationPI& m_rDialog; OUString m_sSpace; AutoTimer m_aUpdTimer; AutoTimer m_aOverlayObjectDelayTimer; @@ -176,7 +176,7 @@ class SwContentTree final : public SfxListener void remove(const weld::TreeIter& rIter); - SwNavigationPI* GetParentWindow(); + SwNavigationPI& GetParentWindow(); bool FillTransferData(TransferDataContainer& rTransfer); @@ -234,7 +234,7 @@ class SwContentTree final : public SfxListener DECL_LINK(EditedEntryHdl, const weld::TreeView::IterColText&, bool); public: - SwContentTree(std::unique_ptr xTreeView, SwNavigationPI* pDialog); + SwContentTree(std::unique_ptr xTreeView, SwNavigationPI& rDialog); ~SwContentTree(); SdrObject* GetDrawingObjectsByContent(const SwContent *pCnt); diff --git a/sw/source/uibase/inc/mmconfigitem.hxx b/sw/source/uibase/inc/mmconfigitem.hxx index d1c8513e8839..153de61d606a 100644 --- a/sw/source/uibase/inc/mmconfigitem.hxx +++ b/sw/source/uibase/inc/mmconfigitem.hxx @@ -19,6 +19,7 @@ #ifndef INCLUDED_SW_SOURCE_UIBASE_INC_MMCONFIGITEM_HXX #define INCLUDED_SW_SOURCE_UIBASE_INC_MMCONFIGITEM_HXX +#include #include #include #include @@ -92,7 +93,7 @@ public: css::uno::Reference< css::sdbcx::XColumnsSupplier> const & GetColumnsSupplier(); - css::uno::Reference< css::sdbc::XResultSet> const & GetResultSet() const; + css::uno::Reference const& GetResultSet() const; void DisposeResultSet(); diff --git a/sw/source/uibase/inc/wordcountdialog.hxx b/sw/source/uibase/inc/wordcountdialog.hxx index 1888f92b3702..1e29744e1769 100644 --- a/sw/source/uibase/inc/wordcountdialog.hxx +++ b/sw/source/uibase/inc/wordcountdialog.hxx @@ -16,8 +16,8 @@ * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ -#ifndef INCLUDED_SW_SOURCE_UIBASE_INC_WORDCOUNTDIALOG_HXX -#define INCLUDED_SW_SOURCE_UIBASE_INC_WORDCOUNTDIALOG_HXX + +#pragma once #include #include @@ -76,6 +76,4 @@ public: void SetCounts(const SwDocStat &rCurrCnt, const SwDocStat &rDocStat); }; -#endif - /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/inc/workctrl.hxx b/sw/source/uibase/inc/workctrl.hxx index 88532c5cf368..aa88bf2b7a69 100644 --- a/sw/source/uibase/inc/workctrl.hxx +++ b/sw/source/uibase/inc/workctrl.hxx @@ -16,8 +16,8 @@ * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ -#ifndef INCLUDED_SW_SOURCE_UIBASE_INC_WORKCTRL_HXX -#define INCLUDED_SW_SOURCE_UIBASE_INC_WORKCTRL_HXX + +#pragma once #include #include @@ -186,5 +186,4 @@ private: NavElementBox_Base* m_pBox; }; -#endif /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/shells/basesh.cxx b/sw/source/uibase/shells/basesh.cxx index ce3d0e19947d..ef440ccbbc26 100644 --- a/sw/source/uibase/shells/basesh.cxx +++ b/sw/source/uibase/shells/basesh.cxx @@ -963,8 +963,7 @@ void SwBaseShell::Execute(SfxRequest &rReq) { case FN_REPAGINATE: { - Reference < XModel > xModel = GetView().GetDocShell()->GetModel(); - auto pDoc = comphelper::getFromUnoTunnel(xModel); + rtl::Reference pDoc = GetView().GetDocShell()->GetBaseModel(); pDoc->NotifyRefreshListeners(); rSh.CalcLayout(); } diff --git a/sw/source/uibase/shells/txtnum.cxx b/sw/source/uibase/shells/txtnum.cxx index ed48075144c8..eaf9900e4d72 100644 --- a/sw/source/uibase/shells/txtnum.cxx +++ b/sw/source/uibase/shells/txtnum.cxx @@ -122,6 +122,8 @@ void SwTextShell::ExecEnterNum(SfxRequest &rReq) case FN_NUMBER_BULLETS: case SID_OUTLINE_BULLET: { + const SfxBoolItem* pIsBulletItem = rReq.GetArg(FN_PARAM_2); + const bool bBulletList = pIsBulletItem && pIsBulletItem->GetValue(); SfxItemSet aSet(SfxItemSet::makeFixedSfxItemSet( GetPool() )); SwDocShell* pDocSh = GetView().GetDocShell(); @@ -183,6 +185,24 @@ void SwTextShell::ExecEnterNum(SfxRequest &rReq) aSvxRule.SetLevel( n, aFormat, false ); } aSvxRule.SetFeatureFlag(SvxNumRuleFlags::ENABLE_EMBEDDED_BMP, false); + + } + + // When starting a new list from the bullet dropdown, + // make it a bullet list on every level + if (bBulletList) + { + UIName aBulletCharFormat; + SwStyleNameMapper::FillUIName(SwPoolFormatId::CHR_BULLET_LEVEL, aBulletCharFormat); + SfxAllItemSet aBulletSet(GetPool()); + aBulletSet.Put(SfxStringItem(SID_BULLET_CHAR_FMT, aBulletCharFormat.toString())); + aBulletSet.Put(SvxNumBulletItem(aSvxRule, SID_ATTR_NUMBERING_RULE)); + svx::sidebar::NBOTypeMgrBase* pBulletsTypeMgr = svx::sidebar::NBOutlineTypeMgrFact::CreateInstance(svx::sidebar::NBOType::Bullets); + if (pBulletsTypeMgr) + { + pBulletsTypeMgr->SetItems(&aBulletSet); + pBulletsTypeMgr->ApplyNumRule(aSvxRule, 0, USHRT_MAX); + } } aSet.Put( SvxNumBulletItem( std::move(aSvxRule) ) ); } diff --git a/sw/source/uibase/sidebar/PageHeaderPanel.cxx b/sw/source/uibase/sidebar/PageHeaderPanel.cxx index ab821bedfb3b..ec7d06566804 100644 --- a/sw/source/uibase/sidebar/PageHeaderPanel.cxx +++ b/sw/source/uibase/sidebar/PageHeaderPanel.cxx @@ -84,6 +84,7 @@ PageHeaderPanel::~PageHeaderPanel() mxHeaderSpacingLB.reset(); mxHeaderLayoutLB.reset(); mxHeaderMarginPresetLB.reset(); + maMetricController.dispose(); mxCustomEntry.reset(); } diff --git a/sw/source/uibase/sidebar/TableEditPanel.cxx b/sw/source/uibase/sidebar/TableEditPanel.cxx index de37f4a9d4b3..75407d401a17 100644 --- a/sw/source/uibase/sidebar/TableEditPanel.cxx +++ b/sw/source/uibase/sidebar/TableEditPanel.cxx @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -124,6 +125,14 @@ void TableEditPanel::NotifyItemUpdate(const sal_uInt16 nSID, const SfxItemState } break; } + case SID_ATTR_METRIC: + { + FieldUnit eFieldUnit = ::GetDfltMetric(false); + m_aRowHeightEdit.SetFieldUnit(eFieldUnit); + m_aColumnWidthEdit.SetFieldUnit(eFieldUnit); + m_aLeftSpacingEdit.SetFieldUnit(eFieldUnit); + m_aRightSpacingEdit.SetFieldUnit(eFieldUnit); + } } } @@ -151,6 +160,7 @@ TableEditPanel::TableEditPanel(weld::Widget* pParent, , m_aRightSpacingEdit(m_xBuilder->weld_metric_spin_button(u"rightspace"_ustr, FieldUnit::CM)) , m_xMisc(m_xBuilder->weld_toolbar(u"misc"_ustr)) , m_xMiscDispatch(new ToolbarUnoDispatcher(*m_xMisc, *m_xBuilder, rxFrame)) + , m_aMetricController(SID_ATTR_METRIC, *pBindings, *this) , m_aRowHeightController(SID_ATTR_TABLE_ROW_HEIGHT, *pBindings, *this) , m_aColumnWidthController(SID_ATTR_TABLE_COLUMN_WIDTH, *pBindings, *this) , m_aAlignmentController(SID_ATTR_TABLE_ALIGNMENT, *pBindings, *this) @@ -186,10 +196,10 @@ TableEditPanel::TableEditPanel(weld::Widget* pParent, void TableEditPanel::InitRowHeightToolitem() { - Link aLink = LINK(this, TableEditPanel, RowHeightMofiyHdl); + Link aLink = LINK(this, TableEditPanel, RowHeightModifyHdl); m_aRowHeightEdit.connect_value_changed(aLink); - FieldUnit eFieldUnit = SwModule::get()->GetFieldUnit(); + FieldUnit eFieldUnit = ::GetDfltMetric(false); m_aRowHeightEdit.SetFieldUnit(eFieldUnit); m_aRowHeightEdit.set_min(MINLAY, FieldUnit::TWIP); @@ -200,10 +210,10 @@ void TableEditPanel::InitRowHeightToolitem() void TableEditPanel::InitColumnWidthToolitem() { - Link aLink = LINK(this, TableEditPanel, ColumnWidthMofiyHdl); + Link aLink = LINK(this, TableEditPanel, ColumnWidthModifyHdl); m_aColumnWidthEdit.connect_value_changed(aLink); - FieldUnit eFieldUnit = SwModule::get()->GetFieldUnit(); + FieldUnit eFieldUnit = ::GetDfltMetric(false); m_aColumnWidthEdit.SetFieldUnit(eFieldUnit); m_aColumnWidthEdit.set_min(MINLAY, FieldUnit::TWIP); @@ -230,7 +240,7 @@ void TableEditPanel::InitAlignmentControls() m_xAlignment->append(OUString::number(text::HoriOrientation::NONE), SwResId(STR_TABLE_PANEL_ALIGN_MANUAL)); - FieldUnit eFieldUnit = SwModule::get()->GetFieldUnit(); + FieldUnit eFieldUnit = ::GetDfltMetric(false); m_aLeftSpacingEdit.SetFieldUnit(eFieldUnit); m_aRightSpacingEdit.SetFieldUnit(eFieldUnit); } @@ -274,6 +284,7 @@ TableEditPanel::~TableEditPanel() m_aSetOptimalColumnWidthController.dispose(); m_aDistributeColumnsController.dispose(); m_aMergeCellsController.dispose(); + m_aMetricController.dispose(); } void TableEditPanel::EnableLeftRight(sal_uInt16 nAlignment) @@ -303,7 +314,7 @@ void TableEditPanel::EnableLeftRight(sal_uInt16 nAlignment) m_aRightSpacingEdit.set_sensitive(enableRight); } -IMPL_LINK_NOARG(TableEditPanel, RowHeightMofiyHdl, weld::MetricSpinButton&, void) +IMPL_LINK_NOARG(TableEditPanel, RowHeightModifyHdl, weld::MetricSpinButton&, void) { SwTwips nNewHeight = static_cast( m_aRowHeightEdit.denormalize(m_aRowHeightEdit.get_value(FieldUnit::TWIP))); @@ -314,7 +325,7 @@ IMPL_LINK_NOARG(TableEditPanel, RowHeightMofiyHdl, weld::MetricSpinButton&, void { &aRowHeight }); } -IMPL_LINK_NOARG(TableEditPanel, ColumnWidthMofiyHdl, weld::MetricSpinButton&, void) +IMPL_LINK_NOARG(TableEditPanel, ColumnWidthModifyHdl, weld::MetricSpinButton&, void) { SwTwips nNewWidth = static_cast( m_aColumnWidthEdit.denormalize(m_aColumnWidthEdit.get_value(FieldUnit::TWIP))); diff --git a/sw/source/uibase/sidebar/TableEditPanel.hxx b/sw/source/uibase/sidebar/TableEditPanel.hxx index 4ae01f803039..58543d90da29 100644 --- a/sw/source/uibase/sidebar/TableEditPanel.hxx +++ b/sw/source/uibase/sidebar/TableEditPanel.hxx @@ -68,6 +68,7 @@ private: std::unique_ptr m_xMisc; std::unique_ptr m_xMiscDispatch; + ::sfx2::sidebar::ControllerItem m_aMetricController; ::sfx2::sidebar::ControllerItem m_aRowHeightController; ::sfx2::sidebar::ControllerItem m_aColumnWidthController; ::sfx2::sidebar::ControllerItem m_aAlignmentController; @@ -88,8 +89,8 @@ private: ::sfx2::sidebar::ControllerItem m_aDistributeColumnsController; ::sfx2::sidebar::ControllerItem m_aMergeCellsController; - DECL_LINK(RowHeightMofiyHdl, weld::MetricSpinButton&, void); - DECL_LINK(ColumnWidthMofiyHdl, weld::MetricSpinButton&, void); + DECL_LINK(RowHeightModifyHdl, weld::MetricSpinButton&, void); + DECL_LINK(ColumnWidthModifyHdl, weld::MetricSpinButton&, void); DECL_LINK(AlignmentHdl, weld::ComboBox&, void); DECL_LINK(SpacingHdl, weld::MetricSpinButton&, void); }; diff --git a/sw/source/uibase/sidebar/WriterInspectorTextPanel.cxx b/sw/source/uibase/sidebar/WriterInspectorTextPanel.cxx index 54b80a085da0..352792ec3b75 100644 --- a/sw/source/uibase/sidebar/WriterInspectorTextPanel.cxx +++ b/sw/source/uibase/sidebar/WriterInspectorTextPanel.cxx @@ -267,6 +267,7 @@ static OUString PropertyNametoRID(const OUString& rName) { "ParaBottomMarginRelative", RID_PARA_BOTTOM_MARGIN_RELATIVE }, { "ParaChapterNumberingLevel", RID_PARA_CHAPTER_NUMBERING_LEVEL }, { "ParaComposer", RID_PARA_COMPOSER }, + { "ParaIsCompoundBased", RID_PARA_COMPOUND_BASED_HYPHENATION }, { "ParaConditionalStyleName", RID_PARA_CONDITIONAL_STYLE_NAME }, { "ParaContextMargin", RID_PARA_CONTEXT_MARGIN }, { "ParaExpandSingleWord", RID_PARA_EXPAND_SINGLE_WORD }, diff --git a/sw/source/uibase/uno/SwXDocumentSettings.cxx b/sw/source/uibase/uno/SwXDocumentSettings.cxx index 763750463f0d..05c76612b1a7 100644 --- a/sw/source/uibase/uno/SwXDocumentSettings.cxx +++ b/sw/source/uibase/uno/SwXDocumentSettings.cxx @@ -114,6 +114,7 @@ enum SwDocumentSettingsPropertyHandles HANDLE_USE_OLD_PRINTER_METRICS, HANDLE_PROTECT_FORM, HANDLE_MS_WORD_COMP_TRAILING_BLANKS, + HANDLE_MS_WORD_COMP_MIN_TEXT_WRAP_GAP, HANDLE_MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY, HANDLE_TABS_RELATIVE_TO_INDENT, HANDLE_RSID, @@ -237,6 +238,7 @@ static rtl::Reference lcl_createSettingsInfo() { u"RsidRoot"_ustr, HANDLE_RSID_ROOT, cppu::UnoType::get(), 0}, { u"ProtectForm"_ustr, HANDLE_PROTECT_FORM, cppu::UnoType::get(), 0}, { u"MsWordCompTrailingBlanks"_ustr, HANDLE_MS_WORD_COMP_TRAILING_BLANKS, cppu::UnoType::get(), 0 }, + { u"MsWordCompMinTextWrapGap"_ustr, HANDLE_MS_WORD_COMP_MIN_TEXT_WRAP_GAP, cppu::UnoType::get(), 0 }, { u"MsWordCompMinLineHeightByFly"_ustr, HANDLE_MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY, cppu::UnoType::get(), 0 }, { u"TabAtLeftIndentForParagraphsInList"_ustr, HANDLE_TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST, cppu::UnoType::get(), 0}, { u"ModifyPasswordInfo"_ustr, HANDLE_MODIFYPASSWORDINFO, cppu::UnoType< cppu::UnoSequenceType >::get(), 0}, @@ -825,6 +827,12 @@ void SwXDocumentSettings::_setSingleValue( const comphelper::PropertyInfo & rInf mpDoc->getIDocumentSettingAccess().set(DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS, bTmp); } break; + case HANDLE_MS_WORD_COMP_MIN_TEXT_WRAP_GAP: + { + bool bTmp = *o3tl::doAccess(rValue); + mpDoc->getIDocumentSettingAccess().set(DocumentSettingId::MS_WORD_COMP_MIN_TEXT_WRAP_GAP, bTmp); + } + break; case HANDLE_MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY: { bool bTmp = *o3tl::doAccess(rValue); @@ -1618,6 +1626,11 @@ void SwXDocumentSettings::_getSingleValue( const comphelper::PropertyInfo & rInf rValue <<= mpDoc->getIDocumentSettingAccess().get(DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS); } break; + case HANDLE_MS_WORD_COMP_MIN_TEXT_WRAP_GAP: + { + rValue <<= mpDoc->getIDocumentSettingAccess().get(DocumentSettingId::MS_WORD_COMP_MIN_TEXT_WRAP_GAP); + } + break; case HANDLE_MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY: { rValue <<= mpDoc->getIDocumentSettingAccess().get(DocumentSettingId::MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY); diff --git a/sw/source/uibase/uno/unotxdoc.cxx b/sw/source/uibase/uno/unotxdoc.cxx index 682971d3676e..ece2d5b7a6e6 100644 --- a/sw/source/uibase/uno/unotxdoc.cxx +++ b/sw/source/uibase/uno/unotxdoc.cxx @@ -2591,6 +2591,11 @@ SwDoc * SwXTextDocument::GetRenderDoc( uno::Reference< frame::XModel > xModel; rSelection >>= xModel; + const bool bPrintCurrentPage = m_pPrintUIOptions && m_pPrintUIOptions->getIntValue("PrintContent", 0) == 3; + + if (bPrintCurrentPage) + return m_pDocShell->GetDoc(); + if (xModel == m_pDocShell->GetModel()) pDoc = m_pDocShell->GetDoc(); else diff --git a/sw/source/uibase/utlui/content.cxx b/sw/source/uibase/utlui/content.cxx index aa302fae39ad..fd4b35c9c4ef 100644 --- a/sw/source/uibase/utlui/content.cxx +++ b/sw/source/uibase/utlui/content.cxx @@ -1446,10 +1446,10 @@ const TranslateId STR_CONTEXT_ARY[] = STR_OUTLINE_TRACKING_OFF }; -SwContentTree::SwContentTree(std::unique_ptr xTreeView, SwNavigationPI* pDialog) +SwContentTree::SwContentTree(std::unique_ptr xTreeView, SwNavigationPI& rDialog) : m_xTreeView(std::move(xTreeView)) , m_aDropTargetHelper(*this) - , m_pDialog(pDialog) + , m_rDialog(rDialog) , m_sSpace(u" "_ustr) , m_aUpdTimer("SwContentTree m_aUpdTimer") , m_aOverlayObjectDelayTimer("SwContentTree m_aOverlayObjectDelayTimer") @@ -1671,7 +1671,7 @@ sal_Int8 SwContentTree::AcceptDrop(const AcceptDropEvent& rEvt) nRet = rEvt.mnAction; } else if (!IsInDrag()) - nRet = GetParentWindow()->AcceptDrop(); + nRet = GetParentWindow().AcceptDrop(); return nRet; } @@ -1761,7 +1761,7 @@ sal_Int8 SwContentTree::ExecuteDrop(const ExecuteDropEvent& rEvt) MoveOutline(nTargetPos); } - return IsInDrag() ? DND_ACTION_NONE : GetParentWindow()->ExecuteDrop(rEvt); + return IsInDrag() ? DND_ACTION_NONE : GetParentWindow().ExecuteDrop(rEvt); } namespace @@ -4213,7 +4213,7 @@ void SwContentTree::ToggleToRoot() Display(State::HIDDEN != m_eState); } m_pConfig->SetRootType( m_nRootType ); - weld::Toolbar* pBox = GetParentWindow()->m_xContent5ToolBox.get(); + weld::Toolbar* pBox = GetParentWindow().m_xContent5ToolBox.get(); pBox->set_item_active(u"root"_ustr, m_bIsRoot); } @@ -4506,7 +4506,7 @@ void SwContentTree::SetHiddenShell(SwWrtShell* pSh) } Display(false); - GetParentWindow()->UpdateListBox(); + GetParentWindow().UpdateListBox(); } void SwContentTree::SetActiveShell(SwWrtShell* pSh) @@ -4649,7 +4649,7 @@ void SwContentTree::ExecCommand(std::u16string_view rCmd, bool bOutlineWithChild return; if (GetWrtShell()->GetView().GetDocShell()->IsReadOnly() || (State::ACTIVE != m_eState && - (State::CONSTANT != m_eState || m_pActiveShell != GetParentWindow()->GetCreateView()->GetWrtShellPtr()))) + (State::CONSTANT != m_eState || m_pActiveShell != GetParentWindow().GetCreateView()->GetWrtShellPtr()))) { return; } @@ -5200,7 +5200,7 @@ IMPL_LINK_NOARG(SwContentTree, TimerUpdate, Timer *, void) // No update while focus is not in document. // No update while drag and drop. // Query view because the Navigator is cleared too late. - SwView* pView = GetParentWindow()->GetCreateView(); + SwView* pView = GetParentWindow().GetCreateView(); SwWrtShell* pActShell = pView ? pView->GetWrtShellPtr() : nullptr; if(pActShell && pActShell->GetWin() && @@ -5212,7 +5212,7 @@ IMPL_LINK_NOARG(SwContentTree, TimerUpdate, Timer *, void) if (State::CONSTANT == m_eState && !lcl_FindShell(m_pActiveShell)) { SetActiveShell(pActShell); - GetParentWindow()->UpdateListBox(); + GetParentWindow().UpdateListBox(); } if (State::ACTIVE == m_eState && pActShell != GetWrtShell()) { @@ -5989,7 +5989,7 @@ void SwContentTree::MoveOutline(SwOutlineNodes::size_type nTargetPos) nTargetPos = nPrevTargetPosOrOffset; } } - GetParentWindow()->MoveOutline(nSourcePos, nTargetPos); + GetParentWindow().MoveOutline(nSourcePos, nTargetPos); } pShell->EndUndo(); @@ -6002,7 +6002,7 @@ void SwContentTree::MoveOutline(SwOutlineNodes::size_type nTargetPos) // Update immediately IMPL_LINK_NOARG(SwContentTree, FocusInHdl, weld::Widget&, void) { - SwView* pActView = GetParentWindow()->GetCreateView(); + SwView* pActView = GetParentWindow().GetCreateView(); if(pActView) { SwWrtShell* pActShell = pActView->GetWrtShellPtr(); @@ -6049,7 +6049,7 @@ IMPL_LINK(SwContentTree, KeyInputHdl, const KeyEvent&, rEvent, bool) { case KEY_MOD2: // Switch boxes - GetParentWindow()->ToggleTree(); + GetParentWindow().ToggleTree(); break; case KEY_MOD1: // Switch RootMode @@ -6497,14 +6497,14 @@ void SwContentTree::ExecuteContextMenuAction(const OUString& rSelectedPopupEntry { if (pSection->GetPassword().hasElements() && aSectionData.IsProtectFlag()) { - SfxPasswordDialog aPasswordDlg(m_pDialog->GetFrameWeld()); + SfxPasswordDialog aPasswordDlg(m_rDialog.GetFrameWeld()); if (aPasswordDlg.run() != RET_OK) return; if (!SvPasswordHelper::CompareHashPassword(aSectionData.GetPassword(), aPasswordDlg.GetPassword())) { std::unique_ptr xInfoBox(Application::CreateMessageDialog( - m_pDialog->GetFrameWeld(), VclMessageType::Info, VclButtonsType::Ok, + m_rDialog.GetFrameWeld(), VclMessageType::Info, VclButtonsType::Ok, SwResId(STR_WRONG_PASSWORD))); xInfoBox->run(); return; @@ -6774,7 +6774,7 @@ void SwContentTree::ExecuteContextMenuAction(const OUString& rSelectedPopupEntry m_eState = (nSelectedPopupEntry == 1) ? State::ACTIVE : State::HIDDEN; Display(nSelectedPopupEntry == 1); } - GetParentWindow()->UpdateListBox(); + GetParentWindow().UpdateListBox(); } } } @@ -6882,10 +6882,10 @@ void SwContentTree::ShowHiddenShell() // only called from IMPL_LINK(SwNavigationPI, DocListBoxSelectHdl, weld::ComboBox&, rBox, void) void SwContentTree::ShowActualView() { - if (SwView* pView = m_pDialog->GetCreateView()) + if (SwView* pView = m_rDialog.GetCreateView()) { SetConstantShell(pView->GetWrtShellPtr()); - m_pDialog->UpdateListBox(); + m_rDialog.UpdateListBox(); } } @@ -6905,14 +6905,12 @@ IMPL_LINK_NOARG(SwContentTree, SelectHdl, weld::ItemView&, void) return; while (m_xTreeView->get_iter_depth(*xEntry)) m_xTreeView->iter_parent(*xEntry); - m_pDialog->SelectNavigateByContentType(m_xTreeView->get_text(*xEntry)); + m_rDialog.SelectNavigateByContentType(m_xTreeView->get_text(*xEntry)); } void SwContentTree::UpdateContentFunctionsToolbar() { - SwNavigationPI* pNavi = GetParentWindow(); - if (pNavi) - pNavi->UpdateContentFunctionsToolbar(); + GetParentWindow().UpdateContentFunctionsToolbar(); } void SwContentTree::SetRootType(ContentTypeId nType) @@ -7756,10 +7754,7 @@ bool NaviContentBookmark::Paste( const TransferableDataHelper& rData, const OUSt return bRet; } -SwNavigationPI* SwContentTree::GetParentWindow() -{ - return m_pDialog; -} +SwNavigationPI& SwContentTree::GetParentWindow() { return m_rDialog; } void SwContentTree::SelectContentType(std::u16string_view rContentTypeName) { diff --git a/sw/source/uibase/utlui/navipi.cxx b/sw/source/uibase/utlui/navipi.cxx index 06776fbdee46..84ba21235e71 100644 --- a/sw/source/uibase/utlui/navipi.cxx +++ b/sw/source/uibase/utlui/navipi.cxx @@ -441,7 +441,7 @@ SwNavigationPI::SwNavigationPI(weld::Widget* pParent, , m_xGlobalToolBox(m_xBuilder->weld_toolbar(u"global"_ustr)) , m_xGotoPageSpinButton(m_xBuilder->weld_spin_button(u"gotopage"_ustr)) , m_xContentBox(m_xBuilder->weld_widget(u"contentbox"_ustr)) - , m_xContentTree(new SwContentTree(m_xBuilder->weld_tree_view(u"contenttree"_ustr), this)) + , m_xContentTree(new SwContentTree(m_xBuilder->weld_tree_view(u"contenttree"_ustr), *this)) , m_xGlobalBox(m_xBuilder->weld_widget(u"globalbox"_ustr)) , m_xGlobalTree(new SwGlobalTree(m_xBuilder->weld_tree_view(u"globaltree"_ustr), this)) , m_xDocListBox(m_xBuilder->weld_combo_box(u"documents"_ustr)) diff --git a/sw/source/uibase/wrtsh/wrtsh2.cxx b/sw/source/uibase/wrtsh/wrtsh2.cxx index a15ba6a83f3b..b25834f20ae4 100644 --- a/sw/source/uibase/wrtsh/wrtsh2.cxx +++ b/sw/source/uibase/wrtsh/wrtsh2.cxx @@ -638,7 +638,13 @@ static bool LoadURL(const SfxViewShell& rView, const OUString& rURL, LoadUrlFlag //#39076# Silent can be removed accordingly to SFX. SfxBoolItem aBrowse( SID_BROWSE, true ); - if ((nFilter & LoadUrlFlags::NewView) && !comphelper::LibreOfficeKit::isActive()) + // tdf#138347 - do not open a new window on internal services + const bool bDispatchOnly + = rURL.startsWithIgnoreAsciiCase("vnd.sun.star.script:") + || rURL.startsWithIgnoreAsciiCase("macro:") || rURL.startsWithIgnoreAsciiCase("slot:") + || rURL.startsWithIgnoreAsciiCase("service:") || rURL.startsWithIgnoreAsciiCase(".uno:"); + if ((nFilter & LoadUrlFlags::NewView) && !comphelper::LibreOfficeKit::isActive() + && !bDispatchOnly) aTargetFrameName.SetValue( u"_blank"_ustr ); const SfxPoolItem* aArr[] = { diff --git a/sw/source/writerfilter/dmapper/DomainMapper.cxx b/sw/source/writerfilter/dmapper/DomainMapper.cxx index da3f6fd097f3..01f45fd2d9bc 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper.cxx @@ -243,7 +243,7 @@ DomainMapper::~DomainMapper() } } - mbHasControls |= m_pImpl->m_pSdtHelper->hasElements(); + mbHasControls |= m_pImpl->GetSdtHelper().hasElements(); if ( (nIndexes || mbHasControls) && m_pImpl->GetTextDocument()) { //index update has to wait until first view is created @@ -1297,7 +1297,7 @@ void DomainMapper::lcl_attribute(Id nName, const Value & val) case NS_ooxml::LN_CT_SdtBlock_sdtContent: case NS_ooxml::LN_CT_SdtRun_sdtContent: { - if (m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::unknown) + if (m_pImpl->GetSdtHelper().getControlType() == SdtControlType::unknown) { // If the SdtControlType is not defined, it really OUGHT to be richText // (although the presence of dataBinding gets treated as plainText by MS Word). @@ -1305,17 +1305,17 @@ void DomainMapper::lcl_attribute(Id nName, const Value & val) // so here we just convert blockSDT richText into plainText instead. // Most likely this will just be changed again elsewhere. - m_pImpl->m_pSdtHelper->setControlType(SdtControlType::plainText); + m_pImpl->GetSdtHelper().setControlType(SdtControlType::plainText); } if (nName == NS_ooxml::LN_CT_SdtRun_sdtContent) { - if (m_pImpl->GetSdtStarts().empty() && m_pImpl->m_pSdtHelper->hasUnusedText()) + if (m_pImpl->GetSdtStarts().empty() && m_pImpl->GetSdtHelper().hasUnusedText()) { // A non-inline SDT is already started, first convert that to a field and only // then map the inline SDT to a content control. - if (m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::plainText) + if (m_pImpl->GetSdtHelper().getControlType() == SdtControlType::plainText) { - m_pImpl->m_pSdtHelper->createPlainTextControl(); + m_pImpl->GetSdtHelper().createPlainTextControl(); } } @@ -1363,12 +1363,12 @@ void DomainMapper::lcl_attribute(Id nName, const Value & val) m_pImpl->clearDeferredBreaks(); } - m_pImpl->m_pSdtHelper->SetSdtType(nName); - m_pImpl->m_pSdtHelper->setControlType(SdtControlType::richText); + m_pImpl->GetSdtHelper().SetSdtType(nName); + m_pImpl->GetSdtHelper().setControlType(SdtControlType::richText); m_pImpl->PushSdt(); break; } - m_pImpl->m_pSdtHelper->SetSdtType(nName); + m_pImpl->GetSdtHelper().SetSdtType(nName); m_pImpl->SetSdt(true); } break; @@ -1377,7 +1377,7 @@ void DomainMapper::lcl_attribute(Id nName, const Value & val) if (nName == NS_ooxml::LN_CT_SdtRun_sdtEndContent) { // Inline SDT. - switch (m_pImpl->m_pSdtHelper->getControlType()) + switch (m_pImpl->GetSdtHelper().getControlType()) { case SdtControlType::richText: case SdtControlType::plainText: @@ -1397,35 +1397,35 @@ void DomainMapper::lcl_attribute(Id nName, const Value & val) // It's not possible to insert the relevant property to the character context here: // the previous, already sent character context may be still active, so the property would be lost. - if (m_pImpl->m_pSdtHelper->isOutsideAParagraph()) + if (m_pImpl->GetSdtHelper().isOutsideAParagraph()) m_pImpl->setParaSdtEndDeferred(true); else m_pImpl->setSdtEndDeferred(true); - switch (m_pImpl->m_pSdtHelper->getControlType()) + switch (m_pImpl->GetSdtHelper().getControlType()) { case SdtControlType::dropDown: case SdtControlType::comboBox: - m_pImpl->m_pSdtHelper->createDropDownControl(); + m_pImpl->GetSdtHelper().createDropDownControl(); break; case SdtControlType::plainText: - m_pImpl->m_pSdtHelper->createPlainTextControl(); + m_pImpl->GetSdtHelper().createPlainTextControl(); break; case SdtControlType::datePicker: - m_pImpl->m_pSdtHelper->createDateContentControl(); + m_pImpl->GetSdtHelper().createDateContentControl(); break; case SdtControlType::unknown: default:; } break; case NS_ooxml::LN_CT_SdtListItem_displayText: - m_pImpl->m_pSdtHelper->getDropDownDisplayTexts().push_back(sStringValue); + m_pImpl->GetSdtHelper().getDropDownDisplayTexts().push_back(sStringValue); break; case NS_ooxml::LN_CT_SdtListItem_value: - m_pImpl->m_pSdtHelper->getDropDownItems().push_back(sStringValue); + m_pImpl->GetSdtHelper().getDropDownItems().push_back(sStringValue); break; case NS_ooxml::LN_CT_SdtDate_fullDate: - m_pImpl->m_pSdtHelper->getDate().append(sStringValue); + m_pImpl->GetSdtHelper().getDate().append(sStringValue); break; case NS_ooxml::LN_CT_Background_color: if (m_pImpl->GetSettingsTable()->GetDisplayBackgroundShape()) @@ -1460,28 +1460,28 @@ void DomainMapper::lcl_attribute(Id nName, const Value & val) } break; case NS_ooxml::LN_CT_DataBinding_prefixMappings: - m_pImpl->m_pSdtHelper->setDataBindingPrefixMapping(sStringValue); + m_pImpl->GetSdtHelper().setDataBindingPrefixMapping(sStringValue); m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, u"ooxml:CT_DataBinding_prefixMappings"_ustr, sStringValue); break; case NS_ooxml::LN_CT_DataBinding_xpath: - m_pImpl->m_pSdtHelper->setDataBindingXPath(sStringValue); + m_pImpl->GetSdtHelper().setDataBindingXPath(sStringValue); m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, u"ooxml:CT_DataBinding_xpath"_ustr, sStringValue); break; case NS_ooxml::LN_CT_DataBinding_storeItemID: - m_pImpl->m_pSdtHelper->setDataBindingStoreItemID(sStringValue); + m_pImpl->GetSdtHelper().setDataBindingStoreItemID(sStringValue); m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, u"ooxml:CT_DataBinding_storeItemID"_ustr, sStringValue); break; case NS_ooxml::LN_CT_SdtPlaceholder_docPart_val: m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, u"ooxml:CT_SdtPlaceholder_docPart_val"_ustr, sStringValue); - m_pImpl->m_pSdtHelper->SetPlaceholderDocPart(sStringValue); + m_pImpl->GetSdtHelper().SetPlaceholderDocPart(sStringValue); break; case NS_ooxml::LN_CT_SdtColor_val: m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, u"ooxml:CT_SdtColor_val"_ustr, sStringValue); - m_pImpl->m_pSdtHelper->SetColor(sStringValue); + m_pImpl->GetSdtHelper().SetColor(sStringValue); break; case NS_ooxml::LN_CT_SdtAppearance_val: m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, u"ooxml:CT_SdtAppearance_val"_ustr, sStringValue); - m_pImpl->m_pSdtHelper->SetAppearance(sStringValue); + m_pImpl->GetSdtHelper().SetAppearance(sStringValue); break; case NS_ooxml::LN_CT_SdtText_multiLine: m_pImpl->appendGrabBag(m_pImpl->m_aInteropGrabBag, u"ooxml:CT_SdtText_multiLine"_ustr, sStringValue); @@ -3040,7 +3040,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) m_pImpl->GetTopContext()->Insert( PROP_CHAR_STYLE_NAME, uno::Any( sConvertedName ) ); if (!m_pImpl->GetSdtStarts().empty()) - m_pImpl->m_pSdtHelper->SetPlaceholderCharStyle(sConvertedName); + m_pImpl->GetSdtHelper().SetPlaceholderCharStyle(sConvertedName); } } break; @@ -3364,7 +3364,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) break; case NS_ooxml::LN_CT_SdtPr_comboBox: { - m_pImpl->m_pSdtHelper->setControlType(SdtControlType::comboBox); + m_pImpl->GetSdtHelper().setControlType(SdtControlType::comboBox); writerfilter::Reference::Pointer_t pProperties = rSprm.getProps(); if (pProperties) pProperties->resolve(*this); @@ -3372,7 +3372,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) break; case NS_ooxml::LN_CT_SdtPr_dropDownList: { - m_pImpl->m_pSdtHelper->setControlType(SdtControlType::dropDown); + m_pImpl->GetSdtHelper().setControlType(SdtControlType::dropDown); writerfilter::Reference::Pointer_t pProperties = rSprm.getProps(); if (pProperties) pProperties->resolve(*this); @@ -3382,21 +3382,21 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) { writerfilter::Reference::Pointer_t pProperties = rSprm.getProps(); - size_t nDropDownDisplayTexts = m_pImpl->m_pSdtHelper->getDropDownDisplayTexts().size(); - size_t nDropDownItems = m_pImpl->m_pSdtHelper->getDropDownItems().size(); + size_t nDropDownDisplayTexts = m_pImpl->GetSdtHelper().getDropDownDisplayTexts().size(); + size_t nDropDownItems = m_pImpl->GetSdtHelper().getDropDownItems().size(); if (pProperties) pProperties->resolve(*this); - if (m_pImpl->m_pSdtHelper->getDropDownDisplayTexts().size() != nDropDownDisplayTexts + 1) + if (m_pImpl->GetSdtHelper().getDropDownDisplayTexts().size() != nDropDownDisplayTexts + 1) { // w:displayText="..." is optional, add empty value if it was not provided. - m_pImpl->m_pSdtHelper->getDropDownDisplayTexts().emplace_back(); + m_pImpl->GetSdtHelper().getDropDownDisplayTexts().emplace_back(); } - if (m_pImpl->m_pSdtHelper->getDropDownItems().size() != nDropDownItems + 1) + if (m_pImpl->GetSdtHelper().getDropDownItems().size() != nDropDownItems + 1) { // w:value="..." is optional, add empty value if it was not provided. - m_pImpl->m_pSdtHelper->getDropDownItems().emplace_back(); + m_pImpl->GetSdtHelper().getDropDownItems().emplace_back(); } } break; @@ -3409,14 +3409,14 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) break; case NS_ooxml::LN_CT_SdtPr_date: { - m_pImpl->m_pSdtHelper->setControlType(SdtControlType::datePicker); + m_pImpl->GetSdtHelper().setControlType(SdtControlType::datePicker); resolveSprmProps(*this, rSprm); - m_pImpl->m_pSdtHelper->setFieldStartRange(GetCurrentTextRange()->getEnd()); + m_pImpl->GetSdtHelper().setFieldStartRange(GetCurrentTextRange()->getEnd()); } break; case NS_ooxml::LN_CT_SdtDate_dateFormat: { - m_pImpl->m_pSdtHelper->getDateFormat().append(sStringValue); + m_pImpl->GetSdtHelper().getDateFormat().append(sStringValue); } break; case NS_ooxml::LN_CT_SdtDate_storeMappedDataAs: @@ -3429,22 +3429,22 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) break; case NS_ooxml::LN_CT_SdtDate_lid: { - m_pImpl->m_pSdtHelper->getLocale().append(sStringValue); + m_pImpl->GetSdtHelper().getLocale().append(sStringValue); } break; case NS_ooxml::LN_CT_SdtPr_text: { - m_pImpl->m_pSdtHelper->setControlType(SdtControlType::plainText); - if (m_pImpl->m_pSdtHelper->GetSdtType() == NS_ooxml::LN_CT_SdtRun_sdtContent) + m_pImpl->GetSdtHelper().setControlType(SdtControlType::plainText); + if (m_pImpl->GetSdtHelper().GetSdtType() == NS_ooxml::LN_CT_SdtRun_sdtContent) { - m_pImpl->m_pSdtHelper->getInteropGrabBagAndClear(); + m_pImpl->GetSdtHelper().getInteropGrabBagAndClear(); break; } enableInteropGrabBag(u"ooxml:CT_SdtPr_text"_ustr); writerfilter::Reference::Pointer_t pProperties = rSprm.getProps(); if (pProperties) pProperties->resolve(*this); - m_pImpl->m_pSdtHelper->appendToInteropGrabBag(getInteropGrabBag()); + m_pImpl->GetSdtHelper().appendToInteropGrabBag(getInteropGrabBag()); m_pImpl->disableInteropGrabBag(); } break; @@ -3470,8 +3470,8 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) { // Although the absence of a element should mean that the control is richText, // in practice, the presence of a dataBinding element makes it plainText - if (m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::richText) - m_pImpl->m_pSdtHelper->setControlType(SdtControlType::plainText); + if (m_pImpl->GetSdtHelper().getControlType() == SdtControlType::richText) + m_pImpl->GetSdtHelper().setControlType(SdtControlType::plainText); } if (!m_pImpl->GetSdtStarts().empty()) @@ -3479,7 +3479,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) if (nSprmId == NS_ooxml::LN_CT_SdtPr_showingPlcHdr) { if (nIntValue) - m_pImpl->m_pSdtHelper->SetShowingPlcHdr(); + m_pImpl->GetSdtHelper().SetShowingPlcHdr(); break; } @@ -3505,37 +3505,37 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) if (nSprmId == NS_ooxml::LN_CT_SdtPr_alias) { - m_pImpl->m_pSdtHelper->SetAlias(sStringValue); + m_pImpl->GetSdtHelper().SetAlias(sStringValue); break; } if (nSprmId == NS_ooxml::LN_CT_SdtPr_tag) { - m_pImpl->m_pSdtHelper->SetTag(sStringValue); + m_pImpl->GetSdtHelper().SetTag(sStringValue); break; } if (nSprmId == NS_ooxml::LN_CT_SdtPr_id) { - m_pImpl->m_pSdtHelper->SetId(nIntValue); + m_pImpl->GetSdtHelper().SetId(nIntValue); break; } if (nSprmId == NS_ooxml::LN_CT_SdtPr_tabIndex) { - m_pImpl->m_pSdtHelper->SetTabIndex(nIntValue); + m_pImpl->GetSdtHelper().SetTabIndex(nIntValue); break; } if (nSprmId == NS_ooxml::LN_CT_SdtPr_lock) { - m_pImpl->m_pSdtHelper->SetLock(sStringValue); + m_pImpl->GetSdtHelper().SetLock(sStringValue); break; } if (nSprmId == NS_ooxml::LN_CT_SdtPr_checkbox) { - m_pImpl->m_pSdtHelper->setControlType(SdtControlType::checkBox); + m_pImpl->GetSdtHelper().setControlType(SdtControlType::checkBox); writerfilter::Reference::Pointer_t pProperties = rSprm.getProps(); if (pProperties) { @@ -3545,7 +3545,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) } else if (nSprmId == NS_ooxml::LN_CT_SdtPr_picture) { - m_pImpl->m_pSdtHelper->setControlType(SdtControlType::picture); + m_pImpl->GetSdtHelper().setControlType(SdtControlType::picture); writerfilter::Reference::Pointer_t pProperties = rSprm.getProps(); if (pProperties) { @@ -3555,7 +3555,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) } else if (nSprmId == NS_ooxml::LN_CT_SdtPr_date) { - m_pImpl->m_pSdtHelper->setControlType(SdtControlType::datePicker); + m_pImpl->GetSdtHelper().setControlType(SdtControlType::datePicker); writerfilter::Reference::Pointer_t pProperties = rSprm.getProps(); if (pProperties) { @@ -3595,7 +3595,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) nSprmId == NS_ooxml::LN_CT_SdtPr_picture || nSprmId == NS_ooxml::LN_CT_SdtPr_citation) { - m_pImpl->m_pSdtHelper->setControlType(SdtControlType::unsupported); + m_pImpl->GetSdtHelper().setControlType(SdtControlType::unsupported); } enableInteropGrabBag(sName); @@ -3611,7 +3611,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) beans::PropertyValue aValue; aValue.Name = sName; aValue.Value <<= sStringValue; - m_pImpl->m_pSdtHelper->appendToInteropGrabBag(aValue); + m_pImpl->GetSdtHelper().appendToInteropGrabBag(aValue); } else if (nSprmId == NS_ooxml::LN_CT_SdtPr_showingPlcHdr) { @@ -3619,7 +3619,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) beans::PropertyValue aValue; aValue.Name = sName; aValue.Value <<= bool(nIntValue); - m_pImpl->m_pSdtHelper->appendToInteropGrabBag(aValue); + m_pImpl->GetSdtHelper().appendToInteropGrabBag(aValue); } else if (nSprmId == NS_ooxml::LN_CT_SdtPr_id || nSprmId == NS_ooxml::LN_CT_SdtPr_tabIndex) { @@ -3627,11 +3627,11 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) beans::PropertyValue aValue; aValue.Name = sName; aValue.Value <<= nIntValue; - m_pImpl->m_pSdtHelper->appendToInteropGrabBag(aValue); + m_pImpl->GetSdtHelper().appendToInteropGrabBag(aValue); } else - m_pImpl->m_pSdtHelper->appendToInteropGrabBag(getInteropGrabBag()); - m_pImpl->m_pSdtHelper->setOutsideAParagraph(m_pImpl->IsOutsideAParagraph()); + m_pImpl->GetSdtHelper().appendToInteropGrabBag(getInteropGrabBag()); + m_pImpl->GetSdtHelper().setOutsideAParagraph(m_pImpl->IsOutsideAParagraph()); m_pImpl->disableInteropGrabBag(); } break; @@ -3641,7 +3641,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) // nIntValue is not just 0 or 1, because we're in the w14 namespace's ST_OnOff. if (nIntValue == NS_ooxml::LN_ST_OnOff_true || nIntValue == NS_ooxml::LN_ST_OnOff_1) { - m_pImpl->m_pSdtHelper->SetChecked(); + m_pImpl->GetSdtHelper().SetChecked(); } } else @@ -3653,7 +3653,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) case NS_ooxml::LN_CT_SdtCheckbox_checkedState: if (!m_pImpl->GetSdtStarts().empty()) { - m_pImpl->m_pSdtHelper->SetCheckedState(OUString(sal_Unicode(sStringValue.toInt32(16)))); + m_pImpl->GetSdtHelper().SetCheckedState(OUString(sal_Unicode(sStringValue.toInt32(16)))); } else { @@ -3664,7 +3664,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) case NS_ooxml::LN_CT_SdtCheckbox_uncheckedState: if (!m_pImpl->GetSdtStarts().empty()) { - m_pImpl->m_pSdtHelper->SetUncheckedState( + m_pImpl->GetSdtHelper().SetUncheckedState( OUString(sal_Unicode(sStringValue.toInt32(16)))); } else @@ -3773,7 +3773,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) case NS_ooxml::LN_CT_SdtPr_rPr: { // Make sure properties from a previous SDT are not merged with the current ones. - m_pImpl->m_pSdtHelper->getInteropGrabBagAndClear(); + m_pImpl->GetSdtHelper().getInteropGrabBagAndClear(); } break; case NS_ooxml::LN_CT_TblPrBase_tblLook: @@ -4657,65 +4657,66 @@ void DomainMapper::lcl_utext(const sal_Unicode *const data_, size_t len) bool bNewLine = len == 1 && (sText[0] == 0x0d || sText[0] == 0x07); if (m_pImpl->GetSdtStarts().empty() - && (m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::dropDown - || m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::comboBox)) + && (m_pImpl->GetSdtHelper().getControlType() == SdtControlType::dropDown + || m_pImpl->GetSdtHelper().getControlType() == SdtControlType::comboBox)) { // Block, cell or row SDT. if (bNewLine) // Dropdown control has single-line texts, so in case of newline, create the control. - m_pImpl->m_pSdtHelper->createDropDownControl(); + m_pImpl->GetSdtHelper().createDropDownControl(); else { - m_pImpl->m_pSdtHelper->getSdtTexts().append(sText); + m_pImpl->GetSdtHelper().getSdtTexts().append(sText); return; } } - else if (m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::datePicker) + else if (m_pImpl->GetSdtHelper().getControlType() == SdtControlType::datePicker) { if (IsInHeaderFooter() && m_pImpl->IsDiscardHeaderFooter()) { - m_pImpl->m_pSdtHelper->getDateFormat().truncate(); - m_pImpl->m_pSdtHelper->getLocale().truncate(); + m_pImpl->GetSdtHelper().getDateFormat().truncate(); + m_pImpl->GetSdtHelper().getLocale().truncate(); return; } } - else if (m_pImpl->m_pSdtHelper->GetSdtType() != NS_ooxml::LN_CT_SdtRun_sdtContent && m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::plainText) + else if (m_pImpl->GetSdtHelper().GetSdtType() != NS_ooxml::LN_CT_SdtRun_sdtContent + && m_pImpl->GetSdtHelper().getControlType() == SdtControlType::plainText) { if (bNewLine) { - if (!m_pImpl->m_pSdtHelper->isFieldStartRangeSet()) - m_pImpl->m_pSdtHelper->setFieldStartRange(GetCurrentTextRange()->getEnd()); + if (!m_pImpl->GetSdtHelper().isFieldStartRangeSet()) + m_pImpl->GetSdtHelper().setFieldStartRange(GetCurrentTextRange()->getEnd()); - m_pImpl->m_pSdtHelper->createPlainTextControl(); + m_pImpl->GetSdtHelper().createPlainTextControl(); finishParagraph(); return; } } - else if (!m_pImpl->m_pSdtHelper->isInteropGrabBagEmpty()) + else if (!m_pImpl->GetSdtHelper().isInteropGrabBagEmpty()) { // there are unsupported SDT properties in the document // save them in the paragraph interop grab bag if (m_pImpl->IsDiscardHeaderFooter()) { // Unless we're supposed to ignore this header/footer. - m_pImpl->m_pSdtHelper->getInteropGrabBagAndClear(); + m_pImpl->GetSdtHelper().getInteropGrabBagAndClear(); return; } - const SdtControlType aControlType = m_pImpl->m_pSdtHelper->getControlType(); + const SdtControlType aControlType = m_pImpl->GetSdtHelper().getControlType(); if (aControlType != SdtControlType::unsupported && aControlType != SdtControlType::unknown - && m_pImpl->m_pSdtHelper->GetSdtType() == NS_ooxml::LN_CT_SdtRun_sdtContent) + && m_pImpl->GetSdtHelper().GetSdtType() == NS_ooxml::LN_CT_SdtRun_sdtContent) { - m_pImpl->m_pSdtHelper->getInteropGrabBagAndClear(); + m_pImpl->GetSdtHelper().getInteropGrabBagAndClear(); } - else if ((m_pImpl->m_pSdtHelper->containedInInteropGrabBag(u"ooxml:CT_SdtPr_checkbox"_ustr) - || m_pImpl->m_pSdtHelper->containedInInteropGrabBag(u"ooxml:CT_SdtPr_text"_ustr) - || m_pImpl->m_pSdtHelper->containedInInteropGrabBag( + else if ((m_pImpl->GetSdtHelper().containedInInteropGrabBag(u"ooxml:CT_SdtPr_checkbox"_ustr) + || m_pImpl->GetSdtHelper().containedInInteropGrabBag(u"ooxml:CT_SdtPr_text"_ustr) + || m_pImpl->GetSdtHelper().containedInInteropGrabBag( u"ooxml:CT_SdtPr_dataBinding"_ustr) - || m_pImpl->m_pSdtHelper->containedInInteropGrabBag( + || m_pImpl->GetSdtHelper().containedInInteropGrabBag( u"ooxml:CT_SdtPr_citation"_ustr) - || (m_pImpl->m_pSdtHelper->containedInInteropGrabBag(u"ooxml:CT_SdtPr_id"_ustr) - && m_pImpl->m_pSdtHelper->getInteropGrabBagSize() == 1)) - && !m_pImpl->m_pSdtHelper->isOutsideAParagraph()) + || (m_pImpl->GetSdtHelper().containedInInteropGrabBag(u"ooxml:CT_SdtPr_id"_ustr) + && m_pImpl->GetSdtHelper().getInteropGrabBagSize() == 1)) + && !m_pImpl->GetSdtHelper().isOutsideAParagraph()) { PropertyMapPtr pContext = m_pImpl->GetTopContextOfType(CONTEXT_CHARACTER); @@ -4723,12 +4724,12 @@ void DomainMapper::lcl_utext(const sal_Unicode *const data_, size_t len) // We have a field, insert the SDT properties to the field's grab-bag, so they won't be lost. pContext = m_pImpl->GetTopFieldContext()->getProperties(); - uno::Sequence aGrabBag = m_pImpl->m_pSdtHelper->getInteropGrabBagAndClear(); + uno::Sequence aGrabBag = m_pImpl->GetSdtHelper().getInteropGrabBagAndClear(); pContext->Insert(PROP_SDTPR, uno::Any(aGrabBag), true, CHAR_GRAB_BAG); } else { - uno::Sequence aGrabBag = m_pImpl->m_pSdtHelper->getInteropGrabBagAndClear(); + uno::Sequence aGrabBag = m_pImpl->GetSdtHelper().getInteropGrabBagAndClear(); if (m_pImpl->GetSdtStarts().empty() || (aControlType != SdtControlType::dropDown && aControlType != SdtControlType::comboBox @@ -4923,8 +4924,8 @@ void DomainMapper::lcl_utext(const sal_Unicode *const data_, size_t len) } bool bInSdtBlockText - = m_pImpl->m_pSdtHelper->GetSdtType() == NS_ooxml::LN_CT_SdtBlock_sdtContent - && m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::plainText; + = m_pImpl->GetSdtHelper().GetSdtType() == NS_ooxml::LN_CT_SdtBlock_sdtContent + && m_pImpl->GetSdtHelper().getControlType() == SdtControlType::plainText; if (pContext && pContext->GetFootnote().is()) { pContext->GetFootnote()->setLabel( sText ); @@ -4937,12 +4938,12 @@ void DomainMapper::lcl_utext(const sal_Unicode *const data_, size_t len) { if (bInSdtBlockText) { - if (m_pImpl->m_pSdtHelper->hasUnusedText()) - m_pImpl->m_pSdtHelper->createPlainTextControl(); - else if (!m_pImpl->m_pSdtHelper->isFieldStartRangeSet()) - m_pImpl->m_pSdtHelper->setFieldStartRange(GetCurrentTextRange()->getEnd()); + if (m_pImpl->GetSdtHelper().hasUnusedText()) + m_pImpl->GetSdtHelper().createPlainTextControl(); + else if (!m_pImpl->GetSdtHelper().isFieldStartRangeSet()) + m_pImpl->GetSdtHelper().setFieldStartRange(GetCurrentTextRange()->getEnd()); // MS Word says plainText control containing a field is a corrupt file - m_pImpl->m_pSdtHelper->setControlType(SdtControlType::richText); + m_pImpl->GetSdtHelper().setControlType(SdtControlType::richText); } m_pImpl->AppendFieldCommand(sText); } @@ -4950,10 +4951,10 @@ void DomainMapper::lcl_utext(const sal_Unicode *const data_, size_t len) { if (bInSdtBlockText) { - if (m_pImpl->m_pSdtHelper->hasUnusedText()) - m_pImpl->m_pSdtHelper->createPlainTextControl(); - else if (!m_pImpl->m_pSdtHelper->isFieldStartRangeSet()) - m_pImpl->m_pSdtHelper->setFieldStartRange(GetCurrentTextRange()->getEnd()); + if (m_pImpl->GetSdtHelper().hasUnusedText()) + m_pImpl->GetSdtHelper().createPlainTextControl(); + else if (!m_pImpl->GetSdtHelper().isFieldStartRangeSet()) + m_pImpl->GetSdtHelper().setFieldStartRange(GetCurrentTextRange()->getEnd()); } /*depending on the success of the field insert operation this result will be set at the field or directly inserted into the text*/ @@ -4964,13 +4965,13 @@ void DomainMapper::lcl_utext(const sal_Unicode *const data_, size_t len) if (pContext == nullptr) pContext = new PropertyMap(); - if (bInSdtBlockText && !m_pImpl->m_pSdtHelper->hasUnusedText()) - m_pImpl->m_pSdtHelper->setFieldStartRange(GetCurrentTextRange()->getEnd()); + if (bInSdtBlockText && !m_pImpl->GetSdtHelper().hasUnusedText()) + m_pImpl->GetSdtHelper().setFieldStartRange(GetCurrentTextRange()->getEnd()); m_pImpl->appendTextPortion( sText, pContext ); if (bInSdtBlockText && !sText.isEmpty()) - m_pImpl->m_pSdtHelper->setHasUnusedText(true); + m_pImpl->GetSdtHelper().setHasUnusedText(true); } } @@ -5456,8 +5457,8 @@ void DomainMapper::HandleRedline( Sprm& rSprm ) void DomainMapper::finishParagraph(const bool bRemove, const bool bNoNumbering) { - if (m_pImpl->m_pSdtHelper->getControlType() == SdtControlType::datePicker) - m_pImpl->m_pSdtHelper->createDateContentControl(); + if (m_pImpl->GetSdtHelper().getControlType() == SdtControlType::datePicker) + m_pImpl->GetSdtHelper().createDateContentControl(); auto pParaContext = static_cast(m_pImpl->GetTopContextOfType(CONTEXT_PARAGRAPH).get()); m_pImpl->finishParagraph(pParaContext, bRemove, bNoNumbering); if (bRemove || mbIsLastPara) diff --git a/sw/source/writerfilter/dmapper/DomainMapperTableHandler.cxx b/sw/source/writerfilter/dmapper/DomainMapperTableHandler.cxx index 7b21d26c650c..49aff0f5c16e 100644 --- a/sw/source/writerfilter/dmapper/DomainMapperTableHandler.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapperTableHandler.cxx @@ -676,7 +676,15 @@ TableStyleSheetEntry * DomainMapperTableHandler::endTableGetTableStyle(TableInfo } sal_Int32 nHoriOrient = text::HoriOrientation::LEFT_AND_WIDTH; - // Fetch Horizontal Orientation in rFrameProperties if not set in m_aTableProperties + // The table style carries the alignment as a plain property. + if (const std::optional oStyleHoriOrient + = m_aTableProperties->getProperty(PROP_HORI_ORIENT)) + { + sal_Int16 nStyleHoriOrient{}; + if (oStyleHoriOrient->second >>= nStyleHoriOrient) + nHoriOrient = nStyleHoriOrient; + } + // Direct formatting wins over it, then Horizontal Orientation in rFrameProperties. if ( !m_aTableProperties->getValue( TablePropertyMap::HORI_ORIENT, nHoriOrient ) ) lcl_extractHoriOrient( rFrameProperties, nHoriOrient ); m_aTableProperties->Insert( PROP_HORI_ORIENT, uno::Any( sal_Int16(nHoriOrient) ) ); diff --git a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx index e455496f62d9..11a4646e1f7c 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx @@ -162,25 +162,22 @@ using namespace ::com::sun::star; using namespace oox; namespace writerfilter::dmapper{ -//line numbering for header/footer -static void lcl_linenumberingHeaderFooter( const rtl::Reference& xStyles, const OUString& rname, DomainMapper_Impl* dmapper ) +static void lcl_disableStyleLineNumbering(const rtl::Reference& xStyles, + const OUString& rProgName) { - const StyleSheetEntryPtr pEntry = dmapper->GetStyleSheetTable()->FindStyleSheetByISTD( rname ); - if (!pEntry) + if (!xStyles.is()) return; - const StyleSheetPropertyMap* pStyleSheetProperties = pEntry->m_pProperties.get(); - if ( !pStyleSheetProperties ) - return; - sal_Int32 nListId = pStyleSheetProperties->props().GetListId(); - if( xStyles.is() ) + + try + { + rtl::Reference xStyle = xStyles->getStyleByName(rProgName); + if (!xStyle.is()) + return; + + xStyle->setPropertyValue(getPropertyName(PROP_PARA_LINE_NUMBER_COUNT), uno::Any(false)); + } + catch (const uno::Exception&) { - if( xStyles->hasByName( rname ) ) - { - rtl::Reference< SwXBaseStyle > xStyle = xStyles->getStyleByName( rname ); - if( !xStyle.is() ) - return; - xStyle->setPropertyValue( getPropertyName( PROP_PARA_LINE_NUMBER_COUNT ), uno::Any( nListId >= 0 ) ); - } } } @@ -403,7 +400,7 @@ DomainMapper_Impl::DomainMapper_Impl( m_bIsActualParagraphFramed( false ), m_bSaxError(false) { - m_StreamStateStack.emplace(); // add state for document body + m_StreamStateStack.emplace(*this, m_xComponentContext); // add state for document body m_aBaseUrl = rMediaDesc.getUnpackedValueOrDefault( utl::MediaDescriptor::PROP_DOCUMENTBASEURL, OUString()); if (m_aBaseUrl.isEmpty()) { @@ -428,8 +425,6 @@ DomainMapper_Impl::DomainMapper_Impl( getTableManager( ).startLevel(); m_bUsingEnhancedFields = !comphelper::IsFuzzing() && officecfg::Office::Common::Filter::Microsoft::Import::ImportWWFieldsAsEnhancedFields::get(); - m_pSdtHelper = new SdtHelper(*this, m_xComponentContext); - m_aRedlines.push(std::vector()); if (m_bIsAltChunk) @@ -984,6 +979,11 @@ void DomainMapper_Impl::SetParaSectpr(bool bParaSectpr) m_StreamStateStack.top().bParaSectpr = bParaSectpr; } +SdtHelper& DomainMapper_Impl::GetSdtHelper() +{ + return m_StreamStateStack.top().m_aSdtHelper; +} + void DomainMapper_Impl::SetSdt(bool bSdt) { // Empty run level SDTs before starting a block level SDT @@ -1035,7 +1035,7 @@ void DomainMapper_Impl::PushSdt() m_xSdtStarts.push({bStart, OUString(), xCursor->getStart()}); // It is captured while the SDT's content run is imported and consumed in PopSdt(). - m_pSdtHelper->SetPlaceholderCharStyle(OUString()); + GetSdtHelper().SetPlaceholderCharStyle(OUString()); } const std::stack& DomainMapper_Impl::GetSdtStarts() const @@ -1103,7 +1103,7 @@ void DomainMapper_Impl::PopSdt() // while the intention is to keep start/end inside the same paragraph for run SDTs. rtl::Reference xParagraphCursor = dynamic_cast(xCursor.get()); if (xParagraphCursor.is() - && m_pSdtHelper->GetSdtType() == NS_ooxml::LN_CT_SdtRun_sdtContent) + && GetSdtHelper().GetSdtType() == NS_ooxml::LN_CT_SdtRun_sdtContent) { xCursor->gotoRange(xEnd, /*bExpand=*/false); xParagraphCursor->gotoStartOfParagraph(/*bExpand=*/false); @@ -1116,9 +1116,8 @@ void DomainMapper_Impl::PopSdt() } xCursor->gotoRange(xEnd, /*bExpand=*/true); - std::optional oData = m_pSdtHelper->getValueFromDataBinding(); - if (oData.has_value() - && m_pSdtHelper->getControlType() != SdtControlType::picture) + std::optional oData = GetSdtHelper().getValueFromDataBinding(); + if (oData.has_value() && GetSdtHelper().getControlType() != SdtControlType::picture) { // Data binding has a value for us, prefer that over the in-document value. // But not for picture content controls: their sdtContent has the rendered @@ -1134,7 +1133,7 @@ void DomainMapper_Impl::PopSdt() // that formatting is meaningful and Word keeps it, so only reset the // char style in the placeholder case. uno::Reference xCursorProps(xCursor, uno::UNO_QUERY); - if (m_pSdtHelper->GetShowingPlcHdr()) + if (GetSdtHelper().GetShowingPlcHdr()) { uno::Reference xPropertyState(xCursor, uno::UNO_QUERY); if (xPropertyState.is()) @@ -1142,7 +1141,7 @@ void DomainMapper_Impl::PopSdt() xPropertyState->setPropertyToDefault(u"CharStyleName"_ustr); } } - else if (!m_pSdtHelper->GetPlaceholderCharStyle().isEmpty() && xCursorProps.is()) + else if (!GetSdtHelper().GetPlaceholderCharStyle().isEmpty() && xCursorProps.is()) { OUString sCurrentCharStyle; xCursorProps->getPropertyValue(u"CharStyleName"_ustr) >>= sCurrentCharStyle; @@ -1150,97 +1149,94 @@ void DomainMapper_Impl::PopSdt() if (sCurrentCharStyle.isEmpty()) { xCursorProps->setPropertyValue( - u"CharStyleName"_ustr, uno::Any(m_pSdtHelper->GetPlaceholderCharStyle())); + u"CharStyleName"_ustr, uno::Any(GetSdtHelper().GetPlaceholderCharStyle())); } } } rtl::Reference xContentControl( m_xTextDocument->createContentControl()); - if (m_pSdtHelper->GetShowingPlcHdr()) + if (GetSdtHelper().GetShowingPlcHdr()) { xContentControl->setPropertyValue(u"ShowingPlaceHolder"_ustr, - uno::Any(m_pSdtHelper->GetShowingPlcHdr())); + uno::Any(GetSdtHelper().GetShowingPlcHdr())); } - if (!m_pSdtHelper->GetPlaceholderDocPart().isEmpty()) + if (!GetSdtHelper().GetPlaceholderDocPart().isEmpty()) { xContentControl->setPropertyValue(u"PlaceholderDocPart"_ustr, - uno::Any(m_pSdtHelper->GetPlaceholderDocPart())); + uno::Any(GetSdtHelper().GetPlaceholderDocPart())); } - if (!m_pSdtHelper->GetDataBindingPrefixMapping().isEmpty()) + if (!GetSdtHelper().GetDataBindingPrefixMapping().isEmpty()) { xContentControl->setPropertyValue(u"DataBindingPrefixMappings"_ustr, - uno::Any(m_pSdtHelper->GetDataBindingPrefixMapping())); + uno::Any(GetSdtHelper().GetDataBindingPrefixMapping())); } - if (!m_pSdtHelper->GetDataBindingXPath().isEmpty()) + if (!GetSdtHelper().GetDataBindingXPath().isEmpty()) { xContentControl->setPropertyValue(u"DataBindingXpath"_ustr, - uno::Any(m_pSdtHelper->GetDataBindingXPath())); + uno::Any(GetSdtHelper().GetDataBindingXPath())); } - if (!m_pSdtHelper->GetDataBindingStoreItemID().isEmpty()) + if (!GetSdtHelper().GetDataBindingStoreItemID().isEmpty()) { xContentControl->setPropertyValue(u"DataBindingStoreItemID"_ustr, - uno::Any(m_pSdtHelper->GetDataBindingStoreItemID())); + uno::Any(GetSdtHelper().GetDataBindingStoreItemID())); } - if (!m_pSdtHelper->GetColor().isEmpty()) + if (!GetSdtHelper().GetColor().isEmpty()) { - xContentControl->setPropertyValue(u"Color"_ustr, - uno::Any(m_pSdtHelper->GetColor())); + xContentControl->setPropertyValue(u"Color"_ustr, uno::Any(GetSdtHelper().GetColor())); } - if (!m_pSdtHelper->GetAppearance().isEmpty()) + if (!GetSdtHelper().GetAppearance().isEmpty()) { xContentControl->setPropertyValue(u"Appearance"_ustr, - uno::Any(m_pSdtHelper->GetAppearance())); + uno::Any(GetSdtHelper().GetAppearance())); } - if (!m_pSdtHelper->GetAlias().isEmpty()) + if (!GetSdtHelper().GetAlias().isEmpty()) { - xContentControl->setPropertyValue(u"Alias"_ustr, - uno::Any(m_pSdtHelper->GetAlias())); + xContentControl->setPropertyValue(u"Alias"_ustr, uno::Any(GetSdtHelper().GetAlias())); } - if (!m_pSdtHelper->GetTag().isEmpty()) + if (!GetSdtHelper().GetTag().isEmpty()) { - xContentControl->setPropertyValue(u"Tag"_ustr, - uno::Any(m_pSdtHelper->GetTag())); + xContentControl->setPropertyValue(u"Tag"_ustr, uno::Any(GetSdtHelper().GetTag())); } - if (m_pSdtHelper->GetId()) + if (GetSdtHelper().GetId()) { - xContentControl->setPropertyValue(u"Id"_ustr, uno::Any(m_pSdtHelper->GetId())); + xContentControl->setPropertyValue(u"Id"_ustr, uno::Any(GetSdtHelper().GetId())); } - if (m_pSdtHelper->GetTabIndex()) + if (GetSdtHelper().GetTabIndex()) { - xContentControl->setPropertyValue(u"TabIndex"_ustr, uno::Any(m_pSdtHelper->GetTabIndex())); + xContentControl->setPropertyValue(u"TabIndex"_ustr, uno::Any(GetSdtHelper().GetTabIndex())); } - if (!m_pSdtHelper->GetLock().isEmpty()) + if (!GetSdtHelper().GetLock().isEmpty()) { - xContentControl->setPropertyValue(u"Lock"_ustr, uno::Any(m_pSdtHelper->GetLock())); + xContentControl->setPropertyValue(u"Lock"_ustr, uno::Any(GetSdtHelper().GetLock())); } - if (m_pSdtHelper->getControlType() == SdtControlType::checkBox) + if (GetSdtHelper().getControlType() == SdtControlType::checkBox) { xContentControl->setPropertyValue(u"Checkbox"_ustr, uno::Any(true)); - xContentControl->setPropertyValue(u"Checked"_ustr, uno::Any(m_pSdtHelper->GetChecked())); + xContentControl->setPropertyValue(u"Checked"_ustr, uno::Any(GetSdtHelper().GetChecked())); xContentControl->setPropertyValue(u"CheckedState"_ustr, - uno::Any(m_pSdtHelper->GetCheckedState())); + uno::Any(GetSdtHelper().GetCheckedState())); xContentControl->setPropertyValue(u"UncheckedState"_ustr, - uno::Any(m_pSdtHelper->GetUncheckedState())); + uno::Any(GetSdtHelper().GetUncheckedState())); } - if (m_pSdtHelper->getControlType() == SdtControlType::dropDown - || m_pSdtHelper->getControlType() == SdtControlType::comboBox) + if (GetSdtHelper().getControlType() == SdtControlType::dropDown + || GetSdtHelper().getControlType() == SdtControlType::comboBox) { - std::vector& rDisplayTexts = m_pSdtHelper->getDropDownDisplayTexts(); - std::vector& rValues = m_pSdtHelper->getDropDownItems(); + std::vector& rDisplayTexts = GetSdtHelper().getDropDownDisplayTexts(); + std::vector& rValues = GetSdtHelper().getDropDownItems(); if (rDisplayTexts.size() == rValues.size()) { uno::Sequence aItems(rValues.size()); @@ -1253,7 +1249,7 @@ void DomainMapper_Impl::PopSdt() }; } xContentControl->setPropertyValue(u"ListItems"_ustr, uno::Any(aItems)); - if (m_pSdtHelper->getControlType() == SdtControlType::dropDown) + if (GetSdtHelper().getControlType() == SdtControlType::dropDown) { xContentControl->setPropertyValue(u"DropDown"_ustr, uno::Any(true)); } @@ -1264,21 +1260,21 @@ void DomainMapper_Impl::PopSdt() } } - if (m_pSdtHelper->getControlType() == SdtControlType::picture) + if (GetSdtHelper().getControlType() == SdtControlType::picture) { xContentControl->setPropertyValue(u"Picture"_ustr, uno::Any(true)); } bool bDateFromDataBinding = false; - if (m_pSdtHelper->getControlType() == SdtControlType::datePicker) + if (GetSdtHelper().getControlType() == SdtControlType::datePicker) { xContentControl->setPropertyValue(u"Date"_ustr, uno::Any(true)); - OUString aDateFormat = m_pSdtHelper->getDateFormat().makeStringAndClear(); + OUString aDateFormat = GetSdtHelper().getDateFormat().makeStringAndClear(); xContentControl->setPropertyValue(u"DateFormat"_ustr, uno::Any(aDateFormat.replaceAll("'", "\""))); xContentControl->setPropertyValue(u"DateLanguage"_ustr, - uno::Any(m_pSdtHelper->getLocale().makeStringAndClear())); - OUString aCurrentDate = m_pSdtHelper->getDate().makeStringAndClear(); + uno::Any(GetSdtHelper().getLocale().makeStringAndClear())); + OUString aCurrentDate = GetSdtHelper().getDate().makeStringAndClear(); if (oData.has_value()) { aCurrentDate = SdtHelper::AdjustDateString(*oData, aDateFormat); @@ -1288,7 +1284,7 @@ void DomainMapper_Impl::PopSdt() uno::Any(aCurrentDate)); } - if (m_pSdtHelper->getControlType() == SdtControlType::plainText) + if (GetSdtHelper().getControlType() == SdtControlType::plainText) { xContentControl->setPropertyValue(u"PlainText"_ustr, uno::Any(true)); } @@ -1303,7 +1299,7 @@ void DomainMapper_Impl::PopSdt() xCursor->setString(aDateString); } - m_pSdtHelper->clear(); + GetSdtHelper().clear(); } void DomainMapper_Impl::PushProperties(ContextType eId) @@ -2548,7 +2544,11 @@ void DomainMapper_Impl::finishParagraph( const ParagraphPropertyMapPtr& pParaCon //select paragraph xParaCursor->gotoStartOfParagraph( true ); xParaCursor->setPropertyToDefault(getPropertyName(PROP_CHAR_ESCAPEMENT)); + // The size the initial is written with belongs to the frame Word puts it + // in, not to the character: Writer's layout sizes a drop cap itself. xParaCursor->setPropertyToDefault(getPropertyName(PROP_CHAR_HEIGHT)); + xParaCursor->setPropertyToDefault(getPropertyName(PROP_CHAR_HEIGHT_ASIAN)); + xParaCursor->setPropertyToDefault(getPropertyName(PROP_CHAR_HEIGHT_COMPLEX)); //handles (2) and part of (6) pToBeSavedProperties = new ParagraphProperties(pParaContext->props()); sal_Int32 nCount = xParaCursor->getString().getLength(); @@ -3071,6 +3071,8 @@ void DomainMapper_Impl::finishParagraph( const ParagraphPropertyMapPtr& pParaCon // table style has got bigger precedence than docDefault style // collect these pending paragraph properties to process in endTable() rtl::Reference xCur = dynamic_cast(xTextRange->getText( )->createTextCursor().get()); + if (!xCur) + throw uno::RuntimeException(); xCur->gotoEnd(false); xCur->goLeft(1, false); rtl::Reference xParaCursor = dynamic_cast @@ -3293,8 +3295,7 @@ void DomainMapper_Impl::finishParagraph( const ParagraphPropertyMapPtr& pParaCon } - bool bIgnoreFrameState = IsInHeaderFooter(); - if( (!bIgnoreFrameState && pParaContext && pParaContext->props().IsFrameMode()) || (bIgnoreFrameState && GetIsPreviousParagraphFramed()) ) + if (pParaContext && pParaContext->props().IsFrameMode()) SetIsPreviousParagraphFramed(true); else SetIsPreviousParagraphFramed(false); @@ -9761,10 +9762,11 @@ void DomainMapper_Impl::ImportGraphic(const writerfilter::Reference uno::Reference xPropertySetInfo = xPropertySet->getPropertySetInfo(); bHasGrabBag = xPropertySetInfo->hasPropertyByName(u"FrameInteropGrabBag"_ustr); // In case we're outside a paragraph, then the SDT properties are stored in the paragraph grab-bag, not the frame one. - if (!m_pSdtHelper->isInteropGrabBagEmpty() && bHasGrabBag && !m_pSdtHelper->isOutsideAParagraph()) + if (!GetSdtHelper().isInteropGrabBagEmpty() && bHasGrabBag + && !GetSdtHelper().isOutsideAParagraph()) { comphelper::SequenceAsHashMap aFrameGrabBag(xPropertySet->getPropertyValue(u"FrameInteropGrabBag"_ustr)); - aFrameGrabBag[u"SdtPr"_ustr] <<= m_pSdtHelper->getInteropGrabBagAndClear(); + aFrameGrabBag[u"SdtPr"_ustr] <<= GetSdtHelper().getInteropGrabBagAndClear(); xPropertySet->setPropertyValue(u"FrameInteropGrabBag"_ustr, uno::Any(aFrameGrabBag.getAsConstPropertyValueList())); } } @@ -9785,11 +9787,11 @@ void DomainMapper_Impl::ImportGraphic(const writerfilter::Reference } } - if (m_pSdtHelper->getControlType() == SdtControlType::plainText + if (GetSdtHelper().getControlType() == SdtControlType::plainText && (m_StreamStateStack.top().bSdt || GetSdtStarts().size())) { // plainText controls cannot contain pictures or shapes - m_pSdtHelper->setControlType(SdtControlType::richText); + GetSdtHelper().setControlType(SdtControlType::richText); } @@ -9920,8 +9922,9 @@ void DomainMapper_Impl::SetLineNumbering( sal_Int32 nLnnMod, sal_uInt32 nLnc, sa m_bLineNumberingSet = true; rtl::Reference xStyleFamilies = m_xTextDocument->getSwStyleFamilies(); rtl::Reference xStyles = xStyleFamilies->GetParagraphStyles(); - lcl_linenumberingHeaderFooter( xStyles, u"Header"_ustr, this ); - lcl_linenumberingHeaderFooter( xStyles, u"Footer"_ustr, this ); + // MS Word never shows line numbering in headers or footers + lcl_disableStyleLineNumbering(xStyles, u"Header"_ustr); + lcl_disableStyleLineNumbering(xStyles, u"Footer"_ustr); } @@ -10374,7 +10377,7 @@ void DomainMapper_Impl::substream(Id rName, // Save "has footnote" state, which is specific to a section in the body // text, so state from substreams is not relevant. - m_StreamStateStack.emplace(); + m_StreamStateStack.emplace(*this, m_xComponentContext); //import of page header/footer //Ensure that only one header/footer per section is pushed @@ -10422,6 +10425,10 @@ void DomainMapper_Impl::substream(Id rName, throw; } + // finalize any waiting, substream-created frames before ending the substream + CheckUnregisteredFrameConversion(); + ExecuteFrameConversion(); + switch( rName ) { case NS_ooxml::LN_headerl: diff --git a/sw/source/writerfilter/dmapper/DomainMapper_Impl.hxx b/sw/source/writerfilter/dmapper/DomainMapper_Impl.hxx index 51e3816aa451..641682042c07 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.hxx +++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.hxx @@ -51,6 +51,7 @@ #include "GraphicImport.hxx" #include "OLEHandler.hxx" #include "FFDataHandler.hxx" +#include "SdtHelper.hxx" #include "SmartTagHandler.hxx" #include "FormControlHelper.hxx" #include @@ -272,6 +273,7 @@ struct SubstreamContext /// If the current paragraph is inside a structured document element. bool bSdt = false; css::uno::Reference xSdtEntryStart; + SdtHelper m_aSdtHelper; OUString sCurrentParaStyleName; ///< highly inaccurate. Overwritten by "overlapping" paragraphs like flys. bool bHasFootnoteStyle = false; bool bCheckFootnoteStyle = false; @@ -289,6 +291,12 @@ struct SubstreamContext css::uno::Reference< css::text::XTextCursor > xTOCMarkerCursor; std::deque m_aFieldStack; + + explicit SubstreamContext(DomainMapper_Impl& rDM_Impl, + css::uno::Reference xContext) + : m_aSdtHelper(rDM_Impl, std::move(xContext)) + { + } }; /// Information about a paragraph to be finished after a field end. @@ -819,6 +827,7 @@ public: void SetSymbolFont( OUString const &rName ) { m_aSymbolData.sFont = rName; } const SymbolData & GetSymbolData() const { return m_aSymbolData;} + SdtHelper& GetSdtHelper(); void SetSdt(bool bSdt); void PushSdt(); @@ -1193,8 +1202,6 @@ public: /// If we're importing autotext. bool IsReadGlossaries() const { return m_bIsReadGlossaries;} - tools::SvRef m_pSdtHelper; - /// Document background color, applied to every page style. std::optional m_oBackgroundColor; bool m_bCopyStandardPageStyleFill = false; diff --git a/sw/source/writerfilter/dmapper/GraphicHelpers.cxx b/sw/source/writerfilter/dmapper/GraphicHelpers.cxx index 179d0937b608..7ba57f9b778a 100644 --- a/sw/source/writerfilter/dmapper/GraphicHelpers.cxx +++ b/sw/source/writerfilter/dmapper/GraphicHelpers.cxx @@ -196,9 +196,11 @@ sal_Int16 PositionHandler::orientation() const { // It appears that to 'line of text' alignment is backwards to other alignments, // 'top' meaning putting on top of the line instead of having top at the line. if( m_nOrient == text::VertOrientation::TOP ) - return text::VertOrientation::BOTTOM; + return text::VertOrientation::LINE_BOTTOM; else if( m_nOrient == text::VertOrientation::BOTTOM ) - return text::VertOrientation::TOP; + return text::VertOrientation::LINE_TOP; + else if (m_nOrient == text::VertOrientation::CENTER) + return text::VertOrientation::LINE_CENTER; } return m_nOrient; } diff --git a/sw/source/writerfilter/dmapper/GraphicImport.cxx b/sw/source/writerfilter/dmapper/GraphicImport.cxx index b24f5dd7f2de..25178e9c133f 100644 --- a/sw/source/writerfilter/dmapper/GraphicImport.cxx +++ b/sw/source/writerfilter/dmapper/GraphicImport.cxx @@ -784,7 +784,12 @@ void GraphicImport::lcl_attribute(Id nName, const Value& rValue) m_nVertRelation = text::RelOrientation::PAGE_PRINT_AREA; // only "from top" and "top" are appropriate. Others are implemented as Top if (m_nVertOrient != text::VertOrientation::NONE) - m_nVertOrient = text::VertOrientation::TOP; + { + if (m_nVertRelation == text::RelOrientation::TEXT_LINE) + m_nVertOrient = text::VertOrientation::LINE_TOP; + else + m_nVertOrient = text::VertOrientation::TOP; + } } else if (!m_bLayoutInCell && m_rDomainMapper.IsInTable()) { diff --git a/sw/source/writerfilter/dmapper/SdtHelper.cxx b/sw/source/writerfilter/dmapper/SdtHelper.cxx index 2348d285449e..f7687a258c46 100644 --- a/sw/source/writerfilter/dmapper/SdtHelper.cxx +++ b/sw/source/writerfilter/dmapper/SdtHelper.cxx @@ -99,8 +99,6 @@ SdtHelper::SdtHelper(DomainMapper_Impl& rDM_Impl, { } -SdtHelper::~SdtHelper() = default; - void SdtHelper::loadPropertiesXMLs() { // Initialize properties xml storage (m_xPropertiesXMLs) diff --git a/sw/source/writerfilter/dmapper/SdtHelper.hxx b/sw/source/writerfilter/dmapper/SdtHelper.hxx index 359091c9ae9f..5f2ce8ac93f5 100644 --- a/sw/source/writerfilter/dmapper/SdtHelper.hxx +++ b/sw/source/writerfilter/dmapper/SdtHelper.hxx @@ -19,7 +19,6 @@ #include #include -#include namespace com::sun::star { @@ -57,7 +56,7 @@ enum class SdtControlType * w:sdt tokens can't be imported as form fields, as w:sdt supports * e.g. date picking as well. */ -class SdtHelper final : public virtual SvRefBase +class SdtHelper { DomainMapper_Impl& m_rDM_Impl; css::uno::Reference m_xComponentContext; @@ -152,7 +151,6 @@ class SdtHelper final : public virtual SvRefBase public: explicit SdtHelper(DomainMapper_Impl& rDM_Impl, css::uno::Reference xContext); - ~SdtHelper() override; std::vector& getDropDownItems() { return m_aDropDownItems; } std::vector& getDropDownDisplayTexts() { return m_aDropDownDisplayTexts; } diff --git a/sw/source/writerfilter/dmapper/SettingsTable.cxx b/sw/source/writerfilter/dmapper/SettingsTable.cxx index 157b78ea320e..23122b42fe02 100644 --- a/sw/source/writerfilter/dmapper/SettingsTable.cxx +++ b/sw/source/writerfilter/dmapper/SettingsTable.cxx @@ -675,6 +675,7 @@ void SettingsTable::ApplyProperties(rtl::Reference const& xDoc) // Shared between DOCX and RTF, unconditional flags. xDocumentSettings->setPropertyValue(u"TableRowKeep"_ustr, uno::Any(true)); + xDocumentSettings->setPropertyValue(u"MsWordCompMinTextWrapGap"_ustr, uno::Any(true)); xDocumentSettings->setPropertyValue(u"AddVerticalFrameOffsets"_ustr, uno::Any(true)); xDocumentSettings->setPropertyValue(u"ApplyParagraphMarkFormatToEmptyLineAtEndOfParagraph"_ustr, uno::Any(true)); diff --git a/sw/source/writerfilter/dmapper/TblStylePrHandler.cxx b/sw/source/writerfilter/dmapper/TblStylePrHandler.cxx index 3ad0375381af..1b6ec15cde1b 100644 --- a/sw/source/writerfilter/dmapper/TblStylePrHandler.cxx +++ b/sw/source/writerfilter/dmapper/TblStylePrHandler.cxx @@ -22,6 +22,7 @@ #include "CellMarginHandler.hxx" #include "PropertyMap.hxx" #include "MeasureHandler.hxx" +#include "ConversionHelper.hxx" #include #include @@ -161,6 +162,20 @@ void TblStylePrHandler::lcl_sprm(Sprm & rSprm) } } break; + case NS_ooxml::LN_CT_TrPrBase_jc: + case NS_ooxml::LN_CT_TblPrBase_jc: + { + // Table alignment: keep it as a property, the TablePropertyMap value that + // TablePropertiesHandler sets does not survive the copy into a style entry. + // Whole table only, the conditional formats (w:tblStylePr) feed cells. + if ( m_nType == TblStyleType::Unknown || m_nType == TblStyleType::WholeTable ) + { + const Value* pValue = rSprm.getValue(); + m_pProperties->Insert( PROP_HORI_ORIENT, uno::Any( + ConversionHelper::convertTableJustification( pValue ? pValue->getInt() : 0 ) ) ); + } + } + break; case NS_ooxml::LN_CT_TrPrBase_tblHeader: { m_pProperties->Insert( PROP_HEADER_ROW_COUNT, uno::Any(sal_Int32(1))); diff --git a/sw/source/writerfilter/rtftok/rtfdispatchdestination.cxx b/sw/source/writerfilter/rtftok/rtfdispatchdestination.cxx index 74f3c073debf..da2b12efa667 100644 --- a/sw/source/writerfilter/rtftok/rtfdispatchdestination.cxx +++ b/sw/source/writerfilter/rtftok/rtfdispatchdestination.cxx @@ -80,6 +80,8 @@ RTFError RTFDocumentImpl::dispatchDestination(RTFKeyword nKeyword) while (!bFoundCode && ch != '}') { Strm().ReadChar(ch); + if (Strm().eof()) + break; if ('\\' == ch) bInKeyword = true; if (!bInKeyword && rtl::isAsciiAlphanumeric(static_cast(ch))) @@ -99,7 +101,7 @@ RTFError RTFDocumentImpl::dispatchDestination(RTFKeyword nKeyword) while (true) { Strm().ReadChar(ch); - if (ch == '}') + if (Strm().eof() || ch == '}') break; aBuf.append(ch); } diff --git a/sw/source/writerfilter/rtftok/rtfdocumentimpl.cxx b/sw/source/writerfilter/rtftok/rtfdocumentimpl.cxx index 32dcd9b58e76..51539cedd6e9 100644 --- a/sw/source/writerfilter/rtftok/rtfdocumentimpl.cxx +++ b/sw/source/writerfilter/rtftok/rtfdocumentimpl.cxx @@ -1464,33 +1464,14 @@ RTFError RTFDocumentImpl::resolveChars(char ch) void RTFDocumentImpl::singleChar(sal_uInt8 nValue, bool bRunProps) { - sal_uInt8 sValue[] = { nValue }; - RTFBuffer_t* pCurrentBuffer = m_aStates.top().getCurrentBuffer(); - - if (!pCurrentBuffer) - { - Mapper().startCharacterGroup(); - } - else - { - pCurrentBuffer->emplace_back(RTFBufferTypes::StartRun, nullptr, nullptr); - } + bufferOrSend(RTFBufferTypes::StartRun); // Should we send run properties? if (bRunProps) runProps(); - if (!pCurrentBuffer) - { - Mapper().text(sValue, 1); - Mapper().endCharacterGroup(); - } - else - { - auto pValue = new RTFValue(*sValue); - pCurrentBuffer->emplace_back(RTFBufferTypes::Text, pValue, nullptr); - pCurrentBuffer->emplace_back(RTFBufferTypes::EndRun, nullptr, nullptr); - } + bufferOrSend(RTFBufferTypes::Text, new RTFValue(nValue)); + bufferOrSend(RTFBufferTypes::EndRun); } void RTFDocumentImpl::handleFontTableEntry() @@ -1727,37 +1708,24 @@ void RTFDocumentImpl::text(OUString& rString) } RTFBuffer_t* pCurrentBuffer = m_aStates.top().getCurrentBuffer(); + // Text sent directly from a footnote destination gets no run group of its own. + const bool bRunGroup + = pCurrentBuffer || m_aStates.top().getDestination() != Destination::FOOTNOTE; - if (!pCurrentBuffer && m_aStates.top().getDestination() != Destination::FOOTNOTE) - Mapper().startCharacterGroup(); - else if (pCurrentBuffer) - { - RTFValue::Pointer_t pValue; - pCurrentBuffer->emplace_back(RTFBufferTypes::StartRun, pValue, nullptr); - } + if (bRunGroup) + bufferOrSend(RTFBufferTypes::StartRun); if (m_aStates.top().getDestination() == Destination::NORMAL || m_aStates.top().getDestination() == Destination::FIELDRESULT || m_aStates.top().getDestination() == Destination::SHAPETEXT) runProps(); - if (!pCurrentBuffer) - Mapper().utext(rString.getStr(), rString.getLength()); - else - { - auto pValue = new RTFValue(rString); - pCurrentBuffer->emplace_back(RTFBufferTypes::UText, pValue, nullptr); - } + bufferOrSend(RTFBufferTypes::UText, new RTFValue(rString)); m_bNeedCr = true; - if (!pCurrentBuffer && m_aStates.top().getDestination() != Destination::FOOTNOTE) - Mapper().endCharacterGroup(); - else if (pCurrentBuffer) - { - RTFValue::Pointer_t pValue; - pCurrentBuffer->emplace_back(RTFBufferTypes::EndRun, pValue, nullptr); - } + if (bRunGroup) + bufferOrSend(RTFBufferTypes::EndRun); } void RTFDocumentImpl::set_tblInd(RTFSprms& tableRowSprms, int val) @@ -1933,29 +1901,7 @@ void RTFDocumentImpl::replayBuffer(RTFBuffer_t& rBuffer, RTFSprms* const pSprms, { Buf_t aTuple(rBuffer.front()); rBuffer.pop_front(); - if (std::get<0>(aTuple) == RTFBufferTypes::Props - || std::get<0>(aTuple) == RTFBufferTypes::PropsChar) - { - // Construct properties via getProperties() and not directly, to take care of deduplication. - writerfilter::Reference::Pointer_t const pProp( - getProperties(std::get<1>(aTuple)->getAttributes(), std::get<1>(aTuple)->getSprms(), - std::get<0>(aTuple) == RTFBufferTypes::PropsChar - ? NS_ooxml::LN_Value_ST_StyleType_character - : 0, - std::get<0>(aTuple) == RTFBufferTypes::PropsChar)); - Mapper().props(pProp); - } - else if (std::get<0>(aTuple) == RTFBufferTypes::NestRow) - { - TableRowBuffer& rRowBuffer(*std::get<2>(aTuple)); - - replayRowBuffer(rRowBuffer.GetBuffer(), rRowBuffer.GetCellsSprms(), - rRowBuffer.GetCellsAttributes(), rRowBuffer.GetCells()); - - sendProperties(rRowBuffer.GetParaProperties(), rRowBuffer.GetFrameProperties(), - rRowBuffer.GetRowProperties()); - } - else if (std::get<0>(aTuple) == RTFBufferTypes::CellEnd) + if (std::get<0>(aTuple) == RTFBufferTypes::CellEnd) { assert(pSprms && pAttributes); auto pValue = new RTFValue(1); @@ -1966,75 +1912,111 @@ void RTFDocumentImpl::replayBuffer(RTFBuffer_t& rBuffer, RTFSprms* const pSprms, tableBreak(); break; } - else if (std::get<0>(aTuple) == RTFBufferTypes::StartRun) - Mapper().startCharacterGroup(); - else if (std::get<0>(aTuple) == RTFBufferTypes::Text) - { - sal_uInt8 const nValue = std::get<1>(aTuple)->getInt(); - Mapper().text(&nValue, 1); - } - else if (std::get<0>(aTuple) == RTFBufferTypes::UText) - { - OUString const aString(std::get<1>(aTuple)->getString()); - Mapper().utext(aString.getStr(), aString.getLength()); - } - else if (std::get<0>(aTuple) == RTFBufferTypes::EndRun) - Mapper().endCharacterGroup(); - else if (std::get<0>(aTuple) == RTFBufferTypes::PAR) - parBreak(); - else if (std::get<0>(aTuple) == RTFBufferTypes::StartShape) - { - // The shape belongs where the replay stands, not in the buffer being replayed. - RTFBuffer_t* pCurrentBuffer = m_aStates.top().getCurrentBuffer(); - m_aStates.top().setCurrentBuffer(nullptr); - m_pSdrImport->resolve(std::get<1>(aTuple)->getShape(), false, RTFSdrImport::SHAPE); - m_aStates.top().setCurrentBuffer(pCurrentBuffer); - } - else if (std::get<0>(aTuple) == RTFBufferTypes::ResolveShape) - { - // Make sure there is no current buffer while replaying the shape, - // otherwise it gets re-buffered. - RTFBuffer_t* pCurrentBuffer = m_aStates.top().getCurrentBuffer(); - m_aStates.top().setCurrentBuffer(nullptr); - - // Set current shape during replay, needed by e.g. wrap in - // background. - RTFShape aShape = m_aStates.top().getShape(); - m_aStates.top().getShape() = std::get<1>(aTuple)->getShape(); - - m_pSdrImport->resolve(std::get<1>(aTuple)->getShape(), true, RTFSdrImport::SHAPE); - m_aStates.top().getShape() = std::move(aShape); - m_aStates.top().setCurrentBuffer(pCurrentBuffer); - } - else if (std::get<0>(aTuple) == RTFBufferTypes::EndShape) - m_pSdrImport->close(); - else if (std::get<0>(aTuple) == RTFBufferTypes::InsertShape) - { - uno::Reference xShape; - std::get<1>(aTuple)->getAny() >>= xShape; - if (xShape.is()) - Mapper().startShape(xShape); - } - else if (std::get<0>(aTuple) == RTFBufferTypes::ResolveSubstream) - { - RTFSprms& rAttributes = std::get<1>(aTuple)->getAttributes(); - std::size_t nPos = rAttributes.find(0)->getInt(); - Id nId = rAttributes.find(1)->getInt(); - OUString aCustomMark = rAttributes.find(2)->getString(); - resolveSubstream(nPos, nId, aCustomMark); - } - else if (std::get<0>(aTuple) == RTFBufferTypes::Picture) - m_aStates.top().getPicture() = std::get<1>(aTuple)->getPicture(); - else if (std::get<0>(aTuple) == RTFBufferTypes::SetStyle) - { - if (!m_aStates.empty()) - m_aStates.top().setCurrentStyleIndex(std::get<1>(aTuple)->getInt()); - } - else - assert(false); + sendBufferEntry(aTuple); } } +void RTFDocumentImpl::bufferOrSend(RTFBufferTypes eType, RTFValue::Pointer_t const& pValue) +{ + RTFBuffer_t* pCurrentBuffer = m_aStates.empty() ? nullptr : m_aStates.top().getCurrentBuffer(); + if (pCurrentBuffer) + pCurrentBuffer->emplace_back(eType, pValue, nullptr); + else + sendBufferEntry(Buf_t(eType, pValue, nullptr)); +} + +void RTFDocumentImpl::sendBufferEntry(Buf_t aTuple) +{ + if (std::get<0>(aTuple) == RTFBufferTypes::Props + || std::get<0>(aTuple) == RTFBufferTypes::PropsChar) + { + // Construct properties via getProperties() and not directly, to take care of deduplication. + writerfilter::Reference::Pointer_t const pProp( + getProperties(std::get<1>(aTuple)->getAttributes(), std::get<1>(aTuple)->getSprms(), + std::get<0>(aTuple) == RTFBufferTypes::PropsChar + ? NS_ooxml::LN_Value_ST_StyleType_character + : 0, + std::get<0>(aTuple) == RTFBufferTypes::PropsChar)); + Mapper().props(pProp); + } + else if (std::get<0>(aTuple) == RTFBufferTypes::NestRow) + { + TableRowBuffer& rRowBuffer(*std::get<2>(aTuple)); + + replayRowBuffer(rRowBuffer.GetBuffer(), rRowBuffer.GetCellsSprms(), + rRowBuffer.GetCellsAttributes(), rRowBuffer.GetCells()); + + sendProperties(rRowBuffer.GetParaProperties(), rRowBuffer.GetFrameProperties(), + rRowBuffer.GetRowProperties()); + } + else if (std::get<0>(aTuple) == RTFBufferTypes::StartRun) + Mapper().startCharacterGroup(); + else if (std::get<0>(aTuple) == RTFBufferTypes::Text) + { + sal_uInt8 const nValue = std::get<1>(aTuple)->getInt(); + Mapper().text(&nValue, 1); + } + else if (std::get<0>(aTuple) == RTFBufferTypes::UText) + { + OUString const aString(std::get<1>(aTuple)->getString()); + Mapper().utext(aString.getStr(), aString.getLength()); + } + else if (std::get<0>(aTuple) == RTFBufferTypes::EndRun) + Mapper().endCharacterGroup(); + else if (std::get<0>(aTuple) == RTFBufferTypes::PAR) + parBreak(); + else if (std::get<0>(aTuple) == RTFBufferTypes::StartShape) + { + // The shape belongs where the replay stands, not in the buffer being replayed. + RTFBuffer_t* pCurrentBuffer = m_aStates.top().getCurrentBuffer(); + m_aStates.top().setCurrentBuffer(nullptr); + m_pSdrImport->resolve(std::get<1>(aTuple)->getShape(), false, RTFSdrImport::SHAPE); + m_aStates.top().setCurrentBuffer(pCurrentBuffer); + } + else if (std::get<0>(aTuple) == RTFBufferTypes::ResolveShape) + { + // Make sure there is no current buffer while replaying the shape, + // otherwise it gets re-buffered. + RTFBuffer_t* pCurrentBuffer = m_aStates.top().getCurrentBuffer(); + m_aStates.top().setCurrentBuffer(nullptr); + + // Set current shape during replay, needed by e.g. wrap in + // background. + RTFShape aShape = m_aStates.top().getShape(); + m_aStates.top().getShape() = std::get<1>(aTuple)->getShape(); + + m_pSdrImport->resolve(std::get<1>(aTuple)->getShape(), true, RTFSdrImport::SHAPE); + m_aStates.top().getShape() = std::move(aShape); + m_aStates.top().setCurrentBuffer(pCurrentBuffer); + } + else if (std::get<0>(aTuple) == RTFBufferTypes::EndShape) + m_pSdrImport->close(); + else if (std::get<0>(aTuple) == RTFBufferTypes::InsertShape) + { + uno::Reference xShape; + std::get<1>(aTuple)->getAny() >>= xShape; + if (xShape.is()) + m_pSdrImport->open(xShape); + } + else if (std::get<0>(aTuple) == RTFBufferTypes::ResolveSubstream) + { + RTFSprms& rAttributes = std::get<1>(aTuple)->getAttributes(); + std::size_t nPos = rAttributes.find(0)->getInt(); + Id nId = rAttributes.find(1)->getInt(); + OUString aCustomMark = rAttributes.find(2)->getString(); + resolveSubstream(nPos, nId, aCustomMark); + } + else if (std::get<0>(aTuple) == RTFBufferTypes::Picture) + m_aStates.top().getPicture() = std::get<1>(aTuple)->getPicture(); + else if (std::get<0>(aTuple) == RTFBufferTypes::SetStyle) + { + if (!m_aStates.empty()) + m_aStates.top().setCurrentStyleIndex(std::get<1>(aTuple)->getInt()); + } + else + assert(false); +} + bool findPropertyName(const std::vector& rProperties, const OUString& rName) { return std::any_of( @@ -3813,7 +3795,9 @@ void RTFDocumentImpl::afterPopState(RTFParserState& rState) && !m_aStates.top().getDrawingObject().getHadShapeText()) { m_aStates.top().setHadShapeText(true); - if (!m_aStates.top().getCurrentBuffer()) + // A replay can send a buffered start before the group ends, and the end + // has to follow it. + if (m_pSdrImport->isOpenInMapper() || !m_aStates.top().getCurrentBuffer()) m_pSdrImport->close(); else m_aStates.top().getCurrentBuffer()->emplace_back(RTFBufferTypes::EndShape, diff --git a/sw/source/writerfilter/rtftok/rtfdocumentimpl.hxx b/sw/source/writerfilter/rtftok/rtfdocumentimpl.hxx index 28b5a0738667..7be95924072c 100644 --- a/sw/source/writerfilter/rtftok/rtfdocumentimpl.hxx +++ b/sw/source/writerfilter/rtftok/rtfdocumentimpl.hxx @@ -816,6 +816,11 @@ private: void replayRowBuffer(RTFBuffer_t& rBuffer, ::std::deque& rCellsSprms, ::std::deque& rCellsAttributes, int nCells); void replayBuffer(RTFBuffer_t& rBuffer, RTFSprms* pSprms, RTFSprms const* pAttributes); + /// Sends one buffer entry to the dmapper. + void sendBufferEntry(Buf_t aTuple); + /// Appends the entry to the current buffer, or sends it to the dmapper when there is none. + void bufferOrSend(RTFBufferTypes eType, + RTFValue::Pointer_t const& pValue = RTFValue::Pointer_t()); /// If we have some unicode or hex characters to send. void checkUnicode(bool bUnicode, bool bHex); /// If we need a final section break at the end of the document. diff --git a/sw/source/writerfilter/rtftok/rtfsdrimport.cxx b/sw/source/writerfilter/rtftok/rtfsdrimport.cxx index 880e5bd9b085..c063fb4c5138 100644 --- a/sw/source/writerfilter/rtftok/rtfsdrimport.cxx +++ b/sw/source/writerfilter/rtftok/rtfsdrimport.cxx @@ -65,6 +65,7 @@ RTFSdrImport::RTFSdrImport(RTFDocumentImpl& rDocument, , m_bTextFrame(false) , m_bTextGraphicObject(false) , m_bFakePict(false) + , m_bOpenInMapper(false) { if (xDstDoc) m_aParents.push(xDstDoc->getDrawPage()); @@ -1165,10 +1166,10 @@ void RTFSdrImport::resolve(RTFShape& rShape, bool bClose, ShapeOrPict const shap // cells exist - resolvePict() defers its own output the same way. if (xShape.is() && !m_rImport.bufferShapeInsertion(xShape, bClose)) { - m_rImport.Mapper().startShape(xShape); + open(xShape); if (bClose) { - m_rImport.Mapper().endShape(); + close(); } } @@ -1186,7 +1187,17 @@ void RTFSdrImport::resolve(RTFShape& rShape, bool bClose, ShapeOrPict const shap m_xShape = std::move(xShape); } -void RTFSdrImport::close() { m_rImport.Mapper().endShape(); } +void RTFSdrImport::open(uno::Reference const& xShape) +{ + m_rImport.Mapper().startShape(xShape); + m_bOpenInMapper = true; +} + +void RTFSdrImport::close() +{ + m_rImport.Mapper().endShape(); + m_bOpenInMapper = false; +} void RTFSdrImport::append(std::u16string_view aKey, std::u16string_view aValue) { diff --git a/sw/source/writerfilter/rtftok/rtfsdrimport.hxx b/sw/source/writerfilter/rtftok/rtfsdrimport.hxx index e9f940f1ac6b..63f0acc7e399 100644 --- a/sw/source/writerfilter/rtftok/rtfsdrimport.hxx +++ b/sw/source/writerfilter/rtftok/rtfsdrimport.hxx @@ -53,6 +53,8 @@ public: PICT }; void resolve(RTFShape& rShape, bool bClose, ShapeOrPict shapeOrPict); + /// Send the start of a shape to the dmapper. It stays open there until its end is sent. + void open(css::uno::Reference const& xShape); void close(); void append(std::u16string_view aKey, std::u16string_view aValue); /// Append property on the current parent. @@ -78,6 +80,7 @@ public: void popParent(); css::uno::Reference const& getCurrentShape() const { return m_xShape; } bool isFakePict() const { return m_bFakePict; } + bool isOpenInMapper() const { return m_bOpenInMapper; } bool isTextGraphicObject() const { return m_bTextGraphicObject; } private: @@ -98,6 +101,8 @@ private: bool m_bTextGraphicObject; /// if inside \pict, but actually it's a shape (not a picture) bool m_bFakePict; + /// If a shape start has gone to the dmapper and its end has not. + bool m_bOpenInMapper; std::stack m_aGraphicZOrderHelpers; }; } // namespace writerfilter::rtftok diff --git a/sw/uiconfig/swriter/ui/asciifilterdialog.ui b/sw/uiconfig/swriter/ui/asciifilterdialog.ui index 3f126364456a..5f3f22adfa66 100644 --- a/sw/uiconfig/swriter/ui/asciifilterdialog.ui +++ b/sw/uiconfig/swriter/ui/asciifilterdialog.ui @@ -95,9 +95,9 @@ True False end - _Character set: + _Encoding: True - charset + encoding 0 @@ -146,13 +146,13 @@ - + True False True - - Specifies the character set of the file for export or import. + + Specifies the encoding of the file for export or import. diff --git a/swext/Extension_wiki-publisher.mk b/swext/Extension_wiki-publisher.mk index c258a406998a..d1753f353842 100644 --- a/swext/Extension_wiki-publisher.mk +++ b/swext/Extension_wiki-publisher.mk @@ -40,7 +40,7 @@ $(eval $(call gb_Extension_add_file,wiki-publisher,filter/math/scripts.xsl,$(gb_ $(eval $(call gb_Extension_add_file,wiki-publisher,filter/math/tables.xsl,$(gb_UnpackedTarball_workdir)/xsltml/tables.xsl)) $(eval $(call gb_Extension_add_file,wiki-publisher,filter/math/tokens.xsl,$(gb_UnpackedTarball_workdir)/xsltml/tokens.xsl)) $(eval $(call gb_Extension_add_file,wiki-publisher,filter/odt2mediawiki.xsl,$(SRCDIR)/swext/mediawiki/src/filter/odt2mediawiki.xsl)) -$(eval $(call gb_Extension_add_file,wiki-publisher,templates/MediaWiki/mediawiki.ott,$(SRCDIR)/swext/mediawiki/src/filter/mediawiki.ott)) +$(eval $(call gb_Extension_add_file,wiki-publisher,templates/MediaWiki/mediawiki.ott,$(gb_CustomTarget_workdir)/mediawiki/template/mediawiki.ott)) $(eval $(call gb_Extension_add_helpfile,wiki-publisher,$(SRCDIR)/swext/mediawiki/help,com.sun.wiki-publisher/wikisend.xhp,wikisend.xhp)) $(eval $(call gb_Extension_add_helpfile,wiki-publisher,$(SRCDIR)/swext/mediawiki/help,com.sun.wiki-publisher/wikiformats.xhp,wikiformats.xhp)) $(eval $(call gb_Extension_add_helpfile,wiki-publisher,$(SRCDIR)/swext/mediawiki/help,com.sun.wiki-publisher/wikiaccount.xhp,wikiaccount.xhp)) @@ -49,4 +49,45 @@ $(eval $(call gb_Extension_add_helpfile,wiki-publisher,$(SRCDIR)/swext/mediawiki $(eval $(call gb_Extension_add_helptreefile,wiki-publisher,$(SRCDIR)/swext/mediawiki/help,help.tree,help.tree,com.sun.wiki-publisher)) +# Build mediawiki.ott +define run_zip_template_recipe = +$(call gb_Output_announce,$(subst $(gb_CustomTarget_workdir)/mediawiki/template/,,$@),$(true),ZIP,2) +$(call gb_Trace_StartRange,$(subst $(gb_CustomTarget_workdir)/mediawiki/template/,,$@),ZIP) +cd $(dir $<) && \ +$(call gb_Helper_wsl_path,\ +$(WSL) zip -q0X --filesync --must-match $@ mimetype && \ +$(WSL) zip -qrX --must-match $@ $(subst $(dir $<),,$^) -x mimetype) \ +$(call gb_Helper_make_zip_deterministic,$@) +$(call gb_Trace_EndRange,$(subst $(gb_CustomTarget_workdir)/mediawiki/template/,,$@),ZIP) +endef + +.SECONDEXPANSION: +# secondexpansion since the patterns not just cover a filename portion, but also include a +# directory portion with different number of elements +# copy regular files (mimetype, *.jpg, *.png, *.rdf, *.svg, *.svm, …) +$(gb_CustomTarget_workdir)/mediawiki/template/% : $(SRCDIR)/swext/mediawiki/src/filter/% \ + | $$(dir $(gb_CustomTarget_workdir)/mediawiki/template/$$*).dir + $(call gb_Output_announce,templates/$*,$(true),CPY,1) + $(call gb_Trace_StartRange,templates/$*,CPY) + cp $< $@ + $(call gb_Trace_EndRange,templates/$*,CPY) + +# test and copy xml files +$(gb_CustomTarget_workdir)/mediawiki/template/%.xml : $(SRCDIR)/swext/mediawiki/src/filter/%.xml \ + | $(call gb_ExternalExecutable_get_dependencies,xsltproc) \ + $$(dir $(gb_CustomTarget_workdir)/mediawiki/template/$$*.xml).dir + $(call gb_Output_announce,templates/$*.xml,$(true),XSL,1) + $(call gb_Trace_StartRange,templates/$*.xml,XSL) + $(call gb_ExternalExecutable_get_command,xsltproc) --nonet -o $@ $(SRCDIR)/extras/util/compact.xsl $< + $(call gb_Trace_EndRange,templates/$*.xml,XSL) + + +$(gb_CustomTarget_workdir)/mediawiki/template/%.ott : \ + $$(addprefix $(gb_CustomTarget_workdir)/mediawiki/template/$$*/,\ + mimetype META-INF/manifest.xml content.xml meta.xml styles.xml \ + settings.xml manifest.rdf Thumbnails/thumbnail.png ) + $(run_zip_template_recipe) + + + # vim: set noet sw=4 ts=4: diff --git a/swext/mediawiki/src/filter/mediawiki.ott b/swext/mediawiki/src/filter/mediawiki.ott deleted file mode 100644 index eb94e9607bc0..000000000000 Binary files a/swext/mediawiki/src/filter/mediawiki.ott and /dev/null differ diff --git a/swext/mediawiki/src/filter/mediawiki/META-INF/manifest.xml b/swext/mediawiki/src/filter/mediawiki/META-INF/manifest.xml new file mode 100644 index 000000000000..cf9dc1c1a150 --- /dev/null +++ b/swext/mediawiki/src/filter/mediawiki/META-INF/manifest.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/swext/mediawiki/src/filter/mediawiki/Thumbnails/thumbnail.png b/swext/mediawiki/src/filter/mediawiki/Thumbnails/thumbnail.png new file mode 100644 index 000000000000..74927e1c1e43 Binary files /dev/null and b/swext/mediawiki/src/filter/mediawiki/Thumbnails/thumbnail.png differ diff --git a/swext/mediawiki/src/filter/mediawiki/content.xml b/swext/mediawiki/src/filter/mediawiki/content.xml new file mode 100644 index 000000000000..11ed62e16841 --- /dev/null +++ b/swext/mediawiki/src/filter/mediawiki/content.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swext/mediawiki/src/filter/mediawiki/manifest.rdf b/swext/mediawiki/src/filter/mediawiki/manifest.rdf new file mode 100644 index 000000000000..927e206bb226 --- /dev/null +++ b/swext/mediawiki/src/filter/mediawiki/manifest.rdf @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/swext/mediawiki/src/filter/mediawiki/meta.xml b/swext/mediawiki/src/filter/mediawiki/meta.xml new file mode 100644 index 000000000000..bd5ec9bca52b --- /dev/null +++ b/swext/mediawiki/src/filter/mediawiki/meta.xml @@ -0,0 +1,18 @@ + + + + OpenOffice.org/2.0$Linux OpenOffice.org_project/680m3$Build-8968 + Bernhard Haumacher + 2006-02-08T21:28:00 + Bernhard Haumacher + 2006-02-08T21:28:29 + MediaWiki + 2 + PT29S + + + + + + + diff --git a/swext/mediawiki/src/filter/mediawiki/mimetype b/swext/mediawiki/src/filter/mediawiki/mimetype new file mode 100644 index 000000000000..6a297d887997 --- /dev/null +++ b/swext/mediawiki/src/filter/mediawiki/mimetype @@ -0,0 +1 @@ +application/vnd.oasis.opendocument.text-template \ No newline at end of file diff --git a/swext/mediawiki/src/filter/mediawiki/settings.xml b/swext/mediawiki/src/filter/mediawiki/settings.xml new file mode 100644 index 000000000000..8072c89dddaf --- /dev/null +++ b/swext/mediawiki/src/filter/mediawiki/settings.xml @@ -0,0 +1,23 @@ + + + + + high-resolution + true + false + false + false + true + true + false + false + false + false + true + true + false + true + true + + + diff --git a/swext/mediawiki/src/filter/mediawiki/styles.xml b/swext/mediawiki/src/filter/mediawiki/styles.xml new file mode 100644 index 000000000000..e8b88ccb7d40 --- /dev/null +++ b/swext/mediawiki/src/filter/mediawiki/styles.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toolkit/Library_tk.mk b/toolkit/Library_tk.mk index b5db9d5196c4..47051fb3d521 100644 --- a/toolkit/Library_tk.mk +++ b/toolkit/Library_tk.mk @@ -110,9 +110,9 @@ $(eval $(call gb_Library_add_exception_objects,tk,\ toolkit/source/controls/table/defaultinputhandler \ toolkit/source/controls/table/gridtablerenderer \ toolkit/source/controls/table/mousefunction \ - toolkit/source/controls/table/tablecontrol \ - toolkit/source/controls/table/tablecontrol_impl \ + toolkit/source/controls/table/TableControl \ toolkit/source/controls/table/tabledatawindow \ + toolkit/source/controls/table/TableFunctionSet \ toolkit/source/controls/table/tablegeometry \ toolkit/source/controls/tree/treecontrol \ toolkit/source/controls/tree/treecontrolpeer \ diff --git a/toolkit/inc/awt/vclxwindows.hxx b/toolkit/inc/awt/vclxwindows.hxx index 3f8035b19385..8329a5da1936 100644 --- a/toolkit/inc/awt/vclxwindows.hxx +++ b/toolkit/inc/awt/vclxwindows.hxx @@ -524,8 +524,8 @@ public: }; namespace svt::table { - class TableControl; - class UnoControlTableModel; +class TableControl; +class UnoControlTableModel; } typedef ::cppu::ImplInheritanceHelper < VCLXWindow @@ -588,8 +588,10 @@ private: void impl_updateColumnsFromModel_nothrow(); void impl_checkTableModelInit(); - void impl_checkColumnIndex_throw( ::svt::table::TableControl const & i_table, sal_Int32 const i_columnIndex ) const; - void impl_checkRowIndex_throw( ::svt::table::TableControl const & i_table, sal_Int32 const i_rowIndex ) const; + void impl_checkColumnIndex_throw(svt::table::TableControl const& i_table, + sal_Int32 const i_columnIndex) const; + void impl_checkRowIndex_throw(svt::table::TableControl const& i_table, + sal_Int32 const i_rowIndex) const; virtual void ProcessWindowEvent( const VclWindowEvent& rVclWindowEvent ) override; void ImplCallItemListeners(); diff --git a/toolkit/inc/controls/table/AccessibleGridControl.hxx b/toolkit/inc/controls/table/AccessibleGridControl.hxx index 9d00ff3f3ac1..37640e5d8cd7 100644 --- a/toolkit/inc/controls/table/AccessibleGridControl.hxx +++ b/toolkit/inc/controls/table/AccessibleGridControl.hxx @@ -22,7 +22,7 @@ #include "AccessibleGridControlBase.hxx" #include "AccessibleGridControlHeader.hxx" #include "AccessibleGridControlTable.hxx" -#include "tablecontrol.hxx" +#include "TableControl.hxx" #include #include diff --git a/toolkit/inc/controls/table/AccessibleGridControlBase.hxx b/toolkit/inc/controls/table/AccessibleGridControlBase.hxx index fc65053ea1e2..bcfda65cebaf 100644 --- a/toolkit/inc/controls/table/AccessibleGridControlBase.hxx +++ b/toolkit/inc/controls/table/AccessibleGridControlBase.hxx @@ -20,7 +20,8 @@ #pragma once -#include +#include +#include #include #include @@ -157,7 +158,7 @@ protected: /** The parent accessible object. */ rtl::Reference m_pParent; /** The SVT Table control. */ - svt::table::TableControl& m_aTable; + svt::table::TableControl& m_rTable; /** The type of this object (for names, descriptions, state sets, ...). */ AccessibleTableControlObjType m_eObjType; }; diff --git a/toolkit/inc/controls/table/AccessibleGridControlHeader.hxx b/toolkit/inc/controls/table/AccessibleGridControlHeader.hxx index 8fae1ca25613..1d8062df2259 100644 --- a/toolkit/inc/controls/table/AccessibleGridControlHeader.hxx +++ b/toolkit/inc/controls/table/AccessibleGridControlHeader.hxx @@ -21,7 +21,7 @@ #pragma once #include -#include +#include namespace accessibility { diff --git a/toolkit/inc/controls/table/AccessibleGridControlHeaderCell.hxx b/toolkit/inc/controls/table/AccessibleGridControlHeaderCell.hxx index da8086d0dd94..585a770a49b5 100644 --- a/toolkit/inc/controls/table/AccessibleGridControlHeaderCell.hxx +++ b/toolkit/inc/controls/table/AccessibleGridControlHeaderCell.hxx @@ -20,7 +20,7 @@ #include #include -#include +#include namespace accessibility { diff --git a/toolkit/inc/controls/table/AccessibleGridControlTableBase.hxx b/toolkit/inc/controls/table/AccessibleGridControlTableBase.hxx index 6e2683efd1dc..79b990802a4d 100644 --- a/toolkit/inc/controls/table/AccessibleGridControlTableBase.hxx +++ b/toolkit/inc/controls/table/AccessibleGridControlTableBase.hxx @@ -21,7 +21,7 @@ #pragma once #include -#include +#include #include #include diff --git a/toolkit/inc/controls/table/AccessibleGridControlTableCell.hxx b/toolkit/inc/controls/table/AccessibleGridControlTableCell.hxx index f767eefc1436..d6c88e4598d1 100644 --- a/toolkit/inc/controls/table/AccessibleGridControlTableCell.hxx +++ b/toolkit/inc/controls/table/AccessibleGridControlTableCell.hxx @@ -19,7 +19,7 @@ #pragma once #include -#include +#include #include #include diff --git a/toolkit/inc/controls/table/AccessibleTableControlObjType.hxx b/toolkit/inc/controls/table/AccessibleTableControlObjType.hxx new file mode 100644 index 000000000000..38e3dc93d4f7 --- /dev/null +++ b/toolkit/inc/controls/table/AccessibleTableControlObjType.hxx @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 . + */ + +#pragma once + +#include + +enum class AccessibleTableControlObjType +{ + GRIDCONTROL, /// The GridControl itself. + TABLE, /// The data table. + ROWHEADERBAR, /// The row header bar. + COLUMNHEADERBAR, /// The horizontal column header bar. + TABLECELL, /// A cell of the data table. + ROWHEADERCELL, /// A cell of the row header bar. + COLUMNHEADERCELL, /// A cell of the column header bar. +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/toolkit/source/controls/table/tablecontrol_impl.hxx b/toolkit/inc/controls/table/TableControl.hxx similarity index 65% rename from toolkit/source/controls/table/tablecontrol_impl.hxx rename to toolkit/inc/controls/table/TableControl.hxx index bb667db3b506..0305c4e66ac1 100644 --- a/toolkit/source/controls/table/tablecontrol_impl.hxx +++ b/toolkit/inc/controls/table/TableControl.hxx @@ -19,11 +19,13 @@ #pragma once -#include +#include +#include #include #include #include +#include #include #include @@ -31,6 +33,11 @@ class ScrollBar; class ScrollBarBox; +namespace accessibility +{ +class AccessibleGridControl; +} + namespace svt::table { struct MutableColumnMetrics : public ColumnMetrics @@ -67,13 +74,24 @@ struct ColumnInfoPositionLess typedef ::std::vector ColumnPositions; -class TableControl; -class TableDataWindow; class TableFunctionSet; -//= TableControl_Impl +/** a basic control which manages table-like data, i.e. a number of cells + organized in m rows and n columns. -class TableControl_Impl : public ITableControl, public ITableModelListener + The control itself does not do any assumptions about the concrete data + it displays, this is encapsulated in an instance supporting the + ->ITableModel interface. + + Also, the control does not do any assumptions about how the model's + content is rendered. This is the responsibility of a component + supporting the ->ITableRenderer interface (the renderer is obtained from + the model). + + The control supports the concept of a current (or active + cell). +*/ +class TableControl final : public Control, public ITableModelListener { friend class TableGeometry; friend class TableRowGeometry; @@ -81,8 +99,6 @@ class TableControl_Impl : public ITableControl, public ITableModelListener friend class SuspendInvariants; private: - /// the control whose impl-instance we implement - TableControl& m_rAntiImpl; /// the model of the table control PTableModel m_pModel; /// the input handler to use, usually the input handler as provided by ->m_pModel @@ -103,10 +119,10 @@ private: /// the number of rows in the table control. Cached model value. TableSize m_nRowCount; - ColPos m_nCurColumn; - RowPos m_nCurRow; - ColPos m_nLeftColumn; - RowPos m_nTopRow; + sal_Int32 m_nCurColumn; + sal_Int32 m_nCurRow; + sal_Int32 m_nLeftColumn; + sal_Int32 m_nTopRow; sal_Int32 m_nCursorHidden; @@ -124,39 +140,50 @@ private: //selection engine - for determining selection range, e.g. single, multiple std::unique_ptr m_pSelEngine; //vector which contains the selected rows - std::vector m_aSelectedRows; + std::vector m_aSelectedRows; //part of selection engine std::unique_ptr m_pTableFunctionSet; //part of selection engine - RowPos m_nAnchor; + sal_Int32 m_nAnchor; bool m_bUpdatingColWidths; rtl::Reference m_xAccessibleTable; public: - void setModel(const PTableModel& _pModel); + /// sets a new table model + void SetModel(const PTableModel& _pModel); const PTableInputHandler& getInputHandler() const { return m_pInputHandler; } - RowPos getCurRow() const { return m_nCurRow; } + sal_Int32 getCurRow() const { return m_nCurRow; } - RowPos getAnchor() const { return m_nAnchor; } - void setAnchor(RowPos const i_anchor) { m_nAnchor = i_anchor; } + sal_Int32 getAnchor() const { return m_nAnchor; } + void setAnchor(sal_Int32 const i_anchor) { m_nAnchor = i_anchor; } - RowPos getTopRow() const { return m_nTopRow; } - ColPos getLeftColumn() const { return m_nLeftColumn; } - - const TableControl& getAntiImpl() const { return m_rAntiImpl; } - TableControl& getAntiImpl() { return m_rAntiImpl; } + sal_Int32 getTopRow() const { return m_nTopRow; } + sal_Int32 getLeftColumn() const { return m_nLeftColumn; } public: - explicit TableControl_Impl(TableControl& _rAntiImpl); - virtual ~TableControl_Impl() override; + explicit TableControl(vcl::Window* pParent, WinBits nStyle); + virtual ~TableControl() override; + virtual void dispose() override; + + // Window overridables + virtual void GetFocus() override; + virtual void LoseFocus() override; + virtual void KeyInput(const KeyEvent& rKEvt) override; + virtual void StateChanged(StateChangedType i_nStateChange) override; + virtual void Resize() override; + + /** Creates and returns the accessible object of the whole GridControl. */ + virtual rtl::Reference CreateAccessible() override; /** to be called when the anti-impl instance has been resized */ void onResize(); + void Select(); + /** paints the table control content which intersects with the given rectangle */ void doPaintContent(vcl::RenderContext& rRenderContext, const tools::Rectangle& _rUpdateRect); @@ -165,7 +192,7 @@ public: To ease the caller's code, the coordinates must not necessarily denote a valid position. If they don't, will be returned. */ - bool goTo(ColPos _nColumn, RowPos _nRow); + bool GoToCell(sal_Int32 _nColumn, sal_Int32 _nRow); /** ensures that the given coordinate is visible @param _nColumn @@ -175,19 +202,20 @@ public: the row position which should be visibleMust be non-negative, and smaller than the row count. */ - void ensureVisible(ColPos _nColumn, RowPos _nRow); + void ensureVisible(sal_Int32 _nColumn, sal_Int32 _nRow); /** retrieves the content of the given cell, converted to a string */ - OUString getCellContentAsString(RowPos const i_row, ColPos const i_col); + OUString GetAccessibleCellText(sal_Int32 const i_row, sal_Int32 const i_col); /** returns the position of the current row in the selection vector */ - static int getRowSelectedNumber(const ::std::vector& selectedRows, RowPos current); + static int getRowSelectedNumber(const ::std::vector& selectedRows, + sal_Int32 current); void invalidateRect(const tools::Rectangle& rInvalidateRect); /** ??? */ - void invalidateSelectedRegion(RowPos _nPrevRow, RowPos _nCurRow); + void invalidateSelectedRegion(sal_Int32 _nPrevRow, sal_Int32 _nCurRow); /** invalidates the part of the data window which is covered by the given rows @param i_firstRow @@ -196,32 +224,40 @@ public: the index of the last row to include in the invalidation, or ROW_INVALID if the invalidation should happen down to the bottom of the data window. */ - void invalidateRowRange(RowPos const i_firstRow, RowPos const i_lastRow); + void invalidateRowRange(sal_Int32 const i_firstRow, sal_Int32 const i_lastRow); /** invalidates the part of the data window which is covered by the given row */ - void invalidateRow(RowPos const i_row) { invalidateRowRange(i_row, i_row); } + void invalidateRow(sal_Int32 const i_row) { invalidateRowRange(i_row, i_row); } /** invalidates all selected rows */ void invalidateSelectedRows(); void checkCursorPosition(); + sal_Int32 GetRowCount() const; + sal_Int32 GetColumnCount() const; + + OUString GetRowName(sal_Int32 nIndex) const; + OUString GetColumnName(sal_Int32 nIndex) const; + bool HasRowHeader(); + bool HasColumnHeader(); + bool hasRowSelection() const { return !m_aSelectedRows.empty(); } - size_t getSelectedRowCount() const { return m_aSelectedRows.size(); } - RowPos getSelectedRowIndex(size_t const i_selectionIndex) const; + size_t GetSelectedRowCount() const { return m_aSelectedRows.size(); } + sal_Int32 GetSelectedRowIndex(size_t const i_selectionIndex) const; /** removes the given row index from m_aSelectedRows @return if and only if the row was previously marked as selected */ - bool markRowAsDeselected(RowPos const i_rowIndex); + bool markRowAsDeselected(sal_Int32 const i_rowIndex); /** marks the given row as selected, by putting it into m_aSelectedRows @return if and only if the row was previously not marked as selected */ - bool markRowAsSelected(RowPos const i_rowIndex); + bool markRowAsSelected(sal_Int32 const i_rowIndex); /** marks all rows as deselected @return @@ -236,14 +272,30 @@ public: bool markAllRowsAsSelected(); void commitAccessibleEvent(sal_Int16 const i_eventID); + // temporary methods + // Those do not really belong into the public API - they're intended for firing A11Y-related events. However, + // firing those events should be an implementation internal to the TableControl, + // instead of something triggered externally. void commitCellEvent(sal_Int16 const i_eventID, const css::uno::Any& i_newValue, const css::uno::Any& i_oldValue); void commitTableEvent(sal_Int16 const i_eventID, const css::uno::Any& i_newValue, const css::uno::Any& i_oldValue); - // ITableControl - virtual void hideCursor() override; - virtual void showCursor() override; + /** hides the cell cursor + + The method cares for successive calls, that is, for every call to + ->hideCursor(), you need one call to ->showCursor. Only if the number + of both calls matches, the cursor is really shown. + + @see showCursor + */ + void hideCursor(); + + /** shows the cell cursor + + @see hideCursor + */ + void showCursor(); /** dispatches an action to the table control @@ -257,25 +309,78 @@ public: */ bool dispatchAction(TableControlAction _eAction); - virtual SelectionEngine* getSelEngine() override; - virtual PTableModel getModel() const override; - virtual ColPos getCurrentColumn() const override; - virtual RowPos getCurrentRow() const override; - virtual void activateCell(ColPos const i_col, RowPos const i_row) override; - virtual ::Size getTableSizePixel() const override; - virtual void setPointer(PointerStyle i_pointer) override; - virtual void captureMouse() override; - virtual void releaseMouse() override; - virtual void invalidate(TableArea const i_what) override; - virtual tools::Long pixelWidthToAppFont(tools::Long const i_pixels) const override; - virtual void hideTracking() override; - virtual void showTracking(tools::Rectangle const& i_location, - ShowTrackFlags const i_flags) override; - RowPos getRowAtPoint(const Point& rPoint) const; - ColPos getColAtPoint(const Point& rPoint) const; - virtual TableCell hitTest(const Point& rPoint) const override; - virtual ColumnMetrics getColumnMetrics(ColPos const i_column) const override; - virtual bool isRowSelected(RowPos i_row) const override; + /** returns selection engine*/ + SelectionEngine* getSelEngine(); + + /** returns the table model + + The returned model is guaranteed to not be . + */ + PTableModel GetModel() const; + + /** retrieves the current column + + The current col is the one which contains the active cell. + + @return + the column index of the active cell, or ->COL_INVALID + if there is no active cell, e.g. because the table does + not contain any rows or columns. + */ + sal_Int32 GetCurrentColumn() const; + + /** retrieves the current row + + The current row is the one which contains the active cell. + + @return + the row index of the active cell, or ->ROW_INVALID + if there is no active cell, e.g. because the table does + not contain any rows or columns. + */ + sal_Int32 GetCurrentRow() const; + + /// activates the given cell + void activateCell(sal_Int32 const i_col, sal_Int32 const i_row); + + /// retrieves the size of the table window, in pixels + Size getTableSizePixel() const; + + /// sets a new mouse pointer for the table window + void setPointer(PointerStyle i_pointer); + + /// captures the mouse to the table window + void captureMouse(); + + /// releases the mouse, after it had previously been captured + void releaseMouse(); + + /// invalidates the table window + void invalidate(TableArea const i_what); + + /// calculates a width, given in pixels, into an AppFont-based width + tools::Long pixelWidthToAppFont(tools::Long const i_pixels) const; + + /// hides a previously shown tracking rectangle + void hideTracking(); + + /// shows a tracking rectangle + void showTracking(tools::Rectangle const& i_location, ShowTrackFlags const i_flags); + + sal_Int32 getRowAtPoint(const Point& rPoint) const; + sal_Int32 getColAtPoint(const Point& rPoint) const; + + /// does a hit test for the given pixel coordinates + TableCell hitTest(const Point& rPoint) const; + + /// retrieves the metrics for a given column + ColumnMetrics getColumnMetrics(sal_Int32 const i_column) const; + + /// determines whether a given row is selected + bool IsRowSelected(sal_Int32 i_row) const; + + void SelectRow(sal_Int32 nRowIndex, bool bSelect); + void SelectAllRows(bool bSelect); tools::Long appFontWidthToPixel(tools::Long const i_appFontUnits) const; @@ -284,6 +389,7 @@ public: ScrollBar* getHorzScrollbar() { return m_pHScroll; } ScrollBar* getVertScrollbar() { return m_pVScroll; } + bool ConvertPointToCellAddress(sal_Int32& rRow, sal_Int32& rColPos, const Point& rPoint); tools::Rectangle calcHeaderRect(bool bColHeader); tools::Rectangle calcHeaderCellRect(bool bColHeader, sal_Int32 nPos); tools::Rectangle calcTableRect() const; @@ -293,15 +399,22 @@ public: const rtl::Reference& getAccessible(const rtl::Reference& rpParent); void disposeAccessible(); + sal_Int32 GetAccessibleControlCount() const; + OUString GetAccessibleObjectName(AccessibleTableControlObjType eObjType, sal_Int32 nRow, + sal_Int32 nCol) const; + OUString GetAccessibleObjectDescription(AccessibleTableControlObjType eObjType) const; + void FillAccessibleStateSet(sal_Int64& rStateSet, AccessibleTableControlObjType eObjType) const; + void FillAccessibleStateSetForCell(sal_Int64& rStateSet, sal_Int32 nRow, + sal_uInt16 nColumnPos) const; // ITableModelListener - virtual void rowsInserted(RowPos first, RowPos last) override; - virtual void rowsRemoved(RowPos first, RowPos last) override; + virtual void rowsInserted(sal_Int32 first, sal_Int32 last) override; + virtual void rowsRemoved(sal_Int32 first, sal_Int32 last) override; virtual void columnInserted() override; virtual void columnRemoved() override; virtual void allColumnsRemoved() override; - virtual void cellsUpdated(RowPos const i_firstRow, RowPos const i_lastRow) override; - virtual void columnChanged(ColPos const i_column, + virtual void cellsUpdated(sal_Int32 const i_firstRow, sal_Int32 const i_lastRow) override; + virtual void columnChanged(sal_Int32 const i_column, ColumnAttributeGroup const i_attributeGroup) override; virtual void tableMetricsChanged() override; @@ -356,7 +469,7 @@ private: the index of a column up to which all columns should be considered as inflexible, or COL_INVALID. */ - void impl_ni_relayout(ColPos const i_assumeInflexibleColumnsUpToIncluding = COL_INVALID); + void impl_ni_relayout(sal_Int32 const i_assumeInflexibleColumnsUpToIncluding = COL_INVALID); /** calculates the new width of our columns, taking into account their min and max widths, and their relative flexibility. @@ -374,7 +487,7 @@ private: the overall width of the grid, which is available for columns */ tools::Long - impl_ni_calculateColumnWidths(ColPos const i_assumeInflexibleColumnsUpToIncluding, + impl_ni_calculateColumnWidths(sal_Int32 const i_assumeInflexibleColumnsUpToIncluding, bool const i_assumeVerticalScrollbar, ::std::vector& o_newColWidthsPixel) const; @@ -432,37 +545,19 @@ private: tools::Rectangle impl_getAllVisibleDataCellArea() const; /** retrieves the column which covers the given ordinate */ - ColPos impl_getColumnForOrdinate(tools::Long const i_ordinate) const; + sal_Int32 impl_getColumnForOrdinate(tools::Long const i_ordinate) const; /** retrieves the row which covers the given abscissa */ - RowPos impl_getRowForAbscissa(tools::Long const i_abscissa) const; + sal_Int32 impl_getRowForAbscissa(tools::Long const i_abscissa) const; /// invalidates the window area occupied by the given column - void impl_invalidateColumn(ColPos const i_column); + void impl_invalidateColumn(sal_Int32 const i_column); DECL_LINK(OnScroll, ScrollBar*, void); DECL_LINK(OnUpdateScrollbars, void*, void); -}; -//see seleng.hxx, seleng.cxx, FunctionSet overridables, part of selection engine -class TableFunctionSet : public FunctionSet -{ -private: - TableControl_Impl* m_pTableControl; - RowPos m_nCurrentRow; - -public: - explicit TableFunctionSet(TableControl_Impl* _pTableControl); - virtual ~TableFunctionSet() override; - - virtual void BeginDrag() override; - virtual void CreateAnchor() override; - virtual void DestroyAnchor() override; - virtual void SetCursorAtPoint(const Point& rPoint, bool bDontSelectAtCursor = false) override; - virtual bool IsSelectionAtPoint(const Point& rPoint) override; - virtual void DeselectAtPoint(const Point& rPoint) override; - virtual void DeselectAll() override; + DECL_LINK(ImplSelectHdl, LinkParamNone*, void); }; } // namespace svt::table diff --git a/toolkit/inc/controls/table/TableFunctionSet.hxx b/toolkit/inc/controls/table/TableFunctionSet.hxx new file mode 100644 index 000000000000..d5b65a1be610 --- /dev/null +++ b/toolkit/inc/controls/table/TableFunctionSet.hxx @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 . + */ + +#pragma once + +#include + +#include + +namespace svt::table +{ +class TableControl; + +//see seleng.hxx, seleng.cxx, FunctionSet overridables, part of selection engine +class TableFunctionSet : public FunctionSet +{ +private: + TableControl& m_rTableControl; + sal_Int32 m_nCurrentRow; + +public: + explicit TableFunctionSet(TableControl& rTableControl); + virtual ~TableFunctionSet() override; + + virtual void BeginDrag() override; + virtual void CreateAnchor() override; + virtual void DestroyAnchor() override; + virtual void SetCursorAtPoint(const Point& rPoint, bool bDontSelectAtCursor = false) override; + virtual bool IsSelectionAtPoint(const Point& rPoint) override; + virtual void DeselectAtPoint(const Point& rPoint) override; + virtual void DeselectAll() override; +}; + +} // namespace svt::table + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/toolkit/inc/controls/table/defaultinputhandler.hxx b/toolkit/inc/controls/table/defaultinputhandler.hxx index d4ecf4335f3e..364052d4388b 100644 --- a/toolkit/inc/controls/table/defaultinputhandler.hxx +++ b/toolkit/inc/controls/table/defaultinputhandler.hxx @@ -29,33 +29,33 @@ class MouseEvent; namespace svt::table { +class TableControl; - class DefaultInputHandler final - { - public: - DefaultInputHandler(); - ~DefaultInputHandler(); +class DefaultInputHandler final +{ +public: + DefaultInputHandler(); + ~DefaultInputHandler(); - // all those methods have the same semantics as the equal-named methods of ->Window, - // with the additional option to return a boolean value indicating whether - // the event should be further processed by the ->Window implementations (), - // or whether it has been sufficiently handled by this class (). - bool MouseMove(ITableControl& _rControl, const MouseEvent& rMEvt); - bool MouseButtonDown(ITableControl& _rControl, const MouseEvent& rMEvt); - bool MouseButtonUp(ITableControl& _rControl, const MouseEvent& rMEvt); + // all those methods have the same semantics as the equal-named methods of ->Window, + // with the additional option to return a boolean value indicating whether + // the event should be further processed by the ->Window implementations (), + // or whether it has been sufficiently handled by this class (). + bool MouseMove(TableControl& _rControl, const MouseEvent& rMEvt); + bool MouseButtonDown(TableControl& _rControl, const MouseEvent& rMEvt); + bool MouseButtonUp(TableControl& _rControl, const MouseEvent& rMEvt); - private: - bool delegateMouseEvent( ITableControl& i_control, const MouseEvent& i_event, - FunctionResult ( MouseFunction::*i_handlerMethod )( ITableControl&, const MouseEvent& ) ); +private: + bool delegateMouseEvent(TableControl& i_control, const MouseEvent& i_event, + FunctionResult (MouseFunction::*i_handlerMethod)(TableControl&, + const MouseEvent&)); - rtl::Reference< MouseFunction > pActiveFunction; - std::vector< rtl::Reference< MouseFunction > > aMouseFunctions; - }; + rtl::Reference pActiveFunction; + std::vector> aMouseFunctions; +}; - typedef std::shared_ptr PTableInputHandler; +typedef std::shared_ptr PTableInputHandler; } // namespace svt::table - - /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/toolkit/inc/controls/table/gridtablerenderer.hxx b/toolkit/inc/controls/table/gridtablerenderer.hxx index e2634b2d260f..9688bbcdc9eb 100644 --- a/toolkit/inc/controls/table/gridtablerenderer.hxx +++ b/toolkit/inc/controls/table/gridtablerenderer.hxx @@ -69,19 +69,18 @@ namespace svt::table vcl::RenderContext& _rDevice, const tools::Rectangle& _rArea, bool _bIsColHeaderArea, bool _bIsRowHeaderArea, const StyleSettings& _rStyle ) override; - virtual void PaintColumnHeader( ColPos _nCol, - vcl::RenderContext& _rDevice, const tools::Rectangle& _rArea, - const StyleSettings& _rStyle ) override; - virtual void PrepareRow( RowPos _nRow, bool i_hasControlFocus, bool _bSelected, - vcl::RenderContext& _rDevice, const tools::Rectangle& _rRowArea, - const StyleSettings& _rStyle ) override; + virtual void PaintColumnHeader(sal_Int32 _nCol, vcl::RenderContext& _rDevice, + const tools::Rectangle& _rArea, + const StyleSettings& _rStyle) override; + virtual void PrepareRow(sal_Int32 _nRow, bool i_hasControlFocus, bool _bSelected, + vcl::RenderContext& _rDevice, const tools::Rectangle& _rRowArea, + const StyleSettings& _rStyle) override; virtual void PaintRowHeader( vcl::RenderContext& _rDevice, const tools::Rectangle& _rArea, const StyleSettings& _rStyle ) override; - virtual void PaintCell( ColPos const i_col, - bool i_hasControlFocus, bool _bSelected, - vcl::RenderContext& _rDevice, const tools::Rectangle& _rArea, - const StyleSettings& _rStyle ) override; + virtual void PaintCell(sal_Int32 const i_col, bool i_hasControlFocus, bool _bSelected, + vcl::RenderContext& _rDevice, const tools::Rectangle& _rArea, + const StyleSettings& _rStyle) override; virtual void ShowCellCursor( vcl::Window& _rView, const tools::Rectangle& _rCursorRect) override; virtual void HideCellCursor( vcl::Window& _rView ) override; virtual bool FitsIntoCell( diff --git a/toolkit/inc/controls/table/mousefunction.hxx b/toolkit/inc/controls/table/mousefunction.hxx index ef68dc89cea4..cd7a4ffa48e9 100644 --- a/toolkit/inc/controls/table/mousefunction.hxx +++ b/toolkit/inc/controls/table/mousefunction.hxx @@ -27,11 +27,11 @@ class MouseEvent; namespace svt::table { -class ITableControl; +class TableControl; //= FunctionResult -enum FunctionResult +enum class FunctionResult { ActivateFunction, ContinueFunction, @@ -48,11 +48,11 @@ public: MouseFunction() {} MouseFunction(const MouseFunction&) = delete; MouseFunction& operator=(const MouseFunction&) = delete; - virtual FunctionResult handleMouseMove(ITableControl& i_tableControl, MouseEvent const& i_event) + virtual FunctionResult handleMouseMove(TableControl& i_tableControl, MouseEvent const& i_event) = 0; - virtual FunctionResult handleMouseDown(ITableControl& i_tableControl, MouseEvent const& i_event) + virtual FunctionResult handleMouseDown(TableControl& i_tableControl, MouseEvent const& i_event) = 0; - virtual FunctionResult handleMouseUp(ITableControl& i_tableControl, MouseEvent const& i_event) + virtual FunctionResult handleMouseUp(TableControl& i_tableControl, MouseEvent const& i_event) = 0; protected: @@ -71,15 +71,15 @@ public: public: // MouseFunction - virtual FunctionResult handleMouseMove(ITableControl& i_tableControl, + virtual FunctionResult handleMouseMove(TableControl& i_tableControl, MouseEvent const& i_event) override; - virtual FunctionResult handleMouseDown(ITableControl& i_tableControl, + virtual FunctionResult handleMouseDown(TableControl& i_tableControl, MouseEvent const& i_event) override; - virtual FunctionResult handleMouseUp(ITableControl& i_tableControl, + virtual FunctionResult handleMouseUp(TableControl& i_tableControl, MouseEvent const& i_event) override; private: - ColPos m_nResizingColumn; + sal_Int32 m_nResizingColumn; }; //= RowSelection @@ -94,11 +94,11 @@ public: public: // MouseFunction - virtual FunctionResult handleMouseMove(ITableControl& i_tableControl, + virtual FunctionResult handleMouseMove(TableControl& i_tableControl, MouseEvent const& i_event) override; - virtual FunctionResult handleMouseDown(ITableControl& i_tableControl, + virtual FunctionResult handleMouseDown(TableControl& i_tableControl, MouseEvent const& i_event) override; - virtual FunctionResult handleMouseUp(ITableControl& i_tableControl, + virtual FunctionResult handleMouseUp(TableControl& i_tableControl, MouseEvent const& i_event) override; private: @@ -117,15 +117,15 @@ public: public: // MouseFunction - virtual FunctionResult handleMouseMove(ITableControl& i_tableControl, + virtual FunctionResult handleMouseMove(TableControl& i_tableControl, MouseEvent const& i_event) override; - virtual FunctionResult handleMouseDown(ITableControl& i_tableControl, + virtual FunctionResult handleMouseDown(TableControl& i_tableControl, MouseEvent const& i_event) override; - virtual FunctionResult handleMouseUp(ITableControl& i_tableControl, + virtual FunctionResult handleMouseUp(TableControl& i_tableControl, MouseEvent const& i_event) override; private: - ColPos m_nActiveColumn; + sal_Int32 m_nActiveColumn; }; } // namespace svt::table diff --git a/toolkit/inc/controls/table/tablecontrol.hxx b/toolkit/inc/controls/table/tablecontrol.hxx deleted file mode 100644 index 3f6270784a9b..000000000000 --- a/toolkit/inc/controls/table/tablecontrol.hxx +++ /dev/null @@ -1,170 +0,0 @@ -/* -*- 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 . - */ - -#pragma once - -#include -#include - -#include -#include - -#include - -enum class AccessibleTableControlObjType -{ - GRIDCONTROL, /// The GridControl itself. - TABLE, /// The data table. - ROWHEADERBAR, /// The row header bar. - COLUMNHEADERBAR, /// The horizontal column header bar. - TABLECELL, /// A cell of the data table. - ROWHEADERCELL, /// A cell of the row header bar. - COLUMNHEADERCELL, /// A cell of the column header bar. -}; - -namespace svt::table -{ -class TableControl_Impl; - -//= TableControl - -/** a basic control which manages table-like data, i.e. a number of cells - organized in m rows and n columns. - - The control itself does not do any assumptions about the concrete data - it displays, this is encapsulated in an instance supporting the - ->ITableModel interface. - - Also, the control does not do any assumptions about how the model's - content is rendered. This is the responsibility of a component - supporting the ->ITableRenderer interface (the renderer is obtained from - the model). - - The control supports the concept of a current (or active - cell). -*/ -class TableControl final : public Control -{ -private: - std::shared_ptr m_pImpl; - -public: - TableControl(vcl::Window* _pParent, WinBits _nStyle); - virtual ~TableControl() override; - virtual void dispose() override; - - /// sets a new table model - void SetModel(const PTableModel& _pModel); - /// retrieves the current table model - PTableModel GetModel() const; - - /** retrieves the current row - - The current row is the one which contains the active cell. - - @return - the row index of the active cell, or ->ROW_INVALID - if there is no active cell, e.g. because the table does - not contain any rows or columns. - */ - sal_Int32 GetCurrentRow() const; - - /** retrieves the current column - - The current col is the one which contains the active cell. - - @return - the column index of the active cell, or ->COL_INVALID - if there is no active cell, e.g. because the table does - not contain any rows or columns. - */ - sal_Int32 GetCurrentColumn() const; - - /** activates the cell at the given position - */ - void GoTo(ColPos _nColumnPos, RowPos _nRow); - - virtual void Resize() override; - void Select(); - - /**after removing a row, updates the vector which contains the selected rows - if the row, which should be removed, is selected, it will be erased from the vector - */ - SelectionEngine* getSelEngine(); - vcl::Window& getDataWindow(); - - // Window overridables - virtual void GetFocus() override; - virtual void LoseFocus() override; - virtual void KeyInput(const KeyEvent& rKEvt) override; - virtual void StateChanged(StateChangedType i_nStateChange) override; - - /** Creates and returns the accessible object of the whole GridControl. */ - virtual rtl::Reference CreateAccessible() override; - OUString GetAccessibleObjectName(AccessibleTableControlObjType eObjType, sal_Int32 _nRow, - sal_Int32 _nCol) const; - void GoToCell(sal_Int32 _nColumnPos, sal_Int32 _nRow); - OUString GetAccessibleObjectDescription(AccessibleTableControlObjType eObjType) const; - void FillAccessibleStateSet(sal_Int64& rStateSet, AccessibleTableControlObjType eObjType) const; - - // temporary methods - // Those do not really belong into the public API - they're intended for firing A11Y-related events. However, - // firing those events should be an implementation internal to the TableControl resp. TableControl_Impl, - // instead of something triggered externally. - void commitCellEvent(sal_Int16 const i_eventID, const css::uno::Any& i_newValue, - const css::uno::Any& i_oldValue); - void commitTableEvent(sal_Int16 const i_eventID, const css::uno::Any& i_newValue, - const css::uno::Any& i_oldValue); - - sal_Int32 GetAccessibleControlCount() const; - sal_Int32 GetRowCount() const; - sal_Int32 GetColumnCount() const; - bool ConvertPointToCellAddress(sal_Int32& _rnRow, sal_Int32& _rnColPos, const Point& _rPoint); - tools::Rectangle calcHeaderRect(bool _bIsColumnBar); - tools::Rectangle calcHeaderCellRect(bool _bIsColumnBar, sal_Int32 nPos); - tools::Rectangle calcTableRect(); - tools::Rectangle calcCellRect(sal_Int32 _nRowPos, sal_Int32 _nColPos); - void FillAccessibleStateSetForCell(sal_Int64& _rStateSet, sal_Int32 _nRow, - sal_uInt16 _nColumnPos) const; - OUString GetRowName(sal_Int32 _nIndex) const; - OUString GetColumnName(sal_Int32 _nIndex) const; - bool HasRowHeader(); - bool HasColHeader(); - OUString GetAccessibleCellText(sal_Int32 _nRowPos, sal_Int32 _nColPos) const; - - sal_Int32 GetSelectedRowCount() const; - sal_Int32 GetSelectedRowIndex(sal_Int32 const i_selectionIndex) const; - bool IsRowSelected(sal_Int32 const i_rowIndex) const; - void SelectRow(sal_Int32 const i_rowIndex, bool const i_select); - void SelectAllRows(bool const i_select); - - TableCell hitTest(const Point& rPoint) const; - void invalidate(const TableArea aArea); - -private: - DECL_LINK(ImplSelectHdl, LinkParamNone*, void); - -private: - TableControl(const TableControl&) = delete; - TableControl& operator=(const TableControl&) = delete; -}; - -} // namespace svt::table - -/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/toolkit/inc/controls/table/tablecontrolinterface.hxx b/toolkit/inc/controls/table/tablecontrolinterface.hxx index e5ff0cd88303..fba06d48c717 100644 --- a/toolkit/inc/controls/table/tablecontrolinterface.hxx +++ b/toolkit/inc/controls/table/tablecontrolinterface.hxx @@ -81,11 +81,11 @@ namespace svt::table struct TableCell { - ColPos nColumn; - RowPos nRow; + sal_Int32 nColumn; + sal_Int32 nRow; TableCellArea eArea; - TableCell( ColPos const i_column, RowPos const i_row ) + TableCell( sal_Int32 const i_column, sal_Int32 const i_row ) :nColumn( i_column ) ,nRow( i_row ) ,eArea( CellContent ) @@ -139,84 +139,6 @@ namespace svt::table All }; - - //= ITableControl - - /** defines a callback interface to be implemented by a concrete table control - */ - class SAL_NO_VTABLE ITableControl - { - public: - /** hides the cell cursor - - The method cares for successive calls, that is, for every call to - ->hideCursor(), you need one call to ->showCursor. Only if the number - of both calls matches, the cursor is really shown. - - @see showCursor - */ - virtual void hideCursor() = 0; - - /** shows the cell cursor - - @see hideCursor - */ - virtual void showCursor() = 0; - - /** returns selection engine*/ - virtual SelectionEngine* getSelEngine() = 0; - - /** returns the table model - - The returned model is guaranteed to not be . - */ - virtual PTableModel getModel() const = 0; - - /// returns the index of the currently active column - virtual ColPos getCurrentColumn() const = 0; - - /// returns the index of the currently active row - virtual RowPos getCurrentRow() const = 0; - - /// activates the given cell - virtual void activateCell( ColPos const i_col, RowPos const i_row ) = 0; - - /// retrieves the size of the table window, in pixels - virtual ::Size getTableSizePixel() const = 0; - - /// sets a new mouse pointer for the table window - virtual void setPointer( PointerStyle i_pointer ) = 0; - - /// captures the mouse to the table window - virtual void captureMouse() = 0; - - /// releases the mouse, after it had previously been captured - virtual void releaseMouse() = 0; - - /// invalidates the table window - virtual void invalidate( TableArea const i_what ) = 0; - - /// calculates a width, given in pixels, into an AppFont-based width - virtual tools::Long pixelWidthToAppFont( tools::Long const i_pixels ) const = 0; - - /// shows a tracking rectangle - virtual void showTracking( tools::Rectangle const & i_location, ShowTrackFlags const i_flags ) = 0; - - /// hides a previously shown tracking rectangle - virtual void hideTracking() = 0; - - /// does a hit test for the given pixel coordinates - virtual TableCell hitTest( const Point& rPoint ) const = 0; - - /// retrieves the metrics for a given column - virtual ColumnMetrics getColumnMetrics( ColPos const i_column ) const = 0; - - /// determines whether a given row is selected - virtual bool isRowSelected( RowPos _nRow ) const = 0; - - virtual ~ITableControl() {}; - }; - } // namespace svt::table /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/toolkit/inc/controls/table/tablemodel.hxx b/toolkit/inc/controls/table/tablemodel.hxx index 97d0c56c9ea7..b5f6023f93e7 100644 --- a/toolkit/inc/controls/table/tablemodel.hxx +++ b/toolkit/inc/controls/table/tablemodel.hxx @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -78,7 +79,7 @@ namespace svt::table /** declares an interface to be implemented by components interested in changes in an ->ITableModel */ - class SAL_NO_VTABLE ITableModelListener : public std::enable_shared_from_this< ITableModelListener > + class SAL_NO_VTABLE ITableModelListener { public: /** notifies the listener that one or more rows have been inserted into @@ -90,7 +91,7 @@ namespace svt::table the index of the last newly inserted row. Must not be smaller than ->first */ - virtual void rowsInserted( RowPos first, RowPos last ) = 0; + virtual void rowsInserted(sal_Int32 first, sal_Int32 last) = 0; /** notifies the listener that one or more rows have been removed from the table @@ -102,7 +103,7 @@ namespace svt::table the old index of the last removed row. Must not be smaller than ->first */ - virtual void rowsRemoved( RowPos first, RowPos last ) = 0; + virtual void rowsRemoved(sal_Int32 first, sal_Int32 last) = 0; /** notifies the listener that one or more columns have been inserted into the table @@ -125,7 +126,7 @@ namespace svt::table they have about the cells in question, in particular any possibly cached cell values. */ - virtual void cellsUpdated( RowPos const i_firstRow, RowPos const i_lastRow ) = 0; + virtual void cellsUpdated(sal_Int32 const i_firstRow, sal_Int32 const i_lastRow) = 0; /** notifies the listener that attributes of a given column changed @@ -135,7 +136,9 @@ namespace svt::table a combination of one or more COL_ATTRS_* flags, denoting the attribute group(s) in which changes occurred. */ - virtual void columnChanged( ColPos const i_column, ColumnAttributeGroup const i_attributeGroup ) = 0; + virtual void columnChanged(sal_Int32 const i_column, + ColumnAttributeGroup const i_attributeGroup) + = 0; /** notifies the listener that the metrics of the table changed. @@ -147,7 +150,6 @@ namespace svt::table /// deletes the listener instance virtual ~ITableModelListener(){}; }; - typedef std::shared_ptr< ITableModelListener > PTableModelListener; //= IColumnModel @@ -276,7 +278,7 @@ namespace svt::table @return the model of the column in question. Must not be */ - virtual PColumnModel getColumnModel( ColPos column ) = 0; + virtual PColumnModel getColumnModel(sal_Int32 column) = 0; /** returns a renderer which is able to paint the table represented by this table model @@ -329,15 +331,17 @@ namespace svt::table /** adds a listener to be notified of changes in the table model */ - virtual void addTableModelListener( const PTableModelListener& i_listener ) = 0; + virtual void addTableModelListener(const VclPtr& i_listener) = 0; /** remove a listener to be notified of changes in the table model */ - virtual void removeTableModelListener( const PTableModelListener& i_listener ) = 0; + virtual void removeTableModelListener(const VclPtr& i_listener) = 0; /** retrieves the content of the given cell */ - virtual void getCellContent( ColPos const i_col, RowPos const i_row, css::uno::Any& o_cellContent ) = 0; + virtual void getCellContent(sal_Int32 const i_col, sal_Int32 const i_row, + css::uno::Any& o_cellContent) + = 0; /** returns an object which should be displayed as tooltip for the given cell @@ -354,11 +358,13 @@ namespace svt::table @param o_cellToolTip takes the tooltip object upon return. */ - virtual void getCellToolTip( ColPos const i_col, RowPos const i_row, css::uno::Any & o_cellToolTip ) = 0; + virtual void getCellToolTip(sal_Int32 const i_col, sal_Int32 const i_row, + css::uno::Any& o_cellToolTip) + = 0; /** retrieves title of a given row */ - virtual css::uno::Any getRowHeading( RowPos const i_rowPos ) const = 0; + virtual css::uno::Any getRowHeading(sal_Int32 const i_rowPos) const = 0; /** returns the color to be used for rendering the grid lines. diff --git a/toolkit/inc/controls/table/tablerenderer.hxx b/toolkit/inc/controls/table/tablerenderer.hxx index b4883140fcff..e38e1b72d3d8 100644 --- a/toolkit/inc/controls/table/tablerenderer.hxx +++ b/toolkit/inc/controls/table/tablerenderer.hxx @@ -106,9 +106,9 @@ namespace svt::table @param _rStyle the style to be used for drawing */ - virtual void PaintColumnHeader( ColPos _nCol, - vcl::RenderContext& _rDevice, const tools::Rectangle& _rArea, - const StyleSettings& _rStyle ) = 0; + virtual void PaintColumnHeader(sal_Int32 _nCol, vcl::RenderContext& _rDevice, + const tools::Rectangle& _rArea, const StyleSettings& _rStyle) + = 0; /** prepares a row for painting @@ -141,9 +141,10 @@ namespace svt::table @param _rStyle the style to be used for drawing */ - virtual void PrepareRow( RowPos _nRow, bool i_hasControlFocus, bool _bSelected, - vcl::RenderContext& _rDevice, const tools::Rectangle& _rRowArea, - const StyleSettings& _rStyle ) = 0; + virtual void PrepareRow(sal_Int32 _nRow, bool i_hasControlFocus, bool _bSelected, + vcl::RenderContext& _rDevice, const tools::Rectangle& _rRowArea, + const StyleSettings& _rStyle) + = 0; /** paints the header of a row @@ -187,10 +188,10 @@ namespace svt::table @param _rStyle the style to be used for drawing */ - virtual void PaintCell( ColPos const i_col, - bool i_hasControlFocus, bool _bSelected, - vcl::RenderContext& _rDevice, const tools::Rectangle& _rArea, - const StyleSettings& _rStyle ) = 0; + virtual void PaintCell(sal_Int32 const i_col, bool i_hasControlFocus, bool _bSelected, + vcl::RenderContext& _rDevice, const tools::Rectangle& _rArea, + const StyleSettings& _rStyle) + = 0; /** draws a cell cursor in the given rectangle diff --git a/toolkit/inc/controls/table/tablesort.hxx b/toolkit/inc/controls/table/tablesort.hxx index 691beea4eddd..77684a6e2c34 100644 --- a/toolkit/inc/controls/table/tablesort.hxx +++ b/toolkit/inc/controls/table/tablesort.hxx @@ -38,7 +38,7 @@ namespace svt::table struct ColumnSort { - ColPos nColumnPos; + sal_Int32 nColumnPos; ColumnSortDirection eSortDirection; ColumnSort() @@ -59,7 +59,9 @@ namespace svt::table public: /** sorts the rows in the model by the given column's data, in the given direction. */ - virtual void sortByColumn( ColPos const i_column, ColumnSortDirection const i_sortDirection ) = 0; + virtual void sortByColumn(sal_Int32 const i_column, + ColumnSortDirection const i_sortDirection) + = 0; /** retrieves the current sort order of the data diff --git a/toolkit/inc/controls/table/tabletypes.hxx b/toolkit/inc/controls/table/tabletypes.hxx index 8fdb18a830d8..c566dd0ff57d 100644 --- a/toolkit/inc/controls/table/tabletypes.hxx +++ b/toolkit/inc/controls/table/tabletypes.hxx @@ -28,23 +28,17 @@ namespace svt::table /// a value denoting the size of a table typedef sal_Int32 TableSize; - /// a value denoting a column position within a table - typedef sal_Int32 ColPos; - /// a value denoting a row position within a table - typedef sal_Int32 RowPos; - typedef sal_Int32 TableMetrics; /// denotes the column containing the row headers -#define COL_ROW_HEADERS (::svt::table::ColPos(-1)) +#define COL_ROW_HEADERS (sal_Int32(-1)) /// denotes the row containing the column headers -#define ROW_COL_HEADERS (::svt::table::RowPos(-1)) +#define ROW_COL_HEADERS (sal_Int32(-1)) /// denotes an invalid column index -#define COL_INVALID (::svt::table::ColPos(-2)) +#define COL_INVALID (sal_Int32(-2)) /// denotes an invalid row index -#define ROW_INVALID (::svt::table::RowPos(-2)) - +#define ROW_INVALID (sal_Int32(-2)) } // namespace svt::table diff --git a/toolkit/qa/complex/toolkit/GridControl.java b/toolkit/qa/complex/toolkit/GridControl.java index b2338ee68f93..a95e1c432800 100644 --- a/toolkit/qa/complex/toolkit/GridControl.java +++ b/toolkit/qa/complex/toolkit/GridControl.java @@ -472,6 +472,8 @@ public class GridControl dataModel.removeRow( rowCount - 1 ); assertEquals( "removed the last and active row, active row was not adjusted!", rowCount - 2, gridControl.getCurrentRow() ); + + impl_dispose(gridControl); } diff --git a/toolkit/source/awt/vclxtoolkit.cxx b/toolkit/source/awt/vclxtoolkit.cxx index d2f584e2c3a6..d12bed33ec90 100644 --- a/toolkit/source/awt/vclxtoolkit.cxx +++ b/toolkit/source/awt/vclxtoolkit.cxx @@ -97,7 +97,7 @@ #include #include -#include +#include #include #include #include @@ -1778,7 +1778,7 @@ vcl::Window* VCLXToolkit::ImplCreateWindow( rtl::Reference* ppNewCom } else if (aServiceName == "grid") { - pNewWindow = VclPtr<::svt::table::TableControl>::Create(pParent, nWinBits); + pNewWindow = VclPtr::Create(pParent, nWinBits); *ppNewComp = new SVTXGridControl; } break; diff --git a/toolkit/source/controls/svtxgridcontrol.cxx b/toolkit/source/controls/svtxgridcontrol.cxx index 182f4da189db..1e6fb10b3af9 100644 --- a/toolkit/source/controls/svtxgridcontrol.cxx +++ b/toolkit/source/controls/svtxgridcontrol.cxx @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include #include "unocontroltablemodel.hxx" @@ -92,15 +92,15 @@ void SVTXGridControl::SetWindow( const VclPtr< vcl::Window > &pWindow ) impl_checkTableModelInit(); } - -void SVTXGridControl::impl_checkColumnIndex_throw( ::svt::table::TableControl const & i_table, sal_Int32 const i_columnIndex ) const +void SVTXGridControl::impl_checkColumnIndex_throw(svt::table::TableControl const& i_table, + sal_Int32 const i_columnIndex) const { if ( ( i_columnIndex < 0 ) || ( i_columnIndex >= i_table.GetColumnCount() ) ) throw IndexOutOfBoundsException( OUString(), *const_cast< SVTXGridControl* >( this ) ); } - -void SVTXGridControl::impl_checkRowIndex_throw( ::svt::table::TableControl const & i_table, sal_Int32 const i_rowIndex ) const +void SVTXGridControl::impl_checkRowIndex_throw(svt::table::TableControl const& i_table, + sal_Int32 const i_rowIndex) const { if ( ( i_rowIndex < 0 ) || ( i_rowIndex >= i_table.GetRowCount() ) ) throw IndexOutOfBoundsException( OUString(), *const_cast< SVTXGridControl* >( this ) ); @@ -111,7 +111,7 @@ sal_Int32 SAL_CALL SVTXGridControl::getRowAtPoint(::sal_Int32 x, ::sal_Int32 y) { SolarMutexGuard aGuard; - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN( pTable, "SVTXGridControl::getRowAtPoint: no control (anymore)!", -1 ); TableCell const tableCell = pTable->hitTest(Point(x, y)); @@ -123,7 +123,7 @@ sal_Int32 SAL_CALL SVTXGridControl::getColumnAtPoint(::sal_Int32 x, ::sal_Int32 { SolarMutexGuard aGuard; - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN( pTable, "SVTXGridControl::getColumnAtPoint: no control (anymore)!", -1 ); TableCell const tableCell = pTable->hitTest(Point(x, y)); @@ -135,7 +135,7 @@ sal_Int32 SAL_CALL SVTXGridControl::getCurrentColumn( ) { SolarMutexGuard aGuard; - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN( pTable, "SVTXGridControl::getCurrentColumn: no control (anymore)!", -1 ); sal_Int32 const nColumn = pTable->GetCurrentColumn(); @@ -147,7 +147,7 @@ sal_Int32 SAL_CALL SVTXGridControl::getCurrentRow( ) { SolarMutexGuard aGuard; - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN( pTable, "SVTXGridControl::getCurrentRow: no control (anymore)!", -1 ); sal_Int32 const nRow = pTable->GetCurrentRow(); @@ -159,13 +159,13 @@ void SAL_CALL SVTXGridControl::goToCell( ::sal_Int32 i_columnIndex, ::sal_Int32 { SolarMutexGuard aGuard; - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::getCurrentRow: no control (anymore)!" ); impl_checkColumnIndex_throw( *pTable, i_columnIndex ); impl_checkRowIndex_throw( *pTable, i_rowIndex ); - pTable->GoTo( i_columnIndex, i_rowIndex ); + pTable->GoToCell(i_columnIndex, i_rowIndex); } @@ -185,7 +185,7 @@ void SVTXGridControl::setProperty( const OUString& PropertyName, const Any& aVal { SolarMutexGuard aGuard; - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::setProperty: no control (anymore)!" ); switch( GetPropertyId( PropertyName ) ) @@ -436,11 +436,11 @@ void SVTXGridControl::impl_checkTableModelInit() if ( !(!m_bTableModelInitCompleted && m_xTableModel->hasColumnModel() && m_xTableModel->hasDataModel()) ) return; - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); if ( !pTable ) return; - pTable->SetModel( PTableModel( m_xTableModel ) ); + pTable->SetModel(m_xTableModel); m_bTableModelInitCompleted = true; @@ -469,7 +469,7 @@ Any SVTXGridControl::getProperty( const OUString& PropertyName ) { SolarMutexGuard aGuard; - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN( pTable, "SVTXGridControl::getProperty: no control (anymore)!", Any() ); Any aPropertyValue; @@ -620,7 +620,7 @@ void SAL_CALL SVTXGridControl::dataChanged( const GridDataEvent& i_event ) // if the data model is sortable, a dataChanged event is also fired in case the sort order changed. // So, just in case, invalidate the column header area, too. - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::dataChanged: no control (anymore)!" ); pTable->invalidate(TableArea::ColumnHeaders); } @@ -630,7 +630,7 @@ void SAL_CALL SVTXGridControl::rowHeadingChanged( const GridDataEvent& ) { SolarMutexGuard aGuard; - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::rowHeadingChanged: no control (anymore)!" ); pTable->invalidate(TableArea::RowHeaders); @@ -676,7 +676,7 @@ void SAL_CALL SVTXGridControl::selectRow( ::sal_Int32 i_rowIndex ) { SolarMutexGuard aGuard; - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::selectRow: no control (anymore)!" ); impl_checkRowIndex_throw( *pTable, i_rowIndex ); @@ -689,7 +689,7 @@ void SAL_CALL SVTXGridControl::selectAllRows() { SolarMutexGuard aGuard; - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::selectAllRows: no control (anymore)!" ); pTable->SelectAllRows( true ); @@ -700,7 +700,7 @@ void SAL_CALL SVTXGridControl::deselectRow( ::sal_Int32 i_rowIndex ) { SolarMutexGuard aGuard; - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::deselectRow: no control (anymore)!" ); impl_checkRowIndex_throw( *pTable, i_rowIndex ); @@ -713,7 +713,7 @@ void SAL_CALL SVTXGridControl::deselectAllRows() { SolarMutexGuard aGuard; - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::deselectAllRows: no control (anymore)!" ); pTable->SelectAllRows( false ); @@ -724,7 +724,7 @@ Sequence< ::sal_Int32 > SAL_CALL SVTXGridControl::getSelectedRows() { SolarMutexGuard aGuard; - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN( pTable, "SVTXGridControl::getSelectedRows: no control (anymore)!", Sequence< sal_Int32 >() ); sal_Int32 selectionCount = pTable->GetSelectedRowCount(); @@ -740,7 +740,7 @@ sal_Bool SAL_CALL SVTXGridControl::hasSelectedRows() { SolarMutexGuard aGuard; - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN( pTable, "SVTXGridControl::hasSelectedRows: no control (anymore)!", true ); return pTable->GetSelectedRowCount() > 0; @@ -751,7 +751,7 @@ sal_Bool SAL_CALL SVTXGridControl::isRowSelected( ::sal_Int32 index ) { SolarMutexGuard aGuard; - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN( pTable, "SVTXGridControl::isRowSelected: no control (anymore)!", false ); return pTable->IsRowSelected( index ); @@ -773,7 +773,7 @@ void SVTXGridControl::ProcessWindowEvent( const VclWindowEvent& rVclWindowEvent Reference< XWindow > xKeepAlive( this ); - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::ProcessWindowEvent: no control (anymore)!" ); bool handled = false; @@ -789,7 +789,7 @@ void SVTXGridControl::ProcessWindowEvent( const VclWindowEvent& rVclWindowEvent case VclEventId::ControlGetFocus: { - // TODO: this doesn't belong here. It belongs into the TableControl/_Impl, so A11Y also + // TODO: this doesn't belong here. It belongs into the TableControl, so A11Y also // works when the control is used outside the UNO context if (pTable->GetCurrentRow() != ROW_INVALID && pTable->GetCurrentColumn() != COL_INVALID) { @@ -817,7 +817,7 @@ void SVTXGridControl::ProcessWindowEvent( const VclWindowEvent& rVclWindowEvent case VclEventId::ControlLoseFocus: { - // TODO: this doesn't belong here. It belongs into the TableControl/_Impl, so A11Y also + // TODO: this doesn't belong here. It belongs into the TableControl, so A11Y also // works when the control is used outside the UNO context if (pTable->GetCurrentRow() != ROW_INVALID && pTable->GetCurrentColumn() != COL_INVALID) { @@ -863,7 +863,7 @@ void SVTXGridControl::setEnable( sal_Bool bEnable ) void SVTXGridControl::ImplCallItemListeners() { - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::ImplCallItemListeners: no control (anymore)!" ); if ( m_aSelectionListeners.getLength() ) @@ -885,7 +885,7 @@ void SVTXGridControl::impl_updateColumnsFromModel_nothrow() { Reference< XGridColumnModel > const xColumnModel( m_xTableModel->getColumnModel() ); ENSURE_OR_RETURN_VOID( xColumnModel.is(), "no model!" ); - VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + VclPtr pTable = GetAsDynamic(); ENSURE_OR_RETURN_VOID( pTable, "no table!" ); try diff --git a/toolkit/source/controls/table/AccessibleGridControl.cxx b/toolkit/source/controls/table/AccessibleGridControl.cxx index 19810d9116b9..98be1368e9d9 100644 --- a/toolkit/source/controls/table/AccessibleGridControl.cxx +++ b/toolkit/source/controls/table/AccessibleGridControl.cxx @@ -68,7 +68,7 @@ void SAL_CALL AccessibleGridControl::disposing() sal_Int64 AccessibleGridControl::implGetAccessibleChildCount() { - return m_aTable.GetAccessibleControlCount(); + return m_rTable.GetAccessibleControlCount(); } // css::accessibility::XAccessibleContext --------------------------------------------------------- @@ -93,21 +93,21 @@ AccessibleGridControl::getAccessibleChild( sal_Int64 nChildIndex ) css::uno::Reference< css::accessibility::XAccessible > xChild; if (isAlive()) { - if(nChildIndex == 0 && m_aTable.HasColHeader()) + if (nChildIndex == 0 && m_rTable.HasColumnHeader()) { if(!m_xColumnHeaderBar.is()) { m_xColumnHeaderBar = new AccessibleGridControlHeader( - this, m_aTable, AccessibleTableControlObjType::COLUMNHEADERBAR); + this, m_rTable, AccessibleTableControlObjType::COLUMNHEADERBAR); } xChild = m_xColumnHeaderBar.get(); } - else if(m_aTable.HasRowHeader() && (nChildIndex == 1 || nChildIndex == 0)) + else if (m_rTable.HasRowHeader() && (nChildIndex == 1 || nChildIndex == 0)) { if(!m_xRowHeaderBar.is()) { m_xRowHeaderBar = new AccessibleGridControlHeader( - this, m_aTable, AccessibleTableControlObjType::ROWHEADERBAR); + this, m_rTable, AccessibleTableControlObjType::ROWHEADERBAR); } xChild = m_xRowHeaderBar.get(); } @@ -115,7 +115,7 @@ AccessibleGridControl::getAccessibleChild( sal_Int64 nChildIndex ) { if(!m_xTable.is()) { - m_xTable = new AccessibleGridControlTable(this, m_aTable); + m_xTable = new AccessibleGridControlTable(this, m_rTable); } xChild = m_xTable.get(); } @@ -163,14 +163,14 @@ void SAL_CALL AccessibleGridControl::grabFocus() { SolarMutexGuard aSolarGuard; ensureAlive(); - m_aTable.GrabFocus(); + m_rTable.GrabFocus(); } // internal virtual methods --------------------------------------------------- AbsoluteScreenPixelRectangle AccessibleGridControl::implGetBoundingBoxOnScreen() { - return m_aTable.GetWindowExtentsAbsolute(); + return m_rTable.GetWindowExtentsAbsolute(); } // internal helper methods ---------------------------------------------------- @@ -185,7 +185,7 @@ void AccessibleGridControl::commitCellEvent(sal_Int16 _nEventId,const Any& _rNew if (css::uno::Reference(m_xTable) == xAccessible) { Reference xCell = m_xTable->getAccessibleCellAt( - m_aTable.GetCurrentRow(), m_aTable.GetCurrentColumn()); + m_rTable.GetCurrentRow(), m_rTable.GetCurrentColumn()); AccessibleGridControlTableCell* pCell = static_cast(xCell.get()); pCell->commitEvent(_nEventId, _rNewValue, _rOldValue); @@ -200,8 +200,8 @@ void AccessibleGridControl::commitTableEvent(sal_Int16 _nEventId,const Any& _rNe if(_nEventId == AccessibleEventId::ACTIVE_DESCENDANT_CHANGED) { - const sal_Int32 nCurrentRow = m_aTable.GetCurrentRow(); - const sal_Int32 nCurrentCol = m_aTable.GetCurrentColumn(); + const sal_Int32 nCurrentRow = m_rTable.GetCurrentRow(); + const sal_Int32 nCurrentCol = m_rTable.GetCurrentColumn(); css::uno::Reference< css::accessibility::XAccessible > xChild; if (nCurrentRow > -1 && nCurrentCol > -1) xChild = m_xTable->getAccessibleCellAt(nCurrentRow, nCurrentCol); diff --git a/toolkit/source/controls/table/AccessibleGridControlBase.cxx b/toolkit/source/controls/table/AccessibleGridControlBase.cxx index 546d230e17a3..05b3121ee6b4 100644 --- a/toolkit/source/controls/table/AccessibleGridControlBase.cxx +++ b/toolkit/source/controls/table/AccessibleGridControlBase.cxx @@ -49,7 +49,7 @@ AccessibleGridControlBase::AccessibleGridControlBase( const rtl::Reference& rpParent, svt::table::TableControl& rTable, AccessibleTableControlObjType eObjType) : m_pParent(rpParent) - , m_aTable(rTable) + , m_rTable(rTable) , m_eObjType(eObjType) { } @@ -79,7 +79,7 @@ OUString SAL_CALL AccessibleGridControlBase::getAccessibleDescription() SolarMutexGuard g; ensureAlive(); - return m_aTable.GetAccessibleObjectDescription(m_eObjType); + return m_rTable.GetAccessibleObjectDescription(m_eObjType); } OUString SAL_CALL AccessibleGridControlBase::getAccessibleName() @@ -87,7 +87,7 @@ OUString SAL_CALL AccessibleGridControlBase::getAccessibleName() SolarMutexGuard g; ensureAlive(); - return m_aTable.GetAccessibleObjectName(m_eObjType, 0, 0); + return m_rTable.GetAccessibleObjectName(m_eObjType, 0, 0); } css::uno::Reference< css::accessibility::XAccessibleRelationSet > SAL_CALL @@ -155,7 +155,7 @@ sal_Int64 AccessibleGridControlBase::implCreateStateSet() if( implIsShowing() ) nStateSet |= AccessibleStateType::SHOWING; // GridControl fills StateSet with states depending on object type - m_aTable.FillAccessibleStateSet( nStateSet, getType() ); + m_rTable.FillAccessibleStateSet(nStateSet, getType()); } else nStateSet |= AccessibleStateType::DEFUNC; @@ -212,15 +212,15 @@ sal_Int32 SAL_CALL AccessibleGridControlBase::getForeground( ) ensureAlive(); Color nColor; - if (m_aTable.IsControlForeground()) - nColor = m_aTable.GetControlForeground(); + if (m_rTable.IsControlForeground()) + nColor = m_rTable.GetControlForeground(); else { vcl::Font aFont; - if (m_aTable.IsControlFont() ) - aFont = m_aTable.GetControlFont(); + if (m_rTable.IsControlFont()) + aFont = m_rTable.GetControlFont(); else - aFont = m_aTable.GetFont(); + aFont = m_rTable.GetFont(); nColor = aFont.GetColor(); } return sal_Int32(nColor); @@ -232,10 +232,10 @@ sal_Int32 SAL_CALL AccessibleGridControlBase::getBackground( ) ensureAlive(); Color nColor; - if (m_aTable.IsControlBackground()) - nColor = m_aTable.GetControlBackground(); + if (m_rTable.IsControlBackground()) + nColor = m_rTable.GetControlBackground(); else - nColor = m_aTable.GetBackground().GetColor(); + nColor = m_rTable.GetBackground().GetColor(); return sal_Int32(nColor); } diff --git a/toolkit/source/controls/table/AccessibleGridControlHeader.cxx b/toolkit/source/controls/table/AccessibleGridControlHeader.cxx index 20902369f26c..0ab891383ac1 100644 --- a/toolkit/source/controls/table/AccessibleGridControlHeader.cxx +++ b/toolkit/source/controls/table/AccessibleGridControlHeader.cxx @@ -63,7 +63,7 @@ sal_Int64 SAL_CALL AccessibleGridControlHeader::getAccessibleIndexInParent() SolarMutexGuard aSolarGuard; ensureAlive(); - if (m_eObjType == AccessibleTableControlObjType::ROWHEADERBAR && m_aTable.HasColHeader()) + if (m_eObjType == AccessibleTableControlObjType::ROWHEADERBAR && m_rTable.HasColumnHeader()) return 1; else return 0; @@ -80,7 +80,7 @@ AccessibleGridControlHeader::getAccessibleAtPoint( const awt::Point& rPoint ) sal_Int32 nRow = 0; sal_Int32 nColumnPos = 0; - bool bConverted = m_aTable.ConvertPointToCellAddress(nRow, nColumnPos, + bool bConverted = m_rTable.ConvertPointToCellAddress(nRow, nColumnPos, vcl::unohelper::ConvertToVCLPoint(rPoint)); return bConverted ? implGetChild( nRow, nColumnPos ) : Reference< XAccessible >(); } @@ -167,8 +167,8 @@ sal_Bool SAL_CALL AccessibleGridControlHeader::isAccessibleSelected( AbsoluteScreenPixelRectangle AccessibleGridControlHeader::implGetBoundingBoxOnScreen() { - AbsoluteScreenPixelRectangle aGridRect( m_aTable.GetWindowExtentsAbsolute() ); - tools::Rectangle aHeaderRect (m_aTable.calcHeaderRect(isColumnBar())); + AbsoluteScreenPixelRectangle aGridRect(m_rTable.GetWindowExtentsAbsolute()); + tools::Rectangle aHeaderRect(m_rTable.calcHeaderRect(isColumnBar())); if(isColumnBar()) return AbsoluteScreenPixelRectangle(aGridRect.TopLeft(), Size(aGridRect.getOpenWidth(),aHeaderRect.getOpenHeight())); else @@ -181,14 +181,16 @@ Reference< XAccessible > AccessibleGridControlHeader::implGetChild( { if (m_eObjType == AccessibleTableControlObjType::COLUMNHEADERBAR) { - rtl::Reference pColHeaderCell = new AccessibleGridControlHeaderCell(nColumnPos, this, m_aTable, - AccessibleTableControlObjType::COLUMNHEADERCELL); + rtl::Reference pColHeaderCell + = new AccessibleGridControlHeaderCell(nColumnPos, this, m_rTable, + AccessibleTableControlObjType::COLUMNHEADERCELL); return pColHeaderCell; } else if (m_eObjType == AccessibleTableControlObjType::ROWHEADERBAR) { - rtl::Reference pRowHeaderCell = new AccessibleGridControlHeaderCell(nRow, this, m_aTable, - AccessibleTableControlObjType::ROWHEADERCELL); + rtl::Reference pRowHeaderCell + = new AccessibleGridControlHeaderCell(nRow, this, m_rTable, + AccessibleTableControlObjType::ROWHEADERCELL); return pRowHeaderCell; } return nullptr; diff --git a/toolkit/source/controls/table/AccessibleGridControlHeaderCell.cxx b/toolkit/source/controls/table/AccessibleGridControlHeaderCell.cxx index 6a9287ea78b3..a2905df9f950 100644 --- a/toolkit/source/controls/table/AccessibleGridControlHeaderCell.cxx +++ b/toolkit/source/controls/table/AccessibleGridControlHeaderCell.cxx @@ -32,14 +32,16 @@ namespace accessibility AccessibleGridControlHeaderCell::AccessibleGridControlHeaderCell( sal_Int32 _nColumnRowId, const rtl::Reference& rpParent, svt::table::TableControl& rTable, AccessibleTableControlObjType eObjType) - : AccessibleGridControlCell(rpParent, rTable, - (eObjType == AccessibleTableControlObjType::ROWHEADERCELL) ? _nColumnRowId : 0, - (eObjType == AccessibleTableControlObjType::ROWHEADERCELL) ? 0 : _nColumnRowId, - eObjType) - , m_nColumnRowId(_nColumnRowId) -{ - assert(eObjType == AccessibleTableControlObjType::ROWHEADERCELL || eObjType == AccessibleTableControlObjType::COLUMNHEADERCELL); -} + : AccessibleGridControlCell( + rpParent, rTable, + (eObjType == AccessibleTableControlObjType::ROWHEADERCELL) ? _nColumnRowId : 0, + (eObjType == AccessibleTableControlObjType::ROWHEADERCELL) ? 0 : _nColumnRowId, + eObjType) + , m_nColumnRowId(_nColumnRowId) + { + assert(eObjType == AccessibleTableControlObjType::ROWHEADERCELL + || eObjType == AccessibleTableControlObjType::COLUMNHEADERCELL); + } /** Return a bitset of states of the current object. */ @@ -58,7 +60,7 @@ sal_Int64 AccessibleGridControlHeaderCell::implCreateStateSet() nStateSet |= AccessibleStateType::TRANSIENT; nStateSet |= AccessibleStateType::SELECTABLE; - if ( m_aTable.IsRowSelected(m_nColumnRowId) ) + if (m_rTable.IsRowSelected(m_nColumnRowId)) nStateSet |= AccessibleStateType::SELECTED; } else @@ -91,13 +93,13 @@ void SAL_CALL AccessibleGridControlHeaderCell::grabFocus() AbsoluteScreenPixelRectangle AccessibleGridControlHeaderCell::implGetBoundingBoxOnScreen() { - AbsoluteScreenPixelRectangle aGridRect( m_aTable.GetWindowExtentsAbsolute() ); + AbsoluteScreenPixelRectangle aGridRect(m_rTable.GetWindowExtentsAbsolute()); sal_Int32 nIndex = getAccessibleIndexInParent(); tools::Rectangle aCellRect; if (m_eObjType == AccessibleTableControlObjType::COLUMNHEADERCELL) - aCellRect = m_aTable.calcHeaderCellRect(true, nIndex); + aCellRect = m_rTable.calcHeaderCellRect(true, nIndex); else - aCellRect = m_aTable.calcHeaderCellRect(false, nIndex); + aCellRect = m_rTable.calcHeaderCellRect(false, nIndex); return AbsoluteScreenPixelRectangle(AbsoluteScreenPixelPoint(aGridRect.Left()+aCellRect.Left(),aGridRect.Top()+aCellRect.Top()), aCellRect.GetSize()); } diff --git a/toolkit/source/controls/table/AccessibleGridControlTable.cxx b/toolkit/source/controls/table/AccessibleGridControlTable.cxx index e605acef77c5..08cc7d39734e 100644 --- a/toolkit/source/controls/table/AccessibleGridControlTable.cxx +++ b/toolkit/source/controls/table/AccessibleGridControlTable.cxx @@ -61,7 +61,9 @@ AccessibleGridControlTable::getAccessibleChild( sal_Int64 nChildIndex ) } if(!m_aCellVector[nChildIndex].is()) { - m_aCellVector[nChildIndex].set(new AccessibleGridControlTableCell(this, m_aTable, nChildIndex/m_aTable.GetColumnCount(), nChildIndex%m_aTable.GetColumnCount())); + m_aCellVector[nChildIndex].set(new AccessibleGridControlTableCell( + this, m_rTable, nChildIndex / m_rTable.GetColumnCount(), + nChildIndex % m_rTable.GetColumnCount())); } return m_aCellVector[nChildIndex]; } @@ -71,9 +73,9 @@ sal_Int64 SAL_CALL AccessibleGridControlTable::getAccessibleIndexInParent() SolarMutexGuard aSolarGuard; ensureAlive(); - if(m_aTable.HasRowHeader() && m_aTable.HasColHeader()) + if (m_rTable.HasRowHeader() && m_rTable.HasColumnHeader()) return 0; - else if(m_aTable.HasRowHeader() != m_aTable.HasColHeader()) + else if (m_rTable.HasRowHeader() != m_rTable.HasColumnHeader()) return 1; else return 2; @@ -93,7 +95,7 @@ AccessibleGridControlTable::getAccessibleAtPoint( const awt::Point& rPoint ) Point aPosInTableControl = vcl::unohelper::ConvertToVCLPoint(rPoint) + implGetBoundingBox().TopLeft(); sal_Int32 nRow = 0; sal_Int32 nColumnPos = 0; - if (m_aTable.ConvertPointToCellAddress(nRow, nColumnPos, aPosInTableControl)) + if (m_rTable.ConvertPointToCellAddress(nRow, nColumnPos, aPosInTableControl)) return getAccessibleCellAt(nRow, nColumnPos); return nullptr; } @@ -103,7 +105,7 @@ void SAL_CALL AccessibleGridControlTable::grabFocus() SolarMutexGuard aSolarGuard; ensureAlive(); - m_aTable.GrabFocus(); + m_rTable.GrabFocus(); } // XAccessibleTable ----------------------------------------------------------- @@ -131,7 +133,7 @@ Reference< XAccessibleTable > SAL_CALL AccessibleGridControlTable::getAccessible SolarMutexGuard g; ensureAlive(); - if(m_aTable.HasColHeader()) + if (m_rTable.HasColumnHeader()) return implGetHeaderBar( 1 ); else return implGetHeaderBar( 0 ); @@ -184,7 +186,7 @@ Reference< XAccessible > SAL_CALL AccessibleGridControlTable::getAccessibleCellA ensureAlive(); ensureIsValidAddress( nRow, nColumn ); - sal_Int64 nChildIndex = static_cast(nRow) * static_cast(m_aTable.GetColumnCount()) + nColumn; + sal_Int64 nChildIndex = static_cast(nRow) * static_cast(m_rTable.GetColumnCount()) + nColumn; return getAccessibleChild(nChildIndex); } @@ -204,9 +206,9 @@ void SAL_CALL AccessibleGridControlTable::selectAccessibleChild( sal_Int64 nChil ensureAlive(); ensureIsValidIndex( nChildIndex ); - sal_Int32 nColumns = m_aTable.GetColumnCount(); + sal_Int32 nColumns = m_rTable.GetColumnCount(); sal_Int32 nRow = nChildIndex / nColumns; - m_aTable.SelectRow( nRow, true ); + m_rTable.SelectRow(nRow, true); } sal_Bool SAL_CALL AccessibleGridControlTable::isAccessibleChildSelected( sal_Int64 nChildIndex ) { @@ -214,7 +216,7 @@ sal_Bool SAL_CALL AccessibleGridControlTable::isAccessibleChildSelected( sal_Int ensureAlive(); ensureIsValidIndex( nChildIndex ); - sal_Int32 nColumns = m_aTable.GetColumnCount(); + sal_Int32 nColumns = m_rTable.GetColumnCount(); sal_Int32 nRow = nChildIndex / nColumns; return isAccessibleRowSelected(nRow); } @@ -223,7 +225,7 @@ void SAL_CALL AccessibleGridControlTable::clearAccessibleSelection() SolarMutexGuard aSolarGuard; ensureAlive(); - m_aTable.SelectAllRows( false ); + m_rTable.SelectAllRows(false); } void SAL_CALL AccessibleGridControlTable::selectAllAccessibleChildren() { @@ -232,7 +234,7 @@ void SAL_CALL AccessibleGridControlTable::selectAllAccessibleChildren() ensureAlive(); Sequence< sal_Int32 > selectedRows = getSelectedAccessibleRows(); auto selectedRowsRange = asNonConstRange(selectedRows); - for(tools::Long i=0; i selectedRows = getSelectedAccessibleRows(); - sal_Int32 nColumns = m_aTable.GetColumnCount(); + sal_Int32 nColumns = m_rTable.GetColumnCount(); return static_cast(selectedRows.getLength()) * static_cast(nColumns); } Reference< XAccessible > SAL_CALL @@ -296,7 +298,7 @@ void AccessibleGridControlTable::commitEvent(sal_Int16 nEventId, const css::uno: if (aChange.Type == AccessibleTableModelChangeType::ROWS_REMOVED) { - int nColCount = m_aTable.GetColumnCount(); + int nColCount = m_rTable.GetColumnCount(); // check valid index - entries are inserted lazily size_t const nStart = nColCount * aChange.FirstRow; size_t const nEnd = nColCount * aChange.LastRow; @@ -317,8 +319,8 @@ void AccessibleGridControlTable::commitEvent(sal_Int16 nEventId, const css::uno: AbsoluteScreenPixelRectangle AccessibleGridControlTable::implGetBoundingBoxOnScreen() { - tools::Rectangle aGridRect( m_aTable.GetWindowExtentsAbsolute()); - tools::Rectangle aTableRect( m_aTable.calcTableRect() ); + tools::Rectangle aGridRect(m_rTable.GetWindowExtentsAbsolute()); + tools::Rectangle aTableRect(m_rTable.calcTableRect()); tools::Long nX = aGridRect.Left() + aTableRect.Left(); tools::Long nY = aGridRect.Top() + aTableRect.Top(); tools::Long nWidth = aGridRect.GetSize().Width()-aTableRect.Left(); diff --git a/toolkit/source/controls/table/AccessibleGridControlTableBase.cxx b/toolkit/source/controls/table/AccessibleGridControlTableBase.cxx index 05f5862cb808..c34df8d700a2 100644 --- a/toolkit/source/controls/table/AccessibleGridControlTableBase.cxx +++ b/toolkit/source/controls/table/AccessibleGridControlTableBase.cxx @@ -50,11 +50,12 @@ sal_Int64 SAL_CALL AccessibleGridControlTableBase::getAccessibleChildCount() ensureAlive(); sal_Int64 nChildren = 0; if (m_eObjType == AccessibleTableControlObjType::ROWHEADERBAR) - nChildren = m_aTable.GetRowCount(); + nChildren = m_rTable.GetRowCount(); else if (m_eObjType == AccessibleTableControlObjType::TABLE) - nChildren = static_cast(m_aTable.GetRowCount()) * static_cast(m_aTable.GetColumnCount()); + nChildren = static_cast(m_rTable.GetRowCount()) + * static_cast(m_rTable.GetColumnCount()); else if (m_eObjType == AccessibleTableControlObjType::COLUMNHEADERBAR) - nChildren = m_aTable.GetColumnCount(); + nChildren = m_rTable.GetColumnCount(); return nChildren; } @@ -76,7 +77,7 @@ sal_Int32 SAL_CALL AccessibleGridControlTableBase::getAccessibleRowCount() if (m_eObjType == AccessibleTableControlObjType::COLUMNHEADERBAR) return 1; - return m_aTable.GetRowCount(); + return m_rTable.GetRowCount(); } sal_Int32 SAL_CALL AccessibleGridControlTableBase::getAccessibleColumnCount() @@ -87,7 +88,7 @@ sal_Int32 SAL_CALL AccessibleGridControlTableBase::getAccessibleColumnCount() if (m_eObjType == AccessibleTableControlObjType::ROWHEADERBAR) return 1; - return m_aTable.GetColumnCount(); + return m_rTable.GetColumnCount(); } sal_Int32 SAL_CALL AccessibleGridControlTableBase::getAccessibleRowExtentAt( @@ -133,7 +134,7 @@ sal_Int64 SAL_CALL AccessibleGridControlTableBase::getAccessibleIndex( ensureAlive(); ensureIsValidAddress( nRow, nColumn ); - return static_cast(nRow) * static_cast(m_aTable.GetColumnCount()) + nColumn; + return static_cast(nRow) * static_cast(m_rTable.GetColumnCount()) + nColumn; } sal_Int32 SAL_CALL AccessibleGridControlTableBase::getAccessibleRow( sal_Int64 nChildIndex ) @@ -170,11 +171,11 @@ sal_Int32 AccessibleGridControlTableBase::implGetColumn( sal_Int64 nChildIndex ) void AccessibleGridControlTableBase::implGetSelectedRows( Sequence< sal_Int32 >& rSeq ) { - sal_Int32 const selectionCount( m_aTable.GetSelectedRowCount() ); + sal_Int32 const selectionCount(m_rTable.GetSelectedRowCount()); rSeq.realloc( selectionCount ); auto pSeq = rSeq.getArray(); for ( sal_Int32 i=0; i= static_cast(m_aTable.GetRowCount()) * static_cast(m_aTable.GetColumnCount())) + if (nChildIndex < 0 + || nChildIndex >= static_cast(m_rTable.GetRowCount()) + * static_cast(m_rTable.GetColumnCount())) throw lang::IndexOutOfBoundsException( u"child index is invalid"_ustr, *this ); } diff --git a/toolkit/source/controls/table/AccessibleGridControlTableCell.cxx b/toolkit/source/controls/table/AccessibleGridControlTableCell.cxx index 4201126a4923..b76182b67e96 100644 --- a/toolkit/source/controls/table/AccessibleGridControlTableCell.cxx +++ b/toolkit/source/controls/table/AccessibleGridControlTableCell.cxx @@ -65,7 +65,7 @@ void SAL_CALL AccessibleGridControlCell::grabFocus() { SolarMutexGuard aSolarGuard; - m_aTable.GoToCell(m_nColPos, m_nRowPos); + m_rTable.GoToCell(m_nColPos, m_nRowPos); } OUString SAL_CALL AccessibleGridControlCell::getAccessibleName() @@ -73,20 +73,20 @@ OUString SAL_CALL AccessibleGridControlCell::getAccessibleName() SolarMutexGuard g; ensureAlive(); - return m_aTable.GetAccessibleObjectName(m_eObjType, m_nRowPos, m_nColPos); + return m_rTable.GetAccessibleObjectName(m_eObjType, m_nRowPos, m_nColPos); } // implementation of a table cell OUString AccessibleGridControlTableCell::implGetText() { ensureAlive(); - return m_aTable.GetAccessibleCellText(getRowPos(), getColumnPos()); + return m_rTable.GetAccessibleCellText(getRowPos(), getColumnPos()); } css::lang::Locale AccessibleGridControlTableCell::implGetLocale() { ensureAlive(); - return m_aTable.GetAccessible()->getLocale(); + return m_rTable.GetAccessible()->getLocale(); } void AccessibleGridControlTableCell::implGetSelection(sal_Int32& nStartIndex, sal_Int32& nEndIndex) @@ -111,7 +111,7 @@ css::awt::Rectangle SAL_CALL AccessibleGridControlTableCell::getCharacterBounds( if (!implIsValidIndex(nIndex, implGetText().getLength())) throw IndexOutOfBoundsException(); - return vcl::unohelper::ConvertToAWTRect(m_aTable.GetCharacterBounds(nIndex)); + return vcl::unohelper::ConvertToAWTRect(m_rTable.GetCharacterBounds(nIndex)); } sal_Int32 SAL_CALL AccessibleGridControlTableCell::getIndexAtPoint(const css::awt::Point& _aPoint) @@ -120,7 +120,7 @@ sal_Int32 SAL_CALL AccessibleGridControlTableCell::getIndexAtPoint(const css::aw ensureAlive(); - return m_aTable.GetIndexForPoint(vcl::unohelper::ConvertToVCLPoint(_aPoint)); + return m_rTable.GetIndexForPoint(vcl::unohelper::ConvertToVCLPoint(_aPoint)); } /** @return The count of visible children. */ @@ -145,7 +145,7 @@ sal_Int64 AccessibleGridControlTableCell::implCreateStateSet() if (implIsShowing()) nStateSet |= AccessibleStateType::SHOWING; - m_aTable.FillAccessibleStateSetForCell(nStateSet, getRowPos(), + m_rTable.FillAccessibleStateSetForCell(nStateSet, getRowPos(), static_cast(getColumnPos())); } else @@ -162,7 +162,7 @@ sal_Int64 SAL_CALL AccessibleGridControlTableCell::getAccessibleIndexInParent() ensureAlive(); - return (static_cast(getRowPos()) * static_cast(m_aTable.GetColumnCount())) + return (static_cast(getRowPos()) * static_cast(m_rTable.GetColumnCount())) + getColumnPos(); } @@ -269,8 +269,8 @@ sal_Bool SAL_CALL AccessibleGridControlTableCell::scrollSubstringTo(sal_Int32, s AbsoluteScreenPixelRectangle AccessibleGridControlTableCell::implGetBoundingBoxOnScreen() { - AbsoluteScreenPixelRectangle aGridRect = m_aTable.GetWindowExtentsAbsolute(); - tools::Rectangle aCellRect = m_aTable.calcCellRect(getRowPos(), getColumnPos()); + AbsoluteScreenPixelRectangle aGridRect = m_rTable.GetWindowExtentsAbsolute(); + tools::Rectangle aCellRect = m_rTable.calcCellRect(getRowPos(), getColumnPos()); tools::Long nX = aGridRect.Left() + aCellRect.Left(); tools::Long nY = aGridRect.Top() + aCellRect.Top(); AbsoluteScreenPixelRectangle aCell(AbsoluteScreenPixelPoint(nX, nY), aCellRect.GetSize()); diff --git a/toolkit/source/controls/table/tablecontrol_impl.cxx b/toolkit/source/controls/table/TableControl.cxx similarity index 72% rename from toolkit/source/controls/table/tablecontrol_impl.cxx rename to toolkit/source/controls/table/TableControl.cxx index 83b8111a357c..ac1f10a29f20 100644 --- a/toolkit/source/controls/table/tablecontrol_impl.cxx +++ b/toolkit/source/controls/table/TableControl.cxx @@ -18,15 +18,16 @@ */ #include -#include +#include +#include #include #include #include "tabledatawindow.hxx" -#include "tablecontrol_impl.hxx" #include "tablegeometry.hxx" #include +#include #include #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include #include @@ -62,10 +64,10 @@ namespace class SuppressCursor { private: - ITableControl& m_rTable; + TableControl& m_rTable; public: - explicit SuppressCursor(ITableControl& _rTable) + explicit SuppressCursor(TableControl& _rTable) : m_rTable(_rTable) { m_rTable.hideCursor(); @@ -91,7 +93,7 @@ public: virtual TableSize getRowCount() const override { return 0; } virtual bool hasColumnHeaders() const override { return false; } virtual bool hasRowHeaders() const override { return false; } - virtual PColumnModel getColumnModel(ColPos) override + virtual PColumnModel getColumnModel(sal_Int32) override { OSL_FAIL("EmptyTableModel::getColumnModel: invalid call!"); return PColumnModel(); @@ -109,8 +111,8 @@ public: { return ScrollbarShowNever; } - virtual void addTableModelListener(const PTableModelListener&) override {} - virtual void removeTableModelListener(const PTableModelListener&) override {} + virtual void addTableModelListener(const VclPtr&) override {} + virtual void removeTableModelListener(const VclPtr&) override {} virtual ::std::optional<::Color> getLineColor() const override { return ::std::optional<::Color>(); @@ -157,17 +159,18 @@ public: } virtual ITableDataSort* getSortAdapter() override { return nullptr; } virtual bool isEnabled() const override { return true; } - virtual void getCellContent(ColPos const, RowPos const, css::uno::Any& o_cellContent) override + virtual void getCellContent(sal_Int32 const, sal_Int32 const, + css::uno::Any& o_cellContent) override { o_cellContent.clear(); } - virtual void getCellToolTip(ColPos const, RowPos const, css::uno::Any&) override {} - virtual Any getRowHeading(RowPos const) const override { return Any(); } + virtual void getCellToolTip(sal_Int32 const, sal_Int32 const, css::uno::Any&) override {} + virtual Any getRowHeading(sal_Int32 const) const override { return Any(); } }; } -TableControl_Impl::TableControl_Impl(TableControl& _rAntiImpl) - : m_rAntiImpl(_rAntiImpl) +TableControl::TableControl(vcl::Window* pParent, WinBits nStyle) + : Control(pParent, nStyle) , m_pModel(std::make_shared()) , m_pInputHandler() , m_nRowHeightPixel(15) @@ -185,7 +188,7 @@ TableControl_Impl::TableControl_Impl(TableControl& _rAntiImpl) , m_pHScroll(nullptr) , m_pScrollCorner(nullptr) , m_aSelectedRows() - , m_pTableFunctionSet(new TableFunctionSet(this)) + , m_pTableFunctionSet(new TableFunctionSet(*this)) , m_nAnchor(-1) , m_bUpdatingColWidths(false) { @@ -193,30 +196,159 @@ TableControl_Impl::TableControl_Impl(TableControl& _rAntiImpl) m_pSelEngine->SetSelectionMode(SelectionMode::Single); m_pDataWindow->SetPosPixel(Point(0, 0)); m_pDataWindow->Show(); + + m_pDataWindow->SetSelectHdl(LINK(this, TableControl, ImplSelectHdl)); + + // by default, use the background as determined by the style settings + const Color aWindowColor(GetSettings().GetStyleSettings().GetFieldColor()); + SetBackground(Wallpaper(aWindowColor)); + GetOutDev()->SetFillColor(aWindowColor); + + SetCompoundControl(true); } -TableControl_Impl::~TableControl_Impl() +TableControl::~TableControl() { disposeOnce(); } + +void TableControl::dispose() { + CallEventListeners(VclEventId::ObjectDying); + + SetModel(PTableModel()); + disposeAccessible(); + m_pVScroll.disposeAndClear(); m_pHScroll.disposeAndClear(); m_pScrollCorner.disposeAndClear(); m_pDataWindow.disposeAndClear(); m_pTableFunctionSet.reset(); m_pSelEngine.reset(); + + Control::dispose(); } -void TableControl_Impl::setModel(const PTableModel& _pModel) +void TableControl::GetFocus() +{ + showCursor(); + + Control::GetFocus(); +} + +void TableControl::LoseFocus() +{ + hideCursor(); + + Control::LoseFocus(); +} + +void TableControl::KeyInput(const KeyEvent& rKEvt) +{ + bool bHandled = false; + + const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); + sal_uInt16 nKeyCode = rKeyCode.GetCode(); + + struct ActionMapEntry + { + sal_uInt16 nKeyCode; + sal_uInt16 nKeyModifier; + TableControlAction eAction; + } static const aKnownActions[] + = { { KEY_DOWN, 0, TableControlAction::cursorDown }, + { KEY_UP, 0, TableControlAction::cursorUp }, + { KEY_LEFT, 0, TableControlAction::cursorLeft }, + { KEY_RIGHT, 0, TableControlAction::cursorRight }, + { KEY_HOME, 0, TableControlAction::cursorToLineStart }, + { KEY_END, 0, TableControlAction::cursorToLineEnd }, + { KEY_PAGEUP, 0, TableControlAction::cursorPageUp }, + { KEY_PAGEDOWN, 0, TableControlAction::cursorPageDown }, + { KEY_PAGEUP, KEY_MOD1, TableControlAction::cursorToFirstLine }, + { KEY_PAGEDOWN, KEY_MOD1, TableControlAction::cursorToLastLine }, + { KEY_HOME, KEY_MOD1, TableControlAction::cursorTopLeft }, + { KEY_END, KEY_MOD1, TableControlAction::cursorBottomRight }, + { KEY_SPACE, KEY_MOD1, TableControlAction::cursorSelectRow }, + { KEY_UP, KEY_SHIFT, TableControlAction::cursorSelectRowUp }, + { KEY_DOWN, KEY_SHIFT, TableControlAction::cursorSelectRowDown }, + { KEY_END, KEY_SHIFT, TableControlAction::cursorSelectRowAreaBottom }, + { KEY_HOME, KEY_SHIFT, TableControlAction::cursorSelectRowAreaTop } }; + for (const ActionMapEntry& rAction : aKnownActions) + { + if ((rAction.nKeyCode == nKeyCode) && (rAction.nKeyModifier == rKeyCode.GetModifier())) + { + bHandled = dispatchAction(rAction.eAction); + break; + } + } + + if (!bHandled) + Control::KeyInput(rKEvt); + else + { + commitCellEvent(AccessibleEventId::STATE_CHANGED, + Any(css::accessibility::AccessibleStateType::FOCUSED), Any()); + // Huh? What the heck? Why do we unconditionally notify a STATE_CHANGE/FOCUSED after each and every + // (handled) key stroke? + + commitTableEvent(AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, Any(), Any()); + // ditto: Why do we notify this unconditionally? We should find the right place to notify the + // ACTIVE_DESCENDANT_CHANGED event. + // Also, we should check if STATE_CHANGED/FOCUSED is really necessary: finally, the children are + // transient, aren't they? + } +} + +void TableControl::StateChanged(StateChangedType i_nStateChange) +{ + Control::StateChanged(i_nStateChange); + + // forward certain settings to the data window + switch (i_nStateChange) + { + case StateChangedType::ControlFocus: + invalidateSelectedRows(); + break; + + case StateChangedType::ControlBackground: + if (IsControlBackground()) + getDataWindow().SetControlBackground(GetControlBackground()); + else + getDataWindow().SetControlBackground(); + break; + + case StateChangedType::ControlForeground: + if (IsControlForeground()) + getDataWindow().SetControlForeground(GetControlForeground()); + else + getDataWindow().SetControlForeground(); + break; + + case StateChangedType::ControlFont: + if (IsControlFont()) + getDataWindow().SetControlFont(GetControlFont()); + else + getDataWindow().SetControlFont(); + break; + default:; + } +} + +void TableControl::Resize() +{ + Control::Resize(); + onResize(); +} + +void TableControl::SetModel(const PTableModel& _pModel) { SuppressCursor aHideCursor(*this); if (m_pModel) - m_pModel->removeTableModelListener(shared_from_this()); + m_pModel->removeTableModelListener(this); m_pModel = _pModel; if (!m_pModel) m_pModel = std::make_shared(); - m_pModel->addTableModelListener(shared_from_this()); + m_pModel->addTableModelListener(this); m_nCurRow = ROW_INVALID; m_nCurColumn = COL_INVALID; @@ -226,7 +358,7 @@ void TableControl_Impl::setModel(const PTableModel& _pModel) impl_ni_relayout(); // completely invalidate - m_rAntiImpl.Invalidate(); + Invalidate(); // reset cursor to (0,0) if (m_nRowCount) @@ -237,8 +369,8 @@ void TableControl_Impl::setModel(const PTableModel& _pModel) namespace { -bool lcl_adjustSelectedRows(::std::vector& io_selectionIndexes, - RowPos const i_firstAffectedRowIndex, TableSize const i_offset) +bool lcl_adjustSelectedRows(::std::vector& io_selectionIndexes, + sal_Int32 const i_firstAffectedRowIndex, TableSize const i_offset) { bool didChanges = false; for (auto& selectionIndex : io_selectionIndexes) @@ -252,9 +384,9 @@ bool lcl_adjustSelectedRows(::std::vector& io_selectionIndexes, } } -void TableControl_Impl::rowsInserted(RowPos i_first, RowPos i_last) +void TableControl::rowsInserted(sal_Int32 i_first, sal_Int32 i_last) { - OSL_PRECOND(i_last >= i_first, "TableControl_Impl::rowsInserted: invalid row indexes!"); + OSL_PRECOND(i_last >= i_first, "TableControl::rowsInserted: invalid row indexes!"); TableSize const insertedRows = i_last - i_first + 1; @@ -266,7 +398,7 @@ void TableControl_Impl::rowsInserted(RowPos i_first, RowPos i_last) // if the rows have been inserted before the current row, adjust this if (i_first <= m_nCurRow) - goTo(m_nCurColumn, m_nCurRow + insertedRows); + GoToCell(m_nCurColumn, m_nCurRow + insertedRows); // relayout, since the scrollbar need might have changed impl_ni_relayout(); @@ -282,10 +414,10 @@ void TableControl_Impl::rowsInserted(RowPos i_first, RowPos i_last) // call selection handlers, if necessary if (selectionChanged) - m_rAntiImpl.Select(); + Select(); } -void TableControl_Impl::rowsRemoved(RowPos i_first, RowPos i_last) +void TableControl::rowsRemoved(sal_Int32 i_first, sal_Int32 i_last) { sal_Int32 firstRemovedRow = i_first; sal_Int32 lastRemovedRow = i_last; @@ -301,8 +433,7 @@ void TableControl_Impl::rowsRemoved(RowPos i_first, RowPos i_last) } else { - ENSURE_OR_RETURN_VOID(i_last >= i_first, - "TableControl_Impl::rowsRemoved: illegal indexes!"); + ENSURE_OR_RETURN_VOID(i_last >= i_first, "TableControl::rowsRemoved: illegal indexes!"); for (sal_Int32 row = i_first; row <= i_last; ++row) { @@ -321,7 +452,7 @@ void TableControl_Impl::rowsRemoved(RowPos i_first, RowPos i_last) if (m_nCurRow >= m_nRowCount) { if (m_nRowCount > 0) - goTo(m_nCurColumn, m_nRowCount - 1); + GoToCell(m_nCurColumn, m_nRowCount - 1); else { m_nCurRow = ROW_INVALID; @@ -347,18 +478,18 @@ void TableControl_Impl::rowsRemoved(RowPos i_first, RowPos i_last) // call selection handlers, if necessary if (selectionChanged) - m_rAntiImpl.Select(); + Select(); } -void TableControl_Impl::columnInserted() +void TableControl::columnInserted() { m_nColumnCount = m_pModel->getColumnCount(); impl_ni_relayout(); - m_rAntiImpl.Invalidate(); + Invalidate(); } -void TableControl_Impl::columnRemoved() +void TableControl::columnRemoved() { m_nColumnCount = m_pModel->getColumnCount(); @@ -366,47 +497,47 @@ void TableControl_Impl::columnRemoved() if (m_nCurColumn >= m_nColumnCount) { if (m_nColumnCount > 0) - goTo(m_nCurColumn - 1, m_nCurRow); + GoToCell(m_nCurColumn - 1, m_nCurRow); else m_nCurColumn = COL_INVALID; } impl_ni_relayout(); - m_rAntiImpl.Invalidate(); + Invalidate(); } -void TableControl_Impl::allColumnsRemoved() +void TableControl::allColumnsRemoved() { m_nColumnCount = m_pModel->getColumnCount(); impl_ni_relayout(); - m_rAntiImpl.Invalidate(); + Invalidate(); } -void TableControl_Impl::cellsUpdated(RowPos const i_firstRow, RowPos const i_lastRow) +void TableControl::cellsUpdated(sal_Int32 const i_firstRow, sal_Int32 const i_lastRow) { invalidateRowRange(i_firstRow, i_lastRow); } -void TableControl_Impl::tableMetricsChanged() +void TableControl::tableMetricsChanged() { impl_ni_updateCachedTableMetrics(); impl_ni_relayout(); - m_rAntiImpl.Invalidate(); + Invalidate(); } -void TableControl_Impl::impl_invalidateColumn(ColPos const i_column) +void TableControl::impl_invalidateColumn(sal_Int32 const i_column) { tools::Rectangle const aAllCellsArea(impl_getAllVisibleCellsArea()); const TableColumnGeometry aColumn(*this, aAllCellsArea, i_column); if (aColumn.isValid()) - m_rAntiImpl.Invalidate(aColumn.getRect()); + Invalidate(aColumn.getRect()); } -void TableControl_Impl::columnChanged(ColPos const i_column, - ColumnAttributeGroup const i_attributeGroup) +void TableControl::columnChanged(sal_Int32 const i_column, + ColumnAttributeGroup const i_attributeGroup) { ColumnAttributeGroup nGroup(i_attributeGroup); if (nGroup & ColumnAttributeGroup::APPEARANCE) @@ -428,10 +559,10 @@ void TableControl_Impl::columnChanged(ColPos const i_column, OSL_ENSURE((nGroup == ColumnAttributeGroup::NONE) || (i_attributeGroup == ColumnAttributeGroup::ALL), - "TableControl_Impl::columnChanged: don't know how to handle this change!"); + "TableControl::columnChanged: don't know how to handle this change!"); } -tools::Rectangle TableControl_Impl::impl_getAllVisibleCellsArea() const +tools::Rectangle TableControl::impl_getAllVisibleCellsArea() const { tools::Rectangle aArea(Point(0, 0), Size(0, 0)); @@ -462,7 +593,7 @@ tools::Rectangle TableControl_Impl::impl_getAllVisibleCellsArea() const return aArea; } -tools::Rectangle TableControl_Impl::impl_getAllVisibleDataCellArea() const +tools::Rectangle TableControl::impl_getAllVisibleDataCellArea() const { tools::Rectangle aArea(impl_getAllVisibleCellsArea()); aArea.SetLeft(m_nRowHeaderWidthPixel); @@ -470,28 +601,25 @@ tools::Rectangle TableControl_Impl::impl_getAllVisibleDataCellArea() const return aArea; } -void TableControl_Impl::impl_ni_updateCachedTableMetrics() +void TableControl::impl_ni_updateCachedTableMetrics() { m_nRowHeightPixel - = m_rAntiImpl.LogicToPixel(Size(0, m_pModel->getRowHeight()), MapMode(MapUnit::MapAppFont)) - .Height(); + = LogicToPixel(Size(0, m_pModel->getRowHeight()), MapMode(MapUnit::MapAppFont)).Height(); m_nColHeaderHeightPixel = 0; if (m_pModel->hasColumnHeaders()) - m_nColHeaderHeightPixel = m_rAntiImpl - .LogicToPixel(Size(0, m_pModel->getColumnHeaderHeight()), - MapMode(MapUnit::MapAppFont)) - .Height(); + m_nColHeaderHeightPixel + = LogicToPixel(Size(0, m_pModel->getColumnHeaderHeight()), MapMode(MapUnit::MapAppFont)) + .Height(); m_nRowHeaderWidthPixel = 0; if (m_pModel->hasRowHeaders()) - m_nRowHeaderWidthPixel = m_rAntiImpl - .LogicToPixel(Size(m_pModel->getRowHeaderWidth(), 0), - MapMode(MapUnit::MapAppFont)) - .Width(); + m_nRowHeaderWidthPixel + = LogicToPixel(Size(m_pModel->getRowHeaderWidth(), 0), MapMode(MapUnit::MapAppFont)) + .Width(); } -void TableControl_Impl::impl_ni_updateCachedModelValues() +void TableControl::impl_ni_updateCachedModelValues() { m_pInputHandler = m_pModel->getInputHandler(); if (!m_pInputHandler) @@ -589,8 +717,9 @@ TableSize lcl_getRowsFittingInto(tools::Long _nOverallHeight, tools::Long _nRowH with the first visible column as given. Partially fitting columns are counted, too, if the respective parameter says so. */ -TableSize lcl_getColumnsVisibleWithin(const tools::Rectangle& _rArea, ColPos _nFirstVisibleColumn, - const TableControl_Impl& _rControl, bool _bAcceptPartialRow) +TableSize lcl_getColumnsVisibleWithin(const tools::Rectangle& _rArea, + sal_Int32 _nFirstVisibleColumn, const TableControl& _rControl, + bool _bAcceptPartialRow) { TableSize visibleColumns = 0; TableColumnGeometry aColumn(_rControl, _rArea, _nFirstVisibleColumn); @@ -608,16 +737,16 @@ TableSize lcl_getColumnsVisibleWithin(const tools::Rectangle& _rArea, ColPos _nF } } -tools::Long TableControl_Impl::impl_ni_calculateColumnWidths( - ColPos const i_assumeInflexibleColumnsUpToIncluding, bool const i_assumeVerticalScrollbar, - ::std::vector& o_newColWidthsPixel) const +tools::Long +TableControl::impl_ni_calculateColumnWidths(sal_Int32 const i_assumeInflexibleColumnsUpToIncluding, + bool const i_assumeVerticalScrollbar, + ::std::vector& o_newColWidthsPixel) const { // the available horizontal space - tools::Long gridWidthPixel = m_rAntiImpl.GetOutputSizePixel().Width(); - ENSURE_OR_RETURN( - !!m_pModel, - "TableControl_Impl::impl_ni_calculateColumnWidths: not allowed without a model!", - gridWidthPixel); + tools::Long gridWidthPixel = GetOutputSizePixel().Width(); + ENSURE_OR_RETURN(!!m_pModel, + "TableControl::impl_ni_calculateColumnWidths: not allowed without a model!", + gridWidthPixel); if (m_pModel->hasRowHeaders() && (gridWidthPixel != 0)) { gridWidthPixel -= m_nRowHeaderWidthPixel; @@ -626,8 +755,7 @@ tools::Long TableControl_Impl::impl_ni_calculateColumnWidths( if (i_assumeVerticalScrollbar && (m_pModel->getVerticalScrollbarVisibility() != ScrollbarShowNever)) { - tools::Long nScrollbarMetrics - = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize(); + tools::Long nScrollbarMetrics = GetSettings().GetStyleSettings().GetScrollBarSize(); gridWidthPixel -= nScrollbarMetrics; } @@ -651,7 +779,7 @@ tools::Long TableControl_Impl::impl_ni_calculateColumnWidths( columnFlexibilities.reserve(colCount); tools::Long flexibilityDenominator = 0; size_t flexibleColumnCount = 0; - for (ColPos col = 0; col < colCount; ++col) + for (sal_Int32 col = 0; col < colCount; ++col) { PColumnModel const pColumn = m_pModel->getColumnModel(col); ENSURE_OR_THROW(!!pColumn, "invalid column returned by the model!"); @@ -665,7 +793,7 @@ tools::Long TableControl_Impl::impl_ni_calculateColumnWidths( // flexibility ::sal_Int32 flexibility = pColumn->getFlexibility(); - OSL_ENSURE(flexibility >= 0, "TableControl_Impl::impl_ni_calculateColumnWidths: a column's " + OSL_ENSURE(flexibility >= 0, "TableControl::impl_ni_calculateColumnWidths: a column's " "flexibility should be non-negative."); if ((flexibility < 0) // normalization || (!pColumn->isResizable()) // column not resizable => no auto-resize @@ -686,7 +814,7 @@ tools::Long TableControl_Impl::impl_ni_calculateColumnWidths( effectiveMin = MIN_COLUMN_WIDTH_PIXEL; tools::Long const maxWidth = appFontWidthToPixel(pColumn->getMaxWidth()); - OSL_ENSURE(minWidth <= maxWidth, "TableControl_Impl::impl_ni_calculateColumnWidths: " + OSL_ENSURE(minWidth <= maxWidth, "TableControl::impl_ni_calculateColumnWidths: " "pretty undecided 'bout its width limits, this " "column!"); if ((maxWidth > 0) && (maxWidth >= minWidth)) @@ -780,7 +908,7 @@ tools::Long TableControl_Impl::impl_ni_calculateColumnWidths( continue; OSL_ENSURE(o_newColWidthsPixel[i] <= effectiveColumnLimits[i].second, - "TableControl_Impl::impl_ni_calculateColumnWidths: inconsistency!"); + "TableControl::impl_ni_calculateColumnWidths: inconsistency!"); if (o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first) { columnFlexibilities[i] = 0; @@ -858,7 +986,7 @@ tools::Long TableControl_Impl::impl_ni_calculateColumnWidths( continue; OSL_ENSURE(o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first, - "TableControl_Impl::impl_ni_calculateColumnWidths: inconsistency!"); + "TableControl::impl_ni_calculateColumnWidths: inconsistency!"); if (o_newColWidthsPixel[i] <= effectiveColumnLimits[i].first) { columnFlexibilities[i] = 0; @@ -876,10 +1004,10 @@ tools::Long TableControl_Impl::impl_ni_calculateColumnWidths( return gridWidthPixel; } -void TableControl_Impl::impl_ni_relayout(ColPos const i_assumeInflexibleColumnsUpToIncluding) +void TableControl::impl_ni_relayout(sal_Int32 const i_assumeInflexibleColumnsUpToIncluding) { ENSURE_OR_RETURN_VOID(!m_bUpdatingColWidths, - "TableControl_Impl::impl_ni_relayout: recursive call detected!"); + "TableControl::impl_ni_relayout: recursive call detected!"); m_aColumnWidths.resize(0); if (!m_pModel) @@ -906,18 +1034,17 @@ void TableControl_Impl::impl_ni_relayout(ColPos const i_assumeInflexibleColumnsU i_assumeInflexibleColumnsUpToIncluding, true, newWidthsPixel); // the width/height of a scrollbar, needed several times below - tools::Long const nScrollbarMetrics - = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize(); + tools::Long const nScrollbarMetrics = GetSettings().GetStyleSettings().GetScrollBarSize(); // determine the playground for the data cells (excluding headers) // TODO: what if the control is smaller than needed for the headers/scrollbars? - tools::Rectangle aDataCellPlayground(Point(0, 0), m_rAntiImpl.GetOutputSizePixel()); + tools::Rectangle aDataCellPlayground(Point(0, 0), GetOutputSizePixel()); aDataCellPlayground.SetLeft(m_nRowHeaderWidthPixel); aDataCellPlayground.SetTop(m_nColHeaderHeightPixel); - OSL_ENSURE( - (m_nRowCount == m_pModel->getRowCount()) && (m_nColumnCount == m_pModel->getColumnCount()), - "TableControl_Impl::impl_ni_relayout: how is this expected to work with invalid data?"); + OSL_ENSURE((m_nRowCount == m_pModel->getRowCount()) + && (m_nColumnCount == m_pModel->getColumnCount()), + "TableControl::impl_ni_relayout: how is this expected to work with invalid data?"); tools::Long const nAllColumnsWidth = ::std::accumulate(newWidthsPixel.begin(), newWidthsPixel.end(), 0); @@ -969,7 +1096,7 @@ void TableControl_Impl::impl_ni_relayout(ColPos const i_assumeInflexibleColumnsU m_aColumnWidths.reserve(colCount); tools::Long accumulatedWidthPixel = m_nRowHeaderWidthPixel; bool anyColumnWidthChanged = false; - for (ColPos col = 0; col < colCount; ++col) + for (sal_Int32 col = 0; col < colCount; ++col) { const tools::Long columnStart = accumulatedWidthPixel; const tools::Long columnEnd = columnStart + newWidthsPixel[col]; @@ -1016,22 +1143,21 @@ void TableControl_Impl::impl_ni_relayout(ColPos const i_assumeInflexibleColumnsU bNeedHorizontalScrollbar); } -void TableControl_Impl::impl_ni_positionChildWindows(tools::Rectangle const& i_dataCellPlayground, - bool const i_verticalScrollbar, - bool const i_horizontalScrollbar) +void TableControl::impl_ni_positionChildWindows(tools::Rectangle const& i_dataCellPlayground, + bool const i_verticalScrollbar, + bool const i_horizontalScrollbar) { - tools::Long const nScrollbarMetrics - = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize(); + tools::Long const nScrollbarMetrics = GetSettings().GetStyleSettings().GetScrollBarSize(); // create or destroy the vertical scrollbar, as needed lcl_updateScrollbar( - m_rAntiImpl, m_pVScroll, i_verticalScrollbar, + *this, m_pVScroll, i_verticalScrollbar, lcl_getRowsFittingInto(i_dataCellPlayground.GetHeight(), m_nRowHeightPixel, false), // visible units m_nTopRow, // current position m_nRowCount, // range false, // vertical - LINK(this, TableControl_Impl, OnScroll) // scroll handler + LINK(this, TableControl, OnScroll) // scroll handler ); // position it @@ -1044,13 +1170,13 @@ void TableControl_Impl::impl_ni_positionChildWindows(tools::Rectangle const& i_d // create or destroy the horizontal scrollbar, as needed lcl_updateScrollbar( - m_rAntiImpl, m_pHScroll, i_horizontalScrollbar, + *this, m_pHScroll, i_horizontalScrollbar, lcl_getColumnsVisibleWithin(i_dataCellPlayground, m_nLeftColumn, *this, false), // visible units m_nLeftColumn, // current position m_nColumnCount, // range true, // horizontal - LINK(this, TableControl_Impl, OnScroll) // scroll handler + LINK(this, TableControl, OnScroll) // scroll handler ); // position it @@ -1083,7 +1209,7 @@ void TableControl_Impl::impl_ni_positionChildWindows(tools::Rectangle const& i_d } else if (!bHaveScrollCorner && bNeedScrollCorner) { - m_pScrollCorner = VclPtr::Create(&m_rAntiImpl); + m_pScrollCorner = VclPtr::Create(this); m_pScrollCorner->SetSizePixel(Size(nScrollbarMetrics, nScrollbarMetrics)); m_pScrollCorner->SetPosPixel( Point(i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1)); @@ -1101,18 +1227,28 @@ void TableControl_Impl::impl_ni_positionChildWindows(tools::Rectangle const& i_d i_dataCellPlayground.GetHeight() + m_nColHeaderHeightPixel)); } -void TableControl_Impl::onResize() +void TableControl::onResize() { impl_ni_relayout(); checkCursorPosition(); } -void TableControl_Impl::doPaintContent(vcl::RenderContext& rRenderContext, - const tools::Rectangle& _rUpdateRect) +void TableControl::Select() { - if (!getModel()) + ImplCallEventListenersAndHandler(VclEventId::TableRowSelect, nullptr); + commitAccessibleEvent(AccessibleEventId::SELECTION_CHANGED); + + commitTableEvent(AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, Any(), Any()); + // TODO: why do we notify this when the *selection* changed? Shouldn't we find a better place for this, + // actually, when the active descendant, i.e. the current cell, *really* changed? +} + +void TableControl::doPaintContent(vcl::RenderContext& rRenderContext, + const tools::Rectangle& _rUpdateRect) +{ + if (!GetModel()) return; - PTableRenderer pRenderer = getModel()->getRenderer(); + PTableRenderer pRenderer = GetModel()->getRenderer(); DBG_ASSERT(!!pRenderer, "TableDataWindow::doPaintContent: invalid renderer!"); if (!pRenderer) return; @@ -1174,7 +1310,7 @@ void TableControl_Impl::doPaintContent(vcl::RenderContext& rRenderContext, } // draw the table content row by row - TableSize colCount = getModel()->getColumnCount(); + TableSize colCount = GetModel()->getColumnCount(); // paint all rows tools::Rectangle const aAllDataCellsArea(impl_getAllVisibleDataCellArea()); @@ -1184,8 +1320,8 @@ void TableControl_Impl::doPaintContent(vcl::RenderContext& rRenderContext, if (_rUpdateRect.GetIntersection(aRowIterator.getRect()).IsEmpty()) continue; - bool const isControlFocused = m_rAntiImpl.HasControlFocus(); - bool const isSelectedRow = isRowSelected(aRowIterator.getRow()); + bool const isControlFocused = HasControlFocus(); + bool const isSelectedRow = IsRowSelected(aRowIterator.getRow()); tools::Rectangle const aRect = aRowIterator.getRect().GetIntersection(aAllDataCellsArea); @@ -1214,20 +1350,20 @@ void TableControl_Impl::doPaintContent(vcl::RenderContext& rRenderContext, } } -void TableControl_Impl::hideCursor() +void TableControl::hideCursor() { if (++m_nCursorHidden == 1) impl_ni_doSwitchCursor(false); } -void TableControl_Impl::showCursor() +void TableControl::showCursor() { - DBG_ASSERT(m_nCursorHidden > 0, "TableControl_Impl::showCursor: cursor not hidden!"); + DBG_ASSERT(m_nCursorHidden > 0, "TableControl::showCursor: cursor not hidden!"); if (--m_nCursorHidden == 0) impl_ni_doSwitchCursor(true); } -bool TableControl_Impl::dispatchAction(TableControlAction _eAction) +bool TableControl::dispatchAction(TableControlAction _eAction) { bool bSuccess = false; bool selectionChanged = false; @@ -1258,7 +1394,7 @@ bool TableControl_Impl::dispatchAction(TableControlAction _eAction) else { if (m_nCurRow < m_nRowCount - 1) - bSuccess = goTo(m_nCurColumn, m_nCurRow + 1); + bSuccess = GoToCell(m_nCurColumn, m_nCurRow + 1); } break; @@ -1288,59 +1424,59 @@ bool TableControl_Impl::dispatchAction(TableControlAction _eAction) else { if (m_nCurRow > 0) - bSuccess = goTo(m_nCurColumn, m_nCurRow - 1); + bSuccess = GoToCell(m_nCurColumn, m_nCurRow - 1); } break; case TableControlAction::cursorLeft: if (m_nCurColumn > 0) - bSuccess = goTo(m_nCurColumn - 1, m_nCurRow); + bSuccess = GoToCell(m_nCurColumn - 1, m_nCurRow); else if ((m_nCurColumn == 0) && (m_nCurRow > 0)) - bSuccess = goTo(m_nColumnCount - 1, m_nCurRow - 1); + bSuccess = GoToCell(m_nColumnCount - 1, m_nCurRow - 1); break; case TableControlAction::cursorRight: if (m_nCurColumn < m_nColumnCount - 1) - bSuccess = goTo(m_nCurColumn + 1, m_nCurRow); + bSuccess = GoToCell(m_nCurColumn + 1, m_nCurRow); else if ((m_nCurColumn == m_nColumnCount - 1) && (m_nCurRow < m_nRowCount - 1)) - bSuccess = goTo(0, m_nCurRow + 1); + bSuccess = GoToCell(0, m_nCurRow + 1); break; case TableControlAction::cursorToLineStart: - bSuccess = goTo(0, m_nCurRow); + bSuccess = GoToCell(0, m_nCurRow); break; case TableControlAction::cursorToLineEnd: - bSuccess = goTo(m_nColumnCount - 1, m_nCurRow); + bSuccess = GoToCell(m_nColumnCount - 1, m_nCurRow); break; case TableControlAction::cursorToFirstLine: - bSuccess = goTo(m_nCurColumn, 0); + bSuccess = GoToCell(m_nCurColumn, 0); break; case TableControlAction::cursorToLastLine: - bSuccess = goTo(m_nCurColumn, m_nRowCount - 1); + bSuccess = GoToCell(m_nCurColumn, m_nRowCount - 1); break; case TableControlAction::cursorPageUp: { - RowPos nNewRow = ::std::max(RowPos(0), m_nCurRow - impl_getVisibleRows(false)); - bSuccess = goTo(m_nCurColumn, nNewRow); + sal_Int32 nNewRow = ::std::max(sal_Int32(0), m_nCurRow - impl_getVisibleRows(false)); + bSuccess = GoToCell(m_nCurColumn, nNewRow); } break; case TableControlAction::cursorPageDown: { - RowPos nNewRow = ::std::min(m_nRowCount - 1, m_nCurRow + impl_getVisibleRows(false)); - bSuccess = goTo(m_nCurColumn, nNewRow); + sal_Int32 nNewRow = ::std::min(m_nRowCount - 1, m_nCurRow + impl_getVisibleRows(false)); + bSuccess = GoToCell(m_nCurColumn, nNewRow); } break; case TableControlAction::cursorTopLeft: - bSuccess = goTo(0, 0); + bSuccess = GoToCell(0, 0); break; case TableControlAction::cursorBottomRight: - bSuccess = goTo(m_nColumnCount - 1, m_nRowCount - 1); + bSuccess = GoToCell(m_nColumnCount - 1, m_nRowCount - 1); break; case TableControlAction::cursorSelectRow: @@ -1540,12 +1676,12 @@ bool TableControl_Impl::dispatchAction(TableControlAction _eAction) else { //select the region between the current and the upper row - RowPos iter = m_nCurRow; + sal_Int32 iter = m_nCurRow; invalidateSelectedRegion(m_nCurRow, 0); //put the rows in vector while (iter >= 0) { - if (!isRowSelected(iter)) + if (!IsRowSelected(iter)) m_aSelectedRows.push_back(iter); --iter; } @@ -1566,12 +1702,12 @@ bool TableControl_Impl::dispatchAction(TableControlAction _eAction) else if (m_pSelEngine->GetSelectionMode() == SelectionMode::Single) return false; //select the region between the current and the last row - RowPos iter = m_nCurRow; + sal_Int32 iter = m_nCurRow; invalidateSelectedRegion(m_nCurRow, m_nRowCount - 1); //put the rows in the vector while (iter <= m_nRowCount) { - if (!isRowSelected(iter)) + if (!IsRowSelected(iter)) m_aSelectedRows.push_back(iter); ++iter; } @@ -1584,19 +1720,19 @@ bool TableControl_Impl::dispatchAction(TableControlAction _eAction) } break; default: - OSL_FAIL("TableControl_Impl::dispatchAction: unsupported action!"); + OSL_FAIL("TableControl::dispatchAction: unsupported action!"); break; } if (bSuccess && selectionChanged) { - m_rAntiImpl.Select(); + Select(); } return bSuccess; } -void TableControl_Impl::impl_ni_doSwitchCursor(bool _bShow) +void TableControl::impl_ni_doSwitchCursor(bool _bShow) { PTableRenderer pRenderer = m_pModel ? m_pModel->getRenderer() : PTableRenderer(); if (pRenderer) @@ -1609,17 +1745,17 @@ void TableControl_Impl::impl_ni_doSwitchCursor(bool _bShow) } } -RowPos TableControl_Impl::getRowAtPoint(const Point& rPoint) const +sal_Int32 TableControl::getRowAtPoint(const Point& rPoint) const { return impl_getRowForAbscissa(rPoint.Y()); } -ColPos TableControl_Impl::getColAtPoint(const Point& rPoint) const +sal_Int32 TableControl::getColAtPoint(const Point& rPoint) const { return impl_getColumnForOrdinate(rPoint.X()); } -TableCell TableControl_Impl::hitTest(Point const& i_point) const +TableCell TableControl::hitTest(Point const& i_point) const { TableCell aCell(getColAtPoint(i_point), getRowAtPoint(i_point)); if (aCell.nColumn > COL_ROW_HEADERS) @@ -1635,28 +1771,28 @@ TableCell TableControl_Impl::hitTest(Point const& i_point) const return aCell; } -ColumnMetrics TableControl_Impl::getColumnMetrics(ColPos const i_column) const +ColumnMetrics TableControl::getColumnMetrics(sal_Int32 const i_column) const { ENSURE_OR_RETURN((i_column >= 0) && (i_column < m_pModel->getColumnCount()), - "TableControl_Impl::getColumnMetrics: illegal column index!", ColumnMetrics()); + "TableControl::getColumnMetrics: illegal column index!", ColumnMetrics()); return m_aColumnWidths[i_column]; } -PTableModel TableControl_Impl::getModel() const { return m_pModel; } +PTableModel TableControl::GetModel() const { return m_pModel; } -ColPos TableControl_Impl::getCurrentColumn() const { return m_nCurColumn; } +sal_Int32 TableControl::GetCurrentColumn() const { return m_nCurColumn; } -RowPos TableControl_Impl::getCurrentRow() const { return m_nCurRow; } +sal_Int32 TableControl::GetCurrentRow() const { return m_nCurRow; } -::Size TableControl_Impl::getTableSizePixel() const { return m_pDataWindow->GetOutputSizePixel(); } +::Size TableControl::getTableSizePixel() const { return m_pDataWindow->GetOutputSizePixel(); } -void TableControl_Impl::setPointer(PointerStyle i_pointer) { m_pDataWindow->SetPointer(i_pointer); } +void TableControl::setPointer(PointerStyle i_pointer) { m_pDataWindow->SetPointer(i_pointer); } -void TableControl_Impl::captureMouse() { m_pDataWindow->CaptureMouse(); } +void TableControl::captureMouse() { m_pDataWindow->CaptureMouse(); } -void TableControl_Impl::releaseMouse() { m_pDataWindow->ReleaseMouse(); } +void TableControl::releaseMouse() { m_pDataWindow->ReleaseMouse(); } -void TableControl_Impl::invalidate(TableArea const i_what) +void TableControl::invalidate(TableArea const i_what) { switch (i_what) { @@ -1675,28 +1811,30 @@ void TableControl_Impl::invalidate(TableArea const i_what) } } -tools::Long TableControl_Impl::pixelWidthToAppFont(tools::Long const i_pixels) const +tools::Long TableControl::pixelWidthToAppFont(tools::Long const i_pixels) const { return m_pDataWindow->PixelToLogic(Size(i_pixels, 0), MapMode(MapUnit::MapAppFont)).Width(); } -tools::Long TableControl_Impl::appFontWidthToPixel(tools::Long const i_appFontUnits) const +tools::Long TableControl::appFontWidthToPixel(tools::Long const i_appFontUnits) const { return m_pDataWindow->LogicToPixel(Size(i_appFontUnits, 0), MapMode(MapUnit::MapAppFont)) .Width(); } -void TableControl_Impl::hideTracking() { m_pDataWindow->HideTracking(); } +void TableControl::hideTracking() { m_pDataWindow->HideTracking(); } -void TableControl_Impl::showTracking(tools::Rectangle const& i_location, - ShowTrackFlags const i_flags) +void TableControl::showTracking(tools::Rectangle const& i_location, ShowTrackFlags const i_flags) { m_pDataWindow->ShowTracking(i_location, i_flags); } -void TableControl_Impl::activateCell(ColPos const i_col, RowPos const i_row) { goTo(i_col, i_row); } +void TableControl::activateCell(sal_Int32 const i_col, sal_Int32 const i_row) +{ + GoToCell(i_col, i_row); +} -void TableControl_Impl::invalidateSelectedRegion(RowPos _nPrevRow, RowPos _nCurRow) +void TableControl::invalidateSelectedRegion(sal_Int32 _nPrevRow, sal_Int32 _nCurRow) { // get the visible area of the table control and set the Left and right border of the region to be repainted tools::Rectangle const aAllCells(impl_getAllVisibleCellsArea()); @@ -1731,14 +1869,14 @@ void TableControl_Impl::invalidateSelectedRegion(RowPos _nPrevRow, RowPos _nCurR invalidateRect(aInvalidateRect); } -void TableControl_Impl::invalidateRect(const tools::Rectangle& rInvalidateRect) +void TableControl::invalidateRect(const tools::Rectangle& rInvalidateRect) { m_pDataWindow->Invalidate(rInvalidateRect, m_pDataWindow->GetControlBackground().IsTransparent() ? InvalidateFlags::Transparent : InvalidateFlags::NONE); } -void TableControl_Impl::invalidateSelectedRows() +void TableControl::invalidateSelectedRows() { for (auto const& selectedRow : m_aSelectedRows) { @@ -1746,11 +1884,11 @@ void TableControl_Impl::invalidateSelectedRows() } } -void TableControl_Impl::invalidateRowRange(RowPos const i_firstRow, RowPos const i_lastRow) +void TableControl::invalidateRowRange(sal_Int32 const i_firstRow, sal_Int32 const i_lastRow) { - RowPos const firstRow = i_firstRow < m_nTopRow ? m_nTopRow : i_firstRow; - RowPos const lastVisibleRow = m_nTopRow + impl_getVisibleRows(true) - 1; - RowPos const lastRow + sal_Int32 const firstRow = i_firstRow < m_nTopRow ? m_nTopRow : i_firstRow; + sal_Int32 const lastVisibleRow = m_nTopRow + impl_getVisibleRows(true) - 1; + sal_Int32 const lastRow = ((i_lastRow == ROW_INVALID) || (i_lastRow > lastVisibleRow)) ? lastVisibleRow : i_lastRow; tools::Rectangle aInvalidateRect; @@ -1769,7 +1907,7 @@ void TableControl_Impl::invalidateRowRange(RowPos const i_firstRow, RowPos const invalidateRect(aInvalidateRect); } -void TableControl_Impl::checkCursorPosition() +void TableControl::checkCursorPosition() { TableSize nVisibleRows = impl_getVisibleRows(true); TableSize nVisibleCols = impl_getVisibleColumns(true); @@ -1794,31 +1932,31 @@ void TableControl_Impl::checkCursorPosition() m_pDataWindow->Invalidate(); } -TableSize TableControl_Impl::impl_getVisibleRows(bool _bAcceptPartialRow) const +TableSize TableControl::impl_getVisibleRows(bool _bAcceptPartialRow) const { - DBG_ASSERT(m_pDataWindow, "TableControl_Impl::impl_getVisibleRows: no data window!"); + DBG_ASSERT(m_pDataWindow, "TableControl::impl_getVisibleRows: no data window!"); return lcl_getRowsFittingInto(m_pDataWindow->GetOutputSizePixel().Height() - m_nColHeaderHeightPixel, m_nRowHeightPixel, _bAcceptPartialRow); } -TableSize TableControl_Impl::impl_getVisibleColumns(bool _bAcceptPartialCol) const +TableSize TableControl::impl_getVisibleColumns(bool _bAcceptPartialCol) const { - DBG_ASSERT(m_pDataWindow, "TableControl_Impl::impl_getVisibleColumns: no data window!"); + DBG_ASSERT(m_pDataWindow, "TableControl::impl_getVisibleColumns: no data window!"); return lcl_getColumnsVisibleWithin( tools::Rectangle(Point(0, 0), m_pDataWindow->GetOutputSizePixel()), m_nLeftColumn, *this, _bAcceptPartialCol); } -bool TableControl_Impl::goTo(ColPos _nColumn, RowPos _nRow) +bool TableControl::GoToCell(sal_Int32 _nColumn, sal_Int32 _nRow) { // TODO: give veto listeners a chance if ((_nColumn < 0) || (_nColumn >= m_nColumnCount) || (_nRow < 0) || (_nRow >= m_nRowCount)) { - OSL_ENSURE(false, "TableControl_Impl::goTo: invalid row or column index!"); + OSL_ENSURE(false, "TableControl::GoToCell: invalid row or column index!"); return false; } @@ -1831,11 +1969,11 @@ bool TableControl_Impl::goTo(ColPos _nColumn, RowPos _nRow) return true; } -void TableControl_Impl::ensureVisible(ColPos _nColumn, RowPos _nRow) +void TableControl::ensureVisible(sal_Int32 _nColumn, sal_Int32 _nRow) { DBG_ASSERT((_nColumn >= 0) && (_nColumn < m_nColumnCount) && (_nRow >= 0) && (_nRow < m_nRowCount), - "TableControl_Impl::ensureVisible: invalid coordinates!"); + "TableControl::ensureVisible: invalid coordinates!"); SuppressCursor aHideCursor(*this); @@ -1862,7 +2000,7 @@ void TableControl_Impl::ensureVisible(ColPos _nColumn, RowPos _nRow) } } -OUString TableControl_Impl::getCellContentAsString(RowPos const i_row, ColPos const i_col) +OUString TableControl::GetAccessibleCellText(sal_Int32 const i_row, sal_Int32 const i_col) { Any aCellValue; m_pModel->getCellContent(i_col, i_row, aCellValue); @@ -1873,14 +2011,14 @@ OUString TableControl_Impl::getCellContentAsString(RowPos const i_row, ColPos co return sCellStringContent; } -TableSize TableControl_Impl::impl_ni_ScrollRows(TableSize _nRowDelta) +TableSize TableControl::impl_ni_ScrollRows(TableSize _nRowDelta) { // compute new top row - RowPos nNewTopRow = ::std::max(::std::min(static_cast(m_nTopRow + _nRowDelta), - static_cast(m_nRowCount - 1)), - RowPos(0)); + sal_Int32 nNewTopRow = ::std::max(::std::min(static_cast(m_nTopRow + _nRowDelta), + static_cast(m_nRowCount - 1)), + sal_Int32(0)); - RowPos nOldTopRow = m_nTopRow; + sal_Int32 nOldTopRow = m_nTopRow; m_nTopRow = nNewTopRow; // if updates are enabled currently, scroll the viewport @@ -1924,25 +2062,25 @@ TableSize TableControl_Impl::impl_ni_ScrollRows(TableSize _nRowDelta) // - the user scroll to row number 1 // => in this case, the need for the scrollbar vanishes immediately. if (m_nTopRow == 0) - m_rAntiImpl.PostUserEvent(LINK(this, TableControl_Impl, OnUpdateScrollbars)); + PostUserEvent(LINK(this, TableControl, OnUpdateScrollbars)); return static_cast(m_nTopRow - nOldTopRow); } -TableSize TableControl_Impl::impl_scrollRows(TableSize const i_rowDelta) +TableSize TableControl::impl_scrollRows(TableSize const i_rowDelta) { return impl_ni_ScrollRows(i_rowDelta); } -TableSize TableControl_Impl::impl_ni_ScrollColumns(TableSize _nColumnDelta) +TableSize TableControl::impl_ni_ScrollColumns(TableSize _nColumnDelta) { // compute new left column - const ColPos nNewLeftColumn - = ::std::max(::std::min(static_cast(m_nLeftColumn + _nColumnDelta), - static_cast(m_nColumnCount - 1)), - ColPos(0)); + const sal_Int32 nNewLeftColumn + = ::std::max(::std::min(static_cast(m_nLeftColumn + _nColumnDelta), + static_cast(m_nColumnCount - 1)), + sal_Int32(0)); - const ColPos nOldLeftColumn = m_nLeftColumn; + const sal_Int32 nOldLeftColumn = m_nLeftColumn; m_nLeftColumn = nNewLeftColumn; // if updates are enabled currently, scroll the viewport @@ -1990,35 +2128,55 @@ TableSize TableControl_Impl::impl_ni_ScrollColumns(TableSize _nColumnDelta) // the scrollbar when it is, in theory, unnecessary, but currently at a position > 0. In this case, it will // be auto-hidden when it's scrolled back to pos 0. if (m_nLeftColumn == 0) - m_rAntiImpl.PostUserEvent(LINK(this, TableControl_Impl, OnUpdateScrollbars)); + PostUserEvent(LINK(this, TableControl, OnUpdateScrollbars)); return static_cast(m_nLeftColumn - nOldLeftColumn); } -TableSize TableControl_Impl::impl_scrollColumns(TableSize const i_columnDelta) +TableSize TableControl::impl_scrollColumns(TableSize const i_columnDelta) { return impl_ni_ScrollColumns(i_columnDelta); } -SelectionEngine* TableControl_Impl::getSelEngine() { return m_pSelEngine.get(); } +SelectionEngine* TableControl::getSelEngine() { return m_pSelEngine.get(); } -bool TableControl_Impl::isRowSelected(RowPos i_row) const +sal_Int32 TableControl::GetRowCount() const { return m_pModel->getRowCount(); } + +sal_Int32 TableControl::GetColumnCount() const { return m_pModel->getColumnCount(); } + +OUString TableControl::GetRowName(sal_Int32 nIndex) const +{ + OUString sRowName; + m_pModel->getRowHeading(nIndex) >>= sRowName; + return sRowName; +} + +OUString TableControl::GetColumnName(sal_Int32 nIndex) const +{ + return m_pModel->getColumnModel(nIndex)->getName(); +} + +bool TableControl::HasRowHeader() { return m_pModel->hasRowHeaders(); } + +bool TableControl::HasColumnHeader() { return m_pModel->hasColumnHeaders(); } + +bool TableControl::IsRowSelected(sal_Int32 i_row) const { return ::std::find(m_aSelectedRows.begin(), m_aSelectedRows.end(), i_row) != m_aSelectedRows.end(); } -RowPos TableControl_Impl::getSelectedRowIndex(size_t const i_selectionIndex) const +sal_Int32 TableControl::GetSelectedRowIndex(size_t const i_selectionIndex) const { if (i_selectionIndex < m_aSelectedRows.size()) return m_aSelectedRows[i_selectionIndex]; return ROW_INVALID; } -int TableControl_Impl::getRowSelectedNumber(const ::std::vector& selectedRows, - RowPos current) +int TableControl::getRowSelectedNumber(const ::std::vector& selectedRows, + sal_Int32 current) { - std::vector::const_iterator it + std::vector::const_iterator it = ::std::find(selectedRows.begin(), selectedRows.end(), current); if (it != selectedRows.end()) { @@ -2027,7 +2185,47 @@ int TableControl_Impl::getRowSelectedNumber(const ::std::vector& selecte return -1; } -ColPos TableControl_Impl::impl_getColumnForOrdinate(tools::Long const i_ordinate) const +void TableControl::SelectRow(sal_Int32 nRowIndex, bool bSelect) +{ + ENSURE_OR_RETURN_VOID((nRowIndex >= 0) && (nRowIndex < m_pModel->getRowCount()), + "TableControl::SelectRow: invalid row index!"); + + if (bSelect) + { + if (!markRowAsSelected(nRowIndex)) + // nothing to do + return; + } + else + { + markRowAsDeselected(nRowIndex); + } + + invalidateRowRange(nRowIndex, nRowIndex); + Select(); +} + +void TableControl::SelectAllRows(bool bSelect) +{ + if (bSelect) + { + if (!markAllRowsAsSelected()) + // nothing to do + return; + } + else + { + if (!markAllRowsAsDeselected()) + // nothing to do + return; + } + + Invalidate(); + // TODO: can't we do better than this, and invalidate only the rows which changed? + Select(); +} + +sal_Int32 TableControl::impl_getColumnForOrdinate(tools::Long const i_ordinate) const { if ((m_aColumnWidths.empty()) || (i_ordinate < 0)) return COL_INVALID; @@ -2049,7 +2247,7 @@ ColPos TableControl_Impl::impl_getColumnForOrdinate(tools::Long const i_ordinate return lowerBound - m_aColumnWidths.begin(); } -RowPos TableControl_Impl::impl_getRowForAbscissa(tools::Long const i_abscissa) const +sal_Int32 TableControl::impl_getRowForAbscissa(tools::Long const i_abscissa) const { if (i_abscissa < 0) return ROW_INVALID; @@ -2062,9 +2260,9 @@ RowPos TableControl_Impl::impl_getRowForAbscissa(tools::Long const i_abscissa) c return row < m_pModel->getRowCount() ? row : ROW_INVALID; } -bool TableControl_Impl::markRowAsDeselected(RowPos const i_rowIndex) +bool TableControl::markRowAsDeselected(sal_Int32 const i_rowIndex) { - ::std::vector::iterator selPos + ::std::vector::iterator selPos = ::std::find(m_aSelectedRows.begin(), m_aSelectedRows.end(), i_rowIndex); if (selPos == m_aSelectedRows.end()) return false; @@ -2073,9 +2271,9 @@ bool TableControl_Impl::markRowAsDeselected(RowPos const i_rowIndex) return true; } -bool TableControl_Impl::markRowAsSelected(RowPos const i_rowIndex) +bool TableControl::markRowAsSelected(sal_Int32 const i_rowIndex) { - if (isRowSelected(i_rowIndex)) + if (IsRowSelected(i_rowIndex)) return false; SelectionMode const eSelMode = getSelEngine()->GetSelectionMode(); @@ -2097,14 +2295,14 @@ bool TableControl_Impl::markRowAsSelected(RowPos const i_rowIndex) break; default: - OSL_ENSURE(false, "TableControl_Impl::markRowAsSelected: unsupported selection mode!"); + OSL_ENSURE(false, "TableControl::markRowAsSelected: unsupported selection mode!"); return false; } return true; } -bool TableControl_Impl::markAllRowsAsDeselected() +bool TableControl::markAllRowsAsDeselected() { if (m_aSelectedRows.empty()) return false; @@ -2113,20 +2311,19 @@ bool TableControl_Impl::markAllRowsAsDeselected() return true; } -bool TableControl_Impl::markAllRowsAsSelected() +bool TableControl::markAllRowsAsSelected() { SelectionMode const eSelMode = getSelEngine()->GetSelectionMode(); ENSURE_OR_RETURN_FALSE(eSelMode == SelectionMode::Multiple, - "TableControl_Impl::markAllRowsAsSelected: unsupported selection mode!"); + "TableControl::markAllRowsAsSelected: unsupported selection mode!"); if (m_aSelectedRows.size() == size_t(m_pModel->getRowCount())) { #if OSL_DEBUG_LEVEL > 0 for (TableSize row = 0; row < m_pModel->getRowCount(); ++row) { - OSL_ENSURE( - isRowSelected(row), - "TableControl_Impl::markAllRowsAsSelected: inconsistency in the selected rows!"); + OSL_ENSURE(IsRowSelected(row), + "TableControl::markAllRowsAsSelected: inconsistency in the selected rows!"); } #endif // already all rows marked as selected @@ -2134,32 +2331,40 @@ bool TableControl_Impl::markAllRowsAsSelected() } m_aSelectedRows.clear(); - for (RowPos i = 0; i < m_pModel->getRowCount(); ++i) + for (sal_Int32 i = 0; i < m_pModel->getRowCount(); ++i) m_aSelectedRows.push_back(i); return true; } -void TableControl_Impl::commitAccessibleEvent(sal_Int16 const i_eventID) +void TableControl::commitAccessibleEvent(sal_Int16 const i_eventID) { impl_commitAccessibleEvent(i_eventID, Any()); } -void TableControl_Impl::commitCellEvent(sal_Int16 const i_eventID, const Any& i_newValue, - const Any& i_oldValue) +void TableControl::commitCellEvent(sal_Int16 const i_eventID, const Any& i_newValue, + const Any& i_oldValue) { if (m_xAccessibleTable.is()) m_xAccessibleTable->commitCellEvent(i_eventID, i_newValue, i_oldValue); } -void TableControl_Impl::commitTableEvent(sal_Int16 const i_eventID, const Any& i_newValue, - const Any& i_oldValue) +void TableControl::commitTableEvent(sal_Int16 const i_eventID, const Any& i_newValue, + const Any& i_oldValue) { if (m_xAccessibleTable.is()) m_xAccessibleTable->commitTableEvent(i_eventID, i_newValue, i_oldValue); } -tools::Rectangle TableControl_Impl::calcHeaderRect(bool bColHeader) +bool TableControl::ConvertPointToCellAddress(sal_Int32& rRow, sal_Int32& rColPos, + const Point& rPoint) +{ + rRow = getRowAtPoint(rPoint); + rColPos = getColAtPoint(rPoint); + return rRow >= 0; +} + +tools::Rectangle TableControl::calcHeaderRect(bool bColHeader) { tools::Rectangle const aRectTableWithHeaders(impl_getAllVisibleCellsArea()); Size const aSizeTableWithHeaders(aRectTableWithHeaders.GetSize()); @@ -2171,7 +2376,7 @@ tools::Rectangle TableControl_Impl::calcHeaderRect(bool bColHeader) Size(m_nRowHeaderWidthPixel, aSizeTableWithHeaders.Height())); } -tools::Rectangle TableControl_Impl::calcHeaderCellRect(bool bColHeader, sal_Int32 nPos) +tools::Rectangle TableControl::calcHeaderCellRect(bool bColHeader, sal_Int32 nPos) { tools::Rectangle const aHeaderRect = calcHeaderRect(bColHeader); TableCellGeometry const aGeometry(*this, aHeaderRect, bColHeader ? nPos : COL_ROW_HEADERS, @@ -2179,12 +2384,9 @@ tools::Rectangle TableControl_Impl::calcHeaderCellRect(bool bColHeader, sal_Int3 return aGeometry.getRect(); } -tools::Rectangle TableControl_Impl::calcTableRect() const -{ - return impl_getAllVisibleDataCellArea(); -} +tools::Rectangle TableControl::calcTableRect() const { return impl_getAllVisibleDataCellArea(); } -tools::Rectangle TableControl_Impl::calcCellRect(sal_Int32 nRow, sal_Int32 nCol) const +tools::Rectangle TableControl::calcCellRect(sal_Int32 nRow, sal_Int32 nCol) const { if (!m_pModel || (nRow == ROW_INVALID) || (nCol == COL_INVALID)) return tools::Rectangle(); @@ -2193,17 +2395,17 @@ tools::Rectangle TableControl_Impl::calcCellRect(sal_Int32 nRow, sal_Int32 nCol) return aCell.getRect(); } -IMPL_LINK_NOARG(TableControl_Impl, OnUpdateScrollbars, void*, void) +IMPL_LINK_NOARG(TableControl, OnUpdateScrollbars, void*, void) { // TODO: can't we simply use lcl_updateScrollbar here, so the scrollbars ranges are updated, instead of // doing a complete re-layout? impl_ni_relayout(); } -IMPL_LINK(TableControl_Impl, OnScroll, ScrollBar*, _pScrollbar, void) +IMPL_LINK(TableControl, OnScroll, ScrollBar*, _pScrollbar, void) { DBG_ASSERT((_pScrollbar == m_pVScroll) || (_pScrollbar == m_pHScroll), - "TableControl_Impl::OnScroll: where did this come from?"); + "TableControl::OnScroll: where did this come from?"); if (_pScrollbar == m_pVScroll) impl_ni_ScrollRows(_pScrollbar->GetDelta()); @@ -2211,8 +2413,16 @@ IMPL_LINK(TableControl_Impl, OnScroll, ScrollBar*, _pScrollbar, void) impl_ni_ScrollColumns(_pScrollbar->GetDelta()); } +IMPL_LINK_NOARG(TableControl, ImplSelectHdl, LinkParamNone*, void) { Select(); } + +rtl::Reference TableControl::CreateAccessible() +{ + rtl::Reference pParent = GetAccessibleParent(); + return getAccessible(pParent); +} + const rtl::Reference& -TableControl_Impl::getAccessible(const rtl::Reference& rpParent) +TableControl::getAccessible(const rtl::Reference& rpParent) { if (m_xAccessibleTable.is()) return m_xAccessibleTable; @@ -2220,146 +2430,200 @@ TableControl_Impl::getAccessible(const rtl::Reference& DBG_TESTSOLARMUTEX(); if (rpParent.is()) { - m_xAccessibleTable = new accessibility::AccessibleGridControl(rpParent, m_rAntiImpl); + m_xAccessibleTable = new accessibility::AccessibleGridControl(rpParent, *this); } return m_xAccessibleTable; } -void TableControl_Impl::disposeAccessible() +void TableControl::disposeAccessible() { if (m_xAccessibleTable.is()) m_xAccessibleTable->dispose(); m_xAccessibleTable.clear(); } -void TableControl_Impl::impl_commitAccessibleEvent(sal_Int16 const i_eventID, Any const& i_newValue) +sal_Int32 TableControl::GetAccessibleControlCount() const +{ + // TC_TABLE is always defined, no matter whether empty or not + sal_Int32 count = 1; + if (m_pModel->hasRowHeaders()) + ++count; + if (m_pModel->hasColumnHeaders()) + ++count; + return count; +} + +OUString TableControl::GetAccessibleObjectName(AccessibleTableControlObjType eObjType, + sal_Int32 nRow, sal_Int32 nCol) const +{ + OUString sRetText; + //Window* pWin; + switch (eObjType) + { + case AccessibleTableControlObjType::GRIDCONTROL: + sRetText = "Grid control"; + break; + case AccessibleTableControlObjType::TABLE: + sRetText = "Grid control"; + break; + case AccessibleTableControlObjType::ROWHEADERBAR: + sRetText = "RowHeaderBar"; + break; + case AccessibleTableControlObjType::COLUMNHEADERBAR: + sRetText = "ColumnHeaderBar"; + break; + case AccessibleTableControlObjType::TABLECELL: + //the name of the cell consists of column name and row name if defined + //if the name is equal to cell content, it'll be read twice + if (m_pModel->hasColumnHeaders()) + { + sRetText = GetColumnName(nCol) + " , "; + } + if (m_pModel->hasRowHeaders()) + { + sRetText += GetRowName(nRow) + " , "; + } + //aRetText = GetAccessibleCellText(_nRow, _nCol); + break; + case AccessibleTableControlObjType::ROWHEADERCELL: + sRetText = GetRowName(nRow); + break; + case AccessibleTableControlObjType::COLUMNHEADERCELL: + sRetText = GetColumnName(nCol); + break; + default: + OSL_FAIL("GridControl::GetAccessibleName: invalid enum!"); + } + return sRetText; +} + +OUString TableControl::GetAccessibleObjectDescription(AccessibleTableControlObjType eObjType) const +{ + OUString sRetText; + switch (eObjType) + { + case AccessibleTableControlObjType::GRIDCONTROL: + sRetText = "Grid control description"; + break; + case AccessibleTableControlObjType::TABLE: + sRetText = "TABLE description"; + break; + case AccessibleTableControlObjType::ROWHEADERBAR: + sRetText = "ROWHEADERBAR description"; + break; + case AccessibleTableControlObjType::COLUMNHEADERBAR: + sRetText = "COLUMNHEADERBAR description"; + break; + case AccessibleTableControlObjType::TABLECELL: + // the description of the cell consists of column name and row name if defined + // if the name is equal to cell content, it'll be read twice + if (m_pModel->hasColumnHeaders()) + { + sRetText = GetColumnName(GetCurrentColumn()) + " , "; + } + if (m_pModel->hasRowHeaders()) + { + sRetText += GetRowName(GetCurrentRow()); + } + break; + case AccessibleTableControlObjType::ROWHEADERCELL: + sRetText = "ROWHEADERCELL description"; + break; + case AccessibleTableControlObjType::COLUMNHEADERCELL: + sRetText = "COLUMNHEADERCELL description"; + break; + } + return sRetText; +} + +void TableControl::FillAccessibleStateSet(sal_Int64& rStateSet, + AccessibleTableControlObjType eObjType) const +{ + switch (eObjType) + { + case AccessibleTableControlObjType::GRIDCONTROL: + case AccessibleTableControlObjType::TABLE: + + rStateSet |= css::accessibility::AccessibleStateType::FOCUSABLE; + + if (m_pSelEngine->GetSelectionMode() == SelectionMode::Multiple) + rStateSet |= css::accessibility::AccessibleStateType::MULTI_SELECTABLE; + + if (HasChildPathFocus()) + rStateSet |= css::accessibility::AccessibleStateType::FOCUSED; + + if (IsActive()) + rStateSet |= css::accessibility::AccessibleStateType::ACTIVE; + + if (getDataWindow().IsEnabled()) + { + rStateSet |= css::accessibility::AccessibleStateType::ENABLED; + rStateSet |= css::accessibility::AccessibleStateType::SENSITIVE; + } + + if (IsReallyVisible()) + rStateSet |= css::accessibility::AccessibleStateType::VISIBLE; + + if (eObjType == AccessibleTableControlObjType::TABLE) + rStateSet |= css::accessibility::AccessibleStateType::MANAGES_DESCENDANTS; + break; + + case AccessibleTableControlObjType::COLUMNHEADERBAR: + case AccessibleTableControlObjType::ROWHEADERBAR: + rStateSet |= css::accessibility::AccessibleStateType::VISIBLE; + rStateSet |= css::accessibility::AccessibleStateType::MANAGES_DESCENDANTS; + break; + + case AccessibleTableControlObjType::TABLECELL: + { + rStateSet |= css::accessibility::AccessibleStateType::FOCUSABLE; + if (HasChildPathFocus()) + rStateSet |= css::accessibility::AccessibleStateType::FOCUSED; + rStateSet |= css::accessibility::AccessibleStateType::ACTIVE; + rStateSet |= css::accessibility::AccessibleStateType::TRANSIENT; + rStateSet |= css::accessibility::AccessibleStateType::SELECTABLE; + rStateSet |= css::accessibility::AccessibleStateType::VISIBLE; + rStateSet |= css::accessibility::AccessibleStateType::SHOWING; + if (IsRowSelected(GetCurrentRow())) + // Hmm? Wouldn't we expect the affected row to be a parameter to this function? + rStateSet |= css::accessibility::AccessibleStateType::SELECTED; + } + break; + + case AccessibleTableControlObjType::ROWHEADERCELL: + rStateSet |= css::accessibility::AccessibleStateType::VISIBLE; + rStateSet |= css::accessibility::AccessibleStateType::TRANSIENT; + break; + + case AccessibleTableControlObjType::COLUMNHEADERCELL: + rStateSet |= css::accessibility::AccessibleStateType::VISIBLE; + break; + } +} + +void TableControl::FillAccessibleStateSetForCell(sal_Int64& rStateSet, sal_Int32 nRow, + sal_uInt16) const +{ + if (IsRowSelected(nRow)) + rStateSet |= css::accessibility::AccessibleStateType::SELECTED; + if (HasChildPathFocus()) + rStateSet |= css::accessibility::AccessibleStateType::FOCUSED; + else // only transient when column is not focused + rStateSet |= css::accessibility::AccessibleStateType::TRANSIENT; + + rStateSet |= css::accessibility::AccessibleStateType::VISIBLE; + rStateSet |= css::accessibility::AccessibleStateType::SHOWING; + rStateSet |= css::accessibility::AccessibleStateType::ENABLED; + rStateSet |= css::accessibility::AccessibleStateType::SENSITIVE; + rStateSet |= css::accessibility::AccessibleStateType::ACTIVE; +} + +void TableControl::impl_commitAccessibleEvent(sal_Int16 const i_eventID, Any const& i_newValue) { if (m_xAccessibleTable.is()) m_xAccessibleTable->commitEvent(i_eventID, i_newValue, css::uno::Any()); } - -//= TableFunctionSet - -TableFunctionSet::TableFunctionSet(TableControl_Impl* _pTableControl) - : m_pTableControl(_pTableControl) - , m_nCurrentRow(ROW_INVALID) -{ -} - -TableFunctionSet::~TableFunctionSet() {} - -void TableFunctionSet::BeginDrag() {} - -void TableFunctionSet::CreateAnchor() { m_pTableControl->setAnchor(m_pTableControl->getCurRow()); } - -void TableFunctionSet::DestroyAnchor() { m_pTableControl->setAnchor(ROW_INVALID); } - -void TableFunctionSet::SetCursorAtPoint(const Point& rPoint, bool bDontSelectAtCursor) -{ - // newRow is the row which includes the point, getCurRow() is the last selected row, before the mouse click - RowPos newRow = m_pTableControl->getRowAtPoint(rPoint); - if (newRow == ROW_COL_HEADERS) - newRow = m_pTableControl->getTopRow(); - - ColPos newCol = m_pTableControl->getColAtPoint(rPoint); - if (newCol == COL_ROW_HEADERS) - newCol = m_pTableControl->getLeftColumn(); - - if ((newRow == ROW_INVALID) || (newCol == COL_INVALID)) - return; - - if (bDontSelectAtCursor) - { - if (m_pTableControl->getSelectedRowCount() > 1) - m_pTableControl->getSelEngine()->AddAlways(true); - } - else if (m_pTableControl->getAnchor() == m_pTableControl->getCurRow()) - { - //selected region lies above the last selection - if (m_pTableControl->getCurRow() >= newRow) - { - //put selected rows in vector - while (m_pTableControl->getAnchor() >= newRow) - { - m_pTableControl->markRowAsSelected(m_pTableControl->getAnchor()); - m_pTableControl->setAnchor(m_pTableControl->getAnchor() - 1); - } - m_pTableControl->setAnchor(m_pTableControl->getAnchor() + 1); - } - //selected region lies beneath the last selected row - else - { - while (m_pTableControl->getAnchor() <= newRow) - { - m_pTableControl->markRowAsSelected(m_pTableControl->getAnchor()); - m_pTableControl->setAnchor(m_pTableControl->getAnchor() + 1); - } - m_pTableControl->setAnchor(m_pTableControl->getAnchor() - 1); - } - m_pTableControl->invalidateSelectedRegion(m_pTableControl->getCurRow(), newRow); - } - //no region selected - else - { - if (!m_pTableControl->hasRowSelection()) - m_pTableControl->markRowAsSelected(newRow); - else - { - if (m_pTableControl->getSelEngine()->GetSelectionMode() == SelectionMode::Single) - { - DeselectAll(); - m_pTableControl->markRowAsSelected(newRow); - } - else - { - m_pTableControl->markRowAsSelected(newRow); - } - } - if (m_pTableControl->getSelectedRowCount() > 1 - && m_pTableControl->getSelEngine()->GetSelectionMode() != SelectionMode::Single) - m_pTableControl->getSelEngine()->AddAlways(true); - - m_pTableControl->invalidateRow(newRow); - } - m_pTableControl->goTo(newCol, newRow); -} - -bool TableFunctionSet::IsSelectionAtPoint(const Point& rPoint) -{ - m_pTableControl->getSelEngine()->AddAlways(false); - if (!m_pTableControl->hasRowSelection()) - return false; - else - { - RowPos curRow = m_pTableControl->getRowAtPoint(rPoint); - m_pTableControl->setAnchor(ROW_INVALID); - bool selected = m_pTableControl->isRowSelected(curRow); - m_nCurrentRow = curRow; - return selected; - } -} - -void TableFunctionSet::DeselectAtPoint(const Point&) -{ - m_pTableControl->invalidateRow(m_nCurrentRow); - m_pTableControl->markRowAsDeselected(m_nCurrentRow); -} - -void TableFunctionSet::DeselectAll() -{ - if (m_pTableControl->hasRowSelection()) - { - for (size_t i = 0; i < m_pTableControl->getSelectedRowCount(); ++i) - { - RowPos const rowIndex = m_pTableControl->getSelectedRowIndex(i); - m_pTableControl->invalidateRow(rowIndex); - } - - m_pTableControl->markAllRowsAsDeselected(); - } -} - } // namespace svt::table /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/toolkit/source/controls/table/TableFunctionSet.cxx b/toolkit/source/controls/table/TableFunctionSet.cxx new file mode 100644 index 000000000000..d2ff2c0d7242 --- /dev/null +++ b/toolkit/source/controls/table/TableFunctionSet.cxx @@ -0,0 +1,146 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 +#include + +namespace svt::table +{ +TableFunctionSet::TableFunctionSet(TableControl& rTableControl) + : m_rTableControl(rTableControl) + , m_nCurrentRow(ROW_INVALID) +{ +} + +TableFunctionSet::~TableFunctionSet() {} + +void TableFunctionSet::BeginDrag() {} + +void TableFunctionSet::CreateAnchor() { m_rTableControl.setAnchor(m_rTableControl.getCurRow()); } + +void TableFunctionSet::DestroyAnchor() { m_rTableControl.setAnchor(ROW_INVALID); } + +void TableFunctionSet::SetCursorAtPoint(const Point& rPoint, bool bDontSelectAtCursor) +{ + // newRow is the row which includes the point, getCurRow() is the last selected row, before the mouse click + sal_Int32 newRow = m_rTableControl.getRowAtPoint(rPoint); + if (newRow == ROW_COL_HEADERS) + newRow = m_rTableControl.getTopRow(); + + sal_Int32 newCol = m_rTableControl.getColAtPoint(rPoint); + if (newCol == COL_ROW_HEADERS) + newCol = m_rTableControl.getLeftColumn(); + + if ((newRow == ROW_INVALID) || (newCol == COL_INVALID)) + return; + + if (bDontSelectAtCursor) + { + if (m_rTableControl.GetSelectedRowCount() > 1) + m_rTableControl.getSelEngine()->AddAlways(true); + } + else if (m_rTableControl.getAnchor() == m_rTableControl.getCurRow()) + { + //selected region lies above the last selection + if (m_rTableControl.getCurRow() >= newRow) + { + //put selected rows in vector + while (m_rTableControl.getAnchor() >= newRow) + { + m_rTableControl.markRowAsSelected(m_rTableControl.getAnchor()); + m_rTableControl.setAnchor(m_rTableControl.getAnchor() - 1); + } + m_rTableControl.setAnchor(m_rTableControl.getAnchor() + 1); + } + //selected region lies beneath the last selected row + else + { + while (m_rTableControl.getAnchor() <= newRow) + { + m_rTableControl.markRowAsSelected(m_rTableControl.getAnchor()); + m_rTableControl.setAnchor(m_rTableControl.getAnchor() + 1); + } + m_rTableControl.setAnchor(m_rTableControl.getAnchor() - 1); + } + m_rTableControl.invalidateSelectedRegion(m_rTableControl.getCurRow(), newRow); + } + //no region selected + else + { + if (!m_rTableControl.hasRowSelection()) + m_rTableControl.markRowAsSelected(newRow); + else + { + if (m_rTableControl.getSelEngine()->GetSelectionMode() == SelectionMode::Single) + { + DeselectAll(); + m_rTableControl.markRowAsSelected(newRow); + } + else + { + m_rTableControl.markRowAsSelected(newRow); + } + } + if (m_rTableControl.GetSelectedRowCount() > 1 + && m_rTableControl.getSelEngine()->GetSelectionMode() != SelectionMode::Single) + m_rTableControl.getSelEngine()->AddAlways(true); + + m_rTableControl.invalidateRow(newRow); + } + m_rTableControl.GoToCell(newCol, newRow); +} + +bool TableFunctionSet::IsSelectionAtPoint(const Point& rPoint) +{ + m_rTableControl.getSelEngine()->AddAlways(false); + if (!m_rTableControl.hasRowSelection()) + return false; + else + { + sal_Int32 curRow = m_rTableControl.getRowAtPoint(rPoint); + m_rTableControl.setAnchor(ROW_INVALID); + bool selected = m_rTableControl.IsRowSelected(curRow); + m_nCurrentRow = curRow; + return selected; + } +} + +void TableFunctionSet::DeselectAtPoint(const Point&) +{ + m_rTableControl.invalidateRow(m_nCurrentRow); + m_rTableControl.markRowAsDeselected(m_nCurrentRow); +} + +void TableFunctionSet::DeselectAll() +{ + if (m_rTableControl.hasRowSelection()) + { + for (size_t i = 0; i < m_rTableControl.GetSelectedRowCount(); ++i) + { + sal_Int32 const rowIndex = m_rTableControl.GetSelectedRowIndex(i); + m_rTableControl.invalidateRow(rowIndex); + } + + m_rTableControl.markAllRowsAsDeselected(); + } +} + +} // namespace svt::table + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/toolkit/source/controls/table/defaultinputhandler.cxx b/toolkit/source/controls/table/defaultinputhandler.cxx index ba00f0bb9384..2846be0aafa6 100644 --- a/toolkit/source/controls/table/defaultinputhandler.cxx +++ b/toolkit/source/controls/table/defaultinputhandler.cxx @@ -25,96 +25,88 @@ namespace svt::table { +//= DefaultInputHandler +DefaultInputHandler::DefaultInputHandler() +{ + aMouseFunctions.push_back(new ColumnResize); + aMouseFunctions.push_back(new RowSelection); + aMouseFunctions.push_back(new ColumnSortHandler); +} - //= DefaultInputHandler +DefaultInputHandler::~DefaultInputHandler() {} - - DefaultInputHandler::DefaultInputHandler() +bool DefaultInputHandler::delegateMouseEvent( + TableControl& i_control, const MouseEvent& i_event, + FunctionResult (MouseFunction::*i_handlerMethod)(TableControl&, const MouseEvent&)) +{ + if (pActiveFunction.is()) { - aMouseFunctions.push_back( new ColumnResize ); - aMouseFunctions.push_back( new RowSelection ); - aMouseFunctions.push_back( new ColumnSortHandler ); - } - - - DefaultInputHandler::~DefaultInputHandler() - { - } - - - bool DefaultInputHandler::delegateMouseEvent( ITableControl& i_control, const MouseEvent& i_event, - FunctionResult ( MouseFunction::*i_handlerMethod )( ITableControl&, const MouseEvent& ) ) - { - if ( pActiveFunction.is() ) + bool furtherHandler = false; + switch ((pActiveFunction.get()->*i_handlerMethod)(i_control, i_event)) { - bool furtherHandler = false; - switch ( (pActiveFunction.get()->*i_handlerMethod)( i_control, i_event ) ) - { - case ActivateFunction: - OSL_ENSURE( false, "lcl_delegateMouseEvent: unexpected - function already *is* active!" ); + case FunctionResult::ActivateFunction: + OSL_ENSURE(false, + "lcl_delegateMouseEvent: unexpected - function already *is* active!"); break; - case ContinueFunction: + case FunctionResult::ContinueFunction: break; - case DeactivateFunction: + case FunctionResult::DeactivateFunction: pActiveFunction.clear(); break; - case SkipFunction: + case FunctionResult::SkipFunction: furtherHandler = true; break; - } - if ( !furtherHandler ) - // handled the event - return true; } + if (!furtherHandler) + // handled the event + return true; + } - // ask all other handlers - bool handled = false; - for (auto const& mouseFunction : aMouseFunctions) + // ask all other handlers + bool handled = false; + for (auto const& mouseFunction : aMouseFunctions) + { + if (handled) + break; + if (mouseFunction == pActiveFunction) + // we already invoked this function + continue; + + switch ((mouseFunction.get()->*i_handlerMethod)(i_control, i_event)) { - if (handled) - break; - if (mouseFunction == pActiveFunction) - // we already invoked this function - continue; - - switch ( (mouseFunction.get()->*i_handlerMethod)( i_control, i_event ) ) - { - case ActivateFunction: + case FunctionResult::ActivateFunction: pActiveFunction = mouseFunction; handled = true; break; - case ContinueFunction: - case DeactivateFunction: - OSL_ENSURE( false, "lcl_delegateMouseEvent: unexpected: inactive handler cannot be continued or deactivated!" ); + case FunctionResult::ContinueFunction: + case FunctionResult::DeactivateFunction: + OSL_ENSURE(false, "lcl_delegateMouseEvent: unexpected: inactive handler cannot be " + "continued or deactivated!"); break; - case SkipFunction: + case FunctionResult::SkipFunction: handled = false; break; - } } - return handled; } + return handled; +} +bool DefaultInputHandler::MouseMove(TableControl& i_tableControl, const MouseEvent& i_event) +{ + return delegateMouseEvent(i_tableControl, i_event, &MouseFunction::handleMouseMove); +} - bool DefaultInputHandler::MouseMove( ITableControl& i_tableControl, const MouseEvent& i_event ) - { - return delegateMouseEvent( i_tableControl, i_event, &MouseFunction::handleMouseMove ); - } +bool DefaultInputHandler::MouseButtonDown(TableControl& i_tableControl, const MouseEvent& i_event) +{ + return delegateMouseEvent(i_tableControl, i_event, &MouseFunction::handleMouseDown); +} - - bool DefaultInputHandler::MouseButtonDown( ITableControl& i_tableControl, const MouseEvent& i_event ) - { - return delegateMouseEvent( i_tableControl, i_event, &MouseFunction::handleMouseDown ); - } - - - bool DefaultInputHandler::MouseButtonUp( ITableControl& i_tableControl, const MouseEvent& i_event ) - { - return delegateMouseEvent( i_tableControl, i_event, &MouseFunction::handleMouseUp ); - } +bool DefaultInputHandler::MouseButtonUp(TableControl& i_tableControl, const MouseEvent& i_event) +{ + return delegateMouseEvent(i_tableControl, i_event, &MouseFunction::handleMouseUp); +} } // namespace svt::table - /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/toolkit/source/controls/table/gridtablerenderer.cxx b/toolkit/source/controls/table/gridtablerenderer.cxx index 1456b10501f8..e9f2eeaf10e6 100644 --- a/toolkit/source/controls/table/gridtablerenderer.cxx +++ b/toolkit/source/controls/table/gridtablerenderer.cxx @@ -106,7 +106,7 @@ namespace svt::table struct GridTableRenderer_Impl { ITableModel& rModel; - RowPos nCurrentRow; + sal_Int32 nCurrentRow; bool bUseGridLines; CachedSortIndicator aSortIndicator; CellValueConversion aStringConverter; @@ -144,7 +144,8 @@ namespace svt::table return aTextArea; } - DrawTextFlags lcl_getAlignmentTextDrawFlags( GridTableRenderer_Impl const & i_impl, ColPos const i_columnPos ) + DrawTextFlags lcl_getAlignmentTextDrawFlags(GridTableRenderer_Impl const& i_impl, + sal_Int32 const i_columnPos) { DrawTextFlags nVertFlag = DrawTextFlags::Top; VerticalAlignment const eVertAlign = i_impl.rModel.getVerticalAlign(); @@ -235,11 +236,9 @@ namespace svt::table rRenderContext.DrawLine(_rArea.BottomRight(), _rArea.TopRight()); } - - void GridTableRenderer::PaintColumnHeader( - ColPos _nCol, - vcl::RenderContext& rRenderContext, - const tools::Rectangle& _rArea, const StyleSettings& _rStyle) + void GridTableRenderer::PaintColumnHeader(sal_Int32 _nCol, vcl::RenderContext& rRenderContext, + const tools::Rectangle& _rArea, + const StyleSettings& _rStyle) { auto popIt = rRenderContext.ScopedPush(vcl::PushFlags::LINECOLOR); @@ -293,9 +292,10 @@ namespace svt::table } } - - void GridTableRenderer::PrepareRow(RowPos _nRow, bool i_hasControlFocus, bool _bSelected, vcl::RenderContext& rRenderContext, - const tools::Rectangle& _rRowArea, const StyleSettings& _rStyle) + void GridTableRenderer::PrepareRow(sal_Int32 _nRow, bool i_hasControlFocus, bool _bSelected, + vcl::RenderContext& rRenderContext, + const tools::Rectangle& _rRowArea, + const StyleSettings& _rStyle) { // remember the row for subsequent calls to the other ->ITableRenderer methods m_pImpl->nCurrentRow = _nRow; @@ -386,12 +386,12 @@ namespace svt::table OutputDevice& rDevice; tools::Rectangle const aContentArea; StyleSettings const & rStyle; - ColPos const nColumn; + sal_Int32 const nColumn; bool const bSelected; bool const bHasControlFocus; CellRenderContext( OutputDevice& i_device, tools::Rectangle const & i_contentArea, - StyleSettings const & i_style, ColPos const i_column, bool const i_selected, bool const i_hasControlFocus ) + StyleSettings const & i_style, sal_Int32 const i_column, bool const i_selected, bool const i_hasControlFocus ) :rDevice( i_device ) ,aContentArea( i_contentArea ) ,rStyle( i_style ) @@ -402,9 +402,9 @@ namespace svt::table } }; - - void GridTableRenderer::PaintCell(ColPos const i_column, bool _bSelected, bool i_hasControlFocus, - vcl::RenderContext& rRenderContext, const tools::Rectangle& _rArea, const StyleSettings& _rStyle) + void GridTableRenderer::PaintCell(sal_Int32 const i_column, bool _bSelected, + bool i_hasControlFocus, vcl::RenderContext& rRenderContext, + const tools::Rectangle& _rArea, const StyleSettings& _rStyle) { auto popIt = rRenderContext.ScopedPush(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR); diff --git a/toolkit/source/controls/table/mousefunction.cxx b/toolkit/source/controls/table/mousefunction.cxx index 80d5e8d0fd17..9a412ef7b413 100644 --- a/toolkit/source/controls/table/mousefunction.cxx +++ b/toolkit/source/controls/table/mousefunction.cxx @@ -18,7 +18,7 @@ */ #include -#include +#include #include #include @@ -26,248 +26,236 @@ namespace svt::table { +//= ColumnResize +FunctionResult ColumnResize::handleMouseMove(TableControl& i_tableControl, + MouseEvent const& i_event) +{ + Point const aPoint = i_event.GetPosPixel(); - //= ColumnResize - - - FunctionResult ColumnResize::handleMouseMove( ITableControl& i_tableControl, MouseEvent const & i_event ) + if (m_nResizingColumn == COL_INVALID) { - Point const aPoint = i_event.GetPosPixel(); - - if ( m_nResizingColumn == COL_INVALID ) - { - // if we hit a column divider, change the mouse pointer accordingly - PointerStyle aNewPointer( PointerStyle::Arrow ); - TableCell const tableCell = i_tableControl.hitTest( aPoint ); - if ( ( tableCell.nRow == ROW_COL_HEADERS ) && ( tableCell.eArea == ColumnDivider ) ) - { - aNewPointer = PointerStyle::HSplit; - } - i_tableControl.setPointer( aNewPointer ); - - return SkipFunction; // TODO: is this correct? - } - - ::Size const tableSize = i_tableControl.getTableSizePixel(); - - // set proper pointer - PointerStyle aNewPointer( PointerStyle::Arrow ); - ColumnMetrics const columnMetrics( i_tableControl.getColumnMetrics( m_nResizingColumn ) ); - if ( ( aPoint.X() > tableSize.Width() ) - || ( aPoint.X() < columnMetrics.nStartPixel ) - ) - { - aNewPointer = PointerStyle::NotAllowed; - } - else + // if we hit a column divider, change the mouse pointer accordingly + PointerStyle aNewPointer(PointerStyle::Arrow); + TableCell const tableCell = i_tableControl.hitTest(aPoint); + if ((tableCell.nRow == ROW_COL_HEADERS) && (tableCell.eArea == ColumnDivider)) { aNewPointer = PointerStyle::HSplit; } - i_tableControl.setPointer( aNewPointer ); + i_tableControl.setPointer(aNewPointer); - // show tracking line - i_tableControl.hideTracking(); - i_tableControl.showTracking( - tools::Rectangle( - Point( aPoint.X(), 0 ), - Size( 1, tableSize.Height() ) - ), - ShowTrackFlags::Split | ShowTrackFlags::TrackWindow - ); - - return ContinueFunction; + return FunctionResult::SkipFunction; // TODO: is this correct? } + ::Size const tableSize = i_tableControl.getTableSizePixel(); - FunctionResult ColumnResize::handleMouseDown( ITableControl& i_tableControl, MouseEvent const & i_event ) + // set proper pointer + PointerStyle aNewPointer(PointerStyle::Arrow); + ColumnMetrics const columnMetrics(i_tableControl.getColumnMetrics(m_nResizingColumn)); + if ((aPoint.X() > tableSize.Width()) || (aPoint.X() < columnMetrics.nStartPixel)) { - if ( m_nResizingColumn != COL_INVALID ) - { - OSL_ENSURE( false, "ColumnResize::handleMouseDown: suspicious: MouseButtonDown while still tracking?" ); - return ContinueFunction; - } - - TableCell const tableCell( i_tableControl.hitTest( i_event.GetPosPixel() ) ); - if ( tableCell.nRow == ROW_COL_HEADERS ) - { - if ( ( tableCell.nColumn != COL_INVALID ) - && ( tableCell.eArea == ColumnDivider ) - ) - { - m_nResizingColumn = tableCell.nColumn; - i_tableControl.captureMouse(); - return ActivateFunction; - } - } - - return SkipFunction; + aNewPointer = PointerStyle::NotAllowed; } - - - FunctionResult ColumnResize::handleMouseUp( ITableControl& i_tableControl, MouseEvent const & i_event ) + else { - if ( m_nResizingColumn == COL_INVALID ) - return SkipFunction; - - Point const aPoint = i_event.GetPosPixel(); - - i_tableControl.hideTracking(); - PColumnModel const pColumn = i_tableControl.getModel()->getColumnModel( m_nResizingColumn ); - tools::Long const maxWidthLogical = pColumn->getMaxWidth(); - tools::Long const minWidthLogical = pColumn->getMinWidth(); - - // new position of mouse - tools::Long const requestedEnd = aPoint.X(); - - // old position of right border - tools::Long const oldEnd = i_tableControl.getColumnMetrics( m_nResizingColumn ).nEndPixel; - - // position of left border if cursor in the to-be-resized column - tools::Long const columnStart = i_tableControl.getColumnMetrics( m_nResizingColumn ).nStartPixel; - tools::Long const requestedWidth = requestedEnd - columnStart; - // TODO: this is not correct, strictly: It assumes that the mouse was pressed exactly on the "end" pos, - // but for a while now, we have relaxed this, and allow clicking a few pixels aside, too - - if ( requestedEnd >= columnStart ) - { - tools::Long requestedWidthLogical = i_tableControl.pixelWidthToAppFont( requestedWidth ); - // respect column width limits - if ( oldEnd > requestedEnd ) - { - // column has become smaller, check against minimum width - if ( ( minWidthLogical != 0 ) && ( requestedWidthLogical < minWidthLogical ) ) - requestedWidthLogical = minWidthLogical; - } - else if ( oldEnd < requestedEnd ) - { - // column has become larger, check against max width - if ( ( maxWidthLogical != 0 ) && ( requestedWidthLogical >= maxWidthLogical ) ) - requestedWidthLogical = maxWidthLogical; - } - pColumn->setWidth( requestedWidthLogical ); - i_tableControl.invalidate( TableArea::All ); - } - - i_tableControl.setPointer( PointerStyle::Arrow ); - i_tableControl.releaseMouse(); - - m_nResizingColumn = COL_INVALID; - return DeactivateFunction; + aNewPointer = PointerStyle::HSplit; } + i_tableControl.setPointer(aNewPointer); + // show tracking line + i_tableControl.hideTracking(); + i_tableControl.showTracking(tools::Rectangle(Point(aPoint.X(), 0), Size(1, tableSize.Height())), + ShowTrackFlags::Split | ShowTrackFlags::TrackWindow); - //= RowSelection + return FunctionResult::ContinueFunction; +} - - FunctionResult RowSelection::handleMouseMove( ITableControl&, MouseEvent const & ) +FunctionResult ColumnResize::handleMouseDown(TableControl& i_tableControl, + MouseEvent const& i_event) +{ + if (m_nResizingColumn != COL_INVALID) { - return SkipFunction; + OSL_ENSURE( + false, + "ColumnResize::handleMouseDown: suspicious: MouseButtonDown while still tracking?"); + return FunctionResult::ContinueFunction; } - - FunctionResult RowSelection::handleMouseDown( ITableControl& i_tableControl, MouseEvent const & i_event ) + TableCell const tableCell(i_tableControl.hitTest(i_event.GetPosPixel())); + if (tableCell.nRow == ROW_COL_HEADERS) { - bool handled = false; - - TableCell const tableCell( i_tableControl.hitTest( i_event.GetPosPixel() ) ); - if ( tableCell.nRow >= 0 ) + if ((tableCell.nColumn != COL_INVALID) && (tableCell.eArea == ColumnDivider)) { - if ( i_tableControl.getSelEngine()->GetSelectionMode() == SelectionMode::NONE ) - { - i_tableControl.activateCell( tableCell.nColumn, tableCell.nRow ); - handled = true; - } - else - { - handled = i_tableControl.getSelEngine()->SelMouseButtonDown( i_event ); - } + m_nResizingColumn = tableCell.nColumn; + i_tableControl.captureMouse(); + return FunctionResult::ActivateFunction; } - - if ( handled ) - m_bActive = true; - return handled ? ActivateFunction : SkipFunction; } + return FunctionResult::SkipFunction; +} - FunctionResult RowSelection::handleMouseUp( ITableControl& i_tableControl, MouseEvent const & i_event ) +FunctionResult ColumnResize::handleMouseUp(TableControl& i_tableControl, MouseEvent const& i_event) +{ + if (m_nResizingColumn == COL_INVALID) + return FunctionResult::SkipFunction; + + Point const aPoint = i_event.GetPosPixel(); + + i_tableControl.hideTracking(); + PColumnModel const pColumn = i_tableControl.GetModel()->getColumnModel(m_nResizingColumn); + tools::Long const maxWidthLogical = pColumn->getMaxWidth(); + tools::Long const minWidthLogical = pColumn->getMinWidth(); + + // new position of mouse + tools::Long const requestedEnd = aPoint.X(); + + // old position of right border + tools::Long const oldEnd = i_tableControl.getColumnMetrics(m_nResizingColumn).nEndPixel; + + // position of left border if cursor in the to-be-resized column + tools::Long const columnStart = i_tableControl.getColumnMetrics(m_nResizingColumn).nStartPixel; + tools::Long const requestedWidth = requestedEnd - columnStart; + // TODO: this is not correct, strictly: It assumes that the mouse was pressed exactly on the "end" pos, + // but for a while now, we have relaxed this, and allow clicking a few pixels aside, too + + if (requestedEnd >= columnStart) { - TableCell const tableCell = i_tableControl.hitTest( i_event.GetPosPixel() ); - if ( tableCell.nRow >= 0 ) + tools::Long requestedWidthLogical = i_tableControl.pixelWidthToAppFont(requestedWidth); + // respect column width limits + if (oldEnd > requestedEnd) { - if ( i_tableControl.getSelEngine()->GetSelectionMode() != SelectionMode::NONE ) - { - i_tableControl.getSelEngine()->SelMouseButtonUp( i_event ); - } + // column has become smaller, check against minimum width + if ((minWidthLogical != 0) && (requestedWidthLogical < minWidthLogical)) + requestedWidthLogical = minWidthLogical; } - if ( m_bActive ) + else if (oldEnd < requestedEnd) { - m_bActive = false; - return DeactivateFunction; + // column has become larger, check against max width + if ((maxWidthLogical != 0) && (requestedWidthLogical >= maxWidthLogical)) + requestedWidthLogical = maxWidthLogical; } - return SkipFunction; + pColumn->setWidth(requestedWidthLogical); + i_tableControl.invalidate(TableArea::All); } + i_tableControl.setPointer(PointerStyle::Arrow); + i_tableControl.releaseMouse(); - //= ColumnSortHandler + m_nResizingColumn = COL_INVALID; + return FunctionResult::DeactivateFunction; +} +//= RowSelection - FunctionResult ColumnSortHandler::handleMouseMove( ITableControl&, MouseEvent const & ) +FunctionResult RowSelection::handleMouseMove(TableControl&, MouseEvent const&) +{ + return FunctionResult::SkipFunction; +} + +FunctionResult RowSelection::handleMouseDown(TableControl& i_tableControl, + MouseEvent const& i_event) +{ + bool handled = false; + + TableCell const tableCell(i_tableControl.hitTest(i_event.GetPosPixel())); + if (tableCell.nRow >= 0) { - return SkipFunction; - } - - - FunctionResult ColumnSortHandler::handleMouseDown( ITableControl& i_tableControl, MouseEvent const & i_event ) - { - if ( m_nActiveColumn != COL_INVALID ) + if (i_tableControl.getSelEngine()->GetSelectionMode() == SelectionMode::NONE) { - OSL_ENSURE( false, "ColumnSortHandler::handleMouseDown: called while already active - suspicious!" ); - return ContinueFunction; + i_tableControl.activateCell(tableCell.nColumn, tableCell.nRow); + handled = true; } - - if ( i_tableControl.getModel()->getSortAdapter() == nullptr ) - // no sorting support at the model - return SkipFunction; - - TableCell const tableCell( i_tableControl.hitTest( i_event.GetPosPixel() ) ); - if ( ( tableCell.nRow != ROW_COL_HEADERS ) || ( tableCell.nColumn < 0 ) ) - return SkipFunction; - - // TODO: ensure the column header is rendered in some special way, indicating its current state - - m_nActiveColumn = tableCell.nColumn; - return ActivateFunction; - } - - - FunctionResult ColumnSortHandler::handleMouseUp( ITableControl& i_tableControl, MouseEvent const & i_event ) - { - if ( m_nActiveColumn == COL_INVALID ) - return SkipFunction; - - TableCell const tableCell( i_tableControl.hitTest( i_event.GetPosPixel() ) ); - if ( ( tableCell.nRow == ROW_COL_HEADERS ) && ( tableCell.nColumn == m_nActiveColumn ) ) + else { - ITableDataSort* pSort = i_tableControl.getModel()->getSortAdapter(); - ENSURE_OR_RETURN( pSort != nullptr, "ColumnSortHandler::handleMouseUp: somebody is mocking with us!", DeactivateFunction ); - // in handleMousButtonDown, the model claimed to have sort support ... - - ColumnSortDirection eSortDirection = ColumnSortAscending; - ColumnSort const aCurrentSort = pSort->getCurrentSortOrder(); - if ( aCurrentSort.nColumnPos == m_nActiveColumn ) - // invert existing sort order - eSortDirection = ( aCurrentSort.eSortDirection == ColumnSortAscending ) ? ColumnSortDescending : ColumnSortAscending; - - pSort->sortByColumn( m_nActiveColumn, eSortDirection ); + handled = i_tableControl.getSelEngine()->SelMouseButtonDown(i_event); } - - m_nActiveColumn = COL_INVALID; - return DeactivateFunction; } + if (handled) + m_bActive = true; + return handled ? FunctionResult::ActivateFunction : FunctionResult::SkipFunction; +} + +FunctionResult RowSelection::handleMouseUp(TableControl& i_tableControl, MouseEvent const& i_event) +{ + TableCell const tableCell = i_tableControl.hitTest(i_event.GetPosPixel()); + if (tableCell.nRow >= 0) + { + if (i_tableControl.getSelEngine()->GetSelectionMode() != SelectionMode::NONE) + { + i_tableControl.getSelEngine()->SelMouseButtonUp(i_event); + } + } + if (m_bActive) + { + m_bActive = false; + return FunctionResult::DeactivateFunction; + } + return FunctionResult::SkipFunction; +} + +//= ColumnSortHandler + +FunctionResult ColumnSortHandler::handleMouseMove(TableControl&, MouseEvent const&) +{ + return FunctionResult::SkipFunction; +} + +FunctionResult ColumnSortHandler::handleMouseDown(TableControl& i_tableControl, + MouseEvent const& i_event) +{ + if (m_nActiveColumn != COL_INVALID) + { + OSL_ENSURE(false, + "ColumnSortHandler::handleMouseDown: called while already active - suspicious!"); + return FunctionResult::ContinueFunction; + } + + if (i_tableControl.GetModel()->getSortAdapter() == nullptr) + // no sorting support at the model + return FunctionResult::SkipFunction; + + TableCell const tableCell(i_tableControl.hitTest(i_event.GetPosPixel())); + if ((tableCell.nRow != ROW_COL_HEADERS) || (tableCell.nColumn < 0)) + return FunctionResult::SkipFunction; + + // TODO: ensure the column header is rendered in some special way, indicating its current state + + m_nActiveColumn = tableCell.nColumn; + return FunctionResult::ActivateFunction; +} + +FunctionResult ColumnSortHandler::handleMouseUp(TableControl& i_tableControl, + MouseEvent const& i_event) +{ + if (m_nActiveColumn == COL_INVALID) + return FunctionResult::SkipFunction; + + TableCell const tableCell(i_tableControl.hitTest(i_event.GetPosPixel())); + if ((tableCell.nRow == ROW_COL_HEADERS) && (tableCell.nColumn == m_nActiveColumn)) + { + ITableDataSort* pSort = i_tableControl.GetModel()->getSortAdapter(); + ENSURE_OR_RETURN(pSort != nullptr, + "ColumnSortHandler::handleMouseUp: somebody is mocking with us!", + FunctionResult::DeactivateFunction); + // in handleMousButtonDown, the model claimed to have sort support ... + + ColumnSortDirection eSortDirection = ColumnSortAscending; + ColumnSort const aCurrentSort = pSort->getCurrentSortOrder(); + if (aCurrentSort.nColumnPos == m_nActiveColumn) + // invert existing sort order + eSortDirection = (aCurrentSort.eSortDirection == ColumnSortAscending) + ? ColumnSortDescending + : ColumnSortAscending; + + pSort->sortByColumn(m_nActiveColumn, eSortDirection); + } + + m_nActiveColumn = COL_INVALID; + return FunctionResult::DeactivateFunction; +} } // namespace svt::table - /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/toolkit/source/controls/table/tablecontrol.cxx b/toolkit/source/controls/table/tablecontrol.cxx deleted file mode 100644 index af48df1f3815..000000000000 --- a/toolkit/source/controls/table/tablecontrol.cxx +++ /dev/null @@ -1,521 +0,0 @@ -/* -*- 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 - -#include "tablecontrol_impl.hxx" -#include "tabledatawindow.hxx" - -#include -#include - -#include -#include -#include -#include - -using namespace ::com::sun::star::uno; -using ::com::sun::star::accessibility::XAccessible; -using namespace ::com::sun::star::accessibility; - -namespace svt::table -{ -namespace AccessibleEventId = ::com::sun::star::accessibility::AccessibleEventId; - -//= TableControl - -TableControl::TableControl(vcl::Window* _pParent, WinBits _nStyle) - : Control(_pParent, _nStyle) - , m_pImpl(std::make_shared(*this)) -{ - TableDataWindow& rDataWindow = m_pImpl->getDataWindow(); - rDataWindow.SetSelectHdl(LINK(this, TableControl, ImplSelectHdl)); - - // by default, use the background as determined by the style settings - const Color aWindowColor(GetSettings().GetStyleSettings().GetFieldColor()); - SetBackground(Wallpaper(aWindowColor)); - GetOutDev()->SetFillColor(aWindowColor); - - SetCompoundControl(true); -} - -TableControl::~TableControl() { disposeOnce(); } - -void TableControl::dispose() -{ - CallEventListeners(VclEventId::ObjectDying); - - m_pImpl->setModel(PTableModel()); - m_pImpl->disposeAccessible(); - m_pImpl.reset(); - Control::dispose(); -} - -void TableControl::GetFocus() -{ - if (m_pImpl) - m_pImpl->showCursor(); - - Control::GetFocus(); -} - -void TableControl::LoseFocus() -{ - if (m_pImpl) - m_pImpl->hideCursor(); - - Control::LoseFocus(); -} - -void TableControl::KeyInput(const KeyEvent& rKEvt) -{ - bool bHandled = false; - if (m_pImpl) - { - const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); - sal_uInt16 nKeyCode = rKeyCode.GetCode(); - - struct ActionMapEntry - { - sal_uInt16 nKeyCode; - sal_uInt16 nKeyModifier; - TableControlAction eAction; - } static const aKnownActions[] - = { { KEY_DOWN, 0, TableControlAction::cursorDown }, - { KEY_UP, 0, TableControlAction::cursorUp }, - { KEY_LEFT, 0, TableControlAction::cursorLeft }, - { KEY_RIGHT, 0, TableControlAction::cursorRight }, - { KEY_HOME, 0, TableControlAction::cursorToLineStart }, - { KEY_END, 0, TableControlAction::cursorToLineEnd }, - { KEY_PAGEUP, 0, TableControlAction::cursorPageUp }, - { KEY_PAGEDOWN, 0, TableControlAction::cursorPageDown }, - { KEY_PAGEUP, KEY_MOD1, TableControlAction::cursorToFirstLine }, - { KEY_PAGEDOWN, KEY_MOD1, TableControlAction::cursorToLastLine }, - { KEY_HOME, KEY_MOD1, TableControlAction::cursorTopLeft }, - { KEY_END, KEY_MOD1, TableControlAction::cursorBottomRight }, - { KEY_SPACE, KEY_MOD1, TableControlAction::cursorSelectRow }, - { KEY_UP, KEY_SHIFT, TableControlAction::cursorSelectRowUp }, - { KEY_DOWN, KEY_SHIFT, TableControlAction::cursorSelectRowDown }, - { KEY_END, KEY_SHIFT, TableControlAction::cursorSelectRowAreaBottom }, - { KEY_HOME, KEY_SHIFT, TableControlAction::cursorSelectRowAreaTop } }; - for (const ActionMapEntry& rAction : aKnownActions) - { - if ((rAction.nKeyCode == nKeyCode) && (rAction.nKeyModifier == rKeyCode.GetModifier())) - { - bHandled = m_pImpl->dispatchAction(rAction.eAction); - break; - } - } - } - - if (!bHandled) - Control::KeyInput(rKEvt); - else - { - m_pImpl->commitCellEvent(AccessibleEventId::STATE_CHANGED, - Any(AccessibleStateType::FOCUSED), Any()); - // Huh? What the heck? Why do we unconditionally notify a STATE_CHANGE/FOCUSED after each and every - // (handled) key stroke? - - m_pImpl->commitTableEvent(AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, Any(), Any()); - // ditto: Why do we notify this unconditionally? We should find the right place to notify the - // ACTIVE_DESCENDANT_CHANGED event. - // Also, we should check if STATE_CHANGED/FOCUSED is really necessary: finally, the children are - // transient, aren't they? - } -} - -void TableControl::StateChanged(StateChangedType i_nStateChange) -{ - Control::StateChanged(i_nStateChange); - - // forward certain settings to the data window - switch (i_nStateChange) - { - case StateChangedType::ControlFocus: - m_pImpl->invalidateSelectedRows(); - break; - - case StateChangedType::ControlBackground: - if (IsControlBackground()) - getDataWindow().SetControlBackground(GetControlBackground()); - else - getDataWindow().SetControlBackground(); - break; - - case StateChangedType::ControlForeground: - if (IsControlForeground()) - getDataWindow().SetControlForeground(GetControlForeground()); - else - getDataWindow().SetControlForeground(); - break; - - case StateChangedType::ControlFont: - if (IsControlFont()) - getDataWindow().SetControlFont(GetControlFont()); - else - getDataWindow().SetControlFont(); - break; - default:; - } -} - -void TableControl::Resize() -{ - Control::Resize(); - m_pImpl->onResize(); -} - -void TableControl::SetModel(const PTableModel& _pModel) { m_pImpl->setModel(_pModel); } - -PTableModel TableControl::GetModel() const { return m_pImpl->getModel(); } - -sal_Int32 TableControl::GetCurrentRow() const { return m_pImpl->getCurrentRow(); } - -sal_Int32 TableControl::GetCurrentColumn() const { return m_pImpl->getCurrentColumn(); } - -void TableControl::GoTo(ColPos _nColumn, RowPos _nRow) { m_pImpl->goTo(_nColumn, _nRow); } - -void TableControl::GoToCell(sal_Int32 _nColPos, sal_Int32 _nRowPos) -{ - m_pImpl->goTo(_nColPos, _nRowPos); -} - -sal_Int32 TableControl::GetSelectedRowCount() const -{ - return sal_Int32(m_pImpl->getSelectedRowCount()); -} - -sal_Int32 TableControl::GetSelectedRowIndex(sal_Int32 const i_selectionIndex) const -{ - return m_pImpl->getSelectedRowIndex(i_selectionIndex); -} - -bool TableControl::IsRowSelected(sal_Int32 const i_rowIndex) const -{ - return m_pImpl->isRowSelected(i_rowIndex); -} - -void TableControl::SelectRow(sal_Int32 const i_rowIndex, bool const i_select) -{ - ENSURE_OR_RETURN_VOID((i_rowIndex >= 0) && (i_rowIndex < m_pImpl->getModel()->getRowCount()), - "TableControl::SelectRow: invalid row index!"); - - if (i_select) - { - if (!m_pImpl->markRowAsSelected(i_rowIndex)) - // nothing to do - return; - } - else - { - m_pImpl->markRowAsDeselected(i_rowIndex); - } - - m_pImpl->invalidateRowRange(i_rowIndex, i_rowIndex); - Select(); -} - -void TableControl::SelectAllRows(bool const i_select) -{ - if (i_select) - { - if (!m_pImpl->markAllRowsAsSelected()) - // nothing to do - return; - } - else - { - if (!m_pImpl->markAllRowsAsDeselected()) - // nothing to do - return; - } - - Invalidate(); - // TODO: can't we do better than this, and invalidate only the rows which changed? - Select(); -} - -SelectionEngine* TableControl::getSelEngine() { return m_pImpl->getSelEngine(); } - -vcl::Window& TableControl::getDataWindow() { return m_pImpl->getDataWindow(); } - -rtl::Reference TableControl::CreateAccessible() -{ - rtl::Reference pParent = GetAccessibleParent(); - return m_pImpl->getAccessible(pParent); -} - -OUString TableControl::GetAccessibleObjectName(AccessibleTableControlObjType eObjType, - sal_Int32 _nRow, sal_Int32 _nCol) const -{ - OUString aRetText; - //Window* pWin; - switch (eObjType) - { - case AccessibleTableControlObjType::GRIDCONTROL: - aRetText = "Grid control"; - break; - case AccessibleTableControlObjType::TABLE: - aRetText = "Grid control"; - break; - case AccessibleTableControlObjType::ROWHEADERBAR: - aRetText = "RowHeaderBar"; - break; - case AccessibleTableControlObjType::COLUMNHEADERBAR: - aRetText = "ColumnHeaderBar"; - break; - case AccessibleTableControlObjType::TABLECELL: - //the name of the cell consists of column name and row name if defined - //if the name is equal to cell content, it'll be read twice - if (GetModel()->hasColumnHeaders()) - { - aRetText = GetColumnName(_nCol) + " , "; - } - if (GetModel()->hasRowHeaders()) - { - aRetText += GetRowName(_nRow) + " , "; - } - //aRetText = GetAccessibleCellText(_nRow, _nCol); - break; - case AccessibleTableControlObjType::ROWHEADERCELL: - aRetText = GetRowName(_nRow); - break; - case AccessibleTableControlObjType::COLUMNHEADERCELL: - aRetText = GetColumnName(_nCol); - break; - default: - OSL_FAIL("GridControl::GetAccessibleName: invalid enum!"); - } - return aRetText; -} - -OUString TableControl::GetAccessibleObjectDescription(AccessibleTableControlObjType eObjType) const -{ - OUString aRetText; - switch (eObjType) - { - case AccessibleTableControlObjType::GRIDCONTROL: - aRetText = "Grid control description"; - break; - case AccessibleTableControlObjType::TABLE: - aRetText = "TABLE description"; - break; - case AccessibleTableControlObjType::ROWHEADERBAR: - aRetText = "ROWHEADERBAR description"; - break; - case AccessibleTableControlObjType::COLUMNHEADERBAR: - aRetText = "COLUMNHEADERBAR description"; - break; - case AccessibleTableControlObjType::TABLECELL: - // the description of the cell consists of column name and row name if defined - // if the name is equal to cell content, it'll be read twice - if (GetModel()->hasColumnHeaders()) - { - aRetText = GetColumnName(GetCurrentColumn()) + " , "; - } - if (GetModel()->hasRowHeaders()) - { - aRetText += GetRowName(GetCurrentRow()); - } - break; - case AccessibleTableControlObjType::ROWHEADERCELL: - aRetText = "ROWHEADERCELL description"; - break; - case AccessibleTableControlObjType::COLUMNHEADERCELL: - aRetText = "COLUMNHEADERCELL description"; - break; - } - return aRetText; -} - -OUString TableControl::GetRowName(sal_Int32 _nIndex) const -{ - OUString sRowName; - GetModel()->getRowHeading(_nIndex) >>= sRowName; - return sRowName; -} - -OUString TableControl::GetColumnName(sal_Int32 _nIndex) const -{ - return GetModel()->getColumnModel(_nIndex)->getName(); -} - -OUString TableControl::GetAccessibleCellText(sal_Int32 _nRowPos, sal_Int32 _nColPos) const -{ - return m_pImpl->getCellContentAsString(_nRowPos, _nColPos); -} - -void TableControl::FillAccessibleStateSet(sal_Int64& rStateSet, - AccessibleTableControlObjType eObjType) const -{ - switch (eObjType) - { - case AccessibleTableControlObjType::GRIDCONTROL: - case AccessibleTableControlObjType::TABLE: - - rStateSet |= AccessibleStateType::FOCUSABLE; - - if (m_pImpl->getSelEngine()->GetSelectionMode() == SelectionMode::Multiple) - rStateSet |= AccessibleStateType::MULTI_SELECTABLE; - - if (HasChildPathFocus()) - rStateSet |= AccessibleStateType::FOCUSED; - - if (IsActive()) - rStateSet |= AccessibleStateType::ACTIVE; - - if (m_pImpl->getDataWindow().IsEnabled()) - { - rStateSet |= AccessibleStateType::ENABLED; - rStateSet |= AccessibleStateType::SENSITIVE; - } - - if (IsReallyVisible()) - rStateSet |= AccessibleStateType::VISIBLE; - - if (eObjType == AccessibleTableControlObjType::TABLE) - rStateSet |= AccessibleStateType::MANAGES_DESCENDANTS; - break; - - case AccessibleTableControlObjType::COLUMNHEADERBAR: - case AccessibleTableControlObjType::ROWHEADERBAR: - rStateSet |= AccessibleStateType::VISIBLE; - rStateSet |= AccessibleStateType::MANAGES_DESCENDANTS; - break; - - case AccessibleTableControlObjType::TABLECELL: - { - rStateSet |= AccessibleStateType::FOCUSABLE; - if (HasChildPathFocus()) - rStateSet |= AccessibleStateType::FOCUSED; - rStateSet |= AccessibleStateType::ACTIVE; - rStateSet |= AccessibleStateType::TRANSIENT; - rStateSet |= AccessibleStateType::SELECTABLE; - rStateSet |= AccessibleStateType::VISIBLE; - rStateSet |= AccessibleStateType::SHOWING; - if (IsRowSelected(GetCurrentRow())) - // Hmm? Wouldn't we expect the affected row to be a parameter to this function? - rStateSet |= AccessibleStateType::SELECTED; - } - break; - - case AccessibleTableControlObjType::ROWHEADERCELL: - rStateSet |= AccessibleStateType::VISIBLE; - rStateSet |= AccessibleStateType::TRANSIENT; - break; - - case AccessibleTableControlObjType::COLUMNHEADERCELL: - rStateSet |= AccessibleStateType::VISIBLE; - break; - } -} - -void TableControl::commitCellEvent(sal_Int16 const i_eventID, const Any& i_newValue, - const Any& i_oldValue) -{ - m_pImpl->commitCellEvent(i_eventID, i_newValue, i_oldValue); -} - -void TableControl::commitTableEvent(sal_Int16 const i_eventID, const Any& i_newValue, - const Any& i_oldValue) -{ - m_pImpl->commitTableEvent(i_eventID, i_newValue, i_oldValue); -} - -bool TableControl::HasRowHeader() { return GetModel()->hasRowHeaders(); } - -bool TableControl::HasColHeader() { return GetModel()->hasColumnHeaders(); } - -sal_Int32 TableControl::GetAccessibleControlCount() const -{ - // TC_TABLE is always defined, no matter whether empty or not - sal_Int32 count = 1; - if (GetModel()->hasRowHeaders()) - ++count; - if (GetModel()->hasColumnHeaders()) - ++count; - return count; -} - -sal_Int32 TableControl::GetRowCount() const { return GetModel()->getRowCount(); } - -sal_Int32 TableControl::GetColumnCount() const { return GetModel()->getColumnCount(); } - -bool TableControl::ConvertPointToCellAddress(sal_Int32& _rnRow, sal_Int32& _rnColPos, - const Point& _rPoint) -{ - _rnRow = m_pImpl->getRowAtPoint(_rPoint); - _rnColPos = m_pImpl->getColAtPoint(_rPoint); - return _rnRow >= 0; -} - -void TableControl::FillAccessibleStateSetForCell(sal_Int64& _rStateSet, sal_Int32 _nRow, - sal_uInt16) const -{ - if (IsRowSelected(_nRow)) - _rStateSet |= AccessibleStateType::SELECTED; - if (HasChildPathFocus()) - _rStateSet |= AccessibleStateType::FOCUSED; - else // only transient when column is not focused - _rStateSet |= AccessibleStateType::TRANSIENT; - - _rStateSet |= AccessibleStateType::VISIBLE; - _rStateSet |= AccessibleStateType::SHOWING; - _rStateSet |= AccessibleStateType::ENABLED; - _rStateSet |= AccessibleStateType::SENSITIVE; - _rStateSet |= AccessibleStateType::ACTIVE; -} - -tools::Rectangle TableControl::calcHeaderRect(bool _bIsColumnBar) -{ - return m_pImpl->calcHeaderRect(_bIsColumnBar); -} - -tools::Rectangle TableControl::calcHeaderCellRect(bool _bIsColumnBar, sal_Int32 nPos) -{ - return m_pImpl->calcHeaderCellRect(_bIsColumnBar, nPos); -} - -tools::Rectangle TableControl::calcTableRect() { return m_pImpl->calcTableRect(); } - -tools::Rectangle TableControl::calcCellRect(sal_Int32 _nRowPos, sal_Int32 _nColPos) -{ - return m_pImpl->calcCellRect(_nRowPos, _nColPos); -} - -IMPL_LINK_NOARG(TableControl, ImplSelectHdl, LinkParamNone*, void) { Select(); } - -void TableControl::Select() -{ - ImplCallEventListenersAndHandler(VclEventId::TableRowSelect, nullptr); - m_pImpl->commitAccessibleEvent(AccessibleEventId::SELECTION_CHANGED); - - m_pImpl->commitTableEvent(AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, Any(), Any()); - // TODO: why do we notify this when the *selection* changed? Shouldn't we find a better place for this, - // actually, when the active descendant, i.e. the current cell, *really* changed? -} - -TableCell TableControl::hitTest(const Point& rPoint) const { return m_pImpl->hitTest(rPoint); } - -void TableControl::invalidate(const TableArea aArea) { return m_pImpl->invalidate(aArea); } - -} // namespace svt::table - -/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/toolkit/source/controls/table/tabledatawindow.cxx b/toolkit/source/controls/table/tabledatawindow.cxx index fc49ee08d621..4b6eddc8d18e 100644 --- a/toolkit/source/controls/table/tabledatawindow.cxx +++ b/toolkit/source/controls/table/tabledatawindow.cxx @@ -17,10 +17,9 @@ * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ -#include +#include #include "tabledatawindow.hxx" -#include "tablecontrol_impl.hxx" #include "tablegeometry.hxx" #include @@ -32,9 +31,9 @@ namespace svt::table { using css::uno::Any; - TableDataWindow::TableDataWindow( TableControl_Impl& _rTableControl ) - :Window( &_rTableControl.getAntiImpl() ) - ,m_rTableControl( _rTableControl ) + TableDataWindow::TableDataWindow(TableControl& _rTableControl) + : Window(&_rTableControl) + , m_rTableControl(_rTableControl) { // by default, use the background as determined by the style settings const Color aWindowColor( GetSettings().GetStyleSettings().GetFieldColor() ); @@ -73,10 +72,10 @@ namespace svt::table QuickHelpFlags nHelpStyle = QuickHelpFlags::NONE; Point const aMousePos( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) ); - RowPos const hitRow = m_rTableControl.getRowAtPoint( aMousePos ); - ColPos const hitCol = m_rTableControl.getColAtPoint( aMousePos ); + sal_Int32 const hitRow = m_rTableControl.getRowAtPoint(aMousePos); + sal_Int32 const hitCol = m_rTableControl.getColAtPoint(aMousePos); - PTableModel const pTableModel( m_rTableControl.getModel() ); + PTableModel const pTableModel(m_rTableControl.GetModel()); if ( ( hitCol >= 0 ) && ( hitCol < pTableModel->getColumnCount() ) ) { if ( hitRow == ROW_COL_HEADERS ) @@ -149,9 +148,9 @@ namespace svt::table impl_hideTipWindow(); Point const aPoint = rMEvt.GetPosPixel(); - RowPos const hitRow = m_rTableControl.getRowAtPoint( aPoint ); - bool const wasRowSelected = m_rTableControl.isRowSelected( hitRow ); - size_t const nPrevSelRowCount = m_rTableControl.getSelectedRowCount(); + sal_Int32 const hitRow = m_rTableControl.getRowAtPoint(aPoint); + bool const wasRowSelected = m_rTableControl.IsRowSelected(hitRow); + size_t const nPrevSelRowCount = m_rTableControl.GetSelectedRowCount(); if ( !m_rTableControl.getInputHandler()->MouseButtonDown( m_rTableControl, rMEvt ) ) { @@ -159,8 +158,8 @@ namespace svt::table return; } - bool const isRowSelected = m_rTableControl.isRowSelected( hitRow ); - size_t const nCurSelRowCount = m_rTableControl.getSelectedRowCount(); + bool const isRowSelected = m_rTableControl.IsRowSelected(hitRow); + size_t const nCurSelRowCount = m_rTableControl.GetSelectedRowCount(); if ( isRowSelected != wasRowSelected || nCurSelRowCount != nPrevSelRowCount ) { m_aSelectHdl.Call( nullptr ); @@ -173,7 +172,7 @@ namespace svt::table if ( !m_rTableControl.getInputHandler()->MouseButtonUp( m_rTableControl, rMEvt ) ) Window::MouseButtonUp( rMEvt ); - m_rTableControl.getAntiImpl().GrabFocus(); + m_rTableControl.GrabFocus(); } diff --git a/toolkit/source/controls/table/tabledatawindow.hxx b/toolkit/source/controls/table/tabledatawindow.hxx index a62d7292f2b0..53a3ff40057a 100644 --- a/toolkit/source/controls/table/tabledatawindow.hxx +++ b/toolkit/source/controls/table/tabledatawindow.hxx @@ -24,7 +24,7 @@ namespace svt::table { - class TableControl_Impl; + class TableControl; /** the window containing the content area (including headers) of a table control @@ -32,11 +32,11 @@ namespace svt::table class TableDataWindow : public vcl::Window { private: - TableControl_Impl& m_rTableControl; + TableControl& m_rTableControl; Link m_aSelectHdl; public: - explicit TableDataWindow( TableControl_Impl& _rTableControl ); + explicit TableDataWindow( TableControl& _rTableControl ); virtual ~TableDataWindow() override; virtual void dispose() override; diff --git a/toolkit/source/controls/table/tablegeometry.cxx b/toolkit/source/controls/table/tablegeometry.cxx index 5b18826c50f7..f065836d1ae2 100644 --- a/toolkit/source/controls/table/tablegeometry.cxx +++ b/toolkit/source/controls/table/tablegeometry.cxx @@ -17,10 +17,9 @@ * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ - #include "tablegeometry.hxx" -#include "tablecontrol_impl.hxx" +#include namespace svt::table { @@ -29,8 +28,8 @@ namespace svt::table //= TableRowGeometry - TableRowGeometry::TableRowGeometry( TableControl_Impl const & _rControl, tools::Rectangle const & _rBoundaries, - RowPos const _nRow, bool const i_allowVirtualRows ) + TableRowGeometry::TableRowGeometry( TableControl const & _rControl, tools::Rectangle const & _rBoundaries, + sal_Int32 const _nRow, bool const i_allowVirtualRows ) :TableGeometry( _rControl, _rBoundaries ) ,m_nRowPos( _nRow ) ,m_bAllowVirtualRows( i_allowVirtualRows ) @@ -58,8 +57,7 @@ namespace svt::table m_aRect.SetEmpty(); } - - bool TableRowGeometry::impl_isValidRow( RowPos const i_row ) const + bool TableRowGeometry::impl_isValidRow(sal_Int32 const i_row) const { return m_bAllowVirtualRows || ( i_row < m_rControl.m_pModel->getRowCount() ); } @@ -86,8 +84,8 @@ namespace svt::table //= TableColumnGeometry - TableColumnGeometry::TableColumnGeometry( TableControl_Impl const & _rControl, tools::Rectangle const & _rBoundaries, - ColPos const _nCol ) + TableColumnGeometry::TableColumnGeometry( TableControl const & _rControl, tools::Rectangle const & _rBoundaries, + sal_Int32 const _nCol ) :TableGeometry( _rControl, _rBoundaries ) ,m_nColPos( _nCol ) { @@ -105,13 +103,13 @@ namespace svt::table void TableColumnGeometry::impl_initRect() { - ColPos nLeftColumn = m_rControl.m_nLeftColumn; + sal_Int32 nLeftColumn = m_rControl.m_nLeftColumn; if ( ( m_nColPos >= nLeftColumn ) && impl_isValidColumn( m_nColPos ) ) { m_aRect.SetLeft( m_rControl.m_nRowHeaderWidthPixel ); // TODO: take into account any possibly frozen columns - for ( ColPos col = nLeftColumn; col < m_nColPos; ++col ) + for (sal_Int32 col = nLeftColumn; col < m_nColPos; ++col) m_aRect.AdjustLeft(m_rControl.m_aColumnWidths[ col ].getWidth() ); m_aRect.SetRight( m_aRect.Left() + m_rControl.m_aColumnWidths[ m_nColPos ].getWidth() - 1 ); } @@ -119,10 +117,9 @@ namespace svt::table m_aRect.SetEmpty(); } - - bool TableColumnGeometry::impl_isValidColumn( ColPos const i_column ) const + bool TableColumnGeometry::impl_isValidColumn(sal_Int32 const i_column) const { - return i_column < ColPos( m_rControl.m_aColumnWidths.size() ); + return i_column < sal_Int32(m_rControl.m_aColumnWidths.size()); } diff --git a/toolkit/source/controls/table/tablegeometry.hxx b/toolkit/source/controls/table/tablegeometry.hxx index dde156ffd253..b5bef5f18a97 100644 --- a/toolkit/source/controls/table/tablegeometry.hxx +++ b/toolkit/source/controls/table/tablegeometry.hxx @@ -26,7 +26,7 @@ namespace svt::table { - class TableControl_Impl; + class TableControl; //= TableGeometry @@ -34,13 +34,13 @@ namespace svt::table class TableGeometry { protected: - const TableControl_Impl& m_rControl; + const TableControl& m_rControl; const tools::Rectangle& m_rBoundaries; tools::Rectangle m_aRect; protected: TableGeometry( - const TableControl_Impl& _rControl, + const TableControl& _rControl, const tools::Rectangle& _rBoundaries ) :m_rControl( _rControl ) @@ -51,7 +51,7 @@ namespace svt::table public: // attribute access - const TableControl_Impl& getControl() const { return m_rControl; } + const TableControl& getControl() const { return m_rControl; } // status const tools::Rectangle& getRect() const { return m_aRect; } @@ -65,23 +65,23 @@ namespace svt::table { public: TableRowGeometry( - TableControl_Impl const & _rControl, + TableControl const & _rControl, tools::Rectangle const & _rBoundaries, - RowPos const _nRow, + sal_Int32 const _nRow, bool const i_allowVirtualRows = false // allow rows >= getRowCount()? ); // status - RowPos getRow() const { return m_nRowPos; } + sal_Int32 getRow() const { return m_nRowPos; } // operations bool moveDown(); private: void impl_initRect(); - bool impl_isValidRow( RowPos const i_row ) const; + bool impl_isValidRow(sal_Int32 const i_row) const; - RowPos m_nRowPos; + sal_Int32 m_nRowPos; bool m_bAllowVirtualRows; }; @@ -91,22 +91,19 @@ namespace svt::table class TableColumnGeometry final : public TableGeometry { public: - TableColumnGeometry( - TableControl_Impl const & _rControl, - tools::Rectangle const & _rBoundaries, - ColPos const _nCol - ); + TableColumnGeometry(TableControl const& _rControl, + tools::Rectangle const& _rBoundaries, sal_Int32 const _nCol); // status - ColPos getCol() const { return m_nColPos; } + sal_Int32 getCol() const { return m_nColPos; } // operations bool moveRight(); private: void impl_initRect(); - bool impl_isValidColumn( ColPos const i_column ) const; + bool impl_isValidColumn(sal_Int32 const i_column) const; - ColPos m_nColPos; + sal_Int32 m_nColPos; }; @@ -122,10 +119,10 @@ namespace svt::table public: TableCellGeometry( - TableControl_Impl const & _rControl, + TableControl const & _rControl, tools::Rectangle const & _rBoundaries, - ColPos const _nCol, - RowPos const _nRow + sal_Int32 const _nCol, + sal_Int32 const _nRow ) :m_aRow( _rControl, _rBoundaries, _nRow, false/*allowVirtualCells*/ ) ,m_aCol( _rControl, _rBoundaries, _nCol ) @@ -134,7 +131,7 @@ namespace svt::table TableCellGeometry( const TableRowGeometry& _rRow, - ColPos _nCol + sal_Int32 _nCol ) :m_aRow( _rRow ) ,m_aCol( _rRow.getControl(), _rRow.getRect(), _nCol ) @@ -142,7 +139,7 @@ namespace svt::table } tools::Rectangle getRect() const { return m_aRow.getRect().GetIntersection( m_aCol.getRect() ); } - ColPos getColumn() const { return m_aCol.getCol(); } + sal_Int32 getColumn() const { return m_aCol.getCol(); } bool isValid() const { return !getRect().IsEmpty(); } bool moveRight() {return m_aCol.moveRight(); } diff --git a/toolkit/source/controls/unocontroltablemodel.cxx b/toolkit/source/controls/unocontroltablemodel.cxx index ff83158dc757..0f2afc948544 100644 --- a/toolkit/source/controls/unocontroltablemodel.cxx +++ b/toolkit/source/controls/unocontroltablemodel.cxx @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -31,800 +32,739 @@ #include #include - namespace svt::table { +using css::uno::Reference; +using css::uno::Sequence; +using css::uno::UNO_QUERY_THROW; +using css::uno::UNO_QUERY; +using css::awt::grid::XGridColumn; +using css::uno::Exception; +using css::awt::grid::XGridDataModel; +using css::awt::grid::XGridColumnModel; +using css::uno::Any; +using css::style::VerticalAlignment_TOP; +using css::style::VerticalAlignment; +using css::awt::grid::GridDataEvent; +using css::awt::grid::XSortableGridData; +using css::beans::Pair; +//= UnoControlTableModel +UnoControlTableModel::UnoControlTableModel() + : aColumns() + , bHasColumnHeaders(true) + , bHasRowHeaders(false) + , eVScrollMode(ScrollbarShowNever) + , eHScrollMode(ScrollbarShowNever) + , pRenderer() + , pInputHandler() + , nRowHeight(10) + , nColumnHeaderHeight(10) + , nRowHeaderWidth(10) + , m_aGridLineColor() + , m_aHeaderBackgroundColor() + , m_aHeaderTextColor() + , m_aActiveSelectionBackColor() + , m_aInactiveSelectionBackColor() + , m_aActiveSelectionTextColor() + , m_aInactiveSelectionTextColor() + , m_aTextColor() + , m_aTextLineColor() + , m_aRowColors() + , m_eVerticalAlign(VerticalAlignment_TOP) + , bEnabled(true) +{ + pRenderer = std::make_shared(*this); + pInputHandler = std::make_shared(); +} - using css::uno::Reference; - using css::uno::Sequence; - using css::uno::UNO_QUERY_THROW; - using css::uno::UNO_QUERY; - using css::awt::grid::XGridColumn; - using css::uno::Exception; - using css::awt::grid::XGridDataModel; - using css::awt::grid::XGridColumnModel; - using css::uno::Any; - using css::style::VerticalAlignment_TOP; - using css::style::VerticalAlignment; - using css::awt::grid::GridDataEvent; - using css::awt::grid::XSortableGridData; - using css::beans::Pair; +UnoControlTableModel::~UnoControlTableModel() {} +TableSize UnoControlTableModel::getColumnCount() const +{ + DBG_TESTSOLARMUTEX(); + return static_cast(aColumns.size()); +} - //= UnoControlTableModel - UnoControlTableModel::UnoControlTableModel() - :aColumns ( ) - ,bHasColumnHeaders ( true ) - ,bHasRowHeaders ( false ) - ,eVScrollMode ( ScrollbarShowNever ) - ,eHScrollMode ( ScrollbarShowNever ) - ,pRenderer ( ) - ,pInputHandler ( ) - ,nRowHeight ( 10 ) - ,nColumnHeaderHeight ( 10 ) - ,nRowHeaderWidth ( 10 ) - ,m_aGridLineColor ( ) - ,m_aHeaderBackgroundColor ( ) - ,m_aHeaderTextColor ( ) - ,m_aActiveSelectionBackColor ( ) - ,m_aInactiveSelectionBackColor ( ) - ,m_aActiveSelectionTextColor ( ) - ,m_aInactiveSelectionTextColor ( ) - ,m_aTextColor ( ) - ,m_aTextLineColor ( ) - ,m_aRowColors ( ) - ,m_eVerticalAlign ( VerticalAlignment_TOP ) - ,bEnabled ( true ) +TableSize UnoControlTableModel::getRowCount() const +{ + DBG_TESTSOLARMUTEX(); + + TableSize nRowCount = 0; + try { - pRenderer = std::make_shared( *this ); - pInputHandler = std::make_shared(); + Reference const xDataModel(m_aDataModel); + ENSURE_OR_THROW(xDataModel.is(), "no data model anymore!"); + nRowCount = xDataModel->getRowCount(); + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + return nRowCount; +} + +bool UnoControlTableModel::hasColumnHeaders() const +{ + DBG_TESTSOLARMUTEX(); + return bHasColumnHeaders; +} + +bool UnoControlTableModel::hasRowHeaders() const +{ + DBG_TESTSOLARMUTEX(); + return bHasRowHeaders; +} + +void UnoControlTableModel::setRowHeaders(bool _bRowHeaders) +{ + DBG_TESTSOLARMUTEX(); + if (bHasRowHeaders == _bRowHeaders) + return; + + bHasRowHeaders = _bRowHeaders; + impl_notifyTableMetricsChanged(); +} + +void UnoControlTableModel::setColumnHeaders(bool _bColumnHeaders) +{ + DBG_TESTSOLARMUTEX(); + if (bHasColumnHeaders == _bColumnHeaders) + return; + + bHasColumnHeaders = _bColumnHeaders; + impl_notifyTableMetricsChanged(); +} + +PColumnModel UnoControlTableModel::getColumnModel(sal_Int32 column) +{ + DBG_TESTSOLARMUTEX(); + ENSURE_OR_RETURN((column >= 0) && (column < getColumnCount()), + "DefaultTableModel::getColumnModel: invalid index!", PColumnModel()); + return aColumns[column]; +} + +void UnoControlTableModel::appendColumn(Reference const& i_column) +{ + DBG_TESTSOLARMUTEX(); + insertColumn(aColumns.size(), i_column); +} + +void UnoControlTableModel::insertColumn(sal_Int32 const i_position, + Reference const& i_column) +{ + DBG_TESTSOLARMUTEX(); + ENSURE_OR_RETURN_VOID((i_position >= 0) && (o3tl::make_unsigned(i_position) <= aColumns.size()), + "UnoControlTableModel::insertColumn: illegal position!"); + + const PColumnModel pColumn = std::make_shared(*this, i_column); + aColumns.insert(aColumns.begin() + i_position, pColumn); + + // notify listeners + ModellListeners aListeners(m_aListeners); + for (auto const& listener : aListeners) + { + listener->columnInserted(); + } +} + +void UnoControlTableModel::removeColumn(sal_Int32 const i_position) +{ + DBG_TESTSOLARMUTEX(); + ENSURE_OR_RETURN_VOID((i_position >= 0) && (o3tl::make_unsigned(i_position) <= aColumns.size()), + "UnoControlTableModel::removeColumn: illegal position!"); + + // remove the column + ColumnModels::iterator pos = aColumns.begin() + i_position; + const PColumnModel pColumn = *pos; + aColumns.erase(pos); + + // notify listeners + ModellListeners aListeners(m_aListeners); + for (auto const& listener : aListeners) + { + listener->columnRemoved(); } + // dispose the column + UnoGridColumnFacade* pColumnImpl = dynamic_cast(pColumn.get()); + OSL_ENSURE(pColumnImpl != nullptr, + "UnoControlTableModel::removeColumn: illegal column implementation!"); + if (pColumnImpl) + pColumnImpl->dispose(); +} - UnoControlTableModel::~UnoControlTableModel() +void UnoControlTableModel::removeAllColumns() +{ + DBG_TESTSOLARMUTEX(); + if (aColumns.empty()) + return; + + // dispose the column instances + for (auto const& col : aColumns) { - } - - - TableSize UnoControlTableModel::getColumnCount() const - { - DBG_TESTSOLARMUTEX(); - return static_cast(aColumns.size()); - } - - - TableSize UnoControlTableModel::getRowCount() const - { - DBG_TESTSOLARMUTEX(); - - TableSize nRowCount = 0; - try + UnoGridColumnFacade* pColumn = dynamic_cast(col.get()); + if (!pColumn) { - Reference< XGridDataModel > const xDataModel( m_aDataModel ); - ENSURE_OR_THROW( xDataModel.is(), "no data model anymore!" ); - nRowCount = xDataModel->getRowCount(); - } - catch( const Exception& ) - { - DBG_UNHANDLED_EXCEPTION("svtools.uno"); - } - return nRowCount; - } - - - bool UnoControlTableModel::hasColumnHeaders() const - { - DBG_TESTSOLARMUTEX(); - return bHasColumnHeaders; - } - - - bool UnoControlTableModel::hasRowHeaders() const - { - DBG_TESTSOLARMUTEX(); - return bHasRowHeaders; - } - - - void UnoControlTableModel::setRowHeaders(bool _bRowHeaders) - { - DBG_TESTSOLARMUTEX(); - if ( bHasRowHeaders == _bRowHeaders ) - return; - - bHasRowHeaders = _bRowHeaders; - impl_notifyTableMetricsChanged(); - } - - - void UnoControlTableModel::setColumnHeaders(bool _bColumnHeaders) - { - DBG_TESTSOLARMUTEX(); - if ( bHasColumnHeaders == _bColumnHeaders ) - return; - - bHasColumnHeaders = _bColumnHeaders; - impl_notifyTableMetricsChanged(); - } - - - PColumnModel UnoControlTableModel::getColumnModel( ColPos column ) - { - DBG_TESTSOLARMUTEX(); - ENSURE_OR_RETURN( ( column >= 0 ) && ( column < getColumnCount() ), - "DefaultTableModel::getColumnModel: invalid index!", PColumnModel() ); - return aColumns[ column ]; - } - - - void UnoControlTableModel::appendColumn( Reference< XGridColumn > const & i_column ) - { - DBG_TESTSOLARMUTEX(); - insertColumn( aColumns.size(), i_column ); - } - - - void UnoControlTableModel::insertColumn( ColPos const i_position, Reference< XGridColumn > const & i_column ) - { - DBG_TESTSOLARMUTEX(); - ENSURE_OR_RETURN_VOID( ( i_position >= 0 ) && ( o3tl::make_unsigned( i_position ) <= aColumns.size() ), - "UnoControlTableModel::insertColumn: illegal position!" ); - - const PColumnModel pColumn = std::make_shared( *this, i_column ); - aColumns.insert( aColumns.begin() + i_position, pColumn ); - - // notify listeners - ModellListeners aListeners( m_aListeners ); - for (auto const& listener : aListeners) - { - listener->columnInserted(); - } - } - - - void UnoControlTableModel::removeColumn( ColPos const i_position ) - { - DBG_TESTSOLARMUTEX(); - ENSURE_OR_RETURN_VOID( ( i_position >= 0 ) && ( o3tl::make_unsigned( i_position ) <= aColumns.size() ), - "UnoControlTableModel::removeColumn: illegal position!" ); - - // remove the column - ColumnModels::iterator pos = aColumns.begin() + i_position; - const PColumnModel pColumn = *pos; - aColumns.erase( pos ); - - // notify listeners - ModellListeners aListeners( m_aListeners ); - for (auto const& listener : aListeners) - { - listener->columnRemoved(); + SAL_WARN("svtools.uno", + "UnoControlTableModel::removeAllColumns: illegal column implementation!"); + continue; } - // dispose the column - UnoGridColumnFacade* pColumnImpl = dynamic_cast< UnoGridColumnFacade* >( pColumn.get() ); - OSL_ENSURE( pColumnImpl != nullptr, "UnoControlTableModel::removeColumn: illegal column implementation!" ); - if ( pColumnImpl ) - pColumnImpl->dispose(); + pColumn->dispose(); } + aColumns.clear(); - - void UnoControlTableModel::removeAllColumns() + // notify listeners + ModellListeners aListeners(m_aListeners); + for (auto const& listener : aListeners) { - DBG_TESTSOLARMUTEX(); - if ( aColumns.empty() ) - return; + listener->allColumnsRemoved(); + } +} - // dispose the column instances - for (auto const& col : aColumns) +void UnoControlTableModel::impl_notifyTableMetricsChanged() const +{ + ModellListeners aListeners(m_aListeners); + for (auto const& listener : aListeners) + { + listener->tableMetricsChanged(); + } +} + +PTableRenderer UnoControlTableModel::getRenderer() const +{ + DBG_TESTSOLARMUTEX(); + return pRenderer; +} + +PTableInputHandler UnoControlTableModel::getInputHandler() const +{ + DBG_TESTSOLARMUTEX(); + return pInputHandler; +} + +TableMetrics UnoControlTableModel::getRowHeight() const +{ + DBG_TESTSOLARMUTEX(); + return nRowHeight; +} + +void UnoControlTableModel::setRowHeight(TableMetrics _nRowHeight) +{ + DBG_TESTSOLARMUTEX(); + if (nRowHeight == _nRowHeight) + return; + + nRowHeight = _nRowHeight; + impl_notifyTableMetricsChanged(); +} + +TableMetrics UnoControlTableModel::getColumnHeaderHeight() const +{ + DBG_TESTSOLARMUTEX(); + DBG_ASSERT(hasColumnHeaders(), "DefaultTableModel::getColumnHeaderHeight: invalid call!"); + return nColumnHeaderHeight; +} + +TableMetrics UnoControlTableModel::getRowHeaderWidth() const +{ + DBG_TESTSOLARMUTEX(); + DBG_ASSERT(hasRowHeaders(), "DefaultTableModel::getRowHeaderWidth: invalid call!"); + return nRowHeaderWidth; +} + +void UnoControlTableModel::setColumnHeaderHeight(TableMetrics _nHeight) +{ + DBG_TESTSOLARMUTEX(); + if (nColumnHeaderHeight == _nHeight) + return; + + nColumnHeaderHeight = _nHeight; + impl_notifyTableMetricsChanged(); +} + +void UnoControlTableModel::setRowHeaderWidth(TableMetrics _nWidth) +{ + DBG_TESTSOLARMUTEX(); + if (nRowHeaderWidth == _nWidth) + return; + + nRowHeaderWidth = _nWidth; + impl_notifyTableMetricsChanged(); +} + +ScrollbarVisibility UnoControlTableModel::getVerticalScrollbarVisibility() const +{ + DBG_TESTSOLARMUTEX(); + return eVScrollMode; +} + +ScrollbarVisibility UnoControlTableModel::getHorizontalScrollbarVisibility() const +{ + DBG_TESTSOLARMUTEX(); + return eHScrollMode; +} + +void UnoControlTableModel::addTableModelListener(const VclPtr& i_listener) +{ + DBG_TESTSOLARMUTEX(); + ENSURE_OR_RETURN_VOID(!!i_listener, "illegal NULL listener"); + m_aListeners.push_back(i_listener); +} + +void UnoControlTableModel::removeTableModelListener(const VclPtr& i_listener) +{ + DBG_TESTSOLARMUTEX(); + auto lookup = std::find(m_aListeners.begin(), m_aListeners.end(), i_listener); + if (lookup != m_aListeners.end()) + { + m_aListeners.erase(lookup); + return; + } + OSL_ENSURE(false, "UnoControlTableModel::removeTableModelListener: listener is not registered " + "- sure you're doing the right thing here?"); +} + +void UnoControlTableModel::setVerticalScrollbarVisibility(ScrollbarVisibility const i_visibility) +{ + DBG_TESTSOLARMUTEX(); + eVScrollMode = i_visibility; +} + +void UnoControlTableModel::setHorizontalScrollbarVisibility(ScrollbarVisibility const i_visibility) +{ + DBG_TESTSOLARMUTEX(); + eHScrollMode = i_visibility; +} + +void UnoControlTableModel::setDataModel(Reference const& i_gridDataModel) +{ + DBG_TESTSOLARMUTEX(); + m_aDataModel = i_gridDataModel; + // TODO: register as listener, so we're notified of row/data changes, and can multiplex them to our + // own listeners +} + +Reference UnoControlTableModel::getDataModel() const +{ + Reference const xDataModel(m_aDataModel); + return xDataModel; +} + +bool UnoControlTableModel::hasDataModel() const { return getDataModel().is(); } + +void UnoControlTableModel::setColumnModel(Reference const& i_gridColumnModel) +{ + DBG_TESTSOLARMUTEX(); + m_aColumnModel = i_gridColumnModel; +} + +Reference UnoControlTableModel::getColumnModel() const +{ + Reference const xColumnModel(m_aColumnModel); + return xColumnModel; +} + +bool UnoControlTableModel::hasColumnModel() const { return getColumnModel().is(); } + +void UnoControlTableModel::getCellContent(sal_Int32 const i_col, sal_Int32 const i_row, + Any& o_cellContent) +{ + DBG_TESTSOLARMUTEX(); + + o_cellContent.clear(); + try + { + Reference const xDataModel(m_aDataModel); + ENSURE_OR_RETURN_VOID(xDataModel.is(), + "UnoControlTableModel::getCellContent: no data model anymore!"); + + PColumnModel const pColumn = getColumnModel(i_col); + UnoGridColumnFacade* pColumnImpl = dynamic_cast(pColumn.get()); + ENSURE_OR_RETURN_VOID( + pColumnImpl != nullptr, + "UnoControlTableModel::getCellContent: no (valid) column at this position!"); + sal_Int32 const nDataColumnIndex + = pColumnImpl->getDataColumnIndex() >= 0 ? pColumnImpl->getDataColumnIndex() : i_col; + + if (nDataColumnIndex >= xDataModel->getColumnCount()) { - UnoGridColumnFacade* pColumn = dynamic_cast< UnoGridColumnFacade* >( col.get() ); - if ( !pColumn ) - { - SAL_WARN( "svtools.uno", "UnoControlTableModel::removeAllColumns: illegal column implementation!" ); - continue; - } - - pColumn->dispose(); +#if OSL_DEBUG_LEVEL > 0 + // this is allowed, in case the column model has been dynamically extended, but the data model does + // not (yet?) know about it. + // So, handle it gracefully. + Reference const xColumnModel(m_aColumnModel); + OSL_ENSURE(xColumnModel.is() && i_col < xColumnModel->getColumnCount(), + "UnoControlTableModel::getCellContent: request a column's value which the " + "ColumnModel doesn't know about!"); +#endif } - aColumns.clear(); - - // notify listeners - ModellListeners aListeners( m_aListeners ); - for (auto const& listener : aListeners) - { - listener->allColumnsRemoved(); - } - } - - - void UnoControlTableModel::impl_notifyTableMetricsChanged() const - { - ModellListeners aListeners( m_aListeners ); - for (auto const& listener : aListeners) - { - listener->tableMetricsChanged(); - } - } - - - PTableRenderer UnoControlTableModel::getRenderer() const - { - DBG_TESTSOLARMUTEX(); - return pRenderer; - } - - - PTableInputHandler UnoControlTableModel::getInputHandler() const - { - DBG_TESTSOLARMUTEX(); - return pInputHandler; - } - - - TableMetrics UnoControlTableModel::getRowHeight() const - { - DBG_TESTSOLARMUTEX(); - return nRowHeight; - } - - - void UnoControlTableModel::setRowHeight(TableMetrics _nRowHeight) - { - DBG_TESTSOLARMUTEX(); - if ( nRowHeight == _nRowHeight ) - return; - - nRowHeight = _nRowHeight; - impl_notifyTableMetricsChanged(); - } - - - TableMetrics UnoControlTableModel::getColumnHeaderHeight() const - { - DBG_TESTSOLARMUTEX(); - DBG_ASSERT( hasColumnHeaders(), "DefaultTableModel::getColumnHeaderHeight: invalid call!" ); - return nColumnHeaderHeight; - } - - - TableMetrics UnoControlTableModel::getRowHeaderWidth() const - { - DBG_TESTSOLARMUTEX(); - DBG_ASSERT( hasRowHeaders(), "DefaultTableModel::getRowHeaderWidth: invalid call!" ); - return nRowHeaderWidth; - } - - void UnoControlTableModel::setColumnHeaderHeight(TableMetrics _nHeight) - { - DBG_TESTSOLARMUTEX(); - if ( nColumnHeaderHeight == _nHeight ) - return; - - nColumnHeaderHeight = _nHeight; - impl_notifyTableMetricsChanged(); - } - - - void UnoControlTableModel::setRowHeaderWidth(TableMetrics _nWidth) - { - DBG_TESTSOLARMUTEX(); - if ( nRowHeaderWidth == _nWidth ) - return; - - nRowHeaderWidth = _nWidth; - impl_notifyTableMetricsChanged(); - } - - - ScrollbarVisibility UnoControlTableModel::getVerticalScrollbarVisibility() const - { - DBG_TESTSOLARMUTEX(); - return eVScrollMode; - } - - - ScrollbarVisibility UnoControlTableModel::getHorizontalScrollbarVisibility() const - { - DBG_TESTSOLARMUTEX(); - return eHScrollMode; - } - - - void UnoControlTableModel::addTableModelListener( const PTableModelListener& i_listener ) - { - DBG_TESTSOLARMUTEX(); - ENSURE_OR_RETURN_VOID( !!i_listener, "illegal NULL listener" ); - m_aListeners.push_back( i_listener ); - } - - - void UnoControlTableModel::removeTableModelListener( const PTableModelListener& i_listener ) - { - DBG_TESTSOLARMUTEX(); - auto lookup = std::find(m_aListeners.begin(), m_aListeners.end(), i_listener); - if (lookup != m_aListeners.end()) - { - m_aListeners.erase( lookup ); - return; - } - OSL_ENSURE( false, "UnoControlTableModel::removeTableModelListener: listener is not registered - sure you're doing the right thing here?" ); - } - - - void UnoControlTableModel::setVerticalScrollbarVisibility( ScrollbarVisibility const i_visibility ) - { - DBG_TESTSOLARMUTEX(); - eVScrollMode = i_visibility; - } - - - void UnoControlTableModel::setHorizontalScrollbarVisibility( ScrollbarVisibility const i_visibility ) - { - DBG_TESTSOLARMUTEX(); - eHScrollMode = i_visibility; - } - - - void UnoControlTableModel::setDataModel( Reference< XGridDataModel > const & i_gridDataModel ) - { - DBG_TESTSOLARMUTEX(); - m_aDataModel = i_gridDataModel; - // TODO: register as listener, so we're notified of row/data changes, and can multiplex them to our - // own listeners - } - - - Reference< XGridDataModel > UnoControlTableModel::getDataModel() const - { - Reference< XGridDataModel > const xDataModel( m_aDataModel ); - return xDataModel; - } - - - bool UnoControlTableModel::hasDataModel() const - { - return getDataModel().is(); - } - - - void UnoControlTableModel::setColumnModel( Reference< XGridColumnModel > const & i_gridColumnModel ) - { - DBG_TESTSOLARMUTEX(); - m_aColumnModel = i_gridColumnModel; - } - - - Reference< XGridColumnModel > UnoControlTableModel::getColumnModel() const - { - Reference< XGridColumnModel > const xColumnModel( m_aColumnModel ); - return xColumnModel; - } - - - bool UnoControlTableModel::hasColumnModel() const - { - return getColumnModel().is(); - } - - - void UnoControlTableModel::getCellContent( ColPos const i_col, RowPos const i_row, Any& o_cellContent ) - { - DBG_TESTSOLARMUTEX(); - - o_cellContent.clear(); - try - { - Reference< XGridDataModel > const xDataModel( m_aDataModel ); - ENSURE_OR_RETURN_VOID( xDataModel.is(), "UnoControlTableModel::getCellContent: no data model anymore!" ); - - PColumnModel const pColumn = getColumnModel( i_col ); - UnoGridColumnFacade* pColumnImpl = dynamic_cast< UnoGridColumnFacade* >( pColumn.get() ); - ENSURE_OR_RETURN_VOID( pColumnImpl != nullptr, "UnoControlTableModel::getCellContent: no (valid) column at this position!" ); - sal_Int32 const nDataColumnIndex = pColumnImpl->getDataColumnIndex() >= 0 ? pColumnImpl->getDataColumnIndex() : i_col; - - if ( nDataColumnIndex >= xDataModel->getColumnCount() ) - { - // this is allowed, in case the column model has been dynamically extended, but the data model does - // not (yet?) know about it. - // So, handle it gracefully. - #if OSL_DEBUG_LEVEL > 0 - Reference< XGridColumnModel > const xColumnModel( m_aColumnModel ); - OSL_ENSURE( xColumnModel.is() && i_col < xColumnModel->getColumnCount(), - "UnoControlTableModel::getCellContent: request a column's value which the ColumnModel doesn't know about!" ); - #endif - } - else - { - o_cellContent = xDataModel->getCellData( nDataColumnIndex, i_row ); - } - } - catch( const Exception& ) - { - DBG_UNHANDLED_EXCEPTION("svtools.uno"); - } - } - - - void UnoControlTableModel::getCellToolTip( ColPos const i_col, RowPos const i_row, Any& o_cellToolTip ) - { - DBG_TESTSOLARMUTEX(); - try - { - Reference< XGridDataModel > const xDataModel( m_aDataModel ); - ENSURE_OR_THROW( xDataModel.is(), "no data model anymore!" ); - - o_cellToolTip = xDataModel->getCellToolTip( i_col, i_row ); - } - catch( const Exception& ) - { - DBG_UNHANDLED_EXCEPTION("svtools.uno"); - } - } - - - Any UnoControlTableModel::getRowHeading( RowPos const i_rowPos ) const - { - DBG_TESTSOLARMUTEX(); - - Any aRowHeading; - - Reference< XGridDataModel > const xDataModel( m_aDataModel ); - ENSURE_OR_RETURN( xDataModel.is(), "UnoControlTableModel::getRowHeading: no data model anymore!", aRowHeading ); - - try - { - aRowHeading = xDataModel->getRowHeading( i_rowPos ); - } - catch( const Exception& ) - { - DBG_UNHANDLED_EXCEPTION("svtools.uno"); - } - return aRowHeading; - } - - - namespace - { - void lcl_setColor( Any const & i_color, ::std::optional< ::Color > & o_convertedColor ) - { - if ( !i_color.hasValue() ) - o_convertedColor.reset(); - else - { - Color nColor = COL_TRANSPARENT; - if ( i_color >>= nColor ) - { - o_convertedColor = nColor; - } - else - { - OSL_ENSURE( false, "lcl_setColor: could not extract color value!" ); - } - } - } - } - - - ::std::optional< ::Color > UnoControlTableModel::getLineColor() const - { - DBG_TESTSOLARMUTEX(); - return m_aGridLineColor; - } - - - void UnoControlTableModel::setLineColor( Any const & i_color ) - { - DBG_TESTSOLARMUTEX(); - lcl_setColor( i_color, m_aGridLineColor ); - } - - - ::std::optional< ::Color > UnoControlTableModel::getHeaderBackgroundColor() const - { - DBG_TESTSOLARMUTEX(); - return m_aHeaderBackgroundColor; - } - - - void UnoControlTableModel::setHeaderBackgroundColor( Any const & i_color ) - { - DBG_TESTSOLARMUTEX(); - lcl_setColor( i_color, m_aHeaderBackgroundColor ); - } - - - ::std::optional< ::Color > UnoControlTableModel::getHeaderTextColor() const - { - DBG_TESTSOLARMUTEX(); - return m_aHeaderTextColor; - } - - - ::std::optional< ::Color > UnoControlTableModel::getActiveSelectionBackColor() const - { - DBG_TESTSOLARMUTEX(); - return m_aActiveSelectionBackColor; - } - - - ::std::optional< ::Color > UnoControlTableModel::getInactiveSelectionBackColor() const - { - DBG_TESTSOLARMUTEX(); - return m_aInactiveSelectionBackColor; - } - - - ::std::optional< ::Color > UnoControlTableModel::getActiveSelectionTextColor() const - { - DBG_TESTSOLARMUTEX(); - return m_aActiveSelectionTextColor; - } - - - ::std::optional< ::Color > UnoControlTableModel::getInactiveSelectionTextColor() const - { - DBG_TESTSOLARMUTEX(); - return m_aInactiveSelectionTextColor; - } - - - void UnoControlTableModel::setHeaderTextColor( Any const & i_color ) - { - DBG_TESTSOLARMUTEX(); - lcl_setColor( i_color, m_aHeaderTextColor ); - } - - - void UnoControlTableModel::setActiveSelectionBackColor( Any const & i_color ) - { - DBG_TESTSOLARMUTEX(); - lcl_setColor( i_color, m_aActiveSelectionBackColor ); - } - - - void UnoControlTableModel::setInactiveSelectionBackColor( Any const & i_color ) - { - DBG_TESTSOLARMUTEX(); - lcl_setColor( i_color, m_aInactiveSelectionBackColor ); - } - - - void UnoControlTableModel::setActiveSelectionTextColor( Any const & i_color ) - { - DBG_TESTSOLARMUTEX(); - lcl_setColor( i_color, m_aActiveSelectionTextColor ); - } - - - void UnoControlTableModel::setInactiveSelectionTextColor( Any const & i_color ) - { - DBG_TESTSOLARMUTEX(); - lcl_setColor( i_color, m_aInactiveSelectionTextColor ); - } - - - ::std::optional< ::Color > UnoControlTableModel::getTextColor() const - { - DBG_TESTSOLARMUTEX(); - return m_aTextColor; - } - - - void UnoControlTableModel::setTextColor( Any const & i_color ) - { - DBG_TESTSOLARMUTEX(); - lcl_setColor( i_color, m_aTextColor ); - } - - - ::std::optional< ::Color > UnoControlTableModel::getTextLineColor() const - { - DBG_TESTSOLARMUTEX(); - return m_aTextColor; - } - - - void UnoControlTableModel::setTextLineColor( Any const & i_color ) - { - DBG_TESTSOLARMUTEX(); - lcl_setColor( i_color, m_aTextLineColor ); - } - - - ::std::optional< ::std::vector< ::Color > > UnoControlTableModel::getRowBackgroundColors() const - { - DBG_TESTSOLARMUTEX(); - return m_aRowColors; - } - - - void UnoControlTableModel::setRowBackgroundColors( css::uno::Any const & i_APIValue ) - { - DBG_TESTSOLARMUTEX(); - Sequence< css::util::Color > aAPIColors; - if ( !( i_APIValue >>= aAPIColors ) ) - m_aRowColors.reset(); else { - ::std::vector< ::Color > aColors( aAPIColors.getLength() ); - std::transform(std::cbegin(aAPIColors), std::cend(aAPIColors), aColors.begin(), - [](const css::util::Color& rAPIColor) -> ::Color { return Color(ColorTransparency, rAPIColor); }); - m_aRowColors = aColors; + o_cellContent = xDataModel->getCellData(nDataColumnIndex, i_row); } } - - - VerticalAlignment UnoControlTableModel::getVerticalAlign() const + catch (const Exception&) { - DBG_TESTSOLARMUTEX(); - return m_eVerticalAlign; + DBG_UNHANDLED_EXCEPTION("svtools.uno"); } +} - - void UnoControlTableModel::setVerticalAlign( VerticalAlignment _xAlign ) +void UnoControlTableModel::getCellToolTip(sal_Int32 const i_col, sal_Int32 const i_row, + Any& o_cellToolTip) +{ + DBG_TESTSOLARMUTEX(); + try { - DBG_TESTSOLARMUTEX(); - m_eVerticalAlign = _xAlign; + Reference const xDataModel(m_aDataModel); + ENSURE_OR_THROW(xDataModel.is(), "no data model anymore!"); + + o_cellToolTip = xDataModel->getCellToolTip(i_col, i_row); } - - - ColPos UnoControlTableModel::getColumnPos( UnoGridColumnFacade const & i_column ) const + catch (const Exception&) { - DBG_TESTSOLARMUTEX(); - ColPos nPos = 0; - for (auto const& col : aColumns) + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } +} + +Any UnoControlTableModel::getRowHeading(sal_Int32 const i_rowPos) const +{ + DBG_TESTSOLARMUTEX(); + + Any aRowHeading; + + Reference const xDataModel(m_aDataModel); + ENSURE_OR_RETURN(xDataModel.is(), "UnoControlTableModel::getRowHeading: no data model anymore!", + aRowHeading); + + try + { + aRowHeading = xDataModel->getRowHeading(i_rowPos); + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + return aRowHeading; +} + +namespace +{ +void lcl_setColor(Any const& i_color, ::std::optional<::Color>& o_convertedColor) +{ + if (!i_color.hasValue()) + o_convertedColor.reset(); + else + { + Color nColor = COL_TRANSPARENT; + if (i_color >>= nColor) { - if ( &i_column == col.get() ) - return nPos; - ++nPos; + o_convertedColor = nColor; } - OSL_ENSURE( false, "UnoControlTableModel::getColumnPos: column not found!" ); - return COL_INVALID; - } - - - ITableDataSort* UnoControlTableModel::getSortAdapter() - { - DBG_TESTSOLARMUTEX(); - - Reference< XSortableGridData > const xSortAccess( getDataModel(), UNO_QUERY ); - if ( xSortAccess.is() ) - return this; - return nullptr; - } - - - bool UnoControlTableModel::isEnabled() const - { - DBG_TESTSOLARMUTEX(); - return bEnabled; - } - - - void UnoControlTableModel::setEnabled( bool _bEnabled ) - { - DBG_TESTSOLARMUTEX(); - bEnabled = _bEnabled; - } - - - void UnoControlTableModel::sortByColumn( ColPos const i_column, ColumnSortDirection const i_sortDirection ) - { - DBG_TESTSOLARMUTEX(); - - try + else { - Reference< XSortableGridData > const xSortAccess( getDataModel(), UNO_QUERY_THROW ); - xSortAccess->sortByColumn( i_column, i_sortDirection == ColumnSortAscending ); - } - catch( const Exception& ) - { - DBG_UNHANDLED_EXCEPTION("svtools.uno"); + OSL_ENSURE(false, "lcl_setColor: could not extract color value!"); } } +} +} +::std::optional<::Color> UnoControlTableModel::getLineColor() const +{ + DBG_TESTSOLARMUTEX(); + return m_aGridLineColor; +} - ColumnSort UnoControlTableModel::getCurrentSortOrder() const +void UnoControlTableModel::setLineColor(Any const& i_color) +{ + DBG_TESTSOLARMUTEX(); + lcl_setColor(i_color, m_aGridLineColor); +} + +::std::optional<::Color> UnoControlTableModel::getHeaderBackgroundColor() const +{ + DBG_TESTSOLARMUTEX(); + return m_aHeaderBackgroundColor; +} + +void UnoControlTableModel::setHeaderBackgroundColor(Any const& i_color) +{ + DBG_TESTSOLARMUTEX(); + lcl_setColor(i_color, m_aHeaderBackgroundColor); +} + +::std::optional<::Color> UnoControlTableModel::getHeaderTextColor() const +{ + DBG_TESTSOLARMUTEX(); + return m_aHeaderTextColor; +} + +::std::optional<::Color> UnoControlTableModel::getActiveSelectionBackColor() const +{ + DBG_TESTSOLARMUTEX(); + return m_aActiveSelectionBackColor; +} + +::std::optional<::Color> UnoControlTableModel::getInactiveSelectionBackColor() const +{ + DBG_TESTSOLARMUTEX(); + return m_aInactiveSelectionBackColor; +} + +::std::optional<::Color> UnoControlTableModel::getActiveSelectionTextColor() const +{ + DBG_TESTSOLARMUTEX(); + return m_aActiveSelectionTextColor; +} + +::std::optional<::Color> UnoControlTableModel::getInactiveSelectionTextColor() const +{ + DBG_TESTSOLARMUTEX(); + return m_aInactiveSelectionTextColor; +} + +void UnoControlTableModel::setHeaderTextColor(Any const& i_color) +{ + DBG_TESTSOLARMUTEX(); + lcl_setColor(i_color, m_aHeaderTextColor); +} + +void UnoControlTableModel::setActiveSelectionBackColor(Any const& i_color) +{ + DBG_TESTSOLARMUTEX(); + lcl_setColor(i_color, m_aActiveSelectionBackColor); +} + +void UnoControlTableModel::setInactiveSelectionBackColor(Any const& i_color) +{ + DBG_TESTSOLARMUTEX(); + lcl_setColor(i_color, m_aInactiveSelectionBackColor); +} + +void UnoControlTableModel::setActiveSelectionTextColor(Any const& i_color) +{ + DBG_TESTSOLARMUTEX(); + lcl_setColor(i_color, m_aActiveSelectionTextColor); +} + +void UnoControlTableModel::setInactiveSelectionTextColor(Any const& i_color) +{ + DBG_TESTSOLARMUTEX(); + lcl_setColor(i_color, m_aInactiveSelectionTextColor); +} + +::std::optional<::Color> UnoControlTableModel::getTextColor() const +{ + DBG_TESTSOLARMUTEX(); + return m_aTextColor; +} + +void UnoControlTableModel::setTextColor(Any const& i_color) +{ + DBG_TESTSOLARMUTEX(); + lcl_setColor(i_color, m_aTextColor); +} + +::std::optional<::Color> UnoControlTableModel::getTextLineColor() const +{ + DBG_TESTSOLARMUTEX(); + return m_aTextColor; +} + +void UnoControlTableModel::setTextLineColor(Any const& i_color) +{ + DBG_TESTSOLARMUTEX(); + lcl_setColor(i_color, m_aTextLineColor); +} + +::std::optional<::std::vector<::Color>> UnoControlTableModel::getRowBackgroundColors() const +{ + DBG_TESTSOLARMUTEX(); + return m_aRowColors; +} + +void UnoControlTableModel::setRowBackgroundColors(css::uno::Any const& i_APIValue) +{ + DBG_TESTSOLARMUTEX(); + Sequence aAPIColors; + if (!(i_APIValue >>= aAPIColors)) + m_aRowColors.reset(); + else { - DBG_TESTSOLARMUTEX(); - - ColumnSort currentSort; - try - { - Reference< XSortableGridData > const xSortAccess( getDataModel(), UNO_QUERY_THROW ); - Pair< ::sal_Int32, sal_Bool > const aCurrentSortOrder( xSortAccess->getCurrentSortOrder() ); - currentSort.nColumnPos = aCurrentSortOrder.First; - currentSort.eSortDirection = aCurrentSortOrder.Second ? ColumnSortAscending : ColumnSortDescending; - } - catch( const Exception& ) - { - DBG_UNHANDLED_EXCEPTION("svtools.uno"); - } - return currentSort; + ::std::vector<::Color> aColors(aAPIColors.getLength()); + std::transform(std::cbegin(aAPIColors), std::cend(aAPIColors), aColors.begin(), + [](const css::util::Color& rAPIColor) -> ::Color { + return Color(ColorTransparency, rAPIColor); + }); + m_aRowColors = aColors; } +} +VerticalAlignment UnoControlTableModel::getVerticalAlign() const +{ + DBG_TESTSOLARMUTEX(); + return m_eVerticalAlign; +} - void UnoControlTableModel::notifyColumnChange( ColPos const i_columnPos, ColumnAttributeGroup const i_attributeGroup ) const +void UnoControlTableModel::setVerticalAlign(VerticalAlignment _xAlign) +{ + DBG_TESTSOLARMUTEX(); + m_eVerticalAlign = _xAlign; +} + +sal_Int32 UnoControlTableModel::getColumnPos(UnoGridColumnFacade const& i_column) const +{ + DBG_TESTSOLARMUTEX(); + sal_Int32 nPos = 0; + for (auto const& col : aColumns) { - DBG_TESTSOLARMUTEX(); - ENSURE_OR_RETURN_VOID( ( i_columnPos >= 0 ) && ( i_columnPos < getColumnCount() ), - "UnoControlTableModel::notifyColumnChange: invalid column index!" ); - - ModellListeners aListeners( m_aListeners ); - for (auto const& listener : aListeners) - { - listener->columnChanged( i_columnPos, i_attributeGroup ); - } + if (&i_column == col.get()) + return nPos; + ++nPos; } + OSL_ENSURE(false, "UnoControlTableModel::getColumnPos: column not found!"); + return COL_INVALID; +} +ITableDataSort* UnoControlTableModel::getSortAdapter() +{ + DBG_TESTSOLARMUTEX(); - void UnoControlTableModel::notifyRowsInserted( GridDataEvent const & i_event ) const + Reference const xSortAccess(getDataModel(), UNO_QUERY); + if (xSortAccess.is()) + return this; + return nullptr; +} + +bool UnoControlTableModel::isEnabled() const +{ + DBG_TESTSOLARMUTEX(); + return bEnabled; +} + +void UnoControlTableModel::setEnabled(bool _bEnabled) +{ + DBG_TESTSOLARMUTEX(); + bEnabled = _bEnabled; +} + +void UnoControlTableModel::sortByColumn(sal_Int32 const i_column, + ColumnSortDirection const i_sortDirection) +{ + DBG_TESTSOLARMUTEX(); + + try { - // check sanity of the event - ENSURE_OR_RETURN_VOID( i_event.FirstRow >= 0, "UnoControlTableModel::notifyRowsInserted: invalid first row!" ); - ENSURE_OR_RETURN_VOID( i_event.LastRow >= i_event.FirstRow, "UnoControlTableModel::notifyRowsInserted: invalid row indexes!" ); - - // check own sanity - Reference< XGridColumnModel > const xColumnModel( m_aColumnModel ); - ENSURE_OR_RETURN_VOID( xColumnModel.is(), "UnoControlTableModel::notifyRowsInserted: no column model anymore!" ); - - Reference< XGridDataModel > const xDataModel( m_aDataModel ); - ENSURE_OR_RETURN_VOID( xDataModel.is(), "UnoControlTableModel::notifyRowsInserted: no data model anymore!" ); - - // implicitly add columns to the column model - // TODO: is this really a good idea? - sal_Int32 const dataColumnCount = xDataModel->getColumnCount(); - OSL_ENSURE( dataColumnCount > 0, "UnoControlTableModel::notifyRowsInserted: no columns at all?" ); - - sal_Int32 const modelColumnCount = xColumnModel->getColumnCount(); - if ( ( modelColumnCount == 0 ) && ( dataColumnCount > 0 ) ) - { - // TODO: shouldn't we clear the mutexes guard for this call? - xColumnModel->setDefaultColumns( dataColumnCount ); - } - - // multiplex the event to our own listeners - ModellListeners aListeners( m_aListeners ); - for (auto const& listener : aListeners) - { - listener->rowsInserted( i_event.FirstRow, i_event.LastRow ); - } + Reference const xSortAccess(getDataModel(), UNO_QUERY_THROW); + xSortAccess->sortByColumn(i_column, i_sortDirection == ColumnSortAscending); } - - - void UnoControlTableModel::notifyRowsRemoved( GridDataEvent const & i_event ) const + catch (const Exception&) { - ModellListeners aListeners( m_aListeners ); - for (auto const& listener : aListeners) - { - listener->rowsRemoved( i_event.FirstRow, i_event.LastRow ); - } + DBG_UNHANDLED_EXCEPTION("svtools.uno"); } +} +ColumnSort UnoControlTableModel::getCurrentSortOrder() const +{ + DBG_TESTSOLARMUTEX(); - void UnoControlTableModel::notifyDataChanged( css::awt::grid::GridDataEvent const & i_event ) const + ColumnSort currentSort; + try { - RowPos const firstRow = i_event.FirstRow == -1 ? 0 : i_event.FirstRow; - RowPos const lastRow = i_event.FirstRow == -1 ? getRowCount() - 1 : i_event.LastRow; - - ModellListeners aListeners( m_aListeners ); - for (auto const& listener : aListeners) - { - listener->cellsUpdated( firstRow, lastRow ); - } + Reference const xSortAccess(getDataModel(), UNO_QUERY_THROW); + Pair<::sal_Int32, sal_Bool> const aCurrentSortOrder(xSortAccess->getCurrentSortOrder()); + currentSort.nColumnPos = aCurrentSortOrder.First; + currentSort.eSortDirection + = aCurrentSortOrder.Second ? ColumnSortAscending : ColumnSortDescending; } - - - void UnoControlTableModel::notifyAllDataChanged() const + catch (const Exception&) { - ModellListeners aListeners( m_aListeners ); - for (auto const& listener : aListeners) - { - listener->cellsUpdated( 0, getRowCount() - 1 ); - } + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + return currentSort; +} + +void UnoControlTableModel::notifyColumnChange(sal_Int32 const i_columnPos, + ColumnAttributeGroup const i_attributeGroup) const +{ + DBG_TESTSOLARMUTEX(); + ENSURE_OR_RETURN_VOID((i_columnPos >= 0) && (i_columnPos < getColumnCount()), + "UnoControlTableModel::notifyColumnChange: invalid column index!"); + + ModellListeners aListeners(m_aListeners); + for (auto const& listener : aListeners) + { + listener->columnChanged(i_columnPos, i_attributeGroup); + } +} + +void UnoControlTableModel::notifyRowsInserted(GridDataEvent const& i_event) const +{ + // check sanity of the event + ENSURE_OR_RETURN_VOID(i_event.FirstRow >= 0, + "UnoControlTableModel::notifyRowsInserted: invalid first row!"); + ENSURE_OR_RETURN_VOID(i_event.LastRow >= i_event.FirstRow, + "UnoControlTableModel::notifyRowsInserted: invalid row indexes!"); + + // check own sanity + Reference const xColumnModel(m_aColumnModel); + ENSURE_OR_RETURN_VOID(xColumnModel.is(), + "UnoControlTableModel::notifyRowsInserted: no column model anymore!"); + + Reference const xDataModel(m_aDataModel); + ENSURE_OR_RETURN_VOID(xDataModel.is(), + "UnoControlTableModel::notifyRowsInserted: no data model anymore!"); + + // implicitly add columns to the column model + // TODO: is this really a good idea? + sal_Int32 const dataColumnCount = xDataModel->getColumnCount(); + OSL_ENSURE(dataColumnCount > 0, "UnoControlTableModel::notifyRowsInserted: no columns at all?"); + + sal_Int32 const modelColumnCount = xColumnModel->getColumnCount(); + if ((modelColumnCount == 0) && (dataColumnCount > 0)) + { + // TODO: shouldn't we clear the mutexes guard for this call? + xColumnModel->setDefaultColumns(dataColumnCount); } + // multiplex the event to our own listeners + ModellListeners aListeners(m_aListeners); + for (auto const& listener : aListeners) + { + listener->rowsInserted(i_event.FirstRow, i_event.LastRow); + } +} + +void UnoControlTableModel::notifyRowsRemoved(GridDataEvent const& i_event) const +{ + ModellListeners aListeners(m_aListeners); + for (auto const& listener : aListeners) + { + listener->rowsRemoved(i_event.FirstRow, i_event.LastRow); + } +} + +void UnoControlTableModel::notifyDataChanged(css::awt::grid::GridDataEvent const& i_event) const +{ + sal_Int32 const firstRow = i_event.FirstRow == -1 ? 0 : i_event.FirstRow; + sal_Int32 const lastRow = i_event.FirstRow == -1 ? getRowCount() - 1 : i_event.LastRow; + + ModellListeners aListeners(m_aListeners); + for (auto const& listener : aListeners) + { + listener->cellsUpdated(firstRow, lastRow); + } +} + +void UnoControlTableModel::notifyAllDataChanged() const +{ + ModellListeners aListeners(m_aListeners); + for (auto const& listener : aListeners) + { + listener->cellsUpdated(0, getRowCount() - 1); + } +} } // svt::table diff --git a/toolkit/source/controls/unocontroltablemodel.hxx b/toolkit/source/controls/unocontroltablemodel.hxx index b77c2e5ce531..0d460eb29981 100644 --- a/toolkit/source/controls/unocontroltablemodel.hxx +++ b/toolkit/source/controls/unocontroltablemodel.hxx @@ -30,151 +30,147 @@ #include #include - namespace svt::table { +//= UnoControlTableModel +class UnoGridColumnFacade; +class UnoControlTableModel : public ITableModel, public ITableDataSort +{ +public: + UnoControlTableModel(); + virtual ~UnoControlTableModel() override; - //= UnoControlTableModel +public: + // ITableModel overridables + virtual TableSize getColumnCount() const override; + virtual TableSize getRowCount() const override; + virtual bool hasColumnHeaders() const override; + virtual bool hasRowHeaders() const override; + virtual PColumnModel getColumnModel(sal_Int32 column) override; + virtual PTableRenderer getRenderer() const override; + virtual PTableInputHandler getInputHandler() const override; + virtual TableMetrics getRowHeight() const override; + virtual TableMetrics getColumnHeaderHeight() const override; + virtual TableMetrics getRowHeaderWidth() const override; + virtual ScrollbarVisibility getVerticalScrollbarVisibility() const override; + virtual ScrollbarVisibility getHorizontalScrollbarVisibility() const override; + virtual void addTableModelListener(const VclPtr& i_listener) override; + virtual void removeTableModelListener(const VclPtr& i_listener) override; + virtual void getCellContent(sal_Int32 const i_col, sal_Int32 const i_row, + css::uno::Any& o_cellContent) override; + virtual void getCellToolTip(sal_Int32 const i_col, sal_Int32 const i_row, + css::uno::Any& o_cellToolTip) override; + virtual css::uno::Any getRowHeading(sal_Int32 const i_rowPos) const override; + virtual ::std::optional<::Color> getLineColor() const override; + virtual ::std::optional<::Color> getHeaderBackgroundColor() const override; + virtual ::std::optional<::Color> getHeaderTextColor() const override; + virtual ::std::optional<::Color> getActiveSelectionBackColor() const override; + virtual ::std::optional<::Color> getInactiveSelectionBackColor() const override; + virtual ::std::optional<::Color> getActiveSelectionTextColor() const override; + virtual ::std::optional<::Color> getInactiveSelectionTextColor() const override; + virtual ::std::optional<::Color> getTextColor() const override; + virtual ::std::optional<::Color> getTextLineColor() const override; + virtual ::std::optional<::std::vector<::Color>> getRowBackgroundColors() const override; + virtual css::style::VerticalAlignment getVerticalAlign() const override; + virtual ITableDataSort* getSortAdapter() override; + virtual bool isEnabled() const override; - class UnoGridColumnFacade; - class UnoControlTableModel : public ITableModel, public ITableDataSort - { - public: - UnoControlTableModel(); - virtual ~UnoControlTableModel() override; + // ITableDataSort overridables + virtual void sortByColumn(sal_Int32 const i_column, + ColumnSortDirection const i_sortDirection) override; + virtual ColumnSort getCurrentSortOrder() const override; - public: - // ITableModel overridables - virtual TableSize getColumnCount() const override; - virtual TableSize getRowCount() const override; - virtual bool hasColumnHeaders() const override; - virtual bool hasRowHeaders() const override; - virtual PColumnModel getColumnModel( ColPos column ) override; - virtual PTableRenderer getRenderer() const override; - virtual PTableInputHandler getInputHandler() const override; - virtual TableMetrics getRowHeight() const override; - virtual TableMetrics getColumnHeaderHeight() const override; - virtual TableMetrics getRowHeaderWidth() const override; - virtual ScrollbarVisibility getVerticalScrollbarVisibility() const override; - virtual ScrollbarVisibility getHorizontalScrollbarVisibility() const override; - virtual void addTableModelListener( const PTableModelListener& i_listener ) override; - virtual void removeTableModelListener( const PTableModelListener& i_listener ) override; - virtual void getCellContent( ColPos const i_col, RowPos const i_row, css::uno::Any& o_cellContent ) override; - virtual void getCellToolTip( ColPos const i_col, RowPos const i_row, css::uno::Any & o_cellToolTip ) override; - virtual css::uno::Any getRowHeading( RowPos const i_rowPos ) const override; - virtual ::std::optional< ::Color > getLineColor() const override; - virtual ::std::optional< ::Color > getHeaderBackgroundColor() const override; - virtual ::std::optional< ::Color > getHeaderTextColor() const override; - virtual ::std::optional< ::Color > getActiveSelectionBackColor() const override; - virtual ::std::optional< ::Color > getInactiveSelectionBackColor() const override; - virtual ::std::optional< ::Color > getActiveSelectionTextColor() const override; - virtual ::std::optional< ::Color > getInactiveSelectionTextColor() const override; - virtual ::std::optional< ::Color > getTextColor() const override; - virtual ::std::optional< ::Color > getTextLineColor() const override; - virtual ::std::optional< ::std::vector< ::Color > > - getRowBackgroundColors() const override; - virtual css::style::VerticalAlignment - getVerticalAlign() const override; - virtual ITableDataSort* getSortAdapter() override; - virtual bool isEnabled() const override; + // column write access + void appendColumn(css::uno::Reference const& i_column); + void insertColumn(sal_Int32 const i_position, + css::uno::Reference const& i_column); + void removeColumn(sal_Int32 const i_position); + void removeAllColumns(); - // ITableDataSort overridables - virtual void sortByColumn( ColPos const i_column, ColumnSortDirection const i_sortDirection ) override; - virtual ColumnSort getCurrentSortOrder() const override; + // other operations + void setVerticalScrollbarVisibility(ScrollbarVisibility const i_visibility); + void setHorizontalScrollbarVisibility(ScrollbarVisibility const i_visibility); - // column write access - void appendColumn( css::uno::Reference< css::awt::grid::XGridColumn > const & i_column ); - void insertColumn( ColPos const i_position, css::uno::Reference< css::awt::grid::XGridColumn > const & i_column ); - void removeColumn( ColPos const i_position ); - void removeAllColumns(); + void setDataModel(css::uno::Reference const& i_gridDataModel); + bool hasDataModel() const; + css::uno::Reference getDataModel() const; + void + setColumnModel(css::uno::Reference const& i_gridColumnModel); + bool hasColumnModel() const; + css::uno::Reference getColumnModel() const; - // other operations - void setVerticalScrollbarVisibility( ScrollbarVisibility const i_visibility ); - void setHorizontalScrollbarVisibility( ScrollbarVisibility const i_visibility ); + void setRowHeaders(bool _bRowHeaders); + void setColumnHeaders(bool _bColumnHeaders); - void setDataModel( css::uno::Reference< css::awt::grid::XGridDataModel > const & i_gridDataModel ); - bool hasDataModel() const; - css::uno::Reference< css::awt::grid::XGridDataModel > - getDataModel() const; - void setColumnModel( css::uno::Reference< css::awt::grid::XGridColumnModel > const & i_gridColumnModel ); - bool hasColumnModel() const; - css::uno::Reference< css::awt::grid::XGridColumnModel > - getColumnModel() const; + void setRowHeight(TableMetrics _nHeight); + void setRowHeaderWidth(TableMetrics _nWidth); + void setColumnHeaderHeight(TableMetrics _nHeight); - void setRowHeaders(bool _bRowHeaders); - void setColumnHeaders(bool _bColumnHeaders); + void setLineColor(css::uno::Any const& i_color); + void setHeaderBackgroundColor(css::uno::Any const& i_color); + void setHeaderTextColor(css::uno::Any const& i_color); + void setActiveSelectionBackColor(css::uno::Any const& i_color); + void setInactiveSelectionBackColor(css::uno::Any const& i_color); + void setActiveSelectionTextColor(css::uno::Any const& i_color); + void setInactiveSelectionTextColor(css::uno::Any const& i_color); + void setTextColor(css::uno::Any const& i_color); + void setTextLineColor(css::uno::Any const& i_color); + void setRowBackgroundColors(css::uno::Any const& i_APIValue); - void setRowHeight( TableMetrics _nHeight ); - void setRowHeaderWidth( TableMetrics _nWidth ); - void setColumnHeaderHeight( TableMetrics _nHeight ); + void setVerticalAlign(css::style::VerticalAlignment _rAlign); + void setEnabled(bool _bEnabled); - void setLineColor( css::uno::Any const & i_color ); - void setHeaderBackgroundColor( css::uno::Any const & i_color ); - void setHeaderTextColor( css::uno::Any const & i_color ); - void setActiveSelectionBackColor( css::uno::Any const & i_color ); - void setInactiveSelectionBackColor( css::uno::Any const & i_color ); - void setActiveSelectionTextColor( css::uno::Any const & i_color ); - void setInactiveSelectionTextColor( css::uno::Any const & i_color ); - void setTextColor( css::uno::Any const & i_color ); - void setTextLineColor( css::uno::Any const & i_color ); - void setRowBackgroundColors( css::uno::Any const & i_APIValue ); + // multiplexing of XGridDataListener events + void notifyRowsInserted(css::awt::grid::GridDataEvent const& i_event) const; + void notifyRowsRemoved(css::awt::grid::GridDataEvent const& i_event) const; + void notifyDataChanged(css::awt::grid::GridDataEvent const& i_event) const; - void setVerticalAlign(css::style::VerticalAlignment _rAlign); - void setEnabled( bool _bEnabled ); + /// retrieves the index of a column within the model + sal_Int32 getColumnPos(UnoGridColumnFacade const& i_column) const; - // multiplexing of XGridDataListener events - void notifyRowsInserted( css::awt::grid::GridDataEvent const & i_event ) const; - void notifyRowsRemoved( css::awt::grid::GridDataEvent const & i_event ) const; - void notifyDataChanged( css::awt::grid::GridDataEvent const & i_event ) const; + /// notifies a change in a column belonging to the model + void notifyColumnChange(sal_Int32 const i_columnPos, + ColumnAttributeGroup const i_attributeGroup) const; - /// retrieves the index of a column within the model - ColPos getColumnPos( UnoGridColumnFacade const & i_column ) const; - - /// notifies a change in a column belonging to the model - void notifyColumnChange( ColPos const i_columnPos, ColumnAttributeGroup const i_attributeGroup ) const; - - /** notifies a change in all data represented by the model. To be used if you cannot specified the changed data + /** notifies a change in all data represented by the model. To be used if you cannot specified the changed data in more detail. */ - void notifyAllDataChanged() const; + void notifyAllDataChanged() const; - private: - void impl_notifyTableMetricsChanged() const; +private: + void impl_notifyTableMetricsChanged() const; - typedef ::std::vector< PTableModelListener > ModellListeners; - typedef ::std::vector< PColumnModel > ColumnModels; - - ColumnModels aColumns; - bool bHasColumnHeaders; - bool bHasRowHeaders; - ScrollbarVisibility eVScrollMode; - ScrollbarVisibility eHScrollMode; - PTableRenderer pRenderer; - PTableInputHandler pInputHandler; - TableMetrics nRowHeight; - TableMetrics nColumnHeaderHeight; - TableMetrics nRowHeaderWidth; - ::std::optional< ::Color > m_aGridLineColor; - ::std::optional< ::Color > m_aHeaderBackgroundColor; - ::std::optional< ::Color > m_aHeaderTextColor; - ::std::optional< ::Color > m_aActiveSelectionBackColor; - ::std::optional< ::Color > m_aInactiveSelectionBackColor; - ::std::optional< ::Color > m_aActiveSelectionTextColor; - ::std::optional< ::Color > m_aInactiveSelectionTextColor; - ::std::optional< ::Color > m_aTextColor; - ::std::optional< ::Color > m_aTextLineColor; - ::std::optional< ::std::vector< ::Color > > m_aRowColors; - css::style::VerticalAlignment m_eVerticalAlign; - bool bEnabled; - ModellListeners m_aListeners; - css::uno::WeakReference< css::awt::grid::XGridDataModel > m_aDataModel; - css::uno::WeakReference< css::awt::grid::XGridColumnModel > m_aColumnModel; - }; + typedef ::std::vector> ModellListeners; + typedef ::std::vector ColumnModels; + ColumnModels aColumns; + bool bHasColumnHeaders; + bool bHasRowHeaders; + ScrollbarVisibility eVScrollMode; + ScrollbarVisibility eHScrollMode; + PTableRenderer pRenderer; + PTableInputHandler pInputHandler; + TableMetrics nRowHeight; + TableMetrics nColumnHeaderHeight; + TableMetrics nRowHeaderWidth; + ::std::optional<::Color> m_aGridLineColor; + ::std::optional<::Color> m_aHeaderBackgroundColor; + ::std::optional<::Color> m_aHeaderTextColor; + ::std::optional<::Color> m_aActiveSelectionBackColor; + ::std::optional<::Color> m_aInactiveSelectionBackColor; + ::std::optional<::Color> m_aActiveSelectionTextColor; + ::std::optional<::Color> m_aInactiveSelectionTextColor; + ::std::optional<::Color> m_aTextColor; + ::std::optional<::Color> m_aTextLineColor; + ::std::optional<::std::vector<::Color>> m_aRowColors; + css::style::VerticalAlignment m_eVerticalAlign; + bool bEnabled; + ModellListeners m_aListeners; + css::uno::WeakReference m_aDataModel; + css::uno::WeakReference m_aColumnModel; +}; } // svt::table - - /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/fsys/urlobj.cxx b/tools/source/fsys/urlobj.cxx index e5da2b755003..9e86a212b2b5 100644 --- a/tools/source/fsys/urlobj.cxx +++ b/tools/source/fsys/urlobj.cxx @@ -4880,7 +4880,6 @@ bool INetURLObject::IsExoticProtocol() const || m_eScheme == INetProtocol::PrivSoffice || m_eScheme == INetProtocol::VndSunStarHelp || m_eScheme == INetProtocol::Https - || m_eScheme == INetProtocol::Javascript || m_eScheme == INetProtocol::Data || m_eScheme == INetProtocol::Cid || m_eScheme == INetProtocol::VndSunStarHier diff --git a/tools/source/stream/stream.cxx b/tools/source/stream/stream.cxx index bce41a48f18e..1dd98d58d077 100644 --- a/tools/source/stream/stream.cxx +++ b/tools/source/stream/stream.cxx @@ -436,6 +436,11 @@ bool SvStream::ReadLine( OStringBuffer& aBuf, sal_Int32 nMaxBytesToRead ) sal_uInt16 j, n; for( j = n = 0; j < nLen ; ++j ) { + if (nTotalLen + j >= o3tl::make_unsigned(nMaxBytesToRead)) + { + bEnd = true; + break; + } c = buf[j]; if ( c == '\n' || c == '\r' ) { @@ -447,12 +452,6 @@ bool SvStream::ReadLine( OStringBuffer& aBuf, sal_Int32 nMaxBytesToRead ) ++n; } nTotalLen += j; - if (nTotalLen > o3tl::make_unsigned(nMaxBytesToRead)) - { - n -= nTotalLen - nMaxBytesToRead; - nTotalLen = nMaxBytesToRead; - bEnd = true; - } if ( n ) aBuf.append(buf, n); } @@ -512,6 +511,11 @@ bool SvStream::ReadUniStringLine( OUString& rStr, sal_Int32 nMaxCodepointsToRead sal_uInt16 j, n; for( j = n = 0; j < nLen ; ++j ) { + if (nTotalLen + j >= o3tl::make_unsigned(nMaxCodepointsToRead)) + { + bEnd = true; + break; + } if (m_isSwap) SwapNumber( buf[n] ); c = buf[j]; @@ -532,12 +536,6 @@ bool SvStream::ReadUniStringLine( OUString& rStr, sal_Int32 nMaxCodepointsToRead } } nTotalLen += j; - if (nTotalLen > o3tl::make_unsigned(nMaxCodepointsToRead)) - { - n -= nTotalLen - nMaxCodepointsToRead; - nTotalLen = nMaxCodepointsToRead; - bEnd = true; - } if ( n ) aBuf.append( buf, n ); } diff --git a/vcl/inc/CoordinateMapper.hxx b/vcl/inc/CoordinateMapper.hxx index 4e75c6706547..9e5de8e8e270 100644 --- a/vcl/inc/CoordinateMapper.hxx +++ b/vcl/inc/CoordinateMapper.hxx @@ -209,6 +209,8 @@ public: // Note: This rounds the distance before stripping offsets to satisfy legacy test parity. tools::Long ViewSubPixelToLogicIntX(double fX) const; tools::Long ViewSubPixelToLogicIntY(double fY) const; + tools::Long ViewSubPixelToLogicIntX(double fX, const ImplMapRes& rRes) const; + tools::Long ViewSubPixelToLogicIntY(double fY, const ImplMapRes& rRes) const; double LogicToViewSubPixelX(double fX) const; double LogicToViewSubPixelY(double fY) const; diff --git a/vcl/inc/bitmaps.hlst b/vcl/inc/bitmaps.hlst index da7f30a3ee8b..1723a7cd45ea 100644 --- a/vcl/inc/bitmaps.hlst +++ b/vcl/inc/bitmaps.hlst @@ -227,4 +227,8 @@ inline constexpr OUString CHEVRON = u"sfx2/res/chevron.png"_ustr; inline constexpr OUString RID_UPDATE_AVAILABLE_16 = u"extensions/res/update/ui/onlineupdate_16.png"_ustr; inline constexpr OUString RID_UPDATE_AVAILABLE_26 = u"extensions/res/update/ui/onlineupdate_26.png"_ustr; +inline constexpr OUString RID_MSGTYPE_INFO = u"vcl/res/information.png"_ustr; +inline constexpr OUString RID_MSGTYPE_WARN = u"vcl/res/warning.png"_ustr; +inline constexpr OUString RID_MSGTYPE_ERR = u"vcl/res/error.png"_ustr; + /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/vcl/inc/colorpicker.hxx b/vcl/inc/colorpicker.hxx index 83992ac967f2..8f9cdb40cfdb 100644 --- a/vcl/inc/colorpicker.hxx +++ b/vcl/inc/colorpicker.hxx @@ -95,7 +95,7 @@ public: void Modify(); void SetValues(sal_uInt16 nBaseValue, ColorMode eMode, double x, double y); - double GetX() const { return mdX; } + double GetX() const; double GetY() const { return mdY; } void SetModifyHdl(const Link& rLink) { maModifyHdl = rLink; } diff --git a/vcl/inc/iconview.hxx b/vcl/inc/iconview.hxx index 618d8d9b63fd..a72548b876a2 100644 --- a/vcl/inc/iconview.hxx +++ b/vcl/inc/iconview.hxx @@ -28,6 +28,9 @@ private: short m_nFixedColumnCount = -1; short m_nColumnCount; + // number of pixels from top, from bottom, between icon and label + int m_nSpacing = 5; + public: IconView(vcl::Window* pParent, WinBits nBits); @@ -35,6 +38,8 @@ public: short GetColumnCount() const { return m_nColumnCount; } void SetFixedColumnCount(short nColumnCount); + void SetSpacing(int nSpacing) { m_nSpacing = nSpacing; } + virtual Size GetOptimalSize() const override; virtual void Resize() override; diff --git a/vcl/osx/DataFlavorMapping.hxx b/vcl/inc/osx/DataFlavorMapping.hxx similarity index 100% rename from vcl/osx/DataFlavorMapping.hxx rename to vcl/inc/osx/DataFlavorMapping.hxx diff --git a/vcl/osx/clipboard.hxx b/vcl/inc/osx/clipboard.hxx similarity index 98% rename from vcl/osx/clipboard.hxx rename to vcl/inc/osx/clipboard.hxx index 4dfa4929f55c..e0a7600825a7 100644 --- a/vcl/osx/clipboard.hxx +++ b/vcl/inc/osx/clipboard.hxx @@ -127,6 +127,8 @@ public: void applicationDidBecomeActive(NSNotification* aNotification); + void setContinuityPasteboard(NSPasteboard *pboard); + private: /* Notify all registered XClipboardListener that the clipboard content @@ -142,6 +144,7 @@ private: DataFlavorMapperPtr_t mpDataFlavorMapper; bool mIsSystemPasteboard; NSPasteboard* mPasteboard; + NSPasteboard* mContinuityPasteboard; int mPasteboardChangeCount; EventListener* mEventListener; }; diff --git a/vcl/inc/osx/salframeview.h b/vcl/inc/osx/salframeview.h index 35b151de3b27..b698da7d5562 100644 --- a/vcl/inc/osx/salframeview.h +++ b/vcl/inc/osx/salframeview.h @@ -86,7 +86,7 @@ enum class SalEvent; @end -@interface SalFrameView : NSView +@interface SalFrameView : NSView { AquaSalFrame* mpFrame; AquaA11yWrapper* mpChildWrapper; @@ -120,6 +120,9 @@ enum class SalEvent; NSTimer* mpMouseDraggedTimer; NSEvent* mpPendingMouseDraggedEvent; } +-(BOOL)readSelectionFromPasteboard: (NSPasteboard *)pboard; +-(BOOL)writeSelectionToPasteboard: (NSPasteboard *)pboard types: (NSArray *)types; +-(id)validRequestorForSendType: (NSPasteboardType)sendType returnType: (NSPasteboardType)returnType; +(void)unsetMouseFrame: (AquaSalFrame*)pFrame; -(id)initWithSalFrame: (AquaSalFrame*)pFrame; -(void)clearMouseDraggedTimer; diff --git a/vcl/inc/osx/salinst.h b/vcl/inc/osx/salinst.h index b8afc38fd210..d0b6db3f5d92 100644 --- a/vcl/inc/osx/salinst.h +++ b/vcl/inc/osx/salinst.h @@ -32,6 +32,7 @@ #include #include "MacInstance.hxx" +#include "clipboard.hxx" class AquaSalFrame; class SalFrame; @@ -62,6 +63,7 @@ public: osl::Mutex maUserEventListMutex; osl::Condition maWaitingYieldCond; bool mbTimerProcessed; + AquaClipboard* mpAquaClipboard; static std::list aAppEventList; diff --git a/vcl/inc/pdf/pdfwriter_impl.hxx b/vcl/inc/pdf/pdfwriter_impl.hxx index 30c748836747..a4ce2a820c68 100644 --- a/vcl/inc/pdf/pdfwriter_impl.hxx +++ b/vcl/inc/pdf/pdfwriter_impl.hxx @@ -386,6 +386,8 @@ struct PDFDest sal_Int32 m_nPage; PDFWriter::DestAreaType m_eType; tools::Rectangle m_aRect; + /// index into m_aStructure, -1 when the target is not tagged + sal_Int32 m_nStructElement = -1; }; //--->i56629 @@ -602,6 +604,10 @@ struct PDFStructureElement std::map m_aAttributes; ::std::vector m_AnnotIds; + // what the content in this element refers to, as indexes into the structure vector + std::vector m_RefElements; + // a Link has one annotation per line, and per fly gap + std::vector m_LinkAnnotIds; tools::Rectangle m_aBBox; OUString m_aActualText; OUString m_aAltText; @@ -788,8 +794,12 @@ private: */ bool m_bEmitStructure; /* role map of struct tree root */ - std::unordered_map< OString, OString > - m_aRoleMap; + struct RoleMapEntry + { + OString m_aTag; + OString m_aAsked; ///< the alias it was claimed for, which may differ + }; + std::unordered_map m_aRoleMap; /* structure elements (object ids) that should have ID */ std::unordered_set m_StructElemObjsWithID; @@ -884,7 +894,11 @@ private: /* the buffer where the data are encrypted, dynamically allocated */ std::vector m_vEncryptionBuffer; - void addRoleMap(const OString& aAlias, vcl::pdf::StructElement eType); + /// Whether the name is a standard structure type's at this PDF version. + [[nodiscard]] bool isStandardStructureName(std::string_view aName); + /// The name the element carries: the alias, or the first indexed name beside it that + /// no standard type and no other alias has taken. + [[nodiscard]] OString claimRoleName(const OString& rAlias, vcl::pdf::StructElement eType); void checkAndEnableStreamEncryption( sal_Int32 nObject ) override; @@ -898,6 +912,9 @@ private: void enableStringEncryption( sal_Int32 nObject ); private: + sal_Int32 convertStructureAttribute(enum pdf::PDFWriter::StructAttribute eAttr, + sal_Int32 nValue); + /* creates fonts and subsets that will be emitted later */ void registerGlyph(const sal_GlyphId, const vcl::font::PhysicalFontFace*, const LogicalFontInstance* pFont, const std::vector&, sal_Int32, sal_uInt8&, sal_Int32&); void registerSimpleGlyph(const sal_GlyphId, const vcl::font::PhysicalFontFace*, const LogicalFontInstance*, const std::vector&, sal_Int32, sal_uInt8&, sal_Int32&); @@ -993,6 +1010,10 @@ private: sal_Int32 emitResources(); // appends a dest bool appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer ); + /// ISO 14289-2 requires a GoTo action to name the structure element, not only the page + bool appendStructureDest(sal_Int32 nDestID, OStringBuffer& rBuffer); + /// writes /Dest, or the GoTo action carrying /SD when the target is tagged + void appendDestOrGoTo(sal_Int32 nDestID, OStringBuffer& rBuffer); // write all links bool emitLinkAnnotations(); // Write all screen annotations. @@ -1025,7 +1046,7 @@ private: // the maximum array elements allowed for PDF array object static const sal_uInt32 ncMaxPDFArraySize = 8191; //check if internal dummy container are needed in the structure elements - void addInternalStructureContainer( PDFStructureElement& rEle ); + void addInternalStructureContainer(sal_Int32 nEle); //<---i94258 // writes namespaces void emitNamespaces(); @@ -1312,6 +1333,8 @@ public: sal_Int32 createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ); sal_Int32 registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ); void setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId ); + void setDestStructureElement(sal_Int32 nDestId, sal_Int32 nStructElementId); + void addStructureRef(sal_Int32 nElementId, sal_Int32 nRefElementId); void setLinkURL( sal_Int32 nLinkId, const OUString& rURL ); void setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId ); diff --git a/vcl/inc/printdlg.hxx b/vcl/inc/printdlg.hxx index 3787f5fb4c45..3db74784fc2b 100644 --- a/vcl/inc/printdlg.hxx +++ b/vcl/inc/printdlg.hxx @@ -190,6 +190,7 @@ namespace vcl OUString maPageStr; OUString maNoPageStr; OUString maNoPreviewStr; + sal_Int32 mnInitialPage; sal_Int32 mnCurPage; sal_Int32 mnCachedPages; diff --git a/vcl/osx/DataFlavorMapping.cxx b/vcl/osx/DataFlavorMapping.cxx index 5cb8727a0ea8..443eabbec234 100644 --- a/vcl/osx/DataFlavorMapping.cxx +++ b/vcl/osx/DataFlavorMapping.cxx @@ -20,7 +20,7 @@ #include #include -#include "DataFlavorMapping.hxx" +#include #include "HtmlFmtFlt.hxx" #include "PictToBmpFlt.hxx" #include diff --git a/vcl/osx/DragSource.cxx b/vcl/osx/DragSource.cxx index 0bc6eaca8681..7b1805ea2463 100644 --- a/vcl/osx/DragSource.cxx +++ b/vcl/osx/DragSource.cxx @@ -27,7 +27,7 @@ #include "DragSource.hxx" #include "DragSourceContext.hxx" -#include "clipboard.hxx" +#include #include "DragActionConversion.hxx" #include diff --git a/vcl/osx/DropTarget.cxx b/vcl/osx/DropTarget.cxx index c254c56ebf72..51088bbba791 100644 --- a/vcl/osx/DropTarget.cxx +++ b/vcl/osx/DropTarget.cxx @@ -21,7 +21,7 @@ #include #include #include -#include "clipboard.hxx" +#include #include "DropTarget.hxx" #include "DragActionConversion.hxx" #include "DragSource.hxx" diff --git a/vcl/osx/DropTarget.hxx b/vcl/osx/DropTarget.hxx index ffc53a4e17ec..3f0c20b958fd 100644 --- a/vcl/osx/DropTarget.hxx +++ b/vcl/osx/DropTarget.hxx @@ -19,7 +19,7 @@ #pragma once -#include "DataFlavorMapping.hxx" +#include #include #include #include diff --git a/vcl/osx/OSXTransferable.cxx b/vcl/osx/OSXTransferable.cxx index ed3aa69e7835..ab7fab52571c 100644 --- a/vcl/osx/OSXTransferable.cxx +++ b/vcl/osx/OSXTransferable.cxx @@ -29,7 +29,7 @@ #include "OSXTransferable.hxx" -#include "DataFlavorMapping.hxx" +#include #include diff --git a/vcl/osx/OSXTransferable.hxx b/vcl/osx/OSXTransferable.hxx index 9725a598ecf7..7a810f11635b 100644 --- a/vcl/osx/OSXTransferable.hxx +++ b/vcl/osx/OSXTransferable.hxx @@ -24,7 +24,7 @@ #include #include -#include "DataFlavorMapping.hxx" +#include #include #import diff --git a/vcl/osx/clipboard.cxx b/vcl/osx/clipboard.cxx index 9437dab7eb26..629378c48035 100644 --- a/vcl/osx/clipboard.cxx +++ b/vcl/osx/clipboard.cxx @@ -17,9 +17,9 @@ * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ -#include "clipboard.hxx" +#include -#include "DataFlavorMapping.hxx" +#include #include "OSXTransferable.hxx" #include #include @@ -134,12 +134,22 @@ AquaClipboard::~AquaClipboard() [mEventListener disposing]; [mEventListener release]; [mPasteboard release]; + if (mContinuityPasteboard) + [mContinuityPasteboard release]; } uno::Reference SAL_CALL AquaClipboard::getContents() { osl::MutexGuard aGuard(m_aMutex); + if (mContinuityPasteboard) + { + return uno::Reference( + new OSXTransferable(mrXMimeCntFactory, + mpDataFlavorMapper, + mContinuityPasteboard)); + } + // tdf#144124 Detect if ownership has been lost // The shortcut assumes that lost ownership notifications from the // system clipboard will happen elsewhere. They do under normal @@ -249,6 +259,8 @@ void AquaClipboard::applicationDidBecomeActive(NSNotification*) fireLostClipboardOwnershipEvent(oldOwner, oldContent); } + setContinuityPasteboard(nil); + fireClipboardChangedEvent(); } } @@ -332,6 +344,22 @@ void SAL_CALL AquaClipboard::flushClipboard() } mXClipboardContent.clear(); } + setContinuityPasteboard(nil); +} + +void AquaClipboard::setContinuityPasteboard(NSPasteboard *pBoard) +{ + if (mContinuityPasteboard) + { + [mContinuityPasteboard release]; + mContinuityPasteboard = nil; + } + + if (pBoard) + { + mContinuityPasteboard = [pBoard retain]; + fireClipboardChangedEvent(); + } } NSPasteboard* AquaClipboard::getPasteboard() const diff --git a/vcl/osx/salframeview.mm b/vcl/osx/salframeview.mm index 67af526dde66..a37c569fc651 100644 --- a/vcl/osx/salframeview.mm +++ b/vcl/osx/salframeview.mm @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -1108,6 +1109,44 @@ static NSString* getCurrentSelection() @end @implementation SalFrameView + +-(id)validRequestorForSendType: (NSPasteboardType)sendType + returnType: (NSPasteboardType)returnType +{ + // For Continuity we only accept clipboard content; never return it + if (sendType == nil && returnType != nil) + return self; + return [super validRequestorForSendType:sendType returnType:returnType]; +} + +-(BOOL)readSelectionFromPasteboard: (NSPasteboard *)pboard +{ + // Continuity Sketch, Scan Documents, and Take Photo operations + // end up here with a picture on the clipboard + if (![NSImage canInitWithPasteboard:pboard]) + return NO; + + // The pasteboard's data is not retained after this method + // returns. We must copy the image and create a new pasteboard + // that we control, because the LO paste operation happens + // asynchronously. + NSImage *image = [[NSImage alloc] initWithPasteboard:pboard]; + NSPasteboard *pCopy = [NSPasteboard pasteboardWithUniqueName]; + [pCopy writeObjects:[NSArray arrayWithObject:image]]; + GetAquaSalInstance()->mpAquaClipboard->setContinuityPasteboard(pCopy); + [pCopy release]; + [image release]; + + KeyEvent aEvent(0, KEY_PASTE, 0); + mpFrame->CallCallback( SalEvent::ExternalKeyInput, &aEvent ); + return YES; +} + +-(BOOL)writeSelectionToPasteboard: (NSPasteboard *)pboard types: (NSArray *)types +{ + return FALSE; +} + +(void)unsetMouseFrame: (AquaSalFrame*)pFrame { if( pFrame == s_pMouseFrame ) diff --git a/vcl/osx/salinst.cxx b/vcl/osx/salinst.cxx index 1280535bf127..d4bfbc5834c5 100644 --- a/vcl/osx/salinst.cxx +++ b/vcl/osx/salinst.cxx @@ -218,6 +218,9 @@ VCLPLUG_OSX_PUBLIC SalInstance* create_SalInstance() // activate our delegate methods [NSApp setDelegate: NSApp]; + if (Application::IsHeadlessModeEnabled()) + [NSApp setActivationPolicy: NSApplicationActivationPolicyProhibited]; + AquaSalInstance* pInst = new AquaSalInstance; // this one is for outside AquaSalInstance::Yield diff --git a/vcl/osx/salmenu.cxx b/vcl/osx/salmenu.cxx index 59e8b0012cf0..3939b154b11b 100644 --- a/vcl/osx/salmenu.cxx +++ b/vcl/osx/salmenu.cxx @@ -300,9 +300,6 @@ AquaSalMenu::~AquaSalMenu() bool AquaSalMenu::ShowNativePopupMenu(FloatingWindow * pWin, const tools::Rectangle& rRect, FloatWinPopupFlags nFlags) { - // set offsets for positioning - const float offset = 9.0; - // get the pointers AquaSalFrame * pParentAquaSalFrame = static_cast(pWin->ImplGetWindowImpl()->mpRealParent->ImplGetFrame()); NSWindow* pParentNSWindow = pParentAquaSalFrame->mpNSWindow; @@ -311,28 +308,35 @@ bool AquaSalMenu::ShowNativePopupMenu(FloatingWindow * pWin, const tools::Rectan NSRect popupFrame = [pPopupNSView frame]; // create frame rect - NSRect displayPopupFrame = NSMakeRect( rRect.Left()+(offset-1), rRect.Top()+(offset+1), popupFrame.size.width, 0 ); + NSRect displayPopupFrame = NSMakeRect( rRect.Left(), rRect.Top(), popupFrame.size.width, 0 ); pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false); // do the same strange semantics as vcl popup windows to arrive at a frame geometry // in mirrored UI case; best done by actually executing the same code sal_uInt16 nArrangeIndex; pWin->SetPosPixel( FloatingWindow::ImplCalcPos( pWin, rRect, nFlags, nArrangeIndex ) ); - displayPopupFrame.origin.x = pWin->ImplGetFrame()->GetUnmirroredGeometry().x() - pParentAquaSalFrame->GetUnmirroredGeometry().x() + offset; - displayPopupFrame.origin.y = pWin->ImplGetFrame()->GetUnmirroredGeometry().y() - pParentAquaSalFrame->GetUnmirroredGeometry().y() + offset; + displayPopupFrame.origin.x = pWin->ImplGetFrame()->GetUnmirroredGeometry().x() - pParentAquaSalFrame->GetUnmirroredGeometry().x(); + displayPopupFrame.origin.y = pWin->ImplGetFrame()->GetUnmirroredGeometry().y() - pParentAquaSalFrame->GetUnmirroredGeometry().y(); pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false); // #i111992# if this menu was opened due to a key event, prevent dispatching that yet again if( [pParentNSView respondsToSelector: @selector(clearLastEvent)] ) [pParentNSView performSelector:@selector(clearLastEvent)]; - // open popup menu - NSPopUpButtonCell * pPopUpButtonCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO]; - [pPopUpButtonCell setMenu: mpMenu]; - [pPopUpButtonCell selectItem:nil]; + NSEvent *fakeMouseEvent = [NSEvent + mouseEventWithType:NSEventTypeRightMouseDown + location:displayPopupFrame.origin + modifierFlags:0 + timestamp:0 + windowNumber:[pParentNSWindow windowNumber] + context:nil + eventNumber:0 + clickCount:1 + pressure:1.0 + ]; + [AquaA11yWrapper setPopupMenuOpen: YES]; - [pPopUpButtonCell performClickWithFrame:displayPopupFrame inView:pParentNSView]; - [pPopUpButtonCell release]; + [NSMenu popUpContextMenu:mpMenu withEvent:fakeMouseEvent forView:pParentNSView]; [AquaA11yWrapper setPopupMenuOpen: NO]; return true; diff --git a/vcl/osx/service_entry.cxx b/vcl/osx/service_entry.cxx index 1c4d5ea44675..c17c8c0fddf1 100644 --- a/vcl/osx/service_entry.cxx +++ b/vcl/osx/service_entry.cxx @@ -29,7 +29,7 @@ #include "DragSource.hxx" #include "DropTarget.hxx" -#include "clipboard.hxx" +#include using namespace ::osl; using namespace ::com::sun::star; @@ -46,7 +46,10 @@ AquaSalInstance::CreateClipboard(ClipboardSelectionType eSelection) SalData* pSalData = GetSalData(); if( ! pSalData->mxClipboard.is() ) - pSalData->mxClipboard = new AquaClipboard(nullptr, true); + { + GetAquaSalInstance()->mpAquaClipboard = new AquaClipboard(nullptr, true);; + pSalData->mxClipboard = GetAquaSalInstance()->mpAquaClipboard; + } return pSalData->mxClipboard; } diff --git a/vcl/osx/vclnsapp.mm b/vcl/osx/vclnsapp.mm index da90e60449b1..c1d2706bf9f6 100644 --- a/vcl/osx/vclnsapp.mm +++ b/vcl/osx/vclnsapp.mm @@ -63,6 +63,9 @@ { (void)pNotification; + NSArray *emptyArray = @[]; + [NSApp registerServicesMenuSendTypes:emptyArray returnTypes:NSImage.imageTypes]; + NSEvent* pEvent = [NSEvent otherEventWithType: NSEventTypeApplicationDefined location: NSZeroPoint modifierFlags: 0 diff --git a/vcl/qa/cppunit/PDFDocumentTest.cxx b/vcl/qa/cppunit/PDFDocumentTest.cxx index 1b9e78cebe38..10d3470e1409 100644 --- a/vcl/qa/cppunit/PDFDocumentTest.cxx +++ b/vcl/qa/cppunit/PDFDocumentTest.cxx @@ -24,6 +24,44 @@ public: constexpr OUString DATA_DIRECTORY = u"/vcl/qa/cppunit/data/"_ustr; +CPPUNIT_TEST_FIXTURE(PDFDocumentTest, testXRefStreamWithoutPredictor) +{ + // /Predictor defaults to 1, so a cross-reference stream may carry no /DecodeParms at all. + // The content stream's /Length is indirect, which is what has to be resolved through it. + { + OUString aURL + = m_directories.getURLFromSrc(DATA_DIRECTORY) + "xref-stream-no-predictor.pdf"; + vcl::filter::PDFDocument aDocument; + SvFileStream aStream(aURL, StreamMode::READ); + CPPUNIT_ASSERT(aDocument.Read(aStream)); + + std::vector aPages = aDocument.GetPages(); + CPPUNIT_ASSERT_EQUAL(size_t(1), aPages.size()); + + vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr); + CPPUNIT_ASSERT(pContents); + CPPUNIT_ASSERT(pContents->GetStream()); + CPPUNIT_ASSERT_EQUAL(sal_uInt64(28), pContents->GetStream()->GetMemory().GetSize()); + } + + // /Filter is optional too, and then the entries stand in the stream as they are + { + OUString aURL + = m_directories.getURLFromSrc(DATA_DIRECTORY) + "xref-stream-uncompressed.pdf"; + vcl::filter::PDFDocument aDocument; + SvFileStream aStream(aURL, StreamMode::READ); + CPPUNIT_ASSERT(aDocument.Read(aStream)); + + std::vector aPages = aDocument.GetPages(); + CPPUNIT_ASSERT_EQUAL(size_t(1), aPages.size()); + + vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr); + CPPUNIT_ASSERT(pContents); + CPPUNIT_ASSERT(pContents->GetStream()); + CPPUNIT_ASSERT_EQUAL(sal_uInt64(28), pContents->GetStream()->GetMemory().GetSize()); + } +} + CPPUNIT_TEST_FIXTURE(PDFDocumentTest, testParseBasicPDF) { OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "basic.pdf"; diff --git a/vcl/qa/cppunit/data/xref-stream-no-predictor.pdf b/vcl/qa/cppunit/data/xref-stream-no-predictor.pdf new file mode 100644 index 000000000000..3220a65f2fb0 Binary files /dev/null and b/vcl/qa/cppunit/data/xref-stream-no-predictor.pdf differ diff --git a/vcl/qa/cppunit/data/xref-stream-uncompressed.pdf b/vcl/qa/cppunit/data/xref-stream-uncompressed.pdf new file mode 100644 index 000000000000..6d6ce9d00811 Binary files /dev/null and b/vcl/qa/cppunit/data/xref-stream-uncompressed.pdf differ diff --git a/vcl/qa/cppunit/pdfexport/data/LinkMultiLine.fodt b/vcl/qa/cppunit/pdfexport/data/LinkMultiLine.fodt new file mode 100644 index 000000000000..9847c9d32e4a --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/LinkMultiLine.fodt @@ -0,0 +1,29 @@ + + + Link on several lines + + + + + + + + + + + + + + + + + + + + + + + This is a hyperlink whose text is deliberately long enough that the Writer layout has to break it onto a second line of the same paragraph, which used to produce one Link structure element per line. + + + diff --git a/vcl/qa/cppunit/pdfexport/data/LinkSpanWithFly.fodt b/vcl/qa/cppunit/pdfexport/data/LinkSpanWithFly.fodt new file mode 100644 index 000000000000..2c1141bb564e --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/LinkSpanWithFly.fodt @@ -0,0 +1,32 @@ + + + Link with a span and an as-character frame + + + + + + + + + + + + + + + + + + + + + + + + + + plain before deutsch inner weiter after + + + diff --git a/vcl/qa/cppunit/pdfexport/data/LinkSpanWraps.fodt b/vcl/qa/cppunit/pdfexport/data/LinkSpanWraps.fodt new file mode 100644 index 000000000000..2b86022e724e --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/LinkSpanWraps.fodt @@ -0,0 +1,32 @@ + + + Link with a wrapped language change + + + + + + + + + + + + + + + + + + + + + + + + + + This hyperlink starts in the document language and then continues in German for long enough that the run itself has to break onto a second line, and the link ends with it + + + diff --git a/vcl/qa/cppunit/pdfexport/data/LinkWithRuby.fodt b/vcl/qa/cppunit/pdfexport/data/LinkWithRuby.fodt new file mode 100644 index 000000000000..cde03ea0aac9 --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/LinkWithRuby.fodt @@ -0,0 +1,32 @@ + + + Link with a ruby + + + + + + + + + + + + + + + + + + + + + + + + + + before baseruby after + + + diff --git a/vcl/qa/cppunit/pdfexport/data/LinkWithSpan.fodt b/vcl/qa/cppunit/pdfexport/data/LinkWithSpan.fodt new file mode 100644 index 000000000000..113d8837e252 --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/LinkWithSpan.fodt @@ -0,0 +1,32 @@ + + + Link with a language change + + + + + + + + + + + + + + + + + + + + + + + + + + before deutsch after + + + diff --git a/vcl/qa/cppunit/pdfexport/data/RubyWithSpan.fodt b/vcl/qa/cppunit/pdfexport/data/RubyWithSpan.fodt new file mode 100644 index 000000000000..8cf5ee6971e0 --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/RubyWithSpan.fodt @@ -0,0 +1,32 @@ + + + Ruby with a character style + + + + + + + + + + + + + + + + + + + + + + + + + + plain baseruby after + + + diff --git a/vcl/qa/cppunit/pdfexport/data/SpanWithFly.fodt b/vcl/qa/cppunit/pdfexport/data/SpanWithFly.fodt new file mode 100644 index 000000000000..7e3ce8af19e0 --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/SpanWithFly.fodt @@ -0,0 +1,32 @@ + + + Span with an as-character frame + + + + + + + + + + + + + + + + + + + + + + + + + + plain deutsch inner weiter after + + + diff --git a/vcl/qa/cppunit/pdfexport/data/StructureNamespaces.fodt b/vcl/qa/cppunit/pdfexport/data/StructureNamespaces.fodt new file mode 100644 index 000000000000..c76c235dfa0c --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/StructureNamespaces.fodt @@ -0,0 +1,69 @@ + + + A table of contents + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Table of Contents + First heading1 + Second heading1 + + + First heading + Body under the first heading.1The footnote body. + A quoted paragraph. + A run in quotation style, a run in source text style, and a citation [Ref1]. + Second heading + Body under the second heading, with a term. + + + + Index + a term1 + + + + + diff --git a/vcl/qa/cppunit/pdfexport/data/TableHeadingDerived.fodt b/vcl/qa/cppunit/pdfexport/data/TableHeadingDerived.fodt new file mode 100644 index 000000000000..68d0fc923000 --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/TableHeadingDerived.fodt @@ -0,0 +1,41 @@ + + + Table heading style and one derived from it + + + + + + + + + + + + + + + + + + + + + + + + + + + + head + one + + + derived + two + + + + + diff --git a/vcl/qa/cppunit/pdfexport/data/master-background-artifact.fodp b/vcl/qa/cppunit/pdfexport/data/master-background-artifact.fodp new file mode 100644 index 000000000000..41ecdb4d2de7 --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/master-background-artifact.fodp @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Slide text + + + + + diff --git a/vcl/qa/cppunit/pdfexport/data/style-named-tags.fodt b/vcl/qa/cppunit/pdfexport/data/style-named-tags.fodt new file mode 100644 index 000000000000..72dbbf68f75f --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/style-named-tags.fodt @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A cell + + + Another cell + + + + A paragraph in the Code paragraph style. + A paragraph in the Title style. + A paragraph in the built-in Index style. + A paragraph in a style really called Code1. + A paragraph in a style called H. + A paragraph in a style called THead. + A paragraph in a style called H7. + A paragraph in a style called Sub. + A paragraph in a style called Artifact. + A paragraph in a style called Code-1. + A heading at level 7. + Body text with a run in the Code character style and a run in Source Text and a run in the character style of that name. + + + diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx index 047385af1762..2306cffbad57 100644 --- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx +++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx @@ -9,6 +9,7 @@ #include +#include #include #include @@ -2598,7 +2599,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD22->GetValue()); { auto pKids = dynamic_cast(pObjectD22->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -2635,7 +2636,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(95.143, pNumL->GetValue(), 1e-3); @@ -2665,7 +2666,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD23->GetValue()); { auto pKids = dynamic_cast(pObjectD23->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -2702,7 +2703,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3); @@ -2731,7 +2732,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD24->GetValue()); { auto pKids = dynamic_cast(pObjectD24->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -2768,7 +2769,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(146.043, pNumL->GetValue(), 1e-3); @@ -2797,7 +2798,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD25->GetValue()); { auto pKids = dynamic_cast(pObjectD25->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -2834,7 +2835,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3); @@ -2863,7 +2864,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD26->GetValue()); { auto pKids = dynamic_cast(pObjectD26->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -2900,7 +2901,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3); @@ -3074,6 +3075,633 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testIndexMarkWithSpan) CPPUNIT_ASSERT_EQUAL("de-DE"_ostr, pSpanLang->GetValue()); } +CPPUNIT_TEST_FIXTURE(PdfExportTest, testLinkWithRuby) +{ + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"PDFUACompliance"_ustr, true) }; + + vcl::filter::PDFDocument aDocument; + loadFromFile(u"LinkWithRuby.fodt"); + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + // Parse the export result. + SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); + CPPUNIT_ASSERT(aDocument.Read(aStream)); + + std::vector aPages = aDocument.GetPages(); + CPPUNIT_ASSERT_EQUAL(size_t(1), aPages.size()); + + // the ruby is content of the link, so the link is one annotation and one SE + auto pAnnots = dynamic_cast(aPages[0]->Lookup("Annots"_ostr)); + CPPUNIT_ASSERT(pAnnots); + CPPUNIT_ASSERT_EQUAL(size_t(1), pAnnots->GetElements().size()); + auto pAnnotRef = dynamic_cast(pAnnots->GetElements()[0]); + CPPUNIT_ASSERT(pAnnotRef); + auto pAnnot = pAnnotRef->LookupObject(); + CPPUNIT_ASSERT(pAnnot); + + vcl::filter::PDFObjectElement* pLinkSE(nullptr); + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pType = dynamic_cast(pObject->Lookup("Type"_ostr)); + auto pS = dynamic_cast(pObject->Lookup("S"_ostr)); + if (pType && pType->GetValue() == "StructElem" && pS && pS->GetValue() == "Link") + { + CPPUNIT_ASSERT_MESSAGE("a ruby must not split the link", !pLinkSE); + pLinkSE = pObject; + } + } + CPPUNIT_ASSERT(pLinkSE); + + // the text before and after the ruby, the ruby itself, and the annotation + auto pKids = dynamic_cast(pLinkSE->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pKids); + size_t nMCID(0); + size_t nMCIDBeforeRuby(0); + vcl::filter::PDFObjectElement* pRubySE(nullptr); + size_t nOBJR(0); + for (const auto pElement : pKids->GetElements()) + { + if (dynamic_cast(pElement)) + { + ++nMCID; + continue; + } + if (auto pRef = dynamic_cast(pElement)) + { + auto pKid = pRef->LookupObject(); + CPPUNIT_ASSERT(pKid); + auto pKidS = dynamic_cast(pKid->Lookup("S"_ostr)); + CPPUNIT_ASSERT(pKidS); + CPPUNIT_ASSERT_EQUAL("Ruby"_ostr, pKidS->GetValue()); + CPPUNIT_ASSERT(!pRubySE); + pRubySE = pKid; + nMCIDBeforeRuby = nMCID; + continue; + } + auto pObjR = dynamic_cast(pElement); + CPPUNIT_ASSERT(pObjR); + auto pOType = dynamic_cast(pObjR->LookupElement("Type"_ostr)); + CPPUNIT_ASSERT(pOType); + CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); + auto pRef + = dynamic_cast(pObjR->LookupElement("Obj"_ostr)); + CPPUNIT_ASSERT(pRef); + CPPUNIT_ASSERT_EQUAL(pAnnot, pRef->LookupObject()); + ++nOBJR; + } + CPPUNIT_ASSERT_EQUAL(size_t(2), nMCID); + CPPUNIT_ASSERT_EQUAL(size_t(1), nOBJR); + CPPUNIT_ASSERT(pRubySE); + // the ruby reads between the text before it and the text after it + CPPUNIT_ASSERT_EQUAL(size_t(1), nMCIDBeforeRuby); + + // the base and the ruby text, each with its own content; RB before RT would + // be the reading order, but the paint order puts RT first + auto pRubyKids = dynamic_cast(pRubySE->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pRubyKids); + CPPUNIT_ASSERT_EQUAL(size_t(2), pRubyKids->GetElements().size()); + std::vector aRubyKids; + for (const auto pElement : pRubyKids->GetElements()) + { + auto pRef = dynamic_cast(pElement); + CPPUNIT_ASSERT(pRef); + auto pKid = pRef->LookupObject(); + CPPUNIT_ASSERT(pKid); + auto pKidS = dynamic_cast(pKid->Lookup("S"_ostr)); + CPPUNIT_ASSERT(pKidS); + aRubyKids.push_back(pKidS->GetValue()); + auto pKidKids = dynamic_cast(pKid->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pKidKids); + CPPUNIT_ASSERT_EQUAL(size_t(1), pKidKids->GetElements().size()); + CPPUNIT_ASSERT(dynamic_cast(pKidKids->GetElement(0))); + } + CPPUNIT_ASSERT(std::find(aRubyKids.begin(), aRubyKids.end(), "RB"_ostr) != aRubyKids.end()); + CPPUNIT_ASSERT(std::find(aRubyKids.begin(), aRubyKids.end(), "RT"_ostr) != aRubyKids.end()); +} + +CPPUNIT_TEST_FIXTURE(PdfExportTest, testLinkWithSpan) +{ + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"PDFUACompliance"_ustr, true) }; + + vcl::filter::PDFDocument aDocument; + loadFromFile(u"LinkWithSpan.fodt"); + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); + CPPUNIT_ASSERT(aDocument.Read(aStream)); + + vcl::filter::PDFObjectElement* pLinkSE(nullptr); + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pType = dynamic_cast(pObject->Lookup("Type"_ostr)); + auto pS = dynamic_cast(pObject->Lookup("S"_ostr)); + if (pType && pType->GetValue() == "StructElem" && pS && pS->GetValue() == "Link") + { + CPPUNIT_ASSERT_MESSAGE("a language change must not split the link", !pLinkSE); + pLinkSE = pObject; + } + } + CPPUNIT_ASSERT(pLinkSE); + + // the link keeps the language of its first portion, so the German must not be on it + auto pLinkLang + = dynamic_cast(pLinkSE->Lookup("Lang"_ostr)); + if (pLinkLang) + { + CPPUNIT_ASSERT(pLinkLang->GetValue() != "de-DE"_ostr); + } + + // the run that differs is a Span inside it, not a second Link + auto pKids = dynamic_cast(pLinkSE->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pKids); + size_t nMCID(0); + size_t nMCIDBeforeSpan(0); + vcl::filter::PDFObjectElement* pSpanSE(nullptr); + for (const auto pElement : pKids->GetElements()) + { + if (dynamic_cast(pElement)) + { + ++nMCID; + continue; + } + auto pRef = dynamic_cast(pElement); + if (!pRef) + continue; // the OBJR for the annotation + auto pKid = pRef->LookupObject(); + CPPUNIT_ASSERT(pKid); + auto pKidS = dynamic_cast(pKid->Lookup("S"_ostr)); + CPPUNIT_ASSERT(pKidS); + CPPUNIT_ASSERT_EQUAL("Span"_ostr, pKidS->GetValue()); + CPPUNIT_ASSERT(!pSpanSE); + pSpanSE = pKid; + nMCIDBeforeSpan = nMCID; + } + CPPUNIT_ASSERT(pSpanSE); + CPPUNIT_ASSERT_EQUAL(size_t(2), nMCID); + // it reads between the text before it and the text after it + CPPUNIT_ASSERT_EQUAL(size_t(1), nMCIDBeforeSpan); + + auto pSpanLang + = dynamic_cast(pSpanSE->Lookup("Lang"_ostr)); + CPPUNIT_ASSERT(pSpanLang); + CPPUNIT_ASSERT_EQUAL("de-DE"_ostr, pSpanLang->GetValue()); +} + +CPPUNIT_TEST_FIXTURE(PdfExportTest, testLinkSpanWithFly) +{ + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"PDFUACompliance"_ustr, true) }; + + vcl::filter::PDFDocument aDocument; + loadFromFile(u"LinkSpanWithFly.fodt"); + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); + CPPUNIT_ASSERT(aDocument.Read(aStream)); + + // the frame's own paragraph is current when the link ends, so neither the span nor + // the link closes there - one link, one span, and the frame nested in the span + vcl::filter::PDFObjectElement* pLinkSE(nullptr); + vcl::filter::PDFObjectElement* pSpanSE(nullptr); + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pType = dynamic_cast(pObject->Lookup("Type"_ostr)); + auto pS = dynamic_cast(pObject->Lookup("S"_ostr)); + if (!pType || pType->GetValue() != "StructElem" || !pS) + continue; + if (pS->GetValue() == "Link") + { + CPPUNIT_ASSERT_MESSAGE("a frame must not split the link", !pLinkSE); + pLinkSE = pObject; + } + else if (pS->GetValue() == "Span") + { + CPPUNIT_ASSERT_MESSAGE("a frame must not split the span", !pSpanSE); + pSpanSE = pObject; + } + } + CPPUNIT_ASSERT(pLinkSE); + CPPUNIT_ASSERT(pSpanSE); + + // the span is a kid of the link, and the frame a kid of the span + auto pSpanParent = dynamic_cast(pSpanSE->Lookup("P"_ostr)); + CPPUNIT_ASSERT(pSpanParent); + CPPUNIT_ASSERT_EQUAL(pLinkSE, pSpanParent->LookupObject()); + auto pSpanKids = dynamic_cast(pSpanSE->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pSpanKids); + size_t nDiv(0); + for (const auto pElement : pSpanKids->GetElements()) + { + auto pRef = dynamic_cast(pElement); + if (!pRef) + continue; + auto pKid = pRef->LookupObject(); + CPPUNIT_ASSERT(pKid); + auto pKidS = dynamic_cast(pKid->Lookup("S"_ostr)); + CPPUNIT_ASSERT(pKidS); + CPPUNIT_ASSERT_EQUAL("Div"_ostr, pKidS->GetValue()); + ++nDiv; + } + CPPUNIT_ASSERT_EQUAL(size_t(1), nDiv); +} + +CPPUNIT_TEST_FIXTURE(PdfExportTest, testSpanWithFly) +{ + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"PDFUACompliance"_ustr, true) }; + + vcl::filter::PDFDocument aDocument; + loadFromFile(u"SpanWithFly.fodt"); + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); + CPPUNIT_ASSERT(aDocument.Read(aStream)); + + // the frame opens its tags inside the span, so the span is not what closes at the + // frame's own paragraph - one span, and the frame nested in it + vcl::filter::PDFObjectElement* pSpanSE(nullptr); + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pType = dynamic_cast(pObject->Lookup("Type"_ostr)); + auto pS = dynamic_cast(pObject->Lookup("S"_ostr)); + if (pType && pType->GetValue() == "StructElem" && pS && pS->GetValue() == "Span") + { + CPPUNIT_ASSERT_MESSAGE("a frame must not split the span", !pSpanSE); + pSpanSE = pObject; + } + } + CPPUNIT_ASSERT(pSpanSE); + + // the text before and after the frame, and the frame between them + auto pKids = dynamic_cast(pSpanSE->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pKids); + size_t nMCID(0); + size_t nDiv(0); + for (const auto pElement : pKids->GetElements()) + { + if (dynamic_cast(pElement)) + { + ++nMCID; + continue; + } + auto pRef = dynamic_cast(pElement); + CPPUNIT_ASSERT(pRef); + auto pKid = pRef->LookupObject(); + CPPUNIT_ASSERT(pKid); + auto pKidS = dynamic_cast(pKid->Lookup("S"_ostr)); + CPPUNIT_ASSERT(pKidS); + CPPUNIT_ASSERT_EQUAL("Div"_ostr, pKidS->GetValue()); + ++nDiv; + } + CPPUNIT_ASSERT_EQUAL(size_t(1), nDiv); + CPPUNIT_ASSERT_GREATER(size_t(1), nMCID); +} + +CPPUNIT_TEST_FIXTURE(PdfExportTest, testLinkSpanWraps) +{ + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"PDFUACompliance"_ustr, true) }; + + vcl::filter::PDFDocument aDocument; + loadFromFile(u"LinkSpanWraps.fodt"); + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); + CPPUNIT_ASSERT(aDocument.Read(aStream)); + + std::vector aPages = aDocument.GetPages(); + CPPUNIT_ASSERT_EQUAL(size_t(1), aPages.size()); + auto pAnnots = dynamic_cast(aPages[0]->Lookup("Annots"_ostr)); + CPPUNIT_ASSERT(pAnnots); + // how many lines the link takes does not matter, only that the styled run breaks + CPPUNIT_ASSERT_GREATER(size_t(1), pAnnots->GetElements().size()); + + vcl::filter::PDFObjectElement* pLinkSE(nullptr); + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pType = dynamic_cast(pObject->Lookup("Type"_ostr)); + auto pS = dynamic_cast(pObject->Lookup("S"_ostr)); + if (pType && pType->GetValue() == "StructElem" && pS && pS->GetValue() == "Link") + { + CPPUNIT_ASSERT_MESSAGE("a wrapped span must not split the link", !pLinkSE); + pLinkSE = pObject; + } + } + CPPUNIT_ASSERT(pLinkSE); + + // both lines' annotations reach the Link, although the span is what is open + // across the break + auto pKids = dynamic_cast(pLinkSE->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pKids); + std::vector aKidAnnots; + for (const auto pElement : pKids->GetElements()) + { + auto pObjR = dynamic_cast(pElement); + if (!pObjR) + continue; + auto pOType = dynamic_cast(pObjR->LookupElement("Type"_ostr)); + CPPUNIT_ASSERT(pOType); + CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); + auto pRef + = dynamic_cast(pObjR->LookupElement("Obj"_ostr)); + CPPUNIT_ASSERT(pRef); + aKidAnnots.push_back(pRef->LookupObject()); + } + CPPUNIT_ASSERT_EQUAL(pAnnots->GetElements().size(), aKidAnnots.size()); + CPPUNIT_ASSERT_EQUAL(aKidAnnots.size(), std::set(aKidAnnots.begin(), aKidAnnots.end()).size()); + + // the span is what straddles the break, so it holds a run of text on each line + vcl::filter::PDFObjectElement* pSpanSE(nullptr); + for (const auto pElement : pKids->GetElements()) + { + auto pRef = dynamic_cast(pElement); + if (!pRef) + continue; + auto pKid = pRef->LookupObject(); + CPPUNIT_ASSERT(pKid); + auto pKidS = dynamic_cast(pKid->Lookup("S"_ostr)); + CPPUNIT_ASSERT(pKidS); + CPPUNIT_ASSERT_EQUAL("Span"_ostr, pKidS->GetValue()); + CPPUNIT_ASSERT(!pSpanSE); + pSpanSE = pKid; + } + CPPUNIT_ASSERT(pSpanSE); + auto pSpanKids = dynamic_cast(pSpanSE->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pSpanKids); + CPPUNIT_ASSERT_GREATER(size_t(1), pSpanKids->GetElements().size()); + + // and every annotation carries a StructParent and is one of the Link's OBJR kids + for (const auto pElement : pAnnots->GetElements()) + { + auto pAnnotRef = dynamic_cast(pElement); + CPPUNIT_ASSERT(pAnnotRef); + auto pAnnot = pAnnotRef->LookupObject(); + CPPUNIT_ASSERT(pAnnot); + CPPUNIT_ASSERT_MESSAGE("every annotation needs a StructParent", + pAnnot->Lookup("StructParent"_ostr)); + CPPUNIT_ASSERT(std::find(aKidAnnots.begin(), aKidAnnots.end(), pAnnot) != aKidAnnots.end()); + } +} + +CPPUNIT_TEST_FIXTURE(PdfExportTest, testRubyWithSpan) +{ + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"PDFUACompliance"_ustr, true) }; + + vcl::filter::PDFDocument aDocument; + loadFromFile(u"RubyWithSpan.fodt"); + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + // Parse the export result. + SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); + CPPUNIT_ASSERT(aDocument.Read(aStream)); + + vcl::filter::PDFObjectElement* pRubySE(nullptr); + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pType = dynamic_cast(pObject->Lookup("Type"_ostr)); + auto pS = dynamic_cast(pObject->Lookup("S"_ostr)); + if (pType && pType->GetValue() == "StructElem" && pS && pS->GetValue() == "Ruby") + { + CPPUNIT_ASSERT(!pRubySE); + pRubySE = pObject; + } + } + CPPUNIT_ASSERT(pRubySE); + + // the character style on the base is a tag inside the RB and closes with it, so + // only RB and RT are content of the Ruby + auto pRubyKids = dynamic_cast(pRubySE->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pRubyKids); + CPPUNIT_ASSERT_EQUAL(size_t(2), pRubyKids->GetElements().size()); + vcl::filter::PDFObjectElement* pEmInRB(nullptr); + for (const auto pElement : pRubyKids->GetElements()) + { + auto pRef = dynamic_cast(pElement); + CPPUNIT_ASSERT(pRef); + auto pKid = pRef->LookupObject(); + CPPUNIT_ASSERT(pKid); + auto pKidS = dynamic_cast(pKid->Lookup("S"_ostr)); + CPPUNIT_ASSERT(pKidS); + auto pKidKids = dynamic_cast(pKid->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pKidKids); + CPPUNIT_ASSERT_EQUAL(size_t(1), pKidKids->GetElements().size()); + if (pKidS->GetValue() == "RT") + { + CPPUNIT_ASSERT(dynamic_cast(pKidKids->GetElement(0))); + continue; + } + CPPUNIT_ASSERT_EQUAL("RB"_ostr, pKidS->GetValue()); + auto pEmRef = dynamic_cast(pKidKids->GetElement(0)); + CPPUNIT_ASSERT(pEmRef); + pEmInRB = pEmRef->LookupObject(); + CPPUNIT_ASSERT(pEmInRB); + auto pEmS = dynamic_cast(pEmInRB->Lookup("S"_ostr)); + CPPUNIT_ASSERT(pEmS); + CPPUNIT_ASSERT_EQUAL("Emphasis"_ostr, pEmS->GetValue()); + } + CPPUNIT_ASSERT(pEmInRB); + + // the text after the ruby keeps a tag of its own, a sibling of the Ruby + auto pParaRef = dynamic_cast(pRubySE->Lookup("P"_ostr)); + CPPUNIT_ASSERT(pParaRef); + auto pPara = pParaRef->LookupObject(); + CPPUNIT_ASSERT(pPara); + auto pParaKids = dynamic_cast(pPara->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pParaKids); + size_t nRuby(0); + size_t nEmAfter(0); + for (const auto pElement : pParaKids->GetElements()) + { + auto pRef = dynamic_cast(pElement); + if (!pRef) + continue; + auto pKid = pRef->LookupObject(); + CPPUNIT_ASSERT(pKid); + if (pKid == pRubySE) + { + ++nRuby; + continue; + } + auto pKidS = dynamic_cast(pKid->Lookup("S"_ostr)); + CPPUNIT_ASSERT(pKidS); + CPPUNIT_ASSERT_EQUAL("Emphasis"_ostr, pKidS->GetValue()); + CPPUNIT_ASSERT(pKid != pEmInRB); + ++nEmAfter; + } + CPPUNIT_ASSERT_EQUAL(size_t(1), nRuby); + CPPUNIT_ASSERT_EQUAL(size_t(1), nEmAfter); +} + +CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf163240) +{ + // Enable PDF/UA + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"PDFUACompliance"_ustr, true) }; + + vcl::filter::PDFDocument aDocument; + loadFromFile(u"LinkMultiLine.fodt"); + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + // Parse the export result. + SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); + CPPUNIT_ASSERT(aDocument.Read(aStream)); + + std::vector aPages = aDocument.GetPages(); + CPPUNIT_ASSERT_EQUAL(size_t(1), aPages.size()); + + // the link is broken across lines, so there is one annotation per line + auto pAnnots = dynamic_cast(aPages[0]->Lookup("Annots"_ostr)); + CPPUNIT_ASSERT(pAnnots); + std::vector aAnnots; + for (const auto pElement : pAnnots->GetElements()) + { + auto pReference = dynamic_cast(pElement); + CPPUNIT_ASSERT(pReference); + auto pAnnot = pReference->LookupObject(); + CPPUNIT_ASSERT(pAnnot); + auto pType = dynamic_cast(pAnnot->Lookup("Type"_ostr)); + CPPUNIT_ASSERT(pType); + CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pType->GetValue()); + auto pSubtype = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); + CPPUNIT_ASSERT(pSubtype); + CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSubtype->GetValue()); + aAnnots.push_back(pAnnot); + } + // how many lines the link takes does not matter, only that it takes several + CPPUNIT_ASSERT_GREATER(size_t(1), aAnnots.size()); + + // ... but only one Link structure element for the whole link + vcl::filter::PDFObjectElement* pLinkSE(nullptr); + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pType = dynamic_cast(pObject->Lookup("Type"_ostr)); + auto pS = dynamic_cast(pObject->Lookup("S"_ostr)); + if (pType && pType->GetValue() == "StructElem" && pS && pS->GetValue() == "Link") + { + CPPUNIT_ASSERT_MESSAGE("one Link SE per link, not per line", !pLinkSE); + pLinkSE = pObject; + } + } + + CPPUNIT_ASSERT(pLinkSE); + // in the paragraph, not orphaned + auto pParentRef = dynamic_cast(pLinkSE->Lookup("P"_ostr)); + CPPUNIT_ASSERT(pParentRef); + auto pParent = pParentRef->LookupObject(); + CPPUNIT_ASSERT(pParent); + auto pParentS = dynamic_cast(pParent->Lookup("S"_ostr)); + CPPUNIT_ASSERT(pParentS); + CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pParentS->GetValue()); + auto pParentKids = dynamic_cast(pParent->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pParentKids); + bool isKid(false); + for (const auto pElement : pParentKids->GetElements()) + { + auto pRef = dynamic_cast(pElement); + isKid = isKid || (pRef && pRef->LookupObject() == pLinkSE); + } + CPPUNIT_ASSERT(isKid); + + // every annotation is nested in it, and the text stayed inside it too + auto pKids = dynamic_cast(pLinkSE->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pKids); + // the fixture is one page and the link contains no nested SE, so every kid + // is either the MCID of a line or the OBJR of that line's annotation + std::vector aNestedAnnots; + size_t nMCID(0); + for (const auto pElement : pKids->GetElements()) + { + if (dynamic_cast(pElement)) + { + ++nMCID; + continue; + } + auto pObjR = dynamic_cast(pElement); + CPPUNIT_ASSERT(pObjR); + auto pOType = dynamic_cast(pObjR->LookupElement("Type"_ostr)); + CPPUNIT_ASSERT(pOType); + CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); + auto pAnnotRef + = dynamic_cast(pObjR->LookupElement("Obj"_ostr)); + CPPUNIT_ASSERT(pAnnotRef); + aNestedAnnots.push_back(pAnnotRef->LookupObject()); + } + CPPUNIT_ASSERT_EQUAL(aAnnots.size(), nMCID); + CPPUNIT_ASSERT_EQUAL(aAnnots.size(), aNestedAnnots.size()); + for (const auto pAnnot : aAnnots) + { + CPPUNIT_ASSERT(std::find(aNestedAnnots.begin(), aNestedAnnots.end(), pAnnot) + != aNestedAnnots.end()); + } + + // ... and each of them resolves to it through the parent tree + vcl::filter::PDFArrayElement* pNums(nullptr); + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pType = dynamic_cast(pObject->Lookup("Type"_ostr)); + if (pType && pType->GetValue() == "StructTreeRoot") + { + auto pParentTree = dynamic_cast( + pObject->Lookup("ParentTree"_ostr)); + CPPUNIT_ASSERT(pParentTree); + auto pNumTree = pParentTree->LookupObject(); + CPPUNIT_ASSERT(pNumTree); + pNums = dynamic_cast(pNumTree->Lookup("Nums"_ostr)); + } + } + CPPUNIT_ASSERT(pNums); + CPPUNIT_ASSERT_EQUAL(size_t(0), pNums->GetElements().size() % 2); + std::set aStructParents; + for (const auto pAnnot : aAnnots) + { + auto pStructParent + = dynamic_cast(pAnnot->Lookup("StructParent"_ostr)); + CPPUNIT_ASSERT(pStructParent); // every link must have it! + // an annotation cannot share the entry of another one + CPPUNIT_ASSERT(aStructParents.insert(pStructParent->GetValue()).second); + int nFound(0); + for (size_t i = 0; i < pNums->GetElements().size(); i += 2) + { + auto pI = dynamic_cast(pNums->GetElement(i)); + CPPUNIT_ASSERT(pI); + if (pI->GetValue() != pStructParent->GetValue()) + continue; + ++nFound; + auto pSERef = dynamic_cast(pNums->GetElement(i + 1)); + CPPUNIT_ASSERT(pSERef); + CPPUNIT_ASSERT_EQUAL(pLinkSE, pSERef->LookupObject()); + } + CPPUNIT_ASSERT_EQUAL(1, nFound); + } +} + CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) { // Enable PDF/UA @@ -3128,6 +3756,19 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) auto pKidsD0 = dynamic_cast(pObjectD0->Lookup("K"_ostr)); CPPUNIT_ASSERT(pKidsD0); auto pKidsD0v = pKidsD0->GetElements(); + CPPUNIT_ASSERT_EQUAL(size_t(2), pKidsD0v.size()); + + // the annotations of one link cannot share a parent tree entry + auto CheckDistinct = [](vcl::filter::PDFObjectElement* pOne, + vcl::filter::PDFObjectElement* pOther) { + auto pStructParentOne + = dynamic_cast(pOne->Lookup("StructParent"_ostr)); + auto pStructParentOther + = dynamic_cast(pOther->Lookup("StructParent"_ostr)); + CPPUNIT_ASSERT(pStructParentOne); + CPPUNIT_ASSERT(pStructParentOther); + CPPUNIT_ASSERT(pStructParentOne->GetValue() != pStructParentOther->GetValue()); + }; auto pRefKidD00 = dynamic_cast(pKidsD0v[0]); CPPUNIT_ASSERT(pRefKidD00); @@ -3138,9 +3779,10 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) auto pSD00 = dynamic_cast(pObjectD00->Lookup("S"_ostr)); CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD00->GetValue()); { + // the fly splits the link into 2 annotations, which share the one Link SE auto pKids = dynamic_cast(pObjectD00->Lookup("K"_ostr)); - auto nMCID(0); - auto nRef(0); + size_t nMCID(0); + std::vector aAnnots; for (size_t i = 0; i < pKids->GetElements().size(); ++i) { auto pNum = dynamic_cast(pKids->GetElement(i)); @@ -3151,49 +3793,53 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) } if (pObjR) { - ++nRef; auto pOType = dynamic_cast(pObjR->LookupElement("Type"_ostr)); CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); auto pAnnotRef = dynamic_cast( pObjR->LookupElement("Obj"_ostr)); - auto pAnnot = pAnnotRef->LookupObject(); - auto pAType - = dynamic_cast(pAnnot->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); - auto pASubtype - = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); - auto pAContents = dynamic_cast( - pAnnot->Lookup("Contents"_ostr)); - CPPUNIT_ASSERT_EQUAL( - u"Firefox119"_ustr, - ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); - auto pStructParent = dynamic_cast( - pAnnot->Lookup("StructParent"_ostr)); - CPPUNIT_ASSERT(pStructParent); // every link must have it! - auto pARect - = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); - CPPUNIT_ASSERT(pARect); - const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); - const auto* pNumL = dynamic_cast(rElements[0]); - CPPUNIT_ASSERT(pNumL); - CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3); - const auto* pNumT = dynamic_cast(rElements[1]); - CPPUNIT_ASSERT(pNumT); - CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumT->GetValue(), 1e-3); - const auto* pNumR = dynamic_cast(rElements[2]); - CPPUNIT_ASSERT(pNumR); - // this changed to the end of the text, not the start of the fly - CPPUNIT_ASSERT_DOUBLES_EQUAL(191.657, pNumR->GetValue(), 1e-3); - const auto* pNumB = dynamic_cast(rElements[3]); - CPPUNIT_ASSERT(pNumB); - CPPUNIT_ASSERT_DOUBLES_EQUAL(785.189, pNumB->GetValue(), 1e-3); + CPPUNIT_ASSERT(pAnnotRef); + aAnnots.push_back(pAnnotRef->LookupObject()); } } - CPPUNIT_ASSERT_EQUAL(static_cast(1), nMCID); - CPPUNIT_ASSERT_EQUAL(static_cast(1), nRef); + CPPUNIT_ASSERT_EQUAL(size_t(2), nMCID); + CPPUNIT_ASSERT_EQUAL(size_t(2), aAnnots.size()); + + auto CheckAnnot = [](vcl::filter::PDFObjectElement* pAnnot, double fLeft, double fRight) { + CPPUNIT_ASSERT(pAnnot); + auto pAType = dynamic_cast(pAnnot->Lookup("Type"_ostr)); + CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); + auto pASubtype + = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); + CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); + auto pAContents + = dynamic_cast(pAnnot->Lookup("Contents"_ostr)); + CPPUNIT_ASSERT_EQUAL(u"Firefox119"_ustr, + vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); + auto pStructParent + = dynamic_cast(pAnnot->Lookup("StructParent"_ostr)); + CPPUNIT_ASSERT(pStructParent); // every link must have it! + auto pARect = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); + CPPUNIT_ASSERT(pARect); + const auto& rElements = pARect->GetElements(); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); + const auto* pNumL = dynamic_cast(rElements[0]); + CPPUNIT_ASSERT(pNumL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(fLeft, pNumL->GetValue(), 1e-3); + const auto* pNumT = dynamic_cast(rElements[1]); + CPPUNIT_ASSERT(pNumT); + CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumT->GetValue(), 1e-3); + const auto* pNumR = dynamic_cast(rElements[2]); + CPPUNIT_ASSERT(pNumR); + CPPUNIT_ASSERT_DOUBLES_EQUAL(fRight, pNumR->GetValue(), 1e-3); + const auto* pNumB = dynamic_cast(rElements[3]); + CPPUNIT_ASSERT(pNumB); + CPPUNIT_ASSERT_DOUBLES_EQUAL(785.189, pNumB->GetValue(), 1e-3); + }; + CheckDistinct(aAnnots[0], aAnnots[1]); + // the first rect ends at the end of the text, not at the start of the fly + CheckAnnot(aAnnots[0], 56.693, 191.657); + CheckAnnot(aAnnots[1], 387.843, 534.407); } auto pRefKidD01 = dynamic_cast(pKidsD0v[1]); @@ -3203,74 +3849,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) auto pTypeD01 = dynamic_cast(pObjectD01->Lookup("Type"_ostr)); CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD01->GetValue()); auto pSD01 = dynamic_cast(pObjectD01->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD01->GetValue()); - { - auto pKids = dynamic_cast(pObjectD01->Lookup("K"_ostr)); - auto nMCID(0); - auto nRef(0); - for (size_t i = 0; i < pKids->GetElements().size(); ++i) - { - auto pNum = dynamic_cast(pKids->GetElement(i)); - auto pObjR = dynamic_cast(pKids->GetElement(i)); - if (pNum) - { - ++nMCID; - } - if (pObjR) - { - ++nRef; - auto pOType - = dynamic_cast(pObjR->LookupElement("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); - auto pAnnotRef = dynamic_cast( - pObjR->LookupElement("Obj"_ostr)); - auto pAnnot = pAnnotRef->LookupObject(); - auto pAType - = dynamic_cast(pAnnot->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); - auto pASubtype - = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); - auto pAContents = dynamic_cast( - pAnnot->Lookup("Contents"_ostr)); - CPPUNIT_ASSERT_EQUAL( - u"Firefox119"_ustr, - ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); - auto pStructParent = dynamic_cast( - pAnnot->Lookup("StructParent"_ostr)); - CPPUNIT_ASSERT(pStructParent); // every link must have it! - auto pARect - = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); - CPPUNIT_ASSERT(pARect); - const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); - const auto* pNumL = dynamic_cast(rElements[0]); - CPPUNIT_ASSERT(pNumL); - CPPUNIT_ASSERT_DOUBLES_EQUAL(387.843, pNumL->GetValue(), 1e-3); - const auto* pNumT = dynamic_cast(rElements[1]); - CPPUNIT_ASSERT(pNumT); - CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumT->GetValue(), 1e-3); - const auto* pNumR = dynamic_cast(rElements[2]); - CPPUNIT_ASSERT(pNumR); - // this changed to the end of the text, not the start of the fly - CPPUNIT_ASSERT_DOUBLES_EQUAL(534.407, pNumR->GetValue(), 1e-3); - const auto* pNumB = dynamic_cast(rElements[3]); - CPPUNIT_ASSERT(pNumB); - CPPUNIT_ASSERT_DOUBLES_EQUAL(785.189, pNumB->GetValue(), 1e-3); - } - } - CPPUNIT_ASSERT_EQUAL(static_cast(1), nMCID); - CPPUNIT_ASSERT_EQUAL(static_cast(1), nRef); - } - - auto pRefKidD02 = dynamic_cast(pKidsD0v[2]); - CPPUNIT_ASSERT(pRefKidD02); - auto pObjectD02 = pRefKidD02->LookupObject(); - CPPUNIT_ASSERT(pObjectD02); - auto pTypeD02 = dynamic_cast(pObjectD02->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD02->GetValue()); - auto pSD02 = dynamic_cast(pObjectD02->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Figure"_ostr, pSD02->GetValue()); + CPPUNIT_ASSERT_EQUAL("Figure"_ostr, pSD01->GetValue()); auto pRefKidD1 = dynamic_cast(pKidsDv[1]); CPPUNIT_ASSERT(pRefKidD1); @@ -3284,6 +3863,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) auto pKidsD1 = dynamic_cast(pObjectD1->Lookup("K"_ostr)); CPPUNIT_ASSERT(pKidsD1); auto pKidsD1v = pKidsD1->GetElements(); + CPPUNIT_ASSERT_EQUAL(size_t(1), pKidsD1v.size()); auto pRefKidD10 = dynamic_cast(pKidsD1v[0]); CPPUNIT_ASSERT(pRefKidD10); @@ -3294,9 +3874,10 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) auto pSD10 = dynamic_cast(pObjectD10->Lookup("S"_ostr)); CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD10->GetValue()); { + // the fly splits this link too auto pKids = dynamic_cast(pObjectD10->Lookup("K"_ostr)); - auto nMCID(0); - auto nRef(0); + size_t nMCID(0); + std::vector aAnnots; for (size_t i = 0; i < pKids->GetElements().size(); ++i) { auto pNum = dynamic_cast(pKids->GetElement(i)); @@ -3307,116 +3888,53 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) } if (pObjR) { - ++nRef; auto pOType = dynamic_cast(pObjR->LookupElement("Type"_ostr)); CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); auto pAnnotRef = dynamic_cast( pObjR->LookupElement("Obj"_ostr)); - auto pAnnot = pAnnotRef->LookupObject(); - auto pAType - = dynamic_cast(pAnnot->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); - auto pASubtype - = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); - auto pAContents = dynamic_cast( - pAnnot->Lookup("Contents"_ostr)); - CPPUNIT_ASSERT_EQUAL( - u"Firefox118"_ustr, - ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); - auto pStructParent = dynamic_cast( - pAnnot->Lookup("StructParent"_ostr)); - CPPUNIT_ASSERT(pStructParent); // every link must have it! - auto pARect - = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); - CPPUNIT_ASSERT(pARect); - const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); - const auto* pNumL = dynamic_cast(rElements[0]); - CPPUNIT_ASSERT(pNumL); - CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3); - const auto* pNumT = dynamic_cast(rElements[1]); - CPPUNIT_ASSERT(pNumT); - CPPUNIT_ASSERT_DOUBLES_EQUAL(757.589, pNumT->GetValue(), 1e-3); - const auto* pNumR = dynamic_cast(rElements[2]); - CPPUNIT_ASSERT(pNumR); - // this changed to the end of the text, not the start of the fly - CPPUNIT_ASSERT_DOUBLES_EQUAL(191.657, pNumR->GetValue(), 1e-3); - const auto* pNumB = dynamic_cast(rElements[3]); - CPPUNIT_ASSERT(pNumB); - CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumB->GetValue(), 1e-3); + CPPUNIT_ASSERT(pAnnotRef); + aAnnots.push_back(pAnnotRef->LookupObject()); } } - CPPUNIT_ASSERT_EQUAL(static_cast(1), nMCID); - CPPUNIT_ASSERT_EQUAL(static_cast(1), nRef); - } + CPPUNIT_ASSERT_EQUAL(size_t(2), nMCID); + CPPUNIT_ASSERT_EQUAL(size_t(2), aAnnots.size()); - auto pRefKidD11 = dynamic_cast(pKidsD1v[1]); - CPPUNIT_ASSERT(pRefKidD11); - auto pObjectD11 = pRefKidD11->LookupObject(); - CPPUNIT_ASSERT(pObjectD11); - auto pTypeD11 = dynamic_cast(pObjectD11->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD11->GetValue()); - auto pSD11 = dynamic_cast(pObjectD11->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD11->GetValue()); - { - auto pKids = dynamic_cast(pObjectD11->Lookup("K"_ostr)); - auto nMCID(0); - auto nRef(0); - for (size_t i = 0; i < pKids->GetElements().size(); ++i) - { - auto pNum = dynamic_cast(pKids->GetElement(i)); - auto pObjR = dynamic_cast(pKids->GetElement(i)); - if (pNum) - { - ++nMCID; - } - if (pObjR) - { - ++nRef; - auto pOType - = dynamic_cast(pObjR->LookupElement("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); - auto pAnnotRef = dynamic_cast( - pObjR->LookupElement("Obj"_ostr)); - auto pAnnot = pAnnotRef->LookupObject(); - auto pAType - = dynamic_cast(pAnnot->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); - auto pASubtype - = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); - auto pAContents = dynamic_cast( - pAnnot->Lookup("Contents"_ostr)); - CPPUNIT_ASSERT_EQUAL( - u"Firefox118"_ustr, - ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); - auto pStructParent = dynamic_cast( - pAnnot->Lookup("StructParent"_ostr)); - CPPUNIT_ASSERT(pStructParent); // every link must have it! - auto pARect - = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); - CPPUNIT_ASSERT(pARect); - const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); - const auto* pNumL = dynamic_cast(rElements[0]); - CPPUNIT_ASSERT(pNumL); - CPPUNIT_ASSERT_DOUBLES_EQUAL(387.843, pNumL->GetValue(), 1e-3); - const auto* pNumT = dynamic_cast(rElements[1]); - CPPUNIT_ASSERT(pNumT); - CPPUNIT_ASSERT_DOUBLES_EQUAL(757.589, pNumT->GetValue(), 1e-3); - const auto* pNumR = dynamic_cast(rElements[2]); - CPPUNIT_ASSERT(pNumR); - // this changed to the end of the text, not the start of the fly - CPPUNIT_ASSERT_DOUBLES_EQUAL(534.407, pNumR->GetValue(), 1e-3); - const auto* pNumB = dynamic_cast(rElements[3]); - CPPUNIT_ASSERT(pNumB); - CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumB->GetValue(), 1e-3); - } - } - CPPUNIT_ASSERT_EQUAL(static_cast(1), nMCID); - CPPUNIT_ASSERT_EQUAL(static_cast(1), nRef); + auto CheckAnnot = [](vcl::filter::PDFObjectElement* pAnnot, double fLeft, double fRight) { + CPPUNIT_ASSERT(pAnnot); + auto pAType = dynamic_cast(pAnnot->Lookup("Type"_ostr)); + CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); + auto pASubtype + = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); + CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); + auto pAContents + = dynamic_cast(pAnnot->Lookup("Contents"_ostr)); + CPPUNIT_ASSERT_EQUAL(u"Firefox118"_ustr, + vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); + auto pStructParent + = dynamic_cast(pAnnot->Lookup("StructParent"_ostr)); + CPPUNIT_ASSERT(pStructParent); // every link must have it! + auto pARect = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); + CPPUNIT_ASSERT(pARect); + const auto& rElements = pARect->GetElements(); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); + const auto* pNumL = dynamic_cast(rElements[0]); + CPPUNIT_ASSERT(pNumL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(fLeft, pNumL->GetValue(), 1e-3); + const auto* pNumT = dynamic_cast(rElements[1]); + CPPUNIT_ASSERT(pNumT); + CPPUNIT_ASSERT_DOUBLES_EQUAL(757.589, pNumT->GetValue(), 1e-3); + const auto* pNumR = dynamic_cast(rElements[2]); + CPPUNIT_ASSERT(pNumR); + CPPUNIT_ASSERT_DOUBLES_EQUAL(fRight, pNumR->GetValue(), 1e-3); + const auto* pNumB = dynamic_cast(rElements[3]); + CPPUNIT_ASSERT(pNumB); + CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumB->GetValue(), 1e-3); + }; + CheckDistinct(aAnnots[0], aAnnots[1]); + // the first rect ends at the end of the text, not at the start of the fly + CheckAnnot(aAnnots[0], 56.693, 191.657); + CheckAnnot(aAnnots[1], 387.843, 534.407); } // the problem was that in addition to the 4 links with SE there was 1 more @@ -3518,19 +4036,44 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) auto pKidsD0 = dynamic_cast(pObjectD0->Lookup("K"_ostr)); CPPUNIT_ASSERT(pKidsD0); auto pKidsD0v = pKidsD0->GetElements(); + CPPUNIT_ASSERT_EQUAL(size_t(2), pKidsD0v.size()); - auto pRefKidD00 = dynamic_cast(pKidsD0v[0]); - CPPUNIT_ASSERT(pRefKidD00); - auto pObjectD00 = pRefKidD00->LookupObject(); - CPPUNIT_ASSERT(pObjectD00); - auto pTypeD00 = dynamic_cast(pObjectD00->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD00->GetValue()); - auto pSD00 = dynamic_cast(pObjectD00->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD00->GetValue()); - { - auto pKids = dynamic_cast(pObjectD00->Lookup("K"_ostr)); - auto nMCID(0); - auto nRef(0); + auto CheckAnnot = [](vcl::filter::PDFObjectElement* pAnnot, double fLeft, double fTop, + double fRight, double fBottom) { + CPPUNIT_ASSERT(pAnnot); + auto pAType = dynamic_cast(pAnnot->Lookup("Type"_ostr)); + CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); + auto pASubtype = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); + CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); + auto pAContents + = dynamic_cast(pAnnot->Lookup("Contents"_ostr)); + CPPUNIT_ASSERT_EQUAL(u"quadfoo"_ustr, + vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); + auto pStructParent + = dynamic_cast(pAnnot->Lookup("StructParent"_ostr)); + CPPUNIT_ASSERT(pStructParent); // every link must have it! + auto pARect = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); + CPPUNIT_ASSERT(pARect); + const auto& rElements = pARect->GetElements(); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); + const auto* pNumL = dynamic_cast(rElements[0]); + CPPUNIT_ASSERT(pNumL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(fLeft, pNumL->GetValue(), 1e-3); + const auto* pNumT = dynamic_cast(rElements[1]); + CPPUNIT_ASSERT(pNumT); + CPPUNIT_ASSERT_DOUBLES_EQUAL(fTop, pNumT->GetValue(), 1e-3); + const auto* pNumR = dynamic_cast(rElements[2]); + CPPUNIT_ASSERT(pNumR); + CPPUNIT_ASSERT_DOUBLES_EQUAL(fRight, pNumR->GetValue(), 1e-3); + const auto* pNumB = dynamic_cast(rElements[3]); + CPPUNIT_ASSERT(pNumB); + CPPUNIT_ASSERT_DOUBLES_EQUAL(fBottom, pNumB->GetValue(), 1e-3); + }; + auto GetLinkAnnots = [](vcl::filter::PDFObjectElement* pLinkSE) { + auto pKids = dynamic_cast(pLinkSE->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pKids); + size_t nMCID(0); + std::vector aAnnots; for (size_t i = 0; i < pKids->GetElements().size(); ++i) { auto pNum = dynamic_cast(pKids->GetElement(i)); @@ -3541,48 +4084,42 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) } if (pObjR) { - ++nRef; auto pOType = dynamic_cast(pObjR->LookupElement("Type"_ostr)); CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); auto pAnnotRef = dynamic_cast( pObjR->LookupElement("Obj"_ostr)); - auto pAnnot = pAnnotRef->LookupObject(); - auto pAType - = dynamic_cast(pAnnot->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); - auto pASubtype - = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); - auto pAContents = dynamic_cast( - pAnnot->Lookup("Contents"_ostr)); - CPPUNIT_ASSERT_EQUAL( - u"quadfoo"_ustr, - ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); - auto pStructParent = dynamic_cast( - pAnnot->Lookup("StructParent"_ostr)); - CPPUNIT_ASSERT(pStructParent); // every link must have it! - auto pARect - = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); - CPPUNIT_ASSERT(pARect); - const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); - const auto* pNumL = dynamic_cast(rElements[0]); - CPPUNIT_ASSERT(pNumL); - CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3); - const auto* pNumT = dynamic_cast(rElements[1]); - CPPUNIT_ASSERT(pNumT); - CPPUNIT_ASSERT_DOUBLES_EQUAL(240.455, pNumT->GetValue(), 1e-3); - const auto* pNumR = dynamic_cast(rElements[2]); - CPPUNIT_ASSERT(pNumR); - CPPUNIT_ASSERT_DOUBLES_EQUAL(241.007, pNumR->GetValue(), 1e-3); - const auto* pNumB = dynamic_cast(rElements[3]); - CPPUNIT_ASSERT(pNumB); - CPPUNIT_ASSERT_DOUBLES_EQUAL(350.855, pNumB->GetValue(), 1e-3); + CPPUNIT_ASSERT(pAnnotRef); + aAnnots.push_back(pAnnotRef->LookupObject()); } } - CPPUNIT_ASSERT_EQUAL(static_cast(1), nMCID); - CPPUNIT_ASSERT_EQUAL(static_cast(1), nRef); + CPPUNIT_ASSERT_EQUAL(size_t(2), nMCID); + CPPUNIT_ASSERT_EQUAL(size_t(2), aAnnots.size()); + // the 2 annotations cannot share one parent tree entry + auto pStructParent0 + = dynamic_cast(aAnnots[0]->Lookup("StructParent"_ostr)); + auto pStructParent1 + = dynamic_cast(aAnnots[1]->Lookup("StructParent"_ostr)); + CPPUNIT_ASSERT(pStructParent0); + CPPUNIT_ASSERT(pStructParent1); + CPPUNIT_ASSERT(pStructParent0->GetValue() != pStructParent1->GetValue()); + return aAnnots; + }; + + // the link is split over 2 frames, and broken over 2 lines in each of them, + // which gives one annotation per line but only one Link SE per frame + auto pRefKidD00 = dynamic_cast(pKidsD0v[0]); + CPPUNIT_ASSERT(pRefKidD00); + auto pObjectD00 = pRefKidD00->LookupObject(); + CPPUNIT_ASSERT(pObjectD00); + auto pTypeD00 = dynamic_cast(pObjectD00->Lookup("Type"_ostr)); + CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD00->GetValue()); + auto pSD00 = dynamic_cast(pObjectD00->Lookup("S"_ostr)); + CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD00->GetValue()); + { + auto aAnnots = GetLinkAnnots(pObjectD00); + CheckAnnot(aAnnots[0], 56.693, 240.455, 241.007, 350.855); + CheckAnnot(aAnnots[1], 56.643, 130.055, 241.007, 240.455); } auto pRefKidD01 = dynamic_cast(pKidsD0v[1]); @@ -3594,194 +4131,11 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) auto pSD01 = dynamic_cast(pObjectD01->Lookup("S"_ostr)); CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD01->GetValue()); { - auto pKids = dynamic_cast(pObjectD01->Lookup("K"_ostr)); - auto nMCID(0); - auto nRef(0); - for (size_t i = 0; i < pKids->GetElements().size(); ++i) - { - auto pNum = dynamic_cast(pKids->GetElement(i)); - auto pObjR = dynamic_cast(pKids->GetElement(i)); - if (pNum) - { - ++nMCID; - } - if (pObjR) - { - ++nRef; - auto pOType - = dynamic_cast(pObjR->LookupElement("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); - auto pAnnotRef = dynamic_cast( - pObjR->LookupElement("Obj"_ostr)); - auto pAnnot = pAnnotRef->LookupObject(); - auto pAType - = dynamic_cast(pAnnot->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); - auto pASubtype - = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); - auto pAContents = dynamic_cast( - pAnnot->Lookup("Contents"_ostr)); - CPPUNIT_ASSERT_EQUAL( - u"quadfoo"_ustr, - ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); - auto pStructParent = dynamic_cast( - pAnnot->Lookup("StructParent"_ostr)); - CPPUNIT_ASSERT(pStructParent); // every link must have it! - auto pARect - = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); - CPPUNIT_ASSERT(pARect); - const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); - const auto* pNumL = dynamic_cast(rElements[0]); - CPPUNIT_ASSERT(pNumL); - CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3); - const auto* pNumT = dynamic_cast(rElements[1]); - CPPUNIT_ASSERT(pNumT); - CPPUNIT_ASSERT_DOUBLES_EQUAL(130.055, pNumT->GetValue(), 1e-3); - const auto* pNumR = dynamic_cast(rElements[2]); - CPPUNIT_ASSERT(pNumR); - CPPUNIT_ASSERT_DOUBLES_EQUAL(241.007, pNumR->GetValue(), 1e-3); - const auto* pNumB = dynamic_cast(rElements[3]); - CPPUNIT_ASSERT(pNumB); - CPPUNIT_ASSERT_DOUBLES_EQUAL(240.455, pNumB->GetValue(), 1e-3); - } - } - CPPUNIT_ASSERT_EQUAL(static_cast(1), nMCID); - CPPUNIT_ASSERT_EQUAL(static_cast(1), nRef); + auto aAnnots = GetLinkAnnots(pObjectD01); + CheckAnnot(aAnnots[0], 56.643, 252.455, 241.007, 362.855); + CheckAnnot(aAnnots[1], 56.643, 142.055, 206.007, 252.455); } - auto pRefKidD02 = dynamic_cast(pKidsD0v[2]); - CPPUNIT_ASSERT(pRefKidD02); - auto pObjectD02 = pRefKidD02->LookupObject(); - CPPUNIT_ASSERT(pObjectD02); - auto pTypeD02 = dynamic_cast(pObjectD02->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD02->GetValue()); - auto pSD02 = dynamic_cast(pObjectD02->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD02->GetValue()); - { - auto pKids = dynamic_cast(pObjectD02->Lookup("K"_ostr)); - auto nMCID(0); - auto nRef(0); - for (size_t i = 0; i < pKids->GetElements().size(); ++i) - { - auto pNum = dynamic_cast(pKids->GetElement(i)); - auto pObjR = dynamic_cast(pKids->GetElement(i)); - if (pNum) - { - ++nMCID; - } - if (pObjR) - { - ++nRef; - auto pOType - = dynamic_cast(pObjR->LookupElement("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); - auto pAnnotRef = dynamic_cast( - pObjR->LookupElement("Obj"_ostr)); - auto pAnnot = pAnnotRef->LookupObject(); - auto pAType - = dynamic_cast(pAnnot->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); - auto pASubtype - = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); - auto pAContents = dynamic_cast( - pAnnot->Lookup("Contents"_ostr)); - CPPUNIT_ASSERT_EQUAL( - u"quadfoo"_ustr, - ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); - auto pStructParent = dynamic_cast( - pAnnot->Lookup("StructParent"_ostr)); - CPPUNIT_ASSERT(pStructParent); // every link must have it! - auto pARect - = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); - CPPUNIT_ASSERT(pARect); - const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); - const auto* pNumL = dynamic_cast(rElements[0]); - CPPUNIT_ASSERT(pNumL); - CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3); - const auto* pNumT = dynamic_cast(rElements[1]); - CPPUNIT_ASSERT(pNumT); - CPPUNIT_ASSERT_DOUBLES_EQUAL(252.455, pNumT->GetValue(), 1e-3); - const auto* pNumR = dynamic_cast(rElements[2]); - CPPUNIT_ASSERT(pNumR); - CPPUNIT_ASSERT_DOUBLES_EQUAL(241.007, pNumR->GetValue(), 1e-3); - const auto* pNumB = dynamic_cast(rElements[3]); - CPPUNIT_ASSERT(pNumB); - CPPUNIT_ASSERT_DOUBLES_EQUAL(362.855, pNumB->GetValue(), 1e-3); - } - } - CPPUNIT_ASSERT_EQUAL(static_cast(1), nMCID); - CPPUNIT_ASSERT_EQUAL(static_cast(1), nRef); - } - - auto pRefKidD03 = dynamic_cast(pKidsD0v[3]); - CPPUNIT_ASSERT(pRefKidD03); - auto pObjectD03 = pRefKidD03->LookupObject(); - CPPUNIT_ASSERT(pObjectD03); - auto pTypeD03 = dynamic_cast(pObjectD03->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD03->GetValue()); - auto pSD03 = dynamic_cast(pObjectD03->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD03->GetValue()); - { - auto pKids = dynamic_cast(pObjectD03->Lookup("K"_ostr)); - auto nMCID(0); - auto nRef(0); - for (size_t i = 0; i < pKids->GetElements().size(); ++i) - { - auto pNum = dynamic_cast(pKids->GetElement(i)); - auto pObjR = dynamic_cast(pKids->GetElement(i)); - if (pNum) - { - ++nMCID; - } - if (pObjR) - { - ++nRef; - auto pOType - = dynamic_cast(pObjR->LookupElement("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); - auto pAnnotRef = dynamic_cast( - pObjR->LookupElement("Obj"_ostr)); - auto pAnnot = pAnnotRef->LookupObject(); - auto pAType - = dynamic_cast(pAnnot->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); - auto pASubtype - = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); - auto pAContents = dynamic_cast( - pAnnot->Lookup("Contents"_ostr)); - CPPUNIT_ASSERT_EQUAL( - u"quadfoo"_ustr, - ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); - auto pStructParent = dynamic_cast( - pAnnot->Lookup("StructParent"_ostr)); - CPPUNIT_ASSERT(pStructParent); // every link must have it! - auto pARect - = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); - CPPUNIT_ASSERT(pARect); - const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); - const auto* pNumL = dynamic_cast(rElements[0]); - CPPUNIT_ASSERT(pNumL); - CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3); - const auto* pNumT = dynamic_cast(rElements[1]); - CPPUNIT_ASSERT(pNumT); - CPPUNIT_ASSERT_DOUBLES_EQUAL(142.055, pNumT->GetValue(), 1e-3); - const auto* pNumR = dynamic_cast(rElements[2]); - CPPUNIT_ASSERT(pNumR); - CPPUNIT_ASSERT_DOUBLES_EQUAL(206.007, pNumR->GetValue(), 1e-3); - const auto* pNumB = dynamic_cast(rElements[3]); - CPPUNIT_ASSERT(pNumB); - CPPUNIT_ASSERT_DOUBLES_EQUAL(252.455, pNumB->GetValue(), 1e-3); - } - } - CPPUNIT_ASSERT_EQUAL(static_cast(1), nMCID); - CPPUNIT_ASSERT_EQUAL(static_cast(1), nRef); - } auto pRefKidD1 = dynamic_cast(pKidsDv[1]); CPPUNIT_ASSERT(pRefKidD1); auto pObjectD1 = pRefKidD1->LookupObject(); @@ -3794,6 +4148,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) auto pKidsD1 = dynamic_cast(pObjectD1->Lookup("K"_ostr)); CPPUNIT_ASSERT(pKidsD1); auto pKidsD1v = pKidsD1->GetElements(); + CPPUNIT_ASSERT_EQUAL(size_t(4), pKidsD1v.size()); auto pRefKidD10 = dynamic_cast(pKidsD1v[0]); CPPUNIT_ASSERT(pRefKidD10); @@ -3805,7 +4160,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD10->GetValue()); { auto pKids = dynamic_cast(pObjectD10->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -3842,7 +4197,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3); @@ -3871,7 +4226,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD11->GetValue()); { auto pKids = dynamic_cast(pObjectD11->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -3908,7 +4263,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3); @@ -3937,7 +4292,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD12->GetValue()); { auto pKids = dynamic_cast(pObjectD12->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -3974,7 +4329,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3); @@ -4003,7 +4358,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD13->GetValue()); { auto pKids = dynamic_cast(pObjectD13->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -4040,7 +4395,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3); diff --git a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx index 43a41584acb0..3386486941a4 100644 --- a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx +++ b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx @@ -36,6 +36,8 @@ #include #include #include +#include +#include #include #include #include @@ -333,7 +335,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf121615) CPPUNIT_ASSERT_EQUAL(tools::Long(300), aBitmap.GetSizePixel().Height()); CPPUNIT_ASSERT_EQUAL(vcl::PixelFormat::N8_BPP, aBitmap.getPixelFormat()); // tdf#121615 was caused by broken handling of data width with 8bit color, - // so the test image has some black in the bottomright corner, check it's there + // so the test image has some black in the bottom-right corner, check it's there CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(0, 0)); CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(0, 299)); CPPUNIT_ASSERT_EQUAL(COL_WHITE, aBitmap.GetPixelColor(199, 0)); @@ -632,7 +634,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testReduceSmallImage) // Without the accompanying fix in place, this test would have failed with: // - Expected: 16 // - Actual : 6 - // i.e. the image was scaled down to 300 DPI, even if it had tiny size. + // i.e. the image was scaled down to 300 DPI, even though it had a tiny size. CPPUNIT_ASSERT_EQUAL(16, nWidth); CPPUNIT_ASSERT_EQUAL(16, nHeight); } @@ -831,7 +833,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testLinkWrongPage) CPPUNIT_TEST_FIXTURE(PdfExportTest2, testLinkWrongPagePartial) { // Given a Draw document with 3 pages, a link on the 2nd page: - // When exporting that the 2nd and 3rd page to pdf: + // When exporting the 2nd and 3rd page to pdf: uno::Sequence aFilterData = { comphelper::makePropertyValue(u"PageRange"_ustr, u"2-3"_ustr), }; @@ -840,7 +842,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testLinkWrongPagePartial) loadFromFile(u"link-wrong-page-partial.odg"); save(TestFilter::PDF_WRITER, aMediaDescriptor.getAsConstPropertyValueList()); - // Then make sure the we have a link on the 1st page, but not on the 2nd one: + // Then make sure that we have a link on the 1st page, but not on the 2nd one: std::unique_ptr pPdfDocument = parsePDFExport(vcl::pdf::PDFiumLibrary::get()); CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount()); @@ -996,8 +998,8 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf160196) // Create an empty document. mxComponent = loadFromDesktop("private:factory/swriter"); - // Add a title to avoid - // The Metadata stream as specified in ISO 32000-2:2020, + // Add a title, because + // the Metadata stream as specified in ISO 32000-2:2020, // 14.3 in the document catalog dictionary shall contain a dc:title entry uno::Reference xDocumentPropertiesSupplier( mxComponent, uno::UNO_QUERY); @@ -1090,7 +1092,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testVersion20) // Check round-trip of importing and exporting the PDF with PDFium filter, // which imports the PDF document as multiple PDFs as graphic object. -// Each page in the document has one PDF graphic object which content is +// Each page in the document has one PDF graphic object whose content is // the corresponding page in the PDF. When such a document is exported, // the PDF graphic gets embedded into the exported PDF document (as a // Form XObject). @@ -1932,7 +1934,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf136805) CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf157517) { - // Only reproduced with PPDF-A/2 / bDF-A/3b and PDF/UA. + // Only reproduced with PDF/A-2 / PDF/A-3b and PDF/UA. uno::Sequence aFilterData(comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) }, { "SelectPdfVersion", uno::Any(static_cast(3)) }, @@ -2181,6 +2183,285 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf149140) CPPUNIT_ASSERT_EQUAL(6, nTH); } +// the structure element type of a table cell that a row's kid refers to +OString GetCellType(vcl::filter::PDFElement* pElement) +{ + auto pRef = dynamic_cast(pElement); + CPPUNIT_ASSERT(pRef); + auto pCell = pRef->LookupObject(); + CPPUNIT_ASSERT(pCell); + auto pS = dynamic_cast(pCell->Lookup("S"_ostr)); + CPPUNIT_ASSERT(pS); + return pS->GetValue(); +} + +CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf173162) +{ + loadFromFile(u"StructureNamespaces.fodt"); + + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"PDFUACompliance"_ustr, true), + comphelper::makePropertyValue(u"SelectPdfVersion"_ustr, + sal_Int32(20)) }; + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + vcl::filter::PDFDocument aDocument; + CPPUNIT_ASSERT(aDocument.Read(*maTempFile.GetStream(StreamMode::READ))); + + static constexpr OString aPDF17("http://iso.org/pdf/ssn"_ostr); + static constexpr OString aPDF20("http://iso.org/pdf2/ssn"_ostr); + + // the namespace each element points at, by object + std::unordered_map aNamespaceOf; + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pType = dynamic_cast(pObject->Lookup("Type"_ostr)); + if (!pType || pType->GetValue() != "Namespace") + continue; + auto pURI = dynamic_cast(pObject->Lookup("NS"_ostr)); + CPPUNIT_ASSERT(pURI); + aNamespaceOf[pObject] = pURI->GetValue(); + + // the 1.7 namespace needs none: for it the structure tree root's RoleMap is the fallback + auto pMap + = dynamic_cast(pObject->Lookup("RoleMapNS"_ostr)); + if (pURI->GetValue() == aPDF17) + CPPUNIT_ASSERT_MESSAGE("the 1.7 namespace maps nothing", !pMap); + else + CPPUNIT_ASSERT(pMap); + } + // one for PDF 2.0 and one for the types it dropped + CPPUNIT_ASSERT_EQUAL(size_t(2), aNamespaceOf.size()); + + CPPUNIT_ASSERT(aDocument.GetCatalog()); + auto pRootRef = dynamic_cast( + aDocument.GetCatalog()->Lookup("StructTreeRoot"_ostr)); + CPPUNIT_ASSERT(pRootRef); + CPPUNIT_ASSERT(pRootRef->LookupObject()); + auto pNamespaces = dynamic_cast( + pRootRef->LookupObject()->Lookup("Namespaces"_ostr)); + CPPUNIT_ASSERT(pNamespaces); + CPPUNIT_ASSERT_EQUAL(size_t(2), pNamespaces->GetElements().size()); + for (const auto pElement : pNamespaces->GetElements()) + { + auto pRef = dynamic_cast(pElement); + CPPUNIT_ASSERT(pRef); + CPPUNIT_ASSERT_MESSAGE("the root names a namespace object", + aNamespaceOf.contains(pRef->LookupObject())); + } + + // The references have to be separated in the file itself: "1 0 R2 0 R" is not two + // references, and this parser reads it as though it were, so check the bytes. veraPDF is + // stricter - it fails to parse such a file and reports nothing, which reads as a pass. + SvStream* pStream = maTempFile.GetStream(StreamMode::READ); + pStream->Seek(0); + const OString aFile(read_uInt8s_ToOString(*pStream, pStream->remainingSize())); + const sal_Int32 nStart(aFile.indexOf("/Namespaces [")); + CPPUNIT_ASSERT_GREATER(sal_Int32(-1), nStart); + const sal_Int32 nEnd(aFile.indexOf("]", nStart)); + CPPUNIT_ASSERT_GREATER(nStart, nEnd); + const std::string_view aArray(aFile.subView(nStart, nEnd - nStart)); + for (size_t nR = aArray.find('R'); nR != std::string_view::npos; nR = aArray.find('R', nR + 1)) + { + const bool bSeparated(nR + 1 == aArray.size() + || !rtl::isAsciiDigit(static_cast(aArray[nR + 1]))); + CPPUNIT_ASSERT_MESSAGE( + OString(OString::Concat("unseparated references in ") + aArray).getStr(), bSeparated); + } + + // the namespace each structure type landed in + std::unordered_map aNamespaceOfType; + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pType = dynamic_cast(pObject->Lookup("Type"_ostr)); + if (!pType || pType->GetValue() != "StructElem") + continue; + auto pS = dynamic_cast(pObject->Lookup("S"_ostr)); + if (!pS) + continue; + auto pNS = dynamic_cast(pObject->Lookup("NS"_ostr)); + CPPUNIT_ASSERT_MESSAGE("every element in a PDF 2.0 file names its namespace", pNS); + const OString aURI(aNamespaceOf[pNS->LookupObject()]); + const auto aEntry(aNamespaceOfType.emplace(pS->GetValue(), aURI)); + if (!aEntry.second) + CPPUNIT_ASSERT_EQUAL_MESSAGE("one type, two namespaces", aEntry.first->second, aURI); + } + + // the types PDF 2.0 dropped, which ISO 14289-2 8.2.4 accepts only in the 1.7 namespace + CPPUNIT_ASSERT_EQUAL(aPDF17, aNamespaceOfType["TOC"_ostr]); + CPPUNIT_ASSERT_EQUAL(aPDF17, aNamespaceOfType["TOCI"_ostr]); + CPPUNIT_ASSERT_EQUAL(aPDF17, aNamespaceOfType["Index"_ostr]); + CPPUNIT_ASSERT_EQUAL(aPDF17, aNamespaceOfType["BlockQuote"_ostr]); + CPPUNIT_ASSERT_EQUAL(aPDF17, aNamespaceOfType["Quote"_ostr]); + CPPUNIT_ASSERT_EQUAL(aPDF17, aNamespaceOfType["Code"_ostr]); + CPPUNIT_ASSERT_EQUAL(aPDF17, aNamespaceOfType["BibEntry"_ostr]); + // and the ones it kept + CPPUNIT_ASSERT_EQUAL(aPDF20, aNamespaceOfType["Document"_ostr]); + CPPUNIT_ASSERT_EQUAL(aPDF20, aNamespaceOfType["H1"_ostr]); + CPPUNIT_ASSERT_EQUAL(aPDF20, aNamespaceOfType["Link"_ostr]); + // the footnote takes the type PDF 2.0 put in place of Note + CPPUNIT_ASSERT_EQUAL(aPDF20, aNamespaceOfType["FENote"_ostr]); +} + +CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf166963) +{ + // Takes about two minutes locally, most of it in the PDFDocument below, which rescans its + // element list once per object. Disable the test if that costs CI too much. + // + // A table with more rows than ncMaxPDFArraySize, so its kid list has to be split into Div + // containers, and with a repeated heading row, which is emitted as a NonStructElement: that + // gives the Table element more children than kids, which is what the split used to mishandle. + utl::TempFileNamed aSource(u"tdf166963", true, u".fodt"); + aSource.EnableKillingFile(); + SvStream* pSource = aSource.GetStream(StreamMode::WRITE | StreamMode::TRUNC); + pSource->WriteOString( + "" + "" + "" + "" + "" + "" + "" + "" + "" + "h" + ""); + for (int i = 0; i < 8300; ++i) + pSource->WriteOString("" + "r"); + pSource->WriteOString(""); + aSource.CloseStream(); + + loadFromURL(aSource.GetURL()); + + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"UseTaggedPDF"_ustr, true) }; + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + vcl::filter::PDFDocument aDocument; + CPPUNIT_ASSERT(aDocument.Read(*maTempFile.GetStream(StreamMode::READ))); + + // which element actually lists each structure element as its kid + std::unordered_map aLister; + std::vector aElements; + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pType = dynamic_cast(pObject->Lookup("Type"_ostr)); + if (!pType) + continue; + const bool bRoot(pType->GetValue() == "StructTreeRoot"); + if (!bRoot && pType->GetValue() != "StructElem") + continue; + if (!bRoot) + aElements.push_back(pObject); + auto pKids = dynamic_cast(pObject->Lookup("K"_ostr)); + if (!pKids) + continue; + for (const auto pKid : pKids->GetElements()) + { + auto pRef = dynamic_cast(pKid); + if (!pRef) + continue; + CPPUNIT_ASSERT_MESSAGE("a structure element is the kid of two elements", + aLister.emplace(pRef->LookupObject(), pObject).second); + } + } + // a generated container holds exactly ncMaxPDFArraySize kids; without one the kid list + // never overflowed and the document proves nothing + size_t nFullContainers(0); + for (const auto pElement : aElements) + { + auto pS = dynamic_cast(pElement->Lookup("S"_ostr)); + auto pKids = dynamic_cast(pElement->Lookup("K"_ostr)); + if (pS && pS->GetValue() == "Div" && pKids && pKids->GetElements().size() == 8191) + ++nFullContainers; + } + CPPUNIT_ASSERT_EQUAL(size_t(1), nFullContainers); + + for (const auto pElement : aElements) + { + auto pParent = dynamic_cast(pElement->Lookup("P"_ostr)); + CPPUNIT_ASSERT(pParent); + CPPUNIT_ASSERT_MESSAGE("a structure element that nothing lists as its kid", + aLister.contains(pElement)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("a structure element claims a parent that does not list it", + aLister.at(pElement), pParent->LookupObject()); + } +} + +CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf173194) +{ + loadFromFile(u"TableHeadingDerived.fodt"); + + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"PDFUACompliance"_ustr, true) }; + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); + vcl::filter::PDFDocument aDocument; + CPPUNIT_ASSERT(aDocument.Read(aStream)); + + vcl::filter::PDFObjectElement* pTableSE(nullptr); + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pType = dynamic_cast(pObject->Lookup("Type"_ostr)); + auto pS = dynamic_cast(pObject->Lookup("S"_ostr)); + if (pType && pType->GetValue() == "StructElem" && pS && pS->GetValue() == "Table") + { + CPPUNIT_ASSERT(!pTableSE); + pTableSE = pObject; + } + } + CPPUNIT_ASSERT(pTableSE); + + auto pRows = dynamic_cast(pTableSE->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pRows); + CPPUNIT_ASSERT_EQUAL(size_t(2), pRows->GetElements().size()); + + // the first column is Table Heading in row one and a style derived from it in row two, + // so both rows read TH then TD + for (const auto pRowElement : pRows->GetElements()) + { + auto pRowRef = dynamic_cast(pRowElement); + CPPUNIT_ASSERT(pRowRef); + auto pRow = pRowRef->LookupObject(); + CPPUNIT_ASSERT(pRow); + auto pRowS = dynamic_cast(pRow->Lookup("S"_ostr)); + CPPUNIT_ASSERT(pRowS); + CPPUNIT_ASSERT_EQUAL("TR"_ostr, pRowS->GetValue()); + + auto pCells = dynamic_cast(pRow->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pCells); + CPPUNIT_ASSERT_EQUAL(size_t(2), pCells->GetElements().size()); + CPPUNIT_ASSERT_EQUAL("TH"_ostr, GetCellType(pCells->GetElement(0))); + CPPUNIT_ASSERT_EQUAL("TD"_ostr, GetCellType(pCells->GetElement(1))); + } +} + CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf153935) { // tdf#153935: PDF/A-1 + PDF/UA must still emit /Scope on TH and /Tabs on @@ -2424,7 +2705,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf157817) SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); CPPUNIT_ASSERT(aDocument.Read(aStream)); - // The document has one page. + // The document has two pages. std::vector aPages = aDocument.GetPages(); CPPUNIT_ASSERT_EQUAL(size_t(2), aPages.size()); @@ -2897,25 +3178,18 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testSpans) auto pKids10010 = dynamic_cast(pObject10010->Lookup("K"_ostr)); CPPUNIT_ASSERT(pKids10010); - // assume there are no MCID ref at this level + // the paragraph is wholly in one language, so it names it itself + auto pLang10010 = dynamic_cast( + pObject10010->Lookup("Lang"_ostr)); + CPPUNIT_ASSERT(pLang10010); + CPPUNIT_ASSERT_EQUAL("en-GB"_ostr, pLang10010->GetValue()); + // and its content needs no span between auto vKids10010 = pKids10010->GetElements(); - // only one span - CPPUNIT_ASSERT_EQUAL(size_t(1), vKids10010.size()); - - auto pRefKid100100 = dynamic_cast(vKids10010[0]); - CPPUNIT_ASSERT(pRefKid100100); - auto pObject100100 = pRefKid100100->LookupObject(); - CPPUNIT_ASSERT(pObject100100); - auto pType100100 = dynamic_cast( - pObject100100->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType100100->GetValue()); - auto pS100100 - = dynamic_cast(pObject100100->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Span"_ostr, pS100100->GetValue()); - // this span exists because of lang - auto pLang100100 = dynamic_cast( - pObject100100->Lookup("Lang"_ostr)); - CPPUNIT_ASSERT_EQUAL("en-GB"_ostr, pLang100100->GetValue()); + CPPUNIT_ASSERT_EQUAL(size_t(8), vKids10010.size()); + for (const auto pKid : vKids10010) + { + CPPUNIT_ASSERT(!dynamic_cast(pKid)); + } auto pRefKid101 = dynamic_cast(vKids10[1]); CPPUNIT_ASSERT(pRefKid101); @@ -2977,6 +3251,8 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testSpans) auto pKids10110 = dynamic_cast(pObject10110->Lookup("K"_ostr)); CPPUNIT_ASSERT(pKids10110); + // this paragraph is in the language of the document, so it names none + CPPUNIT_ASSERT(!pObject10110->Lookup("Lang"_ostr)); auto vKids10110 = pKids10110->GetElements(); // only MCIDs, no span for (size_t i = 0; i < vKids10110.size(); ++i) @@ -3045,89 +3321,48 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testSpans) auto pKids10210 = dynamic_cast(pObject10210->Lookup("K"_ostr)); CPPUNIT_ASSERT(pKids10210); - // assume there are no MCID ref at this level auto vKids10210 = pKids10210->GetElements(); - // 2 span and a hyperlink - CPPUNIT_ASSERT_EQUAL(size_t(3), vKids10210.size()); + CPPUNIT_ASSERT_EQUAL(size_t(8), vKids10210.size()); + // this paragraph too is wholly in one language and names it itself + auto pLang10210 = dynamic_cast( + pObject10210->Lookup("Lang"_ostr)); + CPPUNIT_ASSERT(pLang10210); + CPPUNIT_ASSERT_EQUAL("en-GB"_ostr, pLang10210->GetValue()); - auto pRefKid102100 = dynamic_cast(vKids10210[0]); - CPPUNIT_ASSERT(pRefKid102100); - auto pObject102100 = pRefKid102100->LookupObject(); - CPPUNIT_ASSERT(pObject102100); - auto pType102100 = dynamic_cast( - pObject102100->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType102100->GetValue()); - auto pS102100 - = dynamic_cast(pObject102100->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Span"_ostr, pS102100->GetValue()); - auto pKids102100 - = dynamic_cast(pObject102100->Lookup("K"_ostr)); - CPPUNIT_ASSERT(pKids102100); - auto vKids102100 = pKids102100->GetElements(); - for (size_t i = 0; i < vKids102100.size(); ++i) + // its own text needs no span, so the only elements left are the two links, and + // neither repeats the language + std::vector aLinks; + for (const auto pKid : vKids10210) { - auto pKid = dynamic_cast(vKids102100[i]); - CPPUNIT_ASSERT(!pKid); + auto pRef = dynamic_cast(pKid); + if (!pRef) + continue; + auto pLinked = pRef->LookupObject(); + CPPUNIT_ASSERT(pLinked); + CPPUNIT_ASSERT_EQUAL( + "StructElem"_ostr, + dynamic_cast(pLinked->Lookup("Type"_ostr)) + ->GetValue()); + CPPUNIT_ASSERT_EQUAL("Link"_ostr, dynamic_cast( + pLinked->Lookup("S"_ostr)) + ->GetValue()); + CPPUNIT_ASSERT(!pLinked->Lookup("Lang"_ostr)); + aLinks.push_back(pLinked); } + CPPUNIT_ASSERT_EQUAL(size_t(2), aLinks.size()); - auto pRefKid102101 = dynamic_cast(vKids10210[1]); - CPPUNIT_ASSERT(pRefKid102101); - auto pObject102101 = pRefKid102101->LookupObject(); - CPPUNIT_ASSERT(pObject102101); - auto pType102101 = dynamic_cast( - pObject102101->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType102101->GetValue()); - auto pS102101 - = dynamic_cast(pObject102101->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pS102101->GetValue()); - auto pKids102101 - = dynamic_cast(pObject102101->Lookup("K"_ostr)); - CPPUNIT_ASSERT(pKids102101); - auto vKids102101 = pKids102101->GetElements(); - int nRef(0); - for (size_t i = 0; i < vKids102101.size(); ++i) + // the hyperlink comes before the footnote in the text, so it is the first + // of the two + auto pKidsHyperlink + = dynamic_cast(aLinks[0]->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pKidsHyperlink); + int nAnnots(0); + for (const auto pKid : pKidsHyperlink->GetElements()) { - auto pKid = dynamic_cast(vKids102101[i]); - if (pKid) - { - ++nRef; // annotation - } + if (dynamic_cast(pKid)) + ++nAnnots; } - CPPUNIT_ASSERT_EQUAL(1, nRef); - - auto pRefKid102102 = dynamic_cast(vKids10210[2]); - CPPUNIT_ASSERT(pRefKid102102); - auto pObject102102 = pRefKid102102->LookupObject(); - CPPUNIT_ASSERT(pObject102102); - auto pType102102 = dynamic_cast( - pObject102102->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType102102->GetValue()); - auto pS102102 - = dynamic_cast(pObject102102->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Span"_ostr, pS102102->GetValue()); - auto pKids102102 - = dynamic_cast(pObject102102->Lookup("K"_ostr)); - CPPUNIT_ASSERT(pKids102102); - auto vKids102102 = pKids102102->GetElements(); - // there is a footnote - int nFtn(0); - for (size_t i = 0; i < vKids102102.size(); ++i) - { - auto pKid = dynamic_cast(vKids102102[i]); - if (pKid) - { - auto pObject = pKid->LookupObject(); - CPPUNIT_ASSERT(pObject); - auto pType = dynamic_cast( - pObject->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pType->GetValue()); - auto pS - = dynamic_cast(pObject->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pS->GetValue()); - ++nFtn; - } - } - CPPUNIT_ASSERT_EQUAL(1, nFtn); + CPPUNIT_ASSERT_EQUAL(1, nAnnots); auto pRefKid103 = dynamic_cast(vKids10[3]); CPPUNIT_ASSERT(pRefKid103); @@ -3186,6 +3421,10 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testSpans) auto pS10310 = dynamic_cast(pObject10310->Lookup("S"_ostr)); CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pS10310->GetValue()); + auto pLang10310 = dynamic_cast( + pObject10310->Lookup("Lang"_ostr)); + CPPUNIT_ASSERT(pLang10310); + CPPUNIT_ASSERT_EQUAL("en-GB"_ostr, pLang10310->GetValue()); auto pKids10310 = dynamic_cast(pObject10310->Lookup("K"_ostr)); CPPUNIT_ASSERT(pKids10310); @@ -3217,6 +3456,8 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testSpans) dynamic_cast( pDictA103101->LookupElement("TextDecorationType"_ostr)) ->GetValue()); + // the span stands for the strikeout, and no longer repeats the language + CPPUNIT_ASSERT(!pObject103101->Lookup("Lang"_ostr)); // now the footnote container - following the list auto pRefKid11 = dynamic_cast(vKids1[1]); @@ -3528,6 +3769,163 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, cool16122BadPantoneElement) CPPUNIT_ASSERT(bFound); } +CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf168462) +{ + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"UseTaggedPDF"_ustr, true) }; + loadFromFile(u"master-background-artifact.fodp"); + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + vcl::filter::PDFDocument aDocument; + CPPUNIT_ASSERT(aDocument.Read(*maTempFile.GetStream(StreamMode::READ))); + std::vector aPages = aDocument.GetPages(); + CPPUNIT_ASSERT_EQUAL(size_t(1), aPages.size()); + vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr); + CPPUNIT_ASSERT(pContents); + vcl::filter::PDFStreamElement* pStream = pContents->GetStream(); + CPPUNIT_ASSERT(pStream); + SvMemoryStream& rObjectStream = pStream->GetMemory(); + SvMemoryStream aUncompressed; + ZCodec aZCodec; + aZCodec.BeginCompression(); + rObjectStream.Seek(0); + aZCodec.Decompress(rObjectStream, aUncompressed); + CPPUNIT_ASSERT(aZCodec.EndCompression()); + + // an operator shares its line with its operands, and sometimes with another operator + auto isPaintingOperator = [](const std::string_view line) { + for (size_t nPos = 0; nPos != std::string_view::npos;) + { + const size_t nStart = line.find_first_not_of(' ', nPos); + if (nStart == std::string_view::npos) + break; + nPos = line.find(' ', nStart); + const std::string_view aToken( + line.substr(nStart, nPos == std::string_view::npos ? nPos : nPos - nStart)); + for (const auto op : + { "f", "f*", "F", "S", "s", "B", "B*", "b", "b*", "Do", "TJ", "Tj" }) + { + if (aToken == op) + return true; + } + } + return false; + }; + + auto pStart = static_cast(aUncompressed.GetData()); + const char* const pEnd = pStart + aUncompressed.GetSize(); + int nOpenMarks(0); + int nArtifacts(0); + OStringBuffer aUntagged; + // ISO 14289-1 7.1: content is either tagged or marked as an artifact + while (pStart != pEnd) + { + const auto pLineEnd = std::find(pStart, pEnd, '\n'); + const std::string_view line(pStart, pLineEnd - pStart); + pStart = pLineEnd == pEnd ? pEnd : pLineEnd + 1; + + if (line == "EMC") + --nOpenMarks; + else if (o3tl::ends_with(line, "BMC") || o3tl::ends_with(line, "BDC")) + { + ++nOpenMarks; + if (o3tl::starts_with(line, "/Artifact")) + ++nArtifacts; + } + else if (nOpenMarks == 0 && isPaintingOperator(line)) + aUntagged.append(OString::Concat(line) + " "); + } + + // without the fix the background was painted outside every marked-content section + CPPUNIT_ASSERT_MESSAGE(aUntagged.toString().getStr(), aUntagged.isEmpty()); + // the page decoration, and the background the slide takes from its master page + CPPUNIT_ASSERT_EQUAL(2, nArtifacts); + CPPUNIT_ASSERT_EQUAL(0, nOpenMarks); +} + +CPPUNIT_TEST_FIXTURE(PdfExportTest2, testStyleNamedStructureTypes) +{ + loadFromFile(u"style-named-tags.fodt"); + + // the structure types the file carries, and what its role map makes of the names + auto aExport = [this](sal_Int32 nVersion) { + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"UseTaggedPDF"_ustr, true), + comphelper::makePropertyValue(u"SelectPdfVersion"_ustr, + nVersion) }; + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + vcl::filter::PDFDocument aDocument; + // the temp file hands out one stream and keeps it, so the second export needs a new one + maTempFile.CloseStream(); + CPPUNIT_ASSERT(aDocument.Read(*maTempFile.GetStream(StreamMode::READ))); + OStringBuffer aTypes; + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pType = dynamic_cast(pObject->Lookup("Type"_ostr)); + if (!pType || pType->GetValue() != "StructElem") + continue; + auto pS = dynamic_cast(pObject->Lookup("S"_ostr)); + CPPUNIT_ASSERT(pS); + aTypes.append(pS->GetValue() + " "); + } + + auto pCatalog = aDocument.GetCatalog(); + CPPUNIT_ASSERT(pCatalog); + auto pStructTreeRoot = dynamic_cast( + pCatalog->Lookup("StructTreeRoot"_ostr)); + CPPUNIT_ASSERT(pStructTreeRoot); + CPPUNIT_ASSERT(pStructTreeRoot->LookupObject()); + auto pRoleMap = dynamic_cast( + pStructTreeRoot->LookupObject()->Lookup("RoleMap"_ostr)); + CPPUNIT_ASSERT(pRoleMap); + OStringBuffer aRoles; + // GetItems is sorted, unlike the map the writer keeps + for (const auto& rRole : pRoleMap->GetItems()) + { + auto pValue = dynamic_cast(rRole.second); + CPPUNIT_ASSERT(pValue); + aRoles.append(rRole.first + "=" + pValue->GetValue() + " "); + } + return std::pair(aTypes.makeStringAndClear(), aRoles.makeStringAndClear()); + }; + + // Without the fix, a style named after a standard type kept that name, and the role map + // remapped the type - /Index/P among them - so the first sequence read Index, not Index-1. + // + // a style named after a type, or after another style, takes the first free name beside + // its own; the run in Source Text is the one element really of the Code type, and the + // style really called Code1 keeps a name of its own, being a different style + const auto[aTypes17, aRoles17] = aExport(17); + CPPUNIT_ASSERT_EQUAL(u8"Étude TD Étude TD TR Table" + " Code-1 Title Index-1 Code1 H-1 THead-1 H7 Sub Artifact-1 Code-1-1 H7-1 " + "Code-2 Code Étude-1" + " Standard Document "_ostr, + aTypes17); + CPPUNIT_ASSERT_EQUAL( + u8"Artifact-1=P Code-1=P Code-1-1=P Code-2=Span Code1=P H-1=P" + " H7=P H7-1=H6 Index-1=P Standard=P Sub=P THead-1=P Title=P Étude=P Étude-1=Span "_ostr, + aRoles17); + + // PDF 2.0 has a Title type, and heading levels without limit, so Title names its own + // elements while H7 and Sub have to give way + const auto[aTypes20, aRoles20] = aExport(20); + CPPUNIT_ASSERT_EQUAL(u8"Étude TD Étude TD TR Table" + " Code-1 Title Index-1 Code1 H-1 THead-1 H7-1 Sub-1 Artifact-1 Code-1-1 " + "H7 Code-2 Code Étude-1" + " Standard Document "_ostr, + aTypes20); + CPPUNIT_ASSERT_EQUAL( + u8"Artifact-1=P Code-1=P Code-1-1=P Code-2=Span Code1=P H-1=P" + " H7-1=P Index-1=P Standard=P Sub-1=P THead-1=P Étude=P Étude-1=Span "_ostr, + aRoles20); + // the heading keeps the standard name, which is why nothing maps it + CPPUNIT_ASSERT_EQUAL(sal_Int32(-1), aRoles20.indexOf("H7=")); +} + } // end anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/vcl/qt5/QtFrame.cxx b/vcl/qt5/QtFrame.cxx index 6cbb446d4132..b15e60f85c61 100644 --- a/vcl/qt5/QtFrame.cxx +++ b/vcl/qt5/QtFrame.cxx @@ -221,10 +221,8 @@ QWindow* QtFrame::windowHandle() const case Platform::Windows: case Platform::Xcb: case Platform::Mac: - pChild->setAttribute(Qt::WA_NativeWindow); - break; case Platform::Other: - std::abort(); + pChild->setAttribute(Qt::WA_NativeWindow); break; } return pChild->windowHandle(); diff --git a/vcl/qt5/QtInstance.cxx b/vcl/qt5/QtInstance.cxx index 09fcbcc6764a..fa3dee4117f4 100644 --- a/vcl/qt5/QtInstance.cxx +++ b/vcl/qt5/QtInstance.cxx @@ -652,7 +652,6 @@ Platform QtInstance::GetPlatform() const if (sPlatformName == u"cocoa") return Platform::Mac; - assert(false && "Unsupported qt VCL platform"); return Platform::Other; } diff --git a/vcl/qt5/QtInstanceContainer.cxx b/vcl/qt5/QtInstanceContainer.cxx index b46976f1b4b0..0f9b8643cf0f 100644 --- a/vcl/qt5/QtInstanceContainer.cxx +++ b/vcl/qt5/QtInstanceContainer.cxx @@ -16,7 +16,6 @@ QtInstanceContainer::QtInstanceContainer(QWidget* pWidget) : QtInstanceWidget(pWidget) { - assert(pWidget->layout() && "no layout to use for container"); } void QtInstanceContainer::move(weld::Widget* pWidget, weld::Container* pNewParent) @@ -92,7 +91,7 @@ void QtInstanceContainer::connect_container_focus_changed(const Linklayout(); - assert(pLayout); + assert(pLayout && "Widget doesn't have a layout"); return *pLayout; } diff --git a/vcl/qt5/QtInstanceEntry.cxx b/vcl/qt5/QtInstanceEntry.cxx index c9ddf591ec5e..2891b42b66c1 100644 --- a/vcl/qt5/QtInstanceEntry.cxx +++ b/vcl/qt5/QtInstanceEntry.cxx @@ -143,6 +143,10 @@ void QtInstanceEntry::setMessageType(QLineEdit& rLineEdit, weld::EntryMessageTyp case weld::EntryMessageType::Normal: // don't do anything special return; + case weld::EntryMessageType::Info: + rLineEdit.addAction(QIcon::fromTheme("dialog-information"), + QLineEdit::TrailingPosition); + return; case weld::EntryMessageType::Warning: rLineEdit.addAction(QIcon::fromTheme("dialog-warning"), QLineEdit::TrailingPosition); diff --git a/vcl/source/app/salvtables.cxx b/vcl/source/app/salvtables.cxx index 4c3517775592..eeccc451098f 100644 --- a/vcl/source/app/salvtables.cxx +++ b/vcl/source/app/salvtables.cxx @@ -3213,23 +3213,30 @@ void set_message_type(Edit* pEntry, weld::EntryMessageType eType) switch (eType) { case weld::EntryMessageType::Normal: + case weld::EntryMessageType::Info: pEntry->SetForceControlBackground(false); pEntry->SetControlForeground(); pEntry->SetControlBackground(); + if (eType == weld::EntryMessageType::Normal) + pEntry->SetEntryMessageType(0); + else + pEntry->SetEntryMessageType(1); break; case weld::EntryMessageType::Warning: // tdf#114603: enable setting the background to a different color; // relevant for GTK; see also #i75179# pEntry->SetForceControlBackground(true); pEntry->SetControlForeground(COL_BLACK); - pEntry->SetControlBackground(0xffff38); // "light yellow 1" + pEntry->SetControlBackground(0xffffa6); // "light yellow 3" + pEntry->SetEntryMessageType(2); break; case weld::EntryMessageType::Error: // tdf#114603: enable setting the background to a different color; // relevant for GTK; see also #i75179# pEntry->SetForceControlBackground(true); pEntry->SetControlForeground(COL_BLACK); // contrast of 5.87 to the red background - pEntry->SetControlBackground(0xff3838); // "light red 1" + pEntry->SetControlBackground(0xffa6a6); // "light red 3" + pEntry->SetEntryMessageType(3); break; } } diff --git a/vcl/source/control/edit.cxx b/vcl/source/control/edit.cxx index 001314f3fcb0..6e6a2b5f6c91 100644 --- a/vcl/source/control/edit.cxx +++ b/vcl/source/control/edit.cxx @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -253,6 +254,7 @@ void Edit::ImplInitEditData() mpDDInfo = nullptr; mpIMEInfos = nullptr; mcEchoChar = 0; + maEntryMessageType = 0; // no default mirroring for Edit controls // note: controls that use a subedit will revert this (SpinField, ComboBox) @@ -483,7 +485,16 @@ void Edit::ImplRepaint(vcl::RenderContext& rRenderContext, const tools::Rectangl ImplPaintBorder(rRenderContext); - bool bDrawSelection = maSelection.Len() && (HasFocus() || (GetStyle() & WB_NOHIDESELECTION) || mbActivePopup); + if (maEntryMessageType > 0) + { + const OUString sType( maEntryMessageType == 1 ? RID_MSGTYPE_INFO + : maEntryMessageType == 2 ? RID_MSGTYPE_WARN + : RID_MSGTYPE_ERR); + const Point aPoint(rRectangle.Right() - 18, rRectangle.Bottom() / 2 - 8); + rRenderContext.DrawImage(aPoint, Image(StockImage::Yes, sType)); + } + bool bDrawSelection + = maSelection.Len() && (HasFocus() || (GetStyle() & WB_NOHIDESELECTION) || mbActivePopup); aPos.setX( mnXOffset + ImplGetExtraXOffset() ); if (bPaintPlaceholderText) diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx index 2052ae9bf415..d0d3b236bf7b 100644 --- a/vcl/source/filter/ipdf/pdfdocument.cxx +++ b/vcl/source/filter/ipdf/pdfdocument.cxx @@ -637,7 +637,14 @@ void PDFDocument::WriteXRef(sal_uInt64 nXRefOffset, PDFReferenceElement const* p // Write stream data. SvMemoryStream aXRefStream; - const size_t nOffsetLen = 3; + sal_uInt64 nMaxOffset = 0; + for (const auto& rXRef : m_aXRef) + if (rXRef.second.GetDirty()) + nMaxOffset = std::max(nMaxOffset, rXRef.second.GetOffset()); + // the second field has to hold the largest offset the update writes + size_t nOffsetLen = 1; + for (sal_uInt64 nRest = nMaxOffset; nRest > 0xff; nRest >>= 8) + ++nOffsetLen; // 3 additional bytes: predictor, the first and the third field. const size_t nLineLength = nOffsetLen + 3; // This is the line as it appears before tweaking according to the predictor. @@ -680,8 +687,7 @@ void PDFDocument::WriteXRef(sal_uInt64 nXRefOffset, PDFReferenceElement const* p size_t nByte = nOffsetLen - i - 1; // Fields requiring more than one byte are stored with the // high-order byte first. - unsigned char nCh = (rEntry.GetOffset() & (0xff << (nByte * 8))) >> (nByte * 8); - aOrigLine[nPos++] = nCh; + aOrigLine[nPos++] = static_cast(rEntry.GetOffset() >> (nByte * 8)); } // Third field. @@ -701,8 +707,10 @@ void PDFDocument::WriteXRef(sal_uInt64 nXRefOffset, PDFReferenceElement const* p } m_aEditBuffer.WriteNumberAsString(nXRefStreamId); - m_aEditBuffer.WriteOString( - " 0 obj\n<>/Filter/FlateDecode"); + m_aEditBuffer.WriteOString(" 0 obj\n<>/Filter/FlateDecode"); // ID. auto pID = dynamic_cast(m_pXRefStream->Lookup("ID"_ostr)); @@ -780,7 +788,8 @@ void PDFDocument::WriteXRef(sal_uInt64 nXRefOffset, PDFReferenceElement const* p m_aEditBuffer.WriteOString("/Size "); m_aEditBuffer.WriteNumberAsString(m_aXRef.size()); - m_aEditBuffer.WriteOString("/Type/XRef/W[1 3 1]>>\nstream\n"); + m_aEditBuffer.WriteOString(rtl::Concat2View( + "/Type/XRef/W[1 " + OString::number(sal_Int32(nOffsetLen)) + " 1]>>\nstream\n")); aXRefStream.Seek(0); m_aEditBuffer.WriteStream(aXRefStream); m_aEditBuffer.WriteOString("\nendstream\nendobj\n\n"); @@ -1603,14 +1612,16 @@ void PDFDocument::ReadXRefStream(SvStream& rStream) std::vector aBuf(nLength); rStream.ReadBytes(aBuf.data(), aBuf.size()); - auto pFilter = dynamic_cast(pObject->Lookup("Filter"_ostr)); - if (!pFilter) + PDFElement* pFilterElement = pObject->Lookup("Filter"_ostr); + auto pFilter = dynamic_cast(pFilterElement); + if (pFilterElement && !pFilter) { - SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: no Filter found"); + // ISO 32000-2 Table 5 allows an array of filters, which this reader does not apply + SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: filter is not a name"); return; } - if (pFilter->GetValue() != "FlateDecode") + if (pFilter && pFilter->GetValue() != "FlateDecode") { SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: unexpected filter: " << pFilter->GetValue()); @@ -1633,16 +1644,21 @@ void PDFDocument::ReadXRefStream(SvStream& rStream) nPredictor = pPredictor->GetValue(); } - SvMemoryStream aSource(aBuf.data(), aBuf.size(), StreamMode::READ); SvMemoryStream aStream; - ZCodec aZCodec; - aZCodec.BeginCompression(); - aZCodec.Decompress(aSource, aStream); - if (!aZCodec.EndCompression()) + if (pFilter) { - SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: decompression failed"); - return; + SvMemoryStream aSource(aBuf.data(), aBuf.size(), StreamMode::READ); + ZCodec aZCodec; + aZCodec.BeginCompression(); + aZCodec.Decompress(aSource, aStream); + if (!aZCodec.EndCompression()) + { + SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: decompression failed"); + return; + } } + else + aStream.WriteBytes(aBuf.data(), aBuf.size()); // Look up the first and the last entry we need to read. auto pIndex = dynamic_cast(pObject->Lookup("Index"_ostr)); @@ -1702,8 +1718,9 @@ void PDFDocument::ReadXRefStream(SvStream& rStream) return; } int aW[nWSize]; - // First character is the (kind of) repeated predictor. - int nLineLength = 1; + // only a PNG predictor tags each row with the one it used + const int nPredictorBytes = nPredictor > 1 ? 1 : 0; + int nLineLength = nPredictorBytes; for (size_t i = 0; i < nWSize; ++i) { auto pI = dynamic_cast(pW->GetElements()[i]); @@ -1716,7 +1733,7 @@ void PDFDocument::ReadXRefStream(SvStream& rStream) nLineLength += aW[i]; } - if (nPredictor > 1 && nLineLength - 1 != nColumns) + if (nPredictor > 1 && nLineLength - nPredictorBytes != nColumns) { SAL_WARN("vcl.filter", "PDFDocument::ReadXRefStream: /DecodeParms/Columns is inconsistent with /W"); @@ -1751,7 +1768,8 @@ void PDFDocument::ReadXRefStream(SvStream& rStream) switch (nPredictor) { case 1: - // No prediction. + // no prediction, so the line is what the stream holds + aFilteredLine[i] = aOrigLine[i]; break; case 12: // PNG prediction: up (on all rows). @@ -1764,8 +1782,8 @@ void PDFDocument::ReadXRefStream(SvStream& rStream) } } - // First character is already handled above. - int nPos = 1; + // the predictor byte, where there is one, is already handled above + int nPos = nPredictorBytes; size_t nType = 0; // Start of the current field in the stream data. int nOffset = nPos; diff --git a/vcl/source/filter/itiff/itiff.cxx b/vcl/source/filter/itiff/itiff.cxx index 5b4fd134b191..04be6c8b2fce 100644 --- a/vcl/source/filter/itiff/itiff.cxx +++ b/vcl/source/filter/itiff/itiff.cxx @@ -132,10 +132,19 @@ bool ImportTiffGraphicImport(SvStream& rTIFF, Graphic& rGraphic) }); Context aContext(rTIFF); - TIFF* tif = TIFFClientOpen("libtiff-svstream", "r", &aContext, - tiff_read, tiff_write, - tiff_seek, tiff_close, - tiff_size, nullptr, nullptr); + + TIFFOpenOptions* pOptions = TIFFOpenOptionsAlloc(); + if (pOptions) + { + // Matches the largest strip accepted below, so a geometry that needs more than that + // fails inside libtiff rather than asking the system for the memory. + TIFFOpenOptionsSetMaxSingleMemAlloc(pOptions, SAL_MAX_INT32); + } + TIFF* tif = TIFFClientOpenExt("libtiff-svstream", "r", &aContext, + tiff_read, tiff_write, + tiff_seek, tiff_close, + tiff_size, nullptr, nullptr, pOptions); + TIFFOpenOptionsFree(pOptions); if (!tif) return false; @@ -242,8 +251,10 @@ bool ImportTiffGraphicImport(SvStream& rTIFF, Graphic& rGraphic) // parser that this replaced. But don't allow that for: // a) new compression variations that the old parser didn't handle // b) complicated pixel layout variations that the old parser didn't handle + // c) tiled images, which the old parser didn't handle // so we don't take libtiff into uncharted territory. - aContext.bAllowOneShortRead = !bNewCodec && PhotometricInterpretation != PHOTOMETRIC_YCBCR; + aContext.bAllowOneShortRead = !bNewCodec && PhotometricInterpretation != PHOTOMETRIC_YCBCR + && !TIFFIsTiled(tif); if (TIFFReadRGBAImageOriented(tif, w, h, raster.data(), ORIENTATION_TOPLEFT, 1)) { diff --git a/vcl/source/font/LogicalFontInstance.cxx b/vcl/source/font/LogicalFontInstance.cxx index 8a9be215d191..dfec985eb534 100644 --- a/vcl/source/font/LogicalFontInstance.cxx +++ b/vcl/source/font/LogicalFontInstance.cxx @@ -30,6 +30,127 @@ #include +static constexpr hb_codepoint_t ImplGetReplacementUnicodeForHyphens(const hb_codepoint_t aUnicode) +{ + if (aUnicode == 0x2010 || aUnicode == 0x2011) + return 0x002D; + + return 0x0; +} + +static hb_bool_t ImplHbNominalGlyphFunction(SAL_UNUSED_PARAMETER hb_font_t*, void* pFontData, + hb_codepoint_t aUnicode, hb_codepoint_t* pGlyphIndex, + SAL_UNUSED_PARAMETER void*) +{ + assert(pFontData && pGlyphIndex); + + hb_font_t* pHbParentFont = static_cast(pFontData); + hb_bool_t bRet = hb_font_get_nominal_glyph(pHbParentFont, aUnicode, pGlyphIndex); + if (!bRet) + { + hb_codepoint_t aReplacementUnicode = ImplGetReplacementUnicodeForHyphens(aUnicode); + if (aReplacementUnicode) + bRet = hb_font_get_nominal_glyph(pHbParentFont, aReplacementUnicode, pGlyphIndex); + } + + return bRet; +} + +static unsigned int +ImplHbNominalGlyphsFunction(SAL_UNUSED_PARAMETER hb_font_t* pHbFont, void* pFontData, + unsigned int nCount, const hb_codepoint_t* pFirstUnicode, + unsigned int nUnicodeStride, hb_codepoint_t* pFirstGlyph, + unsigned int nGlyphStride, SAL_UNUSED_PARAMETER void*) +{ + assert(pFontData && pFirstGlyph); + + hb_font_t* pHbParentFont = static_cast(pFontData); + unsigned int nRet = hb_font_get_nominal_glyphs(pHbParentFont, nCount, pFirstUnicode, + nUnicodeStride, pFirstGlyph, nGlyphStride); + if (nRet < nCount) + { + const char* pFailedUnicodeByte + = reinterpret_cast(pFirstUnicode) + (nRet * nUnicodeStride); + char* pFailedGlyphByte = reinterpret_cast(pFirstGlyph) + (nRet * nGlyphStride); + const hb_codepoint_t* pFailedUnicode + = reinterpret_cast(pFailedUnicodeByte); + hb_codepoint_t* pFailedGlyph = reinterpret_cast(pFailedGlyphByte); + + // Try to replace failed character with replacement character + if (ImplHbNominalGlyphFunction(pHbFont, pHbParentFont, *pFailedUnicode, pFailedGlyph, + nullptr)) + { + nRet++; + if (nRet < nCount) + { + const char* pNextUnicodeByte + = reinterpret_cast(pFirstUnicode) + (nRet * nUnicodeStride); + const hb_codepoint_t* pNextUnicode + = reinterpret_cast(pNextUnicodeByte); + char* pNextGlyphByte = reinterpret_cast(pFirstGlyph) + (nRet * nGlyphStride); + hb_codepoint_t* pNextGlyph = reinterpret_cast(pNextGlyphByte); + + // Process characters after replaced character + nRet += ImplHbNominalGlyphsFunction(pHbFont, pFontData, nCount - nRet, pNextUnicode, + nUnicodeStride, pNextGlyph, nGlyphStride, + nullptr); + } + } + } + + return nRet; +} + +static hb_bool_t ImplHbVariationGlyphFunction(SAL_UNUSED_PARAMETER hb_font_t*, void* pFontData, + hb_codepoint_t aUnicode, + hb_codepoint_t aVariationSelector, + hb_codepoint_t* pGlyphIndex, + SAL_UNUSED_PARAMETER void*) +{ + assert(pFontData && pGlyphIndex); + + hb_font_t* pHbParentFont = static_cast(pFontData); + hb_bool_t bRet + = hb_font_get_variation_glyph(pHbParentFont, aUnicode, aVariationSelector, pGlyphIndex); + if (!bRet) + { + hb_codepoint_t aReplacementUnicode = ImplGetReplacementUnicodeForHyphens(aUnicode); + if (aReplacementUnicode) + bRet = hb_font_get_variation_glyph(pHbParentFont, aReplacementUnicode, + aVariationSelector, pGlyphIndex); + } + + return bRet; +} + +// tdf#171555 if no glyph for hyphen characters use minus character +// On at least on macOS, HarfBuzz will not return a glyph for the +// Unicode hyphen characters for some fonts such as Courier New +// and Apple LiGothic but the native text layout functions will +// return the minus character's glyph. So replace the hyphens +// with minus to avoid marking the hyphens as needing fallback. +static void ImplHbSetGlyphFunctions(hb_font_t* pHbFont) +{ + static hb_font_funcs_t* pHbFontFuncs = nullptr; + + if (!pHbFontFuncs) + { + pHbFontFuncs = hb_font_funcs_create(); + + hb_font_funcs_set_nominal_glyph_func(pHbFontFuncs, ImplHbNominalGlyphFunction, nullptr, + nullptr); + hb_font_funcs_set_nominal_glyphs_func(pHbFontFuncs, ImplHbNominalGlyphsFunction, nullptr, + nullptr); + hb_font_funcs_set_variation_glyph_func(pHbFontFuncs, ImplHbVariationGlyphFunction, nullptr, + nullptr); + } + + // The parent font is already retained for the life of the font so pass + // the parent font pointer to avoid repeated lookups every time one of + // the font functions are called + hb_font_set_funcs(pHbFont, pHbFontFuncs, hb_font_get_parent(pHbFont), nullptr); +} + LogicalFontInstance::LogicalFontInstance(const vcl::font::PhysicalFontFace& rFontFace, const vcl::font::FontSelectPattern& rFontSelData) : mxFontMetric(new FontMetricData(rFontSelData)) @@ -116,6 +237,12 @@ hb_font_t* LogicalFontInstance::InitHbFont() ImplInitHbFont(pHbFont); + // Related: tdf#171555 create subfont and set nominal glyph function + hb_font_t* pHbParentFont = pHbFont; + pHbFont = hb_font_create_sub_font(pHbParentFont); + ImplHbSetGlyphFunctions(pHbFont); + hb_font_destroy(pHbParentFont); + return pHbFont; } diff --git a/vcl/source/font/PhysicalFontCollection.cxx b/vcl/source/font/PhysicalFontCollection.cxx index b3ebaf03a1ad..abd4a3002c4a 100644 --- a/vcl/source/font/PhysicalFontCollection.cxx +++ b/vcl/source/font/PhysicalFontCollection.cxx @@ -123,8 +123,8 @@ void PhysicalFontCollection::ImplInitGenericGlyphFallback() const "shree", "mangal", "", "raavi", "shruti", "tunga", "", "latha", "gautami", "kartika", "vrinda", "", - "shayyalmt", "naskmt", "scheherazade", "", "david", "nachlieli", "lucidagrande", "", + "shayyalmt", "naskmt", "Noto Naskh Arabic", "", "norasi", "angsanaupc", "", "khmerossystem", "", "muktinarrow", "", diff --git a/vcl/source/image/ImplImageTree.cxx b/vcl/source/image/ImplImageTree.cxx index a883b13c5025..ac69706fbfb3 100644 --- a/vcl/source/image/ImplImageTree.cxx +++ b/vcl/source/image/ImplImageTree.cxx @@ -57,11 +57,14 @@ #include #include #include +#include #include #include using namespace css; +static bool bPreferDark; //initialized in ImplImageTree c'tor + bool ImageRequestParameters::convertToDarkTheme() { static bool bIconsForDarkTheme = !!getenv("VCL_ICONS_FOR_DARK_THEME"); @@ -222,6 +225,7 @@ void loadImageFromStream(std::shared_ptr const & xStream, OUString con ImplImageTree::ImplImageTree() { + bPreferDark = ThemeColors::GetThemeColors().GetWindowColor().IsDark(); } ImplImageTree::~ImplImageTree() @@ -347,8 +351,13 @@ OUString ImplImageTree::fallbackStyle(std::u16string_view rsStyle) sResult = "breeze"; else if (rsStyle == u"sifr_dark" ) sResult = "breeze_dark"; - else + else if (rsStyle == u"colibre_dark" ) sResult = "colibre"; + else + if (bPreferDark) + sResult = "colibre_dark"; + else + sResult = "colibre"; return sResult; } diff --git a/vcl/source/outdev/CoordinateMapper.cxx b/vcl/source/outdev/CoordinateMapper.cxx index be2ae5a5ad20..d08604adc557 100644 --- a/vcl/source/outdev/CoordinateMapper.cxx +++ b/vcl/source/outdev/CoordinateMapper.cxx @@ -153,8 +153,7 @@ basegfx::B2DHomMatrix CoordinateMapper::GetInverseViewTransformation() const basegfx::B2DHomMatrix CoordinateMapper::GetViewTransformation(const MapMode& rMapMode) const { // #i82615# - ImplMapRes aMapRes; - aMapRes.CalcMapResolution(rMapMode, GetDPIX(), GetDPIY()); + ImplMapRes aMapRes(rMapMode, GetDPIX(), GetDPIY()); basegfx::B2DHomMatrix aTransform; @@ -272,6 +271,17 @@ tools::Long CoordinateMapper::ViewSubPixelToLogicUnitsIntY(double fY) const return ViewSubPixelToLogicDistanceY(fY) - maMapRes.mnMapOfsY; } +tools::Long CoordinateMapper::ViewSubPixelToLogicIntX(double fX, const ImplMapRes& rRes) const +{ + // Round distance (using custom scale), strip custom MapOfs, then strip internal OutOffLogic + return ViewToLogicDistanceX(std::llround(fX), rRes.mfMapScX) - rRes.mnMapOfsX - mnOutOffLogicX; +} + +tools::Long CoordinateMapper::ViewSubPixelToLogicIntY(double fY, const ImplMapRes& rRes) const +{ + return ViewToLogicDistanceY(std::llround(fY), rRes.mfMapScY) - rRes.mnMapOfsY - mnOutOffLogicY; +} + double CoordinateMapper::LogicUnitsToViewSubPixelX(double fX) const { return LogicToViewDistanceSubPixelX(std::llround(fX + maMapRes.mnMapOfsX), maMapRes.mfMapScX); diff --git a/vcl/source/outdev/ImplMapRes.cxx b/vcl/source/outdev/ImplMapRes.cxx index 939ece292c80..f6ce6c898b8a 100644 --- a/vcl/source/outdev/ImplMapRes.cxx +++ b/vcl/source/outdev/ImplMapRes.cxx @@ -29,6 +29,16 @@ #include +ImplMapRes::ImplMapRes(const MapMode& rMapMode, tools::Long nDPIX, tools::Long nDPIY) +{ + // Delegate the complex scaling math to the mutator + CalcMapResolution(rMapMode, nDPIX, nDPIY); + + // Because this is a fresh object, the origin is always absolute + mnMapOfsX = rMapMode.GetOrigin().X(); + mnMapOfsY = rMapMode.GetOrigin().Y(); +} + void ImplMapRes::SetMapRes(const o3tl::Length eUnit) { const auto[nNum, nDen] = o3tl::getConversionMulDiv(eUnit, o3tl::Length::in); @@ -146,4 +156,74 @@ ImplMapRes ImplMapRes::ResolveMapRes(const MapMode* pMode, const MapMode& rDefau return aRes; } +static tools::Long lcl_scaleLogicValue(const tools::Long nSourceValue, const double fSourceScale, + const double fDestScale) +{ + if (fDestScale == 0.0) + return 0; + + return std::llround(nSourceValue * fSourceScale / fDestScale); +} + +tools::Long ImplMapRes::ScaleDistanceX(const tools::Long nDistance, + const ImplMapRes& rDestRes) const +{ + // Distances only care about the scaling multiplier, not the origin offset + return lcl_scaleLogicValue(nDistance, mfMapScX, rDestRes.mfMapScX); +} + +tools::Long ImplMapRes::ScaleDistanceY(const tools::Long nDistance, + const ImplMapRes& rDestRes) const +{ + return lcl_scaleLogicValue(nDistance, mfMapScY, rDestRes.mfMapScY); +} + +// Adds the offset +tools::Long ImplMapRes::LocalToAbsoluteX(const tools::Long nLocalX) const +{ + return nLocalX + mnMapOfsX; +} + +// Subtracts the offset +tools::Long ImplMapRes::AbsoluteToLocalX(const tools::Long nAbsoluteX) const +{ + return nAbsoluteX - mnMapOfsX; +} + +// Adds the offset +tools::Long ImplMapRes::LocalToAbsoluteY(const tools::Long nLocalY) const +{ + return nLocalY + mnMapOfsY; +} + +// Subtracts the offset +tools::Long ImplMapRes::AbsoluteToLocalY(const tools::Long nAbsoluteY) const +{ + return nAbsoluteY - mnMapOfsY; +} + +tools::Long ImplMapRes::TransformPointX(const tools::Long nLocalX, const ImplMapRes& rDestRes) const +{ + // Add (Source to Absolute) + const tools::Long nAbsoluteX = LocalToAbsoluteX(nLocalX); + + // Scale + const tools::Long nScaledX = ScaleDistanceX(nAbsoluteX, rDestRes); + + // Subtract (Absolute to Dest) + return rDestRes.AbsoluteToLocalX(nScaledX); +} + +tools::Long ImplMapRes::TransformPointY(const tools::Long nLocalY, const ImplMapRes& rDestRes) const +{ + // Add (Source to Absolute) + const tools::Long nAbsoluteY = LocalToAbsoluteY(nLocalY); + + // Scale + const tools::Long nScaledY = ScaleDistanceY(nAbsoluteY, rDestRes); + + // Subtract (Absolute to Dest) + return rDestRes.AbsoluteToLocalY(nScaledY); +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/vcl/source/outdev/map.cxx b/vcl/source/outdev/map.cxx index d4d9482eb2a1..9ebba821c0bd 100644 --- a/vcl/source/outdev/map.cxx +++ b/vcl/source/outdev/map.cxx @@ -593,8 +593,7 @@ Point OutputDevice::LogicToPixel( const Point& rLogicPt, return rLogicPt; // convert MapMode resolution and convert - ImplMapRes aMapRes; - aMapRes.CalcMapResolution(rMapMode, mpMapper->GetDPIX(), mpMapper->GetDPIY()); + ImplMapRes aMapRes(rMapMode, mpMapper->GetDPIX(), mpMapper->GetDPIY()); return Point( mpMapper->ViewToWindowUnitsX(mpMapper->LogicUnitsToViewUnitsX(rLogicPt.X(), aMapRes)), @@ -610,8 +609,7 @@ Size OutputDevice::LogicToPixel( const Size& rLogicSize, return rLogicSize; // convert MapMode resolution and convert - ImplMapRes aMapRes; - aMapRes.CalcMapResolution(rMapMode, mpMapper->GetDPIX(), mpMapper->GetDPIY()); + ImplMapRes aMapRes(rMapMode, mpMapper->GetDPIX(), mpMapper->GetDPIY()); return Size(mpMapper->LogicToViewDistanceX(rLogicSize.Width(), aMapRes.mfMapScX), mpMapper->LogicToViewDistanceY(rLogicSize.Height(), aMapRes.mfMapScY)); @@ -625,8 +623,7 @@ tools::Rectangle OutputDevice::LogicToPixel( const tools::Rectangle& rLogicRect, return rLogicRect; // convert MapMode resolution - ImplMapRes aMapRes; - aMapRes.CalcMapResolution(rMapMode, mpMapper->GetDPIX(), mpMapper->GetDPIY()); + ImplMapRes aMapRes(rMapMode, mpMapper->GetDPIX(), mpMapper->GetDPIY()); tools::Rectangle aRetval( mpMapper->LogicToWindowUnitsX(rLogicRect.Left(), aMapRes), @@ -650,8 +647,7 @@ tools::Polygon OutputDevice::LogicToPixel( const tools::Polygon& rLogicPoly, if ( rMapMode.IsDefault() ) return rLogicPoly; - ImplMapRes aMapRes; - aMapRes.CalcMapResolution(rMapMode, mpMapper->GetDPIX(), mpMapper->GetDPIY()); + ImplMapRes aMapRes(rMapMode, mpMapper->GetDPIX(), mpMapper->GetDPIY()); const sal_uInt16 nPoints = rLogicPoly.GetSize(); tools::Polygon aPoly( rLogicPoly ); @@ -716,15 +712,16 @@ tools::Rectangle OutputDevice::PixelToLogic( const tools::Rectangle& rDeviceRect return rDeviceRect; tools::Rectangle aRetval( - mpMapper->ViewToLogicDistanceX(rDeviceRect.Left()) - mpMapper->GetMappingXOffset() - mpMapper->GetLogicalXOffset(), - mpMapper->ViewToLogicDistanceY(rDeviceRect.Top()) - mpMapper->GetMappingYOffset() - mpMapper->GetLogicalYOffset(), - rDeviceRect.IsWidthEmpty() ? 0 : mpMapper->ViewToLogicDistanceX(rDeviceRect.Right()) - mpMapper->GetMappingXOffset() - mpMapper->GetLogicalXOffset(), - rDeviceRect.IsHeightEmpty() ? 0 : mpMapper->ViewToLogicDistanceY(rDeviceRect.Bottom()) - mpMapper->GetMappingYOffset() - mpMapper->GetLogicalYOffset()); + mpMapper->ViewSubPixelToLogicIntX(rDeviceRect.Left()), + mpMapper->ViewSubPixelToLogicIntY(rDeviceRect.Top()), + rDeviceRect.IsWidthEmpty() ? 0 : mpMapper->ViewSubPixelToLogicIntX(rDeviceRect.Right()), + rDeviceRect.IsHeightEmpty() ? 0 : mpMapper->ViewSubPixelToLogicIntY(rDeviceRect.Bottom()) + ); - if(rDeviceRect.IsWidthEmpty()) + if (rDeviceRect.IsWidthEmpty()) aRetval.SetWidthEmpty(); - if(rDeviceRect.IsHeightEmpty()) + if (rDeviceRect.IsHeightEmpty()) aRetval.SetHeightEmpty(); return aRetval; @@ -732,23 +729,18 @@ tools::Rectangle OutputDevice::PixelToLogic( const tools::Rectangle& rDeviceRect tools::Polygon OutputDevice::PixelToLogic( const tools::Polygon& rDevicePoly ) const { - if ( !mpMapper->IsMapModeEnabled() ) return rDevicePoly; const sal_uInt16 nPoints = rDevicePoly.GetSize(); tools::Polygon aPoly( rDevicePoly ); - // get pointer to Point-array (copy data) - const Point* pPointAry = aPoly.GetConstPointAry(); - - for (sal_uInt16 i = 0; i < nPoints; i++) + for (sal_uInt16 i = 0; i < nPoints; ++i) { - const Point* pPt = &(pPointAry[i]); - Point aPt; - aPt.setX(mpMapper->ViewToLogicDistanceX(pPt->X()) - mpMapper->GetMappingXOffset() - mpMapper->GetLogicalXOffset()); - aPt.setY(mpMapper->ViewToLogicDistanceY(pPt->Y()) - mpMapper->GetMappingYOffset() - mpMapper->GetLogicalYOffset()); - aPoly[i] = aPt; + aPoly[i] = Point( + mpMapper->ViewSubPixelToLogicIntX(aPoly[i].X()), + mpMapper->ViewSubPixelToLogicIntY(aPoly[i].Y()) + ); } return aPoly; @@ -824,17 +816,17 @@ vcl::Region OutputDevice::PixelToLogic( const vcl::Region& rDeviceRegion ) const Point OutputDevice::PixelToLogic( const Point& rDevicePt, const MapMode& rMapMode ) const { - // calculate nothing if default-MapMode if ( rMapMode.IsDefault() ) return rDevicePt; - // calculate MapMode-resolution and convert - ImplMapRes aMapRes; - aMapRes.CalcMapResolution(rMapMode, mpMapper->GetDPIX(), mpMapper->GetDPIY()); + // calculate MapMode-resolution + ImplMapRes aMapRes(rMapMode, mpMapper->GetDPIX(), mpMapper->GetDPIY()); - return Point(mpMapper->ViewToLogicDistanceX(rDevicePt.X(), aMapRes.mfMapScX) - aMapRes.mnMapOfsX - mpMapper->GetLogicalXOffset(), - mpMapper->ViewToLogicDistanceY(rDevicePt.Y(), aMapRes.mfMapScY) - aMapRes.mnMapOfsY - mpMapper->GetLogicalYOffset()); + return Point( + mpMapper->ViewSubPixelToLogicIntX(rDevicePt.X(), aMapRes), + mpMapper->ViewSubPixelToLogicIntY(rDevicePt.Y(), aMapRes) + ); } Size OutputDevice::PixelToLogic( const Size& rDeviceSize, @@ -846,35 +838,34 @@ Size OutputDevice::PixelToLogic( const Size& rDeviceSize, return rDeviceSize; // calculate MapMode-resolution and convert - ImplMapRes aMapRes; - aMapRes.CalcMapResolution(rMapMode, mpMapper->GetDPIX(), mpMapper->GetDPIY()); + ImplMapRes aMapRes(rMapMode, mpMapper->GetDPIX(), mpMapper->GetDPIY()); return Size(mpMapper->ViewToLogicDistanceX(rDeviceSize.Width(), aMapRes.mfMapScX), mpMapper->ViewToLogicDistanceY(rDeviceSize.Height(), aMapRes.mfMapScY)); } tools::Rectangle OutputDevice::PixelToLogic( const tools::Rectangle& rDeviceRect, - const MapMode& rMapMode ) const + const MapMode& rMapMode ) const { // calculate nothing if default-MapMode // tdf#141761 see comments above, IsEmpty() removed if ( rMapMode.IsDefault() ) return rDeviceRect; - // calculate MapMode-resolution and convert - ImplMapRes aMapRes; - aMapRes.CalcMapResolution(rMapMode, mpMapper->GetDPIX(), mpMapper->GetDPIY()); + // calculate MapMode-resolution + ImplMapRes aMapRes(rMapMode, mpMapper->GetDPIX(), mpMapper->GetDPIY()); tools::Rectangle aRetval( - mpMapper->ViewToLogicDistanceX(rDeviceRect.Left(), aMapRes.mfMapScX) - aMapRes.mnMapOfsX - mpMapper->GetLogicalXOffset(), - mpMapper->ViewToLogicDistanceY(rDeviceRect.Top(), aMapRes.mfMapScY) - aMapRes.mnMapOfsY - mpMapper->GetLogicalYOffset(), - rDeviceRect.IsWidthEmpty() ? 0 : mpMapper->ViewToLogicDistanceX(rDeviceRect.Right(), aMapRes.mfMapScX) - aMapRes.mnMapOfsX - mpMapper->GetLogicalXOffset(), - rDeviceRect.IsHeightEmpty() ? 0 : mpMapper->ViewToLogicDistanceY(rDeviceRect.Bottom(), aMapRes.mfMapScY) - aMapRes.mnMapOfsY - mpMapper->GetLogicalYOffset()); + mpMapper->ViewSubPixelToLogicIntX(rDeviceRect.Left(), aMapRes), + mpMapper->ViewSubPixelToLogicIntY(rDeviceRect.Top(), aMapRes), + rDeviceRect.IsWidthEmpty() ? 0 : mpMapper->ViewSubPixelToLogicIntX(rDeviceRect.Right(), aMapRes), + rDeviceRect.IsHeightEmpty() ? 0 : mpMapper->ViewSubPixelToLogicIntY(rDeviceRect.Bottom(), aMapRes) + ); - if(rDeviceRect.IsWidthEmpty()) + if (rDeviceRect.IsWidthEmpty()) aRetval.SetWidthEmpty(); - if(rDeviceRect.IsHeightEmpty()) + if (rDeviceRect.IsHeightEmpty()) aRetval.SetHeightEmpty(); return aRetval; @@ -883,28 +874,22 @@ tools::Rectangle OutputDevice::PixelToLogic( const tools::Rectangle& rDeviceRect tools::Polygon OutputDevice::PixelToLogic( const tools::Polygon& rDevicePoly, const MapMode& rMapMode ) const { - // calculate nothing if default-MapMode if ( rMapMode.IsDefault() ) return rDevicePoly; - // calculate MapMode-resolution and convert - ImplMapRes aMapRes; - aMapRes.CalcMapResolution(rMapMode, mpMapper->GetDPIX(), mpMapper->GetDPIY()); + // calculate MapMode-resolution + ImplMapRes aMapRes(rMapMode, mpMapper->GetDPIX(), mpMapper->GetDPIY()); const sal_uInt16 nPoints = rDevicePoly.GetSize(); tools::Polygon aPoly( rDevicePoly ); - // get pointer to Point-array (copy data) - const Point* pPointAry = aPoly.GetConstPointAry(); - - for (sal_uInt16 i = 0; i < nPoints; i++) + for (sal_uInt16 i = 0; i < nPoints; ++i) { - const Point* pPt = &(pPointAry[i]); - Point aPt; - aPt.setX(mpMapper->ViewToLogicDistanceX(pPt->X(), aMapRes.mfMapScX) - aMapRes.mnMapOfsX - mpMapper->GetLogicalXOffset()); - aPt.setY(mpMapper->ViewToLogicDistanceY(pPt->Y(), aMapRes.mfMapScY) - aMapRes.mnMapOfsY - mpMapper->GetLogicalYOffset()); - aPoly[i] = aPt; + aPoly[i] = Point( + mpMapper->ViewSubPixelToLogicIntX(aPoly[i].X(), aMapRes), + mpMapper->ViewSubPixelToLogicIntY(aPoly[i].Y(), aMapRes) + ); } return aPoly; @@ -965,15 +950,6 @@ static std::pair lcl_calcConversionMapRes(const MapMode& return result; } -static tools::Long lcl_scaleLogicValue( const tools::Long n1, - const double n2, - const double n3 ) -{ - if (n3 == 0.0) - return 0; - return std::llround(n1 * n2 / n3); -} - static tools::Long lcl_convertLogicValue(const tools::Long n1, const o3tl::Length eFrom, const o3tl::Length eTo) { if (n1 == 0 || eFrom == o3tl::Length::invalid || eTo == o3tl::Length::invalid) @@ -981,12 +957,6 @@ static tools::Long lcl_convertLogicValue(const tools::Long n1, const o3tl::Lengt bool bOverflow; const auto nResult = o3tl::convert(n1, eFrom, eTo, bOverflow); - if (bOverflow) - { - const auto [n2, n3] = o3tl::getConversionMulDiv(eFrom, eTo); - BigInt a4 = n1; - a4 *= n2; - } if (!bOverflow) return nResult; @@ -1018,8 +988,8 @@ Point OutputDevice::LogicToLogic( const Point& rPtSource, ImplMapRes aMapResSource = mpMapper->ResolveMapRes(pMapModeSource); ImplMapRes aMapResDest = mpMapper->ResolveMapRes(pMapModeDest); - return Point(lcl_scaleLogicValue(rPtSource.X() + aMapResSource.mnMapOfsX, aMapResSource.mfMapScX, aMapResDest.mfMapScX) - aMapResDest.mnMapOfsX, - lcl_scaleLogicValue(rPtSource.Y() + aMapResSource.mnMapOfsY, aMapResSource.mfMapScY, aMapResDest.mfMapScY) - aMapResDest.mnMapOfsY); + return Point(aMapResSource.TransformPointX(rPtSource.X(), aMapResDest), + aMapResSource.TransformPointY(rPtSource.Y(), aMapResDest)); } Size OutputDevice::LogicToLogic( const Size& rSzSource, @@ -1035,8 +1005,8 @@ Size OutputDevice::LogicToLogic( const Size& rSzSource, ImplMapRes aMapResSource = mpMapper->ResolveMapRes(pMapModeSource); ImplMapRes aMapResDest = mpMapper->ResolveMapRes(pMapModeDest); - return Size(lcl_scaleLogicValue(rSzSource.Width(), aMapResSource.mfMapScX, aMapResDest.mfMapScX), - lcl_scaleLogicValue(rSzSource.Height(), aMapResSource.mfMapScY, aMapResDest.mfMapScY)); + return Size(aMapResSource.ScaleDistanceX(rSzSource.Width(), aMapResDest), + aMapResSource.ScaleDistanceY(rSzSource.Height(), aMapResDest)); } tools::Rectangle OutputDevice::LogicToLogic( const tools::Rectangle& rRectSource, @@ -1052,10 +1022,10 @@ tools::Rectangle OutputDevice::LogicToLogic( const tools::Rectangle& rRectSource ImplMapRes aMapResSource = mpMapper->ResolveMapRes(pMapModeSource); ImplMapRes aMapResDest = mpMapper->ResolveMapRes(pMapModeDest); - return tools::Rectangle(lcl_scaleLogicValue(rRectSource.Left() + aMapResSource.mnMapOfsX, aMapResSource.mfMapScX, aMapResDest.mfMapScX) - aMapResDest.mnMapOfsX, - lcl_scaleLogicValue(rRectSource.Top() + aMapResSource.mnMapOfsY, aMapResSource.mfMapScY, aMapResDest.mfMapScY) - aMapResDest.mnMapOfsY, - lcl_scaleLogicValue(rRectSource.Right() + aMapResSource.mnMapOfsX, aMapResSource.mfMapScX, aMapResDest.mfMapScX) - aMapResDest.mnMapOfsX, - lcl_scaleLogicValue(rRectSource.Bottom() + aMapResSource.mnMapOfsY, aMapResSource.mfMapScY, aMapResDest.mfMapScY) - aMapResDest.mnMapOfsY); + return tools::Rectangle(aMapResSource.TransformPointX(rRectSource.Left(), aMapResDest), + aMapResSource.TransformPointY(rRectSource.Top(), aMapResDest), + aMapResSource.TransformPointX(rRectSource.Right(), aMapResDest), + aMapResSource.TransformPointY(rRectSource.Bottom(), aMapResDest)); } Point OutputDevice::LogicToLogic( const Point& rPtSource, @@ -1077,8 +1047,8 @@ Point OutputDevice::LogicToLogic( const Point& rPtSource, const auto [aMapResSource, aMapResDest] = lcl_calcConversionMapRes( rMapModeSource, rMapModeDest ); - return Point(lcl_scaleLogicValue(rPtSource.X() + aMapResSource.mnMapOfsX, aMapResSource.mfMapScX, aMapResDest.mfMapScX) - aMapResDest.mnMapOfsX, - lcl_scaleLogicValue(rPtSource.Y() + aMapResSource.mnMapOfsY, aMapResSource.mfMapScY, aMapResDest.mfMapScY) - aMapResDest.mnMapOfsY); + return Point(aMapResSource.TransformPointX(rPtSource.X(), aMapResDest), + aMapResSource.TransformPointY(rPtSource.Y(), aMapResDest)); } Size OutputDevice::LogicToLogic( const Size& rSzSource, @@ -1100,8 +1070,8 @@ Size OutputDevice::LogicToLogic( const Size& rSzSource, const auto [aMapResSource, aMapResDest] = lcl_calcConversionMapRes( rMapModeSource, rMapModeDest ); - return Size(lcl_scaleLogicValue(rSzSource.Width(), aMapResSource.mfMapScX, aMapResDest.mfMapScX), - lcl_scaleLogicValue(rSzSource.Height(), aMapResSource.mfMapScY, aMapResDest.mfMapScY)); + return Size(aMapResSource.ScaleDistanceX(rSzSource.Width(), aMapResDest), + aMapResSource.ScaleDistanceY(rSzSource.Height(), aMapResDest)); } basegfx::B2DPolygon OutputDevice::LogicToLogic( const basegfx::B2DPolygon& rPolySource, @@ -1190,14 +1160,12 @@ tools::Rectangle OutputDevice::LogicToLogic( const tools::Rectangle& rRectSource { const auto [aMapResSource, aMapResDest] = lcl_calcConversionMapRes( rMapModeSource, rMapModeDest ); - auto left = lcl_scaleLogicValue(rRectSource.Left() + aMapResSource.mnMapOfsX, aMapResSource.mfMapScX, aMapResDest.mfMapScX) - aMapResDest.mnMapOfsX; - auto top = lcl_scaleLogicValue(rRectSource.Top() + aMapResSource.mnMapOfsY, aMapResSource.mfMapScY, aMapResDest.mfMapScY) - aMapResDest.mnMapOfsY; + auto left = aMapResSource.TransformPointX(rRectSource.Left(), aMapResDest); + auto top = aMapResSource.TransformPointY(rRectSource.Top(), aMapResDest); // tdf#141761 see comments above, IsEmpty() removed - auto right = rRectSource.IsWidthEmpty() ? 0 : - lcl_scaleLogicValue(rRectSource.Right() + aMapResSource.mnMapOfsX, aMapResSource.mfMapScX, aMapResDest.mfMapScX) - aMapResDest.mnMapOfsX; - auto bottom = rRectSource.IsHeightEmpty() ? 0 : - lcl_scaleLogicValue(rRectSource.Bottom() + aMapResSource.mnMapOfsY, aMapResSource.mfMapScY, aMapResDest.mfMapScY) - aMapResDest.mnMapOfsY; + auto right = rRectSource.IsWidthEmpty() ? 0 : aMapResSource.TransformPointX(rRectSource.Right(), aMapResDest); + auto bottom = rRectSource.IsHeightEmpty() ? 0 : aMapResSource.TransformPointY(rRectSource.Bottom(), aMapResDest); aRetval = tools::Rectangle(left, top, right, bottom); } diff --git a/vcl/source/outdev/text.cxx b/vcl/source/outdev/text.cxx index 5702c4df4060..fd085a9902f9 100644 --- a/vcl/source/outdev/text.cxx +++ b/vcl/source/outdev/text.cxx @@ -55,6 +55,23 @@ #define TEXT_DRAW_ELLIPSIS (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) +OUString OutputDevice::GetFontReplacement(const OUString& sMissingFontName) +{ + if (getenv("SAL_NON_APPLICATION_FONT_USE") == nullptr) + { + vcl::Font aOldFont(GetFont()); + vcl::Font aFont(sMissingFontName, Size(0, 12)); + aFont.SetLanguageTag(Application::GetSettings().GetLanguageTag()); + SetFont(aFont); + const LogicalFontInstance* pFontInstance = GetFontInstance(); + const vcl::font::PhysicalFontFace* aFontFace = pFontInstance->GetFontFace(); + SetFont(aOldFont); + return aFontFace->GetName(vcl::font::NAME_ID_FULL_NAME); + } + else + return sMissingFontName; +} + void OutputDevice::SetLayoutMode( vcl::text::ComplexTextLayoutFlags nTextLayoutMode ) { if( mpMetaFile ) diff --git a/vcl/source/pdf/PDFiumLibrary.cxx b/vcl/source/pdf/PDFiumLibrary.cxx index e015fb966594..3c8c6f4f0875 100644 --- a/vcl/source/pdf/PDFiumLibrary.cxx +++ b/vcl/source/pdf/PDFiumLibrary.cxx @@ -456,12 +456,10 @@ public: std::unique_ptr getFont() override; PDFTextRenderMode getTextRenderMode() override; Color getFillColor() override; - std::unique_ptr getRenderedFillPattern(PDFiumDocument& rDoc, - PDFiumPage& rPage) override; + std::unique_ptr getRenderedFillPattern(PDFiumDocument& rDoc) override; Color getStrokeColor() override; double getStrokeWidth() override; - std::unique_ptr getRenderedStrokePattern(PDFiumDocument& rDoc, - PDFiumPage& rPage) override; + std::unique_ptr getRenderedStrokePattern(PDFiumDocument& rDoc) override; // Path int getPathSegmentCount() override; std::unique_ptr getPathSegment(int index) override; @@ -1433,14 +1431,11 @@ std::unique_ptr PDFiumPageObjectImpl::getImageBitmap() return pPDFiumBitmap; } -std::unique_ptr PDFiumPageObjectImpl::getRenderedStrokePattern(PDFiumDocument& rDoc, - PDFiumPage& rPage) +std::unique_ptr PDFiumPageObjectImpl::getRenderedStrokePattern(PDFiumDocument& rDoc) { auto& rDocImpl = static_cast(rDoc); - auto& rPageImpl = static_cast(rPage); std::unique_ptr pPDFiumBitmap; - FPDF_BITMAP pBitmap = FPDFPageObj_GetRenderedStrokePattern( - rDocImpl.getPointer(), rPageImpl.getPointer(), mpPageObject); + FPDF_BITMAP pBitmap = FPDFPageObj_GetRenderedStrokePattern(rDocImpl.getPointer(), mpPageObject); if (pBitmap) { pPDFiumBitmap = std::make_unique(pBitmap); @@ -1448,14 +1443,11 @@ std::unique_ptr PDFiumPageObjectImpl::getRenderedStrokePattern(PDF return pPDFiumBitmap; } -std::unique_ptr PDFiumPageObjectImpl::getRenderedFillPattern(PDFiumDocument& rDoc, - PDFiumPage& rPage) +std::unique_ptr PDFiumPageObjectImpl::getRenderedFillPattern(PDFiumDocument& rDoc) { auto& rDocImpl = static_cast(rDoc); - auto& rPageImpl = static_cast(rPage); std::unique_ptr pPDFiumBitmap; - FPDF_BITMAP pBitmap = FPDFPageObj_GetRenderedFillPattern(rDocImpl.getPointer(), - rPageImpl.getPointer(), mpPageObject); + FPDF_BITMAP pBitmap = FPDFPageObj_GetRenderedFillPattern(rDocImpl.getPointer(), mpPageObject); if (pBitmap) { pPDFiumBitmap = std::make_unique(pBitmap); diff --git a/vcl/source/pdf/pdfextoutdevdata.cxx b/vcl/source/pdf/pdfextoutdevdata.cxx index 80a21ef49aa9..d90626699c31 100644 --- a/vcl/source/pdf/pdfextoutdevdata.cxx +++ b/vcl/source/pdf/pdfextoutdevdata.cxx @@ -75,6 +75,18 @@ struct SetLinkDest { sal_Int32 mnLinkId; sal_Int32 mnDestId; }; +/// a destination and the element it names, for the destination's /SD +struct SetDestStructureElement +{ + sal_Int32 mnDestId; + sal_Int32 mnStructElementId; +}; +/// an element and what its content refers to, for the element's /Ref +struct AddStructureRef +{ + sal_Int32 mnElementId; + sal_Int32 mnRefElementId; +}; struct SetLinkURL { OUString maLinkURL; sal_Int32 mnLinkId; @@ -144,6 +156,8 @@ typedef std::variant(action)) + { + const vcl::SetDestStructureElement& rSet = std::get(action); + rWriter.SetDestStructureElement(GetMappedId(rSet.mnDestId), rSet.mnStructElementId); + } + else if (std::holds_alternative(action)) + { + const vcl::AddStructureRef& rAdd = std::get(action); + rWriter.AddStructureRef(rAdd.mnElementId, rAdd.mnRefElementId); + } else if (std::holds_alternative(action)) { const vcl::SetLinkURL& rSetLinkURL = std::get(action); sal_Int32 nLinkId = GetMappedId(rSetLinkURL.mnLinkId); @@ -703,6 +727,16 @@ sal_Int32 PDFExtOutDevData::CreateScreen(const tools::Rectangle& rRect, return it->second; } +void PDFExtOutDevData::SetDestStructureElement(sal_Int32 nDestId, sal_Int32 nStructElementId) +{ + mpGlobalSyncData->mActions.push_back(vcl::SetDestStructureElement{ nDestId, nStructElementId }); +} + +void PDFExtOutDevData::AddStructureRef(sal_Int32 nElementId, sal_Int32 nRefElementId) +{ + mpGlobalSyncData->mActions.push_back(vcl::AddStructureRef{ nElementId, nRefElementId }); +} + void PDFExtOutDevData::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId ) { mpGlobalSyncData->mActions.push_back( vcl::SetLinkDest{ nLinkId, nDestId } ); diff --git a/vcl/source/pdf/pdfwriter.cxx b/vcl/source/pdf/pdfwriter.cxx index 69c8d59224f6..c10e5b308f13 100644 --- a/vcl/source/pdf/pdfwriter.cxx +++ b/vcl/source/pdf/pdfwriter.cxx @@ -353,6 +353,16 @@ void PDFWriter::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId ) xImplementation->setLinkDest( nLinkId, nDestId ); } +void PDFWriter::SetDestStructureElement(sal_Int32 nDestId, sal_Int32 nStructElementId) +{ + xImplementation->setDestStructureElement(nDestId, nStructElementId); +} + +void PDFWriter::AddStructureRef(sal_Int32 nElementId, sal_Int32 nRefElementId) +{ + xImplementation->addStructureRef(nElementId, nRefElementId); +} + void PDFWriter::SetLinkURL( sal_Int32 nLinkId, const OUString& rURL ) { xImplementation->setLinkURL( nLinkId, rURL ); diff --git a/vcl/source/pdf/pdfwriter_impl.cxx b/vcl/source/pdf/pdfwriter_impl.cxx index 59998209b1d9..e1717e082ef1 100644 --- a/vcl/source/pdf/pdfwriter_impl.cxx +++ b/vcl/source/pdf/pdfwriter_impl.cxx @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -126,6 +127,118 @@ namespace void removePlaceholderSE(std::vector & rStructure, PDFStructureElement& rEle); +// the types ISO 32000-2 14.8.6 left out of the PDF 2.0 standard structure namespace +bool isPDF17OnlyType(StructElement eType) +{ + switch (eType) + { + case StructElement::Article: + case StructElement::BlockQuote: + case StructElement::TOC: + case StructElement::TOCI: + case StructElement::Index: + case StructElement::Quote: + case StructElement::Note: + case StructElement::Reference: + case StructElement::BibEntry: + case StructElement::Code: + return true; + default: + return false; + } +} + +// the attributes this writer can set that ISO 32000-2 14.8.5.4 measures in user space units; +// every numerical layout attribute prints as thousandths of a point, so a new one belongs here +bool isLengthAttribute(PDFWriter::StructAttribute eAttr) +{ + switch (eAttr) + { + case PDFWriter::SpaceBefore: + case PDFWriter::SpaceAfter: + case PDFWriter::StartIndent: + case PDFWriter::EndIndent: + case PDFWriter::TextIndent: + case PDFWriter::Width: + case PDFWriter::Height: + case PDFWriter::LineHeight: + case PDFWriter::BaselineShift: + return true; + default: + return false; + } +} + +constexpr auto constTagStrings = frozen::make_unordered_map({ + { StructElement::NonStructElement, "NonStruct" }, + { StructElement::Document, "Document" }, + { StructElement::Part, "Part" }, + { StructElement::Article, "Art" }, + { StructElement::Section, "Sect" }, + { StructElement::Division, "Div" }, + { StructElement::BlockQuote, "BlockQuote" }, + { StructElement::Caption, "Caption" }, + { StructElement::TOC, "TOC" }, + { StructElement::TOCI, "TOCI" }, + { StructElement::Index, "Index" }, + { StructElement::Paragraph, "P" }, + { StructElement::Heading, "H" }, + { StructElement::H1, "H1" }, + { StructElement::H2, "H2" }, + { StructElement::H3, "H3" }, + { StructElement::H4, "H4" }, + { StructElement::H5, "H5" }, + { StructElement::H6, "H6" }, + { StructElement::List, "L" }, + { StructElement::ListItem, "LI" }, + { StructElement::LILabel, "Lbl" }, + { StructElement::LIBody, "LBody" }, + { StructElement::Table, "Table" }, + { StructElement::TableRow, "TR" }, + { StructElement::TableHeader, "TH" }, + { StructElement::TableData, "TD" }, + { StructElement::Span, "Span" }, + { StructElement::Quote, "Quote" }, + { StructElement::Note, "Note" }, + { StructElement::Reference, "Reference" }, + { StructElement::BibEntry, "BibEntry" }, + { StructElement::Code, "Code" }, + { StructElement::Link, "Link" }, + { StructElement::Annot, "Annot" }, + { StructElement::Ruby, "Ruby" }, + { StructElement::RB, "RB" }, + { StructElement::RT, "RT" }, + { StructElement::RP, "RP" }, + { StructElement::Warichu, "Warichu" }, + { StructElement::WT, "WT" }, + { StructElement::WP, "WP" }, + { StructElement::Figure, "Figure" }, + { StructElement::Formula, "Formula" }, + { StructElement::Form, "Form" }, + { StructElement::Title, "Title" }, + { StructElement::Emphasis, "Em" }, + { StructElement::Strong, "Strong" }, + { StructElement::FENote, "FENote" }, +}); + +// standard structure types getStructureTag never returns, so the table above lacks them: +// ISO 32000-1 14.8.4, and the ones ISO 32000-2 adds +// Annot is standard from PDF 1.5, and below that getStructureTag answers Figure for it +constexpr std::string_view constUnusedTagStrings[]{ "Annot", "Artifact", "Private", + "THead", "TBody", "TFoot" }; +constexpr std::string_view constUnusedTagStrings20[]{ "Aside", "DocumentFragment", "Sub" }; + +// PDF 2.0 has heading levels without a limit, so H7 and up are standard there as well; +// H1 to H6 are the table's, and must not answer here, or the level could be claimed wrong +bool isDeepHeading(std::string_view aName) +{ + return aName.size() > 1 && aName[0] == 'H' && aName[1] != '0' + && std::ranges::all_of( + aName.substr(1), + [](char c) { return rtl::isAsciiDigit(static_cast(c)); }) + && o3tl::toInt32(aName.substr(1)) > 6; +} + } // end anonymous namespace void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl ) @@ -417,6 +530,8 @@ PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext, if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0) { m_aNamespacesMap.emplace(constNamespacePDF2, createObject()); + // some types exist only in the PDF 1.7 namespace + m_aNamespacesMap.emplace(constNamespacePDF17, createObject()); } if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 ) @@ -917,30 +1032,6 @@ OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle ) { appendStructureAttributeLine( attribute.first, attribute.second, aTable, false ); } - else if( attribute.first == PDFWriter::LinkAnnotation ) - { - sal_Int32 nLink = attribute.second.nValue; - std::map< sal_Int32, sal_Int32 >::const_iterator link_it = - m_aLinkPropertyMap.find( nLink ); - if( link_it != m_aLinkPropertyMap.end() ) - nLink = link_it->second; - if( nLink >= 0 && o3tl::make_unsigned(nLink) < m_aLinks.size() ) - { - AppendAnnotKid(i_rEle, m_aLinks[nLink]); - } - else - { - OSL_FAIL( "unresolved link id for Link structure" ); - SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure"); - if (g_bDebugDisableCompression) - { - OString aLine = "unresolved link id " + - OString::number( nLink ) + - " for Link structure"; - emitComment( aLine.getStr() ); - } - } - } else if (attribute.first == PDFWriter::NoteAnnotation) { sal_Int32 nNote = attribute.second.nValue; @@ -1035,12 +1126,13 @@ void PDFWriterImpl::emitNamespaces() aWriter.startDict(); aWriter.write("/Type", "/Namespace"); aWriter.writeKeyAndLiteral("/NS", sNamespace); - if( ! m_aRoleMap.empty() ) + // the 1.7 namespace needs none: for it the structure tree root's RoleMap is the fallback + if (sNamespace == constNamespacePDF2 && !m_aRoleMap.empty()) { aLine.append( "/RoleMapNS<<" ); for (auto const& role : m_aRoleMap) { - aLine.append( "/" + role.first + "/" + role.second + "\n" ); + aLine.append("/" + role.first + "/" + role.second.m_aTag + "\n"); } aLine.append( ">>\n" ); } @@ -1101,24 +1193,28 @@ sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) aLine.append("/StructTreeRoot\n"); aWriter.writeKeyAndReference("/ParentTree", nParentTree); - // Write the reference to the PDF 2.0 namespace - if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0) + // Write the references to the namespaces the elements below use + OStringBuffer aNamespaces; + // m_aNamespacesMap is unordered, so name them rather than iterate it + for (auto const& rURI : { constNamespacePDF2, constNamespacePDF17 }) { - auto iterator = m_aNamespacesMap.find(constNamespacePDF2); + auto iterator = m_aNamespacesMap.find(rURI); if (iterator != m_aNamespacesMap.end()) { - aLine.append("/Namespaces ["); - aWriter.writeReference(iterator->second); - aLine.append("]"); + if (!aNamespaces.isEmpty()) + aNamespaces.append(" "); + appendObjectReference(iterator->second, aNamespaces); } } + if (!aNamespaces.isEmpty()) + aLine.append("/Namespaces [" + aNamespaces + "]"); if( ! m_aRoleMap.empty() ) { aLine.append( "/RoleMap<<" ); for (auto const& role : m_aRoleMap) { - aLine.append( "/" + role.first + "/" + role.second + "\n" ); + aLine.append("/" + role.first + "/" + role.second.m_aTag + "\n"); } aLine.append( ">>\n" ); } @@ -1134,13 +1230,12 @@ sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) { aLine.append("/StructElem"); - // Write the reference to the PDF 2.0 namespace - if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0) - { - auto iterator = m_aNamespacesMap.find(constNamespacePDF2); - if (iterator != m_aNamespacesMap.end()) - aWriter.writeKeyAndReference("/NS", iterator->second); - } + // tdf#173162 a type PDF 2.0 dropped stays in the 1.7 namespace, which ISO 14289-2 + // 8.2.4 allows alongside it + auto iterator = m_aNamespacesMap.find( + isPDF17OnlyType(*rEle.m_oType) ? constNamespacePDF17 : constNamespacePDF2); + if (iterator != m_aNamespacesMap.end()) + aWriter.writeKeyAndReference("/NS", iterator->second); aLine.append("/S/"); if( !rEle.m_aAlias.isEmpty() ) aLine.append( rEle.m_aAlias ); @@ -1159,6 +1254,16 @@ sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) "/Pg " + OString::number(rEle.m_nFirstPageObject) + " 0 R\n" ); + if (!rEle.m_RefElements.empty()) + { + aLine.append("/Ref["); + for (const auto nRef : rEle.m_RefElements) + { + aLine.append(" "); + appendObjectReference(m_aStructure[nRef].m_nObject, aLine); + } + aLine.append("]\n"); + } if( !rEle.m_aActualText.isEmpty() ) { aLine.append( "/ActualText" ); @@ -1223,6 +1328,27 @@ sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) } } } + for (const auto id : rEle.m_LinkAnnotIds) + { + sal_Int32 nLink(id); + const auto it(m_aLinkPropertyMap.find(nLink)); + if (it != m_aLinkPropertyMap.end()) + nLink = it->second; + if (0 <= nLink && o3tl::make_unsigned(nLink) < m_aLinks.size()) + { + AppendAnnotKid(rEle, m_aLinks[nLink]); + } + else + { + SAL_WARN("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure"); + if (g_bDebugDisableCompression) + { + const OString aComment + = "unresolved link id " + OString::number(nLink) + " for Link structure"; + emitComment(aComment.getStr()); + } + } + } if( ! rEle.m_aKids.empty() ) { unsigned int i = 0; @@ -2528,8 +2654,7 @@ sal_Int32 PDFWriterImpl::emitOutline() // Dest is not required if( rItem.m_nDestID >= 0 && o3tl::make_unsigned(rItem.m_nDestID) < m_aDests.size() ) { - aLine.append( "/Dest" ); - appendDest( rItem.m_nDestID, aLine ); + appendDestOrGoTo(rItem.m_nDestID, aLine); } aLine.append( "/Parent " + OString::number( rItem.m_nParentObject ) @@ -2555,6 +2680,47 @@ sal_Int32 PDFWriterImpl::emitOutline() return m_aOutline[0].m_nObject; } +bool PDFWriterImpl::appendStructureDest(sal_Int32 nDestID, OStringBuffer& rBuffer) +{ + if (nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size()) + return false; + + const PDFDest& rDest = m_aDests[nDestID]; + if (rDest.m_nStructElement < 0 + || o3tl::make_unsigned(rDest.m_nStructElement) >= m_aStructure.size()) + return false; + + const sal_Int32 nObject(m_aStructure[rDest.m_nStructElement].m_nObject); + if (nObject <= 0) // not emitted: NonStruct, or no structure at all + return false; + + // same as the page destination, bar the first entry + OStringBuffer aPageDest; + if (!appendDest(nDestID, aPageDest)) + return false; + const OString aPage(aPageDest.makeStringAndClear()); + const sal_Int32 nAfterPage(aPage.indexOf(" 0 R") + 4); + + rBuffer.append("[" + OString::number(nObject) + " 0 R" + aPage.subView(nAfterPage)); + return true; +} + +void PDFWriterImpl::appendDestOrGoTo(sal_Int32 nDestID, OStringBuffer& rBuffer) +{ + OStringBuffer aStructure; + if (!appendStructureDest(nDestID, aStructure)) + { + rBuffer.append("/Dest"); + appendDest(nDestID, rBuffer); + return; + } + + // ISO 14289-2 8.8: a destination inside the document shall be a structure destination + rBuffer.append("/A<>"); +} + bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer ) { if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() ) @@ -2802,8 +2968,7 @@ bool PDFWriterImpl::emitLinkAnnotations() } if( rLink.m_nDest >= 0 ) { - aLine.append( "/Dest" ); - appendDest( rLink.m_nDest, aLine ); + appendDestOrGoTo(rLink.m_nDest, aLine); } else { @@ -4383,7 +4548,7 @@ bool PDFWriterImpl::emitCatalog() { removePlaceholderSE(m_aStructure, m_aStructure[0]); // check if dummy structure containers are needed - addInternalStructureContainer(m_aStructure[0]); + addInternalStructureContainer(0); nStructureDict = m_aStructure[0].m_nObject = createObject(); emitStructure( m_aStructure[ 0 ] ); } @@ -9908,6 +10073,30 @@ sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools:: return m_aDestinationIdTranslation[ nDestId ]; } +void PDFWriterImpl::setDestStructureElement(sal_Int32 nDestId, sal_Int32 nStructElementId) +{ + if (nDestId < 0 || o3tl::make_unsigned(nDestId) >= m_aDests.size()) + return; + + m_aDests[nDestId].m_nStructElement = nStructElementId; +} + +void PDFWriterImpl::addStructureRef(sal_Int32 nElementId, sal_Int32 nRefElementId) +{ + if (nElementId < 0 || o3tl::make_unsigned(nElementId) >= m_aStructure.size()) + return; + if (nRefElementId < 0 || o3tl::make_unsigned(nRefElementId) >= m_aStructure.size()) + return; + // an element left out of the tree has no object to point at + if (m_aStructure[nRefElementId].m_nObject <= 0) + return; + // ISO 32000-2 added Ref to a structure element, and PDF 1.7 has no entry of that name + if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0) + return; + + m_aStructure[nElementId].m_RefElements.push_back(nRefElementId); +} + void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId ) { if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() ) @@ -10009,59 +10198,6 @@ void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID ) const char* PDFWriterImpl::getStructureTag(StructElement eType) { - using namespace vcl::pdf; - - static constexpr auto constTagStrings = frozen::make_unordered_map({ - { StructElement::NonStructElement, "NonStruct" }, - { StructElement::Document, "Document" }, - { StructElement::Part, "Part" }, - { StructElement::Article, "Art" }, - { StructElement::Section, "Sect" }, - { StructElement::Division, "Div" }, - { StructElement::BlockQuote, "BlockQuote" }, - { StructElement::Caption, "Caption" }, - { StructElement::TOC, "TOC" }, - { StructElement::TOCI, "TOCI" }, - { StructElement::Index, "Index" }, - { StructElement::Paragraph, "P" }, - { StructElement::Heading, "H" }, - { StructElement::H1, "H1" }, - { StructElement::H2, "H2" }, - { StructElement::H3, "H3" }, - { StructElement::H4, "H4" }, - { StructElement::H5, "H5" }, - { StructElement::H6, "H6" }, - { StructElement::List, "L" }, - { StructElement::ListItem, "LI" }, - { StructElement::LILabel, "Lbl" }, - { StructElement::LIBody, "LBody" }, - { StructElement::Table, "Table" }, - { StructElement::TableRow, "TR" }, - { StructElement::TableHeader, "TH" }, - { StructElement::TableData, "TD" }, - { StructElement::Span, "Span" }, - { StructElement::Quote, "Quote" }, - { StructElement::Note, "Note" }, - { StructElement::Reference, "Reference" }, - { StructElement::BibEntry, "BibEntry" }, - { StructElement::Code, "Code" }, - { StructElement::Link, "Link" }, - { StructElement::Annot, "Annot" }, - { StructElement::Ruby, "Ruby" }, - { StructElement::RB, "RB" }, - { StructElement::RT, "RT" }, - { StructElement::RP, "RP" }, - { StructElement::Warichu, "Warichu" }, - { StructElement::WT, "WT" }, - { StructElement::WP, "WP" }, - { StructElement::Figure, "Figure" }, - { StructElement::Formula, "Formula"}, - { StructElement::Form, "Form" }, - { StructElement::Title, "Title" }, - { StructElement::Emphasis, "Em" }, - { StructElement::Strong, "Strong" }, - }); - // First handle fallbacks for elements that were added in a certain PDF version // PDF 1.5 fallbacks @@ -10079,6 +10215,8 @@ const char* PDFWriterImpl::getStructureTag(StructElement eType) eType = StructElement::Span; break; case StructElement::Strong: eType = StructElement::Span; break; + case StructElement::FENote: + eType = StructElement::Note; break; default: break; } @@ -10092,13 +10230,53 @@ const char* PDFWriterImpl::getStructureTag(StructElement eType) return iterator->second; } -void PDFWriterImpl::addRoleMap(const OString& aAlias, StructElement eType) +bool PDFWriterImpl::isStandardStructureName(std::string_view aName) { - OString aTag = getStructureTag(eType); + // below 2.0 getStructureTag returns P for Title, so a style may take that name + const auto it(std::ranges::find_if(constTagStrings, + [aName](const auto& rTag) { return aName == rTag.second; })); + if ((it != constTagStrings.end() && aName == getStructureTag(it->first)) + || std::ranges::find(constUnusedTagStrings, aName) != std::end(constUnusedTagStrings)) + return true; + + if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0) + return false; + + return isDeepHeading(aName) + || std::ranges::find(constUnusedTagStrings20, aName) + != std::end(constUnusedTagStrings20); +} + +OString PDFWriterImpl::claimRoleName(const OString& rAlias, StructElement eType) +{ + const OString aTag(getStructureTag(eType)); // For PDF/UA it's not allowed to map an alias with the same name. // Not aware of a reason for doing it in any case, so just don't do it. - if (aAlias != aTag) - m_aRoleMap[aAlias] = aTag; + if (rAlias == aTag) + return rAlias; + + // a heading deeper than the six this writer emits: PDF 2.0 knows the name, so the element + // says the level it is and needs no entry + if (isDeepHeading(rAlias) && StructElement::H1 <= eType && eType <= StructElement::H6 + && PDFWriter::PDFVersion::PDF_2_0 <= m_aContext.Version) + return rAlias; + + OString aName(rAlias); + for (sal_Int32 nIndex(1);; ++nIndex) + { + // a standard type's name cannot be made to mean another type + if (!isStandardStructureName(aName)) + { + // any other name belongs to the alias that asked for it first + const auto[it, bInserted]( + m_aRoleMap.emplace(aName, RoleMapEntry{ .m_aTag = aTag, .m_aAsked = rAlias })); + if (bInserted || (it->second.m_aTag == aTag && it->second.m_aAsked == rAlias)) + return aName; + } + + // name taken, try the next index + aName = rAlias + "-" + OString::number(nIndex); + } } void PDFWriterImpl::beginStructureElementMCSeq() @@ -10267,8 +10445,7 @@ void PDFWriterImpl::initStructureElement(sal_Int32 const id, OStringBuffer aNameBuf( rAlias.size() ); COSWriter::appendName( rAlias, aNameBuf ); OString aAliasName( aNameBuf.makeStringAndClear() ); - rEle.m_aAlias = aAliasName; - addRoleMap(aAliasName, eType); + rEle.m_aAlias = claimRoleName(aAliasName, eType); } if (m_bEmitStructure && eType != StructElement::NonStructElement) // don't create nonexistent objects @@ -10276,8 +10453,8 @@ void PDFWriterImpl::initStructureElement(sal_Int32 const id, rEle.m_nObject = createObject(); // update parent's kids list m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(ObjReference{rEle.m_nObject}); - // ISO 14289-1:2014, Clause: 7.9 - if (*rEle.m_oType == StructElement::Note) + // ISO 14289-1:2014, Clause: 7.9, for the element it names Note + if (*rEle.m_oType == StructElement::FENote) { m_StructElemObjsWithID.insert(rEle.m_nObject); } @@ -10419,97 +10596,113 @@ void removePlaceholderSE(std::vector & rStructure, PDFStruc * Recursive function * */ -void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle ) +void PDFWriterImpl::addInternalStructureContainer(const sal_Int32 nEle) { - if (rEle.m_nOwnElement != rEle.m_nParentElement - && *rEle.m_oType == StructElement::NonStructElement) + // m_aStructure grows below, so this addresses elements by index throughout: any + // reference into the vector dies at the next push_back + if (m_aStructure[nEle].m_nOwnElement != m_aStructure[nEle].m_nParentElement + && *m_aStructure[nEle].m_oType == StructElement::NonStructElement) { return; } - for (auto const& child : rEle.m_aChildren) + for (size_t i = 0; i < m_aStructure[nEle].m_aChildren.size(); ++i) { - assert(child > 0 && o3tl::make_unsigned(child) < m_aStructure.size()); - if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() ) + const sal_Int32 nChild = m_aStructure[nEle].m_aChildren[i]; + assert(nChild > 0 && o3tl::make_unsigned(nChild) < m_aStructure.size()); + if (nChild > 0 && o3tl::make_unsigned(nChild) < m_aStructure.size()) { - PDFStructureElement& rChild = m_aStructure[ child ]; - if (*rChild.m_oType != StructElement::NonStructElement) + if (*m_aStructure[nChild].m_oType != StructElement::NonStructElement) { - //triggered when a child of the rEle element is found - assert(rChild.m_nParentElement == rEle.m_nOwnElement); - if( rChild.m_nParentElement == rEle.m_nOwnElement ) - addInternalStructureContainer( rChild );//examine the child + //triggered when a child of the nEle element is found + assert(m_aStructure[nChild].m_nParentElement == m_aStructure[nEle].m_nOwnElement); + if (m_aStructure[nChild].m_nParentElement == m_aStructure[nEle].m_nOwnElement) + addInternalStructureContainer(nChild); //examine the child else { - OSL_FAIL( "PDFWriterImpl::addInternalStructureContainer: invalid child structure element" ); - SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure element with id " << child ); + OSL_FAIL("PDFWriterImpl::addInternalStructureContainer: invalid child " + "structure element"); + SAL_INFO("vcl.pdfwriter", + "PDFWriterImpl::addInternalStructureContainer: invalid child " + "structure element with id " + << nChild); } } } else { - OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" ); - SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure id " << child ); + OSL_FAIL("PDFWriterImpl::emitStructure: invalid child structure id"); + SAL_INFO("vcl.pdfwriter", + "PDFWriterImpl::addInternalStructureContainer: invalid child structure id " + << nChild); } } - if( rEle.m_nOwnElement == rEle.m_nParentElement ) + if (m_aStructure[nEle].m_nOwnElement == m_aStructure[nEle].m_nParentElement) return; - if( rEle.m_aKids.empty() ) + if (m_aStructure[nEle].m_aKids.size() <= ncMaxPDFArraySize) return; - if( rEle.m_aKids.size() <= ncMaxPDFArraySize ) return; - //then we need to add the containers for the kids elements // a list to be used for the new kid element - std::list< PDFStructureElementKid > aNewKids; - std::vector< sal_Int32 > aNewChildren; + std::list aNewKids; + std::vector aNewChildren; - // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?) - OString aAliasName("Div"_ostr); - addRoleMap(aAliasName, StructElement::Division); - - while( rEle.m_aKids.size() > ncMaxPDFArraySize ) + while (m_aStructure[nEle].m_aKids.size() > ncMaxPDFArraySize) { - sal_Int32 nCurrentStructElement = rEle.m_nOwnElement; - sal_Int32 nNewId = sal_Int32(m_aStructure.size()); - m_aStructure.emplace_back( ); - PDFStructureElement& rEleNew = m_aStructure.back(); - rEleNew.m_aAlias = aAliasName; - rEleNew.m_oType.emplace(StructElement::Division); // a new Div type container - rEleNew.m_nOwnElement = nNewId; - rEleNew.m_nParentElement = nCurrentStructElement; - //inherit the same page as the first child to be reparented - rEleNew.m_nFirstPageObject = m_aStructure[ rEle.m_aChildren.front() ].m_nFirstPageObject; - rEleNew.m_nObject = createObject();//assign a PDF object number + const sal_Int32 nNewId = sal_Int32(m_aStructure.size()); + { + //inherit the same page as the first child to be reparented + const sal_Int32 nPage( + m_aStructure[m_aStructure[nEle].m_aChildren.front()].m_nFirstPageObject); + PDFStructureElement aNew(nNewId, nEle, nPage); + aNew.m_oType.emplace(StructElement::Division); // a new Div type container + aNew.m_nObject = createObject(); //assign a PDF object number + m_aStructure.push_back(std::move(aNew)); + } //add the object to the kid list of the parent - aNewKids.emplace_back(ObjReference{rEleNew.m_nObject}); - aNewChildren.push_back( nNewId ); + aNewKids.emplace_back(ObjReference{ m_aStructure[nNewId].m_nObject }); + aNewChildren.push_back(nNewId); - std::vector< sal_Int32 >::iterator aChildEndIt( rEle.m_aChildren.begin() ); - std::list< PDFStructureElementKid >::iterator aKidEndIt( rEle.m_aKids.begin() ); - advance( aChildEndIt, ncMaxPDFArraySize ); - advance( aKidEndIt, ncMaxPDFArraySize ); + // m_aKids and m_aChildren are not parallel: every element gets a child entry, only an + // emitted one gets a kid, and marked content adds kids of its own. Cut the two at + // corresponding points, not at the same count. + const auto aKidEndIt(std::next(m_aStructure[nEle].m_aKids.begin(), ncMaxPDFArraySize)); + size_t nMovedChildren(0); + for (auto it = m_aStructure[nEle].m_aKids.begin(); it != aKidEndIt; ++it) + { + if (std::holds_alternative(*it)) + ++nMovedChildren; + } + auto aChildEndIt(m_aStructure[nEle].m_aChildren.begin()); + for (size_t nSeen = 0; + nSeen < nMovedChildren && aChildEndIt != m_aStructure[nEle].m_aChildren.end(); + ++aChildEndIt) + { + if (m_aStructure[*aChildEndIt].m_nObject != 0) // an emitted child has a kid entry + ++nSeen; + } - rEleNew.m_aKids.splice( rEleNew.m_aKids.begin(), - rEle.m_aKids, - rEle.m_aKids.begin(), - aKidEndIt ); - rEleNew.m_aChildren.insert( rEleNew.m_aChildren.begin(), - rEle.m_aChildren.begin(), - aChildEndIt ); - rEle.m_aChildren.erase( rEle.m_aChildren.begin(), aChildEndIt ); + m_aStructure[nNewId].m_aKids.splice(m_aStructure[nNewId].m_aKids.begin(), + m_aStructure[nEle].m_aKids, + m_aStructure[nEle].m_aKids.begin(), aKidEndIt); + m_aStructure[nNewId].m_aChildren.insert(m_aStructure[nNewId].m_aChildren.begin(), + m_aStructure[nEle].m_aChildren.begin(), + aChildEndIt); + m_aStructure[nEle].m_aChildren.erase(m_aStructure[nEle].m_aChildren.begin(), aChildEndIt); // set the kid's new parent - for (auto const& child : rEleNew.m_aChildren) + for (const auto nMoved : m_aStructure[nNewId].m_aChildren) { - m_aStructure[ child ].m_nParentElement = nNewId; + m_aStructure[nMoved].m_nParentElement = nNewId; } } //finally add the new kids resulting from the container added - rEle.m_aKids.insert( rEle.m_aKids.begin(), aNewKids.begin(), aNewKids.end() ); - rEle.m_aChildren.insert( rEle.m_aChildren.begin(), aNewChildren.begin(), aNewChildren.end() ); + m_aStructure[nEle].m_aKids.insert(m_aStructure[nEle].m_aKids.begin(), aNewKids.begin(), + aNewKids.end()); + m_aStructure[nEle].m_aChildren.insert(m_aStructure[nEle].m_aChildren.begin(), + aNewChildren.begin(), aNewChildren.end()); } bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle ) @@ -10676,6 +10869,7 @@ bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr eType == StructElement::Emphasis || eType == StructElement::Strong || eType == StructElement::Note || + eType == StructElement::FENote || eType == StructElement::Reference || eType == StructElement::BibEntry || eType == StructElement::Code || @@ -10714,6 +10908,7 @@ bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr eType == StructElement::Emphasis || eType == StructElement::Strong || eType == StructElement::Note || + eType == StructElement::FENote || eType == StructElement::Reference || eType == StructElement::BibEntry || eType == StructElement::Code || @@ -10820,6 +11015,21 @@ bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr return bInsert; } +sal_Int32 PDFWriterImpl::convertStructureAttribute(enum PDFWriter::StructAttribute eAttr, + sal_Int32 nValue) +{ + if (!isLengthAttribute(eAttr)) + return nValue; + + const Size aSize( + convert(m_aGraphicsStack.front().m_aMapMode, m_aMapMode, this, Size(nValue, nValue))); + // the map mode can scale X and Y differently + const bool bHorizontal(eAttr == PDFWriter::StartIndent || eAttr == PDFWriter::EndIndent + || eAttr == PDFWriter::TextIndent || eAttr == PDFWriter::Width); + + return bHorizontal ? aSize.Width() : aSize.Height(); +} + bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue ) { if( ! m_aContext.Tagged ) @@ -10920,6 +11130,7 @@ bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttrib eType == StructElement::Emphasis || eType == StructElement::Strong || eType == StructElement::Note || + eType == StructElement::FENote || eType == StructElement::Reference || eType == StructElement::BibEntry || eType == StructElement::Code || @@ -10949,8 +11160,18 @@ bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttrib } } - if( bInsert ) - m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue ); + if (bInsert) + { + if (eAttr == PDFWriter::LinkAnnotation) + { + m_aStructure[m_nCurrentStructElement].m_LinkAnnotIds.push_back(nValue); + } + else + { + m_aStructure[m_nCurrentStructElement].m_aAttributes[eAttr] + = PDFStructureAttribute(convertStructureAttribute(eAttr, nValue)); + } + } else if( m_nCurrentStructElement > 0 && m_bEmitStructure ) SAL_INFO("vcl.pdfwriter", "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr ) diff --git a/vcl/source/pdf/pdfwriter_utils.hxx b/vcl/source/pdf/pdfwriter_utils.hxx index 24164e53b68b..a15cc034e1f5 100644 --- a/vcl/source/pdf/pdfwriter_utils.hxx +++ b/vcl/source/pdf/pdfwriter_utils.hxx @@ -36,6 +36,7 @@ namespace vcl::pdf struct PDFStructureElement; constexpr std::string_view constNamespacePDF2("http://iso.org/pdf2/ssn"); +constexpr std::string_view constNamespacePDF17("http://iso.org/pdf/ssn"); constexpr sal_Int32 nLog10Divisor = 3; constexpr double fDivisor = 1000.0; diff --git a/vcl/source/treelist/SvHeaderTabListBox.cxx b/vcl/source/treelist/SvHeaderTabListBox.cxx index e00fff5cc141..a64468f27a4a 100644 --- a/vcl/source/treelist/SvHeaderTabListBox.cxx +++ b/vcl/source/treelist/SvHeaderTabListBox.cxx @@ -258,20 +258,15 @@ tools::Rectangle SvHeaderTabListBox::calcFieldRectPixel(sal_Int32 _nRow, sal_uIn rtl::Reference SvHeaderTabListBox::CreateAccessibleCell(sal_Int32 _nRow, sal_uInt16 _nColumnPos) { - OSL_ENSURE(m_xAccessible.is(), "Invalid call: Accessible is null"); - - rtl::Reference xChild; + assert(m_xAccessible.is() && "Invalid call: Accessible is null"); TriState eState = TRISTATE_INDET; bool bIsCheckBox = IsCellCheckBox(_nRow, _nColumnPos, eState); if (bIsCheckBox) - xChild = new AccessibleCheckBoxCell(m_xAccessible->getTable(), *this, _nRow, _nColumnPos, - eState, false); - else - xChild = new AccessibleBrowseBoxTableCell(m_xAccessible->getTable(), *this, _nRow, - _nColumnPos); + return new AccessibleCheckBoxCell(m_xAccessible->getTable(), *this, _nRow, _nColumnPos, + eState, false); - return xChild; + return new AccessibleBrowseBoxTableCell(m_xAccessible->getTable(), *this, _nRow, _nColumnPos); } rtl::Reference SvHeaderTabListBox::CreateAccessibleRowHeader(sal_Int32) diff --git a/vcl/source/treelist/iconview.cxx b/vcl/source/treelist/iconview.cxx index e1d27539e012..17827a954104 100644 --- a/vcl/source/treelist/iconview.cxx +++ b/vcl/source/treelist/iconview.cxx @@ -30,7 +30,6 @@ namespace { const int separatorHeight = 10; -const int nSpacing = 5; // 5 pixels from top, from bottom, between icon and label } IconView::IconView(vcl::Window* pParent, WinBits nBits) @@ -64,7 +63,7 @@ void IconView::UpdateEntrySize(const SvTreeListEntry& rEntry) // provide some minimum width if text exists (will be ellipsized if it doesn't fit completely) const tools::Long nMinTextWidth = rEntry.GetFirstItem(SvLBoxItemType::String) ? 100 : 0; - int spacing = nSpacing * 2; + int spacing = m_nSpacing * 2; const short nMinHeight = aImageSize.getHeight() + spacing; const short nMinWidth = std::max(aImageSize.getWidth() + spacing, nMinTextWidth); @@ -98,7 +97,7 @@ bool IconView::HasSeparatorEntry() const void IconView::CalcEntryHeight(const SvTreeListEntry& rEntry) { - int nHeight = nSpacing * 2; + int nHeight = m_nSpacing * 2; SvViewDataEntry& rViewData = GetViewDataEntry(rEntry); const size_t nCount = rEntry.ItemCount(); bool bHasIcon = false; @@ -111,7 +110,7 @@ void IconView::CalcEntryHeight(const SvTreeListEntry& rEntry) } if (bHasIcon && nCount > 1) - nHeight += nSpacing; // between icon and label + nHeight += m_nSpacing; // between icon and label if (nHeight > m_nEntryHeight) { @@ -245,7 +244,7 @@ void IconView::PaintEntry(SvTreeListEntry& rEntry, tools::Long nX, tools::Long n nLabelHeight += nItemHeight; } - int nLabelYPos = nY + nTempEntryHeight - nLabelHeight - nSpacing; // padding from bottom + int nLabelYPos = nY + nTempEntryHeight - nLabelHeight - m_nSpacing; // padding from bottom for (auto nCurItem : aTextItems) { aEntryPos.setY(nLabelYPos); @@ -271,12 +270,12 @@ void IconView::PaintEntry(SvTreeListEntry& rEntry, tools::Long nX, tools::Long n // center horizontally aEntryPos.AdjustX((nTempEntryWidth - nItemWidth) / 2); // center vertically - int nImageAreaHeight = nTempEntryHeight - nSpacing * 2; // spacings from top, from bottom + int nImageAreaHeight = nTempEntryHeight - m_nSpacing * 2; // spacings from top, from bottom if (nLabelHeight > 0) { - nImageAreaHeight -= nLabelHeight + nSpacing; // spacing between icon and label + nImageAreaHeight -= nLabelHeight + m_nSpacing; // spacing between icon and label } - aEntryPos.AdjustY((nImageAreaHeight - nItemHeight) / 2 + nSpacing); + aEntryPos.AdjustY((nImageAreaHeight - nItemHeight) / 2 + m_nSpacing); rItem.Paint(aEntryPos, *this, rRenderContext, &rViewDataEntry, rEntry); } diff --git a/vcl/source/window/builder.cxx b/vcl/source/window/builder.cxx index 706f2ecb8b1d..9750a66c1a27 100644 --- a/vcl/source/window/builder.cxx +++ b/vcl/source/window/builder.cxx @@ -1418,6 +1418,10 @@ VclPtr VclBuilder::makeObject(vcl::Window *pParent, const OUString auto aColumnsIt = rMap.find(u"columns"_ustr); if (aColumnsIt != rMap.end()) xBox->SetFixedColumnCount(aColumnsIt->second.toInt32()); + + auto aItemPaddingIt = rMap.find(u"item-padding"_ustr); + if (aItemPaddingIt != rMap.end()) + xBox->SetSpacing(aItemPaddingIt->second.toInt32()); } else if (name == "GtkTreeView") { diff --git a/vcl/source/window/colorpicker.cxx b/vcl/source/window/colorpicker.cxx index 915e4741d3d2..d7ef32922567 100644 --- a/vcl/source/window/colorpicker.cxx +++ b/vcl/source/window/colorpicker.cxx @@ -391,7 +391,7 @@ void ColorFieldControl::SetValues(sal_uInt16 nBaseValue, ColorMode eMode, double mnBaseValue = nBaseValue; meMode = eMode; - mdX = x; + mdX = IsRTLEnabled() ? 1 - x : x; mdY = y; if (bUpdateBitmap) @@ -401,6 +401,14 @@ void ColorFieldControl::SetValues(sal_uInt16 nBaseValue, ColorMode eMode, double Invalidate(); } +double ColorFieldControl::GetX() const +{ + if (IsRTLEnabled()) + return 1 - mdX; + + return mdX; +} + void ColorFieldControl::UpdatePosition() { Size aSize(GetOutputSizePixel()); diff --git a/vcl/source/window/printdlg.cxx b/vcl/source/window/printdlg.cxx index a02c99e48e4f..2599f2a860bd 100644 --- a/vcl/source/window/printdlg.cxx +++ b/vcl/source/window/printdlg.cxx @@ -580,12 +580,22 @@ PrintDialog::PrintDialog(weld::Window* i_pWindow, std::shared_ptrgetValue(u"PageRange"_ustr); + if (pPageRange && pPageRange->Value.hasValue()) + { + OUString aPageRange; + pPageRange->Value >>= aPageRange; + mnInitialPage = aPageRange.toInt32(); + } + // save printbutton text, gets exchanged occasionally with print to file maPrintText = mxOKButton->get_label(); @@ -1150,18 +1160,51 @@ void PrintDialog::preparePreview( bool i_bMayUseCache ) void PrintDialog::updatePageRange(sal_Int32 nPages) { - if (nPages > 0 && !mxPageRangesRadioButton->get_active()) + if (nPages <= 0) + return; + + PropertyValue* pContentVal = maPController->getValue(u"PrintContent"_ustr); + sal_Int32 nContent = -1; + if (pContentVal) + pContentVal->Value >>= nContent; + + if (nContent == 1) { + OUString sRange; + if (nPages == 1) + sRange = u"1"_ustr; + else + sRange = u"1-"_ustr + OUString::number(nPages); + + mxPageRangeEdit->set_text(sRange); + maPController->setValue(u"PageRange"_ustr, Any(sRange)); + return; + } + + OUString sRange; + + if (nContent == 3) + { + // Current page: preserve the document page selected when + // the print dialog was opened. + sRange = OUString::number(mnInitialPage); + } + else if (nContent == 0) + { + // All pages OUStringBuffer aBuf(32); aBuf.append("1"); if (nPages > 1) - { aBuf.append("-" + OUString::number(nPages)); - } - OUString sRange = aBuf.makeStringAndClear(); - mxPageRangeEdit->set_text(sRange); - maPController->setValue(u"PageRange"_ustr, Any(sRange)); + sRange = aBuf.makeStringAndClear(); } + else + { + return; + } + + mxPageRangeEdit->set_text(sRange); + maPController->setValue(u"PageRange"_ustr, Any(sRange)); } void PrintDialog::updatePageSize(int nOrientation) @@ -2365,6 +2408,15 @@ IMPL_LINK( PrintDialog, UIOption_RadioHdl, weld::Toggleable&, i_rBtn, void ) sal_Int32 nVal = it->second; pVal->Value <<= nVal; + if (pVal->Name == "PrintContent" && nVal == 3) + { + PropertyValue* pPageRange = maPController->getValue(u"PageRange"_ustr); + if (pPageRange) + { + pPageRange->Value <<= OUString::number(mnInitialPage); + } + } + updateOrientationBox(); checkOptionalControlDependencies(); diff --git a/vcl/uiconfig/ui/printdialog.ui b/vcl/uiconfig/ui/printdialog.ui index 5ffa739789f9..5e9e21b6bb8c 100644 --- a/vcl/uiconfig/ui/printdialog.ui +++ b/vcl/uiconfig/ui/printdialog.ui @@ -546,48 +546,6 @@ 0 - - - _Pages: - True - True - False - start - 2 - 2 - True - True - rbAllPages - - - Prints only the pages or slides that you specify in the Pages box. - - - - - 0 - 1 - - - - - True - True - True - True - True - e.g.: 1, 3-5, 7, 9 - - - To print a range of pages, use a format like 3-6. To print single pages, use a format like 7;9;11. You can print a combination of page ranges and single pages, by using a format like 3-6;8;10;12. - - - - - 1 - 1 - - _Selection @@ -609,6 +567,69 @@ 0 + + + _Current page + True + False + start + 2 + True + True + rbAllPages + + + Prints only the current page. + + + + + 0 + 1 + + + + + _Pages: + True + True + False + start + 2 + 2 + True + True + rbAllPages + + + Prints only the pages or slides that you specify in the Pages box. + + + + + 0 + 2 + + + + + True + True + True + True + True + e.g.: 1, 3-5, 7, 9 + + + To print a range of pages, use a format like 3-6. To print single pages, use a format like 7;9;11. You can print a combination of page ranges and single pages, by using a format like 3-6;8;10;12. + + + + + 1 + 2 + + True @@ -621,7 +642,7 @@ 0 - 2 + 3 @@ -643,7 +664,7 @@ 1 - 2 + 3 diff --git a/vcl/unx/gtk3/gtkinst.cxx b/vcl/unx/gtk3/gtkinst.cxx index e31dc40ac10f..265abac878ec 100644 --- a/vcl/unx/gtk3/gtkinst.cxx +++ b/vcl/unx/gtk3/gtkinst.cxx @@ -12802,6 +12802,7 @@ namespace switch (eType) { case weld::EntryMessageType::Normal: + case weld::EntryMessageType::Info: break; case weld::EntryMessageType::Warning: #if GTK_CHECK_VERSION(4, 0, 0) @@ -12834,6 +12835,9 @@ namespace case weld::EntryMessageType::Error: gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-error"); break; + case weld::EntryMessageType::Info: + gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-information"); + break; } } } diff --git a/winaccessibility/source/UAccCOM/MAccessible.cxx b/winaccessibility/source/UAccCOM/MAccessible.cxx index 4b2a7877e308..001a45d78620 100644 --- a/winaccessibility/source/UAccCOM/MAccessible.cxx +++ b/winaccessibility/source/UAccCOM/MAccessible.cxx @@ -1809,7 +1809,7 @@ COM_DECLSPEC_NOTHROW STDMETHODIMP CMAccessible::get_groupPosition(long __RPC_FAR Reference< XAccessibleGroupPosition > xGroupPosition( pRContext, UNO_QUERY ); if ( xGroupPosition.is() ) { - Sequence< sal_Int32 > rSeq = xGroupPosition->getGroupPosition( Any( pRContext ) ); + Sequence rSeq = xGroupPosition->getGroupPosition(m_xAccessible); if (rSeq.getLength() >= 3) { *groupLevel = rSeq[0]; diff --git a/wizards/source/scriptforge/SF_Array.xba b/wizards/source/scriptforge/SF_Array.xba index d0f557a01fcc..12af20e76633 100644 --- a/wizards/source/scriptforge/SF_Array.xba +++ b/wizards/source/scriptforge/SF_Array.xba @@ -133,7 +133,7 @@ Public Function AppendColumn(Optional ByRef Array_2D As Variant _ ''' ARRAYINSERTERROR ''' Examples: ''' SF_Array.AppendColumn(Array(1, 2, 3), Array(4, 5, 6)) returns ((1, 4), (2, 5), (3, 6)) -''' x = SF_Array.AppendColumn(Array(), Array(1, 2, 3)) => ∀ i ∈ {0 ≤ i ≤ 2} : x(0, i) ≡ i +''' x = SF_Array.AppendColumn(Array(), Array(0, 1, 2)) => ∀ i ∈ {0 ≤ i ≤ 2} : x(i, 0) ≡ i Dim vAppendColumn As Variant ' Return value Dim iDims As Integer ' Dimensions of Array_2D @@ -214,7 +214,7 @@ Public Function AppendRow(Optional ByRef Array_2D As Variant _ ''' ARRAYINSERTERROR ''' Examples: ''' SF_Array.AppendRow(Array(1, 2, 3), Array(4, 5, 6)) returns ((1, 2, 3), (4, 5, 6)) -''' x = SF_Array.AppendRow(Array(), Array(1, 2, 3)) => ∀ i ∈ {0 ≤ i ≤ 2} : x(i, 0) ≡ i +''' x = SF_Array.AppendRow(Array(), Array(0, 1, 2)) => ∀ i ∈ {0 ≤ i ≤ 2} : x(0, i) ≡ i Dim vAppendRow As Variant ' Return value Dim iDims As Integer ' Dimensions of Array_2D diff --git a/wizards/source/scriptforge/SF_UI.xba b/wizards/source/scriptforge/SF_UI.xba index 1d4e69967082..bd2e747bcab3 100644 --- a/wizards/source/scriptforge/SF_UI.xba +++ b/wizards/source/scriptforge/SF_UI.xba @@ -100,6 +100,9 @@ Const cstBUILTINTOOLBAR = 0 ' Standard toolbar Const cstCUSTOMTOOLBAR = 1 ' Toolbar added by user and stored in the LibreOffice application Const cstCUSTOMDOCTOOLBAR = 2 ' Toolbar added by user solely for a single document +' Password management +Const cstPASSWORD = "*" + REM ===================================================== CONSTRUCTOR/DESTRUCTOR REM ----------------------------------------------------------------------------- @@ -531,7 +534,9 @@ Public Function EnterValue(Optional ByRef Prompt As Variant _ Dim vEnterValue As Variant ' Return value Dim oDialog As Object ' A SFDialogs.SF_Dialog class instance -Dim oControl As Object ' A SFDialogs.SF_DialogControl class instance +Dim oControl As Object ' The input field as a SFDialogs.SF_DialogControl class instance +Dim oPasswordButton As Object ' The password button as a SFDialogs.SF_DialogControl class instance +Dim oPasswordField As Object ' The readable password as a SFDialogs.SF_DialogControl class instance Dim oTime As Object ' An alternative SFDialogs.SF_DialogControl class instance to represent a TimeField Dim oButton As Object ' A button SFDialogs.SF_DialogControl class instance Dim iExecute As Integer ' dialog.Execute() return value @@ -560,6 +565,7 @@ Const LEFTMARGIN = 10 _ Const INTERVAL = 3 ' One Map AppFont unit is equal to one eighth of the average character (Systemfont) height and one quarter of the average character width. Const TEXTHEIGHT = 8, TEXTWIDTH = 4, TEXTOFFSET = 2, MAXTEXTLENGTH = 70 +Const MAXPASSWORDLENGTH = 30, PASSWORDWIDTH = 20 Const NUMERICWIDTH = 150, CHECKBOXWIDTH = 8, DATEWIDTH = 50, TIMEWIDTH = 50 Const TABSIZE = 4 Const LISTWIDTH = 20, MAXLISTCOUNT = 8 @@ -605,13 +611,15 @@ Try: Next sPrompt lPromptWidth = lPromptWidth * TEXTWIDTH If lPromptWidth > lDialogWidth Then lDialogWidth = lPromptWidth - lPromptHeight = ((UBound(vPrompt) + 1) * (TEXTHEIGHT + TEXTOFFSET)) - TEXTOFFSET + lPromptHeight = ((UBound(vPrompt) + 1) * (TEXTHEIGHT + TEXTOFFSET)) ' Compute dialog control size lControlHeight = TEXTHEIGHT Select Case UCase(ValueType) - Case "STRING", "PASSWORD" + Case "STRING"', "PASSWORD" lControlWidth = TEXTWIDTH * MAXTEXTLENGTH + Case "PASSWORD" + lControlWidth = TEXTWIDTH * MAXPASSWORDLENGTH + TEXTOFFSET + PASSWORDWIDTH Case "BOOL" lControlHeight = lPromptHeight Case "INTEGER", "FLOAT" @@ -661,12 +669,27 @@ Try: ' Setup of enabled control(s) lControlY = TOPMARGIN + lPromptHeight + INTERVAL Select Case UCase(ValueType) - Case "STRING", "PASSWORD" + Case "STRING" Set oControl = oDialog.CreateTextField("InputField" _ , Place := Array(LEFTMARGIN, lControlY, lControlWidth, TEXTHEIGHT) _ , MultiLine := False _ - , PasswordCharacter := Iif(UCase(ValueType) = "PASSWORD", "*", "")) + , PasswordCharacter := "") If VarType(Default) = V_STRING Then oControl.Value = Default + Case "PASSWORD" + Set oControl = oDialog.CreateTextField("InputField" _ + , Place := Array(LEFTMARGIN, lControlY, lControlWidth - TEXTOFFSET - PASSWORDWIDTH, TEXTHEIGHT) _ + , MultiLine := False _ + , PasswordCharacter := cstPASSWORD) + Set oPasswordField = oDialog.CreateTextField("HiddenField" _ + , Place := Array(LEFTMARGIN, lControlY, lControlWidth - TEXTOFFSET - PASSWORDWIDTH, TEXTHEIGHT) _ + , MultiLine := False _ + , PasswordCharacter := "") + oPasswordField.Visible = False + Set oPasswordButton = oDialog.CreateButton("PasswordButton" _ + , Place := Array(oControl.X + oControl.Width + TEXTOFFSET, lControlY, PASSWORDWIDTH, BUTTONHEIGHT)) + If VarType(Default) = V_STRING Then oControl.Value = Default + oPasswordButton.Caption = Chr(8644) ' ⇄ Unicode rightwards arrow over leftwards arrow + oPasswordButton.OnActionPerformed = "vnd.sun.star.script:ScriptForge.SF_UI._RunPasswordButton?language=Basic&location=application" Case "INTEGER", "FLOAT" Set oControl = oDialog.CreateFormattedField("InputField" _ , Place := Array(LEFTMARGIN, lControlY, lControlWidth, TEXTHEIGHT) _ @@ -765,8 +788,10 @@ Try: iExecute = oDialog.Execute(Modal := True) If iExecute = oDialog.OKBUTTON Then Select Case UCase(ValueType) - Case "STRING", "PASSWORD", "DATE", "TIME", "COMBO", "LIST" + Case "STRING", "DATE", "TIME", "COMBO", "LIST" vEnterValue = oControl.Value + Case "PASSWORD" + If oControl.Visible Then vEnterValue = oControl.Value Else vEnterValue = oPasswordField.Value Case "INTEGER" vEnterValue = CLng(oControl.Value) Case "FLOAT" @@ -1856,5 +1881,48 @@ Private Function _Repr() As String End Function ' ScriptForge.SF_UI._Repr +REM ----------------------------------------------------------------------------- +Private Sub _RunPasswordButton(ByRef Optional poEvent As Object) +''' Activated by clicking on the password button defined in the EnterValue dialog box +''' Enable/Disable the echo character of the password control +''' Changing dynamically the EchoChar property in the model, view or peer has no effect, +''' hence - swap the readable and the not readable fields alternatively +''' - set the focus on the newly visible control +''' - put the caret at the current position + +Dim oDialog As Object ' The EnterValue dialog as a SFDialogs.Dialog class instance +Dim oPasswordControl As Object ' The password control as a SFDialogs.DialogControl class instance +Dim oHiddenControl As Object ' The readable control as a SFDialogs.DialogControl class instance (initially hidden) + + On Local Error GoTo Finally ' Never interrupt + If Not IsNull(poEvent) Then + Set oDialog = CreateScriptService("SFDialogs.DialogEvent", poEvent).Parent + Set oPasswordControl = oDialog.Controls("InputField") + Set oHiddenControl = oDialog.Controls("HiddenField") + ' Alternate swap + If oPasswordControl.Visible Then + With oHiddenControl + .Value = oPasswordControl.Value + oPasswordControl.Visible = False + .Visible = True + .SetFocus() + .XControlView.AccessibleContext.setCaretPosition(oPasswordControl.XControlView.AccessibleContext.CaretPosition) + End With + Else + With oPasswordControl + .Value = oHiddenControl.Value + oHiddenControl.Visible = False + .Visible = True + .SetFocus() + .XControlView.AccessibleContext.setCaretPosition(oHiddenControl.XControlView.AccessibleContext.CaretPosition) + End With + End If + End If + +Finally: + On Error GoTo 0 + Exit Sub +End Sub ' ScriptForge.SF_UI._RunPasswordButton + REM ============================================ END OF SCRIPTFORGE.SF_UI \ No newline at end of file diff --git a/wizards/source/scriptforge/script.xlb b/wizards/source/scriptforge/script.xlb index a77ee4a07d5b..0c1ae2408640 100644 --- a/wizards/source/scriptforge/script.xlb +++ b/wizards/source/scriptforge/script.xlb @@ -1,24 +1,24 @@ + + + + + + + + + + + + + + - - - - - - - - - - - - - - \ No newline at end of file diff --git a/xmloff/inc/txtfldi.hxx b/xmloff/inc/txtfldi.hxx index 75e1d798ca25..e620c593b248 100644 --- a/xmloff/inc/txtfldi.hxx +++ b/xmloff/inc/txtfldi.hxx @@ -909,7 +909,9 @@ class XMLUrlFieldImportContext final : public XMLTextFieldImportContext { OUString sURL; OUString sFrame; + OUString sName; bool bFrameOK; + bool bNameOK; public: diff --git a/xmloff/inc/xmlprop.hxx b/xmloff/inc/xmlprop.hxx index bd46d1de9060..22b7c31a9d84 100644 --- a/xmloff/inc/xmlprop.hxx +++ b/xmloff/inc/xmlprop.hxx @@ -498,6 +498,7 @@ inline constexpr OUString PROP_ParaBackTransparent = u"ParaBackTransparent"_ustr inline constexpr OUString PROP_ParaBottomMargin = u"ParaBottomMargin"_ustr; inline constexpr OUString PROP_ParaBottomMarginRelative = u"ParaBottomMarginRelative"_ustr; inline constexpr OUString PROP_ParaComposer = u"ParaComposer"_ustr; +inline constexpr OUString PROP_ParaIsCompoundBased = u"ParaIsCompoundBased"_ustr; inline constexpr OUString PROP_ParaContextMargin = u"ParaContextMargin"_ustr; inline constexpr OUString PROP_ParaExpandSingleWord = u"ParaExpandSingleWord"_ustr; inline constexpr OUString PROP_ParaFirstLineIndent = u"ParaFirstLineIndent"_ustr; diff --git a/xmloff/qa/unit/data/hyperlink-name.fodp b/xmloff/qa/unit/data/hyperlink-name.fodp new file mode 100644 index 000000000000..c905b82aabc4 --- /dev/null +++ b/xmloff/qa/unit/data/hyperlink-name.fodp @@ -0,0 +1,25 @@ + + + + + + + + LibreOffice + + + + + + diff --git a/xmloff/qa/unit/draw.cxx b/xmloff/qa/unit/draw.cxx index f298e61d52f4..faa4bc43b30d 100644 --- a/xmloff/qa/unit/draw.cxx +++ b/xmloff/qa/unit/draw.cxx @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -99,6 +100,29 @@ uno::Reference XmloffDrawTest::getShape(sal_uInt8 nShapeIndex) return xShape; } +CPPUNIT_TEST_FIXTURE(XmloffDrawTest, testHyperlinkName) +{ + // the accessible name of a hyperlink + loadFromFile(u"hyperlink-name.fodp"); + + auto getHyperlinkName = [this]() -> OUString { + uno::Reference xPortion(getShapeTextPortion(0, getShape(0))); + CPPUNIT_ASSERT(xPortion.is()); + auto xField = xPortion->getPropertyValue(u"TextField"_ustr).queryThrow(); + OUString aName; + xField.queryThrow()->getPropertyValue(u"Name"_ustr) >>= aName; + return aName; + }; + + CPPUNIT_ASSERT_EQUAL(u"LibreOffice home page"_ustr, getHyperlinkName()); + + saveAndReload(TestFilter::ODP); + + // office:name was dropped on import and never written on export, so the name a document + // carried did not survive a round trip + CPPUNIT_ASSERT_EQUAL(u"LibreOffice home page"_ustr, getHyperlinkName()); +} + CPPUNIT_TEST_FIXTURE(XmloffDrawTest, testTextBoxLoss) { // Load a document that has a shape with a textbox in it. Save it to ODF and reload. diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx index b40ff54681e7..8e492b0fcbda 100644 --- a/xmloff/source/core/xmltoken.cxx +++ b/xmloff/source/core/xmltoken.cxx @@ -501,6 +501,7 @@ namespace xmloff::token { TOKEN( "command", XML_COMMAND ), TOKEN( "comment", XML_COMMENT ), TOKEN( "compose", XML_COMPOSE ), + TOKEN( "compound-based", XML_COMPOUND_BASED ), TOKEN( "cond-style-name", XML_COND_STYLE_NAME ), TOKEN( "condition", XML_CONDITION ), TOKEN( "condition-source", XML_CONDITION_SOURCE ), diff --git a/xmloff/source/text/txtflde.cxx b/xmloff/source/text/txtflde.cxx index c841cb6014de..8bb2242058ee 100644 --- a/xmloff/source/text/txtflde.cxx +++ b/xmloff/source/text/txtflde.cxx @@ -1744,6 +1744,8 @@ void XMLTextFieldExport::ExportFieldHelper( ProcessString(XML_TARGET_FRAME_NAME, GetStringProperty(gsPropertyTargetFrame,rPropSet), true, XML_NAMESPACE_OFFICE); + ProcessString(XML_NAME, GetStringProperty(u"Name"_ustr, rPropSet), + true, XML_NAMESPACE_OFFICE); GetExport().AddAttribute( XML_NAMESPACE_XLINK, XML_TYPE, XML_SIMPLE ); SvXMLElementExport aUrlField(rExport, XML_NAMESPACE_TEXT, XML_A, false, false); diff --git a/xmloff/source/text/txtfldi.cxx b/xmloff/source/text/txtfldi.cxx index ed14e1edb1a8..a7fbdd27fd33 100644 --- a/xmloff/source/text/txtfldi.cxx +++ b/xmloff/source/text/txtfldi.cxx @@ -2879,7 +2879,8 @@ XMLUrlFieldImportContext::XMLUrlFieldImportContext( SvXMLImport& rImport, XMLTextImportHelper& rHlp) : XMLTextFieldImportContext(rImport, rHlp, sAPI_url), - bFrameOK(false) + bFrameOK(false), + bNameOK(false) { } @@ -2897,6 +2898,10 @@ void XMLUrlFieldImportContext::ProcessAttribute( sFrame = OUString::fromUtf8(sAttrValue); bFrameOK = true; break; + case XML_ELEMENT(OFFICE, XML_NAME): + sName = OUString::fromUtf8(sAttrValue); + bNameOK = true; + break; default: // ignore XMLOFF_WARN_UNKNOWN_ATTR("xmloff", nAttrToken, sAttrValue); @@ -2914,6 +2919,12 @@ void XMLUrlFieldImportContext::PrepareField( xPropertySet->setPropertyValue(u"TargetFrame"_ustr, Any(sFrame)); } + // Calc's own URL field has no name, and this runs outside the try around InsertTextContent + if (bNameOK && xPropertySet->getPropertySetInfo()->hasPropertyByName(u"Name"_ustr)) + { + xPropertySet->setPropertyValue(u"Name"_ustr, Any(sName)); + } + xPropertySet->setPropertyValue(u"Representation"_ustr, Any(GetContent())); } diff --git a/xmloff/source/text/txtprmap.cxx b/xmloff/source/text/txtprmap.cxx index ffc15c274a3b..7affff4b43db 100644 --- a/xmloff/source/text/txtprmap.cxx +++ b/xmloff/source/text/txtprmap.cxx @@ -500,6 +500,7 @@ XMLPropertyMapEntry constexpr aXMLParaPropMap[] = MAP_EXT( PROP_ParaScaleWidthMinimum, XML_NAMESPACE_LO_EXT, XML_TEXT_SCALE_MINIMUM, XML_TYPE_PROP_PARAGRAPH|XML_TYPE_PERCENT16, 0 ), MAP_EXT( PROP_ParaScaleWidthMaximum, XML_NAMESPACE_LO_EXT, XML_TEXT_SCALE_MAXIMUM, XML_TYPE_PROP_PARAGRAPH|XML_TYPE_PERCENT16, 0 ), MAP_EXT( PROP_ParaComposer, XML_NAMESPACE_LO_EXT, XML_PARAGRAPH_COMPOSER, XML_TYPE_PROP_PARAGRAPH|XML_TYPE_BOOL, 0 ), + MAP_EXT( PROP_ParaIsCompoundBased, XML_NAMESPACE_LO_EXT, XML_COMPOUND_BASED, XML_TYPE_PROP_PARAGRAPH|XML_TYPE_BOOL, 0 ), MT_ED( PROP_CharScriptHint, XML_NAMESPACE_STYLE, XML_SCRIPT_TYPE, XML_TYPE_TEXT_SCRIPT_TYPE|MID_FLAG_MERGE_PROPERTY, 0 ), diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt index bc5dd85f814a..52810244381a 100644 --- a/xmloff/source/token/tokens.txt +++ b/xmloff/source/token/tokens.txt @@ -405,6 +405,7 @@ comma-separated command comment compose +compound-based cond-style-name condition condition-source diff --git a/xmlsecurity/source/helper/pdfsignaturehelper.cxx b/xmlsecurity/source/helper/pdfsignaturehelper.cxx index 9e443f4a01a2..c7e980b7a131 100644 --- a/xmlsecurity/source/helper/pdfsignaturehelper.cxx +++ b/xmlsecurity/source/helper/pdfsignaturehelper.cxx @@ -131,6 +131,14 @@ void GetByteRangesFromPDF(const std::unique_ptr& pSig size_t nByteRangeOffset = 0; for (size_t i = 0; i < aByteRange.size(); ++i) { + // Only offsets and lengths that are zero or more describe a part of the file. + if (aByteRange[i] < 0) + { + SAL_WARN("xmlsecurity.helper", "GetByteRangesFromPDF: negative byte range"); + rByteRanges.clear(); + return; + } + if (i % 2 == 0) { nByteRangeOffset = aByteRange[i];