svx: chart look helpers a kit client can drive ChartHelper gains IsChartLook, SetChartLook and WriteChartLook (type, titles, legend, as JSON), moved here from Calc so Impress can use them. The JSON keeps its types (boost.json): a field of the wrong type, such as a legend that is not true or false or a title that is not text, is refused before anything changes. A scatter starts as points only when the chart becomes one; a scatter kept as one keeps its lines. diff --git a/include/svx/charthelper.hxx b/include/svx/charthelper.hxx index 3ad1fba..9bdf0a6 100644 --- a/include/svx/charthelper.hxx +++ b/include/svx/charthelper.hxx @@ -31,6 +31,8 @@ namespace com::sun::star { } namespace basegfx { class B2DRange; } +namespace tools { class JsonWriter; } +namespace com::sun::star::uno { class XInterface; } class SAL_WARN_UNUSED SVXCORE_DLLPUBLIC ChartHelper { @@ -54,6 +56,13 @@ public: // newly created charts using this method static void AdaptDefaultsForChart( const css::uno::Reference < css::embed::XEmbeddedObject > & xEmbObj); + + /// A kit chart look: type, titles, legend, as JSON. + static bool IsChartLook(std::u16string_view rJson); + static bool SetChartLook(const css::uno::Reference& xChart, + std::u16string_view rJson); + static void WriteChartLook(::tools::JsonWriter& rJson, + const css::uno::Reference& xChart); }; #endif // INCLUDED_SVX_CHARTHELPER_HXX diff --git a/svx/Library_svxcore.mk b/svx/Library_svxcore.mk index e2d178b..1dfa044 100644 --- a/svx/Library_svxcore.mk +++ b/svx/Library_svxcore.mk @@ -332,6 +332,7 @@ $(eval $(call gb_Library_add_exception_objects,svxcore,\ svx/source/sdr/properties/e3dcompoundproperties \ svx/source/sdr/properties/oleproperties \ svx/source/svdraw/charthelper \ + svx/source/svdraw/chartlook \ svx/source/svdraw/constructhelper \ svx/source/svdraw/clonelist \ svx/source/svdraw/gradtrns \ diff --git a/svx/source/svdraw/chartjson.hxx b/svx/source/svdraw/chartjson.hxx new file mode 100644 index 0000000..d105464 --- /dev/null +++ b/svx/source/svdraw/chartjson.hxx @@ -0,0 +1,26 @@ +/* -*- 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/. + */ + +#pragma once + +#include +#include + +#include +#include + +namespace svx::chartjson +{ +/// The JSON object, or none if it is not one. +std::optional Object(std::u16string_view rJson); +/// Member `rKey` as text; absent is empty, another type fails. +std::optional Text(const boost::json::object& rObject, std::string_view rKey); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/svdraw/chartlook.cxx b/svx/source/svdraw/chartlook.cxx new file mode 100644 index 0000000..79d4e6c --- /dev/null +++ b/svx/source/svdraw/chartlook.cxx @@ -0,0 +1,193 @@ +/* -*- 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/. + */ + +#include +#include "chartjson.hxx" + +#include +#include +#include +#include +#include +#include +#include +// One translation unit builds boost.json. +#include + +#include +#include +#include + +using namespace ::com::sun::star; + +namespace svx::chartjson +{ +/// The JSON object, or none if it is not one. +std::optional Object(std::u16string_view rJson) +{ + boost::system::error_code aError; + boost::json::value aValue = boost::json::parse(std::string(OUString(rJson).toUtf8()), aError); + if (aError || !aValue.is_object()) + return std::nullopt; + return aValue.as_object(); +} + +/// Member `rKey` as text; absent is empty, another type fails. +std::optional Text(const boost::json::object& rObject, std::string_view rKey) +{ + const boost::json::value* pValue = rObject.if_contains(rKey); + if (!pValue) + return OUString(); + if (!pValue->is_string()) + return std::nullopt; + const boost::json::string& rText = pValue->as_string(); + return OUString::fromUtf8(std::string_view(rText.data(), rText.size())); +} +} + +namespace +{ +/// Kit names and the diagram services they map to. +constexpr std::pair aChartTypes[] + = { { "column", u"com.sun.star.chart.BarDiagram" }, + { "bar", u"com.sun.star.chart.BarDiagram" }, + { "line", u"com.sun.star.chart.LineDiagram" }, + { "scatter", u"com.sun.star.chart.XYDiagram" }, + { "pie", u"com.sun.star.chart.PieDiagram" } }; + +/// A parsed look; "other" keeps the diagram. +struct ChartLook +{ + std::string_view aType; + OUString aTitle, aXTitle, aYTitle; + bool bLegend = true; +}; + +std::optional lcl_ParseLook(std::u16string_view rJson) +{ + const std::optional oObject = svx::chartjson::Object(rJson); + if (!oObject) + return std::nullopt; + const boost::json::value* pType = oObject->if_contains("type"); + if (!pType || !pType->is_string()) + return std::nullopt; + const std::string_view aType(pType->as_string().data(), pType->as_string().size()); + ChartLook aLook; + if (aType == "other") + aLook.aType = "other"; + for (const auto& [rName, rService] : aChartTypes) + if (rName == aType) + aLook.aType = rName; + const std::optional oTitle = svx::chartjson::Text(*oObject, "title"); + const std::optional oXTitle = svx::chartjson::Text(*oObject, "xtitle"); + const std::optional oYTitle = svx::chartjson::Text(*oObject, "ytitle"); + const boost::json::value* pLegend = oObject->if_contains("legend"); + if (aLook.aType.empty() || !oTitle || !oXTitle || !oYTitle || (pLegend && !pLegend->is_bool())) + return std::nullopt; + aLook.aTitle = *oTitle; + aLook.aXTitle = *oXTitle; + aLook.aYTitle = *oYTitle; + aLook.bLegend = !pLegend || pLegend->as_bool(); + return aLook; +} + +/// Shows a title with this text; empty hides it. +void lcl_SetTitle(const uno::Reference& xOwner, const OUString& rHas, + const std::function()>& rTitle, + const OUString& rText) +{ + xOwner->setPropertyValue(rHas, uno::Any(!rText.isEmpty())); + if (rText.isEmpty()) + return; + uno::Reference xTitle(rTitle(), uno::UNO_QUERY_THROW); + xTitle->setPropertyValue(u"String"_ustr, uno::Any(rText)); +} + +OUString lcl_Title(const uno::Reference& xOwner, const OUString& rHas, + const std::function()>& rTitle) +{ + OUString aText; + if (!xOwner->getPropertyValue(rHas).get()) + return aText; + uno::Reference xTitle(rTitle(), uno::UNO_QUERY_THROW); + xTitle->getPropertyValue(u"String"_ustr) >>= aText; + return aText; +} +} + +bool ChartHelper::IsChartLook(std::u16string_view rJson) { return lcl_ParseLook(rJson).has_value(); } + +bool ChartHelper::SetChartLook(const uno::Reference& xChart, + std::u16string_view rJson) +{ + const std::optional oLook = lcl_ParseLook(rJson); + uno::Reference xDoc(xChart, uno::UNO_QUERY); + if (!oLook || !xDoc.is()) + return false; + uno::Reference xDiagram = xDoc->getDiagram(); + const auto pType = std::find_if(std::begin(aChartTypes), std::end(aChartTypes), + [&oLook](const auto& rPair) { return rPair.first == oLook->aType; }); + if (pType != std::end(aChartTypes)) + { + const bool bNewType = xDiagram->getDiagramType() != pType->second; + if (bNewType) + { + uno::Reference xFactory(xDoc, uno::UNO_QUERY_THROW); + xDiagram.set(xFactory->createInstance(OUString(pType->second)), uno::UNO_QUERY_THROW); + xDoc->setDiagram(xDiagram); + } + uno::Reference xProps(xDiagram, uno::UNO_QUERY_THROW); + if (pType->second == u"com.sun.star.chart.BarDiagram") + xProps->setPropertyValue(u"Vertical"_ustr, uno::Any(oLook->aType == "bar")); + // A new scatter starts as points; kept ones keep lines. + if (bNewType && oLook->aType == "scatter") + xProps->setPropertyValue(u"Lines"_ustr, uno::Any(false)); + } + uno::Reference xDocProps(xDoc, uno::UNO_QUERY_THROW); + lcl_SetTitle(xDocProps, u"HasMainTitle"_ustr, [&xDoc] { return xDoc->getTitle(); }, + oLook->aTitle); + xDocProps->setPropertyValue(u"HasLegend"_ustr, uno::Any(oLook->bLegend)); + // A pie has no axes. + if (xDiagram->getDiagramType() == u"com.sun.star.chart.PieDiagram") + return true; + uno::Reference xX(xDiagram, uno::UNO_QUERY_THROW); + uno::Reference xY(xDiagram, uno::UNO_QUERY_THROW); + uno::Reference xProps(xDiagram, uno::UNO_QUERY_THROW); + lcl_SetTitle(xProps, u"HasXAxisTitle"_ustr, [&xX] { return xX->getXAxisTitle(); }, + oLook->aXTitle); + lcl_SetTitle(xProps, u"HasYAxisTitle"_ustr, [&xY] { return xY->getYAxisTitle(); }, + oLook->aYTitle); + return true; +} + +void ChartHelper::WriteChartLook(::tools::JsonWriter& rJson, + const uno::Reference& xChart) +{ + uno::Reference xDoc(xChart, uno::UNO_QUERY_THROW); + uno::Reference xDiagram = xDoc->getDiagram(); + uno::Reference xProps(xDiagram, uno::UNO_QUERY_THROW); + const OUString aService = xDiagram->getDiagramType(); + std::string_view aType = "other"; + for (const auto& [rName, rService] : aChartTypes) + if (rService == aService && aType == "other") + aType = rName; + if (aType == "column" && xProps->getPropertyValue(u"Vertical"_ustr).get()) + aType = "bar"; + rJson.put("type", aType); + uno::Reference xDocProps(xDoc, uno::UNO_QUERY_THROW); + rJson.put("title", lcl_Title(xDocProps, u"HasMainTitle"_ustr, [&xDoc] { return xDoc->getTitle(); })); + rJson.put("legend", xDocProps->getPropertyValue(u"HasLegend"_ustr).get()); + uno::Reference xX(xDiagram, uno::UNO_QUERY_THROW); + uno::Reference xY(xDiagram, uno::UNO_QUERY_THROW); + const bool bAxes = aType != "pie"; + rJson.put("xtitle", bAxes ? lcl_Title(xProps, u"HasXAxisTitle"_ustr, [&xX] { return xX->getXAxisTitle(); }) : OUString()); + rJson.put("ytitle", bAxes ? lcl_Title(xProps, u"HasYAxisTitle"_ustr, [&xY] { return xY->getYAxisTitle(); }) : OUString()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */