Coverage Report

Created: 2026-09-28 10:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/chart2/source/model/main/DataSeries.cxx
Line
Count
Source
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 <DataSeries.hxx>
21
#include <DataSeriesProperties.hxx>
22
#include "DataPointProperties.hxx"
23
#include <CharacterProperties.hxx>
24
#include <UserDefinedProperties.hxx>
25
#include "DataPoint.hxx"
26
#include <DataSeriesHelper.hxx>
27
#include <CloneHelper.hxx>
28
#include <RegressionCurveModel.hxx>
29
#include <ModifyListenerHelper.hxx>
30
#include <unonames.hxx>
31
#include <com/sun/star/chart2/DataPointLabel.hpp>
32
#include <com/sun/star/chart2/Symbol.hpp>
33
#include <com/sun/star/chart2/data/XTextualDataSequence.hpp>
34
#include <com/sun/star/container/NoSuchElementException.hpp>
35
#include <com/sun/star/drawing/LineStyle.hpp>
36
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
37
#include <cppuhelper/supportsservice.hxx>
38
#include <comphelper/diagnose_ex.hxx>
39
#include <rtl/ref.hxx>
40
#include <rtl/ustrbuf.hxx>
41
42
#include <algorithm>
43
44
namespace com::sun::star::uno { class XComponentContext; }
45
46
using namespace ::com::sun::star;
47
48
using ::com::sun::star::beans::Property;
49
using ::com::sun::star::uno::Sequence;
50
using ::com::sun::star::uno::Reference;
51
using ::osl::MutexGuard;
52
53
namespace chart
54
{
55
const ::chart::tPropertyValueMap & StaticDataSeriesDefaults()
56
0
{
57
0
    static const ::chart::tPropertyValueMap aStaticDefaults = []()
58
0
        {
59
0
            ::chart::tPropertyValueMap aMap;
60
0
            ::chart::DataSeriesProperties::AddDefaultsToMap( aMap );
61
0
            ::chart::CharacterProperties::AddDefaultsToMap( aMap );
62
0
            float fDefaultCharHeight = 10.0;
63
0
            ::chart::PropertyHelper::setPropertyValue( aMap, ::chart::CharacterProperties::PROP_CHAR_CHAR_HEIGHT, fDefaultCharHeight );
64
0
            ::chart::PropertyHelper::setPropertyValue( aMap, ::chart::CharacterProperties::PROP_CHAR_ASIAN_CHAR_HEIGHT, fDefaultCharHeight );
65
0
            ::chart::PropertyHelper::setPropertyValue( aMap, ::chart::CharacterProperties::PROP_CHAR_COMPLEX_CHAR_HEIGHT, fDefaultCharHeight );
66
0
            return aMap;
67
0
        }();
68
0
    return aStaticDefaults;
69
0
};
70
} // namespace chart
71
72
namespace
73
{
74
75
::cppu::OPropertyArrayHelper& StaticDataSeriesInfoHelper()
76
0
{
77
0
    static ::cppu::OPropertyArrayHelper oHelper = []()
78
0
        {
79
0
            std::vector< css::beans::Property > aProperties;
80
0
            ::chart::DataSeriesProperties::AddPropertiesToVector( aProperties );
81
0
            ::chart::CharacterProperties::AddPropertiesToVector( aProperties );
82
0
            ::chart::UserDefinedProperties::AddPropertiesToVector( aProperties );
83
84
0
            std::sort( aProperties.begin(), aProperties.end(),
85
0
                         ::chart::PropertyNameLess() );
86
87
0
            return comphelper::containerToSequence( aProperties );
88
0
        }();
89
0
    return oHelper;
90
0
};
91
92
void lcl_SetParent(
93
    const uno::Reference< uno::XInterface > & xChildInterface,
94
    const uno::Reference< uno::XInterface > & xParentInterface )
95
0
{
96
0
    uno::Reference< container::XChild > xChild( xChildInterface, uno::UNO_QUERY );
97
0
    if( xChild.is())
98
0
        xChild->setParent( xParentInterface );
99
0
}
100
101
typedef std::map< sal_Int32, css::uno::Reference< css::beans::XPropertySet > >
102
    lcl_tDataPointMap;
103
104
void lcl_CloneAttributedDataPoints(
105
    const lcl_tDataPointMap & rSource, lcl_tDataPointMap & rDestination,
106
    const uno::Reference< uno::XInterface > & xSeries )
107
0
{
108
0
    for (auto const& elem : rSource)
109
0
    {
110
0
        Reference< beans::XPropertySet > xPoint( elem.second );
111
0
        if( xPoint.is())
112
0
        {
113
0
            Reference< util::XCloneable > xCloneable( xPoint, uno::UNO_QUERY );
114
0
            if( xCloneable.is())
115
0
            {
116
0
                xPoint.set( xCloneable->createClone(), uno::UNO_QUERY );
117
0
                if( xPoint.is())
118
0
                {
119
0
                    lcl_SetParent( xPoint, xSeries );
120
0
                    rDestination.emplace( elem.first, xPoint );
121
0
                }
122
0
            }
123
0
        }
124
0
    }
125
0
}
126
127
} // anonymous namespace
128
129
namespace chart
130
{
131
132
DataSeries::DataSeries() :
133
0
        m_xModifyEventForwarder( new ModifyEventForwarder() )
134
0
{
135
0
}
136
137
DataSeries::DataSeries( const DataSeries & rOther ) :
138
0
        impl::DataSeries_Base(rOther),
139
0
        ::property::OPropertySet( rOther ),
140
0
    m_xModifyEventForwarder( new ModifyEventForwarder() )
141
0
{
142
0
    if( ! rOther.m_aDataSequences.empty())
143
0
    {
144
0
        CloneHelper::CloneRefVector(rOther.m_aDataSequences, m_aDataSequences );
145
0
        ModifyListenerHelper::addListenerToAllElements( m_aDataSequences, m_xModifyEventForwarder );
146
0
    }
147
148
0
    CloneHelper::CloneRefVector( rOther.m_aRegressionCurves, m_aRegressionCurves );
149
0
    ModifyListenerHelper::addListenerToAllElements( m_aRegressionCurves, m_xModifyEventForwarder );
150
151
    // add as listener to XPropertySet properties
152
0
    Reference< beans::XPropertySet > xPropertySet;
153
0
    uno::Any aValue;
154
155
0
    getFastPropertyValue( aValue, DataPointProperties::PROP_DATAPOINT_ERROR_BAR_X );
156
0
    if( ( aValue >>= xPropertySet )
157
0
        && xPropertySet.is())
158
0
        ModifyListenerHelper::addListener( xPropertySet, m_xModifyEventForwarder );
159
160
0
    getFastPropertyValue( aValue, DataPointProperties::PROP_DATAPOINT_ERROR_BAR_Y );
161
0
    if( ( aValue >>= xPropertySet )
162
0
        && xPropertySet.is())
163
0
        ModifyListenerHelper::addListener( xPropertySet, m_xModifyEventForwarder );
164
165
0
    if (rOther.m_xCalculatedYSequence.is())
166
0
    {
167
0
        uno::Reference<util::XCloneable> xCloneable(rOther.m_xCalculatedYSequence, uno::UNO_QUERY);
168
0
        if (xCloneable.is())
169
0
        {
170
0
            m_xCalculatedYSequence.set(xCloneable->createClone(), uno::UNO_QUERY);
171
0
        }
172
0
        else
173
0
        {
174
0
            m_xCalculatedYSequence = rOther.m_xCalculatedYSequence;
175
0
        }
176
0
    }
177
0
}
178
179
// late initialization to call after copy-constructing
180
void DataSeries::Init( const DataSeries & rOther )
181
0
{
182
0
    Reference< uno::XInterface > xThisInterface( static_cast< ::cppu::OWeakObject * >( this ));
183
0
    if( ! rOther.m_aAttributedDataPoints.empty())
184
0
    {
185
0
        lcl_CloneAttributedDataPoints(
186
0
            rOther.m_aAttributedDataPoints, m_aAttributedDataPoints, xThisInterface );
187
0
        ModifyListenerHelper::addListenerToAllMapElements( m_aAttributedDataPoints, m_xModifyEventForwarder );
188
0
    }
189
190
    // add as parent to error bars
191
0
    Reference< beans::XPropertySet > xPropertySet;
192
0
    uno::Any aValue;
193
194
0
    getFastPropertyValue( aValue, DataPointProperties::PROP_DATAPOINT_ERROR_BAR_X );
195
0
    if( ( aValue >>= xPropertySet )
196
0
        && xPropertySet.is())
197
0
        lcl_SetParent( xPropertySet, xThisInterface );
198
199
0
    getFastPropertyValue( aValue, DataPointProperties::PROP_DATAPOINT_ERROR_BAR_Y );
200
0
    if( ( aValue >>= xPropertySet )
201
0
        && xPropertySet.is())
202
0
        lcl_SetParent( xPropertySet, xThisInterface );
203
0
}
204
205
DataSeries::~DataSeries()
206
0
{
207
0
    try
208
0
    {
209
0
        ModifyListenerHelper::removeListenerFromAllMapElements( m_aAttributedDataPoints, m_xModifyEventForwarder );
210
0
        ModifyListenerHelper::removeListenerFromAllElements( m_aRegressionCurves, m_xModifyEventForwarder );
211
0
        ModifyListenerHelper::removeListenerFromAllElements( m_aDataSequences, m_xModifyEventForwarder );
212
213
        // remove listener from XPropertySet properties
214
0
        Reference< beans::XPropertySet > xPropertySet;
215
0
        uno::Any aValue;
216
217
0
        getFastPropertyValue( aValue, DataPointProperties::PROP_DATAPOINT_ERROR_BAR_X );
218
0
        if( ( aValue >>= xPropertySet )
219
0
            && xPropertySet.is())
220
0
            ModifyListenerHelper::removeListener( xPropertySet, m_xModifyEventForwarder );
221
222
0
        getFastPropertyValue( aValue, DataPointProperties::PROP_DATAPOINT_ERROR_BAR_Y );
223
0
        if( ( aValue >>= xPropertySet )
224
0
            && xPropertySet.is())
225
0
            ModifyListenerHelper::removeListener( xPropertySet, m_xModifyEventForwarder );
226
0
    }
227
0
    catch( const uno::Exception & )
228
0
    {
229
0
        DBG_UNHANDLED_EXCEPTION("chart2");
230
0
    }
231
0
}
232
233
// ____ XCloneable ____
234
uno::Reference< util::XCloneable > SAL_CALL DataSeries::createClone()
235
0
{
236
0
    rtl::Reference<DataSeries> pNewSeries( new DataSeries( *this ));
237
    // do initialization that uses uno references to the clone
238
0
    pNewSeries->Init( *this );
239
240
0
    return pNewSeries;
241
0
}
242
243
// ____ OPropertySet ____
244
void DataSeries::GetDefaultValue( sal_Int32 nHandle, uno::Any& rDest ) const
245
0
{
246
0
    const tPropertyValueMap& rStaticDefaults = StaticDataSeriesDefaults();
247
0
    tPropertyValueMap::const_iterator aFound( rStaticDefaults.find( nHandle ) );
248
0
    if( aFound == rStaticDefaults.end() )
249
0
        rDest.clear();
250
0
    else
251
0
        rDest = (*aFound).second;
252
0
}
253
254
// ____ OPropertySet ____
255
::cppu::IPropertyArrayHelper & SAL_CALL DataSeries::getInfoHelper()
256
0
{
257
0
    return StaticDataSeriesInfoHelper();
258
0
}
259
260
// ____ XPropertySet ____
261
uno::Reference< beans::XPropertySetInfo > SAL_CALL DataSeries::getPropertySetInfo()
262
0
{
263
0
    static uno::Reference< beans::XPropertySetInfo > xPropSetInfo =
264
0
        ::cppu::OPropertySetHelper::createPropertySetInfo(StaticDataSeriesInfoHelper() );
265
0
    return xPropSetInfo;
266
0
}
267
268
void SAL_CALL DataSeries::getFastPropertyValue
269
    ( uno::Any& rValue,
270
      sal_Int32 nHandle ) const
271
0
{
272
    // special handling for get.  set is not possible for this property
273
0
    if( nHandle == DataSeriesProperties::PROP_DATASERIES_ATTRIBUTED_DATA_POINTS )
274
0
    {
275
        // TODO: only add those property sets that are really modified
276
277
0
        rValue <<= comphelper::mapKeysToSequence(m_aAttributedDataPoints);
278
0
    }
279
0
    else
280
0
        OPropertySet::getFastPropertyValue( rValue, nHandle );
281
0
}
282
283
void SAL_CALL DataSeries::setFastPropertyValue_NoBroadcast(
284
    sal_Int32 nHandle, const uno::Any& rValue )
285
0
{
286
0
    if(    nHandle == DataPointProperties::PROP_DATAPOINT_ERROR_BAR_Y
287
0
        || nHandle == DataPointProperties::PROP_DATAPOINT_ERROR_BAR_X )
288
0
    {
289
0
        uno::Any aOldValue;
290
0
        Reference< util::XModifyBroadcaster > xBroadcaster;
291
0
        getFastPropertyValue( aOldValue, nHandle );
292
0
        if( aOldValue.hasValue() &&
293
0
            (aOldValue >>= xBroadcaster) &&
294
0
            xBroadcaster.is())
295
0
        {
296
0
            ModifyListenerHelper::removeListener( xBroadcaster, m_xModifyEventForwarder );
297
0
        }
298
299
0
        OSL_ASSERT( rValue.getValueTypeClass() == uno::TypeClass_INTERFACE );
300
0
        if( rValue.hasValue() &&
301
0
            (rValue >>= xBroadcaster) &&
302
0
            xBroadcaster.is())
303
0
        {
304
0
            ModifyListenerHelper::addListener( xBroadcaster, m_xModifyEventForwarder );
305
0
        }
306
0
    }
307
308
0
    ::property::OPropertySet::setFastPropertyValue_NoBroadcast( nHandle, rValue );
309
0
}
310
311
Reference< beans::XPropertySet >
312
    SAL_CALL DataSeries::getDataPointByIndex( sal_Int32 nIndex )
313
0
{
314
0
    Reference< beans::XPropertySet > xResult;
315
316
0
    std::vector< uno::Reference< chart2::data::XLabeledDataSequence > > aSequences;
317
0
    {
318
0
        MutexGuard aGuard( m_aMutex );
319
0
        aSequences = m_aDataSequences;
320
0
    }
321
322
0
    std::vector< uno::Reference< chart2::data::XLabeledDataSequence > > aValuesSeries(
323
0
        DataSeriesHelper::getAllDataSequencesByRole( aSequences , u"values"_ustr ) );
324
325
0
    if (aValuesSeries.empty())
326
0
        throw lang::IndexOutOfBoundsException();
327
328
0
    Reference< chart2::data::XDataSequence > xSeq( aValuesSeries.front()->getValues() );
329
0
    if( 0 <= nIndex && nIndex < xSeq->getData().getLength() )
330
0
    {
331
0
        {
332
0
            MutexGuard aGuard( m_aMutex );
333
0
            tDataPointAttributeContainer::iterator aIt( m_aAttributedDataPoints.find( nIndex ) );
334
0
            if( aIt != m_aAttributedDataPoints.end() )
335
0
                xResult = (*aIt).second;
336
0
        }
337
0
        if( !xResult.is() )
338
0
        {
339
0
            Reference< beans::XPropertySet > xParentProperties;
340
0
            rtl::Reference< ModifyEventForwarder > xModifyEventForwarder;
341
0
            {
342
0
                MutexGuard aGuard( m_aMutex );
343
0
                xParentProperties = this;
344
0
                xModifyEventForwarder = m_xModifyEventForwarder;
345
0
            }
346
347
            // create a new XPropertySet for this data point
348
0
            xResult.set( new DataPoint( xParentProperties ) );
349
0
            {
350
0
                MutexGuard aGuard( m_aMutex );
351
0
                m_aAttributedDataPoints[ nIndex ] = xResult;
352
0
            }
353
0
            ModifyListenerHelper::addListener( xResult, xModifyEventForwarder );
354
0
        }
355
0
    }
356
357
0
    return xResult;
358
0
}
359
360
void SAL_CALL DataSeries::resetDataPoint( sal_Int32 nIndex )
361
0
{
362
0
    Reference< beans::XPropertySet > xDataPointProp;
363
0
    rtl::Reference< ModifyEventForwarder > xModifyEventForwarder;
364
0
    {
365
0
        MutexGuard aGuard( m_aMutex );
366
0
        xModifyEventForwarder = m_xModifyEventForwarder;
367
0
        tDataPointAttributeContainer::iterator aIt( m_aAttributedDataPoints.find( nIndex ));
368
0
        if( aIt != m_aAttributedDataPoints.end())
369
0
        {
370
0
            xDataPointProp = (*aIt).second;
371
0
            m_aAttributedDataPoints.erase(aIt);
372
0
        }
373
374
0
    }
375
0
    if( xDataPointProp.is() )
376
0
    {
377
0
        Reference< util::XModifyBroadcaster > xBroadcaster( xDataPointProp, uno::UNO_QUERY );
378
0
        if( xBroadcaster.is() && xModifyEventForwarder.is())
379
0
            xBroadcaster->removeModifyListener( xModifyEventForwarder );
380
0
        fireModifyEvent();
381
0
    }
382
0
}
383
384
void SAL_CALL DataSeries::resetAllDataPoints()
385
0
{
386
0
    tDataPointAttributeContainer  aOldAttributedDataPoints;
387
0
    rtl::Reference< ModifyEventForwarder > xModifyEventForwarder;
388
0
    {
389
0
        MutexGuard aGuard( m_aMutex );
390
0
        xModifyEventForwarder = m_xModifyEventForwarder;
391
0
        std::swap( aOldAttributedDataPoints, m_aAttributedDataPoints );
392
0
    }
393
0
    ModifyListenerHelper::removeListenerFromAllMapElements( aOldAttributedDataPoints, xModifyEventForwarder );
394
0
    aOldAttributedDataPoints.clear();
395
0
    fireModifyEvent();
396
0
}
397
398
// ____ XDataSink ____
399
void SAL_CALL DataSeries::setData( const uno::Sequence< Reference< chart2::data::XLabeledDataSequence > >& aData )
400
0
{
401
0
    tDataSequenceContainer aOldDataSequences;
402
0
    tDataSequenceContainer aNewDataSequences;
403
0
    rtl::Reference< ModifyEventForwarder > xModifyEventForwarder;
404
0
    {
405
0
        MutexGuard aGuard( m_aMutex );
406
0
        xModifyEventForwarder = m_xModifyEventForwarder;
407
0
        std::swap( aOldDataSequences, m_aDataSequences );
408
0
        aNewDataSequences.insert(aNewDataSequences.end(), aData.begin(), aData.end());
409
0
        m_aDataSequences = aNewDataSequences;
410
0
    }
411
0
    ModifyListenerHelper::removeListenerFromAllElements( aOldDataSequences, xModifyEventForwarder );
412
0
    ModifyListenerHelper::addListenerToAllElements( aNewDataSequences, xModifyEventForwarder );
413
0
    fireModifyEvent();
414
0
}
415
416
void DataSeries::setData( const std::vector< uno::Reference< chart2::data::XLabeledDataSequence > >& aData )
417
0
{
418
0
    tDataSequenceContainer aOldDataSequences;
419
0
    tDataSequenceContainer aNewDataSequences;
420
0
    rtl::Reference< ModifyEventForwarder > xModifyEventForwarder;
421
0
    {
422
0
        MutexGuard aGuard( m_aMutex );
423
0
        xModifyEventForwarder = m_xModifyEventForwarder;
424
0
        std::swap( aOldDataSequences, m_aDataSequences );
425
0
        aNewDataSequences = aData;
426
0
        m_aDataSequences = aNewDataSequences;
427
0
    }
428
0
    ModifyListenerHelper::removeListenerFromAllElements( aOldDataSequences, xModifyEventForwarder );
429
0
    ModifyListenerHelper::addListenerToAllElements( aNewDataSequences, xModifyEventForwarder );
430
0
    fireModifyEvent();
431
0
}
432
433
// ____ XDataSource ____
434
Sequence< Reference< chart2::data::XLabeledDataSequence > > SAL_CALL DataSeries::getDataSequences()
435
0
{
436
0
    MutexGuard aGuard( m_aMutex );
437
0
    return comphelper::containerToSequence<Reference< chart2::data::XLabeledDataSequence >>( m_aDataSequences );
438
0
}
439
440
// ____ XRegressionCurveContainer ____
441
void SAL_CALL DataSeries::addRegressionCurve(
442
    const uno::Reference< chart2::XRegressionCurve >& xRegressionCurve )
443
0
{
444
0
    auto pRegressionCurve = dynamic_cast<RegressionCurveModel*>(xRegressionCurve.get());
445
0
    assert(pRegressionCurve);
446
0
    rtl::Reference< ModifyEventForwarder > xModifyEventForwarder;
447
0
    {
448
0
        MutexGuard aGuard( m_aMutex );
449
0
        xModifyEventForwarder = m_xModifyEventForwarder;
450
0
        if( std::find( m_aRegressionCurves.begin(), m_aRegressionCurves.end(), pRegressionCurve )
451
0
            != m_aRegressionCurves.end())
452
0
            throw lang::IllegalArgumentException(u"curve not found"_ustr, static_cast<cppu::OWeakObject*>(this), 1);
453
0
        m_aRegressionCurves.push_back( pRegressionCurve );
454
0
    }
455
0
    ModifyListenerHelper::addListener( rtl::Reference<RegressionCurveModel>(pRegressionCurve), xModifyEventForwarder );
456
0
    fireModifyEvent();
457
0
}
458
459
void SAL_CALL DataSeries::removeRegressionCurve(
460
    const uno::Reference< chart2::XRegressionCurve >& xRegressionCurve )
461
0
{
462
0
    if( !xRegressionCurve.is() )
463
0
        throw container::NoSuchElementException();
464
0
    auto pRegressionCurve = dynamic_cast<RegressionCurveModel*>(xRegressionCurve.get());
465
0
    assert(pRegressionCurve);
466
467
0
    rtl::Reference< ModifyEventForwarder > xModifyEventForwarder;
468
0
    {
469
0
        MutexGuard aGuard( m_aMutex );
470
0
        xModifyEventForwarder = m_xModifyEventForwarder;
471
0
        tRegressionCurveContainerType::iterator aIt(
472
0
            std::find( m_aRegressionCurves.begin(), m_aRegressionCurves.end(), pRegressionCurve ) );
473
0
        if( aIt == m_aRegressionCurves.end())
474
0
            throw container::NoSuchElementException(
475
0
                u"The given regression curve is no element of this series"_ustr,
476
0
                static_cast< uno::XWeak * >( this ));
477
0
        m_aRegressionCurves.erase( aIt );
478
0
    }
479
480
0
    ModifyListenerHelper::removeListener( rtl::Reference<RegressionCurveModel>(pRegressionCurve), xModifyEventForwarder );
481
0
    fireModifyEvent();
482
0
}
483
484
uno::Sequence< uno::Reference< chart2::XRegressionCurve > > SAL_CALL DataSeries::getRegressionCurves()
485
0
{
486
0
    MutexGuard aGuard( m_aMutex );
487
0
    return comphelper::containerToSequence<uno::Reference< chart2::XRegressionCurve >>( m_aRegressionCurves );
488
0
}
489
490
void SAL_CALL DataSeries::setRegressionCurves(
491
    const Sequence< Reference< chart2::XRegressionCurve > >& aRegressionCurves )
492
0
{
493
0
    tRegressionCurveContainerType aOldCurves;
494
0
    tRegressionCurveContainerType aNewCurves;
495
0
    for (const auto & i : aRegressionCurves)
496
0
    {
497
0
        auto pRegressionCurve = dynamic_cast<RegressionCurveModel*>(i.get());
498
0
        assert(pRegressionCurve);
499
0
        aNewCurves.push_back(pRegressionCurve);
500
0
    }
501
0
    rtl::Reference< ModifyEventForwarder > xModifyEventForwarder;
502
0
    {
503
0
        MutexGuard aGuard( m_aMutex );
504
0
        xModifyEventForwarder = m_xModifyEventForwarder;
505
0
        std::swap( aOldCurves, m_aRegressionCurves );
506
0
        m_aRegressionCurves = aNewCurves;
507
0
    }
508
0
    ModifyListenerHelper::removeListenerFromAllElements( aOldCurves, xModifyEventForwarder );
509
0
    ModifyListenerHelper::addListenerToAllElements( aNewCurves, xModifyEventForwarder );
510
0
    fireModifyEvent();
511
0
}
512
513
// ____ XModifyBroadcaster ____
514
void SAL_CALL DataSeries::addModifyListener( const Reference< util::XModifyListener >& aListener )
515
0
{
516
0
    m_xModifyEventForwarder->addModifyListener( aListener );
517
0
}
518
519
void SAL_CALL DataSeries::removeModifyListener( const Reference< util::XModifyListener >& aListener )
520
0
{
521
0
    m_xModifyEventForwarder->removeModifyListener( aListener );
522
0
}
523
524
// ____ XModifyListener ____
525
void SAL_CALL DataSeries::modified( const lang::EventObject& aEvent )
526
0
{
527
0
    m_xModifyEventForwarder->modified( aEvent );
528
0
}
529
530
// ____ XEventListener (base of XModifyListener) ____
531
void SAL_CALL DataSeries::disposing( const lang::EventObject& )
532
0
{
533
0
}
534
535
// ____ OPropertySet ____
536
void DataSeries::firePropertyChangeEvent()
537
0
{
538
0
    fireModifyEvent();
539
0
}
540
541
void DataSeries::fireModifyEvent()
542
0
{
543
0
    m_xModifyEventForwarder->modified( lang::EventObject( static_cast< uno::XWeak* >( this )));
544
0
}
545
546
using impl::DataSeries_Base;
547
using ::property::OPropertySet;
548
549
IMPLEMENT_FORWARD_XINTERFACE2( DataSeries, DataSeries_Base, OPropertySet )
550
IMPLEMENT_FORWARD_XTYPEPROVIDER2( DataSeries, DataSeries_Base, OPropertySet )
551
552
// implement XServiceInfo methods basing upon getSupportedServiceNames_Static
553
OUString SAL_CALL DataSeries::getImplementationName()
554
0
{
555
0
    return u"com.sun.star.comp.chart.DataSeries"_ustr;
556
0
}
557
558
sal_Bool SAL_CALL DataSeries::supportsService( const OUString& rServiceName )
559
0
{
560
0
    return cppu::supportsService(this, rServiceName);
561
0
}
562
563
css::uno::Sequence< OUString > SAL_CALL DataSeries::getSupportedServiceNames()
564
0
{
565
0
    return {
566
0
        u"com.sun.star.chart2.DataSeries"_ustr,
567
0
        u"com.sun.star.chart2.DataPointProperties"_ustr,
568
0
        u"com.sun.star.beans.PropertySet"_ustr };
569
0
}
570
571
static Reference< chart2::data::XLabeledDataSequence > lcl_findLSequenceWithOnlyLabel(
572
    const Sequence< Reference< chart2::data::XLabeledDataSequence > > & rDataSequences )
573
0
{
574
0
    Reference< chart2::data::XLabeledDataSequence > xResult;
575
576
0
    for( auto const & labeledData : rDataSequences )
577
0
    {
578
0
        OSL_ENSURE( labeledData.is(), "empty LabeledDataSequence" );
579
        // no values are set but a label exists
580
0
        if( labeledData.is() &&
581
0
            ( ! labeledData->getValues().is() &&
582
0
              labeledData->getLabel().is()))
583
0
        {
584
0
            xResult.set( labeledData );
585
0
            break;
586
0
        }
587
0
    }
588
589
0
    return xResult;
590
0
}
591
592
static OUString lcl_getDataSequenceLabel( const Reference< chart2::data::XDataSequence > & xSequence )
593
0
{
594
0
    OUString aResult;
595
596
0
    Reference< chart2::data::XTextualDataSequence > xTextSeq( xSequence, uno::UNO_QUERY );
597
0
    if( xTextSeq.is())
598
0
    {
599
0
        Sequence< OUString > aSeq( xTextSeq->getTextualData());
600
601
0
        const sal_Int32 nMax = aSeq.getLength() - 1;
602
0
        OUStringBuffer aBuf;
603
604
0
        for( sal_Int32 i = 0; i <= nMax; ++i )
605
0
        {
606
0
            aBuf.append( aSeq[i] );
607
0
            if( i < nMax )
608
0
                aBuf.append( ' ');
609
0
        }
610
0
        aResult = aBuf.makeStringAndClear();
611
0
    }
612
0
    else if( xSequence.is())
613
0
    {
614
0
        Sequence< uno::Any > aSeq( xSequence->getData());
615
616
0
        const sal_Int32 nMax = aSeq.getLength() - 1;
617
0
        OUString aVal;
618
0
        OUStringBuffer aBuf;
619
0
        double fNum = 0;
620
621
0
        for( sal_Int32 i = 0; i <= nMax; ++i )
622
0
        {
623
0
            if( aSeq[i] >>= aVal )
624
0
            {
625
0
                aBuf.append( aVal );
626
0
                if( i < nMax )
627
0
                    aBuf.append(  ' ');
628
0
            }
629
0
            else if( aSeq[ i ] >>= fNum )
630
0
            {
631
0
                aBuf.append( fNum );
632
0
                if( i < nMax )
633
0
                    aBuf.append( ' ');
634
0
            }
635
0
        }
636
0
        aResult = aBuf.makeStringAndClear();
637
0
    }
638
639
0
    return aResult;
640
0
}
641
642
static OUString getLabelForLabeledDataSequence(
643
    const Reference< chart2::data::XLabeledDataSequence > & xLabeledSeq )
644
0
{
645
0
    OUString aResult;
646
0
    if( xLabeledSeq.is())
647
0
    {
648
0
        Reference< chart2::data::XDataSequence > xSeq( xLabeledSeq->getLabel());
649
0
        if( xSeq.is() )
650
0
            aResult = lcl_getDataSequenceLabel( xSeq );
651
0
        if( !xSeq.is() || aResult.isEmpty() )
652
0
        {
653
            // no label set or label content is empty -> use auto-generated one
654
0
            Reference< chart2::data::XDataSequence > xValueSeq( xLabeledSeq->getValues() );
655
0
            if( xValueSeq.is() )
656
0
            {
657
0
                Sequence< OUString > aLabels( xValueSeq->generateLabel(
658
0
                    chart2::data::LabelOrigin_SHORT_SIDE ) );
659
                // no labels returned is interpreted as: auto-generation not
660
                // supported by sequence
661
0
                if( aLabels.hasElements() )
662
0
                    aResult=aLabels[0];
663
0
                else
664
0
                {
665
                    //todo?: maybe use the index of the series as name
666
                    //but as the index may change it would be better to have such a name persistent
667
                    //what is not possible at the moment
668
                    //--> maybe use the identifier as part of the name ...
669
0
                    aResult = lcl_getDataSequenceLabel( xValueSeq );
670
0
                }
671
0
            }
672
0
        }
673
0
    }
674
0
    return aResult;
675
0
}
676
677
OUString DataSeries::getLabelForRole( const OUString & rLabelSequenceRole )
678
0
{
679
0
    OUString aResult;
680
681
0
    Reference< chart2::data::XLabeledDataSequence > xLabeledSeq(
682
0
        ::chart::DataSeriesHelper::getDataSequenceByRole( this, rLabelSequenceRole ));
683
0
    if( xLabeledSeq.is())
684
0
        aResult = getLabelForLabeledDataSequence( xLabeledSeq );
685
0
    else
686
0
    {
687
        // special case: labeled data series with only a label and no values may
688
        // serve as label
689
0
        xLabeledSeq.set( lcl_findLSequenceWithOnlyLabel( getDataSequences() ));
690
0
        if( xLabeledSeq.is())
691
0
        {
692
0
            Reference< chart2::data::XDataSequence > xSeq( xLabeledSeq->getLabel());
693
0
            if( xSeq.is())
694
0
                aResult = lcl_getDataSequenceLabel( xSeq );
695
0
        }
696
0
    }
697
698
0
    return aResult;
699
0
}
700
701
static bool lcl_SequenceHasUnhiddenData( const uno::Reference< chart2::data::XDataSequence >& xDataSequence )
702
0
{
703
0
    if (!xDataSequence.is())
704
0
        return false;
705
0
    uno::Reference< beans::XPropertySet > xProp( xDataSequence, uno::UNO_QUERY );
706
0
    if( xProp.is() )
707
0
    {
708
0
        uno::Sequence< sal_Int32 > aHiddenValues;
709
0
        try
710
0
        {
711
0
            xProp->getPropertyValue( u"HiddenValues"_ustr ) >>= aHiddenValues;
712
0
            if( !aHiddenValues.hasElements() )
713
0
                return true;
714
0
        }
715
0
        catch( const uno::Exception& )
716
0
        {
717
0
            return true;
718
0
        }
719
0
    }
720
0
    return xDataSequence->getData().hasElements();
721
0
}
722
723
bool DataSeries::hasUnhiddenData()
724
0
{
725
0
    MutexGuard aGuard( m_aMutex );
726
727
0
    for(uno::Reference< chart2::data::XLabeledDataSequence > const & rDataSequence : m_aDataSequences)
728
0
    {
729
0
        if( !rDataSequence.is() )
730
0
            continue;
731
0
        if( lcl_SequenceHasUnhiddenData( rDataSequence->getValues() ) )
732
0
            return true;
733
0
    }
734
0
    return false;
735
0
}
736
737
bool DataSeries::hasPointOwnColor(
738
        sal_Int32 nPointIndex
739
        , const css::uno::Reference< css::beans::XPropertySet >& xDataPointProperties //may be NULL this is just for performance
740
         )
741
0
{
742
0
    if( hasPointOwnProperties( nPointIndex ))
743
0
    {
744
0
        uno::Reference< beans::XPropertyState > xPointState( xDataPointProperties, uno::UNO_QUERY );
745
0
        if( !xPointState.is() )
746
0
        {
747
0
            xPointState.set( getDataPointByIndex( nPointIndex ), uno::UNO_QUERY );
748
0
        }
749
0
        if( !xPointState.is() )
750
0
            return false;
751
752
0
        return (xPointState->getPropertyState( u"Color"_ustr) != beans::PropertyState_DEFAULT_VALUE );
753
0
    }
754
755
0
    return false;
756
0
}
757
758
bool DataSeries::hasPointOwnProperties( sal_Int32 nPointIndex )
759
0
{
760
0
    MutexGuard aGuard( m_aMutex );
761
0
    return m_aAttributedDataPoints.contains(nPointIndex);
762
0
}
763
764
sal_Int32 DataSeries::getAttachedAxisIndex()
765
0
{
766
0
    sal_Int32 nRet = 0;
767
0
    try
768
0
    {
769
0
        getFastPropertyValue( ::chart::DataSeriesProperties::PROP_DATASERIES_ATTACHED_AXIS_INDEX ) >>= nRet;
770
0
    }
771
0
    catch( const uno::Exception & )
772
0
    {
773
0
        DBG_UNHANDLED_EXCEPTION("chart2");
774
0
    }
775
0
    return nRet;
776
0
}
777
778
void DataSeries::switchSymbolsOnOrOff( bool bSymbolsOn, sal_Int32 nSeriesIndex )
779
0
{
780
0
    css::chart2::Symbol aSymbProp;
781
0
    if( getPropertyValue( u"Symbol"_ustr) >>= aSymbProp )
782
0
    {
783
0
        if( !bSymbolsOn )
784
0
            aSymbProp.Style = chart2::SymbolStyle_NONE;
785
0
        else if( aSymbProp.Style == chart2::SymbolStyle_NONE )
786
0
        {
787
0
            aSymbProp.Style = chart2::SymbolStyle_STANDARD;
788
0
            aSymbProp.StandardSymbol = nSeriesIndex;
789
0
        }
790
0
        setPropertyValue( u"Symbol"_ustr, uno::Any( aSymbProp ));
791
0
    }
792
    //todo: check attributed data points
793
0
}
794
795
void DataSeries::switchLinesOnOrOff( bool bLinesOn )
796
0
{
797
0
    if( bLinesOn )
798
0
    {
799
        // keep line-styles that are not NONE
800
0
        css::drawing::LineStyle eLineStyle;
801
0
        if( (getPropertyValue( u"LineStyle"_ustr) >>= eLineStyle ) &&
802
0
            eLineStyle == drawing::LineStyle_NONE )
803
0
        {
804
0
            setPropertyValue( u"LineStyle"_ustr, uno::Any( drawing::LineStyle_SOLID ) );
805
0
        }
806
0
    }
807
0
    else
808
0
        setPropertyValue( u"LineStyle"_ustr, uno::Any( drawing::LineStyle_NONE ) );
809
0
}
810
811
void DataSeries::makeLinesThickOrThin( bool bThick )
812
0
{
813
0
    sal_Int32 nNewValue = bThick ? 80 : 0;
814
0
    sal_Int32 nOldValue = 0;
815
0
    if( (getPropertyValue( u"LineWidth"_ustr) >>= nOldValue ) &&
816
0
        nOldValue != nNewValue )
817
0
    {
818
0
        if( !(bThick && nOldValue>0))
819
0
            setPropertyValue( u"LineWidth"_ustr, uno::Any( nNewValue ) );
820
0
    }
821
0
}
822
823
void DataSeries::setPropertyAlsoToAllAttributedDataPoints( const OUString& rPropertyName, const uno::Any& rPropertyValue )
824
0
{
825
0
    setPropertyValue( rPropertyName, rPropertyValue );
826
827
0
    std::vector<sal_Int32> aAttributedDataPointIndexList;
828
0
    {
829
0
        MutexGuard aGuard( m_aMutex );
830
0
        aAttributedDataPointIndexList.reserve(m_aAttributedDataPoints.size());
831
0
        for (const auto & rPair : m_aAttributedDataPoints)
832
0
            aAttributedDataPointIndexList.push_back(rPair.first);
833
0
    }
834
835
0
    for(sal_Int32 nIdx : aAttributedDataPointIndexList)
836
0
    {
837
0
        Reference< beans::XPropertySet > xPointProp( getDataPointByIndex(nIdx) );
838
0
        if(!xPointProp.is())
839
0
            continue;
840
0
        xPointProp->setPropertyValue( rPropertyName, rPropertyValue );
841
0
        if( rPropertyName == "LabelPlacement" )
842
0
        {
843
0
            xPointProp->setPropertyValue(u"CustomLabelPosition"_ustr, uno::Any());
844
0
            xPointProp->setPropertyValue(u"CustomLabelSize"_ustr, uno::Any());
845
0
        }
846
0
    }
847
0
}
848
849
bool DataSeries::hasAttributedDataPointDifferentValue(
850
                                              const OUString& rPropertyName, const uno::Any& rPropertyValue )
851
0
{
852
0
    std::vector<sal_Int32> aAttributedDataPointIndexList;
853
0
    {
854
0
        MutexGuard aGuard( m_aMutex );
855
0
        aAttributedDataPointIndexList.reserve(m_aAttributedDataPoints.size());
856
0
        for (const auto & rPair : m_aAttributedDataPoints)
857
0
            aAttributedDataPointIndexList.push_back(rPair.first);
858
0
    }
859
860
0
    for(sal_Int32 nIdx : aAttributedDataPointIndexList)
861
0
    {
862
0
        Reference< beans::XPropertySet > xPointProp( getDataPointByIndex(nIdx) );
863
0
        if(!xPointProp.is())
864
0
            continue;
865
0
        uno::Any aPointValue( xPointProp->getPropertyValue( rPropertyName ) );
866
0
        if( rPropertyValue != aPointValue )
867
0
            return true;
868
0
    }
869
870
0
    return false;
871
0
}
872
873
bool DataSeries::hasDataLabelsAtSeries()
874
0
{
875
0
    bool bRet = false;
876
0
    try
877
0
    {
878
0
        chart2::DataPointLabel aLabel;
879
0
        if( getPropertyValue(CHART_UNONAME_LABEL) >>= aLabel )
880
0
            bRet = aLabel.ShowNumber || aLabel.ShowNumberInPercent || aLabel.ShowCategoryName
881
0
                   || aLabel.ShowSeriesName;
882
0
    }
883
0
    catch(const uno::Exception &)
884
0
    {
885
0
        TOOLS_WARN_EXCEPTION("chart2", "" );
886
0
    }
887
0
    return bRet;
888
0
}
889
890
bool DataSeries::hasDataLabelsAtPoints()
891
0
{
892
0
    bool bRet = false;
893
0
    try
894
0
    {
895
0
        std::vector<sal_Int32> aAttributedDataPointIndexList;
896
0
        {
897
0
            MutexGuard aGuard( m_aMutex );
898
0
            aAttributedDataPointIndexList.reserve(m_aAttributedDataPoints.size());
899
0
            for (const auto & rPair : m_aAttributedDataPoints)
900
0
                aAttributedDataPointIndexList.push_back(rPair.first);
901
0
        }
902
0
        for(sal_Int32 nIdx : aAttributedDataPointIndexList)
903
0
        {
904
0
            Reference< beans::XPropertySet > xPointProp( getDataPointByIndex(nIdx) );
905
0
            if( xPointProp.is() )
906
0
            {
907
0
                chart2::DataPointLabel aLabel;
908
0
                if( xPointProp->getPropertyValue(CHART_UNONAME_LABEL) >>= aLabel )
909
0
                    bRet = aLabel.ShowNumber || aLabel.ShowNumberInPercent
910
0
                           || aLabel.ShowCategoryName || aLabel.ShowCustomLabel
911
0
                           || aLabel.ShowSeriesName;
912
0
                if( bRet )
913
0
                    break;
914
0
            }
915
0
        }
916
0
    }
917
0
    catch(const uno::Exception &)
918
0
    {
919
0
        TOOLS_WARN_EXCEPTION("chart2", "" );
920
0
    }
921
0
    return bRet;
922
0
}
923
924
bool DataSeries::hasDataLabelAtPoint( sal_Int32 nPointIndex )
925
0
{
926
0
    bool bRet = false;
927
0
    try
928
0
    {
929
0
        Reference< beans::XPropertySet > xProp;
930
0
        bool bFound = false;
931
0
        {
932
0
            MutexGuard aGuard( m_aMutex );
933
0
            bFound = m_aAttributedDataPoints.contains(nPointIndex);
934
0
        }
935
0
        if (bFound)
936
0
            xProp = getDataPointByIndex(nPointIndex);
937
0
        else
938
0
            xProp = this;
939
0
        if( xProp.is() )
940
0
        {
941
0
            chart2::DataPointLabel aLabel;
942
0
            if( xProp->getPropertyValue(CHART_UNONAME_LABEL) >>= aLabel )
943
0
                bRet = aLabel.ShowNumber || aLabel.ShowNumberInPercent
944
0
                       || aLabel.ShowCategoryName || aLabel.ShowCustomLabel
945
0
                       || aLabel.ShowSeriesName;
946
0
        }
947
0
    }
948
0
    catch(const uno::Exception &)
949
0
    {
950
0
        TOOLS_WARN_EXCEPTION("chart2", "" );
951
0
    }
952
0
    return bRet;
953
0
}
954
955
void DataSeries::insertDataLabelsToSeriesAndAllPoints()
956
0
{
957
0
    impl_insertOrDeleteDataLabelsToSeriesAndAllPoints( true /*bInsert*/ );
958
0
}
959
960
void DataSeries::deleteDataLabelsFromSeriesAndAllPoints()
961
0
{
962
0
    impl_insertOrDeleteDataLabelsToSeriesAndAllPoints( false /*bInsert*/ );
963
0
}
964
965
void DataSeries::impl_insertOrDeleteDataLabelsToSeriesAndAllPoints( bool bInsert )
966
0
{
967
0
    try
968
0
    {
969
0
        chart2::DataPointLabel aLabelAtSeries;
970
0
        getPropertyValue(CHART_UNONAME_LABEL) >>= aLabelAtSeries;
971
0
        aLabelAtSeries.ShowNumber = bInsert;
972
0
        if( !bInsert )
973
0
        {
974
0
            aLabelAtSeries.ShowNumberInPercent = false;
975
0
            aLabelAtSeries.ShowCategoryName = false;
976
0
        }
977
0
        setPropertyValue(CHART_UNONAME_LABEL, uno::Any(aLabelAtSeries));
978
0
        std::vector<sal_Int32> aAttributedDataPointIndexList;
979
0
        {
980
0
            MutexGuard aGuard( m_aMutex );
981
0
            aAttributedDataPointIndexList.reserve(m_aAttributedDataPoints.size());
982
0
            for (const auto & rPair : m_aAttributedDataPoints)
983
0
                aAttributedDataPointIndexList.push_back(rPair.first);
984
0
        }
985
0
        for(sal_Int32 nIdx : aAttributedDataPointIndexList)
986
0
        {
987
0
            Reference< beans::XPropertySet > xPointProp( getDataPointByIndex(nIdx) );
988
0
            if( xPointProp.is() )
989
0
            {
990
0
                chart2::DataPointLabel aLabel;
991
0
                xPointProp->getPropertyValue(CHART_UNONAME_LABEL) >>= aLabel;
992
0
                aLabel.ShowNumber = bInsert;
993
0
                if( !bInsert )
994
0
                {
995
0
                    aLabel.ShowNumberInPercent = false;
996
0
                    aLabel.ShowCategoryName = false;
997
0
                    aLabel.ShowCustomLabel = false;
998
0
                    aLabel.ShowSeriesName = false;
999
0
                }
1000
0
                xPointProp->setPropertyValue(CHART_UNONAME_LABEL, uno::Any(aLabel));
1001
0
                xPointProp->setPropertyValue(CHART_UNONAME_CUSTOM_LABEL_FIELDS, uno::Any());
1002
0
            }
1003
0
        }
1004
0
    }
1005
0
    catch(const uno::Exception &)
1006
0
    {
1007
0
        TOOLS_WARN_EXCEPTION("chart2", "" );
1008
0
    }
1009
0
}
1010
1011
sal_Int32 DataSeries::getExplicitNumberFormatKeyForDataLabel()
1012
0
{
1013
0
    sal_Int32 nFormat = 0;
1014
0
    try
1015
0
    {
1016
0
        bool bLinkToSource = true;
1017
0
        getPropertyValue(CHART_UNONAME_LINK_TO_SRC_NUMFMT) >>= bLinkToSource;
1018
0
        getPropertyValue(CHART_UNONAME_NUMFMT) >>= nFormat;
1019
1020
0
        if (bLinkToSource)
1021
0
        {
1022
0
            MutexGuard aGuard( m_aMutex );
1023
1024
0
            if (!m_aDataSequences.empty())
1025
0
            {
1026
0
                Reference<chart2::data::XLabeledDataSequence> xLabeledSeq(m_aDataSequences[0]);
1027
0
                if( xLabeledSeq.is() )
1028
0
                {
1029
0
                    Reference< chart2::data::XDataSequence > xSeq( xLabeledSeq->getValues());
1030
0
                    if( xSeq.is() )
1031
0
                    {
1032
0
                        nFormat = xSeq->getNumberFormatKeyByIndex( -1 );
1033
0
                    }
1034
0
                }
1035
0
            }
1036
0
        }
1037
0
    }
1038
0
    catch (const beans::UnknownPropertyException&)
1039
0
    {
1040
0
    }
1041
1042
0
    if (nFormat < 0)
1043
0
        nFormat = 0;
1044
0
    return nFormat;
1045
1046
0
}
1047
1048
void DataSeries::setCalculatedYSequence(css::uno::Reference<css::chart2::data::XLabeledDataSequence> const& xSequence)
1049
0
{
1050
0
    m_xCalculatedYSequence = xSequence;
1051
0
}
1052
1053
css::uno::Reference<css::chart2::data::XLabeledDataSequence> DataSeries::getCalculatedYSequence() const
1054
0
{
1055
0
    return m_xCalculatedYSequence;
1056
0
}
1057
1058
void DataSeries::clearCalculatedYSequence()
1059
0
{
1060
0
    m_xCalculatedYSequence.clear();
1061
0
}
1062
1063
}  // namespace chart
1064
1065
extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
1066
com_sun_star_comp_chart_DataSeries_get_implementation(css::uno::XComponentContext *,
1067
        css::uno::Sequence<css::uno::Any> const &)
1068
0
{
1069
0
    return cppu::acquire(new ::chart::DataSeries );
1070
0
}
1071
1072
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */