diff options
Diffstat (limited to 'chart2/source/controller/dialogs/DataBrowser.cxx')
-rw-r--r-- | chart2/source/controller/dialogs/DataBrowser.cxx | 1376 |
1 files changed, 1376 insertions, 0 deletions
diff --git a/chart2/source/controller/dialogs/DataBrowser.cxx b/chart2/source/controller/dialogs/DataBrowser.cxx new file mode 100644 index 0000000000..30182b95ee --- /dev/null +++ b/chart2/source/controller/dialogs/DataBrowser.cxx @@ -0,0 +1,1376 @@ +/* -*- 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/zforlist.hxx> + +#include "DataBrowser.hxx" +#include "DataBrowserModel.hxx" +#include <strings.hrc> +#include <DataSeries.hxx> +#include <DataSeriesHelper.hxx> +#include <DiagramHelper.hxx> +#include <CommonConverters.hxx> +#include <NumberFormatterWrapper.hxx> +#include <servicenames_charttypes.hxx> +#include <ResId.hxx> +#include <bitmaps.hlst> +#include <helpids.h> +#include <ChartModel.hxx> +#include <ChartType.hxx> + +#include <vcl/weld.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <svl/numformat.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +#include <com/sun/star/container/XIndexReplace.hpp> + +#include <algorithm> +#include <limits> + + +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_getColumnInData( sal_uInt16 nCol ) +{ + return static_cast< sal_Int32 >( nCol ) - 1; +} + +} // anonymous namespace + +namespace chart +{ + +namespace impl +{ + +class SeriesHeaderEdit +{ +public: + explicit SeriesHeaderEdit(std::unique_ptr<weld::Entry> xControl); + + void setStartColumn( sal_Int32 nStartColumn ); + sal_Int32 getStartColumn() const { return m_nStartColumn;} + void SetShowWarningBox( bool bShowWarning ); + + OUString GetText() const { return m_xControl->get_text(); } + void SetText(const OUString& rText) { m_xControl->set_text(rText); } + + bool HasFocus() const { return m_xControl->has_focus(); } + + void set_size_request(int nWidth, int nHeight) { m_xControl->set_size_request(nWidth, nHeight); } + void set_margin_start(int nLeft) { m_xControl->set_margin_start(nLeft); } + + void SetModifyHdl(const Link<SeriesHeaderEdit&,void>& rLink) { m_aModifyHdl = rLink; } + void SetGetFocusHdl(const Link<SeriesHeaderEdit&,void>& rLink) { m_aFocusInHdl = rLink; } + +private: + DECL_LINK(NameEdited, weld::Entry&, void); + DECL_LINK(NameFocusIn, weld::Widget&, void); + DECL_LINK(MousePressHdl, const MouseEvent&, bool); + + std::unique_ptr<weld::Entry> m_xControl; + Link<SeriesHeaderEdit&,void> m_aModifyHdl; + Link<SeriesHeaderEdit&,void> m_aFocusInHdl; + sal_Int32 m_nStartColumn; + bool m_bShowWarningBox; +}; + +SeriesHeaderEdit::SeriesHeaderEdit(std::unique_ptr<weld::Entry> xControl) + : m_xControl(std::move(xControl)) + , m_nStartColumn(0) + , m_bShowWarningBox(false) +{ + m_xControl->set_help_id(HID_SCH_DATA_SERIES_LABEL); + m_xControl->connect_changed(LINK(this, SeriesHeaderEdit, NameEdited)); + m_xControl->connect_focus_in(LINK(this, SeriesHeaderEdit, NameFocusIn)); + m_xControl->connect_mouse_press(LINK(this, SeriesHeaderEdit, MousePressHdl)); +} + +IMPL_LINK_NOARG(SeriesHeaderEdit, NameEdited, weld::Entry&, void) +{ + m_aModifyHdl.Call(*this); +} + +IMPL_LINK_NOARG(SeriesHeaderEdit, NameFocusIn, weld::Widget&, void) +{ + m_aFocusInHdl.Call(*this); +} + +void SeriesHeaderEdit::setStartColumn( sal_Int32 nStartColumn ) +{ + m_nStartColumn = nStartColumn; +} + +void SeriesHeaderEdit::SetShowWarningBox( bool bShowWarning ) +{ + m_bShowWarningBox = bShowWarning; +} + +IMPL_LINK_NOARG(SeriesHeaderEdit, MousePressHdl, const MouseEvent&, bool) +{ + if (m_bShowWarningBox) + { + std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(m_xControl.get(), + VclMessageType::Warning, VclButtonsType::Ok, + SchResId(STR_INVALID_NUMBER))); + xWarn->run(); + } + + return false; +} + +class SeriesHeader +{ +public: + explicit SeriesHeader(weld::Container* pParent, weld::Container* pColorParent); + ~SeriesHeader(); + + void SetColor( const Color & rCol ); + void SetPos(); + void SetWidth( sal_Int32 nWidth ); + void SetChartType( const rtl::Reference< ::chart::ChartType > & 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<SeriesHeaderEdit&,void>& rLink); + + void SetEditChangedHdl( const Link<SeriesHeaderEdit&,void> & rLink ); + + bool HasFocus() const; + +private: + Timer m_aUpdateDataTimer; + + std::unique_ptr<weld::Builder> m_xBuilder1; + std::unique_ptr<weld::Builder> m_xBuilder2; + + weld::Container* m_pParent; + weld::Container* m_pColorParent; + + std::unique_ptr<weld::Container> m_xContainer1; + std::unique_ptr<weld::Container> m_xContainer2; + std::unique_ptr<weld::Image> m_spSymbol; + std::unique_ptr<SeriesHeaderEdit> m_spSeriesName; + std::unique_ptr<weld::Image> m_spColorBar; + VclPtr< OutputDevice> m_xDevice; + Link<SeriesHeaderEdit&,void> m_aChangeLink; + Color m_aColor; + + void notifyChanges(); + DECL_LINK( ImplUpdateDataHdl, Timer*, void ); + DECL_LINK( SeriesNameEdited, SeriesHeaderEdit&, void ); + + static OUString GetChartTypeImage( + const rtl::Reference< ::chart::ChartType > & xChartType, + bool bSwapXAndYAxis + ); + + sal_Int32 m_nStartCol, m_nEndCol; + sal_Int32 m_nWidth; + bool m_bSeriesNameChangePending; +}; + +SeriesHeader::SeriesHeader(weld::Container* pParent, weld::Container* pColorParent) + : m_aUpdateDataTimer( "SeriesHeader UpdateDataTimer" ) + , m_xBuilder1(Application::CreateBuilder(pParent, "modules/schart/ui/columnfragment.ui")) + , m_xBuilder2(Application::CreateBuilder(pColorParent, "modules/schart/ui/imagefragment.ui")) + , m_pParent(pParent) + , m_pColorParent(pColorParent) + , m_xContainer1(m_xBuilder1->weld_container("container")) + , m_xContainer2(m_xBuilder2->weld_container("container")) + , m_spSymbol(m_xBuilder1->weld_image("image")) + , m_spSeriesName(new SeriesHeaderEdit(m_xBuilder1->weld_entry("entry"))) + , m_spColorBar(m_xBuilder2->weld_image("image")) + , m_xDevice(Application::GetDefaultDevice()) + , m_nStartCol( 0 ) + , m_nEndCol( 0 ) + , m_nWidth( 42 ) + , m_bSeriesNameChangePending( false ) +{ + m_aUpdateDataTimer.SetInvokeHandler(LINK(this, SeriesHeader, ImplUpdateDataHdl)); + m_aUpdateDataTimer.SetTimeout(4 * EDIT_UPDATEDATA_TIMEOUT); + + m_spSeriesName->SetModifyHdl(LINK(this, SeriesHeader, SeriesNameEdited)); + Show(); +} + +SeriesHeader::~SeriesHeader() +{ + m_aUpdateDataTimer.Stop(); + m_pParent->move(m_xContainer1.get(), nullptr); + m_pColorParent->move(m_xContainer2.get(), nullptr); +} + +void SeriesHeader::notifyChanges() +{ + m_aChangeLink.Call(*m_spSeriesName); + m_bSeriesNameChangePending = false; +} + +void SeriesHeader::applyChanges() +{ + if( m_bSeriesNameChangePending ) + { + notifyChanges(); + } +} + +void SeriesHeader::SetColor( const Color & rCol ) +{ + m_aColor = rCol; +} + +void SeriesHeader::SetPos() +{ + // chart type symbol + Size aSize( nSymbolHeight, nSymbolHeight ); + aSize = m_xDevice->LogicToPixel(aSize, MapMode(MapUnit::MapAppFont)); + m_spSymbol->set_size_request(aSize.Width(), aSize.Height()); + + // series name edit field + m_spSeriesName->set_margin_start(2); + + sal_Int32 nHeightPx = m_xDevice->LogicToPixel(Size(0, 12), MapMode(MapUnit::MapAppFont)).Height(); + m_spSeriesName->set_size_request(m_nWidth - aSize.Width() - 2, nHeightPx); + + // color bar + nHeightPx = m_xDevice->LogicToPixel(Size(0, 3), MapMode(MapUnit::MapAppFont)).Height(); + m_spColorBar->set_size_request(m_nWidth, nHeightPx); + + ScopedVclPtr<VirtualDevice> xVirDev(m_spColorBar->create_virtual_device()); + xVirDev->SetOutputSizePixel(Size(m_nWidth, nHeightPx)); + xVirDev->SetFillColor(m_aColor); + xVirDev->SetLineColor(m_aColor); + xVirDev->DrawRect(tools::Rectangle(Point(0, 0), aSize)); + m_spColorBar->set_image(xVirDev.get()); +} + +void SeriesHeader::SetWidth( sal_Int32 nWidth ) +{ + m_nWidth = nWidth; + SetPos(); +} + +void SeriesHeader::SetPixelWidth( sal_Int32 nWidth ) +{ + SetWidth(nWidth); +} + +void SeriesHeader::SetChartType( + const rtl::Reference< ChartType > & xChartType, + bool bSwapXAndYAxis +) +{ + m_spSymbol->set_from_icon_name( 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_xContainer1->show(); + m_xContainer2->show(); +} + +void SeriesHeader::Hide() +{ + m_xContainer1->hide(); + m_xContainer2->hide(); +} + +void SeriesHeader::SetEditChangedHdl( const Link<SeriesHeaderEdit&,void> & rLink ) +{ + m_aChangeLink = rLink; +} + +IMPL_LINK_NOARG(SeriesHeader, ImplUpdateDataHdl, Timer*, void) +{ + notifyChanges(); +} + +IMPL_LINK_NOARG(SeriesHeader, SeriesNameEdited, SeriesHeaderEdit&, void) +{ + m_bSeriesNameChangePending = true; + m_aUpdateDataTimer.Start(); +} + +void SeriesHeader::SetGetFocusHdl( const Link<SeriesHeaderEdit&,void>& rLink ) +{ + m_spSeriesName->SetGetFocusHdl( rLink ); +} + +bool SeriesHeader::HasFocus() const +{ + return m_spSeriesName->HasFocus(); +} + +OUString SeriesHeader::GetChartTypeImage( + const rtl::Reference< ChartType > & xChartType, + bool bSwapXAndYAxis +) +{ + OUString aResult; + if( !xChartType.is()) + return aResult; + OUString aChartTypeName( xChartType->getChartType()); + + if( aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_AREA ) + { + aResult = BMP_TYPE_AREA; + } + else if( aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_COLUMN ) + { + if( bSwapXAndYAxis ) + aResult = BMP_TYPE_BAR; + else + aResult = BMP_TYPE_COLUMN; + } + else if( aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_LINE ) + { + aResult = BMP_TYPE_LINE; + } + else if( aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_SCATTER ) + { + aResult = BMP_TYPE_XY; + } + else if( aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_PIE ) + { + aResult = BMP_TYPE_PIE; + } + else if( aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_NET + || aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_FILLED_NET ) + { + aResult = BMP_TYPE_NET; + } + else if( aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_CANDLESTICK ) + { + // @todo: correct image for candle-stick type + aResult = BMP_TYPE_STOCK; + } + else if( aChartTypeName == CHART2_SERVICE_NAME_CHARTTYPE_BUBBLE ) + { + aResult = 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(const css::uno::Reference<css::awt::XWindow> &rParent, + weld::Container* pColumns, weld::Container* pColors) : + ::svt::EditBrowseBox(VCLUnoHelper::GetWindow(rParent), + EditBrowseBoxFlags::SMART_TAB_TRAVEL | EditBrowseBoxFlags::HANDLE_COLUMN_TEXT, + WB_BORDER | WB_TABSTOP, BrowserStdFlags ), + m_nSeekRow( 0 ), + m_bIsReadOnly( false ), + m_bDataValid( true ), + m_aNumberEditField(VclPtr<FormattedControl>::Create(&EditBrowseBox::GetDataWindow(), false)), + m_aTextEditField(VclPtr<EditControl>::Create(&EditBrowseBox::GetDataWindow())), + m_pColumnsWin(pColumns), + m_pColorsWin(pColors), + m_rNumberEditController( new ::svt::FormattedFieldCellController( m_aNumberEditField.get() )), + m_rTextEditController( new ::svt::EditCellController( m_aTextEditField.get() )) +{ + Formatter& rFormatter = m_aNumberEditField->get_formatter(); + rFormatter.SetDefaultValue( std::numeric_limits<double>::quiet_NaN() ); + rFormatter.TreatAsNumber( true ); + RenewTable(); +} + +DataBrowser::~DataBrowser() +{ + disposeOnce(); +} + +void DataBrowser::dispose() +{ + m_aSeriesHeaders.clear(); + 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 (o3tl::make_unsigned( 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 + && !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 (o3tl::make_unsigned( nColIndex ) < (m_aSeriesHeaders.size() - 1)); + } + + sal_Int32 nColIdx = lcl_getColumnInDataOrHeader( GetCurColumnId(), m_aSeriesHeaders ); + return ! IsReadOnly() + && ( nColIdx > 0 ) + && ( nColIdx < ColCount()-2 ) + && m_apDataBrowserModel + && !m_apDataBrowserModel->isCategoriesColumn( nColIdx ); +} + +void DataBrowser::clearHeaders() +{ + for( const auto& spHeader : m_aSeriesHeaders ) + spHeader->applyChanges(); + m_aSeriesHeaders.clear(); +} + +void DataBrowser::RenewTable() +{ + if (!m_apDataBrowserModel) + return; + + sal_Int32 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, u"%COLUMNNUMBER", OUString::number( 24 ) ); + sal_Int32 nColumnWidth = GetDataWindow().GetTextWidth( aDefaultSeriesName ) + + GetDataWindow().LogicToPixel(Point(8 + 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 ))); + + // fill series headers + clearHeaders(); + const DataBrowserModel::tDataHeaderVector& aHeaders( m_apDataBrowserModel->getDataHeaders()); + Link<impl::SeriesHeaderEdit&,void> aFocusLink( LINK( this, DataBrowser, SeriesHeaderGotFocus )); + Link<impl::SeriesHeaderEdit&,void> aSeriesHeaderChangedLink( LINK( this, DataBrowser, SeriesHeaderChanged )); + + for (auto const& elemHeader : aHeaders) + { + auto spHeader = std::make_shared<impl::SeriesHeader>( m_pColumnsWin, m_pColorsWin ); + Color nColor; + // @todo: Set "DraftColor", i.e. interpolated colors for gradients, bitmaps, etc. + if( elemHeader.m_xDataSeries.is() && + ( elemHeader.m_xDataSeries->getPropertyValue( "Color" ) >>= nColor )) + spHeader->SetColor( nColor ); + spHeader->SetChartType( elemHeader.m_xChartType, elemHeader.m_bSwapXAndYAxis ); + spHeader->SetSeriesName( + elemHeader.m_xDataSeries->getLabelForRole( + 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); + if( nColumnId > 0 ) + return m_apDataBrowserModel->getRoleOfColumn( nColumnId - 1 ); + return OUString(); +} + +OUString DataBrowser::GetCellText( sal_Int32 nRow, sal_uInt16 nColumnId ) const +{ + OUString aResult; + + if( nColumnId == 0 ) + { + aResult = OUString::number(nRow + 1); + } + else if( nRow >= 0 && m_apDataBrowserModel) + { + sal_Int32 nColIndex = static_cast< sal_Int32 >( nColumnId ) - 1; + + if( m_apDataBrowserModel->getCellType( nColIndex ) == DataBrowserModel::NUMBER ) + { + double fData( m_apDataBrowserModel->getCellNumber( nColIndex, nRow )); + + if( ! std::isnan( fData ) && + m_spNumberFormatterWrapper ) + { + bool bColorChanged = false; + Color nLabelColor; + 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( ! std::isnan( fDouble ) && m_spNumberFormatterWrapper ) + { + // 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( + m_xChartDoc, 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( sal_Int32 nRow, sal_uInt16 nColumnId ) const +{ + if(( nColumnId >= 1 ) && ( nRow >= 0 ) && m_apDataBrowserModel) + { + return m_apDataBrowserModel->getCellNumber( + static_cast< sal_Int32 >( nColumnId ) - 1, nRow ); + } + + return std::numeric_limits<double>::quiet_NaN(); +} + +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() const +{ + 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->get_widget().get_text()); + + if( !aText.isEmpty() && + m_spNumberFormatterWrapper && + 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 rtl::Reference<::chart::ChartModel> & xChartDoc ) +{ + m_xChartDoc = xChartDoc; + + m_apDataBrowserModel.reset( new DataBrowserModel( m_xChartDoc )); + m_spNumberFormatterWrapper = + std::make_shared<NumberFormatterWrapper>(m_xChartDoc); + + Formatter& rFormatter = m_aNumberEditField->get_formatter(); + rFormatter.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) + { + // 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) + { + // 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) + { + // save changes made to edit-field + if( IsModified() ) + SaveModified(); + + m_bDataValid = true; + m_apDataBrowserModel->removeDataSeriesOrComplexCategoryLevel( nColIdx ); + RenewTable(); + } +} + +void DataBrowser::InsertRow() +{ + sal_Int32 nRowIdx = GetCurRow(); + + if( nRowIdx >= 0 && m_apDataBrowserModel) + { + // save changes made to edit-field + if( IsModified() ) + SaveModified(); + + m_apDataBrowserModel->insertDataPointForAllSeries( nRowIdx ); + RenewTable(); + } +} + +void DataBrowser::RemoveRow() +{ + sal_Int32 nRowIdx = GetCurRow(); + + if( nRowIdx >= 0 && m_apDataBrowserModel) + { + // 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)) + return; + + // 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)) + return; + + // 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 = GetCurRow(); + + if( !(nRowIdx > 0 && m_apDataBrowserModel)) + return; + + // 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 = GetCurRow(); + + if( !(nRowIdx >= 0 && m_apDataBrowserModel)) + return; + + // 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( sal_Int32 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 +{ + sal_Int32 nRow = GetCurRow(); + sal_Int32 nCol = GetCurColumnId(); + + // column 0 is header-column + sal_Int32 nBadCol = bForward + ? GetColumnCount() - 1 + : 1; + sal_Int32 nBadRow = bForward + ? GetRowCount() - 1 + : 0; + + if( !m_bDataValid ) + { + const_cast< DataBrowser* >( this )->ShowWarningBox(); + return false; + } + + return ( nRow != nBadRow || + nCol != nBadCol ); +} + +::svt::CellController* DataBrowser::GetController( sal_Int32 /*nRow*/, sal_uInt16 nCol ) +{ + if( m_bIsReadOnly ) + return nullptr; + + if( CellContainsNumbers( nCol )) + { + Formatter& rFormatter = m_aNumberEditField->get_formatter(); + rFormatter.UseInputStringForFormatting(); + rFormatter.SetFormatKey( GetNumberFormatKey( nCol )); + return m_rNumberEditController.get(); + } + + return m_rTextEditController.get(); +} + +void DataBrowser::InitController( + ::svt::CellControllerRef& rController, sal_Int32 nRow, sal_uInt16 nCol ) +{ + if( rController == m_rTextEditController ) + { + OUString aText( GetCellText( nRow, nCol ) ); + weld::Entry& rEntry = m_aTextEditField->get_widget(); + rEntry.set_text(aText); + rEntry.select_region(0, -1); + } + else if( rController == m_rNumberEditController ) + { + // treat invalid and empty text as Nan + Formatter& rFormatter = m_aNumberEditField->get_formatter(); + rFormatter.EnableNotANumber( true ); + if( std::isnan( GetCellNumber( nRow, nCol ))) + rFormatter.SetTextValue( OUString()); + else + rFormatter.SetValue( GetCellNumber( nRow, nCol ) ); + weld::Entry& rEntry = m_aNumberEditField->get_widget(); + rEntry.select_region(0, -1); + } + else + { + OSL_FAIL( "Invalid Controller" ); + } +} + +bool DataBrowser::CellContainsNumbers( sal_uInt16 nCol ) const +{ + if (!m_apDataBrowserModel) + return false; + return m_apDataBrowserModel->getCellType( lcl_getColumnInData( nCol )) == DataBrowserModel::NUMBER; +} + +sal_uInt32 DataBrowser::GetNumberFormatKey( sal_uInt16 nCol ) const +{ + if (!m_apDataBrowserModel) + 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 ? 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 = GetCurRow(); + const sal_Int32 nCol = lcl_getColumnInData( GetCurColumnId()); + + OSL_ENSURE( nRow >= 0 || nCol >= 0, "This cell should not be modified!" ); + + SvNumberFormatter* pSvNumberFormatter = m_spNumberFormatterWrapper ? m_spNumberFormatterWrapper->getSvNumberFormatter() : nullptr; + switch( m_apDataBrowserModel->getCellType( nCol )) + { + case DataBrowserModel::NUMBER: + { + sal_uInt32 nDummy = 0; + double fDummy = 0.0; + OUString aText(m_aNumberEditField->get_widget().get_text()); + // 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 + { + Formatter& rFormatter = m_aNumberEditField->get_formatter(); + double fData = rFormatter.GetValue(); + bChangeValid = m_apDataBrowserModel->setCellNumber( nCol, nRow, fData ); + } + } + break; + case DataBrowserModel::TEXTORDATE: + { + weld::Entry& rEntry = m_aTextEditField->get_widget(); + OUString aText(rEntry.get_text()); + 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: + { + weld::Entry& rEntry = m_aTextEditField->get_widget(); + OUString aText(rEntry.get_text()); + 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->SaveValue(); + } + + 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() +{ + clearHeaders(); + DataBrowserModel::tDataHeaderVector aHeaders( m_apDataBrowserModel->getDataHeaders()); + Link<impl::SeriesHeaderEdit&,void> aFocusLink( LINK( this, DataBrowser, SeriesHeaderGotFocus )); + Link<impl::SeriesHeaderEdit&,void> aSeriesHeaderChangedLink( LINK( this, DataBrowser, SeriesHeaderChanged )); + + for (auto const& elemHeader : aHeaders) + { + auto spHeader = std::make_shared<impl::SeriesHeader>( m_pColumnsWin, m_pColorsWin ); + Color nColor; + if( elemHeader.m_xDataSeries.is() && + ( elemHeader.m_xDataSeries->getPropertyValue( "Color" ) >>= nColor )) + spHeader->SetColor( nColor ); + spHeader->SetChartType( elemHeader.m_xChartType, elemHeader.m_bSwapXAndYAxis ); + spHeader->SetSeriesName( + elemHeader.m_xDataSeries->getLabelForRole( + 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 ); + + weld::Container* pWin = m_pColumnsWin; + weld::Container* pColorWin = m_pColorsWin; + pWin->set_margin_start(nCurrentPos); + pColorWin->set_margin_start(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 ); + (*aIt)->Show(); + + if (pWin) + { + pWin->set_margin_start(nStartPos); + pColorWin->set_margin_start(nStartPos); + pWin = pColorWin = nullptr; + } + + } + else + (*aIt)->Hide(); + ++aIt; + } + } +} + +IMPL_LINK( DataBrowser, SeriesHeaderGotFocus, impl::SeriesHeaderEdit&, rEdit, void ) +{ + rEdit.SetShowWarningBox( !m_bDataValid ); + + if( !m_bDataValid ) + GoToCell( 0, 0 ); + else + { + MakeFieldVisible( GetCurRow(), static_cast< sal_uInt16 >( rEdit.getStartColumn()) ); + ActivateCell(); + m_aCursorMovedHdlLink.Call( this ); + } +} + +IMPL_LINK( DataBrowser, SeriesHeaderChanged, impl::SeriesHeaderEdit&, rEdit, void ) +{ + rtl::Reference< DataSeries > xSeries = + m_apDataBrowserModel->getDataSeriesByColumn( rEdit.getStartColumn() - 1 ); + if( !xSeries.is()) + return; + + rtl::Reference< ChartType > xChartType( + m_apDataBrowserModel->getHeaderForSeries( xSeries ).m_xChartType ); + if( xChartType.is()) + { + uno::Reference< chart2::data::XLabeledDataSequence > xLabeledSeq = + DataSeriesHelper::getDataSequenceByRole( xSeries, xChartType->getRoleOfSequenceForSeriesLabel()); + if( xLabeledSeq.is()) + { + Reference< container::XIndexReplace > xIndexReplace( xLabeledSeq->getLabel(), uno::UNO_QUERY ); + if( xIndexReplace.is()) + xIndexReplace->replaceByIndex( + 0, uno::Any( rEdit.GetText())); + } + } +} + +} // namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |