diff --git a/sc/sdi/scalc.sdi b/sc/sdi/scalc.sdi index 636c1c1a4..63cc999a7 100644 --- a/sc/sdi/scalc.sdi +++ b/sc/sdi/scalc.sdi @@ -521,7 +521,7 @@ SfxVoidItem StartFormula SID_START_FORMULA SfxVoidItem ChangeChartData SID_CHART_SOURCE -(SfxStringItem Name SID_CHART_NAME,SfxStringItem Range SID_CHART_SOURCE,SfxBoolItem ColHeaders FN_PARAM_1,SfxBoolItem RowHeaders FN_PARAM_2) +(SfxStringItem Name SID_CHART_NAME,SfxStringItem Range SID_CHART_SOURCE,SfxBoolItem ColHeaders FN_PARAM_1,SfxBoolItem RowHeaders FN_PARAM_2,SfxStringItem Chart FN_PARAM_3) [ AutoUpdate = FALSE, FastCall = FALSE, diff --git a/sc/sdi/tabvwsh.sdi b/sc/sdi/tabvwsh.sdi index 42c6018ed..9d7aebd2e 100644 --- a/sc/sdi/tabvwsh.sdi +++ b/sc/sdi/tabvwsh.sdi @@ -68,6 +68,7 @@ interface BaseSelection SfxVoidItem InsertObjectChart SID_INSERT_DIAGRAM ( + SfxStringItem Chart FN_PARAM_3, SfxBoolItem InNewTable FN_PARAM_4, SfxStringItem RangeList FN_PARAM_5 ) diff --git a/sc/inc/ChartTools.hxx b/sc/inc/ChartTools.hxx index bcb91b31f..2e49612ad 100644 --- a/sc/inc/ChartTools.hxx +++ b/sc/inc/ChartTools.hxx @@ -13,13 +13,18 @@ #include #include +#include +#include #include #include #include "types.hxx" +#include "rangelst.hxx" #include class ScDocShell; +class ScDocument; class SdrOle2Obj; +namespace tools { class JsonWriter; } namespace sctools { @@ -48,6 +53,17 @@ SdrOle2Obj* getChartByIndex(ScDocShell* pDocShell, SCTAB nTab, std::vector getAllPivotChartsConnectedTo(std::u16string_view sPivotTableName, ScDocShell& rDocShell); +/// `rText`'s ranges, when every part names existing cells. +std::optional chartRanges(const ScDocument& rDoc, std::u16string_view rText); +/// Whether a kit client's chart look parses. +bool isChartLook(std::u16string_view rJson); +/// Sets type, title, legend and axis titles from JSON. +bool setChartLook(const css::uno::Reference& xChart, + std::u16string_view rJson); +/// Writes the same look as JSON members. +void writeChartLook(::tools::JsonWriter& rJson, + const css::uno::Reference& xChart); + } // end sctools /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/unoobj/ChartTools.cxx b/sc/source/ui/unoobj/ChartTools.cxx index 82419ec2c..caba334bd 100644 --- a/sc/source/ui/unoobj/ChartTools.cxx +++ b/sc/source/ui/unoobj/ChartTools.cxx @@ -10,9 +10,21 @@ #include #include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -169,6 +181,154 @@ std::vector getAllPivotChartsConnectedTo(std::u16string_view sPivot return aObjects; } +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) +{ + boost::property_tree::ptree aTree; + std::stringstream aStream{ std::string(OUString(rJson).toUtf8()) }; + try + { + boost::property_tree::read_json(aStream, aTree); + } + catch (const boost::property_tree::json_parser_error&) + { + return std::nullopt; + } + const std::string aType = aTree.get("type", ""); + ChartLook aLook; + if (aType == "other") + aLook.aType = "other"; + for (const auto& [rName, rService] : aChartTypes) + if (rName == aType) + aLook.aType = rName; + if (aLook.aType.empty()) + return std::nullopt; + aLook.aTitle = OUString::fromUtf8(aTree.get("title", "")); + aLook.aXTitle = OUString::fromUtf8(aTree.get("xtitle", "")); + aLook.aYTitle = OUString::fromUtf8(aTree.get("ytitle", "")); + aLook.bLegend = aTree.get("legend", true); + 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; +} +} + +std::optional chartRanges(const ScDocument& rDoc, std::u16string_view rText) +{ + ScRangeList aRanges; + const ScRefFlags nFlags = aRanges.Parse(rText, rDoc, rDoc.GetAddressConvention()); + const bool bExist = std::all_of(aRanges.begin(), aRanges.end(), [&rDoc](const ScRange& rRange) { + return rRange.IsValid() && rDoc.HasTable(rRange.aStart.Tab()) + && rDoc.HasTable(rRange.aEnd.Tab()); + }); + if (!(nFlags & ScRefFlags::VALID) || aRanges.empty() || !bExist) + return std::nullopt; + return aRanges; +} + +bool isChartLook(std::u16string_view rJson) { return lcl_ParseLook(rJson).has_value(); } + +bool 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)) + { + if (xDiagram->getDiagramType() != pType->second) + { + 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")); + if (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 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()); +} + } // end sctools /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/drawfunc/fuins2.cxx b/sc/source/ui/drawfunc/fuins2.cxx index 23885c062..78e4a9c52 100644 --- a/sc/source/ui/drawfunc/fuins2.cxx +++ b/sc/source/ui/drawfunc/fuins2.cxx @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include @@ -421,6 +422,21 @@ FuInsertChart::FuInsertChart(ScTabViewShell& rViewSh, vcl::Window* pWin, ScDrawV if (!SvtModuleOptions().IsChartInstalled()) return; + // A kit client's look must parse before anything lands. + const SfxStringItem* pLook = rReq.GetArg(FN_PARAM_3); + if (pLook && !sctools::isChartLook(pLook->GetValue())) + { + SAL_WARN("sc.lok", "chart look does not parse: " << pLook->GetValue()); + return; + } + // A bad range would chart sample data. + const SfxStringItem* pRange = rReq.GetArg(FN_PARAM_5); + if (pRange && !sctools::chartRanges(rViewSh.GetViewData().GetDocument(), pRange->GetValue())) + { + SAL_WARN("sc.lok", "chart range does not parse: " << pRange->GetValue()); + return; + } + // BM/IHA -- // get range @@ -622,6 +638,15 @@ FuInsertChart::FuInsertChart(ScTabViewShell& rViewSh, vcl::Window* pWin, ScDrawV if( xChartModel.is() ) xChartModel->unlockControllers(); } + else if (comphelper::LibreOfficeKit::isActive()) + { + // No wizard: one undo step, the look from the request. + pView->AddUndo(std::make_unique(*pObj)); + if (pLook) + sctools::setChartLook(xChartModel, pLook->GetValue()); + if (xChartModel.is()) + xChartModel->unlockControllers(); + } else if (!rViewSh.isLOKMobilePhone()) { //the controller will be unlocked by the dialog when the dialog is told to do so diff --git a/sc/source/ui/docshell/docsh4.cxx b/sc/source/ui/docshell/docsh4.cxx index 33563857e..226b37d05 100644 --- a/sc/source/ui/docshell/docsh4.cxx +++ b/sc/source/ui/docshell/docsh4.cxx @@ -56,7 +56,6 @@ #include #include #include -#include #include #include #include @@ -95,6 +94,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include @@ -1600,6 +1604,68 @@ void UpdateAcceptChangesDialog() } } +namespace +{ +/// A chart's whole diagram and main title, cloned. +struct ChartState +{ + uno::Reference xDiagram; + uno::Reference xTitle; +}; + +template uno::Reference lcl_Clone(const uno::Reference& xFrom) +{ + uno::Reference xCloneable(xFrom, uno::UNO_QUERY); + return xCloneable.is() ? uno::Reference(xCloneable->createClone(), uno::UNO_QUERY_THROW) + : uno::Reference(); +} + +ChartState lcl_ChartState(const uno::Reference& xChart) +{ + uno::Reference xTitled(xChart, uno::UNO_QUERY_THROW); + return { lcl_Clone(xChart->getFirstDiagram()), lcl_Clone(xTitled->getTitleObject()) }; +} + +/// Undoes a chart's look by putting its whole diagram back. +class ScUndoChartLook : public ScSimpleUndo +{ + OUString maName; + ChartState maOld, maNew; + + void Apply(const ChartState& rState) + { + auto xChart = rDocShell.GetDocument().GetChartByName(maName); + uno::Reference xTitled(xChart, uno::UNO_QUERY_THROW); + xChart->setFirstDiagram(lcl_Clone(rState.xDiagram)); + xTitled->setTitleObject(lcl_Clone(rState.xTitle)); + } + +public: + ScUndoChartLook(ScDocShell& rShell, OUString aName, ChartState aOld, ChartState aNew) + : ScSimpleUndo(rShell) + , maName(std::move(aName)) + , maOld(std::move(aOld)) + , maNew(std::move(aNew)) + { + } + void Undo() override + { + BeginUndo(); + Apply(maOld); + EndUndo(); + } + void Redo() override + { + BeginRedo(); + Apply(maNew); + EndRedo(); + } + void Repeat(SfxRepeatTarget&) override {} + bool CanRepeat(SfxRepeatTarget&) const override { return false; } + OUString GetComment() const override { return ScResId(STR_UNDO_CHARTDATA); } +}; +} + void ScDocShell::ExecuteChartSource(SfxRequest& rReq) { const SfxItemSet* pReqArgs = rReq.GetArgs(); @@ -1658,6 +1724,17 @@ void ScDocShell::ExecuteChartSource(SfxRequest& rReq) aRangeListRef.clear(); } + // A kit client's look; it must parse before anything changes. + const SfxStringItem* pLook = rReq.GetArg(FN_PARAM_3); + if (pLook + && (!bColInit || !bRowInit || !sctools::isChartLook(pLook->GetValue()) + || !sctools::chartRanges(rDoc, aRangeName))) + { + SAL_WARN("sc.lok", "chart look needs headers and must parse: " << pLook->GetValue()); + rReq.Ignore(); + return; + } + ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell(); if (!pViewSh || !bValid || aChartName.isEmpty() ) { @@ -1714,9 +1791,23 @@ void ScDocShell::ExecuteChartSource(SfxRequest& rReq) } else { + SfxUndoManager* pUndoManager = GetUndoManager(); + // One undo step; it restores range, then diagram. + if (pLook) + { + pUndoManager->EnterListAction(ScResId(STR_UNDO_CHARTDATA), u""_ustr, 0, pViewSh->GetViewShellId()); + auto xChart = m_pDocument->GetChartByName(aChartName); + ChartState aOld = lcl_ChartState(xChart); + sctools::setChartLook(xChart, pLook->GetValue()); + if (bUndo) + pUndoManager->AddUndoAction(std::make_unique( + *this, aChartName, std::move(aOld), lcl_ChartState(xChart))); + } ExecuteChartSourcePost(bUndo, bMultiRange, aChartName, aRangeListRef, bColHeaders, bRowHeaders, bAddRange, nCol1, nRow1,nCol2, nRow2, nTab); + if (pLook) + pUndoManager->LeaveListAction(); rReq.Done(); } } diff --git a/sc/source/ui/unoobj/docuno.cxx b/sc/source/ui/unoobj/docuno.cxx index 7a0a1e54c..31a7d6fc3 100644 --- a/sc/source/ui/unoobj/docuno.cxx +++ b/sc/source/ui/unoobj/docuno.cxx @@ -154,10 +154,12 @@ #include #include #include +#include #include #include #include #include +#include #include @@ -914,6 +916,29 @@ void lcl_ConditionalFormats(tools::JsonWriter& rJson, ScDocument& rDoc, SCTAB nT } } +/// The one selected chart: name, data range, headers, look. +void lcl_Chart(tools::JsonWriter& rJson, ScDocument& rDoc, ScViewData& rData) +{ + rJson.put("commandName", ".uno:Chart"); + const ScDrawView* pView = rData.GetViewShell()->GetScDrawView(); + if (!pView || pView->GetMarkedObjectList().GetMarkCount() != 1) + return; + const auto* pObj = dynamic_cast(pView->GetMarkedObjectList().GetMark(0)->GetMarkedSdrObj()); + if (!pObj || !pObj->IsChart()) + return; + const OUString aName = pObj->GetPersistName(); + ScRangeList aRanges; + bool bColHeaders = false, bRowHeaders = false; + rDoc.GetOldChartParameters(aName, aRanges, bColHeaders, bRowHeaders); + OUString aRange; + aRanges.Format(aRange, ScRefFlags::RANGE_ABS_3D, rDoc, rDoc.GetAddressConvention()); + rJson.put("name", aName); + rJson.put("range", aRange); + rJson.put("colHeaders", bColHeaders); + rJson.put("rowHeaders", bRowHeaders); + sctools::writeChartLook(rJson, rDoc.GetChartByName(aName)); +} + /// The pivot table at the cursor: ranges and fields. void lcl_PivotTable(tools::JsonWriter& rJson, ScDocument& rDoc, const ScAddress& rCursor) { @@ -970,7 +995,8 @@ bool ScModelObj::supportsCommand(std::u16string_view rCommand) static constexpr std::u16string_view aCommands[] = { u"SheetReferences", u"DefinedNames", u"SelectedRange", u"AutoFilterValues", u"DataSelectValues", u"Validation", - u"NumberFormatCode", u"ConditionalFormats", u"PivotTable" }; + u"NumberFormatCode", u"ConditionalFormats", u"Chart", + u"PivotTable" }; return std::find(std::begin(aCommands), std::end(aCommands), rCommand) != std::end(aCommands); } @@ -1003,6 +1029,8 @@ void ScModelObj::getCommandValues(tools::JsonWriter& rJsonWriter, std::string_vi return lcl_NumberFormat(rJsonWriter, rDoc, aCursor, rCommand); if (rCommand == ".uno:ConditionalFormats") return lcl_ConditionalFormats(rJsonWriter, rDoc, nTab); + if (rCommand == ".uno:Chart") + return lcl_Chart(rJsonWriter, rDoc, *pCurrent); if (rCommand == ".uno:PivotTable") return lcl_PivotTable(rJsonWriter, rDoc, aCursor); }