/* -*- 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.