    /* -*- 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 <svl/zformat.hxx>
    #include <svl/zforlist.hxx>

    #include "DataBrowser.hxx"
    #include "DataBrowserModel.hxx"
    #include <strings.hrc>
    #include <DataSeriesHelper.hxx>
    #include <DiagramHelper.hxx>
    #include <ChartModelHelper.hxx>
    #include <CommonConverters.hxx>
    #include <NumberFormatterWrapper.hxx>
    #include <servicenames_charttypes.hxx>
    #include <ResId.hxx>
    #include <bitmaps.hlst>
    #include <helpids.h>

    #include <vcl/fixed.hxx>
    #include <vcl/image.hxx>
    #include <vcl/layout.hxx>
    #include <vcl/weld.hxx>
    #include <vcl/settings.hxx>
    #include <rtl/math.hxx>

    #include <com/sun/star/util/XCloneable.hpp>
    #include <com/sun/star/chart2/XChartDocument.hpp>
    #include <com/sun/star/chart2/XChartType.hpp>

    #include <com/sun/star/container/XIndexReplace.hpp>
    #include <com/sun/star/util/XNumberFormats.hpp>

    #include <algorithm>
    #include <functional>

    using namespace ::com::sun::star;
    using ::com::sun::star::uno::Reference;

    using namespace ::svt;

    namespace
    {
    /*  BrowserMode::COLUMNSELECTION : single cells may be selected rather than only
                                       entire rows
        BrowserMode::(H|V)LINES : show horizontal or vertical grid-lines
        BrowserMode::AUTO_(H|V)SCROLL : scroll automated horizontally or vertically when
                                        cursor is moved beyond the edge of the dialog
        BrowserMode::HIDESELECT : Do not mark the current row with selection color
                                  (usually blue)
      ! BrowserMode::HIDECURSOR would prevent flickering in edit fields, but navigating
            with shift up/down, and entering non-editable cells would be problematic,
            e.g.  the first cell, or when being in read-only mode
    */
    const BrowserMode BrowserStdFlags = BrowserMode::COLUMNSELECTION |
                                        BrowserMode::HLINES | BrowserMode::VLINES |
                                        BrowserMode::AUTO_HSCROLL | BrowserMode::AUTO_VSCROLL |
                                        BrowserMode::HIDESELECT;

    sal_Int32 lcl_getRowInData( long nRow )
    {
        return static_cast< sal_Int32 >( nRow );
    }

    sal_Int32 lcl_getColumnInData( sal_uInt16 nCol )
    {
        return static_cast< sal_Int32 >( nCol ) - 1;
    }

    } // anonymous namespace

    namespace chart
    {

    namespace impl
    {

    class SeriesHeaderEdit : public Edit
    {
    public:
        explicit SeriesHeaderEdit( vcl::Window * pParent );
        virtual void MouseButtonDown( const MouseEvent& rMEvt ) override;

        void setStartColumn( sal_Int32 nStartColumn );
        sal_Int32 getStartColumn() const { return m_nStartColumn;}
        void SetShowWarningBox( bool bShowWarning );

    private:
        sal_Int32 m_nStartColumn;
        bool m_bShowWarningBox;
    };

    SeriesHeaderEdit::SeriesHeaderEdit( vcl::Window * pParent ) :
            Edit( pParent ),
            m_nStartColumn( 0 ),
            m_bShowWarningBox( false )
    {
        SetHelpId(HID_SCH_DATA_SERIES_LABEL);
    }

    void SeriesHeaderEdit::setStartColumn( sal_Int32 nStartColumn )
    {
        m_nStartColumn = nStartColumn;
    }

    void SeriesHeaderEdit::SetShowWarningBox( bool bShowWarning )
    {
        m_bShowWarningBox = bShowWarning;
    }

    void SeriesHeaderEdit::MouseButtonDown( const MouseEvent& rMEvt )
    {
        Edit::MouseButtonDown( rMEvt );

        if( m_bShowWarningBox )
        {
            std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(GetFrameWeld(),
                                                       VclMessageType::Warning, VclButtonsType::Ok,
                                                       SchResId(STR_INVALID_NUMBER)));
            xWarn->run();
        }
    }

    class SeriesHeader
    {
    public:
        explicit SeriesHeader(vcl::Window * pParent, vcl::Window *pColorParent);
                ~SeriesHeader();

        void SetColor( const Color & rCol );
        void SetPos( const Point & rPos );
        void SetWidth( sal_Int32 nWidth );
        void SetChartType( const Reference< chart2::XChartType > & xChartType,
                           bool bSwapXAndYAxis );
        void SetSeriesName( const OUString & rName );
        void SetRange( sal_Int32 nStartCol, sal_Int32 nEndCol );

        void SetPixelWidth( sal_Int32 nWidth );

        sal_Int32 GetStartColumn() const { return m_nStartCol;}
        sal_Int32 GetEndColumn() const { return m_nEndCol;}

        static const sal_Int32 nSymbolHeight = 10;
        static const sal_Int32 nSymbolDistance = 2;

        static sal_Int32 GetRelativeAppFontXPosForNameField() { return nSymbolHeight + nSymbolDistance; }

        void Show();
        void Hide();

        /** call this before destroying the class.  This notifies the listeners to
            changes of the edit field for the series name.
         */
        void applyChanges();

        void SetGetFocusHdl( const Link<Control&,void>& rLink );

        void SetEditChangedHdl( const Link<SeriesHeaderEdit*,void> & rLink );

        bool HasFocus() const;

    private:
        VclPtr< FixedImage >        m_spSymbol;
        VclPtr< SeriesHeaderEdit >  m_spSeriesName;
        VclPtr< FixedText >         m_spColorBar;
        VclPtr< OutputDevice>       m_pDevice;
        Link<SeriesHeaderEdit*,void> m_aChangeLink;

        void notifyChanges();
        DECL_LINK( SeriesNameChanged, Edit&, void );
        DECL_LINK( SeriesNameEdited, Edit&, void );

        static Image GetChartTypeImage(
            const Reference< chart2::XChartType > & xChartType,
            bool bSwapXAndYAxis
            );

        sal_Int32 m_nStartCol, m_nEndCol;
        sal_Int32 m_nWidth;
        Point     m_aPos;
        bool      m_bSeriesNameChangePending;
    };

    SeriesHeader::SeriesHeader( vcl::Window * pParent, vcl::Window *pColorParent ) :
            m_spSymbol( VclPtr<FixedImage>::Create( pParent, WB_NOBORDER )),
            m_spSeriesName( VclPtr<SeriesHeaderEdit>::Create( pParent )),
            m_spColorBar( VclPtr<FixedText>::Create( pColorParent, WB_NOBORDER )),
            m_pDevice( pParent ),
            m_nStartCol( 0 ),
            m_nEndCol( 0 ),
            m_nWidth( 42 ),
            m_aPos( 0, 22 ),
            m_bSeriesNameChangePending( false )
    {
        m_spSeriesName->EnableUpdateData( 4 * EDIT_UPDATEDATA_TIMEOUT ); // define is in vcl/edit.hxx
        m_spSeriesName->SetUpdateDataHdl( LINK( this, SeriesHeader, SeriesNameChanged ));
        m_spSeriesName->SetModifyHdl( LINK( this, SeriesHeader, SeriesNameEdited ));
        Show();
    }

    SeriesHeader::~SeriesHeader()
    {
        m_spSymbol.disposeAndClear();
        m_spSeriesName.disposeAndClear();
        m_spColorBar.disposeAndClear();
    }

    void SeriesHeader::notifyChanges()
    {
        m_aChangeLink.Call( m_spSeriesName.get());
        m_bSeriesNameChangePending = false;
    }

    void SeriesHeader::applyChanges()
    {
        if( m_bSeriesNameChangePending )
        {
            notifyChanges();
        }
    }

    void SeriesHeader::SetColor( const Color & rCol )
    {
        m_spColorBar->SetControlBackground( rCol );
    }

    void SeriesHeader::SetPos( const Point & rPos )
    {
        m_aPos = rPos;

        // chart type symbol
        Size aSize( nSymbolHeight, nSymbolHeight );
        aSize = m_pDevice->LogicToPixel(aSize, MapMode(MapUnit::MapAppFont));
        m_spSymbol->set_width_request(aSize.Width());
        m_spSymbol->set_height_request(aSize.Height());

        // series name edit field
        aSize.setWidth(nSymbolDistance);
        aSize = m_pDevice->LogicToPixel(aSize, MapMode(MapUnit::MapAppFont));
        m_spSeriesName->set_margin_left(aSize.Width() + 2);
        aSize.setWidth( m_nWidth - nSymbolHeight - nSymbolDistance );
        sal_Int32 nHeight = 12;
        aSize.setHeight( nHeight );
        aSize = m_pDevice->LogicToPixel(aSize, MapMode(MapUnit::MapAppFont));
        m_spSeriesName->set_width_request(aSize.Width());
        m_spSeriesName->set_height_request(aSize.Height());

        // color bar
        aSize.setWidth(1);
        aSize = m_pDevice->LogicToPixel(aSize, MapMode(MapUnit::MapAppFont));
        m_spColorBar->set_margin_left(aSize.Width() + 2);
        nHeight = 3;
        aSize.setWidth( m_nWidth - 1 );
        aSize.setHeight( nHeight );
        aSize = m_pDevice->LogicToPixel(aSize, MapMode(MapUnit::MapAppFont));
        m_spColorBar->set_width_request(aSize.Width());
        m_spColorBar->set_height_request(aSize.Height());
    }

    void SeriesHeader::SetWidth( sal_Int32 nWidth )
    {
        m_nWidth = nWidth;
        SetPos( m_aPos );
    }

    void SeriesHeader::SetPixelWidth( sal_Int32 nWidth )
    {
        SetWidth( m_pDevice->PixelToLogic(Size(nWidth, 0), MapMode(MapUnit::MapAppFont)).getWidth());
    }

    void SeriesHeader::SetChartType(
        const Reference< chart2::XChartType > & xChartType,
        bool bSwapXAndYAxis
    )
    {
        m_spSymbol->SetImage( GetChartTypeImage( xChartType, bSwapXAndYAxis ) );
    }

    void SeriesHeader::SetSeriesName( const OUString & rName )
    {
        m_spSeriesName->SetText( rName );
    }

    void SeriesHeader::SetRange( sal_Int32 nStartCol, sal_Int32 nEndCol )
    {
        m_nStartCol = nStartCol;
        m_nEndCol = std::max(nEndCol, nStartCol);
        m_spSeriesName->setStartColumn( nStartCol );
    }

    void SeriesHeader::Show()
    {
        m_spSymbol->Show();
        m_spSeriesName->Show();
        m_spColorBar->Show();
    }

    void SeriesHeader::Hide()
    {
        m_spSymbol->Hide();
        m_spSeriesName->Hide();
        m_spColorBar->Hide();
    }

    void SeriesHeader::SetEditChangedHdl( const Link<SeriesHeaderEdit*,void> & rLink )
    {
        m_aChangeLink = rLink;
    }

    IMPL_LINK_NOARG(SeriesHeader, SeriesNameChanged, Edit&, void)
    {
        notifyChanges();
    }

    IMPL_LINK_NOARG(SeriesHeader, SeriesNameEdited, Edit&, void)
    {
        m_bSeriesNameChangePending = true;
    }

    void SeriesHeader::SetGetFocusHdl( const Link<Control&,void>& rLink )
    {
        m_spSeriesName->SetGetFocusHdl( rLink );
    }

    bool SeriesHeader::HasFocus() const
    {
        return m_spSeriesName->HasFocus();
    }

    Image SeriesHeader::GetChartTypeImage(
        const Reference< chart2::XChartType > & xChartType,
        bool bSwapXAndYAxis
    )
    {
        Image aResult;
        if( !xChartType.is())
            return aResult;
        OUString aChartTypeName( xChartType->getChartType());

        if( aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_AREA )
        {
            aResult = Image(BitmapEx(BMP_TYPE_AREA));
        }
        else if( aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_COLUMN )
        {
            if( bSwapXAndYAxis )
                aResult = Image(BitmapEx(BMP_TYPE_BAR));
            else
                aResult = Image(BitmapEx(BMP_TYPE_COLUMN));
        }
        else if( aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_LINE )
        {
            aResult = Image(BitmapEx(BMP_TYPE_LINE));
        }
        else if( aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_SCATTER )
        {
            aResult = Image(BitmapEx(BMP_TYPE_XY));
        }
        else if( aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_PIE )
        {
            aResult = Image(BitmapEx(BMP_TYPE_PIE));
        }
        else if( aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_NET
              || aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_FILLED_NET )
        {
            aResult = Image(BitmapEx(BMP_TYPE_NET));
        }
        else if( aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_CANDLESTICK )
        {
            // @todo: correct image for candle-stick type
            aResult = Image(BitmapEx(BMP_TYPE_STOCK));
        }
        else if( aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_BUBBLE )
        {
            aResult = Image(BitmapEx(BMP_TYPE_BUBBLE));
        }

        return aResult;
    }

    } // namespace impl

    namespace
    {

    /** returns false, if no header as the focus.

        If a header has the focus, true is returned and the index of the header
        with focus is set at pIndex if pOutIndex is not 0.
    */
    bool lcl_SeriesHeaderHasFocus(
        const std::vector< std::shared_ptr< ::chart::impl::SeriesHeader > > & rSeriesHeader,
        sal_Int32 * pOutIndex = nullptr )
    {
        sal_Int32 nIndex = 0;
        for (auto const& elem : rSeriesHeader)
        {
            if(elem->HasFocus())
            {
                if( pOutIndex )
                    *pOutIndex = nIndex;
                return true;
            }
            ++nIndex;
        }
        return false;
    }

    sal_Int32 lcl_getColumnInDataOrHeader(
        sal_uInt16 nCol, const std::vector< std::shared_ptr< ::chart::impl::SeriesHeader > > & rSeriesHeader )
    {
        sal_Int32 nColIdx = 0;
        bool bHeaderHasFocus( lcl_SeriesHeaderHasFocus( rSeriesHeader, &nColIdx ));

        if( bHeaderHasFocus )
            nColIdx = lcl_getColumnInData( static_cast< sal_uInt16 >( rSeriesHeader[nColIdx]->GetStartColumn()));
        else
            nColIdx = lcl_getColumnInData( nCol );

        return nColIdx;
    }

    } // anonymous namespace

    DataBrowser::DataBrowser( vcl::Window* pParent, WinBits nStyle, bool bLiveUpdate ) :
        ::svt::EditBrowseBox( pParent, EditBrowseBoxFlags::SMART_TAB_TRAVEL | EditBrowseBoxFlags::HANDLE_COLUMN_TEXT, nStyle, BrowserStdFlags ),
        m_nSeekRow( 0 ),
        m_bIsReadOnly( false ),
        m_bLiveUpdate( bLiveUpdate ),
        m_bDataValid( true ),
        m_aNumberEditField( VclPtr<FormattedField>::Create( & EditBrowseBox::GetDataWindow(), WB_NOBORDER ) ),
        m_aTextEditField( VclPtr<Edit>::Create( & EditBrowseBox::GetDataWindow(), WB_NOBORDER ) ),
        m_rNumberEditController( new ::svt::FormattedFieldCellController( m_aNumberEditField.get() )),
        m_rTextEditController( new ::svt::EditCellController( m_aTextEditField.get() ))
    {
        double fNan;
        ::rtl::math::setNan( & fNan );
        m_aNumberEditField->SetDefaultValue( fNan );
        m_aNumberEditField->TreatAsNumber( true );
        RenewTable();
    }

    DataBrowser::~DataBrowser()
    {
        disposeOnce();
    }

    void DataBrowser::dispose()
    {
        m_aNumberEditField.disposeAndClear();
        m_aTextEditField.disposeAndClear();
        ::svt::EditBrowseBox::dispose();
    }

    bool DataBrowser::MayInsertRow() const
    {
        return ! IsReadOnly()
            && ( !lcl_SeriesHeaderHasFocus( m_aSeriesHeaders ));
    }

    bool DataBrowser::MayInsertColumn() const
    {
        return ! IsReadOnly();
    }

    bool DataBrowser::MayDeleteRow() const
    {
        return ! IsReadOnly()
            && ( !lcl_SeriesHeaderHasFocus( m_aSeriesHeaders ))
            && ( GetCurRow() >= 0 )
            && ( GetRowCount() > 1 );
    }

    bool DataBrowser::MayDeleteColumn() const
    {
        // if a series header has the focus
        if( lcl_SeriesHeaderHasFocus( m_aSeriesHeaders ))
            return true;

        return ! IsReadOnly()
            && ( GetCurColumnId() > 1 )
            && ( ColCount() > 2 );
    }

    bool DataBrowser::MayMoveUpRows() const
    {
        return ! IsReadOnly()
            && ( !lcl_SeriesHeaderHasFocus( m_aSeriesHeaders ))
            && ( GetCurRow() > 0 )
            && ( GetCurRow() <= GetRowCount() - 1 );
    }

    bool DataBrowser::MayMoveDownRows() const
    {
        return ! IsReadOnly()
            && ( !lcl_SeriesHeaderHasFocus( m_aSeriesHeaders ))
            && ( GetCurRow() >= 0 )
            && ( GetCurRow() < GetRowCount() - 1 );
    }

    bool DataBrowser::MayMoveLeftColumns() const
    {
        // if a series header (except the last one) has the focus
        {
            sal_Int32 nColIndex(0);
            if( lcl_SeriesHeaderHasFocus( m_aSeriesHeaders, &nColIndex ))
                return (static_cast< sal_uInt32 >( nColIndex ) <= (m_aSeriesHeaders.size() - 1)) && (static_cast< sal_uInt32 >( nColIndex ) != 0);
        }

        sal_Int32 nColIdx = lcl_getColumnInDataOrHeader( GetCurColumnId(), m_aSeriesHeaders );
        return ! IsReadOnly()
            && ( nColIdx > 1 )
            && ( nColIdx <= ColCount() - 2 )
            && m_apDataBrowserModel.get()
            && !m_apDataBrowserModel->isCategoriesColumn( nColIdx );
    }

    bool DataBrowser::MayMoveRightColumns() const
    {
        // if a series header (except the last one) has the focus
        {
            sal_Int32 nColIndex(0);
            if( lcl_SeriesHeaderHasFocus( m_aSeriesHeaders, &nColIndex ))
                return (static_cast< sal_uInt32 >( nColIndex ) < (m_aSeriesHeaders.size() - 1));
        }

        sal_Int32 nColIdx = lcl_getColumnInDataOrHeader( GetCurColumnId(), m_aSeriesHeaders );
        return ! IsReadOnly()
            && ( nColIdx > 0 )
            && ( nColIdx < ColCount()-2 )
            && m_apDataBrowserModel.get()
            && !m_apDataBrowserModel->isCategoriesColumn( nColIdx );
    }

    void DataBrowser::clearHeaders()
    {
        for( const auto& spHeader : m_aSeriesHeaders )
            spHeader->applyChanges();
        m_aSeriesHeaders.clear();
    }

    void DataBrowser::RenewTable()
    {
        if( ! m_apDataBrowserModel.get())
            return;

        long   nOldRow     = GetCurRow();
        sal_uInt16 nOldColId   = GetCurColumnId();

        bool bLastUpdateMode = GetUpdateMode();
        SetUpdateMode( false );

        if( IsModified() )
            SaveModified();

        DeactivateCell();

        RemoveColumns();
        RowRemoved( 1, GetRowCount() );

        // for row numbers
        InsertHandleColumn( static_cast< sal_uInt16 >(
                                GetDataWindow().LogicToPixel( Size( 42, 0 )).getWidth() ));

        OUString aDefaultSeriesName(SchResId(STR_COLUMN_LABEL));
        replaceParamterInString( aDefaultSeriesName, "%COLUMNNUMBER", OUString::number( 24 ) );
        sal_Int32 nColumnWidth = GetDataWindow().GetTextWidth( aDefaultSeriesName )
            + GetDataWindow().LogicToPixel(Point(4 + impl::SeriesHeader::GetRelativeAppFontXPosForNameField(), 0), MapMode(MapUnit::MapAppFont)).X();
        sal_Int32 nColumnCount = m_apDataBrowserModel->getColumnCount();
        // nRowCount is a member of a base class
        sal_Int32 nRowCountLocal = m_apDataBrowserModel->getMaxRowCount();
        for( sal_Int32 nColIdx=1; nColIdx<=nColumnCount; ++nColIdx )
        {
            InsertDataColumn( static_cast< sal_uInt16 >( nColIdx ), GetColString( nColIdx ), nColumnWidth );
        }

        RowInserted( 1, nRowCountLocal );
        GoToRow( std::min( nOldRow, GetRowCount() - 1 ));
        GoToColumnId( std::min( nOldColId, static_cast< sal_uInt16 >( ColCount() - 1 )));

        Dialog* pDialog = GetParentDialog();
        vcl::Window* pWin = pDialog->get<VclContainer>("columns");
        vcl::Window* pColorWin = pDialog->get<VclContainer>("colorcolumns");

        // fill series headers
        clearHeaders();
        const DataBrowserModel::tDataHeaderVector& aHeaders( m_apDataBrowserModel->getDataHeaders());
        Link<Control&,void> aFocusLink( LINK( this, DataBrowser, SeriesHeaderGotFocus ));
        Link<impl::SeriesHeaderEdit*,void> aSeriesHeaderChangedLink( LINK( this, DataBrowser, SeriesHeaderChanged ));

        for (auto const& elemHeader : aHeaders)
        {
            std::shared_ptr< impl::SeriesHeader > spHeader( new impl::SeriesHeader( pWin, pColorWin ));
            Reference< beans::XPropertySet > xSeriesProp( elemHeader.m_xDataSeries, uno::UNO_QUERY );
            sal_Int32 nColor = 0;
            // @todo: Set "DraftColor", i.e. interpolated colors for gradients, bitmaps, etc.
            if( xSeriesProp.is() &&
                ( xSeriesProp->getPropertyValue( "Color" ) >>= nColor ))
                spHeader->SetColor( Color( nColor ));
            spHeader->SetChartType( elemHeader.m_xChartType, elemHeader.m_bSwapXAndYAxis );
            spHeader->SetSeriesName(
                DataSeriesHelper::getDataSeriesLabel(
                            elemHeader.m_xDataSeries,
                            (elemHeader.m_xChartType.is() ?
                             elemHeader.m_xChartType->getRoleOfSequenceForSeriesLabel() :
                             OUString("values-y"))));
            // index is 1-based, as 0 is for the column that contains the row-numbers
            spHeader->SetRange( elemHeader.m_nStartColumn + 1, elemHeader.m_nEndColumn + 1 );
            spHeader->SetGetFocusHdl( aFocusLink );
            spHeader->SetEditChangedHdl( aSeriesHeaderChangedLink );
            m_aSeriesHeaders.push_back( spHeader );
        }

        ImplAdjustHeaderControls();
        SetUpdateMode( bLastUpdateMode );
        ActivateCell();
        Invalidate();
    }

    OUString DataBrowser::GetColString( sal_Int32 nColumnId ) const
    {
        OSL_ASSERT( m_apDataBrowserModel.get());
        if( nColumnId > 0 )
            return m_apDataBrowserModel->getRoleOfColumn( nColumnId - 1 );
        return OUString();
    }

    OUString DataBrowser::GetCellText( long nRow, sal_uInt16 nColumnId ) const
    {
        OUString aResult;

        if( nColumnId == 0 )
        {
            aResult = OUString::number(static_cast< sal_Int32 >( nRow ) + 1);
        }
        else if( nRow >= 0 && m_apDataBrowserModel.get())
        {
            sal_Int32 nColIndex = static_cast< sal_Int32 >( nColumnId ) - 1;

            if( m_apDataBrowserModel->getCellType( nColIndex ) == DataBrowserModel::NUMBER )
            {
                double fData( m_apDataBrowserModel->getCellNumber( nColIndex, nRow ));
                Color nLabelColor;

                if( ! ::rtl::math::isNan( fData ) &&
                    m_spNumberFormatterWrapper.get() )
                {
                    bool bColorChanged = false;
                    aResult = m_spNumberFormatterWrapper->getFormattedString(
                                          GetNumberFormatKey( nColumnId ),
                                          fData, nLabelColor, bColorChanged );
                }
            }
            else if( m_apDataBrowserModel->getCellType( nColIndex ) == DataBrowserModel::TEXTORDATE )
            {
                uno::Any aAny = m_apDataBrowserModel->getCellAny( nColIndex, nRow );
                OUString aText;
                double fDouble=0.0;
                if( aAny>>=aText )
                    aResult = aText;
                else if( aAny>>=fDouble )
                {
                    if( ! ::rtl::math::isNan( fDouble ) && m_spNumberFormatterWrapper.get() )
                    {
                        // If a numberformat was available here we could directly
                        // obtain the corresponding edit format in
                        // getDateTimeInputNumberFormat() instead of doing the
                        // guess work.
                        sal_Int32 nNumberFormat = DiagramHelper::getDateTimeInputNumberFormat(
                                Reference< util::XNumberFormatsSupplier >( m_xChartDoc, uno::UNO_QUERY), fDouble );
                        Color nLabelColor;
                        bool bColorChanged = false;
                        aResult = m_spNumberFormatterWrapper->getFormattedString(
                            nNumberFormat, fDouble, nLabelColor, bColorChanged );
                    }
                }
            }
            else
            {
                OSL_ASSERT( m_apDataBrowserModel->getCellType( nColIndex ) == DataBrowserModel::TEXT );
                aResult = m_apDataBrowserModel->getCellText( nColIndex, nRow );
            }
        }

        return aResult;
    }

    double DataBrowser::GetCellNumber( long nRow, sal_uInt16 nColumnId ) const
    {
        double fResult;
        ::rtl::math::setNan( & fResult );

        if(( nColumnId >= 1 ) && ( nRow >= 0 ) &&
            m_apDataBrowserModel.get())
        {
            fResult = m_apDataBrowserModel->getCellNumber(
                static_cast< sal_Int32 >( nColumnId ) - 1, nRow );
        }

        return fResult;
    }

    void DataBrowser::Resize()
    {
        bool bLastUpdateMode = GetUpdateMode();
        SetUpdateMode( false );

        ::svt::EditBrowseBox::Resize();
        ImplAdjustHeaderControls();
        SetUpdateMode( bLastUpdateMode );
    }

    void DataBrowser::SetReadOnly( bool bNewState )
    {
        if( m_bIsReadOnly != bNewState )
        {
            m_bIsReadOnly = bNewState;
            Invalidate();
            DeactivateCell();
        }
    }

    void DataBrowser::CursorMoved()
    {
        EditBrowseBox::CursorMoved();

        if( GetUpdateMode() )
            m_aCursorMovedHdlLink.Call( this );
    }

    void DataBrowser::MouseButtonDown( const BrowserMouseEvent& rEvt )
    {
        if( !m_bDataValid )
            ShowWarningBox();
        else
            EditBrowseBox::MouseButtonDown( rEvt );
    }

    void DataBrowser::ShowWarningBox()
    {
        std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(GetFrameWeld(),
                                                   VclMessageType::Warning, VclButtonsType::Ok,
                                                   SchResId(STR_INVALID_NUMBER)));
        xWarn->run();
    }

    bool DataBrowser::ShowQueryBox()
    {
        std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(GetFrameWeld(),
                                                       VclMessageType::Question, VclButtonsType::YesNo,
                                                       SchResId(STR_DATA_EDITOR_INCORRECT_INPUT)));
        return xQueryBox->run() == RET_YES;
    }

    bool DataBrowser::IsDataValid()
    {
        bool bValid = true;
        const sal_Int32 nCol = lcl_getColumnInData( GetCurColumnId());

        if( m_apDataBrowserModel->getCellType( nCol ) == DataBrowserModel::NUMBER )
        {
            sal_uInt32 nDummy = 0;
            double fDummy = 0.0;
            OUString aText( m_aNumberEditField->GetText());

            if( !aText.isEmpty() &&
                m_spNumberFormatterWrapper.get() &&
                m_spNumberFormatterWrapper->getSvNumberFormatter() &&
                ! m_spNumberFormatterWrapper->getSvNumberFormatter()->IsNumberFormat(
                  aText, nDummy, fDummy ))
            {
                bValid = false;
            }
        }

        return bValid;
    }

    void DataBrowser::CellModified()
    {
        m_bDataValid = IsDataValid();
        m_aCursorMovedHdlLink.Call( this );
    }

    void DataBrowser::SetDataFromModel(
        const Reference< chart2::XChartDocument > & xChartDoc,
        const Reference< uno::XComponentContext > & xContext )
    {
        if( m_bLiveUpdate )
        {
            m_xChartDoc.set( xChartDoc );
        }
        else
        {
            Reference< util::XCloneable > xCloneable( xChartDoc, uno::UNO_QUERY );
            if( xCloneable.is())
                m_xChartDoc.set( xCloneable->createClone(), uno::UNO_QUERY );
        }

        m_apDataBrowserModel.reset( new DataBrowserModel( m_xChartDoc, xContext ));
        m_spNumberFormatterWrapper.reset(
            new NumberFormatterWrapper(
                Reference< util::XNumberFormatsSupplier >( m_xChartDoc, uno::UNO_QUERY )));

        if( m_spNumberFormatterWrapper.get() )
            m_aNumberEditField->SetFormatter( m_spNumberFormatterWrapper->getSvNumberFormatter() );

        RenewTable();

        const sal_Int32 nColCnt  = m_apDataBrowserModel->getColumnCount();
        const sal_Int32 nRowCnt =  m_apDataBrowserModel->getMaxRowCount();
        if( nRowCnt && nColCnt )
        {
            GoToRow( 0 );
            GoToColumnId( 1 );
        }
    }

    void DataBrowser::InsertColumn()
    {
        sal_Int32 nColIdx = lcl_getColumnInDataOrHeader( GetCurColumnId(), m_aSeriesHeaders );

        if( nColIdx >= 0 &&
            m_apDataBrowserModel.get())
        {
            // save changes made to edit-field
            if( IsModified() )
                SaveModified();

            m_apDataBrowserModel->insertDataSeries( nColIdx );
            RenewTable();
        }
    }

    void DataBrowser::InsertTextColumn()
    {
        sal_Int32 nColIdx = lcl_getColumnInDataOrHeader( GetCurColumnId(), m_aSeriesHeaders );

        if( nColIdx >= 0 &&
            m_apDataBrowserModel.get())
        {
            // save changes made to edit-field
            if( IsModified() )
                SaveModified();

            m_apDataBrowserModel->insertComplexCategoryLevel( nColIdx );
            RenewTable();
        }
    }

    void DataBrowser::RemoveColumn()
    {
        sal_Int32 nColIdx = lcl_getColumnInDataOrHeader( GetCurColumnId(), m_aSeriesHeaders );

        if( nColIdx >= 0 &&
            m_apDataBrowserModel.get())
        {
            // save changes made to edit-field
            if( IsModified() )
                SaveModified();

            m_bDataValid = true;
            m_apDataBrowserModel->removeDataSeriesOrComplexCategoryLevel( nColIdx );
            RenewTable();
        }
    }

    void DataBrowser::InsertRow()
    {
         sal_Int32 nRowIdx = lcl_getRowInData( GetCurRow());

         if( nRowIdx >= 0 &&
            m_apDataBrowserModel.get())
        {
            // save changes made to edit-field
            if( IsModified() )
                SaveModified();

            m_apDataBrowserModel->insertDataPointForAllSeries( nRowIdx );
            RenewTable();
        }
    }

    void DataBrowser::RemoveRow()
    {
         sal_Int32 nRowIdx = lcl_getRowInData( GetCurRow());

         if( nRowIdx >= 0 &&
            m_apDataBrowserModel.get())
        {
            // save changes made to edit-field
            if( IsModified() )
                SaveModified();

            m_bDataValid = true;
            m_apDataBrowserModel->removeDataPointForAllSeries( nRowIdx );
            RenewTable();
        }
    }

    void DataBrowser::MoveLeftColumn()
    {
        sal_Int32 nColIdx = lcl_getColumnInDataOrHeader( GetCurColumnId(), m_aSeriesHeaders );

        if( nColIdx > 0 &&
            m_apDataBrowserModel.get())
        {
            // save changes made to edit-field
            if( IsModified() )
                SaveModified();

            m_apDataBrowserModel->swapDataSeries( nColIdx - 1 );

            // keep cursor in swapped column
            if(( 0 < GetCurColumnId() ) && ( GetCurColumnId() <= ColCount() - 1 ))
            {
                Dispatch( BROWSER_CURSORLEFT );
            }
            RenewTable();
        }
    }

    void DataBrowser::MoveRightColumn()
    {
        sal_Int32 nColIdx = lcl_getColumnInDataOrHeader( GetCurColumnId(), m_aSeriesHeaders );

        if( nColIdx >= 0 &&
            m_apDataBrowserModel.get())
        {
            // save changes made to edit-field
            if( IsModified() )
                SaveModified();

            m_apDataBrowserModel->swapDataSeries( nColIdx );

            // keep cursor in swapped column
            if( GetCurColumnId() < ColCount() - 1 )
            {
                Dispatch( BROWSER_CURSORRIGHT );
            }
            RenewTable();
        }
    }

    void DataBrowser::MoveUpRow()
    {
         sal_Int32 nRowIdx = lcl_getRowInData( GetCurRow());

         if( nRowIdx > 0 &&
            m_apDataBrowserModel.get())
        {
            // save changes made to edit-field
            if( IsModified() )
                SaveModified();

            m_apDataBrowserModel->swapDataPointForAllSeries( nRowIdx - 1 );

            // keep cursor in swapped row
            if(( 0 < GetCurRow() ) && ( GetCurRow() <= GetRowCount() - 1 ))
            {
                Dispatch( BROWSER_CURSORUP );
            }
            RenewTable();
        }
    }

    void DataBrowser::MoveDownRow()
    {
         sal_Int32 nRowIdx = lcl_getRowInData( GetCurRow());

         if( nRowIdx >= 0 &&
            m_apDataBrowserModel.get())
        {
            // save changes made to edit-field
            if( IsModified() )
                SaveModified();

            m_apDataBrowserModel->swapDataPointForAllSeries( nRowIdx );

            // keep cursor in swapped row
            if( GetCurRow() < GetRowCount() - 1 )
            {
                Dispatch( BROWSER_CURSORDOWN );
            }
            RenewTable();
        }
    }

    void DataBrowser::SetCursorMovedHdl( const Link<DataBrowser*,void>& rLink )
    {
        m_aCursorMovedHdlLink = rLink;
    }

    // implementations for ::svt::EditBrowseBox (pure virtual methods)
    void DataBrowser::PaintCell(
        OutputDevice& rDev, const tools::Rectangle& rRect, sal_uInt16 nColumnId ) const
    {
        Point aPos( rRect.TopLeft());
        aPos.AdjustX(1 );

        OUString aText = GetCellText( m_nSeekRow, nColumnId );
        Size TxtSize( GetDataWindow().GetTextWidth( aText ), GetDataWindow().GetTextHeight());

        // clipping
        if( aPos.X() < rRect.Right() || aPos.X() + TxtSize.Width() > rRect.Right() ||
            aPos.Y() < rRect.Top() || aPos.Y() + TxtSize.Height() > rRect.Bottom())
            rDev.SetClipRegion(vcl::Region(rRect));

        // allow for a disabled control ...
        bool bEnabled = IsEnabled();
        Color aOriginalColor = rDev.GetTextColor();
        if( ! bEnabled )
            rDev.SetTextColor( GetSettings().GetStyleSettings().GetDisableColor() );

        // draw the text
        rDev.DrawText( aPos, aText );

        // reset the color (if necessary)
        if( ! bEnabled )
            rDev.SetTextColor( aOriginalColor );

        if( rDev.IsClipRegion())
            rDev.SetClipRegion();
    }

    bool DataBrowser::SeekRow( long nRow )
    {
        if( ! EditBrowseBox::SeekRow( nRow ))
            return false;

        if( nRow < 0 )
            m_nSeekRow = - 1;
        else
            m_nSeekRow = nRow;

        return true;
    }

    bool DataBrowser::IsTabAllowed( bool bForward ) const
    {
        long nRow = GetCurRow();
        long nCol = GetCurColumnId();

        // column 0 is header-column
        long nBadCol = bForward
            ? GetColumnCount() - 1
            : 1;
        long nBadRow = bForward
            ? GetRowCount() - 1
            : 0;

        if( !m_bDataValid )
        {
            const_cast< DataBrowser* >( this )->ShowWarningBox();
            return false;
        }

        return ( nRow != nBadRow ||
                 nCol != nBadCol );
    }

    ::svt::CellController* DataBrowser::GetController( long /*nRow*/, sal_uInt16 nCol )
    {
        if( m_bIsReadOnly )
            return nullptr;

        if( CellContainsNumbers( nCol ))
        {
            m_aNumberEditField->UseInputStringForFormatting();
            m_aNumberEditField->SetFormatKey( GetNumberFormatKey( nCol ));
            return m_rNumberEditController.get();
        }

        return m_rTextEditController.get();
    }

    void DataBrowser::InitController(
        ::svt::CellControllerRef& rController, long nRow, sal_uInt16 nCol )
    {
        if( rController == m_rTextEditController )
        {
            OUString aText( GetCellText( nRow, nCol ) );
            m_aTextEditField->SetText( aText );
            m_aTextEditField->SetSelection( Selection( 0, aText.getLength() ));
        }
        else if( rController == m_rNumberEditController )
        {
            // treat invalid and empty text as Nan
            m_aNumberEditField->EnableNotANumber( true );
            if( ::rtl::math::isNan( GetCellNumber( nRow, nCol )))
                m_aNumberEditField->SetTextValue( OUString());
            else
                m_aNumberEditField->SetValue( GetCellNumber( nRow, nCol ) );
            OUString aText( m_aNumberEditField->GetText());
            m_aNumberEditField->SetSelection( Selection( 0, aText.getLength()));
        }
        else
        {
            OSL_FAIL( "Invalid Controller" );
        }
    }

    bool DataBrowser::CellContainsNumbers( sal_uInt16 nCol ) const
    {
        if( ! m_apDataBrowserModel.get())
            return false;
        return m_apDataBrowserModel->getCellType( lcl_getColumnInData( nCol )) == DataBrowserModel::NUMBER;
    }

    sal_uInt32 DataBrowser::GetNumberFormatKey( sal_uInt16 nCol ) const
    {
        if( ! m_apDataBrowserModel.get())
            return 0;
        return m_apDataBrowserModel->getNumberFormatKey( lcl_getColumnInData( nCol ) );
    }

    bool DataBrowser::isDateTimeString( const OUString& aInputString, double& fOutDateTimeValue )
    {
        sal_uInt32 nNumberFormat=0;
        SvNumberFormatter* pSvNumberFormatter = m_spNumberFormatterWrapper.get() ? m_spNumberFormatterWrapper->getSvNumberFormatter() : nullptr;
        if( !aInputString.isEmpty() &&  pSvNumberFormatter && pSvNumberFormatter->IsNumberFormat( aInputString, nNumberFormat, fOutDateTimeValue ) )
        {
            SvNumFormatType nType = pSvNumberFormatter->GetType( nNumberFormat);
            return (nType & SvNumFormatType::DATE) || (nType & SvNumFormatType::TIME);
        }
        return false;
    }

    bool DataBrowser::SaveModified()
    {
        if( ! IsModified() )
            return true;

        bool bChangeValid = true;

        const sal_Int32 nRow = lcl_getRowInData( GetCurRow());
        const sal_Int32 nCol = lcl_getColumnInData( GetCurColumnId());

        OSL_ENSURE( nRow >= 0 || nCol >= 0, "This cell should not be modified!" );

        SvNumberFormatter* pSvNumberFormatter = m_spNumberFormatterWrapper.get() ? m_spNumberFormatterWrapper->getSvNumberFormatter() : nullptr;
        switch( m_apDataBrowserModel->getCellType( nCol ))
        {
            case DataBrowserModel::NUMBER:
            {
                sal_uInt32 nDummy = 0;
                double fDummy = 0.0;
                OUString aText( m_aNumberEditField->GetText());
                // an empty string is valid, if no numberformatter exists, all
                // values are treated as valid
                if( !aText.isEmpty() && pSvNumberFormatter &&
                    ! pSvNumberFormatter->IsNumberFormat( aText, nDummy, fDummy ) )
                {
                    bChangeValid = false;
                }
                else
                {
                    double fData = m_aNumberEditField->GetValue();
                    bChangeValid = m_apDataBrowserModel->setCellNumber( nCol, nRow, fData );
                }
            }
            break;
            case DataBrowserModel::TEXTORDATE:
            {
                OUString aText( m_aTextEditField->GetText() );
                double fValue = 0.0;
                bChangeValid = false;
                if( isDateTimeString( aText, fValue ) )
                    bChangeValid = m_apDataBrowserModel->setCellAny( nCol, nRow, uno::Any( fValue ) );
                if(!bChangeValid)
                    bChangeValid = m_apDataBrowserModel->setCellAny( nCol, nRow, uno::Any( aText ) );
            }
            break;
            case DataBrowserModel::TEXT:
            {
                OUString aText( m_aTextEditField->GetText());
                bChangeValid = m_apDataBrowserModel->setCellText( nCol, nRow, aText );
            }
            break;
        }

        // the first valid change changes this to true
        if( bChangeValid )
        {
            RowModified( GetCurRow(), GetCurColumnId());
            ::svt::CellController* pCtrl = GetController( GetCurRow(), GetCurColumnId());
            if( pCtrl )
                pCtrl->ClearModified();
        }

        return bChangeValid;
    }

    bool DataBrowser::EndEditing()
    {
        SaveModified();

        // apply changes made to series headers
        for( const auto& spHeader : m_aSeriesHeaders )
            spHeader->applyChanges();

        if( m_bDataValid )
            return true;
        else
            return ShowQueryBox();
    }

    void DataBrowser::ColumnResized( sal_uInt16 nColId )
    {
        bool bLastUpdateMode = GetUpdateMode();
        SetUpdateMode( false );

        EditBrowseBox::ColumnResized( nColId );
        ImplAdjustHeaderControls();
        SetUpdateMode( bLastUpdateMode );
    }

    void DataBrowser::EndScroll()
    {
        bool bLastUpdateMode = GetUpdateMode();
        SetUpdateMode( false );

        EditBrowseBox::EndScroll();
        RenewSeriesHeaders();

        SetUpdateMode( bLastUpdateMode );
    }

    void DataBrowser::RenewSeriesHeaders()
    {
        Dialog* pDialog = GetParentDialog();
        vcl::Window* pWin = pDialog->get<VclContainer>("columns");
        vcl::Window* pColorWin = pDialog->get<VclContainer>("colorcolumns");

        clearHeaders();
        DataBrowserModel::tDataHeaderVector aHeaders( m_apDataBrowserModel->getDataHeaders());
        Link<Control&,void> aFocusLink( LINK( this, DataBrowser, SeriesHeaderGotFocus ));
        Link<impl::SeriesHeaderEdit*,void> aSeriesHeaderChangedLink( LINK( this, DataBrowser, SeriesHeaderChanged ));

        for (auto const& elemHeader : aHeaders)
        {
            std::shared_ptr< impl::SeriesHeader > spHeader( new impl::SeriesHeader( pWin, pColorWin ));
            Reference< beans::XPropertySet > xSeriesProp(elemHeader.m_xDataSeries, uno::UNO_QUERY);
            sal_Int32 nColor = 0;
            if( xSeriesProp.is() &&
                ( xSeriesProp->getPropertyValue( "Color" ) >>= nColor ))
                spHeader->SetColor( Color( nColor ));
            spHeader->SetChartType( elemHeader.m_xChartType, elemHeader.m_bSwapXAndYAxis );
            spHeader->SetSeriesName(
                DataSeriesHelper::getDataSeriesLabel(
                            elemHeader.m_xDataSeries,
                            (elemHeader.m_xChartType.is() ?
                             elemHeader.m_xChartType->getRoleOfSequenceForSeriesLabel() :
                             OUString( "values-y"))));
            spHeader->SetRange( elemHeader.m_nStartColumn + 1, elemHeader.m_nEndColumn + 1 );
            spHeader->SetGetFocusHdl( aFocusLink );
            spHeader->SetEditChangedHdl( aSeriesHeaderChangedLink );
            m_aSeriesHeaders.push_back( spHeader );
        }

        ImplAdjustHeaderControls();
    }

    void DataBrowser::ImplAdjustHeaderControls()
    {
        sal_uInt16 nColCount = GetColumnCount();
        sal_uInt32 nCurrentPos = GetPosPixel().getX();
        sal_uInt32 nMaxPos = nCurrentPos + GetOutputSizePixel().getWidth();
        sal_uInt32 nStartPos = nCurrentPos;

        // width of header column
        nCurrentPos +=  GetColumnWidth( 0 );

        Dialog* pDialog = GetParentDialog();
        vcl::Window* pWin = pDialog->get<VclContainer>("columns");
        vcl::Window* pColorWin = pDialog->get<VclContainer>("colorcolumns");
        pWin->set_margin_left(nCurrentPos);
        pColorWin->set_margin_left(nCurrentPos);

        tSeriesHeaderContainer::iterator aIt( m_aSeriesHeaders.begin());
        sal_uInt16 i = GetFirstVisibleColNumber();
        while( (aIt != m_aSeriesHeaders.end()) && ((*aIt)->GetStartColumn() < i) )
        {
            (*aIt)->Hide();
            ++aIt;
        }
        for( ; i < nColCount && aIt != m_aSeriesHeaders.end(); ++i )
        {
            if( (*aIt)->GetStartColumn() == i )
                nStartPos = nCurrentPos;

            nCurrentPos += (GetColumnWidth( i ));

            if( (*aIt)->GetEndColumn() == i )
            {
                if( nStartPos < nMaxPos )
                {
                    (*aIt)->SetPixelWidth( nCurrentPos - nStartPos - 3 );
                    (*aIt)->Show();

                    if (pWin)
                    {
                        pWin->set_margin_left(nStartPos);
                        pColorWin->set_margin_left(nStartPos);
                        pWin = pColorWin = nullptr;
                    }

                }
                else
                    (*aIt)->Hide();
                ++aIt;
            }
        }
    }

    IMPL_LINK( DataBrowser, SeriesHeaderGotFocus, Control&, rControl, void )
    {
        impl::SeriesHeaderEdit* pEdit = static_cast<impl::SeriesHeaderEdit*>(&rControl);
        pEdit->SetShowWarningBox( !m_bDataValid );

        if( !m_bDataValid )
            GoToCell( 0, 0 );
        else
        {
            MakeFieldVisible( GetCurRow(), static_cast< sal_uInt16 >( pEdit->getStartColumn()) );
            ActivateCell();
            m_aCursorMovedHdlLink.Call( this );
        }
    }

    IMPL_LINK( DataBrowser, SeriesHeaderChanged, impl::SeriesHeaderEdit*, pEdit, void )
    {
        if( pEdit )
        {
            Reference< chart2::XDataSeries > xSeries(
                m_apDataBrowserModel->getDataSeriesByColumn( pEdit->getStartColumn() - 1 ));
            Reference< chart2::data::XDataSource > xSource( xSeries, uno::UNO_QUERY );
            if( xSource.is())
            {
                Reference< chart2::XChartType > xChartType(
                    m_apDataBrowserModel->getHeaderForSeries( xSeries ).m_xChartType );
                if( xChartType.is())
                {
                    Reference< chart2::data::XLabeledDataSequence > xLabeledSeq(
                        DataSeriesHelper::getDataSequenceByRole( xSource, xChartType->getRoleOfSequenceForSeriesLabel()));
                    if( xLabeledSeq.is())
                    {
                        Reference< container::XIndexReplace > xIndexReplace( xLabeledSeq->getLabel(), uno::UNO_QUERY );
                        if( xIndexReplace.is())
                            xIndexReplace->replaceByIndex(
                                0, uno::Any( pEdit->GetText()));
                    }
                }
            }
        }
    }

    } // namespace chart

    /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

↑ V668 There is no sense in testing the 'm_spNumberFormatterWrapper' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error.