chart2/Library_chart2.mk | 3 chart2/UIConfig_chart2.mk | 3 chart2/source/chart2.component | 4 chart2/source/controller/dialogs/dlg_Theme.cxx | 228 +++ chart2/source/controller/inc/ChartController.hxx | 4 chart2/source/controller/inc/dlg_Theme.hxx | 54 chart2/source/controller/main/ChartController.cxx | 571 ++++++++++ chart2/source/controller/main/ChartController_Insert.cxx | 1 chart2/source/controller/main/ChartController_Properties.cxx | 1 chart2/source/controller/main/ChartController_Window.cxx | 3 chart2/source/controller/main/ControllerCommandDispatch.cxx | 2 chart2/source/controller/sidebar/Chart2PanelFactory.cxx | 5 chart2/source/controller/sidebar/ChartThemeControl.cxx | 223 +++ chart2/source/controller/sidebar/ChartThemeControl.hxx | 77 + chart2/source/controller/sidebar/ChartThemePanel.cxx | 246 ++++ chart2/source/controller/sidebar/ChartThemePanel.hxx | 85 + chart2/source/model/main/ChartModel.cxx | 1 chart2/uiconfig/ui/chartthemepopup.ui | 38 chart2/uiconfig/ui/dlg_Theme.ui | 182 +++ chart2/uiconfig/ui/sidebartheme.ui | 61 + drawinglayer/source/primitive2d/sceneprimitive2d.cxx | 9 include/svx/ChartThemeType.hxx | 88 + officecfg/registry/data/org/openoffice/Office/UI/ChartCommands.xcu | 6 officecfg/registry/data/org/openoffice/Office/UI/Controller.xcu | 11 officecfg/registry/data/org/openoffice/Office/UI/Sidebar.xcu | 30 static/CustomTarget_emscripten_fs_image.mk | 2 vcl/jsdialog/enabled.cxx | 3 27 files changed, 1940 insertions(+), 1 deletion(-)
New commits: commit 2577464d03584dcd8b73c608a31595c437d2217d Author: Attila Szűcs <[email protected]> AuthorDate: Mon Jun 23 16:04:42 2025 +0200 Commit: Marco Cecchetti <[email protected]> CommitDate: Wed Feb 4 11:39:30 2026 +0100 Chart: implemented chart themes (experimental) This commit content is hided behind ExperimentalMode. Added a sidebar panel to set a chart style. Similar to MS office "chart style". It sets many chart properties at once, but now mostly font related. ( It is planned to extend it with many other properties ) We can even save a chart style into the themes there with a button. Added a dialog to manage chart themes. -we can save the selected chart properties into a new theme -we can overwrite a theme with the selected chart. -we can set theme properties to the selected chrat. -we can delete a theme ( MSO does not able to do these. :) ) there is 4 pre-defined theme that user cannot change. ( The 4 pre defeined chart theme is only a test case placeholder it is planned to make them more sensible and more of them. ) There is some todo with it: -render of the chart thumnbail still not fast enought. (now, ChartView::update() is the slow) -we could probably find better places for the predefined themes -expand the saved properties with many more. (already added the legends position) -export/import of the user generated themes Change-Id: Ie93a2561df92c54cea1746d59dec665cda929060 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/186950 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Caolán McNamara <[email protected]> Tested-by: Caolán McNamara <[email protected]> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198495 Reviewed-by: Marco Cecchetti <[email protected]> diff --git a/chart2/Library_chart2.mk b/chart2/Library_chart2.mk index 764ee2642679..a264e364f95d 100644 --- a/chart2/Library_chart2.mk +++ b/chart2/Library_chart2.mk @@ -123,6 +123,7 @@ $(eval $(call gb_Library_add_exception_objects,chart2,\ chart2/source/controller/dialogs/dlg_ObjectProperties \ chart2/source/controller/dialogs/dlg_ShapeFont \ chart2/source/controller/dialogs/dlg_ShapeParagraph \ + chart2/source/controller/dialogs/dlg_Theme \ chart2/source/controller/dialogs/dlg_View3D \ chart2/source/controller/dialogs/ObjectNameProvider \ chart2/source/controller/dialogs/res_BarGeometry \ @@ -217,6 +218,8 @@ $(eval $(call gb_Library_add_exception_objects,chart2,\ chart2/source/controller/sidebar/ChartSeriesPanel \ chart2/source/controller/sidebar/ChartSidebarModifyListener \ chart2/source/controller/sidebar/ChartSidebarSelectionListener \ + chart2/source/controller/sidebar/ChartThemePanel \ + chart2/source/controller/sidebar/ChartThemeControl \ chart2/source/controller/sidebar/ChartTypePanel \ chart2/source/controller/uitest/uiobject \ )) diff --git a/chart2/UIConfig_chart2.mk b/chart2/UIConfig_chart2.mk index 722faf0d8580..12411d12801a 100644 --- a/chart2/UIConfig_chart2.mk +++ b/chart2/UIConfig_chart2.mk @@ -38,6 +38,7 @@ $(eval $(call gb_UIConfig_add_uifiles,modules/schart,\ chart2/uiconfig/ui/3dviewdialog \ chart2/uiconfig/ui/attributedialog \ chart2/uiconfig/ui/chartcolorpalettepopup \ + chart2/uiconfig/ui/chartthemepopup \ chart2/uiconfig/ui/chardialog \ chart2/uiconfig/ui/chartdatadialog \ chart2/uiconfig/ui/charttypedialog \ @@ -48,6 +49,7 @@ $(eval $(call gb_UIConfig_add_uifiles,modules/schart,\ chart2/uiconfig/ui/dlg_InsertDataTable \ chart2/uiconfig/ui/dlg_InsertErrorBars \ chart2/uiconfig/ui/dlg_InsertLegend \ + chart2/uiconfig/ui/dlg_Theme \ chart2/uiconfig/ui/imagefragment \ chart2/uiconfig/ui/insertaxisdlg \ chart2/uiconfig/ui/insertgriddlg \ @@ -55,6 +57,7 @@ $(eval $(call gb_UIConfig_add_uifiles,modules/schart,\ chart2/uiconfig/ui/paradialog \ chart2/uiconfig/ui/sidebaraxis \ chart2/uiconfig/ui/sidebarcolors \ + chart2/uiconfig/ui/sidebartheme \ chart2/uiconfig/ui/sidebarelements \ chart2/uiconfig/ui/sidebarerrorbar \ chart2/uiconfig/ui/sidebarseries \ diff --git a/chart2/source/chart2.component b/chart2/source/chart2.component index 4f1fcb92ed0d..fec31123a100 100644 --- a/chart2/source/chart2.component +++ b/chart2/source/chart2.component @@ -338,4 +338,8 @@ constructor="com_sun_star_comp_chart2_ChartColorPaletteControl_get_implementation"> <service name="com.sun.star.frame.ToolbarController"/> </implementation> + <implementation name="com.sun.star.comp.chart2.ChartThemeControl" + constructor="com_sun_star_comp_chart2_ChartThemeControl_get_implementation"> + <service name="com.sun.star.frame.ToolbarController"/> + </implementation> </component> diff --git a/chart2/source/controller/dialogs/dlg_Theme.cxx b/chart2/source/controller/dialogs/dlg_Theme.cxx new file mode 100644 index 000000000000..576e17d6c5a8 --- /dev/null +++ b/chart2/source/controller/dialogs/dlg_Theme.cxx @@ -0,0 +1,228 @@ +/* -*- 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 <dlg_Theme.hxx> + +#include <comphelper/processfactory.hxx> +#include <unotools/fcm.hxx> +#include <vcl/virdev.hxx> +#include <ChartModel.hxx> +#include <svx/ChartThemeType.hxx> + +using namespace css; + +namespace chart +{ +SchThemeDlg::SchThemeDlg(weld::Window* pWindow, ChartController* pController) + : GenericDialogController(pWindow, u"modules/schart/ui/dlg_Theme.ui"_ustr, + u"ChartThemeDialog"_ustr) + , mxModel(pController->getChartModel()) + , mpController(pController) + , mxThemeIconView(m_xBuilder->weld_icon_view(u"themeview"_ustr)) + , mxSaveTheme(m_xBuilder->weld_button(u"save"_ustr)) + , mxLoadTheme(m_xBuilder->weld_button(u"load"_ustr)) + , mxDeleteTheme(m_xBuilder->weld_button(u"delete"_ustr)) + , mxSaveToNewTheme(m_xBuilder->weld_button(u"savetonew"_ustr)) +{ + mxThemeIconView->connect_selection_changed(LINK(this, SchThemeDlg, ThemeSelectedHdl)); + + mxSaveTheme->connect_clicked(LINK(this, SchThemeDlg, ClickSaveHdl)); + mxLoadTheme->connect_clicked(LINK(this, SchThemeDlg, ClickLoadHdl)); + mxDeleteTheme->connect_clicked(LINK(this, SchThemeDlg, ClickDeleteHdl)); + mxSaveToNewTheme->connect_clicked(LINK(this, SchThemeDlg, ClickSaveToNewHdl)); + + mxSaveTheme->set_sensitive(false); + mxDeleteTheme->set_sensitive(false); + + mxSaveTheme->set_visible(true); + mxLoadTheme->set_visible(true); + mxDeleteTheme->set_visible(true); + mxSaveToNewTheme->set_visible(true); + + mxThemeIconView->set_item_width(200); + + int nThemeCount = ChartThemesType::getInstance().getThemesCount(); + for (int i = 0; i < nThemeCount; i++) + { + ScopedVclPtr<VirtualDevice> device1 = makeImage(i); + Bitmap aBmp = device1->GetBitmap(Point(), device1->GetOutputSizePixel()); + + OUString sId = OUString::number(i); + OUString sLayoutName = OUString::number(i); + mxThemeIconView->insert(i, &sLayoutName, &sId, &aBmp, nullptr); + + device1.disposeAndClear(); + } + mxThemeIconView->select(0); +} + +VclPtr<VirtualDevice> SchThemeDlg::makeImage(int nIndex) +{ + // clone the chart + rtl::Reference<ChartModel> mxModel2 = new ChartModel(*mxModel); + auto xComponent = comphelper::getProcessComponentContext(); + rtl::Reference<ChartController> pController2 = new ChartController(xComponent); + utl::ConnectFrameControllerModel(nullptr, pController2, mxModel2); + + // Change the cloned chart theme + pController2->setTheme(nIndex); + + //make a picture from it + awt::Size aSize = mxModel->getVisualAreaSize(1); //embed::Aspects::MSOLE_CONTENT + if (ReferenceSizeProvider::getAutoResizeState(mxModel2) + != ReferenceSizeProvider::AUTO_RESIZE_YES) + { + awt::Size aPageSize(mxModel2->getPageSize()); + ReferenceSizeProvider aRSP(aPageSize, mxModel2); + aRSP.toggleAutoResizeState(); + } + + ScopedVclPtr<VirtualDevice> device2 + = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA); + + // Todo: find a faster way to render scaled down charts + int scale = 1; + mxModel2->setVisualAreaSize(1, awt::Size(aSize.Width / scale, aSize.Height / scale)); + //device1->SetOutputSize(Size(aSize.Width, aSize.Height)); + device2->SetOutputSize(Size(aSize.Width / scale, aSize.Height / scale)); + + GDIMetaFile aMTF; + aMTF.Record(device2); + + pController2->PrePaint(); + //pController2->execute_Paint(*device1, tools::Rectangle(0, 0, aSize.Width, aSize.Height)); + bChartThumbnailRendered = true; + pController2->execute_Paint(*device2, + tools::Rectangle(0, 0, aSize.Width / scale, aSize.Height / scale)); + bChartThumbnailRendered = false; + aMTF.Stop(); + aMTF.WindStart(); + aMTF.Scale(1 / 64.0, 1 / 64.0); + aMTF.WindStart(); + aMTF.Play(*device2); + + //scale it into a fixed small image + ScopedVclPtr<VirtualDevice> device1 + = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA); + device1->SetOutputSizePixel(Size(ChartThemeThumbSizeX, ChartThemeThumbSizeY)); + device1->SetBackground(Wallpaper(COL_YELLOW)); + device1->Erase(); + + Bitmap aBmpEx = device2->GetBitmap(Point(), device2->GetOutputSizePixel()); + aBmpEx.Scale(Size(ChartThemeThumbSizeX, ChartThemeThumbSizeY), BmpScaleFlag::Fast); + + device1->DrawBitmap(Point(0, 0), aBmpEx); + device2.disposeAndClear(); + + return std::move(device1); +} + +IMPL_LINK_NOARG(SchThemeDlg, ClickSaveHdl, weld::Button&, void) +{ + OUString sId = mxThemeIconView->get_selected_id(); + if (sId.isEmpty()) + return; + int nIndex = sId.toInt32(); + mpController->saveTheme(nIndex); + + //update the image of the list + // TODO: we could gain a bit of performance by not cloneing the chart + // because the new theme was creayed from the selected chart. + ScopedVclPtr<VirtualDevice> device1 = makeImage(nIndex); + + mxThemeIconView->set_image(nIndex, *device1); + device1.disposeAndClear(); +} + +IMPL_LINK_NOARG(SchThemeDlg, ClickLoadHdl, weld::Button&, void) +{ + OUString sId = mxThemeIconView->get_selected_id(); + if (sId.isEmpty()) + return; + int nIndex = sId.toInt32(); + mpController->setTheme(nIndex); +} + +IMPL_LINK_NOARG(SchThemeDlg, ClickDeleteHdl, weld::Button&, void) +{ + OUString sId = mxThemeIconView->get_selected_id(); + if (sId.isEmpty()) + return; + int nIndex = sId.toInt32(); + // remove it from the themes + ChartThemesType& aChartTypes = ChartThemesType::getInstance(); + aChartTypes.m_aThemes.erase(aChartTypes.m_aThemes.begin() + nIndex); + // remove it from the list + mxThemeIconView->remove(nIndex); + // fix the numbers in the list. + int nThemeCount = ChartThemesType::getInstance().getThemesCount(); + for (int i = nIndex; i < nThemeCount; i++) + { + sId = OUString::number(i); + OUString sLayoutName = OUString::number(i); + mxThemeIconView->set_id(i, sId); + mxThemeIconView->set_text(i, sLayoutName); + } +} + +IMPL_LINK_NOARG(SchThemeDlg, ClickSaveToNewHdl, weld::Button&, void) +{ + ChartThemesType& aChartTypes = ChartThemesType::getInstance(); + int nIndex = aChartTypes.m_aThemes.size(); + // maximum 256 chart styles + // Todo: maybe we could figure out some other way to limit this. + if (nIndex >= 256) + { + return; + } + mpController->saveTheme(nIndex); + + //insert image to the list + ScopedVclPtr<VirtualDevice> device1 = makeImage(nIndex); + Bitmap aBmp = device1->GetBitmap(Point(), device1->GetOutputSizePixel()); + + OUString sId = OUString::number(nIndex); + OUString sLayoutName = OUString::number(nIndex); + mxThemeIconView->insert(nIndex, &sLayoutName, &sId, &aBmp, nullptr); + device1.disposeAndClear(); +} + +IMPL_LINK_NOARG(SchThemeDlg, ThemeSelectedHdl, weld::IconView&, void) +{ + // enable / disable buttons based on the selected theme + bool bSelCustomizable = false; + bool bSelected = false; + OUString sId = mxThemeIconView->get_selected_id(); + if (!sId.isEmpty()) + { + int nIndex = sId.toInt32(); + int nPreDefCount = ChartThemesType::getThemesPreDefCount(); + // only non-PreDefined themes can be deleted / overwrited + if (nIndex >= nPreDefCount) + bSelCustomizable = true; + bSelected = true; + } + mxSaveTheme->set_sensitive(bSelCustomizable); + mxDeleteTheme->set_sensitive(bSelCustomizable); + mxLoadTheme->set_sensitive(bSelected); +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/controller/inc/ChartController.hxx b/chart2/source/controller/inc/ChartController.hxx index 16538f97a50c..e7f848ad0153 100644 --- a/chart2/source/controller/inc/ChartController.hxx +++ b/chart2/source/controller/inc/ChartController.hxx @@ -332,6 +332,9 @@ public: const Selection& getSelectionMember() const { return m_aSelection; } + void setTheme(sal_uInt32 nIndex); + void saveTheme(sal_uInt32 nIndex); + private: class TheModel : public salhelper::SimpleReferenceObject { @@ -425,6 +428,7 @@ private: void executeDlg_ObjectProperties_withUndoGuard( std::shared_ptr<UndoGuard> aUndoGuard, const OUString& rObjectCID, bool bSuccessOnUnchanged ); void executeDispatch_ChartType(); + void executeDispatch_ManageThemes(); void executeDispatch_InsertTitles(); void executeDispatch_InsertLegend(); diff --git a/chart2/source/controller/inc/dlg_Theme.hxx b/chart2/source/controller/inc/dlg_Theme.hxx new file mode 100644 index 000000000000..3891e205565d --- /dev/null +++ b/chart2/source/controller/inc/dlg_Theme.hxx @@ -0,0 +1,54 @@ +/* -*- 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 "res_Titles.hxx" +#include <vcl/weld.hxx> +#include <memory> +#include "ChartController.hxx" + +namespace chart +{ +class SchThemeDlg final : public weld::GenericDialogController +{ +private: + rtl::Reference<ChartModel> mxModel; + ChartController* mpController; + + std::unique_ptr<weld::IconView> mxThemeIconView; + std::unique_ptr<weld::Button> mxSaveTheme; + std::unique_ptr<weld::Button> mxLoadTheme; + std::unique_ptr<weld::Button> mxDeleteTheme; + std::unique_ptr<weld::Button> mxSaveToNewTheme; + + DECL_LINK(ClickSaveHdl, weld::Button&, void); + DECL_LINK(ClickLoadHdl, weld::Button&, void); + DECL_LINK(ClickDeleteHdl, weld::Button&, void); + DECL_LINK(ClickSaveToNewHdl, weld::Button&, void); + DECL_LINK(ThemeSelectedHdl, weld::IconView&, void); + + VclPtr<VirtualDevice> makeImage(int nIndex); + +public: + SchThemeDlg(weld::Window* pParent, ChartController* pController); +}; + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/controller/main/ChartController.cxx b/chart2/source/controller/main/ChartController.cxx index 2f69ec1f7764..e81171eb8a86 100644 --- a/chart2/source/controller/main/ChartController.cxx +++ b/chart2/source/controller/main/ChartController.cxx @@ -98,6 +98,10 @@ #include <editeng/kernitem.hxx> #include <editeng/flstitem.hxx> +#include <dlg_Theme.hxx> +#include <ObjectHierarchy.hxx> +#include <svx/ChartThemeType.hxx> + // enable the following define to let the controller listen to model changes and // react on this by rebuilding the view #define TEST_ENABLE_MODIFY_LISTENER @@ -1139,6 +1143,8 @@ void SAL_CALL ChartController::dispatch( } else if(aCommand == "DiagramData" ) this->executeDispatch_EditData(); + else if( aCommand == "ManageThemes") + this->executeDispatch_ManageThemes(); //insert objects else if( aCommand == "InsertTitles" || aCommand == "InsertMenuTitles") @@ -1461,6 +1467,7 @@ void SAL_CALL ChartController::dispatch( } if (bAllPropertiesExist) { + SolarMutexGuard aSolarGuard; UndoGuard aUndoGuard(EditResId(RID_OUTLUNDO_ATTR), m_xUndoManager); if (aCommand == "Bold") { @@ -1577,6 +1584,33 @@ void ChartController::executeDispatch_ChartType() }); } +void ChartController::executeDispatch_ManageThemes() +{ + SolarMutexGuard aSolarGuard; + auto xUndoGuard = std::make_shared<UndoGuard>( + ActionDescriptionProvider::createDescription(ActionDescriptionProvider::ActionType::Insert, + SchResId(STR_OBJECT_TITLES)), + m_xUndoManager); + + try + { + auto aDlg = std::make_shared<SchThemeDlg>(GetChartFrame(), this); + weld::DialogController::runAsync( + aDlg, + [aDlg, xUndoGuard = std::move(xUndoGuard)](int nResult) { + if (nResult == RET_OK) + { + xUndoGuard->commit(); + } + }); + } + catch (const uno::RuntimeException&) + { + TOOLS_WARN_EXCEPTION("chart2", ""); + } +} + + void ChartController::executeDispatch_SourceData() { //convert properties to ItemSet @@ -1803,6 +1837,7 @@ const o3tl::sorted_vector< std::u16string_view >& ChartController::impl_getAvail static const o3tl::sorted_vector< std::u16string_view > s_AvailableCommands { // toolbar commands u"ChartElementSelector", + u"ManageThemes", // LOK commands u"LOKSetTextSelection", u"LOKTransform", @@ -1815,6 +1850,542 @@ ViewElementListProvider ChartController::getViewElementListProvider() return ViewElementListProvider(m_pDrawModelWrapper.get()); } +static std::set<OUString>& lcl_GetPropertyNameVec() +{ + static std::set<OUString> aFontPropertyNameVec + { + "CharFontName", + "CharFontFamily", + "CharFontStyleName", + "CharFontCharSet", + "CharFontPitch", + "CharPosture", + "CharWeight", + "CharHeight", + + "CharFontNameAsian", + "CharFontFamilyAsian", + "CharFontStyleNameAsian", + "CharFontCharSetAsian", + "CharFontPitchAsian", + "CharPostureAsian", + "CharWeightAsian", + "CharHeightAsian", + + "CharFontNameComplex", + "CharFontFamilyComplex", + "CharFontStyleNameComplex", + "CharFontCharSetComplex", + "CharFontPitchComplex", + "CharPostureComplex", + "CharWeightComplex", + "CharHeightComplex", + + "CharUnderline", + "CharUnderlineColor", + "CharUnderlineHasColor", + "CharStrikeout", + "CharOverline", + "CharOverlineColor", + "CharOverlineHasColor", + + "CharColor", + "CharKerning", + "CharShadowed", + "CharRelief", + "CharContoured", + "CharEmphasis", + //some non font related but needed property + "AnchorPosition" + }; + return aFontPropertyNameVec; +} + +static void lcl_SetOrGetThemeToOrFromElement(bool bSet, ChartThemeType& aTheme, + ObjectHierarchy& aHierarchy, + ObjectIdentifier& aElement, + rtl::Reference<::chart::ChartModel>& xCModel) +{ + const OUString& aCID = aElement.getObjectCID(); + ObjectType aElementType = aElement.getObjectType(); + + ChartThemeElementID nElementID = ChartThemeElementID::Nothing; + switch (aElementType) + { + case OBJECTTYPE_TITLE: + { + size_t nLastSign = aCID.lastIndexOf(u":Title="); + if (nLastSign != std::u16string_view::npos) + nElementID = ChartThemeElementID::SubTitle; + else + nElementID = ChartThemeElementID::Title; + } + break; + case OBJECTTYPE_LEGEND: + nElementID = ChartThemeElementID::Legend; + break; + case OBJECTTYPE_AXIS: + { + nElementID = ChartThemeElementID::Axis; + size_t nLastSign = aCID.lastIndexOf(u":Axis="); + if (nLastSign != std::u16string_view::npos) + { + auto nIndex = aCID[nLastSign + 6]; + switch (nIndex) + { + case '1': + nElementID = ChartThemeElementID::AxisY; + break; + case '2': + nElementID = ChartThemeElementID::AxisZ; + break; + } + } + } + break; + case OBJECTTYPE_DATA_LABEL: + case OBJECTTYPE_DATA_LABELS: + case OBJECTTYPE_DATA_SERIES: + nElementID = ChartThemeElementID::Label; + break; + // Todo: we may want to save other element types too + default: + nElementID = ChartThemeElementID::Nothing; + break; + } + + if (nElementID != ChartThemeElementID::Nothing) + { + // Get the actual chart element properties. + // We want to save it, or overwrite it later. + std::vector<Reference<beans::XPropertySet>> xProperties; + xProperties.emplace(xProperties.end(), + ObjectIdentifier::getObjectPropertySet(aCID, xCModel)); + + if (aElementType == OBJECTTYPE_TITLE) + { + Reference<chart2::XTitle> xTitle(xProperties[0], uno::UNO_QUERY); + if (xTitle.is()) + { + const Sequence<Reference<chart2::XFormattedString>> aStrings(xTitle->getText()); + xProperties.pop_back(); + for (int i = 0; i < aStrings.getLength(); i++) + { + Reference<beans::XPropertySet> xTitlePropSet(aStrings[i], uno::UNO_QUERY); + xProperties.push_back(xTitlePropSet); + } + } + } + + // If we set the style of the chart .. we clear the customized data point properties + if (bSet && nElementID == ChartThemeElementID::Label) + { + rtl::Reference<DataSeries> xSeries( + ObjectIdentifier::getDataSeriesForCID(aCID, xCModel)); + if (xSeries.is()) + { + xSeries->resetAllDataPoints(); + } + } + + // Get the properties of the Theme + // we may want to set it to the chart, or overwrite it with the chart element properties. + std::vector<std::pair<OUString, uno::Any>>& aProperties + = aTheme.m_aElements[static_cast<int>(nElementID)].m_aProperties; + + for (size_t i = 0; i < xProperties.size(); i++) + { + if (bSet) + { + // set properties of chart from the stored in theme + for (auto& aProperty : aProperties) + { + try + { + if (aProperty.second != xProperties[i]->getPropertyValue(aProperty.first)) + { + xProperties[i]->setPropertyValue(aProperty.first, aProperty.second); + } + } + catch (const beans::UnknownPropertyException&) + { + TOOLS_WARN_EXCEPTION("chart2", "unknown Property: " << aProperty.first); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("chart2", ""); + } + } + } + else + { + // get style from chart and save to theme + // TODO: we should save not all the properties + Reference<beans::XPropertySetInfo> xInfo = xProperties[0]->getPropertySetInfo(); + if (xInfo.is()) + { + auto aPropSeq = xInfo->getProperties(); + uno::Any aValue; + if (aPropSeq.getLength() > 0) + { + // clear the theme properties, and reserve enought for the current style + // TODO: we may want to keep some that cannot be overwritten? + aProperties.clear(); + aProperties.reserve(aPropSeq.getLength()); + + std::set<OUString>& rPropSet = lcl_GetPropertyNameVec(); + for (auto& aProp : aPropSeq) + { + std::set<OUString>::const_iterator aIt(rPropSet.find(aProp.Name)); + if (aIt != rPropSet.end()) + { + aValue = xProperties[0]->getPropertyValue(aProp.Name); + if (aValue.hasValue()) + { + aProperties.push_back(std::make_pair(aProp.Name, aValue)); + } + } + } + } + } + // if there is more element in xProperties, then it is a Title with multiple styles .. + // we can save only 1 so we are finished here + break; + } + } + } + + auto aChild = aHierarchy.getChildren(aElement); + if (aChild.size() > 0) + { + for (auto& aCElement : aChild) + { + lcl_SetOrGetThemeToOrFromElement(bSet, aTheme, aHierarchy, aCElement, xCModel); + } + } +} + +void ChartController::setTheme(sal_uInt32 nIndex) +{ + if (nIndex >= ChartThemesType::getInstance().getThemesCount()) + return; + + ChartThemeType& aTheme = ChartThemesType::getInstance().m_aThemes[nIndex]; + + rtl::Reference<::chart::ChartModel> xCModel = getChartModel(); + + xCModel->lockControllers(); + ChartView* pExplicitValueProvider = nullptr; + ObjectHierarchy aHierarchy(xCModel, pExplicitValueProvider, true, true); + auto aTop = aHierarchy.getTopLevelChildren(); + for (auto& aElement : aTop) + { + lcl_SetOrGetThemeToOrFromElement(true, aTheme, aHierarchy, aElement, xCModel); + } + xCModel->unlockControllers(); +} + +void ChartController::saveTheme(sal_uInt32 nIndex) +{ + ChartThemesType& aChartTypes = ChartThemesType::getInstance(); + if (nIndex >= aChartTypes.getThemesCount()) + { + nIndex = aChartTypes.getThemesCount(); + // maximum 256 chart styles + // Todo: maybe we could figure out some other way to limit this. + if (nIndex >= 256) + return; + aChartTypes.m_aThemes.resize(nIndex + 1); + } + + ChartThemeType& aTheme = aChartTypes.m_aThemes[nIndex]; + + rtl::Reference<::chart::ChartModel> xCModel = getChartModel(); + + ChartView* pExplicitValueProvider = nullptr; + ObjectHierarchy aHierarchy(xCModel, pExplicitValueProvider, true, true); + auto aTop = aHierarchy.getTopLevelChildren(); + for (auto& aElement : aTop) + { + lcl_SetOrGetThemeToOrFromElement(false, aTheme, aHierarchy, aElement, xCModel); + } +} + +ChartThemesType* ChartThemesType::m_aInstance = nullptr; +ChartThemesType& ChartThemesType::getInstance() +{ + if (!m_aInstance) { + m_aInstance = new ChartThemesType; + } + return *m_aInstance; +} + +typedef std::pair<OUString, sal_uInt8> tPropertyNameWithMemberId; +typedef std::map<sal_uInt16, tPropertyNameWithMemberId> ItemPropertyMapType; + +static ItemPropertyMapType& lcl_GetPropertyMap() +{ + static ItemPropertyMapType aCharacterPropertyMap{ + { EE_CHAR_COLOR, { "CharColor", 0 } }, + { EE_CHAR_KERNING, { "CharKerning", 0 } }, + { EE_CHAR_SHADOW, { "CharShadowed", 0 } }, + { EE_CHAR_RELIEF, { "CharRelief", 0 } }, + { EE_CHAR_OUTLINE, { "CharContoured", 0 } }, + { EE_CHAR_EMPHASISMARK, { "CharEmphasis", 0 } }, + }; + return aCharacterPropertyMap; +} + +void ChartElementThemeType::convertPoolItemsToProperties(std::vector<SfxPoolItem*> aPoolItems) +{ + uno::Any aValue; + + for (SfxPoolItem* pPoolItem : aPoolItems) + { + sal_uInt16 nWhichId = pPoolItem->Which(); + + switch (nWhichId) + { + case EE_CHAR_FONTINFO: + case EE_CHAR_FONTINFO_CJK: + case EE_CHAR_FONTINFO_CTL: + { + OUString aPostfix; + if (nWhichId == EE_CHAR_FONTINFO_CJK) + aPostfix = "Asian"; + else if (nWhichId == EE_CHAR_FONTINFO_CTL) + aPostfix = "Complex"; + + const SvxFontItem& rItem = static_cast<const SvxFontItem&>(*pPoolItem); + + if (rItem.QueryValue(aValue, MID_FONT_FAMILY_NAME)) + { + m_aProperties.push_back( + std::pair<OUString, uno::Any>("CharFontName" + aPostfix, aValue)); + } + if (rItem.QueryValue(aValue, MID_FONT_FAMILY)) + { + m_aProperties.push_back( + std::pair<OUString, uno::Any>("CharFontFamily" + aPostfix, aValue)); + } + if (rItem.QueryValue(aValue, MID_FONT_STYLE_NAME)) + { + m_aProperties.push_back( + std::pair<OUString, uno::Any>("CharFontStyleName" + aPostfix, aValue)); + } + if (rItem.QueryValue(aValue, MID_FONT_CHAR_SET)) + { + m_aProperties.push_back( + std::pair<OUString, uno::Any>("CharFontCharSet" + aPostfix, aValue)); + } + if (rItem.QueryValue(aValue, MID_FONT_PITCH)) + { + m_aProperties.push_back( + std::pair<OUString, uno::Any>("CharFontPitch" + aPostfix, aValue)); + } + } + break; + + case EE_CHAR_UNDERLINE: + { + const SvxUnderlineItem& rItem = static_cast<const SvxUnderlineItem&>(*pPoolItem); + + if (rItem.QueryValue(aValue, MID_TL_STYLE)) + { + m_aProperties.push_back(std::make_pair(u"CharUnderline"_ustr, aValue)); + } + + if (rItem.QueryValue(aValue, MID_TL_COLOR)) + { + m_aProperties.push_back(std::make_pair(u"CharUnderlineColor"_ustr, aValue)); + } + + if (rItem.QueryValue(aValue, MID_TL_HASCOLOR)) + { + m_aProperties.push_back(std::make_pair(u"CharUnderlineHasColor"_ustr, aValue)); + } + } + break; + + case EE_CHAR_STRIKEOUT: + { + const SvxCrossedOutItem& rItem = static_cast<const SvxCrossedOutItem&>(*pPoolItem); + + if (rItem.QueryValue(aValue, MID_TL_STYLE)) + { + m_aProperties.push_back(std::make_pair(u"CharStrikeout"_ustr, aValue)); + } + } + break; + + case EE_CHAR_OVERLINE: + { + const SvxOverlineItem& rItem = static_cast<const SvxOverlineItem&>(*pPoolItem); + + if (rItem.QueryValue(aValue, MID_TL_STYLE)) + { + m_aProperties.push_back(std::make_pair(u"CharOverline"_ustr, aValue)); + } + + if (rItem.QueryValue(aValue, MID_TL_COLOR)) + { + m_aProperties.push_back(std::make_pair(u"CharOverlineColor"_ustr, aValue)); + } + + if (rItem.QueryValue(aValue, MID_TL_HASCOLOR)) + { + m_aProperties.push_back(std::make_pair(u"CharOverlineHasColor"_ustr, aValue)); + } + } + break; + + case EE_CHAR_ITALIC: + case EE_CHAR_ITALIC_CJK: + case EE_CHAR_ITALIC_CTL: + { + OUString aPostfix; + if (nWhichId == EE_CHAR_ITALIC_CJK) + aPostfix = "Asian"; + else if (nWhichId == EE_CHAR_ITALIC_CTL) + aPostfix = "Complex"; + + const SvxPostureItem& rItem = static_cast<const SvxPostureItem&>(*pPoolItem); + + if (rItem.QueryValue(aValue, MID_POSTURE)) + { + m_aProperties.push_back( + std::pair<OUString, uno::Any>("CharPosture" + aPostfix, aValue)); + } + } + break; + + case EE_CHAR_WEIGHT: + case EE_CHAR_WEIGHT_CJK: + case EE_CHAR_WEIGHT_CTL: + { + OUString aPostfix; + if (nWhichId == EE_CHAR_WEIGHT_CJK) + aPostfix = "Asian"; + else if (nWhichId == EE_CHAR_WEIGHT_CTL) + aPostfix = "Complex"; + + const SvxWeightItem& rItem = static_cast<const SvxWeightItem&>(*pPoolItem); + + if (rItem.QueryValue(aValue, MID_WEIGHT)) + { + m_aProperties.push_back( + std::pair<OUString, uno::Any>("CharWeight" + aPostfix, aValue)); + } + } + break; + + case EE_CHAR_FONTHEIGHT: + case EE_CHAR_FONTHEIGHT_CJK: + case EE_CHAR_FONTHEIGHT_CTL: + { + OUString aPostfix; + if (nWhichId == EE_CHAR_FONTHEIGHT_CJK) + aPostfix = "Asian"; + else if (nWhichId == EE_CHAR_FONTHEIGHT_CTL) + aPostfix = "Complex"; + + const SvxFontHeightItem& rItem = static_cast<const SvxFontHeightItem&>(*pPoolItem); + + if (rItem.QueryValue(aValue, MID_FONTHEIGHT)) + { + m_aProperties.push_back( + std::pair<OUString, uno::Any>("CharHeight" + aPostfix, aValue)); + } + } + break; + default: + { + ItemPropertyMapType& rMap(lcl_GetPropertyMap()); + ItemPropertyMapType::const_iterator aIt(rMap.find(nWhichId)); + + if (aIt != rMap.end()) + { + tPropertyNameWithMemberId aProperty = (*aIt).second; + + pPoolItem->QueryValue(aValue, aProperty.second /* nMemberId */); + m_aProperties.push_back(std::make_pair(aProperty.first, aValue)); + } + } + break; + } + } +} + +ChartThemesType::ChartThemesType() +{ + // TODO: find a better place/way to store the pre-defined chart themes + // Now it is just some placeholder test cases. + // by default there is now 4 pre-defined chart style + // and 7 chart elements + size_t ElementCount = ChartThemeType::ElementCount; + m_aThemes.resize(FixedCount); + for (size_t i = 0; i < FixedCount * ElementCount; i++) + { + std::vector<SfxPoolItem*> aSet; + + ChartThemeElementID nElementType = ChartThemeElementID(i % ElementCount); + + sal_uInt32 nHeight = 240 + i * 10; + sal_uInt32 nColor = 170 << (i % 15); + const Color aColor(ColorAlpha, nColor); + short nKerning = 0; + FontWeight nBold = WEIGHT_NORMAL; + FontItalic nItalic = ITALIC_NONE; + + if (nElementType == ChartThemeElementID::Title) + { + nHeight *= 2; + nKerning = 100 * (i / ElementCount + 1); + nBold = WEIGHT_BOLD; + } + if (nElementType == ChartThemeElementID::SubTitle) + { + nHeight = nHeight * 3 / 2; + nKerning = -50 * (i / ElementCount + 1); + nItalic = ITALIC_NORMAL; + } + + aSet.push_back(new SvxFontItem(FAMILY_DONTKNOW, u"Kristen ITC"_ustr, u""_ustr, + PITCH_VARIABLE, + RTL_TEXTENCODING_DONTKNOW, EE_CHAR_FONTINFO)); + aSet.push_back(new SvxFontItem(FAMILY_DONTKNOW, u"Mistral"_ustr, u""_ustr, PITCH_VARIABLE, + RTL_TEXTENCODING_DONTKNOW, EE_CHAR_FONTINFO_CJK)); + aSet.push_back(new SvxFontItem(FAMILY_DONTKNOW, u"Maiandra GD"_ustr, u""_ustr, + PITCH_VARIABLE, + RTL_TEXTENCODING_DONTKNOW, EE_CHAR_FONTINFO_CTL)); + + aSet.push_back(new SvxFontHeightItem(nHeight, 100, EE_CHAR_FONTHEIGHT)); + aSet.push_back(new SvxFontHeightItem(nHeight + 1, 100, EE_CHAR_FONTHEIGHT_CJK)); + aSet.push_back(new SvxFontHeightItem(nHeight + 2, 100, EE_CHAR_FONTHEIGHT_CTL)); + + aSet.push_back(new SvxWeightItem(nBold, EE_CHAR_WEIGHT)); + aSet.push_back(new SvxWeightItem(nBold, EE_CHAR_WEIGHT_CJK)); + aSet.push_back(new SvxWeightItem(nBold, EE_CHAR_WEIGHT_CTL)); + + aSet.push_back(new SvxPostureItem(nItalic, EE_CHAR_ITALIC)); + aSet.push_back(new SvxPostureItem(nItalic, EE_CHAR_ITALIC_CJK)); + aSet.push_back(new SvxPostureItem(nItalic, EE_CHAR_ITALIC_CTL)); + + aSet.push_back(new SvxUnderlineItem(LINESTYLE_LONGDASH, EE_CHAR_UNDERLINE)); + + aSet.push_back(new SvxCrossedOutItem(STRIKEOUT_BOLD, EE_CHAR_STRIKEOUT)); + aSet.push_back(new SvxColorItem(aColor, EE_CHAR_COLOR)); + aSet.push_back(new SvxShadowedItem(true, EE_CHAR_SHADOW)); + if (nKerning != 0) + { + aSet.push_back(new SvxKerningItem(nKerning, EE_CHAR_KERNING)); + } + //convert them to properties + m_aThemes[i / ElementCount].m_aElements[i % ElementCount].convertPoolItemsToProperties( + aSet); + } +} + } //namespace chart /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/controller/main/ChartController_Insert.cxx b/chart2/source/controller/main/ChartController_Insert.cxx index bb882d8e733c..aeff083b0253 100644 --- a/chart2/source/controller/main/ChartController_Insert.cxx +++ b/chart2/source/controller/main/ChartController_Insert.cxx @@ -274,6 +274,7 @@ void ChartController::executeDispatch_DeleteDataTable() void ChartController::executeDispatch_InsertTitles() { + SolarMutexGuard aSolarGuard; auto xUndoGuard = std::make_shared<UndoGuard>( ActionDescriptionProvider::createDescription( ActionDescriptionProvider::ActionType::Insert, SchResId( STR_OBJECT_TITLES )), diff --git a/chart2/source/controller/main/ChartController_Properties.cxx b/chart2/source/controller/main/ChartController_Properties.cxx index d1c635b3c747..c62a0ebb2342 100644 --- a/chart2/source/controller/main/ChartController_Properties.cxx +++ b/chart2/source/controller/main/ChartController_Properties.cxx @@ -684,6 +684,7 @@ OUString lcl_getFormatCIDforSelectedCID( const OUString& rSelectedCID ) void ChartController::executeDlg_ObjectProperties( const OUString& rSelectedObjectCID ) { + SolarMutexGuard aSolarGuard; OUString aObjectCID = lcl_getFormatCIDforSelectedCID( rSelectedObjectCID ); auto xUndoGuard = std::make_shared<UndoGuard>( diff --git a/chart2/source/controller/main/ChartController_Window.cxx b/chart2/source/controller/main/ChartController_Window.cxx index ba982b3de35f..675b45ef6ab3 100644 --- a/chart2/source/controller/main/ChartController_Window.cxx +++ b/chart2/source/controller/main/ChartController_Window.cxx @@ -88,6 +88,7 @@ #include <boost/property_tree/json_parser.hpp> #include <sfx2/dispatch.hxx> #include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <officecfg/Office/Common.hxx> #define DRGPIX 2 // Drag MinMove in Pixel @@ -1215,6 +1216,8 @@ void ChartController::execute_Command( const CommandEvent& rCEvt ) lcl_insertMenuCommand( xPopupMenu, nUniqueId++, u".uno:DataRanges"_ustr ); lcl_insertMenuCommand( xPopupMenu, nUniqueId++, u".uno:DiagramData"_ustr ); lcl_insertMenuCommand( xPopupMenu, nUniqueId++, u".uno:View3D"_ustr ); + if (officecfg::Office::Common::Misc::ExperimentalMode::get()) + lcl_insertMenuCommand( xPopupMenu, nUniqueId++, u".uno:ManageThemes"_ustr); } css::uno::Sequence< css::uno::Any > aArgs{ diff --git a/chart2/source/controller/main/ControllerCommandDispatch.cxx b/chart2/source/controller/main/ControllerCommandDispatch.cxx index af015e46bfbd..e64151fbedae 100644 --- a/chart2/source/controller/main/ControllerCommandDispatch.cxx +++ b/chart2/source/controller/main/ControllerCommandDispatch.cxx @@ -592,6 +592,8 @@ void ControllerCommandDispatch::updateCommandAvailability() m_aCommandAvailability[ u".uno:ArrangeRow"_ustr ] = bShapeContext || ( bIsWritable && bControllerStateIsValid && ( m_apControllerState->bMayMoveSeriesForward || m_apControllerState->bMayMoveSeriesBackward ) ); + m_aCommandAvailability[ u".uno:ManageThemes"_ustr] = true; + // insert objects m_aCommandAvailability[ u".uno:InsertTitles"_ustr ] = m_aCommandAvailability[ u".uno:InsertMenuTitles"_ustr ] = bIsWritable; m_aCommandAvailability[ u".uno:InsertLegend"_ustr ] = m_aCommandAvailability[ u".uno:InsertMenuLegend"_ustr ] = bIsWritable; diff --git a/chart2/source/controller/sidebar/Chart2PanelFactory.cxx b/chart2/source/controller/sidebar/Chart2PanelFactory.cxx index 0ce778ec8460..a750679a9d7a 100644 --- a/chart2/source/controller/sidebar/Chart2PanelFactory.cxx +++ b/chart2/source/controller/sidebar/Chart2PanelFactory.cxx @@ -36,6 +36,8 @@ #include "ChartLinePanel.hxx" #include "ChartColorsPanel.hxx" #include "ChartEffectPanel.hxx" +#include "ChartThemePanel.hxx" +#include <officecfg/Office/Common.hxx> using namespace css::uno; @@ -104,6 +106,9 @@ Reference<css::ui::XUIElement> SAL_CALL ChartPanelFactory::createUIElement ( xPanel = ChartColorsPanel::Create(pParent, xFrame, pController); else if (rsResourceURL.endsWith("/EffectPropertyPanel")) xPanel = ChartEffectPanel::Create(pParent, pController); + else if (rsResourceURL.endsWith("/ThemePanel") + && officecfg::Office::Common::Misc::ExperimentalMode::get()) + xPanel = ChartThemePanel::Create(pParent, xFrame, pController); if (xPanel) xElement = sfx2::sidebar::SidebarPanelBase::Create( diff --git a/chart2/source/controller/sidebar/ChartThemeControl.cxx b/chart2/source/controller/sidebar/ChartThemeControl.cxx new file mode 100644 index 000000000000..083ad93058a3 --- /dev/null +++ b/chart2/source/controller/sidebar/ChartThemeControl.cxx @@ -0,0 +1,223 @@ +/* -*- 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 <com/sun/star/drawing/FillStyle.hpp> + +#include <memory> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/virdev.hxx> + +#include "ChartThemeControl.hxx" +#include <svx/ChartThemeType.hxx> + +#include <vcl/event.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/graph.hxx> + +using namespace css; + +namespace chart::sidebar +{ +ChartThemeControl::ChartThemeControl(const uno::Reference<uno::XComponentContext>& rContext) + : PopupWindowController(rContext, nullptr, OUString()) +{ +} + +ChartThemeControl::~ChartThemeControl() = default; + +void ChartThemeControl::initialize(const uno::Sequence<uno::Any>& rArguments) +{ + svt::PopupWindowController::initialize(rArguments); + + if (m_pToolbar) + { + mxPopoverContainer = std::make_unique<ToolbarPopupContainer>(m_pToolbar); + m_pToolbar->set_item_popover(m_aCommandURL, mxPopoverContainer->getTopLevel()); + m_pToolbar->set_item_sensitive(m_aCommandURL, true); + } + + ToolBox* pToolBox = nullptr; + ToolBoxItemId nId; + if (getToolboxId(nId, &pToolBox)) + { + pToolBox->SetItemBits(nId, pToolBox->GetItemBits(nId) | ToolBoxItemBits::DROPDOWNONLY); + pToolBox->EnableItem(nId, true); + } +} + +void ChartThemeControl::execute(sal_Int16) +{ + if (m_pToolbar) + { + // Toggle the popup also when toolbutton is activated + m_pToolbar->set_menu_item_active(m_aCommandURL, + !m_pToolbar->get_menu_item_active(m_aCommandURL)); + } + else + { + // Open the popup also when Enter key is pressed. + createPopupWindow(); + } +} + +void ChartThemeControl::statusChanged(const frame::FeatureStateEvent& rEvent) +{ + ToolBox* pToolBox = nullptr; + ToolBoxItemId nId; + if (!getToolboxId(nId, &pToolBox) && !m_pToolbar) + return; + + if (rEvent.FeatureURL.Complete == m_aCommandURL) + { + updateStatus(); + } +} + +void ChartThemeControl::updateStatus(bool bForce) +{ + if (!mpHandler) + return; + // Todo: render something to the dropdown image + if (bForce) // || changed ?? + { + /* + ToolBox* pToolBox = nullptr; + ToolBoxItemId nId; + if (!getToolboxId(nId, &pToolBox) && !m_pToolbar) + return; + + auto pDev = VclPtr<VirtualDevice>::Create(); + // render the actual chart into pDev + auto aSelItemImg(pDev->GetBitmapEx(Point(), pDev->GetOutputSizePixel())); + if (m_pToolbar) + { + m_pToolbar->set_item_image(m_aCommandURL, Graphic(aSelItemImg).GetXGraphic()); + } + else + { + pToolBox->SetItemImage(nId, Image(aSelItemImg)); + } + */ + } +} +void ChartThemeControl::setThemeHandler(std::shared_ptr<IThemeHandler> rThemeHandler) +{ + if (!mpHandler) + { + mpHandler = rThemeHandler; + updateStatus(true); + } +} + +std::unique_ptr<WeldToolbarPopup> ChartThemeControl::weldPopupWindow() +{ + return std::make_unique<ChartThemePopup>(this, m_pToolbar); +} + +VclPtr<vcl::Window> ChartThemeControl::createVclPopupWindow(vcl::Window* pParent) +{ + mxInterimPopover = VclPtr<InterimToolbarPopup>::Create( + getFrameInterface(), pParent, + std::make_unique<ChartThemePopup>(this, pParent->GetFrameWeld())); + + mxInterimPopover->Show(); + + return mxInterimPopover; +} + +OUString ChartThemeControl::getImplementationName() +{ + return "com.sun.star.comp.chart2.ChartThemeControl"; +} + +uno::Sequence<OUString> ChartThemeControl::getSupportedServiceNames() +{ + return { "com.sun.star.frame.ToolbarController" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +com_sun_star_comp_chart2_ChartThemeControl_get_implementation(uno::XComponentContext* rContext, + uno::Sequence<uno::Any> const&) +{ + return cppu::acquire(new ChartThemeControl(rContext)); +} + +ChartThemePopup::ChartThemePopup(ChartThemeControl* pControl, weld::Widget* pParent) + : WeldToolbarPopup(pControl->getFrameInterface(), pParent, + "modules/schart/ui/chartthemepopup.ui", "ThemeWindow") + , mxControl(pControl) + , mxThemesIconView(m_xBuilder->weld_icon_view(u"themeview"_ustr)) +{ + mxThemesIconView->connect_item_activated(LINK(this, ChartThemePopup, ThemeSelectedHdl)); + mxThemesIconView->set_item_width(ChartThemeThumbSizeX); + + int nThemeCount = ChartThemesType::getInstance().getThemesCount(); + for (int i = 0; i < nThemeCount; i++) + { + ScopedVclPtr<VirtualDevice> device1 = makeImage(i); + Bitmap aBmp = device1->GetBitmap(Point(), device1->GetOutputSizePixel()); + OUString sId = OUString::number(i); + OUString sLayoutName = OUString::number(i); + mxThemesIconView->insert(i, &sLayoutName, &sId, &aBmp, nullptr); + + device1.disposeAndClear(); + } +} + +ChartThemePopup::~ChartThemePopup() { mxThemesIconView.reset(); } + +VclPtr<VirtualDevice> ChartThemePopup::makeImage(int nIndex) +{ + ScopedVclPtr<VirtualDevice> device1 + = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA); + device1->SetOutputSizePixel(Size(ChartThemeThumbSizeX, ChartThemeThumbSizeY)); + + VclPtr<VirtualDevice> pDefaultOut = mxControl->mpHandler->makePictureFromThemedChart(nIndex); + + if (pDefaultOut) + { + Bitmap aBmp = pDefaultOut->GetBitmap(Point(), pDefaultOut->GetOutputSizePixel()); + aBmp.Scale(Size(ChartThemeThumbSizeX, ChartThemeThumbSizeY), BmpScaleFlag::Fast); + + device1->DrawBitmap(Point(0, 0), aBmp); + pDefaultOut.disposeAndClear(); + } + return std::move(device1); +} + +void ChartThemePopup::GrabFocus() {} + +IMPL_LINK_NOARG(ChartThemePopup, ThemeSelectedHdl, weld::IconView&, bool) +{ + OUString sId = mxThemesIconView->get_selected_id(); + + if (sId.isEmpty()) + return false; + int nIndex = sId.toInt32(); + + mxControl->mpHandler->select(nIndex); + + return true; +} + +} // end namespace chart::sidebar + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/controller/sidebar/ChartThemeControl.hxx b/chart2/source/controller/sidebar/ChartThemeControl.hxx new file mode 100644 index 000000000000..d380416220c8 --- /dev/null +++ b/chart2/source/controller/sidebar/ChartThemeControl.hxx @@ -0,0 +1,77 @@ +/* -*- 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 <svtools/popupwindowcontroller.hxx> +#include <svtools/toolbarmenu.hxx> + +namespace chart +{ +class ChartColorPaletteHelper; + +namespace sidebar +{ +struct IThemeHandler +{ + virtual ~IThemeHandler() = default; + virtual void select(sal_uInt32 nIndex) = 0; + [[nodiscard]] virtual VclPtr<VirtualDevice> makePictureFromThemedChart(sal_uInt32 nIndex) = 0; +}; + +class ChartThemeControl final : public svt::PopupWindowController +{ +public: + std::shared_ptr<IThemeHandler> mpHandler; + +public: + explicit ChartThemeControl(const css::uno::Reference<css::uno::XComponentContext>& rContext); + + // XInitialization + void SAL_CALL initialize(const css::uno::Sequence<css::uno::Any>& rArguments) override; + + // XServiceInfo + OUString SAL_CALL getImplementationName() override; + css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + void SAL_CALL execute(sal_Int16 nKeyModifier) override; + void SAL_CALL statusChanged(const css::frame::FeatureStateEvent& rEvent) override; + + ~ChartThemeControl() override; + + void setThemeHandler(std::shared_ptr<IThemeHandler> rThemeHandler); + + void updateStatus(bool bForce = false); + +private: + std::unique_ptr<WeldToolbarPopup> weldPopupWindow() override; + VclPtr<vcl::Window> createVclPopupWindow(vcl::Window* pParent) override; +}; + +class ChartThemePopup final : public WeldToolbarPopup +{ + rtl::Reference<ChartThemeControl> mxControl; + std::unique_ptr<weld::IconView> mxThemesIconView; + + DECL_LINK(ThemeSelectedHdl, weld::IconView&, bool); + + void GrabFocus() override; + + VclPtr<VirtualDevice> makeImage(int nIndex); + +public: + ChartThemePopup(ChartThemeControl* pControl, weld::Widget* pParent); + ~ChartThemePopup() override; +}; + +} // end namespace sidebar +} // end namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/controller/sidebar/ChartThemePanel.cxx b/chart2/source/controller/sidebar/ChartThemePanel.cxx new file mode 100644 index 000000000000..887ce76182e8 --- /dev/null +++ b/chart2/source/controller/sidebar/ChartThemePanel.cxx @@ -0,0 +1,246 @@ +/* -*- 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 <sal/log.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> + +#include <svx/ChartThemeType.hxx> +#include "ChartThemePanel.hxx" +#include "ChartThemeControl.hxx" + +#include <ChartController.hxx> +#include <ChartModel.hxx> + +#include <sfx2/weldutils.hxx> +#include <svtools/toolbarmenu.hxx> + +#include <algorithm> + +#include <comphelper/processfactory.hxx> +#include <unotools/fcm.hxx> + +using namespace css; + +namespace chart::sidebar +{ +namespace +{ +ChartThemeControl* getChartThemeControl(const ToolbarUnoDispatcher& rToolBoxColor) +{ + const uno::Reference<frame::XToolbarController> xController + = rToolBoxColor.GetControllerForCommand(sUnoChartTheme); + const auto pToolBoxLineStyleControl = dynamic_cast<ChartThemeControl*>(xController.get()); + return pToolBoxLineStyleControl; +} +} // end unnamed namespace + +class ThemeWrapper final : public IThemeHandler +{ +public: + ThemeWrapper(rtl::Reference<ChartModel> mxModel, ChartThemeControl* pControl, + ChartController* pController); + + void updateModel(const rtl::Reference<ChartModel>& xModel); + void updateData() const; + + void select(sal_uInt32 nIndex) override; + [[nodiscard]] VclPtr<VirtualDevice> makePictureFromThemedChart(sal_uInt32 nIndex) override; + +private: + rtl::Reference<ChartModel> mxModel; + ChartController* mpController = nullptr; + ChartThemeControl* mpControl = nullptr; +}; + +ThemeWrapper::ThemeWrapper(rtl::Reference<ChartModel> xModel, ChartThemeControl* pControl, + ChartController* pController) + : mxModel(std::move(xModel)) + , mpController(pController) + , mpControl(pControl) +{ +} + +void ThemeWrapper::updateModel(const rtl::Reference<ChartModel>& xModel) { mxModel = xModel; } + +void ThemeWrapper::updateData() const +{ + util::URL aUrl; + aUrl.Complete = sUnoChartTheme; + + frame::FeatureStateEvent aEvent; + aEvent.FeatureURL = aUrl; + aEvent.IsEnabled = true; + + if (mpControl) + mpControl->statusChanged(aEvent); +} + +void ThemeWrapper::select(const sal_uInt32 nIndex) { mpController->setTheme(nIndex); } + +VclPtr<VirtualDevice> ThemeWrapper::makePictureFromThemedChart(sal_uInt32 nIndex) +{ + if (mpController) + { + // Make a copy of the chart to take screenshot with different style + rtl::Reference<ChartModel> mxModel2 = new ChartModel(*mxModel); + auto xComponent = comphelper::getProcessComponentContext(); + rtl::Reference<ChartController> pController2 = new ChartController(xComponent); + utl::ConnectFrameControllerModel(nullptr, pController2, mxModel2); + // Change the cloned chart style + pController2->setTheme(nIndex); + + //make a picture from it + ScopedVclPtr<VirtualDevice> device1 + = VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA); + + awt::Size aSize = mxModel->getVisualAreaSize(1); + + // TODO: find a better way to render thumbnail from a chart + //use scaleText property to scale + if (ReferenceSizeProvider::getAutoResizeState(mxModel2) + != ReferenceSizeProvider::AUTO_RESIZE_YES) + { + awt::Size aPageSize(mxModel2->getPageSize()); + ReferenceSizeProvider aRSP(aPageSize, mxModel2); + aRSP.toggleAutoResizeState(); + } + + int scale = 4; + mxModel2->setVisualAreaSize(1, awt::Size(aSize.Width / scale, aSize.Height / scale)); + device1->SetOutputSize(Size(aSize.Width / scale, aSize.Height / scale)); + + pController2->PrePaint(); + bChartThumbnailRendered = true; + pController2->execute_Paint( + *device1, tools::Rectangle(0, 0, aSize.Width / scale, aSize.Height / scale)); + bChartThumbnailRendered = false; + + return std::move(device1); + } + return nullptr; +} + +ChartThemePanel::ChartThemePanel(weld::Widget* pParent, + const uno::Reference<frame::XFrame>& rxFrame, + ChartController* pController) + : PanelLayout(pParent, "ChartThemePanel", "modules/schart/ui/sidebartheme.ui") + , mxModel(pController->getChartModel()) + , mpController(pController) + , mxModifyListener(new ChartSidebarModifyListener(this)) + , mbModelValid(true) + , mxThemeTB(m_xBuilder->weld_toolbar("themetype")) + , mxThemeDispatch(new ToolbarUnoDispatcher(*mxThemeTB, *m_xBuilder, rxFrame)) + , mxSaveToNewTheme(m_xBuilder->weld_button(u"savetonew"_ustr)) +{ + Initialize(); +} + +ChartThemePanel::~ChartThemePanel() +{ + doUpdateModel(nullptr); + mxThemeDispatch.reset(); + mxThemeTB.reset(); + mxSaveToNewTheme.reset(); +} + +void ChartThemePanel::Initialize() +{ + mxModel->addModifyListener(mxModifyListener); + + ChartThemeControl* pThemeControl = getChartThemeControl(*mxThemeDispatch); + assert(pThemeControl); + mxThemeWrapper = std::make_shared<ThemeWrapper>(mxModel, pThemeControl, mpController); + pThemeControl->setThemeHandler(mxThemeWrapper); + + mxSaveToNewTheme->connect_clicked(LINK(this, ChartThemePanel, ClickSaveToNewHdl)); + mxSaveToNewTheme->set_visible(true); + + updateData(); +} + +void ChartThemePanel::updateData() +{ + if (!mbModelValid) + return; + mxThemeWrapper->updateData(); +} + +std::unique_ptr<PanelLayout> ChartThemePanel::Create(weld::Widget* pParent, + const uno::Reference<frame::XFrame>& rxFrame, + ChartController* pController) +{ + if (pParent == nullptr) + throw lang::IllegalArgumentException("no parent Window given to ChartThemePanel::Create", + nullptr, 0); + if (!rxFrame.is()) + throw lang::IllegalArgumentException("no XFrame given to ChartThemePanel::Create", nullptr, + 1); + + return std::make_unique<ChartThemePanel>(pParent, rxFrame, pController); +} + +void ChartThemePanel::DataChanged(const DataChangedEvent& rEvent) +{ + PanelLayout::DataChanged(rEvent); + updateData(); +} + +void ChartThemePanel::HandleContextChange(const vcl::EnumContext&) { updateData(); } + +void ChartThemePanel::NotifyItemUpdate(sal_uInt16 /*nSID*/, SfxItemState /*eState*/, + const SfxPoolItem* /*pState*/) +{ +} + +void ChartThemePanel::modelInvalid() { mbModelValid = false; } + +void ChartThemePanel::doUpdateModel(const rtl::Reference<ChartModel>& xModel) +{ + if (mbModelValid) + { + mxModel->removeModifyListener(mxModifyListener); + } + + mxModel = xModel; + mbModelValid = mxModel.is(); + + if (!mbModelValid) + return; + + mxThemeWrapper->updateModel(mxModel); + + mxModel->addModifyListener(mxModifyListener); +} + +void ChartThemePanel::updateModel(const uno::Reference<frame::XModel> xModel) +{ + const auto pModel = dynamic_cast<ChartModel*>(xModel.get()); + assert(!xModel || pModel); + doUpdateModel(pModel); +} + +IMPL_LINK_NOARG(ChartThemePanel, ClickSaveToNewHdl, weld::Button&, void) +{ + mpController->saveTheme(10000000); +} + +} // end of namespace ::chart::sidebar + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/controller/sidebar/ChartThemePanel.hxx b/chart2/source/controller/sidebar/ChartThemePanel.hxx new file mode 100644 index 000000000000..73453cfa2ff1 --- /dev/null +++ b/chart2/source/controller/sidebar/ChartThemePanel.hxx @@ -0,0 +1,85 @@ +/* -*- 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 <com/sun/star/frame/XFrame.hpp> + +#include <sfx2/sidebar/ControllerItem.hxx> +#include <sfx2/sidebar/IContextChangeReceiver.hxx> +#include <sfx2/sidebar/SidebarModelUpdate.hxx> +#include <sfx2/sidebar/PanelLayout.hxx> +#include "ChartSidebarModifyListener.hxx" +#include <ChartModel.hxx> + +class ToolbarUnoDispatcher; + +namespace chart +{ +class ChartController; + +namespace sidebar +{ +class ThemeWrapper; + +class ChartThemePanel final : public PanelLayout, + public sfx2::sidebar::IContextChangeReceiver, + public sfx2::sidebar::ControllerItem::ItemUpdateReceiverInterface, + public sfx2::sidebar::SidebarModelUpdate, + public ChartSidebarModifyListenerParent +{ +public: + static std::unique_ptr<PanelLayout> + Create(weld::Widget* pParent, const css::uno::Reference<css::frame::XFrame>& rxFrame, + ChartController* pController); + + void DataChanged(const DataChangedEvent& rEvent) override; + + void HandleContextChange(const vcl::EnumContext& rContext) override; + + void NotifyItemUpdate(sal_uInt16 nSId, SfxItemState eState, const SfxPoolItem* pState) override; + + void GetControlState(const sal_uInt16 /*nSId*/, + boost::property_tree::ptree& /*rState*/) override + { + } + + // constructor/destructor + ChartThemePanel(weld::Widget* pParent, const css::uno::Reference<css::frame::XFrame>& rxFrame, + ChartController* pController); + ~ChartThemePanel() override; + + void updateData() override; + void modelInvalid() override; + + void updateModel(css::uno::Reference<css::frame::XModel> xModel) override; + +private: + rtl::Reference<ChartModel> mxModel; + ChartController* mpController; + + css::uno::Reference<css::util::XModifyListener> mxModifyListener; + + bool mbModelValid; + + std::unique_ptr<weld::Toolbar> mxThemeTB; + std::unique_ptr<ToolbarUnoDispatcher> mxThemeDispatch; + std::shared_ptr<ThemeWrapper> mxThemeWrapper; + std::unique_ptr<weld::Button> mxSaveToNewTheme; + + DECL_LINK(ClickSaveToNewHdl, weld::Button&, void); + + void Initialize(); + void doUpdateModel(const rtl::Reference<ChartModel>& xModel); +}; +} // end of namespace sidebar +} // end of namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/model/main/ChartModel.cxx b/chart2/source/model/main/ChartModel.cxx index 409132b48e5c..8960189b3d2b 100644 --- a/chart2/source/model/main/ChartModel.cxx +++ b/chart2/source/model/main/ChartModel.cxx @@ -79,6 +79,7 @@ #include <docmodel/theme/Theme.hxx> #include <docmodel/uno/UnoChartStyle.hxx> #include <docmodel/uno/UnoChartColorStyle.hxx> +#include <svx/ChartThemeType.hxx> using ::com::sun::star::uno::Sequence; using ::com::sun::star::uno::Reference; diff --git a/chart2/uiconfig/ui/chartthemepopup.ui b/chart2/uiconfig/ui/chartthemepopup.ui new file mode 100644 index 000000000000..ad04c97cd533 --- /dev/null +++ b/chart2/uiconfig/ui/chartthemepopup.ui @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.40.0 --> +<interface domain="chart"> + <requires lib="gtk+" version="3.24"/> + <object class="GtkPopover" id="ThemeWindow"> + <property name="can-focus">False</property> + <child> + <object class="GtkBox" id="container"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkScrolledWindow"> + <property name="width-request">213</property> + <property name="height-request">360</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="hscrollbar-policy">never</property> + <property name="vscrollbar-policy">always</property> + <property name="shadow-type">in</property> + <property name="min-content-width">192</property> + <child> + <object class="GtkIconView" id="themeview"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="margin">0</property> + <property name="model">liststore1</property> + <property name="columns">1</property> + <property name="item-width">192</property> + </object> + </child> + </object> + </child> + </object> + </child> + </object> +</interface> diff --git a/chart2/uiconfig/ui/dlg_Theme.ui b/chart2/uiconfig/ui/dlg_Theme.ui new file mode 100644 index 000000000000..a1f5ff43c649 --- /dev/null +++ b/chart2/uiconfig/ui/dlg_Theme.ui @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.40.0 --> +<interface domain="chart"> + <requires lib="gtk+" version="3.24"/> + <object class="GtkDialog" id="ChartThemeDialog"> + <property name="can-focus">False</property> + <property name="border-width">6</property> + <property name="title" translatable="yes" context="dlg_Theme|ChartThemeDialog">Manage Chart Themes</property> + <property name="modal">True</property> + <property name="default-width">0</property> + <property name="default-height">0</property> + <property name="type-hint">dialog</property> + <child internal-child="vbox"> + <object class="GtkBox" id="messagedialog-vbox"> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child internal-child="action_area"> + <object class="GtkButtonBox"> + <property name="can-focus">False</property> + <property name="layout-style">expand</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="iconviewlabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes" context="dlg_Theme|iconviewlabel|tooltip_text">Selectable chart themes list.</property> + <property name="halign">start</property> + <property name="valign">start</property> + <property name="hexpand">False</property> + <property name="vexpand">False</property> + <property name="label" translatable="yes" context="dlg_Theme|iconviewlabel">_Chart themes:</property> + <property name="use-underline">True</property> + <property name="xalign">0</property> + <accessibility> + <relation type="label-for" target="themeview"/> + </accessibility> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <!-- n-columns=2 n-rows=1 --> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="border-width">6</property> + <property name="row-spacing">3</property> + <property name="column-spacing">6</property> + <child> + <object class="GtkScrolledWindow"> + <property name="width-request">420</property> + <property name="height-request">360</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="hscrollbar-policy">never</property> + <property name="vscrollbar-policy">always</property> + <property name="shadow-type">in</property> + <property name="min-content-width">200</property> + <child> + <object class="GtkIconView" id="themeview"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="margin">0</property> + <property name="model">liststore1</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="columns">1</property> + <property name="item-width">200</property> + <accessibility> + <relation type="labelled-by" target="iconviewlabel"/> + </accessibility> + </object> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <!-- n-columns=1 n-rows=4 --> + <object class="GtkGrid" id="grid2"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">end</property> + <property name="valign">start</property> + <property name="hexpand">False</property> + <property name="vexpand">False</property> + <property name="border-width">6</property> + <property name="row-spacing">3</property> + <property name="column-spacing">6</property> + <child> + <object class="GtkButton" id="load"> + <property name="label" translatable="yes" context="dlg_Theme|themeload">_Load theme to chart</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="save"> + <property name="label" translatable="yes" context="dlg_Theme|themesave">_Save to theme</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="delete"> + <property name="label" translatable="yes" context="dlg_Theme|themedelete">_Delete theme</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="savetonew"> + <property name="label" translatable="yes" context="dlg_Theme|themesavenew">_Save to new theme</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">3</property> + </packing> + </child> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">-1</property> + </packing> + </child> + </object> + </child> + </object> +</interface> diff --git a/chart2/uiconfig/ui/sidebartheme.ui b/chart2/uiconfig/ui/sidebartheme.ui new file mode 100644 index 000000000000..1163651e20dc --- /dev/null +++ b/chart2/uiconfig/ui/sidebartheme.ui @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.40.0 --> +<interface domain="chart"> + <requires lib="gtk+" version="3.24"/> + <!-- n-columns=2 n-rows=1 --> + <object class="GtkGrid" id="ChartThemePanel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="hexpand">True</property> + <property name="border-width">6</property> + <property name="row-spacing">3</property> + <property name="column-spacing">6</property> + <child> + <object class="GtkToolbar" id="themetype"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="valign">start</property> + <property name="toolbar-style">icons</property> + <property name="show-arrow">False</property> + <property name="icon_size">2</property> + <child> + <object class="GtkMenuToolButton" id=".uno:ChartTheme"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="tooltip-text" translatable="yes" context="sidebartheme|themelist|tooltip_text">Allow to select a theme to apply to the current chart.</property> + <property name="halign">start</property> + <property name="valign">start</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child internal-child="accessible"> + <object class="AtkObject" id="themetype-atkobject"> + <property name="AtkObject::accessible-name" translatable="yes" context="sidebartheme|themetype-atkobject">Themes</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="savetonew"> + <property name="label" translatable="yes" context="sidebartheme|savetonew">_Save to new theme</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="halign">start</property> + <property name="valign">start</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">0</property> + </packing> + </child> + </object> +</interface> diff --git a/drawinglayer/source/primitive2d/sceneprimitive2d.cxx b/drawinglayer/source/primitive2d/sceneprimitive2d.cxx index 65cd3d1f0373..2551881272ee 100644 --- a/drawinglayer/source/primitive2d/sceneprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/sceneprimitive2d.cxx @@ -38,6 +38,7 @@ #include <comphelper/threadpool.hxx> #include <comphelper/lok.hxx> #include <officecfg/Office/Common.hxx> +#include <svx/ChartThemeType.hxx> using namespace com::sun::star; @@ -246,7 +247,13 @@ namespace drawinglayer::primitive2d double fViewSizeX(aVisibleDiscreteRange.getWidth()); double fViewSizeY(aVisibleDiscreteRange.getHeight()); const double fViewVisibleArea(fViewSizeX * fViewSizeY); - const double fMaximumVisibleArea(officecfg::Office::Common::Drawinglayer::Quadratic3DRenderLimit::get()); + double fMaximumVisibleArea(officecfg::Office::Common::Drawinglayer::Quadratic3DRenderLimit::get()); + // Todo: find a better way, to render lower resolution chart thumbnails. + if (chart::bChartThumbnailRendered) + { + fMaximumVisibleArea = fMaximumVisibleArea / 100; + } + double fReduceFactor(1.0); if(fViewVisibleArea > fMaximumVisibleArea) diff --git a/include/svx/ChartThemeType.hxx b/include/svx/ChartThemeType.hxx new file mode 100644 index 000000000000..bc5dd8ac26e8 --- /dev/null +++ b/include/svx/ChartThemeType.hxx @@ -0,0 +1,88 @@ +/* -*- 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 <svl/itempool.hxx> +#include <com/sun/star/uno/Any.h> + +//#include <vector> + +namespace chart +{ +// if you change the size of the chart thumbnails, update the UI files that display them. +// dlg_Theme.ui , chartthemepopup.ui +constexpr int ChartThemeThumbSizeX = 192; +constexpr int ChartThemeThumbSizeY = 140; + +constexpr OUString sUnoChartTheme = u".uno:ChartTheme"_ustr; + +// TODO: find a less hacky way to make chart thumnbail render to lower resolution +// Set this to true only while a thumbnail of a chart is rendered, and set it back to false +// used in drawinglayer/source/primitive2d/sceneprimitive2d.cxx to save performance. +static bool bChartThumbnailRendered = false; + +// Properties of a chart element, like Title. +// At first it contained properties only from Font, and Font Effect dialog tab panel. +// but now, it store other properties. For more info about what properties it can store, +// check lcl_GetPropertyNameVec in ChartController.cxx +class ChartElementThemeType +{ +public: + std::vector<std::pair<OUString, css::uno::Any>> m_aProperties; + void convertPoolItemsToProperties(std::vector<SfxPoolItem*> aPoolItems); +}; + +// A chart theme (or style) conains several chart elements properties +enum class ChartThemeElementID : sal_uInt8 +{ + Title, + Legend, + Axis, + Label, + SubTitle, + AxisY, + AxisZ, + Nothing +}; +class ChartThemeType +{ +public: + static constexpr size_t ElementCount = 7; + std::array<ChartElementThemeType, ElementCount> m_aElements; + ChartThemeType() {} + + OUString m_aName; + sal_Int32 m_nID; +}; + +// It is a list of themes for charts. +// All of them contains separete properties for different chart elements. +// TODO: it is a singleton .. probably we shoul find a new place to store it. +class ChartThemesType +{ +public: + std::vector<ChartThemeType> m_aThemes; + + static ChartThemesType& getInstance(); + ChartThemesType(); + ChartThemesType(ChartThemesType const&) = delete; + void operator=(ChartThemesType const&) = delete; + + static ChartThemesType* m_aInstance; + + // the first FixedCount Themes are not customiyable, they are pre-defined + static constexpr sal_Int32 FixedCount = 4; + sal_uInt32 getThemesCount() { return m_aThemes.size(); } + static sal_uInt32 getThemesPreDefCount() { return FixedCount; } +}; + +} // end namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/officecfg/registry/data/org/openoffice/Office/UI/ChartCommands.xcu b/officecfg/registry/data/org/openoffice/Office/UI/ChartCommands.xcu index 3debc9384847..ac694569b8b7 100644 --- a/officecfg/registry/data/org/openoffice/Office/UI/ChartCommands.xcu +++ b/officecfg/registry/data/org/openoffice/Office/UI/ChartCommands.xcu @@ -306,6 +306,12 @@ <value xml:lang="en-US">Format Title...</value> </prop> </node> + <!-- theme --> + <node oor:name=".uno:ManageThemes" oor:op="replace"> + <prop oor:name="Label" oor:type="xs:string"> + <value xml:lang="en-US">Manage Themes...</value> + </prop> + </node> <!-- legend --> <node oor:name=".uno:InsertLegend" oor:op="replace"> <prop oor:name="Label" oor:type="xs:string"> diff --git a/officecfg/registry/data/org/openoffice/Office/UI/Controller.xcu b/officecfg/registry/data/org/openoffice/Office/UI/Controller.xcu index ee087a796a3a..77126b24940e 100644 --- a/officecfg/registry/data/org/openoffice/Office/UI/Controller.xcu +++ b/officecfg/registry/data/org/openoffice/Office/UI/Controller.xcu @@ -2101,6 +2101,17 @@ <value>com.sun.star.comp.chart2.ChartColorPaletteControl</value> </prop> </node> + <node oor:name="ChartThemeControl" oor:op="replace"> + <prop oor:name="Command"> + <value>.uno:ChartTheme</value> + </prop> + <prop oor:name="Module"> + <value/> + </prop> + <prop oor:name="Controller"> + <value>com.sun.star.comp.chart2.ChartThemeControl</value> + </prop> + </node> </node> <node oor:name="StatusBar"> <node oor:name="c5" oor:op="replace"> diff --git a/officecfg/registry/data/org/openoffice/Office/UI/Sidebar.xcu b/officecfg/registry/data/org/openoffice/Office/UI/Sidebar.xcu index d0871e8f6742..d7b98d06b990 100644 --- a/officecfg/registry/data/org/openoffice/Office/UI/Sidebar.xcu +++ b/officecfg/registry/data/org/openoffice/Office/UI/Sidebar.xcu @@ -2126,6 +2126,36 @@ </prop> </node> + <node oor:name="ChartThemePanel" oor:op="replace"> + <prop oor:name="Title" oor:type="xs:string"> + <value xml:lang="en-US">Theme</value> + </prop> + <prop oor:name="Id" oor:type="xs:string"> + <value>ChartThemePanel</value> + </prop> + <prop oor:name="DeckId" oor:type="xs:string"> + <value>PropertyDeck</value> + </prop> + <prop oor:name="DefaultMenuCommand"> + <value>.uno:ChartProperties</value> + </prop> + <prop oor:name="ContextList"> + <value oor:separator=";"> + Chart, Chart, visible ; + Chart, Series, visible ; + </value> + </prop> + <prop oor:name="ImplementationURL" oor:type="xs:string"> + <value>private:resource/toolpanel/ChartPanelFactory/ThemePanel</value> + </prop> + <prop oor:name="OrderIndex" oor:type="xs:int"> + <value>8</value> + </prop> + <prop oor:name="WantsAWT" oor:type="xs:boolean"> + <value>false</value> + </prop> + </node> + <node oor:name="ChartEffectPanel" oor:op="replace"> <prop oor:name="Title" oor:type="xs:string"> <value xml:lang="en-US">Effects</value> diff --git a/static/CustomTarget_emscripten_fs_image.mk b/static/CustomTarget_emscripten_fs_image.mk index fe513b81a6bb..01d6d7105446 100644 --- a/static/CustomTarget_emscripten_fs_image.mk +++ b/static/CustomTarget_emscripten_fs_image.mk @@ -1735,6 +1735,7 @@ gb_emscripten_fs_image_files += \ $(INSTROOT)/$(LIBO_SHARE_FOLDER)/config/soffice.cfg/modules/schart/ui/chartcolorpalettepopup.ui \ $(INSTROOT)/$(LIBO_SHARE_FOLDER)/config/soffice.cfg/modules/schart/ui/chardialog.ui \ $(INSTROOT)/$(LIBO_SHARE_FOLDER)/config/soffice.cfg/modules/schart/ui/chartdatadialog.ui \ + $(INSTROOT)/$(LIBO_SHARE_FOLDER)/config/soffice.cfg/modules/schart/ui/chartthemepopup.ui \ $(INSTROOT)/$(LIBO_SHARE_FOLDER)/config/soffice.cfg/modules/schart/ui/charttypedialog.ui \ $(INSTROOT)/$(LIBO_SHARE_FOLDER)/config/soffice.cfg/modules/schart/ui/columnfragment.ui \ $(INSTROOT)/$(LIBO_SHARE_FOLDER)/config/soffice.cfg/modules/schart/ui/combobox.ui \ @@ -1752,6 +1753,7 @@ gb_emscripten_fs_image_files += \ $(INSTROOT)/$(LIBO_SHARE_FOLDER)/config/soffice.cfg/modules/schart/ui/sidebarelements.ui \ $(INSTROOT)/$(LIBO_SHARE_FOLDER)/config/soffice.cfg/modules/schart/ui/sidebarerrorbar.ui \ $(INSTROOT)/$(LIBO_SHARE_FOLDER)/config/soffice.cfg/modules/schart/ui/sidebarseries.ui \ + $(INSTROOT)/$(LIBO_SHARE_FOLDER)/config/soffice.cfg/modules/schart/ui/sidebartheme.ui \ $(INSTROOT)/$(LIBO_SHARE_FOLDER)/config/soffice.cfg/modules/schart/ui/sidebartype.ui \ $(INSTROOT)/$(LIBO_SHARE_FOLDER)/config/soffice.cfg/modules/schart/ui/smoothlinesdlg.ui \ $(INSTROOT)/$(LIBO_SHARE_FOLDER)/config/soffice.cfg/modules/schart/ui/steppedlinesdlg.ui \ diff --git a/vcl/jsdialog/enabled.cxx b/vcl/jsdialog/enabled.cxx index 1cea2ac404a7..64c06a8e8065 100644 --- a/vcl/jsdialog/enabled.cxx +++ b/vcl/jsdialog/enabled.cxx @@ -306,6 +306,7 @@ constexpr auto SchartDialogList { u"modules/schart/ui/attributedialog.ui" }, { u"modules/schart/ui/charttypedialog.ui" }, { u"modules/schart/ui/datarangedialog.ui" }, + { u"modules/schart/ui/dlg_Theme.ui" }, { u"modules/schart/ui/insertaxisdlg.ui" }, { u"modules/schart/ui/inserttitledlg.ui" }, { u"modules/schart/ui/smoothlinesdlg.ui" }, @@ -389,6 +390,7 @@ constexpr auto PopupList { u"modules/scalc/ui/floatinglinestyle.ui" }, // schart { u"modules/schart/ui/chartcolorpalettepopup.ui" }, + { u"modules/schart/ui/chartthemepopup.ui" }, // svt { u"svt/ui/datewindow.ui" }, { u"svt/ui/linewindow.ui" }, @@ -433,6 +435,7 @@ constexpr auto SidebarList { u"modules/schart/ui/sidebarelements.ui" }, { u"modules/schart/ui/sidebarerrorbar.ui" }, { u"modules/schart/ui/sidebarseries.ui" }, + { u"modules/schart/ui/sidebartheme.ui" }, { u"modules/schart/ui/sidebartype.ui" }, // simpress { u"modules/simpress/ui/customanimationfragment.ui" },
