1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <com/sun/star/chart/ErrorBarStyle.hpp>
21 #include <com/sun/star/chart/DataLabelPlacement.hpp>
22 
23 #include <vcl/svapp.hxx>
24 #include <sal/log.hxx>
25 
26 #include "ChartSeriesPanel.hxx"
27 #include <ChartController.hxx>
28 #include <ChartModel.hxx>
29 #include <ChartType.hxx>
30 #include <DataSeries.hxx>
31 #include <DataSeriesHelper.hxx>
32 #include <Diagram.hxx>
33 #include <RegressionCurveHelper.hxx>
34 #include <RegressionCurveModel.hxx>
35 #include <StatisticsHelper.hxx>
36 #include <BaseCoordinateSystem.hxx>
37 
38 #include <comphelper/processfactory.hxx>
39 
40 using namespace css;
41 using namespace css::uno;
42 
43 namespace chart::sidebar {
44 
45 namespace {
46 
isDataLabelVisible(const rtl::Reference<::chart::ChartModel> & xModel,std::u16string_view rCID)47 bool isDataLabelVisible(const rtl::Reference<::chart::ChartModel>& xModel, std::u16string_view rCID)
48 {
49     rtl::Reference< DataSeries > xSeries =
50         ObjectIdentifier::getDataSeriesForCID(rCID, xModel);
51 
52     if (!xSeries.is())
53         return false;
54 
55     return DataSeriesHelper::hasDataLabelsAtSeries(xSeries);
56 }
57 
setDataLabelVisible(const rtl::Reference<::chart::ChartModel> & xModel,std::u16string_view rCID,bool bVisible)58 void setDataLabelVisible(const rtl::Reference<::chart::ChartModel>& xModel, std::u16string_view rCID, bool bVisible)
59 {
60     rtl::Reference< DataSeries > xSeries =
61         ObjectIdentifier::getDataSeriesForCID(rCID, xModel);
62 
63     if (!xSeries.is())
64         return;
65 
66     if (bVisible)
67         DataSeriesHelper::insertDataLabelsToSeriesAndAllPoints(xSeries);
68     else
69         DataSeriesHelper::deleteDataLabelsFromSeriesAndAllPoints(xSeries);
70 }
71 
72 struct LabelPlacementMap
73 {
74     sal_Int32 nPos;
75     sal_Int32 nApi;
76 };
77 
78 LabelPlacementMap const aLabelPlacementMap[] = {
79     { 0, css::chart::DataLabelPlacement::TOP },
80     { 1, css::chart::DataLabelPlacement::BOTTOM },
81     { 2, css::chart::DataLabelPlacement::CENTER },
82     { 3, css::chart::DataLabelPlacement::OUTSIDE },
83     { 4, css::chart::DataLabelPlacement::INSIDE  },
84     { 5, css::chart::DataLabelPlacement::NEAR_ORIGIN }
85 };
86 
getDataLabelPlacement(const rtl::Reference<::chart::ChartModel> & xModel,std::u16string_view rCID)87 sal_Int32 getDataLabelPlacement(const rtl::Reference<::chart::ChartModel>& xModel,
88         std::u16string_view rCID)
89 {
90     rtl::Reference< DataSeries > xSeries =
91         ObjectIdentifier::getDataSeriesForCID(rCID, xModel);
92 
93     if (!xSeries.is())
94         return 0;
95 
96     css::uno::Any aAny = xSeries->getPropertyValue(u"LabelPlacement"_ustr);
97     if (!aAny.hasValue())
98         return 0;
99 
100     sal_Int32 nPlacement = 0;
101     aAny >>= nPlacement;
102 
103     for (LabelPlacementMap const & i : aLabelPlacementMap)
104     {
105         if (i.nApi == nPlacement)
106             return i.nPos;
107     }
108 
109     return 0;
110 }
111 
setDataLabelPlacement(const rtl::Reference<::chart::ChartModel> & xModel,std::u16string_view rCID,sal_Int32 nPos)112 void setDataLabelPlacement(const rtl::Reference<::chart::ChartModel>& xModel,
113         std::u16string_view rCID, sal_Int32 nPos)
114 {
115     rtl::Reference< DataSeries > xSeries =
116         ObjectIdentifier::getDataSeriesForCID(rCID, xModel);
117 
118     if (!xSeries.is())
119         return;
120 
121     sal_Int32 nApi = 0;
122     for (LabelPlacementMap const & i : aLabelPlacementMap)
123     {
124         if (i.nPos == nPos)
125         {
126             nApi = i.nApi;
127             break;
128         }
129     }
130 
131     xSeries->setPropertyValue(u"LabelPlacement"_ustr, css::uno::Any(nApi));
132 }
133 
isTrendlineVisible(const rtl::Reference<::chart::ChartModel> & xModel,std::u16string_view rCID)134 bool isTrendlineVisible(const rtl::Reference<::chart::ChartModel>& xModel,
135         std::u16string_view rCID)
136 {
137     rtl::Reference< DataSeries > xRegressionCurveContainer =
138         ObjectIdentifier::getDataSeriesForCID(rCID, xModel);
139 
140     if (!xRegressionCurveContainer.is())
141         return false;
142 
143     return !xRegressionCurveContainer->getRegressionCurves2().empty();
144 }
145 
setTrendlineVisible(const rtl::Reference<::chart::ChartModel> & xModel,std::u16string_view rCID,bool bVisible)146 void setTrendlineVisible(const rtl::Reference<::chart::ChartModel>&
147         xModel, std::u16string_view rCID, bool bVisible)
148 {
149     rtl::Reference< DataSeries > xRegressionCurveContainer =
150         ObjectIdentifier::getDataSeriesForCID(rCID, xModel);
151 
152     if (!xRegressionCurveContainer.is())
153         return;
154 
155     if (bVisible)
156     {
157         RegressionCurveHelper::addRegressionCurve(
158                     SvxChartRegress::Linear,
159                     xRegressionCurveContainer);
160     }
161     else
162         RegressionCurveHelper::removeAllExceptMeanValueLine(
163                 xRegressionCurveContainer );
164 
165 }
166 
isErrorBarVisible(const rtl::Reference<::chart::ChartModel> & xModel,std::u16string_view rCID,bool bYError)167 bool isErrorBarVisible(const rtl::Reference<::chart::ChartModel>& xModel,
168                        std::u16string_view rCID, bool bYError)
169 {
170     rtl::Reference< DataSeries > xSeries =
171         ObjectIdentifier::getDataSeriesForCID(rCID, xModel);
172 
173     if (!xSeries.is())
174         return false;
175 
176     return StatisticsHelper::hasErrorBars(xSeries, bYError);
177 }
178 
setErrorBarVisible(const rtl::Reference<::chart::ChartModel> & xModel,std::u16string_view rCID,bool bYError,bool bVisible)179 void setErrorBarVisible(const rtl::Reference<::chart::ChartModel>&
180         xModel, std::u16string_view rCID, bool bYError, bool bVisible)
181 {
182     rtl::Reference< DataSeries > xSeries =
183         ObjectIdentifier::getDataSeriesForCID(rCID, xModel);
184 
185     if (!xSeries.is())
186         return;
187 
188     if (bVisible)
189     {
190         StatisticsHelper::addErrorBars( xSeries,
191                     css::chart::ErrorBarStyle::STANDARD_DEVIATION,
192                     bYError);
193     }
194     else
195     {
196         StatisticsHelper::removeErrorBars( xSeries, bYError );
197     }
198 }
199 
isPrimaryAxis(const rtl::Reference<::chart::ChartModel> & xModel,std::u16string_view rCID)200 bool isPrimaryAxis(const rtl::Reference<::chart::ChartModel>&
201         xModel, std::u16string_view rCID)
202 {
203     rtl::Reference< DataSeries > xSeries =
204         ObjectIdentifier::getDataSeriesForCID(rCID, xModel);
205 
206     if (!xSeries.is())
207         return true;
208 
209     return DataSeriesHelper::getAttachedAxisIndex(xSeries) == 0;
210 }
211 
setAttachedAxisType(const rtl::Reference<::chart::ChartModel> & xModel,std::u16string_view rCID,bool bPrimary)212 void setAttachedAxisType(const rtl::Reference<::chart::ChartModel>&
213         xModel, std::u16string_view rCID, bool bPrimary)
214 {
215     const rtl::Reference<DataSeries> xDataSeries = ObjectIdentifier::getDataSeriesForCID(rCID, xModel);
216 
217     if (!xDataSeries.is())
218         return;
219 
220     rtl::Reference<Diagram> xDiagram = xModel->getFirstChartDiagram();
221     xDiagram->attachSeriesToAxis(bPrimary, xDataSeries, comphelper::getProcessComponentContext());
222 }
223 
getChartType(const rtl::Reference<::chart::ChartModel> & xModel)224 rtl::Reference<ChartType> getChartType(
225         const rtl::Reference<::chart::ChartModel>& xModel)
226 {
227     rtl::Reference<Diagram> xDiagram = xModel->getFirstChartDiagram();
228     const std::vector< rtl::Reference< BaseCoordinateSystem > > & xCooSysSequence( xDiagram->getBaseCoordinateSystems());
229     return xCooSysSequence[0]->getChartTypes2()[0];
230 }
231 
getSeriesLabel(const rtl::Reference<::chart::ChartModel> & xModel,std::u16string_view rCID)232 OUString getSeriesLabel(const rtl::Reference<::chart::ChartModel>& xModel, std::u16string_view rCID)
233 {
234     rtl::Reference< DataSeries > xSeries =
235         ObjectIdentifier::getDataSeriesForCID(rCID, xModel);
236 
237     if (!xSeries.is())
238         return OUString();
239 
240     rtl::Reference<ChartType> xChartType = getChartType(xModel);
241     return xSeries->getLabelForRole(xChartType->getRoleOfSequenceForSeriesLabel());
242 }
243 
getCID(const css::uno::Reference<css::frame::XModel> & xModel)244 OUString getCID(const css::uno::Reference<css::frame::XModel>& xModel)
245 {
246     css::uno::Reference<css::frame::XController> xController(xModel->getCurrentController());
247     css::uno::Reference<css::view::XSelectionSupplier> xSelectionSupplier(xController, css::uno::UNO_QUERY);
248     if (!xSelectionSupplier.is())
249         return OUString();
250 
251     uno::Any aAny = xSelectionSupplier->getSelection();
252     if (!aAny.hasValue())
253         return OUString();
254 
255     OUString aCID;
256     aAny >>= aCID;
257 
258     if (aCID.isEmpty())
259         return OUString();
260 
261 #if defined DBG_UTIL && !defined NDEBUG
262     ObjectType eType = ObjectIdentifier::getObjectType(aCID);
263     if (eType != OBJECTTYPE_DATA_SERIES &&
264          eType != OBJECTTYPE_DATA_POINT &&
265          eType != OBJECTTYPE_DATA_CURVE)
266         SAL_WARN("chart2","Selected item is not a chart series");
267 #endif
268 
269     return aCID;
270 }
271 
272 }
273 
ChartSeriesPanel(weld::Widget * pParent,ChartController * pController)274 ChartSeriesPanel::ChartSeriesPanel(
275     weld::Widget* pParent,
276     ChartController* pController)
277     : PanelLayout(pParent, u"ChartSeriesPanel"_ustr, u"modules/schart/ui/sidebarseries.ui"_ustr)
278     , mxCBLabel(m_xBuilder->weld_check_button(u"checkbutton_label"_ustr))
279     , mxCBTrendline(m_xBuilder->weld_check_button(u"checkbutton_trendline"_ustr))
280     , mxCBXError(m_xBuilder->weld_check_button(u"checkbutton_x_error"_ustr))
281     , mxCBYError(m_xBuilder->weld_check_button(u"checkbutton_y_error"_ustr))
282     , mxRBPrimaryAxis(m_xBuilder->weld_radio_button(u"radiobutton_primary_axis"_ustr))
283     , mxRBSecondaryAxis(m_xBuilder->weld_radio_button(u"radiobutton_secondary_axis"_ustr))
284     , mxBoxLabelPlacement(m_xBuilder->weld_widget(u"datalabel_box"_ustr))
285     , mxLBLabelPlacement(m_xBuilder->weld_combo_box(u"comboboxtext_label"_ustr))
286     , mxFTSeriesName(m_xBuilder->weld_label(u"label_series_name"_ustr))
287     , mxFTSeriesTemplate(m_xBuilder->weld_label(u"label_series_tmpl"_ustr))
288     , mxModel(pController->getChartModel())
289     , mxListener(new ChartSidebarModifyListener(this))
290     , mxSelectionListener(new ChartSidebarSelectionListener(this, OBJECTTYPE_DATA_SERIES))
291     , mbModelValid(true)
292 {
293     Initialize();
294 }
295 
~ChartSeriesPanel()296 ChartSeriesPanel::~ChartSeriesPanel()
297 {
298     doUpdateModel(nullptr);
299 
300     mxCBLabel.reset();
301     mxCBTrendline.reset();
302     mxCBXError.reset();
303     mxCBYError.reset();
304 
305     mxRBPrimaryAxis.reset();
306     mxRBSecondaryAxis.reset();
307 
308     mxBoxLabelPlacement.reset();
309     mxLBLabelPlacement.reset();
310 
311     mxFTSeriesName.reset();
312     mxFTSeriesTemplate.reset();
313 }
314 
Initialize()315 void ChartSeriesPanel::Initialize()
316 {
317     mxModel->addModifyListener(mxListener);
318     css::uno::Reference<css::view::XSelectionSupplier> xSelectionSupplier(mxModel->getCurrentController(), css::uno::UNO_QUERY);
319     if (xSelectionSupplier.is())
320         xSelectionSupplier->addSelectionChangeListener(mxSelectionListener);
321 
322     updateData();
323 
324     Link<weld::Toggleable&,void> aLink = LINK(this, ChartSeriesPanel, CheckBoxHdl);
325     mxCBLabel->connect_toggled(aLink);
326     mxCBTrendline->connect_toggled(aLink);
327     mxCBXError->connect_toggled(aLink);
328     mxCBYError->connect_toggled(aLink);
329 
330     Link<weld::Toggleable&,void> aLink2 = LINK(this, ChartSeriesPanel, RadioBtnHdl);
331     mxRBPrimaryAxis->connect_toggled(aLink2);
332     mxRBSecondaryAxis->connect_toggled(aLink2);
333 
334     mxLBLabelPlacement->connect_changed(LINK(this, ChartSeriesPanel, ListBoxHdl));
335 }
336 
updateData()337 void ChartSeriesPanel::updateData()
338 {
339     if (!mbModelValid)
340         return;
341 
342     OUString aCID = getCID(mxModel);
343     ObjectType eType = ObjectIdentifier::getObjectType(aCID);
344     if (eType!=OBJECTTYPE_DATA_SERIES &&
345           eType != OBJECTTYPE_DATA_POINT &&
346           eType != OBJECTTYPE_DATA_CURVE)
347         return;
348 
349     SolarMutexGuard aGuard;
350     bool bLabelVisible = isDataLabelVisible(mxModel, aCID);
351     mxCBLabel->set_active(bLabelVisible);
352     mxCBTrendline->set_active(isTrendlineVisible(mxModel, aCID));
353     mxCBXError->set_active(isErrorBarVisible(mxModel, aCID, false));
354     mxCBYError->set_active(isErrorBarVisible(mxModel, aCID, true));
355 
356     bool bPrimaryAxis = isPrimaryAxis(mxModel, aCID);
357     mxRBPrimaryAxis->set_active(bPrimaryAxis);
358     mxRBSecondaryAxis->set_active(!bPrimaryAxis);
359 
360     mxBoxLabelPlacement->set_sensitive(bLabelVisible);
361     mxLBLabelPlacement->set_active(getDataLabelPlacement(mxModel, aCID));
362 
363     OUString aFrameLabel = mxFTSeriesTemplate->get_label();
364     aFrameLabel = aFrameLabel.replaceFirst("%1", getSeriesLabel(mxModel, aCID));
365     mxFTSeriesName->set_label(aFrameLabel);
366 }
367 
Create(weld::Widget * pParent,ChartController * pController)368 std::unique_ptr<PanelLayout> ChartSeriesPanel::Create (
369     weld::Widget* pParent,
370     ChartController* pController)
371 {
372     if (pParent == nullptr)
373         throw lang::IllegalArgumentException(u"no parent Window given to ChartSeriesPanel::Create"_ustr, nullptr, 0);
374 
375     return std::make_unique<ChartSeriesPanel>(pParent, pController);
376 }
377 
DataChanged(const DataChangedEvent & rEvent)378 void ChartSeriesPanel::DataChanged(const DataChangedEvent& rEvent)
379 {
380     PanelLayout::DataChanged(rEvent);
381     updateData();
382 }
383 
HandleContextChange(const vcl::EnumContext &)384 void ChartSeriesPanel::HandleContextChange(
385     const vcl::EnumContext& )
386 {
387     updateData();
388 }
389 
NotifyItemUpdate(sal_uInt16,SfxItemState,const SfxPoolItem *)390 void ChartSeriesPanel::NotifyItemUpdate(
391     sal_uInt16 /*nSID*/,
392     SfxItemState /*eState*/,
393     const SfxPoolItem* /*pState*/ )
394 {
395 }
396 
modelInvalid()397 void ChartSeriesPanel::modelInvalid()
398 {
399     mbModelValid = false;
400 }
401 
doUpdateModel(const rtl::Reference<::chart::ChartModel> & xModel)402 void ChartSeriesPanel::doUpdateModel(const rtl::Reference<::chart::ChartModel>& xModel)
403 {
404     if (mbModelValid)
405     {
406         mxModel->removeModifyListener(mxListener);
407     }
408 
409     css::uno::Reference<css::view::XSelectionSupplier> oldSelectionSupplier(
410         mxModel->getCurrentController(), css::uno::UNO_QUERY);
411     if (oldSelectionSupplier.is()) {
412         oldSelectionSupplier->removeSelectionChangeListener(mxSelectionListener);
413     }
414 
415     mxModel = xModel;
416     mbModelValid = mxModel.is();
417 
418     if (!mbModelValid)
419         return;
420 
421     mxModel->addModifyListener(mxListener);
422 
423     css::uno::Reference<css::view::XSelectionSupplier> xSelectionSupplier(mxModel->getCurrentController(), css::uno::UNO_QUERY);
424     if (xSelectionSupplier.is())
425         xSelectionSupplier->addSelectionChangeListener(mxSelectionListener);
426 }
427 
updateModel(css::uno::Reference<css::frame::XModel> xModel)428 void ChartSeriesPanel::updateModel(css::uno::Reference<css::frame::XModel> xModel)
429 {
430     ::chart::ChartModel* pModel = dynamic_cast<::chart::ChartModel*>(xModel.get());
431     assert(!xModel || pModel);
432     doUpdateModel(pModel);
433 }
434 
selectionChanged(bool bCorrectType)435 void ChartSeriesPanel::selectionChanged(bool bCorrectType)
436 {
437     if (bCorrectType)
438         updateData();
439 }
440 
IMPL_LINK(ChartSeriesPanel,CheckBoxHdl,weld::Toggleable &,rCheckBox,void)441 IMPL_LINK(ChartSeriesPanel, CheckBoxHdl, weld::Toggleable&, rCheckBox, void)
442 {
443     bool bChecked = rCheckBox.get_active();
444     OUString aCID = getCID(mxModel);
445     if (&rCheckBox == mxCBLabel.get())
446         setDataLabelVisible(mxModel, aCID, bChecked);
447     else if (&rCheckBox == mxCBTrendline.get())
448         setTrendlineVisible(mxModel, aCID, bChecked);
449     else if (&rCheckBox == mxCBXError.get())
450         setErrorBarVisible(mxModel, aCID, false, bChecked);
451     else if (&rCheckBox == mxCBYError.get())
452         setErrorBarVisible(mxModel, aCID, true, bChecked);
453 }
454 
IMPL_LINK_NOARG(ChartSeriesPanel,RadioBtnHdl,weld::Toggleable &,void)455 IMPL_LINK_NOARG(ChartSeriesPanel, RadioBtnHdl, weld::Toggleable&, void)
456 {
457     OUString aCID = getCID(mxModel);
458     bool bChecked = mxRBPrimaryAxis->get_active();
459 
460     setAttachedAxisType(mxModel, aCID, bChecked);
461 }
462 
IMPL_LINK_NOARG(ChartSeriesPanel,ListBoxHdl,weld::ComboBox &,void)463 IMPL_LINK_NOARG(ChartSeriesPanel, ListBoxHdl, weld::ComboBox&, void)
464 {
465     OUString aCID = getCID(mxModel);
466 
467     sal_Int32 nPos = mxLBLabelPlacement->get_active();
468     setDataLabelPlacement(mxModel, aCID, nPos);
469 }
470 
471 } // end of namespace ::chart::sidebar
472 
473 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
474