sc: add ODF import/export of the Theme + tests

One missing thing is the support in calc to save the Theme into
the ODS document and read that back. The theme element is added as
a child element to the office:styles - the same as it already is
added in Writer.

Also adds "Theme" property as a top level document property
so it is possible to get and set the theme in xmloff.

Also tests have been added to cover this usecases.

Change-Id: Ic214ff5e945b77d50e6c881def9d49509560a0e0
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/156363
Tested-by: Jenkins
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
diff --git a/sc/inc/unonames.hxx b/sc/inc/unonames.hxx
index 522582a..f88d89f 100644
--- a/sc/inc/unonames.hxx
+++ b/sc/inc/unonames.hxx
@@ -47,6 +47,7 @@ inline constexpr OUStringLiteral SC_UNO_ROWLABELRNG          = u"RowLabelRanges"
inline constexpr OUStringLiteral SC_UNO_SHEETLINKS           = u"SheetLinks";
inline constexpr OUStringLiteral SC_UNO_FORBIDDEN            = u"ForbiddenCharacters";
inline constexpr OUStringLiteral SC_UNO_HASDRAWPAGES         = u"HasDrawPages";
inline constexpr OUStringLiteral SC_UNO_THEME                = u"Theme";

//  CharacterProperties
inline constexpr OUStringLiteral SC_UNONAME_CCOLOR           = u"CharColor";
diff --git a/sc/qa/extras/scspreadsheetsettingsobj.cxx b/sc/qa/extras/scspreadsheetsettingsobj.cxx
index 117fc4d..9173a80 100644
--- a/sc/qa/extras/scspreadsheetsettingsobj.cxx
+++ b/sc/qa/extras/scspreadsheetsettingsobj.cxx
@@ -48,21 +48,10 @@ public:

ScSpreadsheetSettingsObj::ScSpreadsheetSettingsObj()
    : UnoApiTest("/sc/qa/extras/testdocuments")
    , XPropertySet({
          "AreaLinks",
          "CharLocale",
          "CharLocaleAsian",
          "CharLocaleComplex",
          "ColumnLabelRanges",
          "DDELinks",
          "DatabaseRanges",
          "ExternalDocLinks",
          "InteropGrabBag",
          "NamedRanges",
          "NullDate",
          "RowLabelRanges",
          "SheetLinks",
      })
    , XPropertySet({ "AreaLinks", "CharLocale", "CharLocaleAsian", "CharLocaleComplex",
                     "ColumnLabelRanges", "DDELinks", "DatabaseRanges", "ExternalDocLinks",
                     "InteropGrabBag", "NamedRanges", "NullDate", "RowLabelRanges", "SheetLinks",
                     "Theme" })
{
}

diff --git a/sc/qa/unit/ThemeImportExportTest.cxx b/sc/qa/unit/ThemeImportExportTest.cxx
index 9eac376..e8e1a31 100644
--- a/sc/qa/unit/ThemeImportExportTest.cxx
+++ b/sc/qa/unit/ThemeImportExportTest.cxx
@@ -15,6 +15,11 @@
#include <editeng/colritem.hxx>
#include <editeng/borderline.hxx>

#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/util/XTheme.hpp>
#include <docmodel/uno/UnoTheme.hxx>
#include <docmodel/theme/Theme.hxx>

using namespace css;

namespace
@@ -28,7 +33,85 @@ public:
    }
};

CPPUNIT_TEST_FIXTURE(ThemeImportExportTest, testThemeExport)
CPPUNIT_TEST_FIXTURE(ThemeImportExportTest, testThemeExportAndImport)
{
    mxComponent = loadFromDesktop("private:factory/scalc");
    {
        uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY_THROW);

        auto pTheme = std::make_shared<model::Theme>("MyTheme");
        auto pColorSet = std::make_shared<model::ColorSet>("MyColorSet");
        pColorSet->add(model::ThemeColorType::Dark1, 0x111111);
        pColorSet->add(model::ThemeColorType::Light1, 0x222222);
        pColorSet->add(model::ThemeColorType::Dark2, 0x333333);
        pColorSet->add(model::ThemeColorType::Light2, 0x444444);
        pColorSet->add(model::ThemeColorType::Accent1, 0x555555);
        pColorSet->add(model::ThemeColorType::Accent2, 0x666666);
        pColorSet->add(model::ThemeColorType::Accent3, 0x777777);
        pColorSet->add(model::ThemeColorType::Accent4, 0x888888);
        pColorSet->add(model::ThemeColorType::Accent5, 0x999999);
        pColorSet->add(model::ThemeColorType::Accent6, 0xaaaaaa);
        pColorSet->add(model::ThemeColorType::Hyperlink, 0xbbbbbb);
        pColorSet->add(model::ThemeColorType::FollowedHyperlink, 0xcccccc);
        pTheme->setColorSet(pColorSet);

        xPropertySet->setPropertyValue("Theme", uno::Any(model::theme::createXTheme(pTheme)));
    }

    // Check the "Theme" property
    {
        uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY_THROW);
        uno::Reference<util::XTheme> xTheme(xPropertySet->getPropertyValue("Theme"),
                                            uno::UNO_QUERY_THROW);
        CPPUNIT_ASSERT(xTheme.is());
        auto* pUnoTheme = dynamic_cast<UnoTheme*>(xTheme.get());
        CPPUNIT_ASSERT(pUnoTheme);
        auto pTheme = pUnoTheme->getTheme();

        CPPUNIT_ASSERT_EQUAL(OUString("MyTheme"), pTheme->GetName());
        CPPUNIT_ASSERT_EQUAL(OUString("MyColorSet"), pTheme->getColorSet()->getName());
        CPPUNIT_ASSERT_EQUAL(OUString("Office"), pTheme->getFontScheme().getName());
        CPPUNIT_ASSERT_EQUAL(OUString(""), pTheme->getFormatScheme().getName());
    }

    saveAndReload("calc8");

    {
        xmlDocUniquePtr pXmlDoc = parseExport("styles.xml");
        static constexpr OStringLiteral sThemePath = "//office:styles/loext:theme";
        assertXPath(pXmlDoc, sThemePath, 1);
        assertXPath(pXmlDoc, sThemePath + "[@loext:name='MyTheme']");
        const OString sThemeColorsPath = sThemePath + "/loext:theme-colors";
        assertXPath(pXmlDoc, sThemeColorsPath, 1);
        assertXPath(pXmlDoc, sThemeColorsPath + "[@loext:name='MyColorSet']");
        const OString sThemeColorPath = sThemeColorsPath + "/loext:color";
        assertXPath(pXmlDoc, sThemeColorPath, 12);
        assertXPath(pXmlDoc, sThemeColorPath + "[3]", "name", "dark2");
        assertXPath(pXmlDoc, sThemeColorPath + "[3]", "color", "#333333");
        assertXPath(pXmlDoc, sThemeColorPath + "[9]", "name", "accent5");
        assertXPath(pXmlDoc, sThemeColorPath + "[9]", "color", "#999999");
        assertXPath(pXmlDoc, sThemeColorPath + "[12]", "name", "followed-hyperlink");
        assertXPath(pXmlDoc, sThemeColorPath + "[12]", "color", "#cccccc");
    }

    // Check the theme after import/export cycle
    {
        uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY_THROW);
        uno::Reference<util::XTheme> xTheme(xPropertySet->getPropertyValue("Theme"),
                                            uno::UNO_QUERY_THROW);
        CPPUNIT_ASSERT(xTheme.is());
        auto* pUnoTheme = dynamic_cast<UnoTheme*>(xTheme.get());
        CPPUNIT_ASSERT(pUnoTheme);
        auto pTheme = pUnoTheme->getTheme();

        CPPUNIT_ASSERT_EQUAL(OUString("MyTheme"), pTheme->GetName());
        CPPUNIT_ASSERT_EQUAL(OUString("MyColorSet"), pTheme->getColorSet()->getName());
        CPPUNIT_ASSERT_EQUAL(OUString("Office"), pTheme->getFontScheme().getName());
        CPPUNIT_ASSERT_EQUAL(OUString(""), pTheme->getFormatScheme().getName());
    }
}

CPPUNIT_TEST_FIXTURE(ThemeImportExportTest, testThemeExportOOXML)
{
    loadFromURL(u"xlsx/CalcThemeTest.xlsx");

diff --git a/sc/source/filter/xml/xmlexprt.cxx b/sc/source/filter/xml/xmlexprt.cxx
index e38ed5d..65a9425 100644
--- a/sc/source/filter/xml/xmlexprt.cxx
+++ b/sc/source/filter/xml/xmlexprt.cxx
@@ -121,7 +121,9 @@
#include <svx/svdobj.hxx>
#include <svx/svdocapt.hxx>
#include <svx/svdomeas.hxx>
#include <svx/svdmodel.hxx>
#include <vcl/svapp.hxx>
#include <docmodel/theme/Theme.hxx>

#include <comphelper/processfactory.hxx>
#include <com/sun/star/beans/XPropertySet.hpp>
@@ -1991,6 +1993,25 @@ void ScXMLExport::ExportStyles_( bool bUsed )
        XML_STYLE_FAMILY_TABLE_CELL_STYLES_NAME, xCellStylesExportPropertySetMapper, false, XmlStyleFamily::TABLE_CELL);

    SvXMLExport::ExportStyles_(bUsed);

    exportTheme();
}

void ScXMLExport::exportTheme()
{
    if ((getSaneDefaultVersion() & SvtSaveOptions::ODFSVER_EXTENDED) == 0)
        return;

    SdrModel* pModel = GetDocument()->GetDrawLayer();

    if (!pModel)
        return;

    auto const& pTheme = pModel->getTheme();
    if (!pTheme)
        return;

    ExportThemeElement(pTheme);
}

void ScXMLExport::AddStyleFromCells(const uno::Reference<beans::XPropertySet>& xProperties,
diff --git a/sc/source/filter/xml/xmlexprt.hxx b/sc/source/filter/xml/xmlexprt.hxx
index 2fcdd02..aaf4907 100644
--- a/sc/source/filter/xml/xmlexprt.hxx
+++ b/sc/source/filter/xml/xmlexprt.hxx
@@ -222,6 +222,8 @@ class ScXMLExport : public SvXMLExport

    const ScXMLEditAttributeMap& GetEditAttributeMap() const;

    void exportTheme();

protected:
    virtual SvXMLAutoStylePoolP* CreateAutoStylePool() override;
    virtual XMLPageExport* CreatePageExport() override;
diff --git a/sc/source/ui/unoobj/docuno.cxx b/sc/source/ui/unoobj/docuno.cxx
index d987319..86333bb 100644
--- a/sc/source/ui/unoobj/docuno.cxx
+++ b/sc/source/ui/unoobj/docuno.cxx
@@ -57,6 +57,7 @@

#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/util/Date.hpp>
#include <com/sun/star/util/XTheme.hpp>
#include <com/sun/star/sheet/XNamedRanges.hpp>
#include <com/sun/star/sheet/XLabelRanges.hpp>
#include <com/sun/star/sheet/XSelectedSheetsSupplier.hpp>
@@ -82,6 +83,8 @@
#include <sfx2/lokhelper.hxx>
#include <sfx2/lokcomponenthelpers.hxx>
#include <sfx2/LokControlHandler.hxx>
#include <docmodel/uno/UnoTheme.hxx>
#include <docmodel/theme/Theme.hxx>

#include <cellsuno.hxx>
#include <columnspanset.hxx>
@@ -165,6 +168,7 @@ static o3tl::span<const SfxItemPropertyMapEntry> lcl_GetDocOptPropertyMap()
        { SC_UNO_LOOKUPLABELS,            PROP_UNO_LOOKUPLABELS, cppu::UnoType<bool>::get(),                         0, 0},
        { SC_UNO_MATCHWHOLE,              PROP_UNO_MATCHWHOLE, cppu::UnoType<bool>::get(),                           0, 0},
        { SC_UNO_NAMEDRANGES,             0, cppu::UnoType<sheet::XNamedRanges>::get(),             0, 0},
        { SC_UNO_THEME,                   0, cppu::UnoType<util::XTheme>::get(), 0,  0},
        { SC_UNO_DATABASERNG,             0, cppu::UnoType<sheet::XDatabaseRanges>::get(),          0, 0},
        { SC_UNO_NULLDATE,                PROP_UNO_NULLDATE, cppu::UnoType<util::Date>::get(),                      0, 0},
        { SC_UNO_ROWLABELRNG,             0, cppu::UnoType<sheet::XLabelRanges>::get(),             0, 0},
@@ -2837,6 +2841,16 @@ void SAL_CALL ScModelObj::setPropertyValue(
    {
        setGrabBagItem(aValue);
    }
    else if (aPropertyName == SC_UNO_THEME)
    {
        SdrModel& rSdrModel = getSdrModelFromUnoModel();
        uno::Reference<util::XTheme> xTheme;
        if (aValue >>= xTheme)
        {
            auto& rUnoTheme = dynamic_cast<UnoTheme&>(*xTheme);
            rSdrModel.setTheme(rUnoTheme.getTheme());
        }
    }

    if ( aNewOpt != rOldOpt )
    {
@@ -3023,6 +3037,15 @@ uno::Any SAL_CALL ScModelObj::getPropertyValue( const OUString& aPropertyName )
        {
            getGrabBagItem(aRet);
        }
        else if (aPropertyName == SC_UNO_THEME)
        {
            SdrModel& rSdrModel = getSdrModelFromUnoModel();
            css::uno::Reference<css::util::XTheme> xTheme;
            auto pTheme = rSdrModel.getTheme();
            if (pTheme)
                xTheme = model::theme::createXTheme(pTheme);
            aRet <<= xTheme;
        }
    }

    return aRet;
diff --git a/xmloff/inc/XMLThemeContext.hxx b/xmloff/inc/XMLThemeContext.hxx
index ce6f5ec..f8ee08d 100644
--- a/xmloff/inc/XMLThemeContext.hxx
+++ b/xmloff/inc/XMLThemeContext.hxx
@@ -10,7 +10,7 @@
#include <utility>
#include <xmloff/xmlprcon.hxx>

#include <com/sun/star/drawing/XDrawPage.hpp>
#include <com/sun/star/uno/XInterface.hpp>
#include <com/sun/star/util/Color.hpp>
#include <com/sun/star/container/XNameContainer.hpp>

@@ -24,13 +24,13 @@ class Theme;
/// Imports the theme
class XMLThemeContext : public SvXMLImportContext
{
    css::uno::Reference<css::drawing::XDrawPage> m_xPage;
    css::uno::Reference<css::uno::XInterface> m_xObject;
    std::shared_ptr<model::Theme> mpTheme;

public:
    XMLThemeContext(SvXMLImport& rImport,
                    css::uno::Reference<css::xml::sax::XFastAttributeList> const& xAttrList,
                    css::uno::Reference<css::drawing::XDrawPage> const& xPage);
                    css::uno::Reference<css::uno::XInterface> const& xObject);
    ~XMLThemeContext();

    css::uno::Reference<css::xml::sax::XFastContextHandler> SAL_CALL createFastChildContext(
diff --git a/xmloff/source/style/XMLThemeContext.cxx b/xmloff/source/style/XMLThemeContext.cxx
index 1579af4..5c210c7 100644
--- a/xmloff/source/style/XMLThemeContext.cxx
+++ b/xmloff/source/style/XMLThemeContext.cxx
@@ -33,9 +33,9 @@ using namespace xmloff::token;

XMLThemeContext::XMLThemeContext(SvXMLImport& rImport,
                                 const uno::Reference<xml::sax::XFastAttributeList>& xAttrList,
                                 css::uno::Reference<css::drawing::XDrawPage> const& xPage)
                                 css::uno::Reference<css::uno::XInterface> const& xObject)
    : SvXMLImportContext(rImport)
    , m_xPage(xPage)
    , m_xObject(xObject)
    , mpTheme(new model::Theme)
{
    for (const auto& rAttribute : sax_fastparser::castToFastAttributeList(xAttrList))
@@ -56,7 +56,7 @@ XMLThemeContext::~XMLThemeContext()
{
    if (mpTheme && mpTheme->getColorSet())
    {
        uno::Reference<beans::XPropertySet> xPropertySet(m_xPage, uno::UNO_QUERY);
        uno::Reference<beans::XPropertySet> xPropertySet(m_xObject, uno::UNO_QUERY);
        auto xTheme = model::theme::createXTheme(mpTheme);
        xPropertySet->setPropertyValue("Theme", uno::Any(xTheme));
    }
diff --git a/xmloff/source/style/xmlstyle.cxx b/xmloff/source/style/xmlstyle.cxx
index d0dc368..5158b43 100644
--- a/xmloff/source/style/xmlstyle.cxx
+++ b/xmloff/source/style/xmlstyle.cxx
@@ -683,6 +683,20 @@ SvXMLStylesContext::~SvXMLStylesContext()
css::uno::Reference< css::xml::sax::XFastContextHandler > SvXMLStylesContext::createFastChildContext(
        sal_Int32 nElement, const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList )
{
    if (nElement ==  XML_ELEMENT(LO_EXT, XML_THEME))
    {
        uno::Reference<uno::XInterface> xObject(GetImport().GetModel(), uno::UNO_QUERY);
        uno::Reference<drawing::XDrawPageSupplier> const xDrawPageSupplier(GetImport().GetModel(), uno::UNO_QUERY);
        if (xDrawPageSupplier.is())
        {
            uno::Reference<drawing::XDrawPage> xPage = xDrawPageSupplier->getDrawPage();
            if (xPage.is())
                xObject = xPage;
        }

        return new XMLThemeContext(GetImport(), xAttrList, xObject);
    }

    SvXMLStyleContext* pStyle = CreateStyleChildContext( nElement, xAttrList );
    if (pStyle)
    {
@@ -690,16 +704,6 @@ css::uno::Reference< css::xml::sax::XFastContextHandler > SvXMLStylesContext::cr
            mpImpl->AddStyle(pStyle);
        return pStyle;
    }
    else if (nElement ==  XML_ELEMENT(LO_EXT, XML_THEME))
    {
        uno::Reference<drawing::XDrawPageSupplier> const xDrawPageSupplier(GetImport().GetModel(), uno::UNO_QUERY);
        if (xDrawPageSupplier.is())
        {
            uno::Reference<drawing::XDrawPage> xPage = xDrawPageSupplier->getDrawPage();
            if (xPage.is())
                return new XMLThemeContext(GetImport(), xAttrList, xPage);
        }
    }

    return nullptr;
}