diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /svx/source/fmcomp | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'svx/source/fmcomp')
-rw-r--r-- | svx/source/fmcomp/dbaexchange.cxx | 630 | ||||
-rw-r--r-- | svx/source/fmcomp/dbaobjectex.cxx | 140 | ||||
-rw-r--r-- | svx/source/fmcomp/fmgridcl.cxx | 2096 | ||||
-rw-r--r-- | svx/source/fmcomp/fmgridif.cxx | 2819 | ||||
-rw-r--r-- | svx/source/fmcomp/gridcell.cxx | 4603 | ||||
-rw-r--r-- | svx/source/fmcomp/gridcols.cxx | 103 | ||||
-rw-r--r-- | svx/source/fmcomp/gridctrl.cxx | 3365 | ||||
-rw-r--r-- | svx/source/fmcomp/xmlexchg.cxx | 61 |
8 files changed, 13817 insertions, 0 deletions
diff --git a/svx/source/fmcomp/dbaexchange.cxx b/svx/source/fmcomp/dbaexchange.cxx new file mode 100644 index 0000000000..904740e388 --- /dev/null +++ b/svx/source/fmcomp/dbaexchange.cxx @@ -0,0 +1,630 @@ +/* -*- 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 <svx/dbaexchange.hxx> +#include <osl/diagnose.h> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <fmprop.hxx> +#include <comphelper/extract.hxx> +#include <sot/formats.hxx> +#include <sot/exchange.hxx> +#include <o3tl/string_view.hxx> + + +namespace svx +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::sdb; + using namespace ::com::sun::star::sdbc; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::sdbcx; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::datatransfer; + + OColumnTransferable::OColumnTransferable(ColumnTransferFormatFlags nFormats) + : m_nFormatFlags(nFormats) + { + } + + void OColumnTransferable::setDescriptor(const ODataAccessDescriptor& rDescriptor) + { + ClearFormats(); + + OUString sDataSource, sDatabaseLocation, sConnectionResource, sCommand, sFieldName; + if ( rDescriptor.has( DataAccessDescriptorProperty::DataSource ) ) rDescriptor[ DataAccessDescriptorProperty::DataSource ] >>= sDataSource; + if ( rDescriptor.has( DataAccessDescriptorProperty::DatabaseLocation ) ) rDescriptor[ DataAccessDescriptorProperty::DatabaseLocation ] >>= sDatabaseLocation; + if ( rDescriptor.has( DataAccessDescriptorProperty::ConnectionResource ) ) rDescriptor[ DataAccessDescriptorProperty::ConnectionResource ] >>= sConnectionResource; + if ( rDescriptor.has( DataAccessDescriptorProperty::Command ) ) rDescriptor[ DataAccessDescriptorProperty::Command ] >>= sCommand; + if ( rDescriptor.has( DataAccessDescriptorProperty::ColumnName ) ) rDescriptor[ DataAccessDescriptorProperty::ColumnName ] >>= sFieldName; + + sal_Int32 nCommandType = CommandType::TABLE; + OSL_VERIFY( rDescriptor[ DataAccessDescriptorProperty::CommandType ] >>= nCommandType ); + + implConstruct( + sDataSource.isEmpty() ? sDatabaseLocation : sDataSource, + sConnectionResource, nCommandType, sCommand, sFieldName ); + + if ( m_nFormatFlags & ColumnTransferFormatFlags::COLUMN_DESCRIPTOR ) + { + if ( rDescriptor.has( DataAccessDescriptorProperty::Connection ) ) + m_aDescriptor[ DataAccessDescriptorProperty::Connection ] = rDescriptor[ DataAccessDescriptorProperty::Connection ]; + if ( rDescriptor.has( DataAccessDescriptorProperty::ColumnObject ) ) + m_aDescriptor[ DataAccessDescriptorProperty::ColumnObject ] = rDescriptor[ DataAccessDescriptorProperty::ColumnObject ]; + } + } + + OColumnTransferable::OColumnTransferable(const Reference< XPropertySet >& _rxForm, + const OUString& _rFieldName, const Reference< XPropertySet >& _rxColumn, + const Reference< XConnection >& _rxConnection, ColumnTransferFormatFlags _nFormats) + :m_nFormatFlags(_nFormats) + { + OSL_ENSURE(_rxForm.is(), "OColumnTransferable::OColumnTransferable: invalid form!"); + // collect the necessary information from the form + OUString sCommand; + sal_Int32 nCommandType = CommandType::TABLE; + OUString sDatasource,sURL; + + bool bTryToParse = true; + try + { + _rxForm->getPropertyValue(FM_PROP_COMMANDTYPE) >>= nCommandType; + _rxForm->getPropertyValue(FM_PROP_COMMAND) >>= sCommand; + _rxForm->getPropertyValue(FM_PROP_DATASOURCE) >>= sDatasource; + _rxForm->getPropertyValue(FM_PROP_URL) >>= sURL; + bTryToParse = ::cppu::any2bool(_rxForm->getPropertyValue(FM_PROP_ESCAPE_PROCESSING)); + } + catch(Exception&) + { + OSL_FAIL("OColumnTransferable::OColumnTransferable: could not collect essential data source attributes !"); + } + + // If the data source is an SQL-statement and simple enough (means "select <field list> from <table> where...") + // we are able to fake the drag information we are about to create. + if (bTryToParse && (CommandType::COMMAND == nCommandType)) + { + try + { + Reference< XTablesSupplier > xSupTab; + _rxForm->getPropertyValue("SingleSelectQueryComposer") >>= xSupTab; + + if(xSupTab.is()) + { + Reference< XNameAccess > xNames = xSupTab->getTables(); + if (xNames.is()) + { + Sequence< OUString > aTables = xNames->getElementNames(); + if (1 == aTables.getLength()) + { + sCommand = aTables[0]; + nCommandType = CommandType::TABLE; + } + } + } + } + catch(Exception&) + { + OSL_FAIL("OColumnTransferable::OColumnTransferable: could not collect essential data source attributes (part two) !"); + } + } + + implConstruct(sDatasource, sURL,nCommandType, sCommand, _rFieldName); + + if ((m_nFormatFlags & ColumnTransferFormatFlags::COLUMN_DESCRIPTOR) == ColumnTransferFormatFlags::COLUMN_DESCRIPTOR) + { + if (_rxColumn.is()) + m_aDescriptor[DataAccessDescriptorProperty::ColumnObject] <<= _rxColumn; + if (_rxConnection.is()) + m_aDescriptor[DataAccessDescriptorProperty::Connection] <<= _rxConnection; + } + } + + + SotClipboardFormatId OColumnTransferable::getDescriptorFormatId() + { + static SotClipboardFormatId s_nFormat = static_cast<SotClipboardFormatId>(-1); + if (static_cast<SotClipboardFormatId>(-1) == s_nFormat) + { + s_nFormat = SotExchange::RegisterFormatName("application/x-openoffice;windows_formatname=\"dbaccess.ColumnDescriptorTransfer\""); + OSL_ENSURE(static_cast<SotClipboardFormatId>(-1) != s_nFormat, "OColumnTransferable::getDescriptorFormatId: bad exchange id!"); + } + return s_nFormat; + } + + + void OColumnTransferable::implConstruct( const OUString& _rDatasource + ,const OUString& _rConnectionResource + ,const sal_Int32 _nCommandType + ,const OUString& _rCommand + , const OUString& _rFieldName) + { + const sal_Unicode cSeparator = u'\x000B'; + const OUString sSeparator(&cSeparator, 1); + + m_sCompatibleFormat.clear(); + m_sCompatibleFormat += _rDatasource; + m_sCompatibleFormat += sSeparator; + m_sCompatibleFormat += _rCommand; + m_sCompatibleFormat += sSeparator; + + sal_Unicode cCommandType; + switch (_nCommandType) + { + case CommandType::TABLE: + cCommandType = '0'; + break; + case CommandType::QUERY: + cCommandType = '1'; + break; + default: + cCommandType = '2'; + break; + } + m_sCompatibleFormat += OUStringChar(cCommandType); + m_sCompatibleFormat += sSeparator; + m_sCompatibleFormat += _rFieldName; + + m_aDescriptor.clear(); + if ((m_nFormatFlags & ColumnTransferFormatFlags::COLUMN_DESCRIPTOR) == ColumnTransferFormatFlags::COLUMN_DESCRIPTOR) + { + m_aDescriptor.setDataSource(_rDatasource); + if ( !_rConnectionResource.isEmpty() ) + m_aDescriptor[DataAccessDescriptorProperty::ConnectionResource] <<= _rConnectionResource; + + m_aDescriptor[DataAccessDescriptorProperty::Command] <<= _rCommand; + m_aDescriptor[DataAccessDescriptorProperty::CommandType] <<= _nCommandType; + m_aDescriptor[DataAccessDescriptorProperty::ColumnName] <<= _rFieldName; + } + } + + + void OColumnTransferable::AddSupportedFormats() + { + if (ColumnTransferFormatFlags::CONTROL_EXCHANGE & m_nFormatFlags) + AddFormat(SotClipboardFormatId::SBA_CTRLDATAEXCHANGE); + + if (ColumnTransferFormatFlags::FIELD_DESCRIPTOR & m_nFormatFlags) + AddFormat(SotClipboardFormatId::SBA_FIELDDATAEXCHANGE); + + if (ColumnTransferFormatFlags::COLUMN_DESCRIPTOR & m_nFormatFlags) + AddFormat(getDescriptorFormatId()); + } + + + bool OColumnTransferable::GetData( const DataFlavor& _rFlavor, const OUString& /*rDestDoc*/ ) + { + const SotClipboardFormatId nFormatId = SotExchange::GetFormat(_rFlavor); + switch (nFormatId) + { + case SotClipboardFormatId::SBA_FIELDDATAEXCHANGE: + case SotClipboardFormatId::SBA_CTRLDATAEXCHANGE: + return SetString(m_sCompatibleFormat); + default: break; + } + if (nFormatId == getDescriptorFormatId()) + return SetAny( Any( m_aDescriptor.createPropertyValueSequence() ) ); + + return false; + } + + + bool OColumnTransferable::canExtractColumnDescriptor(const DataFlavorExVector& _rFlavors, ColumnTransferFormatFlags _nFormats) + { + bool bFieldFormat = bool(_nFormats & ColumnTransferFormatFlags::FIELD_DESCRIPTOR); + bool bControlFormat = bool(_nFormats & ColumnTransferFormatFlags::CONTROL_EXCHANGE); + bool bDescriptorFormat = bool(_nFormats & ColumnTransferFormatFlags::COLUMN_DESCRIPTOR); + SotClipboardFormatId nFormatId = getDescriptorFormatId(); + return std::any_of(_rFlavors.begin(), _rFlavors.end(), + [&](const DataFlavorEx& rCheck) { + return (bFieldFormat && (SotClipboardFormatId::SBA_FIELDDATAEXCHANGE == rCheck.mnSotId)) + || (bControlFormat && (SotClipboardFormatId::SBA_CTRLDATAEXCHANGE == rCheck.mnSotId)) + || (bDescriptorFormat && (nFormatId == rCheck.mnSotId)); + }); + } + + + ODataAccessDescriptor OColumnTransferable::extractColumnDescriptor(const TransferableDataHelper& _rData) + { + if (_rData.HasFormat(getDescriptorFormatId())) + { + // the object has a real descriptor object (not just the old compatible format) + + // extract the any from the transferable + DataFlavor aFlavor; + bool bSuccess = + SotExchange::GetFormatDataFlavor(getDescriptorFormatId(), aFlavor); + OSL_ENSURE(bSuccess, "OColumnTransferable::extractColumnDescriptor: invalid data format (no flavor)!"); + + Any aDescriptor = _rData.GetAny(aFlavor, OUString()); + + // extract the property value sequence + Sequence< PropertyValue > aDescriptorProps; + bSuccess = aDescriptor >>= aDescriptorProps; + OSL_ENSURE(bSuccess, "OColumnTransferable::extractColumnDescriptor: invalid clipboard format!"); + + // build the real descriptor + return ODataAccessDescriptor(aDescriptorProps); + } + + // only the old (compatible) format exists -> use the other extract method ... + OUString sDatasource, sCommand, sFieldName,sDatabaseLocation,sConnectionResource; + sal_Int32 nCommandType = CommandType::COMMAND; + + ODataAccessDescriptor aDescriptor; + if (extractColumnDescriptor(_rData, sDatasource, sDatabaseLocation,sConnectionResource,nCommandType, sCommand, sFieldName)) + { + // and build an own descriptor + if ( !sDatasource.isEmpty() ) + aDescriptor[DataAccessDescriptorProperty::DataSource] <<= sDatasource; + if ( !sDatabaseLocation.isEmpty() ) + aDescriptor[DataAccessDescriptorProperty::DatabaseLocation] <<= sDatabaseLocation; + if ( !sConnectionResource.isEmpty() ) + aDescriptor[DataAccessDescriptorProperty::ConnectionResource] <<= sConnectionResource; + + aDescriptor[DataAccessDescriptorProperty::Command] <<= sCommand; + aDescriptor[DataAccessDescriptorProperty::CommandType] <<= nCommandType; + aDescriptor[DataAccessDescriptorProperty::ColumnName] <<= sFieldName; + } + return aDescriptor; + } + + + bool OColumnTransferable::extractColumnDescriptor(const TransferableDataHelper& _rData + ,OUString& _rDatasource + ,OUString& _rDatabaseLocation + ,OUString& _rConnectionResource + ,sal_Int32& _nCommandType + ,OUString& _rCommand + ,OUString& _rFieldName) + { + if ( _rData.HasFormat(getDescriptorFormatId()) ) + { + ODataAccessDescriptor aDescriptor = extractColumnDescriptor(_rData); + if ( aDescriptor.has(DataAccessDescriptorProperty::DataSource) ) + aDescriptor[DataAccessDescriptorProperty::DataSource] >>= _rDatasource; + if ( aDescriptor.has(DataAccessDescriptorProperty::DatabaseLocation) ) + aDescriptor[DataAccessDescriptorProperty::DatabaseLocation] >>= _rDatabaseLocation; + if ( aDescriptor.has(DataAccessDescriptorProperty::ConnectionResource) ) + aDescriptor[DataAccessDescriptorProperty::ConnectionResource] >>= _rConnectionResource; + + aDescriptor[DataAccessDescriptorProperty::Command] >>= _rCommand; + aDescriptor[DataAccessDescriptorProperty::CommandType] >>= _nCommandType; + aDescriptor[DataAccessDescriptorProperty::ColumnName] >>= _rFieldName; + return true; + } + + // check if we have a (string) format we can use... + SotClipboardFormatId nRecognizedFormat = SotClipboardFormatId::NONE; + if (_rData.HasFormat(SotClipboardFormatId::SBA_FIELDDATAEXCHANGE)) + nRecognizedFormat = SotClipboardFormatId::SBA_FIELDDATAEXCHANGE; + if (_rData.HasFormat(SotClipboardFormatId::SBA_CTRLDATAEXCHANGE)) + nRecognizedFormat = SotClipboardFormatId::SBA_CTRLDATAEXCHANGE; + if (nRecognizedFormat == SotClipboardFormatId::NONE) + return false; + + OUString sFieldDescription; + (void)_rData.GetString(nRecognizedFormat, sFieldDescription); + + const sal_Unicode cSeparator = u'\x000B'; + sal_Int32 nIdx{ 0 }; + _rDatasource = sFieldDescription.getToken(0, cSeparator, nIdx); + _rCommand = sFieldDescription.getToken(0, cSeparator, nIdx); + _nCommandType = o3tl::toInt32(o3tl::getToken(sFieldDescription, 0, cSeparator, nIdx)); + _rFieldName = sFieldDescription.getToken(0, cSeparator, nIdx); + + return true; + } + + ODataAccessObjectTransferable::ODataAccessObjectTransferable() + { + } + + void ODataAccessObjectTransferable::Update( + const OUString& _rDatasource, + const sal_Int32 _nCommandType, + const OUString& _rCommand) + { + construct(_rDatasource,OUString(),_nCommandType,_rCommand,nullptr,(CommandType::COMMAND == _nCommandType),_rCommand); + } + + void ODataAccessObjectTransferable::Update( + const OUString& _rDatasource, + const sal_Int32 _nCommandType, + const OUString& _rCommand, + const Reference< XConnection >& _rxConnection) + { + OSL_ENSURE(_rxConnection.is(), "Wrong Update used.!"); + construct(_rDatasource,OUString(),_nCommandType,_rCommand,_rxConnection,(CommandType::COMMAND == _nCommandType),_rCommand); + } + + ODataAccessObjectTransferable::ODataAccessObjectTransferable(const Reference< XPropertySet >& _rxLivingForm) + { + // collect some properties of the form + OUString sDatasourceName,sConnectionResource; + sal_Int32 nObjectType = CommandType::COMMAND; + OUString sObjectName; + Reference< XConnection > xConnection; + try + { + _rxLivingForm->getPropertyValue(FM_PROP_COMMANDTYPE) >>= nObjectType; + _rxLivingForm->getPropertyValue(FM_PROP_COMMAND) >>= sObjectName; + _rxLivingForm->getPropertyValue(FM_PROP_DATASOURCE) >>= sDatasourceName; + _rxLivingForm->getPropertyValue(FM_PROP_URL) >>= sConnectionResource; + _rxLivingForm->getPropertyValue(FM_PROP_ACTIVE_CONNECTION) >>= xConnection; + } + catch(Exception&) + { + OSL_FAIL("ODataAccessObjectTransferable::ODataAccessObjectTransferable: could not collect essential form attributes !"); + return; + } + + // check if the SQL-statement is modified + OUString sCompleteStatement; + try + { + _rxLivingForm->getPropertyValue(FM_PROP_ACTIVECOMMAND) >>= sCompleteStatement; + } + catch (const Exception&) + { + OSL_FAIL("ODataAccessObjectTransferable::ODataAccessObjectTransferable: could not collect essential form attributes (part two) !"); + return; + } + + construct( sDatasourceName + ,sConnectionResource + ,nObjectType + ,sObjectName,xConnection + ,CommandType::QUERY != nObjectType + ,sCompleteStatement); + } + + + void ODataAccessObjectTransferable::AddSupportedFormats() + { + sal_Int32 nObjectType = CommandType::COMMAND; + m_aDescriptor[DataAccessDescriptorProperty::CommandType] >>= nObjectType; + switch (nObjectType) + { + case CommandType::TABLE: + AddFormat(SotClipboardFormatId::DBACCESS_TABLE); + break; + case CommandType::QUERY: + AddFormat(SotClipboardFormatId::DBACCESS_QUERY); + break; + case CommandType::COMMAND: + AddFormat(SotClipboardFormatId::DBACCESS_COMMAND); + break; + } + + if (!m_sCompatibleObjectDescription.isEmpty()) + AddFormat(SotClipboardFormatId::SBA_DATAEXCHANGE); + } + + + bool ODataAccessObjectTransferable::GetData( const DataFlavor& rFlavor, const OUString& /*rDestDoc*/ ) + { + SotClipboardFormatId nFormat = SotExchange::GetFormat(rFlavor); + switch (nFormat) + { + case SotClipboardFormatId::DBACCESS_TABLE: + case SotClipboardFormatId::DBACCESS_QUERY: + case SotClipboardFormatId::DBACCESS_COMMAND: + return SetAny( Any(m_aDescriptor.createPropertyValueSequence()) ); + + case SotClipboardFormatId::SBA_DATAEXCHANGE: + return SetString(m_sCompatibleObjectDescription); + default: break; + } + return false; + } + + + bool ODataAccessObjectTransferable::canExtractObjectDescriptor(const DataFlavorExVector& _rFlavors) + { + return std::any_of(_rFlavors.begin(), _rFlavors.end(), + [](const DataFlavorEx& rCheck) { + return SotClipboardFormatId::DBACCESS_TABLE == rCheck.mnSotId + || SotClipboardFormatId::DBACCESS_QUERY == rCheck.mnSotId + || SotClipboardFormatId::DBACCESS_COMMAND == rCheck.mnSotId; + }); + } + + + ODataAccessDescriptor ODataAccessObjectTransferable::extractObjectDescriptor(const TransferableDataHelper& _rData) + { + SotClipboardFormatId nKnownFormatId = SotClipboardFormatId::NONE; + if ( _rData.HasFormat( SotClipboardFormatId::DBACCESS_TABLE ) ) + nKnownFormatId = SotClipboardFormatId::DBACCESS_TABLE; + if ( _rData.HasFormat( SotClipboardFormatId::DBACCESS_QUERY ) ) + nKnownFormatId = SotClipboardFormatId::DBACCESS_QUERY; + if ( _rData.HasFormat( SotClipboardFormatId::DBACCESS_COMMAND ) ) + nKnownFormatId = SotClipboardFormatId::DBACCESS_COMMAND; + + if (SotClipboardFormatId::NONE != nKnownFormatId) + { + // extract the any from the transferable + DataFlavor aFlavor; + bool bSuccess = + SotExchange::GetFormatDataFlavor(nKnownFormatId, aFlavor); + OSL_ENSURE(bSuccess, "OColumnTransferable::extractColumnDescriptor: invalid data format (no flavor)!"); + + Any aDescriptor = _rData.GetAny(aFlavor, OUString()); + + // extract the property value sequence + Sequence< PropertyValue > aDescriptorProps; + bSuccess = aDescriptor >>= aDescriptorProps; + OSL_ENSURE(bSuccess, "OColumnTransferable::extractColumnDescriptor: invalid clipboard format!"); + + // build the real descriptor + return ODataAccessDescriptor(aDescriptorProps); + } + + OSL_FAIL( "OColumnTransferable::extractColumnDescriptor: unsupported formats only!" ); + return ODataAccessDescriptor(); + } + + + void ODataAccessObjectTransferable::addCompatibleSelectionDescription( const Sequence< Any >& _rSelRows ) + { + const sal_Unicode cSeparator(11); + const OUString sSeparator(&cSeparator, 1); + + for ( const Any& rSelRow : _rSelRows ) + { + sal_Int32 nSelectedRow( 0 ); + OSL_VERIFY( rSelRow >>= nSelectedRow ); + + m_sCompatibleObjectDescription += OUString::number(nSelectedRow); + m_sCompatibleObjectDescription += sSeparator; + } + } + + + void ODataAccessObjectTransferable::ObjectReleased() + { + m_aDescriptor.clear(); + } + + void ODataAccessObjectTransferable::construct( const OUString& _rDatasource + ,const OUString& _rConnectionResource + ,const sal_Int32 _nCommandType + ,const OUString& _rCommand + ,const Reference< XConnection >& _rxConnection + ,bool _bAddCommand + ,const OUString& _sActiveCommand) + { + m_aDescriptor.setDataSource(_rDatasource); + // build the descriptor (the property sequence) + if ( !_rConnectionResource.isEmpty() ) + m_aDescriptor[DataAccessDescriptorProperty::ConnectionResource] <<= _rConnectionResource; + if ( _rxConnection.is() ) + m_aDescriptor[DataAccessDescriptorProperty::Connection] <<= _rxConnection; + m_aDescriptor[DataAccessDescriptorProperty::Command] <<= _rCommand; + m_aDescriptor[DataAccessDescriptorProperty::CommandType] <<= _nCommandType; + + // extract the single values from the sequence + + OUString sObjectName = _rCommand; + + // for compatibility: create a string which can be used for the SotClipboardFormatId::SBA_DATAEXCHANGE format + + bool bTreatAsStatement = (CommandType::COMMAND == _nCommandType); + // statements are - in this old and ugly format - described as queries + + const sal_Unicode cSeparator = u'\x000B'; + const OUString sSeparator(&cSeparator, 1); + + const sal_Unicode cTableMark = '1'; + const sal_Unicode cQueryMark = '0'; + + // build the descriptor string + m_sCompatibleObjectDescription += _rDatasource; + m_sCompatibleObjectDescription += sSeparator; + m_sCompatibleObjectDescription += bTreatAsStatement ? OUString() : sObjectName; + m_sCompatibleObjectDescription += sSeparator; + switch (_nCommandType) + { + case CommandType::TABLE: + m_sCompatibleObjectDescription += OUStringChar(cTableMark); + break; + case CommandType::QUERY: + m_sCompatibleObjectDescription += OUStringChar(cQueryMark); + break; + case CommandType::COMMAND: + m_sCompatibleObjectDescription += OUStringChar(cQueryMark); + // think of it as a query + break; + } + m_sCompatibleObjectDescription += sSeparator; + m_sCompatibleObjectDescription += _bAddCommand ? _sActiveCommand : OUString(); + m_sCompatibleObjectDescription += sSeparator; + } + + OMultiColumnTransferable::OMultiColumnTransferable() + { + } + + void OMultiColumnTransferable::setDescriptors(const Sequence< PropertyValue >& rDescriptors) + { + ClearFormats(); + m_aDescriptors = rDescriptors; + } + + SotClipboardFormatId OMultiColumnTransferable::getDescriptorFormatId() + { + static SotClipboardFormatId s_nFormat = static_cast<SotClipboardFormatId>(-1); + if (static_cast<SotClipboardFormatId>(-1) == s_nFormat) + { + s_nFormat = SotExchange::RegisterFormatName("application/x-openoffice;windows_formatname=\"dbaccess.MultipleColumnDescriptorTransfer\""); + OSL_ENSURE(static_cast<SotClipboardFormatId>(-1) != s_nFormat, "OColumnTransferable::getDescriptorFormatId: bad exchange id!"); + } + return s_nFormat; + } + + void OMultiColumnTransferable::AddSupportedFormats() + { + AddFormat(getDescriptorFormatId()); + } + + bool OMultiColumnTransferable::GetData( const DataFlavor& _rFlavor, const OUString& /*rDestDoc*/ ) + { + const SotClipboardFormatId nFormatId = SotExchange::GetFormat(_rFlavor); + if (nFormatId == getDescriptorFormatId()) + { + return SetAny( Any( m_aDescriptors ) ); + } + + return false; + } + + bool OMultiColumnTransferable::canExtractDescriptor(const DataFlavorExVector& _rFlavors) + { + const SotClipboardFormatId nFormatId = getDescriptorFormatId(); + return std::all_of(_rFlavors.begin(), _rFlavors.end(), + [&nFormatId](const DataFlavorEx& rCheck) { return nFormatId == rCheck.mnSotId; }); + } + + Sequence< PropertyValue > OMultiColumnTransferable::extractDescriptor(const TransferableDataHelper& _rData) + { + Sequence< PropertyValue > aList; + if (_rData.HasFormat(getDescriptorFormatId())) + { + // extract the any from the transferable + DataFlavor aFlavor; + bool bSuccess = + SotExchange::GetFormatDataFlavor(getDescriptorFormatId(), aFlavor); + OSL_ENSURE(bSuccess, "OColumnTransferable::extractColumnDescriptor: invalid data format (no flavor)!"); + + _rData.GetAny(aFlavor, OUString()) >>= aList; + } // if (_rData.HasFormat(getDescriptorFormatId())) + return aList; + } + + void OMultiColumnTransferable::ObjectReleased() + { + m_aDescriptors.realloc(0); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/fmcomp/dbaobjectex.cxx b/svx/source/fmcomp/dbaobjectex.cxx new file mode 100644 index 0000000000..e3d22d2c35 --- /dev/null +++ b/svx/source/fmcomp/dbaobjectex.cxx @@ -0,0 +1,140 @@ +/* -*- 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 <svx/dbaobjectex.hxx> +#include <osl/diagnose.h> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <com/sun/star/sdb/XSQLQueryComposerFactory.hpp> +#include <com/sun/star/ucb/XContent.hpp> +#include <sot/formats.hxx> +#include <sot/exchange.hxx> + + +namespace svx +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::sdb; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::ucb; + using namespace ::com::sun::star::sdbcx; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::datatransfer; + + OComponentTransferable::OComponentTransferable() + { + } + + void OComponentTransferable::Update(const OUString& rDatasourceOrLocation + ,const Reference< XContent>& xContent) + { + ClearFormats(); + + m_aDescriptor.setDataSource(rDatasourceOrLocation); + m_aDescriptor[DataAccessDescriptorProperty::Component] <<= xContent; + + AddSupportedFormats(); + } + + SotClipboardFormatId OComponentTransferable::getDescriptorFormatId(bool _bExtractForm) + { + static SotClipboardFormatId s_nReportFormat = static_cast<SotClipboardFormatId>(-1); + static SotClipboardFormatId s_nFormFormat = static_cast<SotClipboardFormatId>(-1); + if ( _bExtractForm && static_cast<SotClipboardFormatId>(-1) == s_nFormFormat ) + { + s_nFormFormat = SotExchange::RegisterFormatName("application/x-openoffice;windows_formatname=\"dbaccess.FormComponentDescriptorTransfer\""); + OSL_ENSURE(static_cast<SotClipboardFormatId>(-1) != s_nFormFormat, "OComponentTransferable::getDescriptorFormatId: bad exchange id!"); + } + else if ( !_bExtractForm && static_cast<SotClipboardFormatId>(-1) == s_nReportFormat) + { + s_nReportFormat = SotExchange::RegisterFormatName("application/x-openoffice;windows_formatname=\"dbaccess.ReportComponentDescriptorTransfer\""); + OSL_ENSURE(static_cast<SotClipboardFormatId>(-1) != s_nReportFormat, "OComponentTransferable::getDescriptorFormatId: bad exchange id!"); + } + return _bExtractForm ? s_nFormFormat : s_nReportFormat; + } + + + void OComponentTransferable::AddSupportedFormats() + { + bool bForm = true; + try + { + Reference<XPropertySet> xProp; + m_aDescriptor[DataAccessDescriptorProperty::Component] >>= xProp; + if ( xProp.is() ) + xProp->getPropertyValue("IsForm") >>= bForm; + } + catch(const Exception&) + {} + AddFormat(getDescriptorFormatId(bForm)); + } + + + bool OComponentTransferable::GetData( const DataFlavor& _rFlavor, const OUString& /*rDestDoc*/ ) + { + const SotClipboardFormatId nFormatId = SotExchange::GetFormat(_rFlavor); + if ( nFormatId == getDescriptorFormatId(true) || nFormatId == getDescriptorFormatId(false) ) + return SetAny( Any( m_aDescriptor.createPropertyValueSequence() ) ); + + return false; + } + + + bool OComponentTransferable::canExtractComponentDescriptor(const DataFlavorExVector& _rFlavors, bool _bForm ) + { + SotClipboardFormatId nFormatId = getDescriptorFormatId(_bForm); + return std::any_of(_rFlavors.begin(), _rFlavors.end(), + [&nFormatId](const DataFlavorEx& rCheck) { return nFormatId == rCheck.mnSotId; }); + } + + + ODataAccessDescriptor OComponentTransferable::extractComponentDescriptor(const TransferableDataHelper& _rData) + { + bool bForm = _rData.HasFormat(getDescriptorFormatId(true)); + if ( bForm || _rData.HasFormat(getDescriptorFormatId(false)) ) + { + // the object has a real descriptor object (not just the old compatible format) + + // extract the any from the transferable + DataFlavor aFlavor; + bool bSuccess = + SotExchange::GetFormatDataFlavor(getDescriptorFormatId(bForm), aFlavor); + OSL_ENSURE(bSuccess, "OComponentTransferable::extractColumnDescriptor: invalid data format (no flavor)!"); + + Any aDescriptor = _rData.GetAny(aFlavor, OUString()); + + // extract the property value sequence + Sequence< PropertyValue > aDescriptorProps; + bSuccess = aDescriptor >>= aDescriptorProps; + OSL_ENSURE(bSuccess, "OComponentTransferable::extractColumnDescriptor: invalid clipboard format!"); + + // build the real descriptor + return ODataAccessDescriptor(aDescriptorProps); + } + + return ODataAccessDescriptor(); + } + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/fmcomp/fmgridcl.cxx b/svx/source/fmcomp/fmgridcl.cxx new file mode 100644 index 0000000000..aa26e412a4 --- /dev/null +++ b/svx/source/fmcomp/fmgridcl.cxx @@ -0,0 +1,2096 @@ +/* -*- 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 <svx/fmgridif.hxx> +#include <fmprop.hxx> +#include <svx/fmtools.hxx> +#include <fmservs.hxx> +#include <fmurl.hxx> +#include <formcontrolfactory.hxx> +#include <gridcell.hxx> +#include <gridcols.hxx> +#include <svx/dbaexchange.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <svx/fmgridcl.hxx> +#include <svx/svxdlg.hxx> +#include <svx/svxids.hrc> +#include <bitmaps.hlst> + +#include <com/sun/star/form/XConfirmDeleteListener.hpp> +#include <com/sun/star/form/XFormComponent.hpp> +#include <com/sun/star/form/XGridColumnFactory.hpp> +#include <com/sun/star/io/XPersistObject.hpp> +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/sdb/RowChangeAction.hpp> +#include <com/sun/star/sdb/XQueriesSupplier.hpp> +#include <com/sun/star/sdbc/DataType.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/sdbc/XPreparedStatement.hpp> +#include <com/sun/star/sdbc/XResultSetUpdate.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <com/sun/star/sdbcx/XDeleteRows.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <com/sun/star/util/XNumberFormats.hpp> +#include <com/sun/star/util/XNumberFormatsSupplier.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/property.hxx> +#include <comphelper/string.hxx> +#include <comphelper/types.hxx> +#include <connectivity/dbtools.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/viewfrm.hxx> +#include <svl/eitem.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/svapp.hxx> +#include <tools/debug.hxx> +#include <tools/multisel.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/help.hxx> +#include <vcl/settings.hxx> +#include <sal/log.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <memory> +#include <string_view> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::view; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdbcx; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::form; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::container; +using namespace ::cppu; +using namespace ::svxform; +using namespace ::svx; +using namespace ::dbtools; + +struct FmGridHeaderData +{ + ODataAccessDescriptor aDropData; + Point aDropPosPixel; + sal_Int8 nDropAction; + Reference< XInterface > xDroppedStatement; + Reference< XInterface > xDroppedResultSet; +}; + +static void InsertMenuItem(weld::Menu& rMenu, int nMenuPos, const OUString& id, const OUString& rText, const OUString& rImgId) +{ + rMenu.insert(nMenuPos, id, rText, &rImgId, nullptr, nullptr, TRISTATE_INDET); +} + +FmGridHeader::FmGridHeader( BrowseBox* pParent, WinBits nWinBits) + :EditBrowserHeader(pParent, nWinBits) + ,DropTargetHelper(this) + ,m_pImpl(new FmGridHeaderData) +{ +} + +FmGridHeader::~FmGridHeader() +{ + disposeOnce(); +} + +void FmGridHeader::dispose() +{ + m_pImpl.reset(); + DropTargetHelper::dispose(); + svt::EditBrowserHeader::dispose(); +} + +sal_uInt16 FmGridHeader::GetModelColumnPos(sal_uInt16 nId) const +{ + return static_cast<FmGridControl*>(GetParent())->GetModelColumnPos(nId); +} + +void FmGridHeader::notifyColumnSelect(sal_uInt16 nColumnId) +{ + sal_uInt16 nPos = GetModelColumnPos(nColumnId); + Reference< XIndexAccess > xColumns = static_cast<FmGridControl*>(GetParent())->GetPeer()->getColumns(); + if ( nPos < xColumns->getCount() ) + { + Reference< XSelectionSupplier > xSelSupplier(xColumns, UNO_QUERY); + if ( xSelSupplier.is() ) + { + Reference< XPropertySet > xColumn; + xColumns->getByIndex(nPos) >>= xColumn; + xSelSupplier->select(Any(xColumn)); + } + } +} + +void FmGridHeader::Select() +{ + EditBrowserHeader::Select(); + notifyColumnSelect(GetCurItemId()); +} + +void FmGridHeader::RequestHelp( const HelpEvent& rHEvt ) +{ + sal_uInt16 nItemId = GetItemId( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) ); + if ( nItemId ) + { + if ( rHEvt.GetMode() & (HelpEventMode::QUICK | HelpEventMode::BALLOON) ) + { + tools::Rectangle aItemRect = GetItemRect( nItemId ); + Point aPt = OutputToScreenPixel( aItemRect.TopLeft() ); + aItemRect.SetLeft( aPt.X() ); + aItemRect.SetTop( aPt.Y() ); + aPt = OutputToScreenPixel( aItemRect.BottomRight() ); + aItemRect.SetRight( aPt.X() ); + aItemRect.SetBottom( aPt.Y() ); + + sal_uInt16 nPos = GetModelColumnPos(nItemId); + Reference< css::container::XIndexContainer > xColumns(static_cast<FmGridControl*>(GetParent())->GetPeer()->getColumns()); + try + { + Reference< css::beans::XPropertySet > xColumn(xColumns->getByIndex(nPos),UNO_QUERY); + OUString aHelpText; + xColumn->getPropertyValue(FM_PROP_HELPTEXT) >>= aHelpText; + if ( aHelpText.isEmpty() ) + xColumn->getPropertyValue(FM_PROP_DESCRIPTION) >>= aHelpText; + if ( !aHelpText.isEmpty() ) + { + if ( rHEvt.GetMode() & HelpEventMode::BALLOON ) + Help::ShowBalloon( this, aItemRect.Center(), aItemRect, aHelpText ); + else + Help::ShowQuickHelp( this, aItemRect, aHelpText ); + return; + } + } + catch(Exception&) + { + return; + } + } + } + EditBrowserHeader::RequestHelp( rHEvt ); +} + +sal_Int8 FmGridHeader::AcceptDrop( const AcceptDropEvent& rEvt ) +{ + // drop allowed in design mode only + if (!static_cast<FmGridControl*>(GetParent())->IsDesignMode()) + return DND_ACTION_NONE; + + // search for recognized formats + const DataFlavorExVector& rFlavors = GetDataFlavorExVector(); + if (OColumnTransferable::canExtractColumnDescriptor(rFlavors, ColumnTransferFormatFlags::COLUMN_DESCRIPTOR | ColumnTransferFormatFlags::FIELD_DESCRIPTOR)) + return rEvt.mnAction; + + return DND_ACTION_NONE; +} + +sal_Int8 FmGridHeader::ExecuteDrop( const ExecuteDropEvent& _rEvt ) +{ + if (!static_cast<FmGridControl*>(GetParent())->IsDesignMode()) + return DND_ACTION_NONE; + + TransferableDataHelper aDroppedData(_rEvt.maDropEvent.Transferable); + + // check the formats + bool bColumnDescriptor = OColumnTransferable::canExtractColumnDescriptor(aDroppedData.GetDataFlavorExVector(), ColumnTransferFormatFlags::COLUMN_DESCRIPTOR); + bool bFieldDescriptor = OColumnTransferable::canExtractColumnDescriptor(aDroppedData.GetDataFlavorExVector(), ColumnTransferFormatFlags::FIELD_DESCRIPTOR); + if (!bColumnDescriptor && !bFieldDescriptor) + { + OSL_FAIL("FmGridHeader::ExecuteDrop: should never have reached this (no extractable format)!"); + return DND_ACTION_NONE; + } + + // extract the descriptor + OUString sDatasource, sCommand, sFieldName,sDatabaseLocation; + sal_Int32 nCommandType = CommandType::COMMAND; + Reference< XPreparedStatement > xStatement; + Reference< XResultSet > xResultSet; + Reference< XPropertySet > xField; + Reference< XConnection > xConnection; + + ODataAccessDescriptor aColumn = OColumnTransferable::extractColumnDescriptor(aDroppedData); + if (aColumn.has(DataAccessDescriptorProperty::DataSource)) aColumn[DataAccessDescriptorProperty::DataSource] >>= sDatasource; + if (aColumn.has(DataAccessDescriptorProperty::DatabaseLocation)) aColumn[DataAccessDescriptorProperty::DatabaseLocation] >>= sDatabaseLocation; + if (aColumn.has(DataAccessDescriptorProperty::Command)) aColumn[DataAccessDescriptorProperty::Command] >>= sCommand; + if (aColumn.has(DataAccessDescriptorProperty::CommandType)) aColumn[DataAccessDescriptorProperty::CommandType] >>= nCommandType; + if (aColumn.has(DataAccessDescriptorProperty::ColumnName)) aColumn[DataAccessDescriptorProperty::ColumnName] >>= sFieldName; + if (aColumn.has(DataAccessDescriptorProperty::ColumnObject))aColumn[DataAccessDescriptorProperty::ColumnObject] >>= xField; + if (aColumn.has(DataAccessDescriptorProperty::Connection)) aColumn[DataAccessDescriptorProperty::Connection] >>= xConnection; + + if ( sFieldName.isEmpty() + || sCommand.isEmpty() + || ( sDatasource.isEmpty() + && sDatabaseLocation.isEmpty() + && !xConnection.is() + ) + ) + { + OSL_FAIL( "FmGridHeader::ExecuteDrop: somebody started a nonsense drag operation!!" ); + return DND_ACTION_NONE; + } + + try + { + // need a connection + if (!xConnection.is()) + { // the transferable did not contain the connection -> build an own one + try + { + OUString sSignificantSource( sDatasource.isEmpty() ? sDatabaseLocation : sDatasource ); + xConnection = getConnection_withFeedback(sSignificantSource, OUString(), OUString(), + static_cast<FmGridControl*>(GetParent())->getContext(), nullptr ); + } + catch(NoSuchElementException&) + { // allowed, means sDatasource isn't a valid data source name... + } + catch(Exception&) + { + OSL_FAIL("FmGridHeader::ExecuteDrop: could not retrieve the database access object !"); + } + + if (!xConnection.is()) + { + OSL_FAIL("FmGridHeader::ExecuteDrop: could not retrieve the database access object !"); + return DND_ACTION_NONE; + } + } + + // try to obtain the column object + if (!xField.is()) + { +#ifdef DBG_UTIL + Reference< XServiceInfo > xServiceInfo(xConnection, UNO_QUERY); + DBG_ASSERT(xServiceInfo.is() && xServiceInfo->supportsService(SRV_SDB_CONNECTION), "FmGridHeader::ExecuteDrop: invalid connection (no database access connection !)"); +#endif + + Reference< XNameAccess > xFields; + switch (nCommandType) + { + case CommandType::TABLE: + { + Reference< XTablesSupplier > xSupplyTables(xConnection, UNO_QUERY); + Reference< XColumnsSupplier > xSupplyColumns; + xSupplyTables->getTables()->getByName(sCommand) >>= xSupplyColumns; + xFields = xSupplyColumns->getColumns(); + } + break; + case CommandType::QUERY: + { + Reference< XQueriesSupplier > xSupplyQueries(xConnection, UNO_QUERY); + Reference< XColumnsSupplier > xSupplyColumns; + xSupplyQueries->getQueries()->getByName(sCommand) >>= xSupplyColumns; + xFields = xSupplyColumns->getColumns(); + } + break; + default: + { + xStatement = xConnection->prepareStatement(sCommand); + // not interested in any results + + Reference< XPropertySet > xStatProps(xStatement,UNO_QUERY); + xStatProps->setPropertyValue("MaxRows", Any(sal_Int32(0))); + + xResultSet = xStatement->executeQuery(); + Reference< XColumnsSupplier > xSupplyCols(xResultSet, UNO_QUERY); + if (xSupplyCols.is()) + xFields = xSupplyCols->getColumns(); + } + } + + if (xFields.is() && xFields->hasByName(sFieldName)) + xFields->getByName(sFieldName) >>= xField; + + if (!xField.is()) + { + ::comphelper::disposeComponent(xStatement); + return DND_ACTION_NONE; + } + } + + // do the drop asynchronously + // (85957 - UI actions within the drop are not allowed, but we want to open a popup menu) + m_pImpl->aDropData = aColumn; + m_pImpl->aDropData[DataAccessDescriptorProperty::Connection] <<= xConnection; + m_pImpl->aDropData[DataAccessDescriptorProperty::ColumnObject] <<= xField; + + m_pImpl->nDropAction = _rEvt.mnAction; + m_pImpl->aDropPosPixel = _rEvt.maPosPixel; + m_pImpl->xDroppedStatement = xStatement; + m_pImpl->xDroppedResultSet = xResultSet; + + PostUserEvent(LINK(this, FmGridHeader, OnAsyncExecuteDrop), nullptr, true); + } + catch (Exception&) + { + TOOLS_WARN_EXCEPTION("svx", "caught an exception while creatin' the column !"); + ::comphelper::disposeComponent(xStatement); + return DND_ACTION_NONE; + } + + return DND_ACTION_LINK; +} + +IMPL_LINK_NOARG( FmGridHeader, OnAsyncExecuteDrop, void*, void ) +{ + OUString sCommand, sFieldName,sURL; + sal_Int32 nCommandType = CommandType::COMMAND; + Reference< XPropertySet > xField; + Reference< XConnection > xConnection; + + OUString sDatasource = m_pImpl->aDropData.getDataSource(); + if ( sDatasource.isEmpty() && m_pImpl->aDropData.has(DataAccessDescriptorProperty::ConnectionResource) ) + m_pImpl->aDropData[DataAccessDescriptorProperty::ConnectionResource] >>= sURL; + m_pImpl->aDropData[DataAccessDescriptorProperty::Command] >>= sCommand; + m_pImpl->aDropData[DataAccessDescriptorProperty::CommandType] >>= nCommandType; + m_pImpl->aDropData[DataAccessDescriptorProperty::ColumnName] >>= sFieldName; + m_pImpl->aDropData[DataAccessDescriptorProperty::Connection] >>= xConnection; + m_pImpl->aDropData[DataAccessDescriptorProperty::ColumnObject] >>= xField; + + try + { + // need number formats + Reference< XNumberFormatsSupplier > xSupplier = getNumberFormats(xConnection, true); + Reference< XNumberFormats > xNumberFormats; + if (xSupplier.is()) + xNumberFormats = xSupplier->getNumberFormats(); + if (!xNumberFormats.is()) + { + ::comphelper::disposeComponent(m_pImpl->xDroppedResultSet); + ::comphelper::disposeComponent(m_pImpl->xDroppedStatement); + return; + } + + // The field now needs two pieces of information: + // a.) Name of the field for label and ControlSource + // b.) FormatKey, to determine which field is to be created + sal_Int32 nDataType = 0; + xField->getPropertyValue(FM_PROP_FIELDTYPE) >>= nDataType; + // these datatypes can not be processed in Gridcontrol + switch (nDataType) + { + case DataType::BLOB: + case DataType::LONGVARBINARY: + case DataType::BINARY: + case DataType::VARBINARY: + case DataType::OTHER: + ::comphelper::disposeComponent(m_pImpl->xDroppedResultSet); + ::comphelper::disposeComponent(m_pImpl->xDroppedStatement); + return; + } + + // Creating the column + Reference< XIndexContainer > xCols(static_cast<FmGridControl*>(GetParent())->GetPeer()->getColumns()); + Reference< XGridColumnFactory > xFactory(xCols, UNO_QUERY); + + sal_uInt16 nColId = GetItemId(m_pImpl->aDropPosPixel); + // insert position, always before the current column + sal_uInt16 nPos = GetModelColumnPos(nColId); + Reference< XPropertySet > xCol, xSecondCol; + + // Create Column based on type, default textfield + std::vector<OUString> aPossibleTypes; + std::vector<OUString> aImgResId; + std::vector<TranslateId> aStrResId; + + switch (nDataType) + { + case DataType::BIT: + case DataType::BOOLEAN: + aPossibleTypes.emplace_back(FM_COL_CHECKBOX); + aImgResId.emplace_back(RID_SVXBMP_CHECKBOX); + aStrResId.emplace_back(RID_STR_PROPTITLE_CHECKBOX); + break; + case DataType::TINYINT: + case DataType::SMALLINT: + case DataType::INTEGER: + aPossibleTypes.emplace_back(FM_COL_NUMERICFIELD); + aImgResId.emplace_back(RID_SVXBMP_NUMERICFIELD); + aStrResId.emplace_back(RID_STR_PROPTITLE_NUMERICFIELD); + aPossibleTypes.emplace_back(FM_COL_FORMATTEDFIELD); + aImgResId.emplace_back(RID_SVXBMP_FORMATTEDFIELD); + aStrResId.emplace_back(RID_STR_PROPTITLE_FORMATTED); + break; + case DataType::REAL: + case DataType::DOUBLE: + case DataType::NUMERIC: + case DataType::DECIMAL: + aPossibleTypes.emplace_back(FM_COL_FORMATTEDFIELD); + aImgResId.emplace_back(RID_SVXBMP_FORMATTEDFIELD); + aStrResId.emplace_back(RID_STR_PROPTITLE_FORMATTED); + aPossibleTypes.emplace_back(FM_COL_NUMERICFIELD); + aImgResId.emplace_back(RID_SVXBMP_NUMERICFIELD); + aStrResId.emplace_back(RID_STR_PROPTITLE_NUMERICFIELD); + break; + case DataType::TIMESTAMP: + aPossibleTypes.emplace_back("dateandtimefield"); + aImgResId.emplace_back(RID_SVXBMP_DATE_N_TIME_FIELDS); + aStrResId.emplace_back(RID_STR_DATE_AND_TIME); + aPossibleTypes.emplace_back(FM_COL_DATEFIELD); + aImgResId.emplace_back(RID_SVXBMP_DATEFIELD); + aStrResId.emplace_back(RID_STR_PROPTITLE_DATEFIELD); + aPossibleTypes.emplace_back(FM_COL_TIMEFIELD); + aImgResId.emplace_back(RID_SVXBMP_TIMEFIELD); + aStrResId.emplace_back(RID_STR_PROPTITLE_TIMEFIELD); + aPossibleTypes.emplace_back(FM_COL_FORMATTEDFIELD); + aImgResId.emplace_back(RID_SVXBMP_FORMATTEDFIELD); + aStrResId.emplace_back(RID_STR_PROPTITLE_FORMATTED); + break; + case DataType::DATE: + aPossibleTypes.emplace_back(FM_COL_DATEFIELD); + aImgResId.emplace_back(RID_SVXBMP_DATEFIELD); + aStrResId.emplace_back(RID_STR_PROPTITLE_DATEFIELD); + aPossibleTypes.emplace_back(FM_COL_FORMATTEDFIELD); + aImgResId.emplace_back(RID_SVXBMP_FORMATTEDFIELD); + aStrResId.emplace_back(RID_STR_PROPTITLE_FORMATTED); + break; + case DataType::TIME: + aPossibleTypes.emplace_back(FM_COL_TIMEFIELD); + aImgResId.emplace_back(RID_SVXBMP_TIMEFIELD); + aStrResId.emplace_back(RID_STR_PROPTITLE_TIMEFIELD); + aPossibleTypes.emplace_back(FM_COL_FORMATTEDFIELD); + aImgResId.emplace_back(RID_SVXBMP_FORMATTEDFIELD); + aStrResId.emplace_back(RID_STR_PROPTITLE_FORMATTED); + break; + case DataType::CHAR: + case DataType::VARCHAR: + case DataType::LONGVARCHAR: + default: + aPossibleTypes.emplace_back(FM_COL_TEXTFIELD); + aImgResId.emplace_back(RID_SVXBMP_EDITBOX); + aStrResId.emplace_back(RID_STR_PROPTITLE_EDIT); + aPossibleTypes.emplace_back(FM_COL_FORMATTEDFIELD); + aImgResId.emplace_back(RID_SVXBMP_FORMATTEDFIELD); + aStrResId.emplace_back(RID_STR_PROPTITLE_FORMATTED); + break; + } + // if it's a currency field, a "currency field" option + try + { + if ( ::comphelper::hasProperty(FM_PROP_ISCURRENCY, xField) + && ::comphelper::getBOOL(xField->getPropertyValue(FM_PROP_ISCURRENCY))) + { + aPossibleTypes.insert(aPossibleTypes.begin(), FM_COL_CURRENCYFIELD); + aImgResId.insert(aImgResId.begin(), RID_SVXBMP_CURRENCYFIELD); + aStrResId.insert(aStrResId.begin(), RID_STR_PROPTITLE_CURRENCYFIELD); + } + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } + + assert(aPossibleTypes.size() == aImgResId.size()); + + bool bDateNTimeCol = false; + if (!aPossibleTypes.empty()) + { + OUString sPreferredType = aPossibleTypes[0]; + if ((m_pImpl->nDropAction == DND_ACTION_LINK) && (aPossibleTypes.size() > 1)) + { + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(nullptr, "svx/ui/colsmenu.ui")); + std::unique_ptr<weld::Menu> xTypeMenu(xBuilder->weld_menu("insertmenu")); + + int nMenuPos = 0; + std::vector<OUString>::const_iterator iter; + std::vector<TranslateId>::const_iterator striter; + std::vector<OUString>::const_iterator imgiter; + for (iter = aPossibleTypes.begin(), imgiter = aImgResId.begin(), striter = aStrResId.begin(); + iter != aPossibleTypes.end(); ++iter, ++striter, ++imgiter) + { + InsertMenuItem(*xTypeMenu, nMenuPos++, *iter, SvxResId(*striter), *imgiter); + } + + ::tools::Rectangle aRect(m_pImpl->aDropPosPixel, Size(1,1)); + weld::Window* pParent = weld::GetPopupParent(*this, aRect); + OUString sResult = xTypeMenu->popup_at_rect(pParent, aRect); + if (!sResult.isEmpty()) + sPreferredType = sResult; + } + + bDateNTimeCol = sPreferredType == "dateandtimefield"; + sal_uInt16 nColCount = bDateNTimeCol ? 2 : 1; + OUString sFieldService; + while (nColCount--) + { + if (bDateNTimeCol) + sPreferredType = nColCount ? FM_COL_DATEFIELD : FM_COL_TIMEFIELD; + + sFieldService = sPreferredType; + Reference< XPropertySet > xThisRoundCol; + if ( !sFieldService.isEmpty() ) + xThisRoundCol = xFactory->createColumn(sFieldService); + if (nColCount) + xSecondCol = xThisRoundCol; + else + xCol = xThisRoundCol; + } + } + + if (!xCol.is() || (bDateNTimeCol && !xSecondCol.is())) + { + ::comphelper::disposeComponent(xCol); // in case only the creation of the second column failed + ::comphelper::disposeComponent(m_pImpl->xDroppedResultSet); + ::comphelper::disposeComponent(m_pImpl->xDroppedStatement); + return; + } + + if (bDateNTimeCol) + { + OUString sTimePostfix(SvxResId(RID_STR_POSTFIX_TIME)); + xCol->setPropertyValue(FM_PROP_LABEL, Any( OUString( sFieldName + sTimePostfix ) ) ); + + OUString sDatePostfix(SvxResId( RID_STR_POSTFIX_DATE)); + xSecondCol->setPropertyValue(FM_PROP_LABEL, Any( OUString( sFieldName + sDatePostfix ) ) ); + } + else + xCol->setPropertyValue(FM_PROP_LABEL, Any(sFieldName)); + + // insert now + Any aElement; + aElement <<= xCol; + + xCols->insertByIndex(nPos, aElement); + + FormControlFactory aControlFactory; + aControlFactory.initializeControlModel( DocumentClassification::classifyHostDocument( xCols ), xCol ); + FormControlFactory::initializeFieldDependentProperties( xField, xCol, xNumberFormats ); + + xCol->setPropertyValue(FM_PROP_CONTROLSOURCE, Any(sFieldName)); + if ( xSecondCol.is() ) + xSecondCol->setPropertyValue(FM_PROP_CONTROLSOURCE, Any(sFieldName)); + + if (bDateNTimeCol) + { + OUString aPostfix[] = { + SvxResId(RID_STR_POSTFIX_DATE), + SvxResId(RID_STR_POSTFIX_TIME) + }; + + for ( size_t i=0; i<2; ++i ) + { + OUString sPurePostfix = comphelper::string::stripStart(aPostfix[i], ' '); + sPurePostfix = comphelper::string::stripStart(sPurePostfix, '('); + sPurePostfix = comphelper::string::stripEnd(sPurePostfix, ')'); + OUString sRealName = sFieldName + "_" + sPurePostfix; + if (i) + xSecondCol->setPropertyValue(FM_PROP_NAME, Any(sRealName)); + else + xCol->setPropertyValue(FM_PROP_NAME, Any(sRealName)); + } + } + else + xCol->setPropertyValue(FM_PROP_NAME, Any(sFieldName)); + + if (bDateNTimeCol) + { + aElement <<= xSecondCol; + xCols->insertByIndex(nPos == sal_uInt16(-1) ? nPos : ++nPos, aElement); + } + + // is the component::Form tied to the database? + Reference< XFormComponent > xFormCp(xCols, UNO_QUERY); + Reference< XPropertySet > xForm(xFormCp->getParent(), UNO_QUERY); + if (xForm.is()) + { + if (::comphelper::getString(xForm->getPropertyValue(FM_PROP_DATASOURCE)).isEmpty()) + { + if ( !sDatasource.isEmpty() ) + xForm->setPropertyValue(FM_PROP_DATASOURCE, Any(sDatasource)); + else + xForm->setPropertyValue(FM_PROP_URL, Any(sURL)); + } + + if (::comphelper::getString(xForm->getPropertyValue(FM_PROP_COMMAND)).isEmpty()) + { + xForm->setPropertyValue(FM_PROP_COMMAND, Any(sCommand)); + Any aCommandType; + switch (nCommandType) + { + case CommandType::TABLE: + aCommandType <<= sal_Int32(CommandType::TABLE); + break; + case CommandType::QUERY: + aCommandType <<= sal_Int32(CommandType::QUERY); + break; + default: + aCommandType <<= sal_Int32(CommandType::COMMAND); + xForm->setPropertyValue(FM_PROP_ESCAPE_PROCESSING, css::uno::Any(2 == nCommandType)); + break; + } + xForm->setPropertyValue(FM_PROP_COMMANDTYPE, aCommandType); + } + } + } + catch (Exception&) + { + TOOLS_WARN_EXCEPTION("svx", "caught an exception while creatin' the column !"); + ::comphelper::disposeComponent(m_pImpl->xDroppedResultSet); + ::comphelper::disposeComponent(m_pImpl->xDroppedStatement); + return; + } + + ::comphelper::disposeComponent(m_pImpl->xDroppedResultSet); + ::comphelper::disposeComponent(m_pImpl->xDroppedStatement); +} + +void FmGridHeader::PreExecuteColumnContextMenu(sal_uInt16 nColId, weld::Menu& rMenu, + weld::Menu& rInsertMenu, weld::Menu& rChangeMenu, + weld::Menu& rShowMenu) +{ + bool bDesignMode = static_cast<FmGridControl*>(GetParent())->IsDesignMode(); + + Reference< css::container::XIndexContainer > xCols(static_cast<FmGridControl*>(GetParent())->GetPeer()->getColumns()); + // Building of the Insert Menu + // mark the column if nColId != HEADERBAR_ITEM_NOTFOUND + if(nColId > 0) + { + sal_uInt16 nPos2 = GetModelColumnPos(nColId); + + Reference< css::container::XIndexContainer > xColumns(static_cast<FmGridControl*>(GetParent())->GetPeer()->getColumns()); + Reference< css::beans::XPropertySet> xColumn( xColumns->getByIndex(nPos2), css::uno::UNO_QUERY); + Reference< css::view::XSelectionSupplier > xSelSupplier(xColumns, UNO_QUERY); + if (xSelSupplier.is()) + xSelSupplier->select(Any(xColumn)); + } + + // insert position, always before the current column + sal_uInt16 nPos = GetModelColumnPos(nColId); + bool bMarked = nColId && static_cast<FmGridControl*>(GetParent())->isColumnMarked(nColId); + + if (bDesignMode) + { + int nMenuPos = 0; + InsertMenuItem(rInsertMenu, nMenuPos++, FM_COL_TEXTFIELD, SvxResId(RID_STR_PROPTITLE_EDIT), RID_SVXBMP_EDITBOX); + InsertMenuItem(rInsertMenu, nMenuPos++, FM_COL_CHECKBOX, SvxResId(RID_STR_PROPTITLE_CHECKBOX), RID_SVXBMP_CHECKBOX); + InsertMenuItem(rInsertMenu, nMenuPos++, FM_COL_COMBOBOX, SvxResId(RID_STR_PROPTITLE_COMBOBOX), RID_SVXBMP_COMBOBOX); + InsertMenuItem(rInsertMenu, nMenuPos++, FM_COL_LISTBOX, SvxResId(RID_STR_PROPTITLE_LISTBOX), RID_SVXBMP_LISTBOX); + InsertMenuItem(rInsertMenu, nMenuPos++, FM_COL_DATEFIELD, SvxResId(RID_STR_PROPTITLE_DATEFIELD), RID_SVXBMP_DATEFIELD); + InsertMenuItem(rInsertMenu, nMenuPos++, FM_COL_TIMEFIELD, SvxResId(RID_STR_PROPTITLE_TIMEFIELD), RID_SVXBMP_TIMEFIELD); + InsertMenuItem(rInsertMenu, nMenuPos++, FM_COL_NUMERICFIELD, SvxResId(RID_STR_PROPTITLE_NUMERICFIELD), RID_SVXBMP_NUMERICFIELD); + InsertMenuItem(rInsertMenu, nMenuPos++, FM_COL_CURRENCYFIELD, SvxResId(RID_STR_PROPTITLE_CURRENCYFIELD), RID_SVXBMP_CURRENCYFIELD); + InsertMenuItem(rInsertMenu, nMenuPos++, FM_COL_PATTERNFIELD, SvxResId(RID_STR_PROPTITLE_PATTERNFIELD), RID_SVXBMP_PATTERNFIELD); + InsertMenuItem(rInsertMenu, nMenuPos++, FM_COL_FORMATTEDFIELD, SvxResId(RID_STR_PROPTITLE_FORMATTED), RID_SVXBMP_FORMATTEDFIELD); + } + + if (xCols.is() && nColId) + { + Reference< css::beans::XPropertySet > xPropSet( xCols->getByIndex(nPos), css::uno::UNO_QUERY); + + Reference< css::io::XPersistObject > xServiceQuestion(xPropSet, UNO_QUERY); + sal_Int32 nColType = xServiceQuestion.is() ? getColumnTypeByModelName(xServiceQuestion->getServiceName()) : 0; + if (nColType == TYPE_TEXTFIELD) + { // edit fields and formatted fields have the same service name, thus getColumnTypeByModelName returns TYPE_TEXTFIELD + // in both cases. And as columns don't have a css::lang::XServiceInfo interface, we have to distinguish both + // types via the existence of special properties + if (xPropSet.is()) + { + Reference< css::beans::XPropertySetInfo > xPropsInfo = xPropSet->getPropertySetInfo(); + if (xPropsInfo.is() && xPropsInfo->hasPropertyByName(FM_PROP_FORMATSSUPPLIER)) + nColType = TYPE_FORMATTEDFIELD; + } + } + + if (bDesignMode) + { + int nMenuPos = 0; + if (nColType != TYPE_TEXTFIELD) + InsertMenuItem(rChangeMenu, nMenuPos++, FM_COL_TEXTFIELD"1", SvxResId(RID_STR_PROPTITLE_EDIT), RID_SVXBMP_EDITBOX); + if (nColType != TYPE_CHECKBOX) + InsertMenuItem(rChangeMenu, nMenuPos++, FM_COL_CHECKBOX"1", SvxResId(RID_STR_PROPTITLE_CHECKBOX), RID_SVXBMP_CHECKBOX); + if (nColType != TYPE_COMBOBOX) + InsertMenuItem(rChangeMenu, nMenuPos++, FM_COL_COMBOBOX"1", SvxResId(RID_STR_PROPTITLE_COMBOBOX), RID_SVXBMP_COMBOBOX); + if (nColType != TYPE_LISTBOX) + InsertMenuItem(rChangeMenu, nMenuPos++, FM_COL_LISTBOX"1", SvxResId(RID_STR_PROPTITLE_LISTBOX), RID_SVXBMP_LISTBOX); + if (nColType != TYPE_DATEFIELD) + InsertMenuItem(rChangeMenu, nMenuPos++, FM_COL_DATEFIELD"1", SvxResId(RID_STR_PROPTITLE_DATEFIELD), RID_SVXBMP_DATEFIELD); + if (nColType != TYPE_TIMEFIELD) + InsertMenuItem(rChangeMenu, nMenuPos++, FM_COL_TIMEFIELD"1", SvxResId(RID_STR_PROPTITLE_TIMEFIELD), RID_SVXBMP_TIMEFIELD); + if (nColType != TYPE_NUMERICFIELD) + InsertMenuItem(rChangeMenu, nMenuPos++, FM_COL_NUMERICFIELD"1", SvxResId(RID_STR_PROPTITLE_NUMERICFIELD), RID_SVXBMP_NUMERICFIELD); + if (nColType != TYPE_CURRENCYFIELD) + InsertMenuItem(rChangeMenu, nMenuPos++, FM_COL_CURRENCYFIELD"1", SvxResId(RID_STR_PROPTITLE_CURRENCYFIELD), RID_SVXBMP_CURRENCYFIELD); + if (nColType != TYPE_PATTERNFIELD) + InsertMenuItem(rChangeMenu, nMenuPos++, FM_COL_PATTERNFIELD"1", SvxResId(RID_STR_PROPTITLE_PATTERNFIELD), RID_SVXBMP_PATTERNFIELD); + if (nColType != TYPE_FORMATTEDFIELD) + InsertMenuItem(rChangeMenu, nMenuPos++, FM_COL_FORMATTEDFIELD"1", SvxResId(RID_STR_PROPTITLE_FORMATTED), RID_SVXBMP_FORMATTEDFIELD); + } + + + rMenu.set_visible("change", bDesignMode && bMarked && xCols.is()); + rMenu.set_sensitive("change", bDesignMode && bMarked && xCols.is()); + } + else + { + rMenu.set_visible("change", false); + rMenu.set_sensitive("change", false); + } + + rMenu.set_visible("insert", bDesignMode && xCols.is()); + rMenu.set_sensitive("insert", bDesignMode && xCols.is()); + rMenu.set_visible("delete", bDesignMode && bMarked && xCols.is()); + rMenu.set_sensitive("delete", bDesignMode && bMarked && xCols.is()); + rMenu.set_visible("column", bDesignMode && bMarked && xCols.is()); + rMenu.set_sensitive("column", bDesignMode && bMarked && xCols.is()); + + sal_uInt16 nHiddenCols = 0; + if (xCols.is()) + { + // check for hidden cols + Reference< css::beans::XPropertySet > xCurCol; + Any aHidden,aName; + for (sal_Int32 i=0; i<xCols->getCount(); ++i) + { + xCurCol.set(xCols->getByIndex(i), css::uno::UNO_QUERY); + DBG_ASSERT(xCurCol.is(), "FmGridHeader::PreExecuteColumnContextMenu : the Peer has invalid columns !"); + aHidden = xCurCol->getPropertyValue(FM_PROP_HIDDEN); + DBG_ASSERT(aHidden.getValueType().getTypeClass() == TypeClass_BOOLEAN, + "FmGridHeader::PreExecuteColumnContextMenu : the property 'hidden' should be boolean !"); + if (::comphelper::getBOOL(aHidden)) + { + // put the column name into the 'show col' menu + if (nHiddenCols < 16) + { + // (only the first 16 items to keep the menu rather small) + aName = xCurCol->getPropertyValue(FM_PROP_LABEL); + // the ID is arbitrary, but should be unique within the whole menu + rMenu.insert(nHiddenCols, OUString::number(nHiddenCols + 1), ::comphelper::getString(aName), + nullptr, nullptr, nullptr, TRISTATE_INDET); + } + ++nHiddenCols; + } + } + } + rShowMenu.set_visible("more", xCols.is() && (nHiddenCols > 16)); + rMenu.set_visible("show", xCols.is() && (nHiddenCols > 0)); + rMenu.set_sensitive("show", xCols.is() && (nHiddenCols > 0)); + + // allow the 'hide column' item ? + bool bAllowHide = bMarked; // a column is marked + bAllowHide = bAllowHide || (!bDesignMode && (nPos != sal_uInt16(-1))); // OR we are in alive mode and have hit a column + bAllowHide = bAllowHide && xCols.is(); // AND we have a column container + bAllowHide = bAllowHide && (xCols->getCount()-nHiddenCols > 1); // AND there are at least two visible columns + rMenu.set_visible("hide", bAllowHide); + rMenu.set_sensitive("hide", bAllowHide); + + if (!bMarked) + return; + + SfxViewFrame* pCurrentFrame = SfxViewFrame::Current(); + // ask the bindings of the current view frame (which should be the one we're residing in) for the state + if (pCurrentFrame) + { + std::unique_ptr<SfxBoolItem> pItem; + SfxItemState eState = pCurrentFrame->GetBindings().QueryState(SID_FM_CTL_PROPERTIES, pItem); + + if (eState >= SfxItemState::DEFAULT && pItem != nullptr) + { + bool bChecked = pItem && pItem->GetValue(); + rMenu.set_active("column", bChecked); + } + } +} + +namespace { + +enum InspectorAction { eOpenInspector, eCloseInspector, eUpdateInspector, eNone }; + +} + +void FmGridHeader::PostExecuteColumnContextMenu(sal_uInt16 nColId, const weld::Menu& rMenu, const OUString& rExecutionResult) +{ + Reference< css::container::XIndexContainer > xCols(static_cast<FmGridControl*>(GetParent())->GetPeer()->getColumns()); + sal_uInt16 nPos = GetModelColumnPos(nColId); + + OUString aFieldType; + bool bReplace = false; + InspectorAction eInspectorAction = eNone; + + if (rExecutionResult == "delete") + { + Reference< XInterface > xCol( + xCols->getByIndex(nPos), css::uno::UNO_QUERY); + xCols->removeByIndex(nPos); + ::comphelper::disposeComponent(xCol); + } + else if (rExecutionResult == "hide") + { + Reference< css::beans::XPropertySet > xCurCol( xCols->getByIndex(nPos), css::uno::UNO_QUERY); + xCurCol->setPropertyValue(FM_PROP_HIDDEN, Any(true)); + } + else if (rExecutionResult == "column") + { + eInspectorAction = rMenu.get_active("column") ? eOpenInspector : eCloseInspector; + } + else if (rExecutionResult.startsWith(FM_COL_TEXTFIELD)) + { + if (rExecutionResult != FM_COL_TEXTFIELD) + bReplace = true; + aFieldType = FM_COL_TEXTFIELD; + } + else if (rExecutionResult.startsWith(FM_COL_COMBOBOX)) + { + if (rExecutionResult != FM_COL_COMBOBOX) + bReplace = true; + aFieldType = FM_COL_COMBOBOX; + } + else if (rExecutionResult.startsWith(FM_COL_LISTBOX)) + { + if (rExecutionResult != FM_COL_LISTBOX) + bReplace = true; + aFieldType = FM_COL_LISTBOX; + } + else if (rExecutionResult.startsWith(FM_COL_CHECKBOX)) + { + if (rExecutionResult != FM_COL_CHECKBOX) + bReplace = true; + aFieldType = FM_COL_CHECKBOX; + } + else if (rExecutionResult.startsWith(FM_COL_DATEFIELD)) + { + if (rExecutionResult != FM_COL_DATEFIELD) + bReplace = true; + aFieldType = FM_COL_DATEFIELD; + } + else if (rExecutionResult.startsWith(FM_COL_TIMEFIELD)) + { + if (rExecutionResult != FM_COL_TIMEFIELD) + bReplace = true; + aFieldType = FM_COL_TIMEFIELD; + } + else if (rExecutionResult.startsWith(FM_COL_NUMERICFIELD)) + { + if (rExecutionResult != FM_COL_NUMERICFIELD) + bReplace = true; + aFieldType = FM_COL_NUMERICFIELD; + } + else if (rExecutionResult.startsWith(FM_COL_CURRENCYFIELD)) + { + if (rExecutionResult != FM_COL_CURRENCYFIELD) + bReplace = true; + aFieldType = FM_COL_CURRENCYFIELD; + } + else if (rExecutionResult.startsWith(FM_COL_PATTERNFIELD)) + { + if (rExecutionResult != FM_COL_PATTERNFIELD) + bReplace = true; + aFieldType = FM_COL_PATTERNFIELD; + } + else if (rExecutionResult.startsWith(FM_COL_FORMATTEDFIELD)) + { + if (rExecutionResult != FM_COL_FORMATTEDFIELD) + bReplace = true; + aFieldType = FM_COL_FORMATTEDFIELD; + } + else if (rExecutionResult == "more") + { + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractFmShowColsDialog> pDlg(pFact->CreateFmShowColsDialog(GetFrameWeld())); + pDlg->SetColumns(xCols); + pDlg->Execute(); + } + else if (rExecutionResult == "all") + { + // just iterate through all the cols ... + Reference< css::beans::XPropertySet > xCurCol; + for (sal_Int32 i=0; i<xCols->getCount(); ++i) + { + xCurCol.set(xCols->getByIndex(i), css::uno::UNO_QUERY); + xCurCol->setPropertyValue(FM_PROP_HIDDEN, Any(false)); + } + // TODO : there must be a more clever way to do this... + // with the above the view is updated after every single model update ... + } + else if (!rExecutionResult.isEmpty()) + { + sal_Int32 nExecutionResult = rExecutionResult.toInt32(); + if (nExecutionResult>0 && nExecutionResult<=16) + { + // it was a "show column/<colname>" command (there are at most 16 such items) + // search the nExecutionResult'th hidden col + Reference< css::beans::XPropertySet > xCurCol; + for (sal_Int32 i=0; i<xCols->getCount() && nExecutionResult; ++i) + { + xCurCol.set(xCols->getByIndex(i), css::uno::UNO_QUERY); + Any aHidden = xCurCol->getPropertyValue(FM_PROP_HIDDEN); + if (::comphelper::getBOOL(aHidden)) + if (!--nExecutionResult) + { + xCurCol->setPropertyValue(FM_PROP_HIDDEN, Any(false)); + break; + } + } + } + } + + if ( !aFieldType.isEmpty() ) + { + try + { + Reference< XGridColumnFactory > xFactory( xCols, UNO_QUERY_THROW ); + Reference< XPropertySet > xNewCol( xFactory->createColumn( aFieldType ), UNO_SET_THROW ); + + if ( bReplace ) + { + // rescue over a few properties + Reference< XPropertySet > xReplaced( xCols->getByIndex( nPos ), UNO_QUERY ); + + TransferFormComponentProperties( + xReplaced, xNewCol, Application::GetSettings().GetUILanguageTag().getLocale() ); + + xCols->replaceByIndex( nPos, Any( xNewCol ) ); + ::comphelper::disposeComponent( xReplaced ); + + eInspectorAction = eUpdateInspector; + } + else + { + FormControlFactory factory; + + OUString sLabel = FormControlFactory::getDefaultUniqueName_ByComponentType( + Reference< XNameAccess >( xCols, UNO_QUERY_THROW ), xNewCol ); + xNewCol->setPropertyValue( FM_PROP_LABEL, Any( sLabel ) ); + xNewCol->setPropertyValue( FM_PROP_NAME, Any( sLabel ) ); + + factory.initializeControlModel( DocumentClassification::classifyHostDocument( xCols ), xNewCol ); + + xCols->insertByIndex( nPos, Any( xNewCol ) ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + SfxViewFrame* pCurrentFrame = SfxViewFrame::Current(); + OSL_ENSURE( pCurrentFrame, "FmGridHeader::PostExecuteColumnContextMenu: no view frame -> no bindings -> no property browser!" ); + if ( !pCurrentFrame ) + return; + + if ( eInspectorAction == eUpdateInspector ) + { + if ( !pCurrentFrame->HasChildWindow( SID_FM_SHOW_PROPERTIES ) ) + eInspectorAction = eNone; + } + + if ( eInspectorAction != eNone ) + { + SfxBoolItem aShowItem( SID_FM_SHOW_PROPERTIES, eInspectorAction != eCloseInspector ); + + pCurrentFrame->GetBindings().GetDispatcher()->ExecuteList( + SID_FM_SHOW_PROPERTY_BROWSER, SfxCallMode::ASYNCHRON, + { &aShowItem }); + } +} + +void FmGridHeader::triggerColumnContextMenu( const ::Point& _rPreferredPos ) +{ + // the affected col + sal_uInt16 nColId = GetItemId( _rPreferredPos ); + + // the menu + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(nullptr, "svx/ui/colsmenu.ui")); + std::unique_ptr<weld::Menu> xContextMenu(xBuilder->weld_menu("menu")); + std::unique_ptr<weld::Menu> xInsertMenu(xBuilder->weld_menu("insertmenu")); + std::unique_ptr<weld::Menu> xChangeMenu(xBuilder->weld_menu("changemenu")); + std::unique_ptr<weld::Menu> xShowMenu(xBuilder->weld_menu("showmenu")); + + // let derivatives modify the menu + PreExecuteColumnContextMenu(nColId, *xContextMenu, *xInsertMenu, *xChangeMenu, *xShowMenu); + + bool bEmpty = true; + for (int i = 0, nCount = xContextMenu->n_children(); i < nCount; ++i) + { + bEmpty = !xContextMenu->get_sensitive(xContextMenu->get_id(i)); + if (!bEmpty) + break; + } + if (bEmpty) + return; + + // execute the menu + ::tools::Rectangle aRect(_rPreferredPos, Size(1,1)); + weld::Window* pParent = weld::GetPopupParent(*this, aRect); + OUString sResult = xContextMenu->popup_at_rect(pParent, aRect); + + // let derivatives handle the result + PostExecuteColumnContextMenu(nColId, *xContextMenu, sResult); +} + +void FmGridHeader::Command(const CommandEvent& rEvt) +{ + switch (rEvt.GetCommand()) + { + case CommandEventId::ContextMenu: + { + if (!rEvt.IsMouseEvent()) + return; + + triggerColumnContextMenu( rEvt.GetMousePosPixel() ); + } + break; + default: + EditBrowserHeader::Command(rEvt); + } +} + +FmGridControl::FmGridControl( + const Reference< css::uno::XComponentContext >& _rxContext, + vcl::Window* pParent, + FmXGridPeer* _pPeer, + WinBits nBits) + :DbGridControl(_rxContext, pParent, nBits) + ,m_pPeer(_pPeer) + ,m_nCurrentSelectedColumn(-1) + ,m_nMarkedColumnId(BROWSER_INVALIDID) + ,m_bSelecting(false) + ,m_bInColumnMove(false) +{ + EnableInteractiveRowHeight( ); +} + +void FmGridControl::Command(const CommandEvent& _rEvt) +{ + if ( CommandEventId::ContextMenu == _rEvt.GetCommand() ) + { + FmGridHeader* pMyHeader = static_cast< FmGridHeader* >( GetHeaderBar() ); + if ( pMyHeader && !_rEvt.IsMouseEvent() ) + { // context menu requested by keyboard + if ( 1 == GetSelectColumnCount() || IsDesignMode() ) + { + sal_uInt16 nSelId = GetColumnId( + sal::static_int_cast< sal_uInt16 >( FirstSelectedColumn() ) ); + ::tools::Rectangle aColRect( GetFieldRectPixel( 0, nSelId, false ) ); + + Point aRelativePos( pMyHeader->ScreenToOutputPixel( OutputToScreenPixel( aColRect.TopCenter() ) ) ); + pMyHeader->triggerColumnContextMenu(aRelativePos); + + // handled + return; + } + } + } + + DbGridControl::Command( _rEvt ); +} + +// css::beans::XPropertyChangeListener +void FmGridControl::propertyChange(const css::beans::PropertyChangeEvent& evt) +{ + if (evt.PropertyName == FM_PROP_ROWCOUNT) + { + // if we're not in the main thread call AdjustRows asynchronously + implAdjustInSolarThread(true); + return; + } + + const DbGridRowRef& xRow = GetCurrentRow(); + // no adjustment of the properties is carried out during positioning + Reference<XPropertySet> xSet(evt.Source,UNO_QUERY); + if (!(xRow.is() && (::cppu::any2bool(xSet->getPropertyValue(FM_PROP_ISNEW))|| CompareBookmark(getDataSource()->getBookmark(), xRow->GetBookmark())))) + return; + + if (evt.PropertyName == FM_PROP_ISMODIFIED) + { + // modified or clean ? + GridRowStatus eStatus = ::comphelper::getBOOL(evt.NewValue) ? GridRowStatus::Modified : GridRowStatus::Clean; + if (eStatus != xRow->GetStatus()) + { + xRow->SetStatus(eStatus); + SolarMutexGuard aGuard; + RowModified(GetCurrentPos()); + } + } +} + +void FmGridControl::SetDesignMode(bool bMode) +{ + bool bOldMode = IsDesignMode(); + DbGridControl::SetDesignMode(bMode); + if (bOldMode == bMode) + return; + + if (!bMode) + { + // cancel selection + markColumn(USHRT_MAX); + } + else + { + Reference< css::container::XIndexContainer > xColumns(GetPeer()->getColumns()); + Reference< css::view::XSelectionSupplier > xSelSupplier(xColumns, UNO_QUERY); + if (xSelSupplier.is()) + { + Any aSelection = xSelSupplier->getSelection(); + Reference< css::beans::XPropertySet > xColumn; + if (aSelection.getValueType().getTypeClass() == TypeClass_INTERFACE) + xColumn.set(aSelection, css::uno::UNO_QUERY); + Reference< XInterface > xCurrent; + for (sal_Int32 i=0; i<xColumns->getCount(); ++i) + { + xCurrent.set(xColumns->getByIndex(i), css::uno::UNO_QUERY); + if (xCurrent == xColumn) + { + markColumn(GetColumnIdFromModelPos(i)); + break; + } + } + } + } +} + +void FmGridControl::DeleteSelectedRows() +{ + if (!m_pSeekCursor) + return; + + // how many rows are selected? + sal_Int32 nSelectedRows = GetSelectRowCount(); + + // the current line should be deleted but it is currently in edit mode + if ( IsCurrentAppending() ) + return; + // is the insert row selected + if (GetEmptyRow().is() && IsRowSelected(GetRowCount() - 1)) + nSelectedRows -= 1; + + // nothing to do + if (nSelectedRows <= 0) + return; + + // try to confirm the delete + Reference< css::frame::XDispatchProvider > xDispatcher = static_cast<css::frame::XDispatchProvider*>(GetPeer()); + if (xDispatcher.is()) + { + css::util::URL aUrl; + aUrl.Complete = FMURL_CONFIRM_DELETION; + Reference< css::util::XURLTransformer > xTransformer( + css::util::URLTransformer::create(::comphelper::getProcessComponentContext()) ); + xTransformer->parseStrict( aUrl ); + + Reference< css::frame::XDispatch > xDispatch = xDispatcher->queryDispatch(aUrl, OUString(), 0); + Reference< css::form::XConfirmDeleteListener > xConfirm(xDispatch, UNO_QUERY); + if (xConfirm.is()) + { + css::sdb::RowChangeEvent aEvent; + aEvent.Source = Reference< XInterface >(*getDataSource()); + aEvent.Rows = nSelectedRows; + aEvent.Action = css::sdb::RowChangeAction::DELETE; + if (!xConfirm->confirmDelete(aEvent)) + return; + } + } + + const MultiSelection* pRowSelection = GetSelection(); + if ( pRowSelection && pRowSelection->IsAllSelected() ) + { + BeginCursorAction(); + CursorWrapper* pCursor = getDataSource(); + Reference< XResultSetUpdate > xUpdateCursor(Reference< XInterface >(*pCursor), UNO_QUERY); + try + { + pCursor->beforeFirst(); + while( pCursor->next() ) + xUpdateCursor->deleteRow(); + + SetUpdateMode(false); + SetNoSelection(); + + xUpdateCursor->moveToInsertRow(); + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("svx", "Exception caught while deleting rows!"); + } + // adapt to the data cursor + AdjustDataSource(true); + EndCursorAction(); + SetUpdateMode(true); + } + else + { + Reference< css::sdbcx::XDeleteRows > xDeleteThem(Reference< XInterface >(*getDataSource()), UNO_QUERY); + + // collect the bookmarks of the selected rows + Sequence < Any> aBookmarks = getSelectionBookmarks(); + + // determine the next row to position after deletion + Any aBookmark; + bool bNewPos = false; + // if the current row isn't selected we take the row as row after deletion + OSL_ENSURE( GetCurrentRow().is(), "FmGridControl::DeleteSelectedRows: no current row here?" ); + // crash reports suggest it can happen we don't have a current row - how? + // #154303# / 2008-04-23 / frank.schoenheit@sun.com + if ( !IsRowSelected( GetCurrentPos() ) && !IsCurrentAppending() && GetCurrentRow().is() ) + { + aBookmark = GetCurrentRow()->GetBookmark(); + bNewPos = true; + } + else + { + // we look for the first row after the selected block for selection + tools::Long nIdx = LastSelectedRow() + 1; + if (nIdx < GetRowCount() - 1) + { + // there is a next row to position on + if (SeekCursor(nIdx)) + { + GetSeekRow()->SetState(m_pSeekCursor.get(), true); + + bNewPos = true; + // if it's not the row for inserting we keep the bookmark + if (!IsInsertionRow(nIdx)) + aBookmark = m_pSeekCursor->getBookmark(); + } + } + else + { + // we look for the first row before the selected block for selection after deletion + nIdx = FirstSelectedRow() - 1; + if (nIdx >= 0 && SeekCursor(nIdx)) + { + GetSeekRow()->SetState(m_pSeekCursor.get(), true); + + bNewPos = true; + aBookmark = m_pSeekCursor->getBookmark(); + } + } + } + + // Are all rows selected? + // Second condition if no insertion line exists + bool bAllSelected = GetTotalCount() == nSelectedRows || GetRowCount() == nSelectedRows; + + BeginCursorAction(); + + // now delete the row + Sequence<sal_Int32> aDeletedRows; + SetUpdateMode( false ); + try + { + aDeletedRows = xDeleteThem->deleteRows(aBookmarks); + } + catch(SQLException&) + { + } + SetUpdateMode( true ); + + // how many rows are deleted? + sal_Int32 nDeletedRows = static_cast<sal_Int32>(std::count_if(std::cbegin(aDeletedRows), std::cend(aDeletedRows), + [](const sal_Int32 nRow) { return nRow != 0; })); + + // have rows been deleted? + if (nDeletedRows) + { + SetUpdateMode(false); + SetNoSelection(); + try + { + // did we delete all the rows than try to move to the next possible row + if (nDeletedRows == aDeletedRows.getLength()) + { + // there exists a new position to move on + if (bNewPos) + { + if (aBookmark.hasValue()) + getDataSource()->moveToBookmark(aBookmark); + // no valid bookmark so move to the insert row + else + { + Reference< XResultSetUpdate > xUpdateCursor(Reference< XInterface >(*m_pDataCursor), UNO_QUERY); + xUpdateCursor->moveToInsertRow(); + } + } + else + { + Reference< css::beans::XPropertySet > xSet(Reference< XInterface >(*m_pDataCursor), UNO_QUERY); + + sal_Int32 nRecordCount(0); + xSet->getPropertyValue(FM_PROP_ROWCOUNT) >>= nRecordCount; + if ( m_pDataCursor->rowDeleted() ) + --nRecordCount; + + // there are no rows left and we have an insert row + if (!nRecordCount && GetEmptyRow().is()) + { + Reference< XResultSetUpdate > xUpdateCursor(Reference< XInterface >(*m_pDataCursor), UNO_QUERY); + xUpdateCursor->moveToInsertRow(); + } + else if (nRecordCount) + // move to the first row + getDataSource()->first(); + } + } + // not all the rows where deleted, so move to the first row which remained in the resultset + else + { + auto pRow = std::find(std::cbegin(aDeletedRows), std::cend(aDeletedRows), 0); + if (pRow != std::cend(aDeletedRows)) + { + auto i = static_cast<sal_Int32>(std::distance(std::cbegin(aDeletedRows), pRow)); + getDataSource()->moveToBookmark(aBookmarks[i]); + } + } + } + catch(const Exception&) + { + try + { + // positioning went wrong so try to move to the first row + getDataSource()->first(); + } + catch(const Exception&) + { + } + } + + // adapt to the data cursor + AdjustDataSource(true); + + // not all rows could be deleted; + // never select again there the ones that could not be deleted + if (nDeletedRows < nSelectedRows) + { + // were all selected + if (bAllSelected) + { + SelectAll(); + if (IsInsertionRow(GetRowCount() - 1)) // not the insertion row + SelectRow(GetRowCount() - 1, false); + } + else + { + // select the remaining rows + for (const sal_Int32 nSuccess : std::as_const(aDeletedRows)) + { + try + { + if (!nSuccess) + { + m_pSeekCursor->moveToBookmark(m_pDataCursor->getBookmark()); + SetSeekPos(m_pSeekCursor->getRow() - 1); + SelectRow(GetSeekPos()); + } + } + catch(const Exception&) + { + // keep the seekpos in all cases + SetSeekPos(m_pSeekCursor->getRow() - 1); + } + } + } + } + + EndCursorAction(); + SetUpdateMode(true); + } + else // row could not be deleted + { + EndCursorAction(); + try + { + // currentrow is the insert row? + if (!IsCurrentAppending()) + getDataSource()->refreshRow(); + } + catch(const Exception&) + { + } + } + } + + // if there is no selection anymore we can start editing + if (!GetSelectRowCount()) + ActivateCell(); +} + +// XCurrentRecordListener +void FmGridControl::positioned() +{ + SAL_INFO("svx.fmcomp", "FmGridControl::positioned"); + // position on the data source (force it to be done in the main thread) + implAdjustInSolarThread(false); +} + +bool FmGridControl::commit() +{ + // execute commit only if an update is not already executed by the + // css::form::component::GridControl + if (!IsUpdating()) + { + if (Controller().is() && Controller()->IsValueChangedFromSaved()) + { + if (!SaveModified()) + return false; + } + } + return true; +} + +void FmGridControl::inserted() +{ + const DbGridRowRef& xRow = GetCurrentRow(); + if (!xRow.is()) + return; + + // line has been inserted, then reset the status and mode + xRow->SetState(m_pDataCursor.get(), false); + xRow->SetNew(false); + +} + +VclPtr<BrowserHeader> FmGridControl::imp_CreateHeaderBar(BrowseBox* pParent) +{ + DBG_ASSERT( pParent == this, "FmGridControl::imp_CreateHeaderBar: parent?" ); + return VclPtr<FmGridHeader>::Create( pParent ); +} + +void FmGridControl::markColumn(sal_uInt16 nId) +{ + if (!(GetHeaderBar() && m_nMarkedColumnId != nId)) + return; + + // deselect + if (m_nMarkedColumnId != BROWSER_INVALIDID) + { + HeaderBarItemBits aBits = GetHeaderBar()->GetItemBits(m_nMarkedColumnId) & ~HeaderBarItemBits::FLAT; + GetHeaderBar()->SetItemBits(m_nMarkedColumnId, aBits); + } + + + if (nId != BROWSER_INVALIDID) + { + HeaderBarItemBits aBits = GetHeaderBar()->GetItemBits(nId) | HeaderBarItemBits::FLAT; + GetHeaderBar()->SetItemBits(nId, aBits); + } + m_nMarkedColumnId = nId; +} + +bool FmGridControl::isColumnMarked(sal_uInt16 nId) const +{ + return m_nMarkedColumnId == nId; +} + +tools::Long FmGridControl::QueryMinimumRowHeight() +{ + tools::Long const nMinimalLogicHeight = 20; // 0.2 cm + tools::Long nMinimalPixelHeight = LogicToPixel(Point(0, nMinimalLogicHeight), MapMode(MapUnit::Map10thMM)).Y(); + return CalcZoom( nMinimalPixelHeight ); +} + +void FmGridControl::RowHeightChanged() +{ + DbGridControl::RowHeightChanged(); + + Reference< XPropertySet > xModel( GetPeer()->getColumns(), UNO_QUERY ); + DBG_ASSERT( xModel.is(), "FmGridControl::RowHeightChanged: no model!" ); + if ( !xModel.is() ) + return; + + try + { + sal_Int32 nUnzoomedPixelHeight = CalcReverseZoom( GetDataRowHeight() ); + Any aProperty( static_cast<sal_Int32>(PixelToLogic( Point(0, nUnzoomedPixelHeight), MapMode(MapUnit::Map10thMM)).Y()) ); + xModel->setPropertyValue( FM_PROP_ROWHEIGHT, aProperty ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "FmGridControl::RowHeightChanged" ); + } +} + +void FmGridControl::ColumnResized(sal_uInt16 nId) +{ + DbGridControl::ColumnResized(nId); + + // transfer value to the model + DbGridColumn* pCol = DbGridControl::GetColumns()[ GetModelColumnPos(nId) ].get(); + const Reference< css::beans::XPropertySet >& xColModel(pCol->getModel()); + if (xColModel.is()) + { + Any aWidth; + sal_Int32 nColumnWidth = GetColumnWidth(nId); + nColumnWidth = CalcReverseZoom(nColumnWidth); + // convert to 10THMM + aWidth <<= static_cast<sal_Int32>(PixelToLogic(Point(nColumnWidth, 0), MapMode(MapUnit::Map10thMM)).X()); + xColModel->setPropertyValue(FM_PROP_WIDTH, aWidth); + } +} + +void FmGridControl::CellModified() +{ + DbGridControl::CellModified(); + GetPeer()->CellModified(); +} + +void FmGridControl::BeginCursorAction() +{ + DbGridControl::BeginCursorAction(); + m_pPeer->stopCursorListening(); +} + +void FmGridControl::EndCursorAction() +{ + m_pPeer->startCursorListening(); + DbGridControl::EndCursorAction(); +} + +void FmGridControl::ColumnMoved(sal_uInt16 nId) +{ + m_bInColumnMove = true; + + DbGridControl::ColumnMoved(nId); + Reference< css::container::XIndexContainer > xColumns(GetPeer()->getColumns()); + + if (xColumns.is()) + { + // locate the column and move in the model; + // get ColumnPos + DbGridColumn* pCol = DbGridControl::GetColumns()[ GetModelColumnPos(nId) ].get(); + Reference< css::beans::XPropertySet > xCol; + + // inserting must be based on the column positions + sal_Int32 i; + Reference< XInterface > xCurrent; + for (i = 0; !xCol.is() && i < xColumns->getCount(); i++) + { + xCurrent.set(xColumns->getByIndex(i), css::uno::UNO_QUERY); + if (xCurrent == pCol->getModel()) + { + xCol = pCol->getModel(); + break; + } + } + + DBG_ASSERT(i < xColumns->getCount(), "Wrong css::sdbcx::Index"); + xColumns->removeByIndex(i); + Any aElement; + aElement <<= xCol; + xColumns->insertByIndex(GetModelColumnPos(nId), aElement); + pCol->setModel(xCol); + // if the column which is shown here is selected ... + if ( isColumnSelected(pCol) ) + markColumn(nId); // ... -> mark it + } + + m_bInColumnMove = false; +} + +void FmGridControl::InitColumnsByModels(const Reference< css::container::XIndexContainer >& xColumns) +{ + // reset columns; + // if there is only one HandleColumn, then don't + if (GetModelColCount()) + { + RemoveColumns(); + InsertHandleColumn(); + } + + if (!xColumns.is()) + return; + + SetUpdateMode(false); + + // inserting must be based on the column positions + sal_Int32 i; + Any aWidth; + for (i = 0; i < xColumns->getCount(); ++i) + { + Reference< css::beans::XPropertySet > xCol( + xColumns->getByIndex(i), css::uno::UNO_QUERY); + + OUString aName( + comphelper::getString(xCol->getPropertyValue(FM_PROP_LABEL))); + + aWidth = xCol->getPropertyValue(FM_PROP_WIDTH); + sal_Int32 nWidth = 0; + if (aWidth >>= nWidth) + nWidth = LogicToPixel(Point(nWidth, 0), MapMode(MapUnit::Map10thMM)).X(); + + AppendColumn(aName, static_cast<sal_uInt16>(nWidth)); + DbGridColumn* pCol = DbGridControl::GetColumns()[ i ].get(); + pCol->setModel(xCol); + } + + // and now remove the hidden columns as well + // (we did not already make it in the upper loop, since we would then have gotten + // problems with the IDs of the columns: AppendColumn allocates them automatically, + // but the column _after_ a hidden one needs an ID increased by one ...) + Any aHidden; + for (i = 0; i < xColumns->getCount(); ++i) + { + Reference< css::beans::XPropertySet > xCol( xColumns->getByIndex(i), css::uno::UNO_QUERY); + aHidden = xCol->getPropertyValue(FM_PROP_HIDDEN); + if (::comphelper::getBOOL(aHidden)) + HideColumn(GetColumnIdFromModelPos(static_cast<sal_uInt16>(i))); + } + + SetUpdateMode(true); +} + +void FmGridControl::InitColumnByField( + DbGridColumn* _pColumn, const Reference< XPropertySet >& _rxColumnModel, + const Reference< XNameAccess >& _rxFieldsByNames, const Reference< XIndexAccess >& _rxFieldsByIndex ) +{ + DBG_ASSERT( _rxFieldsByNames == _rxFieldsByIndex, "FmGridControl::InitColumnByField: invalid container interfaces!" ); + + // lookup the column which belongs to the control source + OUString sFieldName; + _rxColumnModel->getPropertyValue( FM_PROP_CONTROLSOURCE ) >>= sFieldName; + Reference< XPropertySet > xField; + _rxColumnModel->getPropertyValue( FM_PROP_BOUNDFIELD ) >>= xField; + + + if ( !xField.is() && /*sFieldName.getLength() && */_rxFieldsByNames->hasByName( sFieldName ) ) // #i93452# do not check for name length + _rxFieldsByNames->getByName( sFieldName ) >>= xField; + + // determine the position of this column + sal_Int32 nFieldPos = -1; + if ( xField.is() ) + { + Reference< XPropertySet > xCheck; + sal_Int32 nFieldCount = _rxFieldsByIndex->getCount(); + for ( sal_Int32 i = 0; i < nFieldCount; ++i) + { + _rxFieldsByIndex->getByIndex( i ) >>= xCheck; + if ( xField.get() == xCheck.get() ) + { + nFieldPos = i; + break; + } + } + } + + if ( xField.is() && ( nFieldPos >= 0 ) ) + { + // some data types are not allowed + sal_Int32 nDataType = DataType::OTHER; + xField->getPropertyValue( FM_PROP_FIELDTYPE ) >>= nDataType; + + bool bIllegalType = false; + switch ( nDataType ) + { + case DataType::BLOB: + case DataType::LONGVARBINARY: + case DataType::BINARY: + case DataType::VARBINARY: + case DataType::OTHER: + bIllegalType = true; + break; + } + + if ( bIllegalType ) + { + _pColumn->SetObject( static_cast<sal_Int16>(nFieldPos) ); + return; + } + } + + // the control type is determined by the ColumnServiceName + static constexpr OUString s_sPropColumnServiceName = u"ColumnServiceName"_ustr; + if ( !::comphelper::hasProperty( s_sPropColumnServiceName, _rxColumnModel ) ) + return; + + _pColumn->setModel( _rxColumnModel ); + + OUString sColumnServiceName; + _rxColumnModel->getPropertyValue( s_sPropColumnServiceName ) >>= sColumnServiceName; + + sal_Int32 nTypeId = getColumnTypeByModelName( sColumnServiceName ); + _pColumn->CreateControl( nFieldPos, xField, nTypeId ); +} + +void FmGridControl::InitColumnsByFields(const Reference< css::container::XIndexAccess >& _rxFields) +{ + if ( !_rxFields.is() ) + return; + + // initialize columns + Reference< XIndexContainer > xColumns( GetPeer()->getColumns() ); + Reference< XNameAccess > xFieldsAsNames( _rxFields, UNO_QUERY ); + + // inserting must be based on the column positions + for (sal_Int32 i = 0; i < xColumns->getCount(); i++) + { + DbGridColumn* pCol = GetColumns()[ i ].get(); + OSL_ENSURE(pCol,"No grid column!"); + if ( pCol ) + { + Reference< XPropertySet > xColumnModel( + xColumns->getByIndex( i ), css::uno::UNO_QUERY); + + InitColumnByField( pCol, xColumnModel, xFieldsAsNames, _rxFields ); + } + } +} + +void FmGridControl::HideColumn(sal_uInt16 nId) +{ + DbGridControl::HideColumn(nId); + + sal_uInt16 nPos = GetModelColumnPos(nId); + if (nPos == sal_uInt16(-1)) + return; + + DbGridColumn* pColumn = GetColumns()[ nPos ].get(); + if (pColumn->IsHidden()) + GetPeer()->columnHidden(pColumn); + + if (nId == m_nMarkedColumnId) + m_nMarkedColumnId = sal_uInt16(-1); +} + +bool FmGridControl::isColumnSelected(DbGridColumn const * _pColumn) const +{ + OSL_ENSURE(_pColumn,"Column can not be null!"); + bool bSelected = false; + // if the column which is shown here is selected ... + Reference< css::view::XSelectionSupplier > xSelSupplier(GetPeer()->getColumns(), UNO_QUERY); + if ( xSelSupplier.is() ) + { + Reference< css::beans::XPropertySet > xColumn; + xSelSupplier->getSelection() >>= xColumn; + bSelected = (xColumn.get() == _pColumn->getModel().get()); + } + return bSelected; +} + +void FmGridControl::ShowColumn(sal_uInt16 nId) +{ + DbGridControl::ShowColumn(nId); + + sal_uInt16 nPos = GetModelColumnPos(nId); + if (nPos == sal_uInt16(-1)) + return; + + DbGridColumn* pColumn = GetColumns()[ nPos ].get(); + if (!pColumn->IsHidden()) + GetPeer()->columnVisible(pColumn); + + // if the column which is shown here is selected ... + if ( isColumnSelected(pColumn) ) + markColumn(nId); // ... -> mark it +} + +bool FmGridControl::selectBookmarks(const Sequence< Any >& _rBookmarks) +{ + SolarMutexGuard aGuard; + // need to lock the SolarMutex so that no paint call disturbs us ... + + if ( !m_pSeekCursor ) + { + OSL_FAIL( "FmGridControl::selectBookmarks: no seek cursor!" ); + return false; + } + + SetNoSelection(); + + bool bAllSuccessful = true; + try + { + for (const Any& rBookmark : _rBookmarks) + { + // move the seek cursor to the row given + if (m_pSeekCursor->moveToBookmark(rBookmark)) + SelectRow( m_pSeekCursor->getRow() - 1); + else + bAllSuccessful = false; + } + } + catch(Exception&) + { + OSL_FAIL("FmGridControl::selectBookmarks: could not move to one of the bookmarks!"); + return false; + } + + return bAllSuccessful; +} + +Sequence< Any> FmGridControl::getSelectionBookmarks() +{ + // lock our update so no paint-triggered seeks interfere ... + SetUpdateMode(false); + + sal_Int32 nSelectedRows = GetSelectRowCount(), i = 0; + Sequence< Any> aBookmarks(nSelectedRows); + if ( nSelectedRows ) + { + Any* pBookmarks = aBookmarks.getArray(); + + // (I'm not sure if the problem isn't deeper: The scenario: a large table displayed by a grid with a + // thread-safe cursor (dBase). On loading the sdb-cursor started a counting thread. While this counting progress + // was running, I tried do delete 3 records from within the grid. Deletion caused a SeekCursor, which made a + // m_pSeekCursor->moveRelative and a m_pSeekCursor->getPosition. + // Unfortunately the first call caused a propertyChanged(RECORDCOUNT) which resulted in a repaint of the + // navigation bar and the grid. The latter itself will result in SeekRow calls. So after (successfully) returning + // from the moveRelative the getPosition returns an invalid value. And so the SeekCursor fails. + // In the consequence ALL parts of code where two calls to the seek cursor are done, while the second call _relies_ on + // the first one, should be secured against recursion, with a broad-minded interpretation of "recursion": if any of these + // code parts is executed, no other should be accessible. But this sounds very difficult to achieve... + // ) + + // The next problem caused by the same behavior (SeekCursor causes a propertyChanged): when adjusting rows we implicitly + // change our selection. So a "FirstSelected(); SeekCursor(); NextSelected();" may produce unpredictable results. + // That's why we _first_ collect the indices of the selected rows and _then_ their bookmarks. + tools::Long nIdx = FirstSelectedRow(); + while (nIdx != BROWSER_ENDOFSELECTION) + { + // (we misuse the bookmarks array for this ...) + pBookmarks[i++] <<= static_cast<sal_Int32>(nIdx); + nIdx = NextSelectedRow(); + } + DBG_ASSERT(i == nSelectedRows, "FmGridControl::DeleteSelectedRows : could not collect the row indices !"); + + for (i=0; i<nSelectedRows; ++i) + { + nIdx = ::comphelper::getINT32(pBookmarks[i]); + if (IsInsertionRow(nIdx)) + { + // do not delete empty row + aBookmarks.realloc(--nSelectedRows); + SelectRow(nIdx, false); // cancel selection for empty row + break; + } + + // first, position the data cursor on the selected block + if (SeekCursor(nIdx)) + { + GetSeekRow()->SetState(m_pSeekCursor.get(), true); + + pBookmarks[i] = m_pSeekCursor->getBookmark(); + } + #ifdef DBG_UTIL + else + OSL_FAIL("FmGridControl::DeleteSelectedRows : a bookmark could not be determined !"); + #endif + } + } + SetUpdateMode(true); + + // if one of the SeekCursor-calls failed... + aBookmarks.realloc(i); + + // (the alternative : while collecting the bookmarks lock our propertyChanged, this should resolve both our problems. + // but this would be incompatible as we need a locking flag, then...) + + return aBookmarks; +} + +namespace +{ + OUString getColumnPropertyFromPeer(FmXGridPeer* _pPeer,sal_Int32 _nPosition,const OUString& _sPropName) + { + OUString sRetText; + if ( _pPeer && _nPosition != -1) + { + Reference<XIndexContainer> xIndex = _pPeer->getColumns(); + if ( xIndex.is() && xIndex->getCount() > _nPosition ) + { + Reference<XPropertySet> xProp; + xIndex->getByIndex( _nPosition ) >>= xProp; + if ( xProp.is() ) + { + try { + xProp->getPropertyValue( _sPropName ) >>= sRetText; + } catch (UnknownPropertyException const&) { + TOOLS_WARN_EXCEPTION("svx.fmcomp", ""); + } + } + } + } + return sRetText; + } +} + +// Object data and state +OUString FmGridControl::GetAccessibleObjectName( AccessibleBrowseBoxObjType _eObjType,sal_Int32 _nPosition ) const +{ + OUString sRetText; + switch( _eObjType ) + { + case AccessibleBrowseBoxObjType::BrowseBox: + if ( GetPeer() ) + { + Reference<XPropertySet> xProp(GetPeer()->getColumns(),UNO_QUERY); + if ( xProp.is() ) + xProp->getPropertyValue(FM_PROP_NAME) >>= sRetText; + } + break; + case AccessibleBrowseBoxObjType::ColumnHeaderCell: + sRetText = getColumnPropertyFromPeer( + GetPeer(), + GetModelColumnPos( + sal::static_int_cast< sal_uInt16 >(_nPosition)), + FM_PROP_LABEL); + break; + default: + sRetText = DbGridControl::GetAccessibleObjectName(_eObjType,_nPosition); + } + return sRetText; +} + +OUString FmGridControl::GetAccessibleObjectDescription( AccessibleBrowseBoxObjType _eObjType,sal_Int32 _nPosition ) const +{ + OUString sRetText; + switch( _eObjType ) + { + case AccessibleBrowseBoxObjType::BrowseBox: + if ( GetPeer() ) + { + Reference<XPropertySet> xProp(GetPeer()->getColumns(),UNO_QUERY); + if ( xProp.is() ) + { + xProp->getPropertyValue(FM_PROP_HELPTEXT) >>= sRetText; + if ( sRetText.isEmpty() ) + xProp->getPropertyValue(FM_PROP_DESCRIPTION) >>= sRetText; + } + } + break; + case AccessibleBrowseBoxObjType::ColumnHeaderCell: + sRetText = getColumnPropertyFromPeer( + GetPeer(), + GetModelColumnPos( + sal::static_int_cast< sal_uInt16 >(_nPosition)), + FM_PROP_HELPTEXT); + if ( sRetText.isEmpty() ) + sRetText = getColumnPropertyFromPeer( + GetPeer(), + GetModelColumnPos( + sal::static_int_cast< sal_uInt16 >(_nPosition)), + FM_PROP_DESCRIPTION); + + break; + default: + sRetText = DbGridControl::GetAccessibleObjectDescription(_eObjType,_nPosition); + } + return sRetText; +} + +void FmGridControl::Select() +{ + DbGridControl::Select(); + // ... does it affect our columns? + const MultiSelection* pColumnSelection = GetColumnSelection(); + + sal_uInt16 nSelectedColumn = + pColumnSelection && pColumnSelection->GetSelectCount() + ? sal::static_int_cast< sal_uInt16 >( + const_cast<MultiSelection*>(pColumnSelection)->FirstSelected()) + : SAL_MAX_UINT16; + // the HandleColumn is not selected + switch (nSelectedColumn) + { + case SAL_MAX_UINT16: break; // no selection + case 0 : nSelectedColumn = SAL_MAX_UINT16; break; + // handle col can't be selected + default : + // get the model col pos instead of the view col pos + nSelectedColumn = GetModelColumnPos(GetColumnIdFromViewPos(nSelectedColumn - 1)); + break; + } + + if (nSelectedColumn == m_nCurrentSelectedColumn) + return; + + // BEFORE calling the select at the SelectionSupplier! + m_nCurrentSelectedColumn = nSelectedColumn; + + if (m_bSelecting) + return; + + m_bSelecting = true; + + try + { + Reference< XIndexAccess > xColumns = GetPeer()->getColumns(); + Reference< XSelectionSupplier > xSelSupplier(xColumns, UNO_QUERY); + if (xSelSupplier.is()) + { + if (nSelectedColumn != SAL_MAX_UINT16) + { + Reference< XPropertySet > xColumn( + xColumns->getByIndex(nSelectedColumn), + css::uno::UNO_QUERY); + xSelSupplier->select(Any(xColumn)); + } + else + { + xSelSupplier->select(Any()); + } + } + } + catch(Exception&) + { + } + + + m_bSelecting = false; +} + + +void FmGridControl::KeyInput( const KeyEvent& rKEvt ) +{ + bool bDone = false; + const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); + if ( IsDesignMode() + && !rKeyCode.IsShift() + && !rKeyCode.IsMod1() + && !rKeyCode.IsMod2() + && GetParent() ) + { + switch ( rKeyCode.GetCode() ) + { + case KEY_ESCAPE: + GetParent()->GrabFocus(); + bDone = true; + break; + case KEY_DELETE: + if ( GetSelectColumnCount() && GetPeer() && m_nCurrentSelectedColumn >= 0 ) + { + Reference< css::container::XIndexContainer > xCols(GetPeer()->getColumns()); + if ( xCols.is() ) + { + try + { + if ( m_nCurrentSelectedColumn < xCols->getCount() ) + { + Reference< XInterface > xCol; + xCols->getByIndex(m_nCurrentSelectedColumn) >>= xCol; + xCols->removeByIndex(m_nCurrentSelectedColumn); + ::comphelper::disposeComponent(xCol); + } + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("svx", "exception occurred while deleting a column"); + } + } + } + bDone = true; + break; + } + } + if ( !bDone ) + DbGridControl::KeyInput( rKEvt ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/fmcomp/fmgridif.cxx b/svx/source/fmcomp/fmgridif.cxx new file mode 100644 index 0000000000..7f367a2d99 --- /dev/null +++ b/svx/source/fmcomp/fmgridif.cxx @@ -0,0 +1,2819 @@ +/* -*- 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 <sal/config.h> + +#include <string_view> + +#include <svx/fmgridif.hxx> +#include <fmprop.hxx> +#include <fmservs.hxx> +#include <svx/fmtools.hxx> +#include <fmurl.hxx> +#include <formcontrolfactory.hxx> +#include <gridcell.hxx> +#include <sdbdatacolumn.hxx> +#include <svx/fmgridcl.hxx> +#include <tools/urlobj.hxx> + +#include <com/sun/star/awt/PosSize.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/form/XFormComponent.hpp> +#include <com/sun/star/form/XLoadable.hpp> +#include <com/sun/star/form/XReset.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/sdbc/ResultSetType.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> +#include <com/sun/star/sdbcx/XRowLocate.hpp> + +#include <comphelper/enumhelper.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/property.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/types.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <o3tl/safeint.hxx> +#include <vcl/unohelp.hxx> +#include <vcl/svapp.hxx> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <sal/macros.h> + +using namespace ::svxform; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::view; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::form; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star; + +using ::com::sun::star::sdbcx::XColumnsSupplier; +using ::com::sun::star::frame::XDispatchProviderInterceptor; +using ::com::sun::star::frame::XDispatchProvider; +using ::com::sun::star::accessibility::XAccessible; +using ::com::sun::star::accessibility::XAccessibleContext; +using ::com::sun::star::sdb::XRowSetSupplier; +using ::com::sun::star::awt::XVclWindowPeer; + + +static css::awt::FontDescriptor ImplCreateFontDescriptor( const vcl::Font& rFont ) +{ + css::awt::FontDescriptor aFD; + aFD.Name = rFont.GetFamilyName(); + aFD.StyleName = rFont.GetStyleName(); + aFD.Height = static_cast<sal_Int16>(rFont.GetFontSize().Height()); + aFD.Width = static_cast<sal_Int16>(rFont.GetFontSize().Width()); + aFD.Family = static_cast<sal_Int16>(rFont.GetFamilyType()); + aFD.CharSet = rFont.GetCharSet(); + aFD.Pitch = static_cast<sal_Int16>(rFont.GetPitch()); + aFD.CharacterWidth = vcl::unohelper::ConvertFontWidth( rFont.GetWidthType() ); + aFD.Weight= vcl::unohelper::ConvertFontWeight( rFont.GetWeight() ); + aFD.Slant = vcl::unohelper::ConvertFontSlant( rFont.GetItalic() ); + aFD.Underline = static_cast<sal_Int16>(rFont.GetUnderline()); + aFD.Strikeout = static_cast<sal_Int16>(rFont.GetStrikeout()); + aFD.Orientation = toDegrees(rFont.GetOrientation()); + aFD.Kerning = rFont.IsKerning(); + aFD.WordLineMode = rFont.IsWordLineMode(); + aFD.Type = 0; // ??? => only to metric... + return aFD; +} + + +static vcl::Font ImplCreateFont( const css::awt::FontDescriptor& rDescr ) +{ + vcl::Font aFont; + aFont.SetFamilyName( rDescr.Name ); + aFont.SetStyleName( rDescr.StyleName ); + aFont.SetFontSize( ::Size( rDescr.Width, rDescr.Height ) ); + aFont.SetFamily( static_cast<FontFamily>(rDescr.Family) ); + aFont.SetCharSet( static_cast<rtl_TextEncoding>(rDescr.CharSet) ); + aFont.SetPitch( static_cast<FontPitch>(rDescr.Pitch) ); + aFont.SetWidthType( vcl::unohelper::ConvertFontWidth( rDescr.CharacterWidth ) ); + aFont.SetWeight( vcl::unohelper::ConvertFontWeight( rDescr.Weight ) ); + aFont.SetItalic( static_cast<FontItalic>(rDescr.Slant) ); + aFont.SetUnderline( static_cast<::FontLineStyle>(rDescr.Underline) ); + aFont.SetStrikeout( static_cast<::FontStrikeout>(rDescr.Strikeout) ); + aFont.SetOrientation( Degree10(static_cast<sal_Int16>(rDescr.Orientation * 10)) ); + aFont.SetKerning( static_cast<FontKerning>(rDescr.Kerning) ); + aFont.SetWordLineMode( rDescr.WordLineMode ); + return aFont; +} + +FmXModifyMultiplexer::FmXModifyMultiplexer( ::cppu::OWeakObject& rSource, ::osl::Mutex& _rMutex ) + :OWeakSubObject( rSource ) + ,OInterfaceContainerHelper3( _rMutex ) +{ +} + + +Any SAL_CALL FmXModifyMultiplexer::queryInterface(const Type& _rType) +{ + Any aReturn = ::cppu::queryInterface(_rType, + static_cast< css::util::XModifyListener*>(this), + static_cast< XEventListener*>(this) + ); + + if (!aReturn.hasValue()) + aReturn = OWeakSubObject::queryInterface( _rType ); + + return aReturn; +} + + +void FmXModifyMultiplexer::disposing(const EventObject& ) +{ +} + + +void FmXModifyMultiplexer::modified(const EventObject& e) +{ + EventObject aMulti( e); + aMulti.Source = &m_rParent; + notifyEach( &XModifyListener::modified, aMulti ); +} + +FmXUpdateMultiplexer::FmXUpdateMultiplexer( ::cppu::OWeakObject& rSource, ::osl::Mutex& _rMutex ) + :OWeakSubObject( rSource ) + ,OInterfaceContainerHelper3( _rMutex ) +{ +} + + +Any SAL_CALL FmXUpdateMultiplexer::queryInterface(const Type& _rType) +{ + Any aReturn = ::cppu::queryInterface(_rType, + static_cast< XUpdateListener*>(this), + static_cast< XEventListener*>(this) + ); + + if (!aReturn.hasValue()) + aReturn = OWeakSubObject::queryInterface( _rType ); + + return aReturn; +} + + +void FmXUpdateMultiplexer::disposing(const EventObject& ) +{ +} + + +sal_Bool FmXUpdateMultiplexer::approveUpdate(const EventObject &e) +{ + EventObject aMulti( e ); + aMulti.Source = &m_rParent; + + bool bResult = true; + if (getLength()) + { + ::comphelper::OInterfaceIteratorHelper3 aIter(*this); + while ( bResult && aIter.hasMoreElements() ) + bResult = aIter.next()->approveUpdate( aMulti ); + } + + return bResult; +} + + +void FmXUpdateMultiplexer::updated(const EventObject &e) +{ + EventObject aMulti( e ); + aMulti.Source = &m_rParent; + notifyEach( &XUpdateListener::updated, aMulti ); +} + +FmXSelectionMultiplexer::FmXSelectionMultiplexer( ::cppu::OWeakObject& rSource, ::osl::Mutex& _rMutex ) + :OWeakSubObject( rSource ) + ,OInterfaceContainerHelper3( _rMutex ) +{ +} + + +Any SAL_CALL FmXSelectionMultiplexer::queryInterface(const Type& _rType) +{ + Any aReturn = ::cppu::queryInterface(_rType, + static_cast< XSelectionChangeListener*>(this), + static_cast< XEventListener*>(this) + ); + + if (!aReturn.hasValue()) + aReturn = OWeakSubObject::queryInterface( _rType ); + + return aReturn; +} + + +void FmXSelectionMultiplexer::disposing(const EventObject& ) +{ +} + + +void SAL_CALL FmXSelectionMultiplexer::selectionChanged( const EventObject& _rEvent ) +{ + EventObject aMulti(_rEvent); + aMulti.Source = &m_rParent; + notifyEach( &XSelectionChangeListener::selectionChanged, aMulti ); +} + +FmXContainerMultiplexer::FmXContainerMultiplexer( ::cppu::OWeakObject& rSource, ::osl::Mutex& _rMutex ) + :OWeakSubObject( rSource ) + ,OInterfaceContainerHelper3( _rMutex ) +{ +} + + +Any SAL_CALL FmXContainerMultiplexer::queryInterface(const Type& _rType) +{ + Any aReturn = ::cppu::queryInterface(_rType, + static_cast< XContainerListener*>(this), + static_cast< XEventListener*>(this) + ); + + if (!aReturn.hasValue()) + aReturn = OWeakSubObject::queryInterface( _rType ); + + return aReturn; +} + + +void FmXContainerMultiplexer::disposing(const EventObject& ) +{ +} + +void FmXContainerMultiplexer::elementInserted(const ContainerEvent& e) +{ + ContainerEvent aMulti( e ); + aMulti.Source = &m_rParent; + notifyEach( &XContainerListener::elementInserted, aMulti ); +} + + +void FmXContainerMultiplexer::elementRemoved(const ContainerEvent& e) +{ + ContainerEvent aMulti( e ); + aMulti.Source = &m_rParent; + notifyEach( &XContainerListener::elementRemoved, aMulti ); +} + + +void FmXContainerMultiplexer::elementReplaced(const ContainerEvent& e) +{ + ContainerEvent aMulti( e ); + aMulti.Source = &m_rParent; + notifyEach( &XContainerListener::elementReplaced, aMulti ); +} + +FmXGridControlMultiplexer::FmXGridControlMultiplexer( ::cppu::OWeakObject& rSource, ::osl::Mutex& _rMutex ) + :OWeakSubObject( rSource ) + ,OInterfaceContainerHelper3( _rMutex ) +{ +} + + +Any SAL_CALL FmXGridControlMultiplexer::queryInterface(const Type& _rType) +{ + Any aReturn = ::cppu::queryInterface( _rType, + static_cast< XGridControlListener*>(this) + ); + + if (!aReturn.hasValue()) + aReturn = OWeakSubObject::queryInterface( _rType ); + + return aReturn; +} + + +void FmXGridControlMultiplexer::disposing( const EventObject& ) +{ +} + + +void SAL_CALL FmXGridControlMultiplexer::columnChanged( const EventObject& _event ) +{ + EventObject aForwardedEvent( _event ); + aForwardedEvent.Source = &m_rParent; + notifyEach( &XGridControlListener::columnChanged, aForwardedEvent ); +} + + +//= FmXGridControl + + +Reference< XInterface > FmXGridControl_NewInstance_Impl(const Reference< XMultiServiceFactory>& _rxFactory) +{ + return *(new FmXGridControl( comphelper::getComponentContext(_rxFactory) )); +} + +FmXGridControl::FmXGridControl(const Reference< XComponentContext >& _rxContext) + :m_aModifyListeners(*this, GetMutex()) + ,m_aUpdateListeners(*this, GetMutex()) + ,m_aContainerListeners(*this, GetMutex()) + ,m_aSelectionListeners(*this, GetMutex()) + ,m_aGridControlListeners(*this, GetMutex()) + ,m_bInDraw(false) + ,m_xContext(_rxContext) +{ +} + + +FmXGridControl::~FmXGridControl() +{ +} + + +Any SAL_CALL FmXGridControl::queryAggregation(const Type& _rType) +{ + Any aReturn = FmXGridControl_BASE::queryInterface(_rType); + + if (!aReturn.hasValue()) + aReturn = UnoControl::queryAggregation( _rType ); + return aReturn; +} + + +Sequence< Type> SAL_CALL FmXGridControl::getTypes( ) +{ + return comphelper::concatSequences(UnoControl::getTypes(),FmXGridControl_BASE::getTypes()); +} + + +Sequence<sal_Int8> SAL_CALL FmXGridControl::getImplementationId( ) +{ + return css::uno::Sequence<sal_Int8>(); +} + +// XServiceInfo +sal_Bool SAL_CALL FmXGridControl::supportsService(const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +OUString SAL_CALL FmXGridControl::getImplementationName() +{ + return "com.sun.star.form.FmXGridControl"; +} + +css::uno::Sequence<OUString> SAL_CALL FmXGridControl::getSupportedServiceNames() +{ + return { FM_SUN_CONTROL_GRIDCONTROL, "com.sun.star.awt.UnoControl" }; +} + + +void SAL_CALL FmXGridControl::dispose() +{ + SolarMutexGuard aGuard; + + EventObject aEvt; + aEvt.Source = getXWeak(); + m_aModifyListeners.disposeAndClear(aEvt); + m_aUpdateListeners.disposeAndClear(aEvt); + m_aContainerListeners.disposeAndClear(aEvt); + + UnoControl::dispose(); +} + + +OUString FmXGridControl::GetComponentServiceName() const +{ + return "DBGrid"; +} + + +sal_Bool SAL_CALL FmXGridControl::setModel(const Reference< css::awt::XControlModel >& rModel) +{ + SolarMutexGuard aGuard; + + if (!UnoControl::setModel(rModel)) + return false; + + Reference< XGridPeer > xGridPeer(getPeer(), UNO_QUERY); + if (xGridPeer.is()) + { + Reference< XIndexContainer > xCols(mxModel, UNO_QUERY); + xGridPeer->setColumns(xCols); + } + return true; +} + + +rtl::Reference<FmXGridPeer> FmXGridControl::imp_CreatePeer(vcl::Window* pParent) +{ + rtl::Reference<FmXGridPeer> pReturn = new FmXGridPeer(m_xContext); + + // translate properties into WinBits + WinBits nStyle = WB_TABSTOP; + Reference< XPropertySet > xModelSet(getModel(), UNO_QUERY); + if (xModelSet.is()) + { + try + { + if (::comphelper::getINT16(xModelSet->getPropertyValue(FM_PROP_BORDER))) + nStyle |= WB_BORDER; + } + catch(const Exception&) + { + OSL_FAIL("Can not get style"); + } + } + + pReturn->Create(pParent, nStyle); + return pReturn; +} + + +void SAL_CALL FmXGridControl::createPeer(const Reference< css::awt::XToolkit >& /*rToolkit*/, const Reference< css::awt::XWindowPeer >& rParentPeer) +{ + if ( !mxModel.is() ) + throw DisposedException( OUString(), *this ); + + DBG_ASSERT(/*(0 == m_nPeerCreationLevel) && */!mbCreatingPeer, "FmXGridControl::createPeer : recursion!"); + // I think this should never assert, now that we're using the base class' mbCreatingPeer in addition to + // our own m_nPeerCreationLevel + // But I'm not sure as I don't _fully_ understand the underlying toolkit implementations... + // (if this asserts, we still need m_nPeerCreationLevel. If not, we could omit it...) + // 14.05.2001 - 86836 - frank.schoenheit@germany.sun.com + + // TODO: why the hell this whole class does not use any mutex? + + if (getPeer().is()) + return; + + mbCreatingPeer = true; + // mbCreatingPeer is virtually the same as m_nPeerCreationLevel, but it's the base class' method + // to prevent recursion. + + vcl::Window* pParentWin = nullptr; + if (rParentPeer.is()) + { + VCLXWindow* pParent = dynamic_cast<VCLXWindow*>(rParentPeer.get()); + if (pParent) + pParentWin = pParent->GetWindow(); + } + + rtl::Reference<FmXGridPeer> pPeer = imp_CreatePeer(pParentWin); + DBG_ASSERT(pPeer != nullptr, "FmXGridControl::createPeer : imp_CreatePeer didn't return a peer !"); + setPeer( pPeer ); + + // reading the properties from the model +// ++m_nPeerCreationLevel; + updateFromModel(); + + // consider the following ugly scenario: updateFromModel leads to a propertiesChanges on the Control, + // which determines, dat a "critical" property has changed (e.g. "Border") and therefore starts a new + // Peer, which lands again here in createPeer we also start a second FmXGridPeer and initialise it. + // Then we exit from the first incarnation's updateFromModel and continue working with the pPeer, + // that is in fact now already obsolete (as another peer is being started in the second incarnation). + // Therefore the effort with the PeerCreationLevel, which ensures that we really use the Peer + // created at the deepest level, but first initialise it in the top-level. +// if (--m_nPeerCreationLevel == 0) + { + DBG_ASSERT(getPeer().is(), "FmXGridControl::createPeer : something went wrong ... no top level peer !"); + pPeer = dynamic_cast<FmXGridPeer*>(getPeer().get()); + + setPosSize( maComponentInfos.nX, maComponentInfos.nY, maComponentInfos.nWidth, maComponentInfos.nHeight, css::awt::PosSize::POSSIZE ); + + Reference< XIndexContainer > xColumns(getModel(), UNO_QUERY); + if (xColumns.is()) + pPeer->setColumns(xColumns); + + if (maComponentInfos.bVisible) + pPeer->setVisible(true); + + if (!maComponentInfos.bEnable) + pPeer->setEnable(false); + + if (maWindowListeners.getLength()) + pPeer->addWindowListener( &maWindowListeners ); + + if (maFocusListeners.getLength()) + pPeer->addFocusListener( &maFocusListeners ); + + if (maKeyListeners.getLength()) + pPeer->addKeyListener( &maKeyListeners ); + + if (maMouseListeners.getLength()) + pPeer->addMouseListener( &maMouseListeners ); + + if (maMouseMotionListeners.getLength()) + pPeer->addMouseMotionListener( &maMouseMotionListeners ); + + if (maPaintListeners.getLength()) + pPeer->addPaintListener( &maPaintListeners ); + + if (m_aModifyListeners.getLength()) + pPeer->addModifyListener( &m_aModifyListeners ); + + if (m_aUpdateListeners.getLength()) + pPeer->addUpdateListener( &m_aUpdateListeners ); + + if (m_aContainerListeners.getLength()) + pPeer->addContainerListener( &m_aContainerListeners ); + + // forward the design mode + bool bForceAlivePeer = m_bInDraw && !maComponentInfos.bVisible; + // (we force an alive-mode peer if we're in "draw", cause in this case the peer will be used for drawing in + // foreign devices. We ensure this with the visibility check as a living peer is assumed to be noncritical + // only if invisible) + Any aOldCursorBookmark; + if (!mbDesignMode || bForceAlivePeer) + { + Reference< XFormComponent > xComp(getModel(), UNO_QUERY); + if (xComp.is()) + { + Reference< XRowSet > xForm(xComp->getParent(), UNO_QUERY); + // is the form alive? + // we can see that if the form contains columns + Reference< css::sdbcx::XColumnsSupplier > xColumnsSupplier(xForm, UNO_QUERY); + if (xColumnsSupplier.is()) + { + if (Reference< XIndexAccess > (xColumnsSupplier->getColumns(),UNO_QUERY_THROW)->getCount()) + { + // we get only a new bookmark if the resultset is not forwardonly + if (::comphelper::getINT32(Reference< XPropertySet > (xForm, UNO_QUERY_THROW)->getPropertyValue(FM_PROP_RESULTSET_TYPE)) != ResultSetType::FORWARD_ONLY) + { + // as the FmGridControl touches the data source it is connected to we have to remember the current + // cursor position (and restore afterwards) + // OJ: but only when we stand on a valid row + if ( !xForm->isBeforeFirst() && !xForm->isAfterLast() ) + { + try + { + aOldCursorBookmark = Reference< css::sdbcx::XRowLocate > (xForm, UNO_QUERY_THROW)->getBookmark(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + } + } + } + pPeer->setRowSet(xForm); + } + } + pPeer->setDesignMode(mbDesignMode && !bForceAlivePeer); + + try + { + if (aOldCursorBookmark.hasValue()) + { // we have a valid bookmark, so we have to restore the cursor's position + Reference< XFormComponent > xComp(getModel(), UNO_QUERY); + Reference< css::sdbcx::XRowLocate > xLocate(xComp->getParent(), UNO_QUERY); + xLocate->moveToBookmark(aOldCursorBookmark); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + Reference< css::awt::XView > xPeerView(getPeer(), UNO_QUERY); + xPeerView->setZoom( maComponentInfos.nZoomX, maComponentInfos.nZoomY ); + xPeerView->setGraphics( mxGraphics ); + } + mbCreatingPeer = false; +} + + +void FmXGridControl::addModifyListener(const Reference< css::util::XModifyListener >& l) +{ + m_aModifyListeners.addInterface( l ); + if( getPeer().is() && m_aModifyListeners.getLength() == 1 ) + { + Reference< css::util::XModifyBroadcaster > xGrid(getPeer(), UNO_QUERY); + xGrid->addModifyListener( &m_aModifyListeners); + } +} + + +sal_Bool SAL_CALL FmXGridControl::select( const Any& _rSelection ) +{ + SolarMutexGuard aGuard; + Reference< XSelectionSupplier > xPeer(getPeer(), UNO_QUERY); + return xPeer->select(_rSelection); +} + + +Any SAL_CALL FmXGridControl::getSelection( ) +{ + SolarMutexGuard aGuard; + Reference< XSelectionSupplier > xPeer(getPeer(), UNO_QUERY); + return xPeer->getSelection(); +} + + +void SAL_CALL FmXGridControl::addSelectionChangeListener( const Reference< XSelectionChangeListener >& _rxListener ) +{ + m_aSelectionListeners.addInterface( _rxListener ); + if( getPeer().is() && 1 == m_aSelectionListeners.getLength() ) + { + Reference< XSelectionSupplier > xGrid(getPeer(), UNO_QUERY); + xGrid->addSelectionChangeListener( &m_aSelectionListeners); + } +} + + +void SAL_CALL FmXGridControl::removeSelectionChangeListener( const Reference< XSelectionChangeListener >& _rxListener ) +{ + if( getPeer().is() && 1 == m_aSelectionListeners.getLength() ) + { + Reference< XSelectionSupplier > xGrid(getPeer(), UNO_QUERY); + xGrid->removeSelectionChangeListener( &m_aSelectionListeners); + } + m_aSelectionListeners.removeInterface( _rxListener ); +} + + +Sequence< sal_Bool > SAL_CALL FmXGridControl::queryFieldDataType( const Type& xType ) +{ + if (getPeer().is()) + { + Reference< XGridFieldDataSupplier > xPeerSupplier(getPeer(), UNO_QUERY); + if (xPeerSupplier.is()) + return xPeerSupplier->queryFieldDataType(xType); + } + + return Sequence<sal_Bool>(); +} + + +Sequence< Any > SAL_CALL FmXGridControl::queryFieldData( sal_Int32 nRow, const Type& xType ) +{ + if (getPeer().is()) + { + Reference< XGridFieldDataSupplier > xPeerSupplier(getPeer(), UNO_QUERY); + if (xPeerSupplier.is()) + return xPeerSupplier->queryFieldData(nRow, xType); + } + + return Sequence< Any>(); +} + + +void SAL_CALL FmXGridControl::removeModifyListener(const Reference< css::util::XModifyListener >& l) +{ + if( getPeer().is() && m_aModifyListeners.getLength() == 1 ) + { + Reference< css::util::XModifyBroadcaster > xGrid(getPeer(), UNO_QUERY); + xGrid->removeModifyListener( &m_aModifyListeners); + } + m_aModifyListeners.removeInterface( l ); +} + + +void SAL_CALL FmXGridControl::draw( sal_Int32 x, sal_Int32 y ) +{ + SolarMutexGuard aGuard; + m_bInDraw = true; + UnoControl::draw(x, y); + m_bInDraw = false; +} + + +void SAL_CALL FmXGridControl::setDesignMode(sal_Bool bOn) +{ + css::util::ModeChangeEvent aModeChangeEvent; + + // --- <mutex_lock> --- + { + SolarMutexGuard aGuard; + + Reference< XRowSetSupplier > xGrid(getPeer(), UNO_QUERY); + + if (xGrid.is() && (bool(bOn) != mbDesignMode || (!bOn && !xGrid->getRowSet().is()))) + { + if (bOn) + { + xGrid->setRowSet(Reference< XRowSet > ()); + } + else + { + Reference< XFormComponent > xComp(getModel(), UNO_QUERY); + if (xComp.is()) + { + Reference< XRowSet > xForm(xComp->getParent(), UNO_QUERY); + xGrid->setRowSet(xForm); + } + } + + // Avoid infinite recursion when calling XVclWindowPeer::setDesignMode below + mbDesignMode = bOn; + + Reference< XVclWindowPeer > xVclWindowPeer( getPeer(), UNO_QUERY ); + if (xVclWindowPeer.is()) + xVclWindowPeer->setDesignMode(bOn); + } + else + { + mbDesignMode = bOn; + } + + // dispose our current AccessibleContext, if we have one + // (changing the design mode implies having a new implementation for this context, + // so the old one must be declared DEFUNC) + DisposeAccessibleContext( + Reference<XComponent>(maAccessibleContext, UNO_QUERY)); + maAccessibleContext.clear(); + + // prepare firing an event + aModeChangeEvent.Source = *this; + aModeChangeEvent.NewMode = mbDesignMode ? std::u16string_view( u"design" ) : std::u16string_view( u"alive" ); + } + + // --- </mutex_lock> --- + maModeChangeListeners.notifyEach( &XModeChangeListener::modeChanged, aModeChangeEvent ); +} + +// XBoundComponent + +void SAL_CALL FmXGridControl::addUpdateListener(const Reference< XUpdateListener >& l) +{ + m_aUpdateListeners.addInterface( l ); + if( getPeer().is() && m_aUpdateListeners.getLength() == 1 ) + { + Reference< XBoundComponent > xBound(getPeer(), UNO_QUERY); + xBound->addUpdateListener( &m_aUpdateListeners); + } +} + + +void SAL_CALL FmXGridControl::removeUpdateListener(const Reference< XUpdateListener >& l) +{ + if( getPeer().is() && m_aUpdateListeners.getLength() == 1 ) + { + Reference< XBoundComponent > xBound(getPeer(), UNO_QUERY); + xBound->removeUpdateListener( &m_aUpdateListeners); + } + m_aUpdateListeners.removeInterface( l ); +} + + +sal_Bool SAL_CALL FmXGridControl::commit() +{ + Reference< XBoundComponent > xBound(getPeer(), UNO_QUERY); + if (xBound.is()) + return xBound->commit(); + else + return true; +} + +// XContainer + +void SAL_CALL FmXGridControl::addContainerListener(const Reference< XContainerListener >& l) +{ + m_aContainerListeners.addInterface( l ); + if( getPeer().is() && m_aContainerListeners.getLength() == 1 ) + { + Reference< XContainer > xContainer(getPeer(), UNO_QUERY); + xContainer->addContainerListener( &m_aContainerListeners); + } +} + + +void SAL_CALL FmXGridControl::removeContainerListener(const Reference< XContainerListener >& l) +{ + if( getPeer().is() && m_aContainerListeners.getLength() == 1 ) + { + Reference< XContainer > xContainer(getPeer(), UNO_QUERY); + xContainer->removeContainerListener( &m_aContainerListeners); + } + m_aContainerListeners.removeInterface( l ); +} + + +Reference< css::frame::XDispatch > SAL_CALL FmXGridControl::queryDispatch(const css::util::URL& aURL, const OUString& aTargetFrameName, sal_Int32 nSearchFlags) +{ + Reference< css::frame::XDispatchProvider > xPeerProvider(getPeer(), UNO_QUERY); + if (xPeerProvider.is()) + return xPeerProvider->queryDispatch(aURL, aTargetFrameName, nSearchFlags); + else + return Reference< css::frame::XDispatch > (); +} + + +Sequence< Reference< css::frame::XDispatch > > SAL_CALL FmXGridControl::queryDispatches(const Sequence< css::frame::DispatchDescriptor>& aDescripts) +{ + Reference< css::frame::XDispatchProvider > xPeerProvider(getPeer(), UNO_QUERY); + if (xPeerProvider.is()) + return xPeerProvider->queryDispatches(aDescripts); + else + return Sequence< Reference< css::frame::XDispatch > >(); +} + + +void SAL_CALL FmXGridControl::registerDispatchProviderInterceptor(const Reference< css::frame::XDispatchProviderInterceptor >& _xInterceptor) +{ + Reference< css::frame::XDispatchProviderInterception > xPeerInterception(getPeer(), UNO_QUERY); + if (xPeerInterception.is()) + xPeerInterception->registerDispatchProviderInterceptor(_xInterceptor); +} + + +void SAL_CALL FmXGridControl::releaseDispatchProviderInterceptor(const Reference< css::frame::XDispatchProviderInterceptor >& _xInterceptor) +{ + Reference< css::frame::XDispatchProviderInterception > xPeerInterception(getPeer(), UNO_QUERY); + if (xPeerInterception.is()) + xPeerInterception->releaseDispatchProviderInterceptor(_xInterceptor); +} + + +void SAL_CALL FmXGridControl::addGridControlListener( const Reference< XGridControlListener >& _listener ) +{ + ::osl::MutexGuard aGuard( GetMutex() ); + + m_aGridControlListeners.addInterface( _listener ); + if ( getPeer().is() && 1 == m_aGridControlListeners.getLength() ) + { + Reference< XGridControl > xPeerGrid( getPeer(), UNO_QUERY ); + if ( xPeerGrid.is() ) + xPeerGrid->addGridControlListener( &m_aGridControlListeners ); + } +} + + +void SAL_CALL FmXGridControl::removeGridControlListener( const Reference< XGridControlListener >& _listener ) +{ + ::osl::MutexGuard aGuard( GetMutex() ); + + if( getPeer().is() && 1 == m_aGridControlListeners.getLength() ) + { + Reference< XGridControl > xPeerGrid( getPeer(), UNO_QUERY ); + if ( xPeerGrid.is() ) + xPeerGrid->removeGridControlListener( &m_aGridControlListeners ); + } + + m_aGridControlListeners.removeInterface( _listener ); +} + + +sal_Int16 SAL_CALL FmXGridControl::getCurrentColumnPosition() +{ + Reference< XGridControl > xGrid( getPeer(), UNO_QUERY ); + return xGrid.is() ? xGrid->getCurrentColumnPosition() : -1; +} + + +void SAL_CALL FmXGridControl::setCurrentColumnPosition(sal_Int16 nPos) +{ + Reference< XGridControl > xGrid( getPeer(), UNO_QUERY ); + if ( xGrid.is() ) + { + SolarMutexGuard aGuard; + xGrid->setCurrentColumnPosition( nPos ); + } +} + +// XElementAccess + +sal_Bool SAL_CALL FmXGridControl::hasElements() +{ + Reference< XElementAccess > xPeer(getPeer(), UNO_QUERY); + return xPeer.is() && xPeer->hasElements(); +} + + +Type SAL_CALL FmXGridControl::getElementType( ) +{ + return cppu::UnoType<css::awt::XTextComponent>::get(); +} + +// XEnumerationAccess + +Reference< XEnumeration > SAL_CALL FmXGridControl::createEnumeration() +{ + Reference< XEnumerationAccess > xPeer(getPeer(), UNO_QUERY); + if (xPeer.is()) + return xPeer->createEnumeration(); + else + return new ::comphelper::OEnumerationByIndex(this); +} + +// XIndexAccess + +sal_Int32 SAL_CALL FmXGridControl::getCount() +{ + Reference< XIndexAccess > xPeer(getPeer(), UNO_QUERY); + return xPeer.is() ? xPeer->getCount() : 0; +} + + +Any SAL_CALL FmXGridControl::getByIndex(sal_Int32 _nIndex) +{ + Reference< XIndexAccess > xPeer(getPeer(), UNO_QUERY); + if (!xPeer.is()) + throw IndexOutOfBoundsException(); + + return xPeer->getByIndex(_nIndex); +} + +// css::util::XModeSelector + +void SAL_CALL FmXGridControl::setMode(const OUString& Mode) +{ + Reference< css::util::XModeSelector > xPeer(getPeer(), UNO_QUERY); + if (!xPeer.is()) + throw NoSupportException(); + + xPeer->setMode(Mode); +} + + +OUString SAL_CALL FmXGridControl::getMode() +{ + Reference< css::util::XModeSelector > xPeer(getPeer(), UNO_QUERY); + return xPeer.is() ? xPeer->getMode() : OUString(); +} + + +css::uno::Sequence<OUString> SAL_CALL FmXGridControl::getSupportedModes() +{ + Reference< css::util::XModeSelector > xPeer(getPeer(), UNO_QUERY); + return xPeer.is() ? xPeer->getSupportedModes() : css::uno::Sequence<OUString>(); +} + + +sal_Bool SAL_CALL FmXGridControl::supportsMode(const OUString& Mode) +{ + Reference< css::util::XModeSelector > xPeer(getPeer(), UNO_QUERY); + return xPeer.is() && xPeer->supportsMode(Mode); +} + +void SAL_CALL FmXGridControl::setFocus() +{ + rtl::Reference<FmXGridPeer> pPeer = dynamic_cast<FmXGridPeer*>(getPeer().get()); + if (pPeer) + { + VclPtr<FmGridControl> xGrid = pPeer->GetAs<FmGridControl>(); + bool bAlreadyHasFocus = xGrid->HasChildPathFocus() || xGrid->ControlHasFocus(); + // if the focus is already in the control don't grab focus again which + // would grab focus away from any native widgets hosted in the control + if (bAlreadyHasFocus) + return; + } + UnoControl::setFocus(); +} + +// helper class which prevents that in the peer's header the FmGridListener must be known +class FmXGridPeer::GridListenerDelegator : public FmGridListener +{ +protected: + FmXGridPeer* m_pPeer; + +public: + explicit GridListenerDelegator( FmXGridPeer* _pPeer ); + virtual ~GridListenerDelegator(); + +protected: + virtual void selectionChanged() override; + virtual void columnChanged() override; +}; + + +FmXGridPeer::GridListenerDelegator::GridListenerDelegator(FmXGridPeer* _pPeer) + :m_pPeer(_pPeer) +{ + DBG_ASSERT(m_pPeer, "GridListenerDelegator::GridListenerDelegator"); +} + +FmXGridPeer::GridListenerDelegator::~GridListenerDelegator() +{ +} + + +void FmXGridPeer::GridListenerDelegator::selectionChanged() +{ + m_pPeer->selectionChanged(); +} + + +void FmXGridPeer::GridListenerDelegator::columnChanged() +{ + m_pPeer->columnChanged(); +} + +void FmXGridPeer::selectionChanged() +{ + std::unique_lock g(m_aMutex); + EventObject aSource; + aSource.Source = getXWeak(); + m_aSelectionListeners.notifyEach( g, &XSelectionChangeListener::selectionChanged, aSource); +} + + +void FmXGridPeer::columnChanged() +{ + std::unique_lock g(m_aMutex); + EventObject aEvent( *this ); + m_aGridControlListeners.notifyEach( g, &XGridControlListener::columnChanged, aEvent ); +} + + +FmXGridPeer::FmXGridPeer(const Reference< XComponentContext >& _rxContext) + :m_xContext(_rxContext) + ,m_aMode("DataMode") + ,m_nCursorListening(0) + ,m_bInterceptingDispatch(false) +{ + // Create must be called after this constructor + m_pGridListener.reset( new GridListenerDelegator( this ) ); +} + + +VclPtr<FmGridControl> FmXGridPeer::imp_CreateControl(vcl::Window* pParent, WinBits nStyle) +{ + return VclPtr<FmGridControl>::Create(m_xContext, pParent, this, nStyle); +} + + +void FmXGridPeer::Create(vcl::Window* pParent, WinBits nStyle) +{ + VclPtr<FmGridControl> pWin = imp_CreateControl(pParent, nStyle); + DBG_ASSERT(pWin != nullptr, "FmXGridPeer::Create : imp_CreateControl didn't return a control !"); + + pWin->SetStateProvider(LINK(this, FmXGridPeer, OnQueryGridSlotState)); + pWin->SetSlotExecutor(LINK(this, FmXGridPeer, OnExecuteGridSlot)); + + // want to hear about row selections + pWin->setGridListener( m_pGridListener.get() ); + + // Init must always be called + pWin->Init(); + pWin->SetComponentInterface(this); + + getSupportedURLs(); +} + +FmXGridPeer::~FmXGridPeer() +{ + setRowSet(Reference< XRowSet > ()); + setColumns(Reference< XIndexContainer > ()); +} + +// XEventListener + +void FmXGridPeer::disposing(const EventObject& e) +{ + bool bKnownSender = false; + + Reference< XIndexContainer > xCols( e.Source, UNO_QUERY ); + if ( xCols.is() ) + { + setColumns(Reference< XIndexContainer > ()); + bKnownSender = true; + } + + Reference< XRowSet > xCursor(e.Source, UNO_QUERY); + if (xCursor.is()) + { + setRowSet( m_xCursor ); + m_xCursor = nullptr; + bKnownSender = true; + } + + + if ( !bKnownSender && m_pDispatchers ) + { + const Sequence< URL>& aSupportedURLs = getSupportedURLs(); + const URL* pSupportedURLs = aSupportedURLs.getConstArray(); + for ( sal_Int32 i=0; i < ( aSupportedURLs.getLength() ) && !bKnownSender; ++i, ++pSupportedURLs ) + { + if ( m_pDispatchers[i] == e.Source ) + { + m_pDispatchers[i]->removeStatusListener( static_cast< css::frame::XStatusListener* >( this ), *pSupportedURLs ); + m_pDispatchers[i] = nullptr; + m_pStateCache[i] = false; + bKnownSender = true; + } + } + } + + if ( !bKnownSender ) + VCLXWindow::disposing(e); +} + + +void FmXGridPeer::addModifyListener(const Reference< css::util::XModifyListener >& l) +{ + std::unique_lock g(m_aMutex); + m_aModifyListeners.addInterface( g, l ); +} + + +void FmXGridPeer::removeModifyListener(const Reference< css::util::XModifyListener >& l) +{ + std::unique_lock g(m_aMutex); + m_aModifyListeners.removeInterface( g, l ); +} + + +#define LAST_KNOWN_TYPE FormComponentType::PATTERNFIELD +Sequence< sal_Bool > SAL_CALL FmXGridPeer::queryFieldDataType( const Type& xType ) +{ + // a 'conversion table' + static const bool bCanConvert[LAST_KNOWN_TYPE][4] = + { + { false, false, false, false }, // FormComponentType::CONTROL + { false, false, false, false }, // FormComponentType::COMMANDBUTTON + { false, false, false, false }, // FormComponentType::RADIOBUTTON + { false, false, false, false }, // FormComponentType::IMAGEBUTTON + { false, false, false, true }, // FormComponentType::CHECKBOX + { false, false, false, false }, // FormComponentType::LISTBOX + { false, false, false, false }, // FormComponentType::COMBOBOX + { false, false, false, false }, // FormComponentType::GROUPBOX + { true , false, false, false }, // FormComponentType::TEXTFIELD + { false, false, false, false }, // FormComponentType::FIXEDTEXT + { false, false, false, false }, // FormComponentType::GRIDCONTROL + { false, false, false, false }, // FormComponentType::FILECONTROL + { false, false, false, false }, // FormComponentType::HIDDENCONTROL + { false, false, false, false }, // FormComponentType::IMAGECONTROL + { true , true , true , false }, // FormComponentType::DATEFIELD + { true , true , false, false }, // FormComponentType::TIMEFIELD + { true , true , false, false }, // FormComponentType::NUMERICFIELD + { true , true , false, false }, // FormComponentType::CURRENCYFIELD + { true , false, false, false } // FormComponentType::PATTERNFIELD + }; + + + sal_Int16 nMapColumn = -1; + switch (xType.getTypeClass()) + { + case TypeClass_STRING : nMapColumn = 0; break; + case TypeClass_FLOAT: + case TypeClass_DOUBLE : nMapColumn = 1; break; + case TypeClass_SHORT: + case TypeClass_LONG: + case TypeClass_UNSIGNED_LONG: + case TypeClass_UNSIGNED_SHORT : nMapColumn = 2; break; + case TypeClass_BOOLEAN : nMapColumn = 3; break; + default: + break; + } + + Reference< XIndexContainer > xColumns = getColumns(); + + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + sal_Int32 nColumns = pGrid->GetViewColCount(); + + std::vector< std::unique_ptr<DbGridColumn> > const & aColumns = pGrid->GetColumns(); + + Sequence<sal_Bool> aReturnSequence(nColumns); + sal_Bool* pReturnArray = aReturnSequence.getArray(); + + bool bRequestedAsAny = (xType.getTypeClass() == TypeClass_ANY); + + DbGridColumn* pCol; + Reference< css::sdb::XColumn > xFieldContent; + Reference< XPropertySet > xCurrentColumn; + for (sal_Int32 i=0; i<nColumns; ++i) + { + if (bRequestedAsAny) + { + pReturnArray[i] = true; + continue; + } + + pReturnArray[i] = false; + + sal_uInt16 nModelPos = pGrid->GetModelColumnPos(pGrid->GetColumnIdFromViewPos(static_cast<sal_uInt16>(i))); + DBG_ASSERT(nModelPos != sal_uInt16(-1), "FmXGridPeer::queryFieldDataType : no model pos !"); + + pCol = aColumns[ nModelPos ].get(); + const DbGridRowRef xRow = pGrid->GetSeekRow(); + xFieldContent = (xRow.is() && xRow->HasField(pCol->GetFieldPos())) ? xRow->GetField(pCol->GetFieldPos()).getColumn() : Reference< css::sdb::XColumn > (); + if (!xFieldContent.is()) + // can't supply anything without a field content + // FS - 07.12.99 - 54391 + continue; + + xColumns->getByIndex(nModelPos) >>= xCurrentColumn; + if (!::comphelper::hasProperty(FM_PROP_CLASSID, xCurrentColumn)) + continue; + + sal_Int16 nClassId = sal_Int16(); + xCurrentColumn->getPropertyValue(FM_PROP_CLASSID) >>= nClassId; + if (nClassId>LAST_KNOWN_TYPE) + continue; + DBG_ASSERT(nClassId>0, "FmXGridPeer::queryFieldDataType : somebody changed the definition of the FormComponentType enum !"); + + if (nMapColumn != -1) + pReturnArray[i] = bCanConvert[nClassId-1][nMapColumn]; + } + + return aReturnSequence; +} + + +Sequence< Any > SAL_CALL FmXGridPeer::queryFieldData( sal_Int32 nRow, const Type& xType ) +{ + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + DBG_ASSERT(pGrid && pGrid->IsOpen(), "FmXGridPeer::queryFieldData : have no valid grid window !"); + if (!pGrid || !pGrid->IsOpen()) + return Sequence< Any>(); + + // move the control to the specified row + if (!pGrid->SeekRow(nRow)) + { + throw IllegalArgumentException(); + } + + // don't use GetCurrentRow as this isn't affected by the above SeekRow + // FS - 30.09.99 - 68644 + DbGridRowRef xPaintRow = pGrid->GetPaintRow(); + ENSURE_OR_THROW( xPaintRow.is(), "invalid paint row" ); + + // I need the columns of the control for GetFieldText + std::vector< std::unique_ptr<DbGridColumn> > const & aColumns = pGrid->GetColumns(); + + // and through all the columns + sal_Int32 nColumnCount = pGrid->GetViewColCount(); + + Sequence< Any> aReturnSequence(nColumnCount); + Any* pReturnArray = aReturnSequence.getArray(); + + bool bRequestedAsAny = (xType.getTypeClass() == TypeClass_ANY); + Reference< css::sdb::XColumn > xFieldContent; + for (sal_Int32 i=0; i < nColumnCount; ++i) + { + sal_uInt16 nModelPos = pGrid->GetModelColumnPos(pGrid->GetColumnIdFromViewPos(static_cast<sal_uInt16>(i))); + DBG_ASSERT(nModelPos != sal_uInt16(-1), "FmXGridPeer::queryFieldData : invalid model pos !"); + + // don't use GetCurrentFieldValue to determine the field content as this isn't affected by the above SeekRow + // FS - 30.09.99 - 68644 + DbGridColumn* pCol = aColumns[ nModelPos ].get(); + xFieldContent = xPaintRow->HasField( pCol->GetFieldPos() ) + ? xPaintRow->GetField( pCol->GetFieldPos() ).getColumn() + : Reference< XColumn > (); + + if ( !xFieldContent.is() ) + continue; + + if (bRequestedAsAny) + { + Reference< XPropertySet > xFieldSet(xFieldContent, UNO_QUERY); + pReturnArray[i] = xFieldSet->getPropertyValue(FM_PROP_VALUE); + } + else + { + switch (xType.getTypeClass()) + { + // Strings are dealt with directly by the GetFieldText + case TypeClass_STRING : + { + OUString sText = aColumns[ nModelPos ]->GetCellText( xPaintRow.get(), pGrid->getNumberFormatter() ); + pReturnArray[i] <<= sText; + } + break; + // everything else is requested in the DatabaseVariant + case TypeClass_FLOAT : pReturnArray[i] <<= xFieldContent->getFloat(); break; + case TypeClass_DOUBLE : pReturnArray[i] <<= xFieldContent->getDouble(); break; + case TypeClass_SHORT : pReturnArray[i] <<= xFieldContent->getShort(); break; + case TypeClass_LONG : pReturnArray[i] <<= static_cast<sal_Int32>(xFieldContent->getLong()); break; + case TypeClass_UNSIGNED_SHORT : pReturnArray[i] <<= static_cast<sal_uInt16>(xFieldContent->getShort()); break; + case TypeClass_UNSIGNED_LONG : pReturnArray[i] <<= static_cast<sal_uInt32>(xFieldContent->getLong()); break; + case TypeClass_BOOLEAN : pReturnArray[i] <<= xFieldContent->getBoolean(); break; + default: + { + throw IllegalArgumentException(); + } + } + } + } + return aReturnSequence; +} + + +void FmXGridPeer::CellModified() +{ + std::unique_lock g(m_aMutex); + EventObject aEvt; + aEvt.Source = getXWeak(); + m_aModifyListeners.notifyEach( g, &XModifyListener::modified, aEvt ); +} + +// XPropertyChangeListener + +void FmXGridPeer::propertyChange(const PropertyChangeEvent& evt) +{ + SolarMutexGuard aGuard; + // want to do a lot of VCL stuff here ... + // this should not be (deadlock) critical, as by definition, every component should release + // any own mutexes before notifying + + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + if (!pGrid) + return; + + // Database event + if (evt.PropertyName == FM_PROP_VALUE || m_xCursor == evt.Source) + pGrid->propertyChange(evt); + else if (pGrid && m_xColumns.is() && m_xColumns->hasElements()) + { + // next find which column has changed + css::uno::Reference<css::uno::XInterface> xCurrent; + sal_Int32 i; + + for ( i = 0; i < m_xColumns->getCount(); i++) + { + xCurrent.set(m_xColumns->getByIndex(i), css::uno::UNO_QUERY); + if (evt.Source == xCurrent) + break; + } + + if (i >= m_xColumns->getCount()) + // this is valid because we are listening at the cursor, too (RecordCount, -status, edit mode) + return; + + sal_uInt16 nId = pGrid->GetColumnIdFromModelPos(static_cast<sal_uInt16>(i)); + bool bInvalidateColumn = false; + + if (evt.PropertyName == FM_PROP_LABEL) + { + OUString aName = ::comphelper::getString(evt.NewValue); + if (aName != pGrid->GetColumnTitle(nId)) + pGrid->SetColumnTitle(nId, aName); + } + else if (evt.PropertyName == FM_PROP_WIDTH) + { + sal_Int32 nWidth = 0; + if (evt.NewValue.getValueType().getTypeClass() == TypeClass_VOID) + nWidth = pGrid->GetDefaultColumnWidth(pGrid->GetColumnTitle(nId)); + // GetDefaultColumnWidth already considered the zoom factor + else + { + sal_Int32 nTest = 0; + if (evt.NewValue >>= nTest) + { + nWidth = pGrid->LogicToPixel(Point(nTest, 0), MapMode(MapUnit::Map10thMM)).X(); + // take the zoom factor into account + nWidth = pGrid->CalcZoom(nWidth); + } + } + if (nWidth != (sal_Int32(pGrid->GetColumnWidth(nId)))) + { + if (pGrid->IsEditing()) + { + pGrid->DeactivateCell(); + pGrid->ActivateCell(); + } + pGrid->SetColumnWidth(nId, nWidth); + } + } + else if (evt.PropertyName == FM_PROP_HIDDEN) + { + DBG_ASSERT(evt.NewValue.getValueType().getTypeClass() == TypeClass_BOOLEAN, + "FmXGridPeer::propertyChange : the property 'hidden' should be of type boolean !"); + if (::comphelper::getBOOL(evt.NewValue)) + pGrid->HideColumn(nId); + else + pGrid->ShowColumn(nId); + } + else if (evt.PropertyName == FM_PROP_ALIGN) + { + // in design mode it doesn't matter + if (!isDesignMode()) + { + DbGridColumn* pCol = pGrid->GetColumns()[i].get(); + + pCol->SetAlignmentFromModel(-1); + bInvalidateColumn = true; + } + } + else if (evt.PropertyName == FM_PROP_FORMATKEY) + { + if (!isDesignMode()) + bInvalidateColumn = true; + } + + // need to invalidate the affected column ? + if (bInvalidateColumn) + { + bool bWasEditing = pGrid->IsEditing(); + if (bWasEditing) + pGrid->DeactivateCell(); + + ::tools::Rectangle aColRect = pGrid->GetFieldRect(nId); + aColRect.SetTop( 0 ); + aColRect.SetBottom( pGrid->GetSizePixel().Height() ); + pGrid->Invalidate(aColRect); + + if (bWasEditing) + pGrid->ActivateCell(); + } + } +} + +// XBoundComponent + +void FmXGridPeer::addUpdateListener(const Reference< XUpdateListener >& l) +{ + std::unique_lock g(m_aMutex); + m_aUpdateListeners.addInterface(g, l); +} + + +void FmXGridPeer::removeUpdateListener(const Reference< XUpdateListener >& l) +{ + std::unique_lock g(m_aMutex); + m_aUpdateListeners.removeInterface(g, l); +} + + +sal_Bool FmXGridPeer::commit() +{ + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + if (!m_xCursor.is() || !pGrid) + return true; + + std::unique_lock g(m_aMutex); + EventObject aEvt(getXWeak()); + ::comphelper::OInterfaceIteratorHelper4 aIter(g, m_aUpdateListeners); + bool bCancel = false; + while (aIter.hasMoreElements() && !bCancel) + if ( !aIter.next()->approveUpdate( aEvt ) ) + bCancel = true; + + if (!bCancel) + bCancel = !pGrid->commit(); + + if (!bCancel) + m_aUpdateListeners.notifyEach( g, &XUpdateListener::updated, aEvt ); + return !bCancel; +} + + +void FmXGridPeer::cursorMoved(const EventObject& _rEvent) +{ + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + // we are not interested in moving to insert row only in the reset event + // which is fired after positioning and the insert row + if (pGrid && pGrid->IsOpen() && !::comphelper::getBOOL(Reference< XPropertySet > (_rEvent.Source, UNO_QUERY_THROW)->getPropertyValue(FM_PROP_ISNEW))) + pGrid->positioned(); +} + + +void FmXGridPeer::rowChanged(const EventObject& /*_rEvent*/) +{ + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + if (pGrid && pGrid->IsOpen()) + { + if (m_xCursor->rowUpdated() && !pGrid->IsCurrentAppending()) + pGrid->RowModified(pGrid->GetCurrentPos()); + else if (m_xCursor->rowInserted()) + pGrid->inserted(); + } +} + + +void FmXGridPeer::rowSetChanged(const EventObject& /*event*/) +{ + // not interested in ... + // (our parent is a form which means we get a loaded or reloaded after this rowSetChanged) +} + +// XLoadListener + +void FmXGridPeer::loaded(const EventObject& /*rEvent*/) +{ + updateGrid(m_xCursor); +} + + +void FmXGridPeer::unloaded(const EventObject& /*rEvent*/) +{ + updateGrid( Reference< XRowSet > (nullptr) ); +} + + +void FmXGridPeer::reloading(const EventObject& /*aEvent*/) +{ + // empty the grid + updateGrid( Reference< XRowSet > (nullptr) ); +} + + +void FmXGridPeer::unloading(const EventObject& /*aEvent*/) +{ + // empty the grid + updateGrid( Reference< XRowSet > (nullptr) ); +} + + +void FmXGridPeer::reloaded(const EventObject& aEvent) +{ + { + const sal_Int32 cnt = m_xColumns->getCount(); + for(sal_Int32 i=0; i<cnt; ++i) + { + Reference< XLoadListener> xll(m_xColumns->getByIndex(i), UNO_QUERY); + if(xll.is()) + { + xll->reloaded(aEvent); + } + } + } + updateGrid(m_xCursor); +} + +// XGridPeer + +Reference< XIndexContainer > FmXGridPeer::getColumns() +{ + return m_xColumns; +} + + +void FmXGridPeer::addColumnListeners(const Reference< XPropertySet >& xCol) +{ + static constexpr OUString aPropsListenedTo[] = + { + FM_PROP_LABEL, FM_PROP_WIDTH, FM_PROP_HIDDEN, FM_PROP_ALIGN, + FM_PROP_FORMATKEY + }; + + // as not all properties have to be supported by all columns we have to check this + // before adding a listener + Reference< XPropertySetInfo > xInfo = xCol->getPropertySetInfo(); + for (size_t i=0; i<SAL_N_ELEMENTS(aPropsListenedTo); ++i) + { + if ( xInfo->hasPropertyByName( aPropsListenedTo[i] ) ) + { + Property aPropDesc = xInfo->getPropertyByName( aPropsListenedTo[i] ); + if ( 0 != ( aPropDesc.Attributes & PropertyAttribute::BOUND ) ) + xCol->addPropertyChangeListener( aPropsListenedTo[i], this ); + } + } +} + + +void FmXGridPeer::removeColumnListeners(const Reference< XPropertySet >& xCol) +{ + // the same props as in addColumnListeners... linux has problems with global static UStrings, so + // we have to do it this way... + static constexpr OUString aPropsListenedTo[] = + { + FM_PROP_LABEL, FM_PROP_WIDTH, FM_PROP_HIDDEN, FM_PROP_ALIGN, + FM_PROP_FORMATKEY + }; + + Reference< XPropertySetInfo > xInfo = xCol->getPropertySetInfo(); + for (const auto & i : aPropsListenedTo) + if (xInfo->hasPropertyByName(i)) + xCol->removePropertyChangeListener(i, this); +} + + +void FmXGridPeer::setColumns(const Reference< XIndexContainer >& Columns) +{ + SolarMutexGuard aGuard; + + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + + if (m_xColumns.is()) + { + Reference< XPropertySet > xCol; + for (sal_Int32 i = 0; i < m_xColumns->getCount(); i++) + { + xCol.set(m_xColumns->getByIndex(i), css::uno::UNO_QUERY); + removeColumnListeners(xCol); + } + Reference< XContainer > xContainer(m_xColumns, UNO_QUERY); + xContainer->removeContainerListener(this); + + Reference< XSelectionSupplier > xSelSupplier(m_xColumns, UNO_QUERY); + xSelSupplier->removeSelectionChangeListener(this); + + Reference< XReset > xColumnReset(m_xColumns, UNO_QUERY); + if (xColumnReset.is()) + xColumnReset->removeResetListener(static_cast<XResetListener*>(this)); + } + if (Columns.is()) + { + Reference< XContainer > xContainer(Columns, UNO_QUERY); + xContainer->addContainerListener(this); + + Reference< XSelectionSupplier > xSelSupplier(Columns, UNO_QUERY); + xSelSupplier->addSelectionChangeListener(this); + + Reference< XPropertySet > xCol; + for (sal_Int32 i = 0; i < Columns->getCount(); i++) + { + xCol.set(Columns->getByIndex(i), css::uno::UNO_QUERY); + addColumnListeners(xCol); + } + + Reference< XReset > xColumnReset(Columns, UNO_QUERY); + if (xColumnReset.is()) + xColumnReset->addResetListener(static_cast<XResetListener*>(this)); + } + m_xColumns = Columns; + if (pGrid) + { + pGrid->InitColumnsByModels(m_xColumns); + + if (m_xColumns.is()) + { + EventObject aEvt(m_xColumns); + selectionChanged(aEvt); + } + } +} + + +void FmXGridPeer::setDesignMode(sal_Bool bOn) +{ + if (bOn != isDesignMode()) + { + VclPtr<vcl::Window> pWin = GetWindow(); + if (pWin) + static_cast<FmGridControl*>(pWin.get())->SetDesignMode(bOn); + } + + if (bOn) + DisConnectFromDispatcher(); + else + UpdateDispatches(); // will connect if not already connected and just update else +} + + +sal_Bool FmXGridPeer::isDesignMode() +{ + VclPtr<vcl::Window> pWin = GetWindow(); + if (pWin) + return static_cast<FmGridControl*>(pWin.get())->IsDesignMode(); + else + return false; +} + + +void FmXGridPeer::elementInserted(const ContainerEvent& evt) +{ + SolarMutexGuard aGuard; + + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + // take handle column into account + if (!pGrid || !m_xColumns.is() || pGrid->IsInColumnMove() || m_xColumns->getCount() == static_cast<sal_Int32>(pGrid->GetModelColCount())) + return; + + Reference< XPropertySet > xNewColumn(evt.Element, css::uno::UNO_QUERY); + addColumnListeners(xNewColumn); + + OUString aName = ::comphelper::getString(xNewColumn->getPropertyValue(FM_PROP_LABEL)); + Any aWidth = xNewColumn->getPropertyValue(FM_PROP_WIDTH); + sal_Int32 nWidth = 0; + if (aWidth >>= nWidth) + nWidth = pGrid->LogicToPixel(Point(nWidth, 0), MapMode(MapUnit::Map10thMM)).X(); + + pGrid->AppendColumn(aName, static_cast<sal_uInt16>(nWidth), static_cast<sal_Int16>(::comphelper::getINT32(evt.Accessor))); + + // now set the column + DbGridColumn* pCol = pGrid->GetColumns()[ ::comphelper::getINT32(evt.Accessor) ].get(); + pCol->setModel(xNewColumn); + + Any aHidden = xNewColumn->getPropertyValue(FM_PROP_HIDDEN); + if (::comphelper::getBOOL(aHidden)) + pGrid->HideColumn(pCol->GetId()); + + FormControlFactory( m_xContext ).initializeTextFieldLineEnds( xNewColumn ); +} + + +void FmXGridPeer::elementReplaced(const ContainerEvent& evt) +{ + SolarMutexGuard aGuard; + + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + + // take handle column into account + if (!pGrid || !m_xColumns.is() || pGrid->IsInColumnMove()) + return; + + Reference< XPropertySet > xNewColumn(evt.Element, css::uno::UNO_QUERY); + Reference< XPropertySet > xOldColumn( + evt.ReplacedElement, css::uno::UNO_QUERY); + + bool bWasEditing = pGrid->IsEditing(); + if (bWasEditing) + pGrid->DeactivateCell(); + + pGrid->RemoveColumn(pGrid->GetColumnIdFromModelPos(static_cast<sal_uInt16>(::comphelper::getINT32(evt.Accessor)))); + + removeColumnListeners(xOldColumn); + addColumnListeners(xNewColumn); + + OUString aName = ::comphelper::getString(xNewColumn->getPropertyValue(FM_PROP_LABEL)); + Any aWidth = xNewColumn->getPropertyValue(FM_PROP_WIDTH); + sal_Int32 nWidth = 0; + if (aWidth >>= nWidth) + nWidth = pGrid->LogicToPixel(Point(nWidth, 0), MapMode(MapUnit::Map10thMM)).X(); + sal_uInt16 nNewId = pGrid->AppendColumn(aName, static_cast<sal_uInt16>(nWidth), static_cast<sal_Int16>(::comphelper::getINT32(evt.Accessor))); + sal_uInt16 nNewPos = pGrid->GetModelColumnPos(nNewId); + + // set the model of the new column + DbGridColumn* pCol = pGrid->GetColumns()[ nNewPos ].get(); + + // for initializing this grid column, we need the fields of the grid's data source + Reference< XColumnsSupplier > xSuppColumns; + CursorWrapper* pGridDataSource = pGrid->getDataSource(); + if ( pGridDataSource ) + xSuppColumns.set(Reference< XInterface >( *pGridDataSource ), css::uno::UNO_QUERY); + Reference< XNameAccess > xColumnsByName; + if ( xSuppColumns.is() ) + xColumnsByName = xSuppColumns->getColumns(); + Reference< XIndexAccess > xColumnsByIndex( xColumnsByName, UNO_QUERY ); + + if ( xColumnsByIndex.is() ) + FmGridControl::InitColumnByField( pCol, xNewColumn, xColumnsByName, xColumnsByIndex ); + else + // the simple version, applies when the grid is not yet connected to a data source + pCol->setModel(xNewColumn); + + if (bWasEditing) + pGrid->ActivateCell(); +} + + +void FmXGridPeer::elementRemoved(const ContainerEvent& evt) +{ + SolarMutexGuard aGuard; + + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + + // take handle column into account + if (!pGrid || !m_xColumns.is() || pGrid->IsInColumnMove() || m_xColumns->getCount() == static_cast<sal_Int32>(pGrid->GetModelColCount())) + return; + + pGrid->RemoveColumn(pGrid->GetColumnIdFromModelPos(static_cast<sal_uInt16>(::comphelper::getINT32(evt.Accessor)))); + + Reference< XPropertySet > xOldColumn(evt.Element, css::uno::UNO_QUERY); + removeColumnListeners(xOldColumn); +} + + +void FmXGridPeer::setProperty( const OUString& PropertyName, const Any& Value) +{ + SolarMutexGuard aGuard; + + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + + bool bVoid = !Value.hasValue(); + + if ( PropertyName == FM_PROP_TEXTLINECOLOR ) + { + ::Color aTextLineColor( bVoid ? COL_TRANSPARENT : ::Color(ColorTransparency, ::comphelper::getINT32( Value )) ); + if (bVoid) + { + pGrid->SetTextLineColor(); + pGrid->GetDataWindow().SetTextLineColor(); + } + else + { + pGrid->SetTextLineColor(aTextLineColor); + pGrid->GetDataWindow().SetTextLineColor(aTextLineColor); + } + + // need to forward this to the columns + std::vector< std::unique_ptr<DbGridColumn> > const & rColumns = pGrid->GetColumns(); + for (auto const & pLoop : rColumns) + { + FmXGridCell* pXCell = pLoop->GetCell(); + if (pXCell) + { + if (bVoid) + pXCell->SetTextLineColor(); + else + pXCell->SetTextLineColor(aTextLineColor); + } + } + + if (isDesignMode()) + pGrid->Invalidate(); + } + else if ( PropertyName == FM_PROP_FONTEMPHASISMARK ) + { + vcl::Font aGridFont = pGrid->GetControlFont(); + sal_Int16 nValue = ::comphelper::getINT16(Value); + aGridFont.SetEmphasisMark( static_cast<FontEmphasisMark>(nValue) ); + pGrid->SetControlFont( aGridFont ); + } + else if ( PropertyName == FM_PROP_FONTRELIEF ) + { + vcl::Font aGridFont = pGrid->GetControlFont(); + sal_Int16 nValue = ::comphelper::getINT16(Value); + aGridFont.SetRelief( static_cast<FontRelief>(nValue) ); + pGrid->SetControlFont( aGridFont ); + } + else if ( PropertyName == FM_PROP_HELPURL ) + { + OUString sHelpURL; + OSL_VERIFY( Value >>= sHelpURL ); + INetURLObject aHID( sHelpURL ); + if ( aHID.GetProtocol() == INetProtocol::Hid ) + sHelpURL = aHID.GetURLPath(); + pGrid->SetHelpId( sHelpURL ); + } + else if ( PropertyName == FM_PROP_DISPLAYSYNCHRON ) + { + pGrid->setDisplaySynchron(::comphelper::getBOOL(Value)); + } + else if ( PropertyName == FM_PROP_CURSORCOLOR ) + { + if (bVoid) + pGrid->SetCursorColor(COL_TRANSPARENT); + else + pGrid->SetCursorColor( ::Color(ColorTransparency, ::comphelper::getINT32(Value))); + if (isDesignMode()) + pGrid->Invalidate(); + } + else if ( PropertyName == FM_PROP_ALWAYSSHOWCURSOR ) + { + pGrid->EnablePermanentCursor(::comphelper::getBOOL(Value)); + if (isDesignMode()) + pGrid->Invalidate(); + } + else if ( PropertyName == FM_PROP_FONT ) + { + if ( bVoid ) + pGrid->SetControlFont( vcl::Font() ); + else + { + css::awt::FontDescriptor aFont; + if (Value >>= aFont) + { + vcl::Font aNewVclFont; + if (aFont != ::comphelper::getDefaultFont()) // is this the default + aNewVclFont = ImplCreateFont( aFont ); + + // need to add relief and emphasis (they're stored in a VCL-Font, but not in a FontDescriptor + vcl::Font aOldVclFont = pGrid->GetControlFont(); + aNewVclFont.SetRelief( aOldVclFont.GetRelief() ); + aNewVclFont.SetEmphasisMark( aOldVclFont.GetEmphasisMark() ); + + // now set it ... + pGrid->SetControlFont( aNewVclFont ); + + // if our row-height property is void (which means "calculate it font-dependent") we have + // to adjust the control's row height + Reference< XPropertySet > xModelSet(getColumns(), UNO_QUERY); + if (xModelSet.is() && ::comphelper::hasProperty(FM_PROP_ROWHEIGHT, xModelSet)) + { + Any aHeight = xModelSet->getPropertyValue(FM_PROP_ROWHEIGHT); + if (!aHeight.hasValue()) + pGrid->SetDataRowHeight(0); + } + + } + } + } + else if ( PropertyName == FM_PROP_BACKGROUNDCOLOR ) + { + if ( bVoid ) + { + pGrid->SetControlBackground(); + } + else + { + ::Color aColor( ColorTransparency, ::comphelper::getINT32(Value) ); + pGrid->SetBackground( aColor ); + pGrid->SetControlBackground( aColor ); + } + } + else if ( PropertyName == FM_PROP_TEXTCOLOR ) + { + if ( bVoid ) + { + pGrid->SetControlForeground(); + } + else + { + ::Color aColor( ColorTransparency, ::comphelper::getINT32(Value) ); + pGrid->SetTextColor( aColor ); + pGrid->SetControlForeground( aColor ); + } + } + else if ( PropertyName == FM_PROP_ROWHEIGHT ) + { + sal_Int32 nLogHeight(0); + if (Value >>= nLogHeight) + { + sal_Int32 nHeight = pGrid->LogicToPixel(Point(0, nLogHeight), MapMode(MapUnit::Map10thMM)).Y(); + // take the zoom factor into account + nHeight = pGrid->CalcZoom(nHeight); + pGrid->SetDataRowHeight(nHeight); + } + else if (bVoid) + pGrid->SetDataRowHeight(0); + } + else if ( PropertyName == FM_PROP_HASNAVIGATION ) + { + bool bValue( true ); + OSL_VERIFY( Value >>= bValue ); + pGrid->EnableNavigationBar( bValue ); + } + else if ( PropertyName == FM_PROP_RECORDMARKER ) + { + bool bValue( true ); + OSL_VERIFY( Value >>= bValue ); + pGrid->EnableHandle( bValue ); + } + else if ( PropertyName == FM_PROP_ENABLED ) + { + bool bValue( true ); + OSL_VERIFY( Value >>= bValue ); + + // In design mode, disable only the data window. + // Else the control cannot be configured anymore. + if (isDesignMode()) + pGrid->GetDataWindow().Enable( bValue ); + else + pGrid->Enable( bValue ); + } + else + VCLXWindow::setProperty( PropertyName, Value ); +} + + +Reference< XAccessibleContext > FmXGridPeer::CreateAccessibleContext() +{ + Reference< XAccessibleContext > xContext; + + // use the AccessibleContext provided by the VCL window + VclPtr<vcl::Window> pGrid = GetWindow(); + if ( pGrid ) + { + Reference< XAccessible > xAcc( pGrid->GetAccessible() ); + if ( xAcc.is() ) + xContext = xAcc->getAccessibleContext(); + // TODO: this has a slight conceptual problem: + + // We know that the XAccessible and XAccessibleContext implementation of the browse + // box is the same (the class implements both interfaces), which, speaking strictly, + // is bad here (means when a browse box acts as UnoControl): We (the FmXGridPeer) are + // the XAccessible here, and the browse box should be able to provide us an XAccessibleContext, + // but it should _not_ be the XAccessible itself. + // However, as long as no client implementation uses dirty hacks such as querying an + // XAccessibleContext for XAccessible, this should not be a problem. + } + + if ( !xContext.is() ) + xContext = VCLXWindow::CreateAccessibleContext( ); + + return xContext; +} + + +Any FmXGridPeer::getProperty( const OUString& _rPropertyName ) +{ + Any aProp; + if (GetWindow()) + { + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + vcl::Window* pDataWindow = &pGrid->GetDataWindow(); + + if ( _rPropertyName == FM_PROP_NAME ) + { + vcl::Font aFont = pDataWindow->GetControlFont(); + aProp <<= ImplCreateFontDescriptor( aFont ); + } + else if ( _rPropertyName == FM_PROP_TEXTCOLOR ) + { + aProp <<= pDataWindow->GetControlForeground(); + } + else if ( _rPropertyName == FM_PROP_BACKGROUNDCOLOR ) + { + aProp <<= pDataWindow->GetControlBackground(); + } + else if ( _rPropertyName == FM_PROP_ROWHEIGHT ) + { + sal_Int32 nPixelHeight = pGrid->GetDataRowHeight(); + // take the zoom factor into account + nPixelHeight = pGrid->CalcReverseZoom(nPixelHeight); + aProp <<= static_cast<sal_Int32>(pGrid->PixelToLogic(Point(0, nPixelHeight), MapMode(MapUnit::Map10thMM)).Y()); + } + else if ( _rPropertyName == FM_PROP_HASNAVIGATION ) + { + bool bHasNavBar = pGrid->HasNavigationBar(); + aProp <<= bHasNavBar; + } + else if ( _rPropertyName == FM_PROP_RECORDMARKER ) + { + bool bHasHandle = pGrid->HasHandle(); + aProp <<= bHasHandle; + } + else if ( _rPropertyName == FM_PROP_ENABLED ) + { + aProp <<= pDataWindow->IsEnabled(); + } + else + aProp = VCLXWindow::getProperty( _rPropertyName ); + } + return aProp; +} + + +void FmXGridPeer::dispose() +{ + { + std::unique_lock g(m_aMutex); + EventObject aEvt; + aEvt.Source = getXWeak(); + m_aModifyListeners.disposeAndClear(g, aEvt); + m_aUpdateListeners.disposeAndClear(g, aEvt); + m_aContainerListeners.disposeAndClear(g, aEvt); + } + // release all interceptors + Reference< XDispatchProviderInterceptor > xInterceptor( m_xFirstDispatchInterceptor ); + m_xFirstDispatchInterceptor.clear(); + while ( xInterceptor.is() ) + { + // tell the interceptor it has a new (means no) predecessor + xInterceptor->setMasterDispatchProvider( nullptr ); + + // ask for its successor + Reference< XDispatchProvider > xSlave = xInterceptor->getSlaveDispatchProvider(); + // and give it the new (means no) successoert + xInterceptor->setSlaveDispatchProvider( nullptr ); + + // start over with the next chain element + xInterceptor.set(xSlave, css::uno::UNO_QUERY); + } + + DisConnectFromDispatcher(); + + // unregister all listeners + if (m_xCursor.is()) + { + m_xCursor->removeRowSetListener(this); + + Reference< XReset > xReset(m_xCursor, UNO_QUERY); + if (xReset.is()) + xReset->removeResetListener(this); + Reference< XLoadable > xLoadable(m_xCursor, UNO_QUERY); + if (xLoadable.is()) + xLoadable->removeLoadListener(this); + Reference< XPropertySet > xSet(m_xCursor, UNO_QUERY); + if (xSet.is()) + { + xSet->removePropertyChangeListener(FM_PROP_ISMODIFIED, this); + xSet->removePropertyChangeListener(FM_PROP_ROWCOUNT, this); + } + m_xCursor.clear(); + } + + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + if (pGrid) + { + pGrid->setDataSource(Reference< XRowSet > ()); + pGrid->DisposeAccessible(); + } + + VCLXWindow::dispose(); +} + +// XContainer + +void FmXGridPeer::addContainerListener(const Reference< XContainerListener >& l) +{ + std::unique_lock g(m_aMutex); + m_aContainerListeners.addInterface( g, l ); +} + +void FmXGridPeer::removeContainerListener(const Reference< XContainerListener >& l) +{ + std::unique_lock g(m_aMutex); + m_aContainerListeners.removeInterface( g, l ); +} + +// css::data::XDatabaseCursorSupplier + +void FmXGridPeer::startCursorListening() +{ + if (!m_nCursorListening) + { + if (m_xCursor.is()) + m_xCursor->addRowSetListener(this); + + Reference< XReset > xReset(m_xCursor, UNO_QUERY); + if (xReset.is()) + xReset->addResetListener(this); + + // register all listeners + Reference< XPropertySet > xSet(m_xCursor, UNO_QUERY); + if (xSet.is()) + { + xSet->addPropertyChangeListener(FM_PROP_ISMODIFIED, this); + xSet->addPropertyChangeListener(FM_PROP_ROWCOUNT, this); + } + } + m_nCursorListening++; +} + + +void FmXGridPeer::stopCursorListening() +{ + if (--m_nCursorListening) + return; + + if (m_xCursor.is()) + m_xCursor->removeRowSetListener(this); + + Reference< XReset > xReset(m_xCursor, UNO_QUERY); + if (xReset.is()) + xReset->removeResetListener(this); + + Reference< XPropertySet > xSet(m_xCursor, UNO_QUERY); + if (xSet.is()) + { + xSet->removePropertyChangeListener(FM_PROP_ISMODIFIED, this); + xSet->removePropertyChangeListener(FM_PROP_ROWCOUNT, this); + } +} + + +void FmXGridPeer::updateGrid(const Reference< XRowSet >& _rxCursor) +{ + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + if (pGrid) + pGrid->setDataSource(_rxCursor); +} + + +Reference< XRowSet > FmXGridPeer::getRowSet() +{ + return m_xCursor; +} + + +void FmXGridPeer::setRowSet(const Reference< XRowSet >& _rDatabaseCursor) +{ + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + if (!pGrid || !m_xColumns.is() || !m_xColumns->getCount()) + return; + // unregister all listeners + if (m_xCursor.is()) + { + Reference< XLoadable > xLoadable(m_xCursor, UNO_QUERY); + // only if the form is loaded we set the rowset + if (xLoadable.is()) + { + stopCursorListening(); + xLoadable->removeLoadListener(this); + } + } + + m_xCursor = _rDatabaseCursor; + + if (!pGrid) + return; + + Reference< XLoadable > xLoadable(m_xCursor, UNO_QUERY); + // only if the form is loaded we set the rowset + if (xLoadable.is() && xLoadable->isLoaded()) + pGrid->setDataSource(m_xCursor); + else + pGrid->setDataSource(Reference< XRowSet > ()); + + if (xLoadable.is()) + { + startCursorListening(); + xLoadable->addLoadListener(this); + } +} + + +void SAL_CALL FmXGridPeer::addGridControlListener( const Reference< XGridControlListener >& _listener ) +{ + std::unique_lock g(m_aMutex); + m_aGridControlListeners.addInterface( g, _listener ); +} + + +void SAL_CALL FmXGridPeer::removeGridControlListener( const Reference< XGridControlListener >& _listener ) +{ + std::unique_lock g(m_aMutex); + m_aGridControlListeners.removeInterface( g, _listener ); +} + + +sal_Int16 FmXGridPeer::getCurrentColumnPosition() +{ + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + return pGrid ? pGrid->GetViewColumnPos(pGrid->GetCurColumnId()) : -1; +} + + +void FmXGridPeer::setCurrentColumnPosition(sal_Int16 nPos) +{ + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + if (pGrid) + pGrid->GoToColumnId(pGrid->GetColumnIdFromViewPos(nPos)); +} + + +void FmXGridPeer::selectionChanged(const EventObject& evt) +{ + SolarMutexGuard aGuard; + + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + if (!pGrid) + return; + + Reference< css::view::XSelectionSupplier > xSelSupplier(evt.Source, UNO_QUERY); + Any aSelection = xSelSupplier->getSelection(); + DBG_ASSERT(aSelection.getValueType().getTypeClass() == TypeClass_INTERFACE, "FmXGridPeer::selectionChanged : invalid selection !"); + Reference< XPropertySet > xSelection; + aSelection >>= xSelection; + if (xSelection.is()) + { + Reference< XPropertySet > xCol; + sal_Int32 i = 0; + sal_Int32 nColCount = m_xColumns->getCount(); + + for (; i < nColCount; ++i) + { + m_xColumns->getByIndex(i) >>= xCol; + if ( xCol == xSelection ) + { + pGrid->markColumn(pGrid->GetColumnIdFromModelPos(static_cast<sal_uInt16>(i))); + break; + } + } + // The columns have to be 1-based for the VCL control. + // If necessary, pass on the selection to the VCL control + if ( i != pGrid->GetSelectedColumn() ) + { // (if this does not take effect, the selectionChanged was implicitly triggered by the control itself) + if ( i < nColCount ) + { + pGrid->SelectColumnPos(pGrid->GetViewColumnPos(pGrid->GetColumnIdFromModelPos( static_cast<sal_uInt16>(i) )) + 1); + // SelectColumnPos has led to an implicit ActivateCell again + if (pGrid->IsEditing()) + pGrid->DeactivateCell(); + } + else + pGrid->SetNoSelection(); + } + } + else + pGrid->markColumn(USHRT_MAX); +} + +// XElementAccess + +sal_Bool FmXGridPeer::hasElements() +{ + return getCount() != 0; +} + + +Type SAL_CALL FmXGridPeer::getElementType( ) +{ + return cppu::UnoType<css::awt::XControl>::get(); +} + +// XEnumerationAccess + +Reference< XEnumeration > FmXGridPeer::createEnumeration() +{ + return new ::comphelper::OEnumerationByIndex(this); +} + +// XIndexAccess + +sal_Int32 FmXGridPeer::getCount() +{ + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + if (pGrid) + return pGrid->GetViewColCount(); + else + return 0; +} + + +Any FmXGridPeer::getByIndex(sal_Int32 _nIndex) +{ + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + if (_nIndex < 0 || + _nIndex >= getCount() || !pGrid) + throw IndexOutOfBoundsException(); + + Any aElement; + // get the columnid + sal_uInt16 nId = pGrid->GetColumnIdFromViewPos(static_cast<sal_uInt16>(_nIndex)); + // get the list position + sal_uInt16 nPos = pGrid->GetModelColumnPos(nId); + + if ( nPos == GRID_COLUMN_NOT_FOUND ) + return aElement; + + DbGridColumn* pCol = pGrid->GetColumns()[ nPos ].get(); + Reference< css::awt::XControl > xControl(pCol->GetCell()); + aElement <<= xControl; + + return aElement; +} + +// css::util::XModeSelector + +void FmXGridPeer::setMode(const OUString& Mode) +{ + if (!supportsMode(Mode)) + throw NoSupportException(); + + if (Mode == m_aMode) + return; + + m_aMode = Mode; + + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + if ( Mode == "FilterMode" ) + pGrid->SetFilterMode(true); + else + { + pGrid->SetFilterMode(false); + pGrid->setDataSource(m_xCursor); + } +} + + +OUString FmXGridPeer::getMode() +{ + return m_aMode; +} + + +css::uno::Sequence<OUString> FmXGridPeer::getSupportedModes() +{ + static css::uno::Sequence<OUString> const aModes + { + "DataMode", + "FilterMode" + }; + return aModes; +} + + +sal_Bool FmXGridPeer::supportsMode(const OUString& Mode) +{ + css::uno::Sequence<OUString> aModes(getSupportedModes()); + return comphelper::findValue(aModes, Mode) != -1; +} + + +void FmXGridPeer::columnVisible(DbGridColumn const * pColumn) +{ + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + + sal_Int32 _nIndex = pGrid->GetModelColumnPos(pColumn->GetId()); + Reference< css::awt::XControl > xControl(pColumn->GetCell()); + ContainerEvent aEvt; + aEvt.Source = static_cast<XContainer*>(this); + aEvt.Accessor <<= _nIndex; + aEvt.Element <<= xControl; + + std::unique_lock g(m_aMutex); + m_aContainerListeners.notifyEach( g, &XContainerListener::elementInserted, aEvt ); +} + + +void FmXGridPeer::columnHidden(DbGridColumn const * pColumn) +{ + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + + sal_Int32 _nIndex = pGrid->GetModelColumnPos(pColumn->GetId()); + Reference< css::awt::XControl > xControl(pColumn->GetCell()); + ContainerEvent aEvt; + aEvt.Source = static_cast<XContainer*>(this); + aEvt.Accessor <<= _nIndex; + aEvt.Element <<= xControl; + + std::unique_lock g(m_aMutex); + m_aContainerListeners.notifyEach( g, &XContainerListener::elementRemoved, aEvt ); +} + + +void FmXGridPeer::draw( sal_Int32 x, sal_Int32 y ) +{ + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + EditBrowseBoxFlags nOldFlags = pGrid->GetBrowserFlags(); + pGrid->SetBrowserFlags(nOldFlags | EditBrowseBoxFlags::NO_HANDLE_COLUMN_CONTENT); + + VCLXWindow::draw(x, y); + + pGrid->SetBrowserFlags(nOldFlags); +} + + +Reference< css::frame::XDispatch > FmXGridPeer::queryDispatch(const css::util::URL& aURL, const OUString& aTargetFrameName, sal_Int32 nSearchFlags) +{ + Reference< css::frame::XDispatch > xResult; + + // first ask our interceptor chain + if (m_xFirstDispatchInterceptor.is() && !m_bInterceptingDispatch) + { + m_bInterceptingDispatch = true; + // safety against recursion : as we are master of the first chain element and slave of the last one we would + // have an infinite loop without this if no dispatcher can fulfill the request + xResult = m_xFirstDispatchInterceptor->queryDispatch(aURL, aTargetFrameName, nSearchFlags); + m_bInterceptingDispatch = false; + } + + // then ask ourself : we don't have any dispatches + return xResult; +} + + +Sequence< Reference< css::frame::XDispatch > > FmXGridPeer::queryDispatches(const Sequence< css::frame::DispatchDescriptor>& aDescripts) +{ + if (m_xFirstDispatchInterceptor.is()) + return m_xFirstDispatchInterceptor->queryDispatches(aDescripts); + + // then ask ourself : we don't have any dispatches + return Sequence< Reference< css::frame::XDispatch > >(); +} + + +void FmXGridPeer::registerDispatchProviderInterceptor(const Reference< css::frame::XDispatchProviderInterceptor >& _xInterceptor) +{ + if (!_xInterceptor.is()) + return; + + if (m_xFirstDispatchInterceptor.is()) + { + // there is already an interceptor; the new one will become its master + _xInterceptor->setSlaveDispatchProvider(m_xFirstDispatchInterceptor); + m_xFirstDispatchInterceptor->setMasterDispatchProvider(m_xFirstDispatchInterceptor); + } + else + { + // it is the first interceptor; set ourself as slave + _xInterceptor->setSlaveDispatchProvider(static_cast<css::frame::XDispatchProvider*>(this)); + } + + // we are the master of the chain's first interceptor + m_xFirstDispatchInterceptor = _xInterceptor; + m_xFirstDispatchInterceptor->setMasterDispatchProvider(static_cast<css::frame::XDispatchProvider*>(this)); + + // we have a new interceptor and we're alive ? + if (!isDesignMode()) + // -> check for new dispatchers + UpdateDispatches(); +} + + +void FmXGridPeer::releaseDispatchProviderInterceptor(const Reference< css::frame::XDispatchProviderInterceptor >& _xInterceptor) +{ + if (!_xInterceptor.is()) + return; + + Reference< css::frame::XDispatchProviderInterceptor > xChainWalk(m_xFirstDispatchInterceptor); + + if (m_xFirstDispatchInterceptor == _xInterceptor) + { // our chain will have a new first element + Reference< css::frame::XDispatchProviderInterceptor > xSlave(m_xFirstDispatchInterceptor->getSlaveDispatchProvider(), UNO_QUERY); + m_xFirstDispatchInterceptor = xSlave; + } + // do this before removing the interceptor from the chain as we won't know it's slave afterwards) + + while (xChainWalk.is()) + { + // walk along the chain of interceptors and look for the interceptor that has to be removed + Reference< css::frame::XDispatchProviderInterceptor > xSlave(xChainWalk->getSlaveDispatchProvider(), UNO_QUERY); + + if (xChainWalk == _xInterceptor) + { + // old master may be an interceptor too + Reference< css::frame::XDispatchProviderInterceptor > xMaster(xChainWalk->getMasterDispatchProvider(), UNO_QUERY); + + // unchain the interceptor that has to be removed + xChainWalk->setSlaveDispatchProvider(Reference< css::frame::XDispatchProvider > ()); + xChainWalk->setMasterDispatchProvider(Reference< css::frame::XDispatchProvider > ()); + + // reconnect the chain + if (xMaster.is()) + { + if (xSlave.is()) + xMaster->setSlaveDispatchProvider(xSlave); + else + // it's the first interceptor of the chain, set ourself as slave + xMaster->setSlaveDispatchProvider(static_cast<css::frame::XDispatchProvider*>(this)); + } + else + { + // the chain's first element was removed, set ourself as new master of the second one + if (xSlave.is()) + xSlave->setMasterDispatchProvider(static_cast<css::frame::XDispatchProvider*>(this)); + } + } + + xChainWalk = xSlave; + } + // our interceptor chain has changed and we're alive ? + if (!isDesignMode()) + // -> check the dispatchers + UpdateDispatches(); +} + + +void FmXGridPeer::statusChanged(const css::frame::FeatureStateEvent& Event) +{ + DBG_ASSERT(m_pStateCache, "FmXGridPeer::statusChanged : invalid call !"); + DBG_ASSERT(m_pDispatchers, "FmXGridPeer::statusChanged : invalid call !"); + + const Sequence< css::util::URL>& aUrls = getSupportedURLs(); + + const std::vector<DbGridControlNavigationBarState>& aSlots = getSupportedGridSlots(); + + auto pUrl = std::find_if(aUrls.begin(), aUrls.end(), + [&Event](const css::util::URL& rUrl) { return rUrl.Main == Event.FeatureURL.Main; }); + if (pUrl != aUrls.end()) + { + auto i = static_cast<sal_uInt32>(std::distance(aUrls.begin(), pUrl)); + DBG_ASSERT(m_pDispatchers[i] == Event.Source, "FmXGridPeer::statusChanged : the event source is a little bit suspect !"); + m_pStateCache[i] = Event.IsEnabled; + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + if (aSlots[i] != DbGridControlNavigationBarState::Undo) + pGrid->GetNavigationBar().InvalidateState(aSlots[i]); + } + DBG_ASSERT(pUrl != aUrls.end(), "FmXGridPeer::statusChanged : got a call for an unknown url !"); +} + + +sal_Bool FmXGridPeer::approveReset(const EventObject& /*rEvent*/) +{ + return true; +} + + +sal_Bool SAL_CALL FmXGridPeer::select( const Any& _rSelection ) +{ + Sequence< Any > aBookmarks; + if ( !( _rSelection >>= aBookmarks ) ) + throw IllegalArgumentException(); + + return GetAs< FmGridControl >()->selectBookmarks(aBookmarks); + + // TODO: + // speaking strictly, we would have to adjust our model, as our ColumnSelection may have changed. + // Our model is a XSelectionSupplier, too, it handles the selection of single columns. + // This is somewhat strange, as selection should be a view (not a model) aspect. + // So for a clean solution, we should handle column selection ourself, and the model shouldn't + // deal with selection at all. +} + + +Any SAL_CALL FmXGridPeer::getSelection( ) +{ + VclPtr< FmGridControl > pVclControl = GetAs< FmGridControl >(); + Sequence< Any > aSelectionBookmarks = pVclControl->getSelectionBookmarks(); + return Any(aSelectionBookmarks); +} + + +void SAL_CALL FmXGridPeer::addSelectionChangeListener( const Reference< XSelectionChangeListener >& _rxListener ) +{ + std::unique_lock g(m_aMutex); + m_aSelectionListeners.addInterface( g, _rxListener ); +} + + +void SAL_CALL FmXGridPeer::removeSelectionChangeListener( const Reference< XSelectionChangeListener >& _rxListener ) +{ + std::unique_lock g(m_aMutex); + m_aSelectionListeners.removeInterface( g, _rxListener ); +} + + +void FmXGridPeer::resetted(const EventObject& rEvent) +{ + if (m_xColumns == rEvent.Source) + { // my model was reset -> refresh the grid content + SolarMutexGuard aGuard; + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + if (!pGrid) + return; + pGrid->resetCurrentRow(); + } + // if the cursor fired a reset event we seem to be on the insert row + else if (m_xCursor == rEvent.Source) + { + SolarMutexGuard aGuard; + VclPtr< FmGridControl > pGrid = GetAs< FmGridControl >(); + if (pGrid && pGrid->IsOpen()) + pGrid->positioned(); + } +} + + +const std::vector<DbGridControlNavigationBarState>& FmXGridPeer::getSupportedGridSlots() +{ + static const std::vector<DbGridControlNavigationBarState> aSupported { + DbGridControlNavigationBarState::First, + DbGridControlNavigationBarState::Prev, + DbGridControlNavigationBarState::Next, + DbGridControlNavigationBarState::Last, + DbGridControlNavigationBarState::New, + DbGridControlNavigationBarState::Undo + }; + return aSupported; +} + + +Sequence< css::util::URL>& FmXGridPeer::getSupportedURLs() +{ + static Sequence< css::util::URL> aSupported = []() + { + static constexpr OUString sSupported[] = { + FMURL_RECORD_MOVEFIRST, + FMURL_RECORD_MOVEPREV, + FMURL_RECORD_MOVENEXT, + FMURL_RECORD_MOVELAST, + FMURL_RECORD_MOVETONEW, + FMURL_RECORD_UNDO + }; + Sequence< css::util::URL> tmp(SAL_N_ELEMENTS(sSupported)); + css::util::URL* pSupported = tmp.getArray(); + + for ( sal_Int32 i = 0; i < tmp.getLength(); ++i, ++pSupported) + pSupported->Complete = sSupported[i]; + + // let a css::util::URL-transformer normalize the URLs + Reference< css::util::XURLTransformer > xTransformer( + util::URLTransformer::create(::comphelper::getProcessComponentContext()) ); + for (css::util::URL & rURL : asNonConstRange(tmp)) + xTransformer->parseStrict(rURL); + return tmp; + }(); + + return aSupported; +} + + +void FmXGridPeer::UpdateDispatches() +{ + if (!m_pStateCache) + { // we don't have any dispatchers yet -> do the initial connect + ConnectToDispatcher(); + return; + } + + sal_uInt16 nDispatchersGot = 0; + const Sequence< css::util::URL>& aSupportedURLs = getSupportedURLs(); + const css::util::URL* pSupportedURLs = aSupportedURLs.getConstArray(); + Reference< css::frame::XDispatch > xNewDispatch; + for (sal_Int32 i=0; i<aSupportedURLs.getLength(); ++i, ++pSupportedURLs) + { + xNewDispatch = queryDispatch(*pSupportedURLs, OUString(), 0); + if (xNewDispatch != m_pDispatchers[i]) + { + if (m_pDispatchers[i].is()) + m_pDispatchers[i]->removeStatusListener(static_cast<css::frame::XStatusListener*>(this), *pSupportedURLs); + m_pDispatchers[i] = xNewDispatch; + if (m_pDispatchers[i].is()) + m_pDispatchers[i]->addStatusListener(static_cast<css::frame::XStatusListener*>(this), *pSupportedURLs); + } + if (m_pDispatchers[i].is()) + ++nDispatchersGot; + } + + if (!nDispatchersGot) + { + m_pStateCache.reset(); + m_pDispatchers.reset(); + } +} + + +void FmXGridPeer::ConnectToDispatcher() +{ + DBG_ASSERT((m_pStateCache != nullptr) == (m_pDispatchers != nullptr), "FmXGridPeer::ConnectToDispatcher : inconsistent !"); + if (m_pStateCache) + { // already connected -> just do an update + UpdateDispatches(); + return; + } + + const Sequence< css::util::URL>& aSupportedURLs = getSupportedURLs(); + + // _before_ adding the status listeners (as the add should result in a statusChanged-call) ! + m_pStateCache.reset(new bool[aSupportedURLs.getLength()]); + m_pDispatchers.reset(new Reference< css::frame::XDispatch > [aSupportedURLs.getLength()]); + + sal_uInt16 nDispatchersGot = 0; + const css::util::URL* pSupportedURLs = aSupportedURLs.getConstArray(); + for (sal_Int32 i=0; i<aSupportedURLs.getLength(); ++i, ++pSupportedURLs) + { + m_pStateCache[i] = false; + m_pDispatchers[i] = queryDispatch(*pSupportedURLs, OUString(), 0); + if (m_pDispatchers[i].is()) + { + m_pDispatchers[i]->addStatusListener(static_cast<css::frame::XStatusListener*>(this), *pSupportedURLs); + ++nDispatchersGot; + } + } + + if (!nDispatchersGot) + { + m_pStateCache.reset(); + m_pDispatchers.reset(); + } +} + + +void FmXGridPeer::DisConnectFromDispatcher() +{ + if (!m_pStateCache || !m_pDispatchers) + return; + // we're not connected + + const Sequence< css::util::URL>& aSupportedURLs = getSupportedURLs(); + const css::util::URL* pSupportedURLs = aSupportedURLs.getConstArray(); + for (sal_Int32 i=0; i<aSupportedURLs.getLength(); ++i, ++pSupportedURLs) + { + if (m_pDispatchers[i].is()) + m_pDispatchers[i]->removeStatusListener(static_cast<css::frame::XStatusListener*>(this), *pSupportedURLs); + } + + m_pStateCache.reset(); + m_pDispatchers.reset(); +} + + +IMPL_LINK(FmXGridPeer, OnQueryGridSlotState, DbGridControlNavigationBarState, nSlot, int) +{ + if (!m_pStateCache) + return -1; // unspecified + + // search the given slot with our supported sequence + const std::vector<DbGridControlNavigationBarState>& aSupported = getSupportedGridSlots(); + for (size_t i=0; i<aSupported.size(); ++i) + { + if (aSupported[i] == nSlot) + { + if (!m_pDispatchers[i].is()) + return -1; // nothing known about this slot + else + return m_pStateCache[i] ? 1 : 0; + } + } + + return -1; +} + + +IMPL_LINK(FmXGridPeer, OnExecuteGridSlot, DbGridControlNavigationBarState, nSlot, bool) +{ + if (!m_pDispatchers) + return false; // not handled + + Sequence< css::util::URL>& aUrls = getSupportedURLs(); + const css::util::URL* pUrls = aUrls.getConstArray(); + + const std::vector<DbGridControlNavigationBarState>& aSlots = getSupportedGridSlots(); + + DBG_ASSERT(aSlots.size() == o3tl::make_unsigned(aUrls.getLength()), "FmXGridPeer::OnExecuteGridSlot : inconsistent data returned by getSupportedURLs/getSupportedGridSlots!"); + + for (size_t i=0; i<aSlots.size(); ++i, ++pUrls) + { + if (aSlots[i] == nSlot) + { + if (m_pDispatchers[i].is()) + { + // commit any changes done so far, if it's not the undoRecord URL + if ( pUrls->Complete == FMURL_RECORD_UNDO || commit() ) + m_pDispatchers[i]->dispatch(*pUrls, Sequence< PropertyValue>()); + + return true; // handled + } + } + } + + return false; // not handled +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/fmcomp/gridcell.cxx b/svx/source/fmcomp/gridcell.cxx new file mode 100644 index 0000000000..e8c56fb0ac --- /dev/null +++ b/svx/source/fmcomp/gridcell.cxx @@ -0,0 +1,4603 @@ +/* -*- 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 <memory> +#include <sal/log.hxx> +#include <fmprop.hxx> +#include <svx/strings.hrc> +#include <svx/fmtools.hxx> +#include <gridcell.hxx> +#include <gridcols.hxx> +#include <sdbdatacolumn.hxx> + +#include <com/sun/star/awt/LineEndFormat.hpp> +#include <com/sun/star/awt/MouseWheelBehavior.hpp> +#include <com/sun/star/awt/VisualEffect.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/form/XBoundComponent.hpp> +#include <com/sun/star/script/XEventAttacherManager.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <com/sun/star/sdbc/DataType.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/sdbc/XRowSet.hpp> +#include <com/sun/star/sdbc/XStatement.hpp> +#include <com/sun/star/util/XNumberFormatsSupplier.hpp> +#include <com/sun/star/util/XNumberFormatter.hpp> +#include <com/sun/star/util/Time.hpp> +#include <com/sun/star/util/Date.hpp> + +#include <comphelper/numbers.hxx> +#include <comphelper/property.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/string.hxx> +#include <comphelper/types.hxx> +#include <connectivity/formattedcolumnvalue.hxx> +#include <i18nlangtag/lang.h> +#include <o3tl/safeint.hxx> +#include <svl/numformat.hxx> +#include <svl/numuno.hxx> +#include <svx/dialmgr.hxx> +#include <toolkit/helper/listenermultiplexer.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/debug.hxx> +#include <tools/fract.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <connectivity/dbtools.hxx> +#include <connectivity/dbconversion.hxx> +#include <connectivity/sqlnode.hxx> + +using namespace ::connectivity; +using namespace ::svxform; +using namespace ::comphelper; +using namespace ::svt; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdbcx; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::form; +using namespace ::dbtools::DBTypeConversion; +using namespace ::dbtools; + +using ::com::sun::star::util::XNumberFormatter; + +constexpr OUString INVALIDTEXT = u"###"_ustr; +constexpr OUString OBJECTTEXT = u"<OBJECT>"_ustr; + + +//= helper + +namespace +{ + LineEnd getModelLineEndSetting( const Reference< XPropertySet >& _rxModel ) + { + LineEnd eFormat = LINEEND_LF; + + try + { + sal_Int16 nLineEndFormat = awt::LineEndFormat::LINE_FEED; + + Reference< XPropertySetInfo > xPSI; + if ( _rxModel.is() ) + xPSI = _rxModel->getPropertySetInfo(); + + OSL_ENSURE( xPSI.is(), "getModelLineEndSetting: invalid column model!" ); + if ( xPSI.is() && xPSI->hasPropertyByName( FM_PROP_LINEENDFORMAT ) ) + { + OSL_VERIFY( _rxModel->getPropertyValue( FM_PROP_LINEENDFORMAT ) >>= nLineEndFormat ); + + switch ( nLineEndFormat ) + { + case awt::LineEndFormat::CARRIAGE_RETURN: eFormat = LINEEND_CR; break; + case awt::LineEndFormat::LINE_FEED: eFormat = LINEEND_LF; break; + case awt::LineEndFormat::CARRIAGE_RETURN_LINE_FEED: eFormat = LINEEND_CRLF; break; + default: + OSL_FAIL( "getModelLineEndSetting: what's this?" ); + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "getModelLineEndSetting" ); + } + return eFormat; + } +} + + +//= DbGridColumn + + +CellControllerRef DbGridColumn::s_xEmptyController; + + +void DbGridColumn::CreateControl(sal_Int32 _nFieldPos, const Reference< css::beans::XPropertySet >& xField, sal_Int32 nTypeId) +{ + Clear(); + + m_nTypeId = static_cast<sal_Int16>(nTypeId); + if (xField != m_xField) + { + // initial setting + m_xField = xField; + xField->getPropertyValue(FM_PROP_FORMATKEY) >>= m_nFormatKey; + m_nFieldPos = static_cast<sal_Int16>(_nFieldPos); + m_bReadOnly = ::comphelper::getBOOL(xField->getPropertyValue(FM_PROP_ISREADONLY)); + m_bAutoValue = ::comphelper::getBOOL(xField->getPropertyValue(FM_PROP_AUTOINCREMENT)); + m_nFieldType = static_cast<sal_Int16>(::comphelper::getINT32(xField->getPropertyValue(FM_PROP_FIELDTYPE))); + + switch (m_nFieldType) + { + case DataType::DATE: + case DataType::TIME: + case DataType::TIMESTAMP: + case DataType::BIT: + case DataType::BOOLEAN: + case DataType::TINYINT: + case DataType::SMALLINT: + case DataType::INTEGER: + case DataType::BIGINT: + case DataType::FLOAT: + case DataType::REAL: + case DataType::DOUBLE: + case DataType::NUMERIC: + case DataType::DECIMAL: + m_nAlign = css::awt::TextAlign::RIGHT; + m_bNumeric = true; + break; + default: + m_nAlign = css::awt::TextAlign::LEFT; + break; + } + } + + std::unique_ptr<DbCellControl> pCellControl; + if (m_rParent.IsFilterMode()) + { + pCellControl.reset(new DbFilterField(m_rParent.getContext(),*this)); + } + else + { + + switch (nTypeId) + { + case TYPE_CHECKBOX: pCellControl.reset(new DbCheckBox(*this)); break; + case TYPE_COMBOBOX: pCellControl.reset(new DbComboBox(*this)); break; + case TYPE_CURRENCYFIELD: pCellControl.reset(new DbCurrencyField(*this)); break; + case TYPE_DATEFIELD: pCellControl.reset(new DbDateField(*this)); break; + case TYPE_LISTBOX: pCellControl.reset(new DbListBox(*this)); break; + case TYPE_NUMERICFIELD: pCellControl.reset(new DbNumericField(*this)); break; + case TYPE_PATTERNFIELD: pCellControl.reset(new DbPatternField( *this, m_rParent.getContext() )); break; + case TYPE_TEXTFIELD: pCellControl.reset(new DbTextField(*this)); break; + case TYPE_TIMEFIELD: pCellControl.reset(new DbTimeField(*this)); break; + case TYPE_FORMATTEDFIELD: pCellControl.reset(new DbFormattedField(*this)); break; + default: + OSL_FAIL("DbGridColumn::CreateControl: Unknown Column"); + return; + } + + } + Reference< XRowSet > xCur; + if (m_rParent.getDataSource()) + xCur.set(Reference< XInterface >(*m_rParent.getDataSource()), UNO_QUERY); + // TODO : the cursor wrapper should use an XRowSet interface, too + + pCellControl->Init( m_rParent.GetDataWindow(), xCur ); + + // now create the control wrapper + auto pTempCellControl = pCellControl.get(); + if (m_rParent.IsFilterMode()) + m_pCell = new FmXFilterCell(this, std::unique_ptr<DbFilterField>(static_cast<DbFilterField*>(pCellControl.release()))); + else + { + switch (nTypeId) + { + case TYPE_CHECKBOX: m_pCell = new FmXCheckBoxCell( this, std::move(pCellControl) ); break; + case TYPE_LISTBOX: m_pCell = new FmXListBoxCell( this, std::move(pCellControl) ); break; + case TYPE_COMBOBOX: m_pCell = new FmXComboBoxCell( this, std::move(pCellControl) ); break; + default: + m_pCell = new FmXEditCell( this, std::move(pCellControl) ); + } + } + m_pCell->init(); + + impl_toggleScriptManager_nothrow( true ); + + // only if we use have a bound field, we use a controller for displaying the + // window in the grid + if (m_xField.is()) + m_xController = pTempCellControl->CreateController(); +} + + +void DbGridColumn::impl_toggleScriptManager_nothrow( bool _bAttach ) +{ + try + { + Reference< container::XChild > xChild( m_xModel, UNO_QUERY_THROW ); + Reference< script::XEventAttacherManager > xManager( xChild->getParent(), UNO_QUERY_THROW ); + Reference< container::XIndexAccess > xContainer( xChild->getParent(), UNO_QUERY_THROW ); + + sal_Int32 nIndexInParent( getElementPos( xContainer, m_xModel ) ); + + Reference< XInterface > xCellInterface( *m_pCell, UNO_QUERY ); + if ( _bAttach ) + xManager->attach( nIndexInParent, xCellInterface, Any( xCellInterface ) ); + else + xManager->detach( nIndexInParent, xCellInterface ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + +void DbGridColumn::UpdateFromField(const DbGridRow* pRow, const Reference< XNumberFormatter >& xFormatter) +{ + if (FmXFilterCell* pCell = dynamic_cast<FmXFilterCell*>(m_pCell.get())) + pCell->Update(); + else if (pRow && pRow->IsValid() && m_nFieldPos >= 0 && m_pCell.is() && pRow->HasField(m_nFieldPos)) + { + dynamic_cast<FmXDataCell&>(*m_pCell).UpdateFromField( pRow->GetField( m_nFieldPos ).getColumn(), xFormatter ); + } +} + +bool DbGridColumn::Commit() +{ + bool bResult = true; + if (!m_bInSave && m_pCell.is()) + { + m_bInSave = true; + bResult = m_pCell->Commit(); + + // store the data into the model + FmXDataCell* pDataCell = dynamic_cast<FmXDataCell*>( m_pCell.get() ); + if (bResult && pDataCell) + { + Reference< css::form::XBoundComponent > xComp(m_xModel, UNO_QUERY); + if (xComp.is()) + bResult = xComp->commit(); + } + m_bInSave = false; + } + return bResult; +} + +DbGridColumn::~DbGridColumn() +{ + Clear(); +} + +void DbGridColumn::setModel(const css::uno::Reference< css::beans::XPropertySet >& _xModel) +{ + if ( m_pCell.is() ) + impl_toggleScriptManager_nothrow( false ); + + m_xModel = _xModel; + + if ( m_pCell.is() ) + impl_toggleScriptManager_nothrow( true ); +} + + +void DbGridColumn::Clear() +{ + if ( m_pCell.is() ) + { + impl_toggleScriptManager_nothrow( false ); + + m_pCell->dispose(); + m_pCell.clear(); + } + + m_xController = nullptr; + m_xField = nullptr; + + m_nFormatKey = 0; + m_nFieldPos = -1; + m_bReadOnly = true; + m_bAutoValue = false; + m_nFieldType = DataType::OTHER; +} + + +sal_Int16 DbGridColumn::SetAlignment(sal_Int16 _nAlign) +{ + if (_nAlign == -1) + { // 'Standard' + if (m_xField.is()) + { + sal_Int32 nType = 0; + m_xField->getPropertyValue(FM_PROP_FIELDTYPE) >>= nType; + + switch (nType) + { + case DataType::NUMERIC: + case DataType::DECIMAL: + case DataType::DOUBLE: + case DataType::REAL: + case DataType::BIGINT: + case DataType::INTEGER: + case DataType::SMALLINT: + case DataType::TINYINT: + case DataType::DATE: + case DataType::TIME: + case DataType::TIMESTAMP: + _nAlign = css::awt::TextAlign::RIGHT; + break; + case DataType::BIT: + case DataType::BOOLEAN: + _nAlign = css::awt::TextAlign::CENTER; + break; + default: + _nAlign = css::awt::TextAlign::LEFT; + break; + } + } + else + _nAlign = css::awt::TextAlign::LEFT; + } + + m_nAlign = _nAlign; + if (m_pCell.is() && m_pCell->isAlignedController()) + m_pCell->AlignControl(m_nAlign); + + return m_nAlign; +} + + +sal_Int16 DbGridColumn::SetAlignmentFromModel(sal_Int16 nStandardAlign) +{ + Any aAlign( m_xModel->getPropertyValue(FM_PROP_ALIGN)); + if (aAlign.hasValue()) + { + sal_Int16 nTest = sal_Int16(); + if (aAlign >>= nTest) + nStandardAlign = nTest; + } + return SetAlignment(nStandardAlign); +} + + +void DbGridColumn::setLock(bool _bLock) +{ + if (m_bLocked == _bLock) + return; + m_bLocked = _bLock; + + // is the column we represent active ? + if (m_bHidden) + return; // no, it isn't (or at least it shouldn't be ...) + + if (m_rParent.GetCurColumnId() == m_nId) + { + m_rParent.DeactivateCell(); + m_rParent.ActivateCell(m_rParent.GetCurRow(), m_rParent.GetCurColumnId()); + } +} + + +OUString DbGridColumn::GetCellText(const DbGridRow* pRow, const Reference< XNumberFormatter >& xFormatter) const +{ + OUString aText; + if (m_pCell.is() && dynamic_cast<const FmXFilterCell*>( m_pCell.get() ) != nullptr) + return aText; + + if (!pRow || !pRow->IsValid()) + aText = INVALIDTEXT; + else if (pRow->HasField(m_nFieldPos)) + { + aText = GetCellText( pRow->GetField( m_nFieldPos ).getColumn(), xFormatter ); + } + return aText; +} + +OUString DbGridColumn::GetCellText(const Reference< css::sdb::XColumn >& xField, const Reference< XNumberFormatter >& xFormatter) const +{ + OUString aText; + if (xField.is()) + { + FmXTextCell* pTextCell = dynamic_cast<FmXTextCell*>( m_pCell.get() ); + if (pTextCell) + aText = pTextCell->GetText(xField, xFormatter); + else if (m_bObject) + aText = OBJECTTEXT; + } + return aText; +} + +Reference< css::sdb::XColumn > DbGridColumn::GetCurrentFieldValue() const +{ + Reference< css::sdb::XColumn > xField; + const DbGridRowRef xRow = m_rParent.GetCurrentRow(); + if (xRow.is() && xRow->HasField(m_nFieldPos)) + { + xField = xRow->GetField(m_nFieldPos).getColumn(); + } + return xField; +} + + +void DbGridColumn::Paint(OutputDevice& rDev, + const tools::Rectangle& rRect, + const DbGridRow* pRow, + const Reference< XNumberFormatter >& xFormatter) +{ + bool bEnabled = ( rDev.GetOutDevType() != OUTDEV_WINDOW ) + || ( rDev.GetOwnerWindow()->IsEnabled() ); + + FmXDataCell* pDataCell = dynamic_cast<FmXDataCell*>( m_pCell.get() ); + if (pDataCell) + { + if (!pRow || !pRow->IsValid()) + { + DrawTextFlags nStyle = DrawTextFlags::Clip | DrawTextFlags::Center; + if ( !bEnabled ) + nStyle |= DrawTextFlags::Disable; + + rDev.DrawText(rRect, INVALIDTEXT, nStyle); + } + else if (m_bAutoValue && pRow->IsNew()) + { + DrawTextFlags nStyle = DrawTextFlags::Clip | DrawTextFlags::VCenter; + if ( !bEnabled ) + nStyle |= DrawTextFlags::Disable; + + switch (GetAlignment()) + { + case css::awt::TextAlign::RIGHT: + nStyle |= DrawTextFlags::Right; + break; + case css::awt::TextAlign::CENTER: + nStyle |= DrawTextFlags::Center; + break; + default: + nStyle |= DrawTextFlags::Left; + } + + rDev.DrawText(rRect, SvxResId(RID_STR_AUTOFIELD), nStyle); + } + else if (pRow->HasField(m_nFieldPos)) + { + pDataCell->PaintFieldToCell(rDev, rRect, pRow->GetField( m_nFieldPos ).getColumn(), xFormatter); + } + } + else if (!m_pCell.is()) + { + if (!pRow || !pRow->IsValid()) + { + DrawTextFlags nStyle = DrawTextFlags::Clip | DrawTextFlags::Center; + if ( !bEnabled ) + nStyle |= DrawTextFlags::Disable; + + rDev.DrawText(rRect, INVALIDTEXT, nStyle); + } + else if (pRow->HasField(m_nFieldPos) && m_bObject) + { + DrawTextFlags nStyle = DrawTextFlags::Clip | DrawTextFlags::Center; + if ( !bEnabled ) + nStyle |= DrawTextFlags::Disable; + rDev.DrawText(rRect, OBJECTTEXT, nStyle); + } + } + else if ( auto pFilterCell = dynamic_cast<FmXFilterCell*>( m_pCell.get() ) ) + pFilterCell->PaintCell( rDev, rRect ); +} + + +void DbGridColumn::ImplInitWindow( vcl::Window const & rParent, const InitWindowFacet _eInitWhat ) +{ + if ( m_pCell.is() ) + m_pCell->ImplInitWindow( rParent, _eInitWhat ); +} + + +//= cell controls + + +DbCellControl::DbCellControl( DbGridColumn& _rColumn ) + :OPropertyChangeListener(m_aMutex) + ,m_bTransparent( false ) + ,m_bAlignedController( true ) + ,m_bAccessingValueProperty( false ) + ,m_rColumn( _rColumn ) + ,m_pPainter( nullptr ) + ,m_pWindow( nullptr ) +{ + Reference< XPropertySet > xColModelProps = _rColumn.getModel(); + if ( !xColModelProps.is() ) + return; + + // if our model's format key changes we want to propagate the new value to our windows + m_pModelChangeBroadcaster = new ::comphelper::OPropertyChangeMultiplexer(this, _rColumn.getModel()); + + // be listener for some common properties + implDoPropertyListening( FM_PROP_READONLY, false ); + implDoPropertyListening( FM_PROP_ENABLED, false ); + + // add as listener for all known "value" properties + implDoPropertyListening( FM_PROP_VALUE, false ); + implDoPropertyListening( FM_PROP_STATE, false ); + implDoPropertyListening( FM_PROP_TEXT, false ); + implDoPropertyListening( FM_PROP_EFFECTIVE_VALUE, false ); + implDoPropertyListening( FM_PROP_SELECT_SEQ, false ); + implDoPropertyListening( FM_PROP_DATE, false ); + implDoPropertyListening( FM_PROP_TIME, false ); + + // be listener at the bound field as well + try + { + Reference< XPropertySetInfo > xPSI( xColModelProps->getPropertySetInfo(), UNO_SET_THROW ); + if ( xPSI->hasPropertyByName( FM_PROP_BOUNDFIELD ) ) + { + Reference< XPropertySet > xField; + xColModelProps->getPropertyValue( FM_PROP_BOUNDFIELD ) >>= xField; + if ( xField.is() ) + { + m_pFieldChangeBroadcaster = new ::comphelper::OPropertyChangeMultiplexer(this, xField); + m_pFieldChangeBroadcaster->addProperty( FM_PROP_ISREADONLY ); + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "DbCellControl::doPropertyListening" ); + } +} + + +void DbCellControl::implDoPropertyListening(const OUString& _rPropertyName, bool _bWarnIfNotExistent) +{ + try + { + Reference< XPropertySet > xColModelProps = m_rColumn.getModel(); + Reference< XPropertySetInfo > xPSI; + if ( xColModelProps.is() ) + xPSI = xColModelProps->getPropertySetInfo(); + + DBG_ASSERT( !_bWarnIfNotExistent || ( xPSI.is() && xPSI->hasPropertyByName( _rPropertyName ) ), + "DbCellControl::doPropertyListening: no property set info or non-existent property!" ); + + if ( xPSI.is() && xPSI->hasPropertyByName( _rPropertyName ) ) + m_pModelChangeBroadcaster->addProperty( _rPropertyName ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "DbCellControl::doPropertyListening" ); + } +} + + +void DbCellControl::doPropertyListening(const OUString& _rPropertyName) +{ + implDoPropertyListening( _rPropertyName, true ); +} + +static void lcl_clearBroadCaster(rtl::Reference<::comphelper::OPropertyChangeMultiplexer>& _pBroadcaster) +{ + if ( _pBroadcaster.is() ) + { + _pBroadcaster->dispose(); + _pBroadcaster.clear(); + // no delete, this is done implicitly + } +} + +DbCellControl::~DbCellControl() +{ + lcl_clearBroadCaster(m_pModelChangeBroadcaster); + lcl_clearBroadCaster(m_pFieldChangeBroadcaster); + + m_pWindow.disposeAndClear(); + m_pPainter.disposeAndClear(); +} + +void DbCellControl::implValuePropertyChanged( ) +{ + OSL_ENSURE( !isValuePropertyLocked(), + "DbCellControl::implValuePropertyChanged: not to be called with the value property locked!" ); + + if ( m_pWindow ) + { + if ( m_rColumn.getModel().is() ) + updateFromModel( m_rColumn.getModel() ); + } +} + + +void DbCellControl::implAdjustGenericFieldSetting( const Reference< XPropertySet >& /*_rxModel*/ ) +{ + // nothing to do here +} + + +void DbCellControl::_propertyChanged(const PropertyChangeEvent& _rEvent) +{ + SolarMutexGuard aGuard; + + Reference< XPropertySet > xSourceProps( _rEvent.Source, UNO_QUERY ); + + if ( _rEvent.PropertyName == FM_PROP_VALUE + || _rEvent.PropertyName == FM_PROP_STATE + || _rEvent.PropertyName == FM_PROP_TEXT + || _rEvent.PropertyName == FM_PROP_EFFECTIVE_VALUE + || _rEvent.PropertyName == FM_PROP_SELECT_SEQ + || _rEvent.PropertyName == FM_PROP_DATE + || _rEvent.PropertyName == FM_PROP_TIME + ) + { // it was one of the known "value" properties + if ( !isValuePropertyLocked() ) + { + implValuePropertyChanged( ); + } + } + else if ( _rEvent.PropertyName == FM_PROP_READONLY ) + { + implAdjustReadOnly( xSourceProps, true); + } + else if ( _rEvent.PropertyName == FM_PROP_ISREADONLY ) + { + bool bReadOnly = true; + _rEvent.NewValue >>= bReadOnly; + m_rColumn.SetReadOnly(bReadOnly); + implAdjustReadOnly( xSourceProps, false); + } + else if ( _rEvent.PropertyName == FM_PROP_ENABLED ) + { + implAdjustEnabled( xSourceProps ); + } + else + implAdjustGenericFieldSetting( xSourceProps ); +} + +bool DbCellControl::Commit() +{ + // lock the listening for value property changes + lockValueProperty(); + // commit the content of the control into the model's value property + bool bReturn = false; + try + { + bReturn = commitControl(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + // unlock the listening for value property changes + unlockValueProperty(); + // outta here + return bReturn; +} + +void DbCellControl::ImplInitWindow( vcl::Window const & rParent, const InitWindowFacet _eInitWhat ) +{ + svt::ControlBase* pWindows[] = { m_pPainter, m_pWindow }; + + if (_eInitWhat & InitWindowFacet::WritingMode) + { + for (svt::ControlBase* pWindow : pWindows) + { + if (pWindow) + pWindow->EnableRTL(rParent.IsRTLEnabled()); + } + } + + if (_eInitWhat & InitWindowFacet::Font) + { + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + const Fraction& rZoom = rParent.GetZoom(); + + for (svt::ControlBase* pWindow : pWindows) + { + if (!pWindow) + continue; + + vcl::Font aFont = rStyleSettings.GetFieldFont(); + aFont.SetTransparent(isTransparent()); + + if (rParent.IsControlFont()) + aFont.Merge(rParent.GetControlFont()); + + if (rZoom.GetNumerator() != rZoom.GetDenominator()) + { + Size aSize = aFont.GetFontSize(); + aSize.setWidth(std::round(double(aSize.Width() * rZoom))); + aSize.setHeight(std::round(double(aSize.Height() * rZoom))); + aFont.SetFontSize(aSize); + } + + pWindow->SetPointFont(aFont); + } + } + + if ((_eInitWhat & InitWindowFacet::Font) || (_eInitWhat & InitWindowFacet::Foreground)) + { + Color aTextColor(rParent.IsControlForeground() ? rParent.GetControlForeground() : rParent.GetTextColor()); + + bool bTextLineColor = rParent.IsTextLineColor(); + Color aTextLineColor(rParent.GetTextLineColor()); + + for (svt::ControlBase* pWindow : pWindows) + { + if (pWindow) + { + pWindow->SetTextColor(aTextColor); + if (rParent.IsControlForeground()) + pWindow->SetControlForeground(aTextColor); + + if (bTextLineColor) + pWindow->SetTextLineColor(); + else + pWindow->SetTextLineColor(aTextLineColor); + } + } + } + + if (!(_eInitWhat & InitWindowFacet::Background)) + return; + + if (rParent.IsControlBackground()) + { + Color aColor(rParent.GetControlBackground()); + for (svt::ControlBase* pWindow : pWindows) + { + if (pWindow) + { + if (isTransparent()) + pWindow->SetBackground(); + else + { + pWindow->SetBackground(aColor); + pWindow->SetControlBackground(aColor); + } + pWindow->GetOutDev()->SetFillColor(aColor); + } + } + } + else + { + if (m_pPainter) + { + if (isTransparent()) + m_pPainter->SetBackground(); + else + m_pPainter->SetBackground(rParent.GetBackground()); + m_pPainter->GetOutDev()->SetFillColor(rParent.GetOutDev()->GetFillColor()); + } + + if (m_pWindow) + { + if (isTransparent()) + m_pWindow->SetBackground(rParent.GetBackground()); + else + m_pWindow->GetOutDev()->SetFillColor(rParent.GetOutDev()->GetFillColor()); + } + } +} + +void DbCellControl::implAdjustReadOnly( const Reference< XPropertySet >& _rxModel,bool i_bReadOnly ) +{ + DBG_ASSERT( m_pWindow, "DbCellControl::implAdjustReadOnly: not to be called without window!" ); + DBG_ASSERT( _rxModel.is(), "DbCellControl::implAdjustReadOnly: invalid model!" ); + if ( !(m_pWindow && _rxModel.is()) ) + return; + + bool bReadOnly = m_rColumn.IsReadOnly(); + if ( !bReadOnly ) + { + _rxModel->getPropertyValue( i_bReadOnly ? FM_PROP_READONLY : FM_PROP_ISREADONLY) >>= bReadOnly; + } + m_pWindow->SetEditableReadOnly(bReadOnly); +} + +void DbCellControl::implAdjustEnabled( const Reference< XPropertySet >& _rxModel ) +{ + DBG_ASSERT( m_pWindow, "DbCellControl::implAdjustEnabled: not to be called without window!" ); + DBG_ASSERT( _rxModel.is(), "DbCellControl::implAdjustEnabled: invalid model!" ); + if ( m_pWindow && _rxModel.is() ) + { + bool bEnable = true; + _rxModel->getPropertyValue( FM_PROP_ENABLED ) >>= bEnable; + m_pWindow->Enable( bEnable ); + } +} + +void DbCellControl::Init(BrowserDataWin& rParent, const Reference< XRowSet >& _rxCursor) +{ + ImplInitWindow( rParent, InitWindowFacet::All ); + + if ( m_pWindow ) + { + // align the control + if ( isAlignedController() ) + AlignControl( m_rColumn.GetAlignment() ); + + try + { + // some other common properties + Reference< XPropertySet > xModel( m_rColumn.getModel(), UNO_SET_THROW ); + Reference< XPropertySetInfo > xModelPSI( xModel->getPropertySetInfo(), UNO_SET_THROW ); + + if ( xModelPSI->hasPropertyByName( FM_PROP_READONLY ) ) + { + implAdjustReadOnly( xModel,true ); + } + + if ( xModelPSI->hasPropertyByName( FM_PROP_ENABLED ) ) + { + implAdjustEnabled( xModel ); + } + + if ( xModelPSI->hasPropertyByName( FM_PROP_MOUSE_WHEEL_BEHAVIOR ) ) + { + sal_Int16 nWheelBehavior = css::awt::MouseWheelBehavior::SCROLL_FOCUS_ONLY; + OSL_VERIFY( xModel->getPropertyValue( FM_PROP_MOUSE_WHEEL_BEHAVIOR ) >>= nWheelBehavior ); + MouseWheelBehaviour nVclSetting = MouseWheelBehaviour::FocusOnly; + switch ( nWheelBehavior ) + { + case css::awt::MouseWheelBehavior::SCROLL_DISABLED: nVclSetting = MouseWheelBehaviour::Disable; break; + case css::awt::MouseWheelBehavior::SCROLL_FOCUS_ONLY: nVclSetting = MouseWheelBehaviour::FocusOnly; break; + case css::awt::MouseWheelBehavior::SCROLL_ALWAYS: nVclSetting = MouseWheelBehaviour::ALWAYS; break; + default: + OSL_FAIL( "DbCellControl::Init: invalid MouseWheelBehavior!" ); + break; + } + + AllSettings aSettings = m_pWindow->GetSettings(); + MouseSettings aMouseSettings = aSettings.GetMouseSettings(); + aMouseSettings.SetWheelBehavior( nVclSetting ); + aSettings.SetMouseSettings( aMouseSettings ); + m_pWindow->SetSettings( aSettings, true ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + m_xCursor = _rxCursor; + if ( m_rColumn.getModel().is() ) + updateFromModel( m_rColumn.getModel() ); +} + + +void DbCellControl::SetTextLineColor() +{ + if (m_pWindow) + m_pWindow->SetTextLineColor(); + if (m_pPainter) + m_pPainter->SetTextLineColor(); +} + + +void DbCellControl::SetTextLineColor(const Color& _rColor) +{ + if (m_pWindow) + m_pWindow->SetTextLineColor(_rColor); + if (m_pPainter) + m_pPainter->SetTextLineColor(_rColor); +} + +namespace +{ + void lcl_implAlign( vcl::Window* _pWindow, WinBits _nAlignmentBit ) + { + if (EditControlBase* pControl = dynamic_cast<EditControlBase*>(_pWindow)) + { + switch (_nAlignmentBit) + { + case WB_LEFT: + pControl->get_widget().set_alignment(TxtAlign::Left); + break; + case WB_CENTER: + pControl->get_widget().set_alignment(TxtAlign::Center); + break; + case WB_RIGHT: + pControl->get_widget().set_alignment(TxtAlign::Right); + break; + } + return; + } + + WinBits nStyle = _pWindow->GetStyle(); + nStyle &= ~(WB_LEFT | WB_RIGHT | WB_CENTER); + _pWindow->SetStyle( nStyle | _nAlignmentBit ); + } +} + +void DbCellControl::AlignControl(sal_Int16 nAlignment) +{ + WinBits nAlignmentBit = 0; + switch (nAlignment) + { + case css::awt::TextAlign::RIGHT: + nAlignmentBit = WB_RIGHT; + break; + case css::awt::TextAlign::CENTER: + nAlignmentBit = WB_CENTER; + break; + default: + nAlignmentBit = WB_LEFT; + break; + } + lcl_implAlign( m_pWindow, nAlignmentBit ); + if ( m_pPainter ) + lcl_implAlign( m_pPainter, nAlignmentBit ); +} + +void DbCellControl::PaintCell(OutputDevice& rDev, const tools::Rectangle& rRect) +{ + m_pPainter->SetSizePixel(rRect.GetSize()); + m_pPainter->Draw(&rDev, rRect.TopLeft(), SystemTextColorFlags::NONE); +} + +void DbCellControl::PaintFieldToCell( OutputDevice& _rDev, const tools::Rectangle& _rRect, const Reference< XColumn >& _rxField, const Reference< XNumberFormatter >& _rxFormatter ) +{ + m_pPainter->SetText( GetFormatText( _rxField, _rxFormatter ) ); + PaintCell( _rDev, _rRect ); +} + +double DbCellControl::GetValue(const Reference< css::sdb::XColumn >& _rxField, const Reference< XNumberFormatter >& xFormatter) const +{ + double fValue = 0; + if (m_rColumn.IsNumeric()) + { + try + { + fValue = _rxField->getDouble(); + } + catch(const Exception&) { } + } + else + { + bool bSuccess = false; + try + { + fValue = _rxField->getDouble(); + bSuccess = true; + } + catch(const Exception&) { } + if (!bSuccess) + { + try + { + fValue = xFormatter->convertStringToNumber(m_rColumn.GetKey(), _rxField->getString()); + } + catch(const Exception&) { } + } + } + return fValue; +} + +void DbCellControl::invalidatedController() +{ + m_rColumn.GetParent().refreshController(m_rColumn.GetId(), DbGridControl::GrantControlAccess()); +} + +// CellModels + +DbLimitedLengthField::DbLimitedLengthField( DbGridColumn& _rColumn ) + :DbCellControl( _rColumn ) +{ + doPropertyListening( FM_PROP_MAXTEXTLEN ); +} + + +void DbLimitedLengthField::implAdjustGenericFieldSetting( const Reference< XPropertySet >& _rxModel ) +{ + DBG_ASSERT( m_pWindow, "DbLimitedLengthField::implAdjustGenericFieldSetting: not to be called without window!" ); + DBG_ASSERT( _rxModel.is(), "DbLimitedLengthField::implAdjustGenericFieldSetting: invalid model!" ); + if ( m_pWindow && _rxModel.is() ) + { + sal_Int16 nMaxLen = 0; + _rxModel->getPropertyValue( FM_PROP_MAXTEXTLEN ) >>= nMaxLen; + implSetMaxTextLen( nMaxLen ); + } +} + +void DbLimitedLengthField::implSetEffectiveMaxTextLen(sal_Int32 nMaxLen) +{ + dynamic_cast<EditControlBase&>(*m_pWindow).get_widget().set_max_length(nMaxLen); + if (m_pPainter) + dynamic_cast<EditControlBase&>(*m_pPainter).get_widget().set_max_length(nMaxLen); +} + +DbTextField::DbTextField(DbGridColumn& _rColumn) + :DbLimitedLengthField(_rColumn) + ,m_bIsMultiLineEdit(false) +{ +} + +DbTextField::~DbTextField( ) +{ + m_pPainterImplementation.reset(); + m_pEdit.reset(); +} + +void DbTextField::Init(BrowserDataWin& rParent, const Reference< XRowSet >& xCursor) +{ + sal_Int16 nAlignment = m_rColumn.SetAlignmentFromModel(-1); + + Reference< XPropertySet > xModel( m_rColumn.getModel() ); + + bool bLeftAlign = true; + + // is this a multi-line field? + bool bIsMultiLine = false; + try + { + if ( xModel.is() ) + { + OSL_VERIFY( xModel->getPropertyValue( FM_PROP_MULTILINE ) >>= bIsMultiLine ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION("svx", + "caught an exception while determining the multi-line capabilities!"); + } + + m_bIsMultiLineEdit = bIsMultiLine; + if ( bIsMultiLine ) + { + auto xEditControl = VclPtr<MultiLineTextCell>::Create(&rParent); + auto xEditPainter = VclPtr<MultiLineTextCell>::Create(&rParent); + + switch (nAlignment) + { + case awt::TextAlign::RIGHT: + xEditControl->get_widget().set_alignment(TxtAlign::Right); + xEditPainter->get_widget().set_alignment(TxtAlign::Right); + bLeftAlign = false; + break; + case awt::TextAlign::CENTER: + xEditControl->get_widget().set_alignment(TxtAlign::Center); + xEditPainter->get_widget().set_alignment(TxtAlign::Center); + bLeftAlign = false; + break; + } + + m_pWindow = xEditControl; + m_pEdit.reset(new MultiLineEditImplementation(*xEditControl)); + + m_pPainter = xEditPainter; + m_pPainterImplementation.reset(new MultiLineEditImplementation(*xEditPainter)); + } + else + { + auto xEditControl = VclPtr<EditControl>::Create(&rParent); + auto xEditPainter = VclPtr<EditControl>::Create(&rParent); + + switch (nAlignment) + { + case awt::TextAlign::RIGHT: + xEditControl->get_widget().set_alignment(TxtAlign::Right); + xEditPainter->get_widget().set_alignment(TxtAlign::Right); + bLeftAlign = false; + break; + case awt::TextAlign::CENTER: + xEditControl->get_widget().set_alignment(TxtAlign::Center); + xEditPainter->get_widget().set_alignment(TxtAlign::Center); + bLeftAlign = false; + break; + } + + m_pWindow = xEditControl; + m_pEdit.reset(new EntryImplementation(*xEditControl)); + + m_pPainter = xEditPainter; + m_pPainterImplementation.reset(new EntryImplementation(*xEditPainter)); + } + + if (bLeftAlign) + { + // this is so that when getting the focus, the selection is oriented left-to-right + AllSettings aSettings = m_pWindow->GetSettings(); + StyleSettings aStyleSettings = aSettings.GetStyleSettings(); + aStyleSettings.SetSelectionOptions( + aStyleSettings.GetSelectionOptions() | SelectionOptions::ShowFirst); + aSettings.SetStyleSettings(aStyleSettings); + m_pWindow->SetSettings(aSettings); + } + + implAdjustGenericFieldSetting( xModel ); + + DbLimitedLengthField::Init( rParent, xCursor ); +} + +CellControllerRef DbTextField::CreateController() const +{ + return new EditCellController( m_pEdit.get() ); +} + +void DbTextField::PaintFieldToCell( OutputDevice& _rDev, const tools::Rectangle& _rRect, const Reference< XColumn >& _rxField, const Reference< XNumberFormatter >& _rxFormatter ) +{ + if ( m_pPainterImplementation ) + m_pPainterImplementation->SetText( GetFormatText( _rxField, _rxFormatter ) ); + + DbLimitedLengthField::PaintFieldToCell( _rDev, _rRect, _rxField, _rxFormatter ); +} + +OUString DbTextField::GetFormatText(const Reference< XColumn >& _rxField, const Reference< XNumberFormatter >& xFormatter, const Color** /*ppColor*/) +{ + if (!_rxField.is()) + return OUString(); + + const css::uno::Reference<css::beans::XPropertySet> xPS(_rxField, UNO_QUERY); + FormattedColumnValue fmter( xFormatter, xPS ); + + try + { + return fmter.getFormattedValue(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return OUString(); + +} + +void DbTextField::UpdateFromField(const Reference< css::sdb::XColumn >& _rxField, const Reference< XNumberFormatter >& xFormatter) +{ + m_pEdit->SetText( GetFormatText( _rxField, xFormatter ) ); + m_pEdit->SetSelection( Selection( SELECTION_MAX, SELECTION_MIN ) ); +} + +void DbTextField::updateFromModel( Reference< XPropertySet > _rxModel ) +{ + OSL_ENSURE( _rxModel.is() && m_pWindow, "DbTextField::updateFromModel: invalid call!" ); + + OUString sText; + _rxModel->getPropertyValue( FM_PROP_TEXT ) >>= sText; + + sal_Int32 nMaxTextLen = m_pEdit->GetMaxTextLen(); + if (nMaxTextLen > 0 && sText.getLength() > nMaxTextLen) + { + sal_Int32 nDiff = sText.getLength() - nMaxTextLen; + sText = sText.replaceAt(sText.getLength() - nDiff,nDiff, u""); + } + + m_pEdit->SetText( sText ); + m_pEdit->SetSelection( Selection( SELECTION_MAX, SELECTION_MIN ) ); +} + +bool DbTextField::commitControl() +{ + OUString aText( m_pEdit->GetText( getModelLineEndSetting( m_rColumn.getModel() ) ) ); + // we have to check if the length before we can decide if the value was modified + sal_Int32 nMaxTextLen = m_pEdit->GetMaxTextLen(); + if (nMaxTextLen > 0) + { + OUString sOldValue; + m_rColumn.getModel()->getPropertyValue( FM_PROP_TEXT ) >>= sOldValue; + // if the new value didn't change we must set the old long value again + if ( sOldValue.getLength() > nMaxTextLen && sOldValue.compareTo(aText,nMaxTextLen) == 0 ) + aText = sOldValue; + } + m_rColumn.getModel()->setPropertyValue( FM_PROP_TEXT, Any( aText ) ); + return true; +} + +void DbTextField::implSetEffectiveMaxTextLen( sal_Int32 _nMaxLen ) +{ + if ( m_pEdit ) + m_pEdit->SetMaxTextLen( _nMaxLen ); + if ( m_pPainterImplementation ) + m_pPainterImplementation->SetMaxTextLen( _nMaxLen ); +} + +DbFormattedField::DbFormattedField(DbGridColumn& _rColumn) + :DbLimitedLengthField(_rColumn) +{ + // if our model's format key changes we want to propagate the new value to our windows + doPropertyListening( FM_PROP_FORMATKEY ); +} + +DbFormattedField::~DbFormattedField() +{ +} + +void DbFormattedField::Init( BrowserDataWin& rParent, const Reference< XRowSet >& xCursor) +{ + sal_Int16 nAlignment = m_rColumn.SetAlignmentFromModel(-1); + + Reference< css::beans::XPropertySet > xUnoModel = m_rColumn.getModel(); + + auto xEditControl = VclPtr<FormattedControl>::Create(&rParent, false); + auto xEditPainter = VclPtr<FormattedControl>::Create(&rParent, false); + + weld::EntryFormatter& rControlFormatter = xEditControl->get_formatter(); + weld::EntryFormatter& rPainterFormatter = xEditPainter->get_formatter(); + + m_pWindow = xEditControl.get(); + m_pPainter = xEditPainter.get(); + + switch (nAlignment) + { + case awt::TextAlign::RIGHT: + xEditControl->get_widget().set_alignment(TxtAlign::Right); + xEditPainter->get_widget().set_alignment(TxtAlign::Right); + break; + case awt::TextAlign::CENTER: + xEditControl->get_widget().set_alignment(TxtAlign::Center); + xEditPainter->get_widget().set_alignment(TxtAlign::Center); + break; + default: + { + // Everything just so that the selection goes from right to left when getting focus + SelectionOptions eOptions = rControlFormatter.GetEntrySelectionOptions(); + rControlFormatter.SetEntrySelectionOptions(eOptions | SelectionOptions::ShowFirst); + break; + } + } + + implAdjustGenericFieldSetting( xUnoModel ); + + rControlFormatter.SetStrictFormat(false); + rPainterFormatter.SetStrictFormat(false); + // if one allows any formatting, one cannot make an entry check anyway + // (the FormattedField does not support that anyway, only derived classes) + + // get the formatter from the uno model + // (I could theoretically also go via the css::util::NumberFormatter, which the cursor would + // surely give me. The problem is that I can not really rely on the fact that the two + // formatters are the same. Clean is the whole thing if I go via the UNO model.) + sal_Int32 nFormatKey = -1; + + // let's see if the model has one ... + DBG_ASSERT(::comphelper::hasProperty(FM_PROP_FORMATSSUPPLIER, xUnoModel), "DbFormattedField::Init : invalid UNO model !"); + Any aSupplier( xUnoModel->getPropertyValue(FM_PROP_FORMATSSUPPLIER)); + if (aSupplier.hasValue()) + { + m_xSupplier.set(aSupplier, css::uno::UNO_QUERY); + if (m_xSupplier.is()) + { + // if we take the supplier from the model, then also the key + Any aFmtKey( xUnoModel->getPropertyValue(FM_PROP_FORMATKEY)); + if (aFmtKey.hasValue()) + { + DBG_ASSERT(aFmtKey.getValueType().getTypeClass() == TypeClass_LONG, "DbFormattedField::Init : invalid format key property (no sal_Int32) !"); + nFormatKey = ::comphelper::getINT32(aFmtKey); + } + else + { + SAL_INFO("svx.fmcomp", "DbFormattedField::Init : my uno-model has no format-key, but a formats supplier !"); + // the OFormattedModel which we usually are working with ensures that the model has a format key + // as soon as the form is loaded. Unfortunally this method here is called from within loaded, too. + // So if our LoadListener is called before the LoadListener of the model, this "else case" is + // allowed. + // Of course our property listener for the FormatKey property will notify us if the prop is changed, + // so this here isn't really bad... + nFormatKey = 0; + } + } + } + + // No? Maybe the css::form::component::Form behind the cursor? + if (!m_xSupplier.is()) + { + if (xCursor.is()) + { // If we take the formatter from the cursor, then also the key from the field to which we are bound + m_xSupplier = getNumberFormats(getConnection(xCursor)); + + if (m_rColumn.GetField().is()) + nFormatKey = ::comphelper::getINT32(m_rColumn.GetField()->getPropertyValue(FM_PROP_FORMATKEY)); + } + } + + SvNumberFormatter* pFormatterUsed = nullptr; + if (m_xSupplier.is()) + { + SvNumberFormatsSupplierObj* pImplementation = comphelper::getFromUnoTunnel<SvNumberFormatsSupplierObj>(m_xSupplier); + if (pImplementation) + pFormatterUsed = pImplementation->GetNumberFormatter(); + else + // Everything is invalid: the supplier is of the wrong type, then we can not + // rely on a standard formatter to know the (possibly non-standard) key. + nFormatKey = -1; + } + + // a standard formatter ... + if (pFormatterUsed == nullptr) + { + pFormatterUsed = rControlFormatter.StandardFormatter(); + DBG_ASSERT(pFormatterUsed != nullptr, "DbFormattedField::Init : no standard formatter given by the numeric field !"); + } + // ... and a standard key + if (nFormatKey == -1) + nFormatKey = 0; + + rControlFormatter.SetFormatter(pFormatterUsed); + rPainterFormatter.SetFormatter(pFormatterUsed); + + rControlFormatter.SetFormatKey(nFormatKey); + rPainterFormatter.SetFormatKey(nFormatKey); + + rControlFormatter.TreatAsNumber(m_rColumn.IsNumeric()); + rPainterFormatter.TreatAsNumber(m_rColumn.IsNumeric()); + + // min and max values + if (m_rColumn.IsNumeric()) + { + bool bClearMin = true; + if (::comphelper::hasProperty(FM_PROP_EFFECTIVE_MIN, xUnoModel)) + { + Any aMin( xUnoModel->getPropertyValue(FM_PROP_EFFECTIVE_MIN)); + if (aMin.getValueType().getTypeClass() != TypeClass_VOID) + { + DBG_ASSERT(aMin.getValueType().getTypeClass() == TypeClass_DOUBLE, "DbFormattedField::Init : the model has an invalid min value !"); + double dMin = ::comphelper::getDouble(aMin); + rControlFormatter.SetMinValue(dMin); + rPainterFormatter.SetMinValue(dMin); + bClearMin = false; + } + } + if (bClearMin) + { + rControlFormatter.ClearMinValue(); + rPainterFormatter.ClearMinValue(); + } + bool bClearMax = true; + if (::comphelper::hasProperty(FM_PROP_EFFECTIVE_MAX, xUnoModel)) + { + Any aMax(xUnoModel->getPropertyValue(FM_PROP_EFFECTIVE_MAX)); + if (aMax.getValueType().getTypeClass() != TypeClass_VOID) + { + DBG_ASSERT(aMax.getValueType().getTypeClass() == TypeClass_DOUBLE, "DbFormattedField::Init : the model has an invalid max value !"); + double dMax = ::comphelper::getDouble(aMax); + rControlFormatter.SetMaxValue(dMax); + rPainterFormatter.SetMaxValue(dMax); + bClearMax = false; + } + } + if (bClearMax) + { + rControlFormatter.ClearMaxValue(); + rPainterFormatter.ClearMaxValue(); + } + } + + // the default value + Any aDefault( xUnoModel->getPropertyValue(FM_PROP_EFFECTIVE_DEFAULT)); + if (aDefault.hasValue()) + { // the thing can be a double or a string + switch (aDefault.getValueType().getTypeClass()) + { + case TypeClass_DOUBLE: + if (m_rColumn.IsNumeric()) + { + rControlFormatter.SetDefaultValue(::comphelper::getDouble(aDefault)); + rPainterFormatter.SetDefaultValue(::comphelper::getDouble(aDefault)); + } + else + { + OUString sConverted; + const Color* pDummy; + pFormatterUsed->GetOutputString(::comphelper::getDouble(aDefault), 0, sConverted, &pDummy); + rControlFormatter.SetDefaultText(sConverted); + rPainterFormatter.SetDefaultText(sConverted); + } + break; + case TypeClass_STRING: + { + OUString sDefault( ::comphelper::getString(aDefault) ); + if (m_rColumn.IsNumeric()) + { + double dVal; + sal_uInt32 nTestFormat(0); + if (pFormatterUsed->IsNumberFormat(sDefault, nTestFormat, dVal)) + { + rControlFormatter.SetDefaultValue(dVal); + rPainterFormatter.SetDefaultValue(dVal); + } + } + else + { + rControlFormatter.SetDefaultText(sDefault); + rPainterFormatter.SetDefaultText(sDefault); + } + } + break; + default: + OSL_FAIL( "DbFormattedField::Init: unexpected value type!" ); + break; + } + } + DbLimitedLengthField::Init( rParent, xCursor ); +} + +CellControllerRef DbFormattedField::CreateController() const +{ + return new ::svt::FormattedFieldCellController(static_cast<FormattedControlBase*>(m_pWindow.get())); +} + +void DbFormattedField::_propertyChanged( const PropertyChangeEvent& _rEvent ) +{ + if (_rEvent.PropertyName == FM_PROP_FORMATKEY ) + { + sal_Int32 nNewKey = _rEvent.NewValue.hasValue() ? ::comphelper::getINT32(_rEvent.NewValue) : 0; + + DBG_ASSERT(m_pWindow && m_pPainter, "DbFormattedField::_propertyChanged : where are my windows ?"); + if (m_pWindow) + static_cast<FormattedControlBase*>(m_pWindow.get())->get_formatter().SetFormatKey(nNewKey); + if (m_pPainter) + static_cast<FormattedControlBase*>(m_pPainter.get())->get_formatter().SetFormatKey(nNewKey); + } + else + { + DbLimitedLengthField::_propertyChanged( _rEvent ); + } +} + +OUString DbFormattedField::GetFormatText(const Reference< css::sdb::XColumn >& _rxField, const Reference< XNumberFormatter >& /*xFormatter*/, const Color** ppColor) +{ + // no color specification by default + if (ppColor != nullptr) + *ppColor = nullptr; + + // NULL value -> empty text + if (!_rxField.is()) + return OUString(); + + FormattedControlBase* pControl = static_cast<FormattedControlBase*>(m_pPainter.get()); + weld::EntryFormatter& rPainterFormatter = pControl->get_formatter(); + + OUString aText; + try + { + if (m_rColumn.IsNumeric()) + { + // The IsNumeric at the column says nothing about the class of the used format, but + // about the class of the field bound to the column. So when you bind a FormattedField + // column to a double field and format it as text, m_rColumn.IsNumeric() returns + // sal_True. So that simply means that I can query the contents of the variant using + // getDouble, and then I can leave the rest (the formatting) to the FormattedField. + double dValue = getValue( _rxField, m_rColumn.GetParent().getNullDate() ); + if (_rxField->wasNull()) + return aText; + rPainterFormatter.SetValue(dValue); + } + else + { + // Here I can not work with a double, since the field can not provide it to me. + // So simply bind the text from the css::util::NumberFormatter to the correct css::form::component::Form. + aText = _rxField->getString(); + if (_rxField->wasNull()) + return aText; + rPainterFormatter.SetTextFormatted(aText); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + aText = pControl->get_widget().get_text(); + if (ppColor != nullptr) + *ppColor = rPainterFormatter.GetLastOutputColor(); + + return aText; +} + +void DbFormattedField::UpdateFromField(const Reference< css::sdb::XColumn >& _rxField, const Reference< XNumberFormatter >& /*xFormatter*/) +{ + try + { + FormattedControlBase* pEditControl = static_cast<FormattedControlBase*>(m_pWindow.get()); + weld::Entry& rEntry = pEditControl->get_widget(); + weld::EntryFormatter& rEditFormatter = pEditControl->get_formatter(); + + if (!_rxField.is()) + { + // NULL value -> empty text + rEntry.set_text(OUString()); + } + else if (m_rColumn.IsNumeric()) + { + // The IsNumeric at the column says nothing about the class of the used format, but + // about the class of the field bound to the column. So when you bind a FormattedField + // column to a double field and format it as text, m_rColumn.IsNumeric() returns + // sal_True. So that simply means that I can query the contents of the variant using + // getDouble, and then I can leave the rest (the formatting) to the FormattedField. + double dValue = getValue( _rxField, m_rColumn.GetParent().getNullDate() ); + if (_rxField->wasNull()) + rEntry.set_text(OUString()); + else + rEditFormatter.SetValue(dValue); + } + else + { + // Here I can not work with a double, since the field can not provide it to me. + // So simply bind the text from the css::util::NumberFormatter to the correct css::form::component::Form. + OUString sText( _rxField->getString()); + + rEditFormatter.SetTextFormatted( sText ); + rEntry.select_region(0, -1); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + +void DbFormattedField::updateFromModel( Reference< XPropertySet > _rxModel ) +{ + OSL_ENSURE( _rxModel.is() && m_pWindow, "DbFormattedField::updateFromModel: invalid call!" ); + + FormattedControlBase* pEditControl = static_cast<FormattedControlBase*>(m_pWindow.get()); + weld::Entry& rEntry = pEditControl->get_widget(); + weld::EntryFormatter& rEditFormatter = pEditControl->get_formatter(); + + OUString sText; + Any aValue = _rxModel->getPropertyValue( FM_PROP_EFFECTIVE_VALUE ); + if ( !aValue.hasValue() || (aValue >>= sText) ) + { + // our effective value is transferred as string + rEditFormatter.SetTextFormatted( sText ); + rEntry.select_region(0, -1); + } + else + { + double dValue = 0; + aValue >>= dValue; + rEditFormatter.SetValue(dValue); + } +} + +bool DbFormattedField::commitControl() +{ + Any aNewVal; + + FormattedControlBase* pEditControl = static_cast<FormattedControlBase*>(m_pWindow.get()); + weld::Entry& rEntry = pEditControl->get_widget(); + weld::EntryFormatter& rEditFormatter = pEditControl->get_formatter(); + + if (m_rColumn.IsNumeric()) + { + if (!rEntry.get_text().isEmpty()) + aNewVal <<= rEditFormatter.GetValue(); + // an empty string is passed on as void by default, to start with + } + else + aNewVal <<= rEditFormatter.GetTextValue(); + + m_rColumn.getModel()->setPropertyValue(FM_PROP_EFFECTIVE_VALUE, aNewVal); + return true; +} + +DbCheckBox::DbCheckBox( DbGridColumn& _rColumn ) + :DbCellControl( _rColumn ) +{ + setAlignedController( false ); +} + +namespace +{ + void setCheckBoxStyle( vcl::Window* _pWindow, bool bMono ) + { + AllSettings aSettings = _pWindow->GetSettings(); + StyleSettings aStyleSettings = aSettings.GetStyleSettings(); + if( bMono ) + aStyleSettings.SetOptions( aStyleSettings.GetOptions() | StyleSettingsOptions::Mono ); + else + aStyleSettings.SetOptions( aStyleSettings.GetOptions() & (~StyleSettingsOptions::Mono) ); + aSettings.SetStyleSettings( aStyleSettings ); + _pWindow->SetSettings( aSettings ); + } +} + +void DbCheckBox::Init(BrowserDataWin& rParent, const Reference< XRowSet >& xCursor) +{ + setTransparent( true ); + + m_pWindow = VclPtr<CheckBoxControl>::Create( &rParent ); + m_pPainter = VclPtr<CheckBoxControl>::Create( &rParent ); + + m_pWindow->SetPaintTransparent( true ); + m_pPainter->SetPaintTransparent( true ); + + m_pPainter->SetBackground(); + + try + { + Reference< XPropertySet > xModel( m_rColumn.getModel(), UNO_SET_THROW ); + + sal_Int16 nStyle = awt::VisualEffect::LOOK3D; + OSL_VERIFY( xModel->getPropertyValue( FM_PROP_VISUALEFFECT ) >>= nStyle ); + + setCheckBoxStyle( m_pWindow, nStyle == awt::VisualEffect::FLAT ); + setCheckBoxStyle( m_pPainter, nStyle == awt::VisualEffect::FLAT ); + + bool bTristate = true; + OSL_VERIFY( xModel->getPropertyValue( FM_PROP_TRISTATE ) >>= bTristate ); + static_cast< CheckBoxControl* >( m_pWindow.get() )->EnableTriState( bTristate ); + static_cast< CheckBoxControl* >( m_pPainter.get() )->EnableTriState( bTristate ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + DbCellControl::Init( rParent, xCursor ); +} + +CellControllerRef DbCheckBox::CreateController() const +{ + return new CheckBoxCellController(static_cast<CheckBoxControl*>(m_pWindow.get())); +} + +static void lcl_setCheckBoxState( const Reference< css::sdb::XColumn >& _rxField, + CheckBoxControl* _pCheckBoxControl ) +{ + TriState eState = TRISTATE_INDET; + if (_rxField.is()) + { + try + { + bool bValue = _rxField->getBoolean(); + if (!_rxField->wasNull()) + eState = bValue ? TRISTATE_TRUE : TRISTATE_FALSE; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + _pCheckBoxControl->SetState(eState); +} + +void DbCheckBox::UpdateFromField(const Reference< css::sdb::XColumn >& _rxField, const Reference< XNumberFormatter >& /*xFormatter*/) +{ + lcl_setCheckBoxState( _rxField, static_cast<CheckBoxControl*>(m_pWindow.get()) ); +} + +void DbCheckBox::PaintFieldToCell(OutputDevice& rDev, const tools::Rectangle& rRect, + const Reference< css::sdb::XColumn >& _rxField, + const Reference< XNumberFormatter >& xFormatter) +{ + CheckBoxControl* pControl = static_cast<CheckBoxControl*>(m_pPainter.get()); + lcl_setCheckBoxState( _rxField, pControl ); + + Size aBoxSize; + + switch (rDev.GetOutDevType()) + { + case OUTDEV_WINDOW: + case OUTDEV_VIRDEV: + aBoxSize = pControl->GetBox().get_preferred_size(); + break; + case OUTDEV_PRINTER: + case OUTDEV_PDF: + { + auto nSize = std::min(rRect.GetWidth(), rRect.GetHeight()); + aBoxSize = Size(nSize, nSize); + break; + } + } + + tools::Rectangle aRect(Point(rRect.Left() + ((rRect.GetWidth() - aBoxSize.Width()) / 2), + rRect.Top() + ((rRect.GetHeight() - aBoxSize.Height()) / 2)), + aBoxSize); + + DbCellControl::PaintFieldToCell(rDev, aRect, _rxField, xFormatter); +} + +void DbCheckBox::PaintCell(OutputDevice& rDev, const tools::Rectangle& rRect) +{ + switch (rDev.GetOutDevType()) + { + case OUTDEV_WINDOW: + case OUTDEV_VIRDEV: + DbCellControl::PaintCell(rDev, rRect); + break; + case OUTDEV_PRINTER: + case OUTDEV_PDF: + { + TriState eState = static_cast<CheckBoxControl*>(m_pWindow.get())->GetState(); + + MapMode aResMapMode(MapUnit::Map100thMM); + Size aImageSize = rDev.LogicToPixel(Size(300, 300), aResMapMode); + Size aBrd1Size = rDev.LogicToPixel(Size(20, 20), aResMapMode); + Size aBrd2Size = rDev.LogicToPixel(Size(30, 30), aResMapMode); + int nCheckWidth = rDev.LogicToPixel(Size(20, 20), aResMapMode).Width(); + + tools::Rectangle aStateRect; + aStateRect.SetLeft(rRect.Left() + ((rRect.GetWidth() - aImageSize.Width()) / 2)); + aStateRect.SetTop(rRect.Top() + ((rRect.GetHeight() - aImageSize.Height()) / 2)); + aStateRect.SetRight(aStateRect.Left() + aImageSize.Width() - 1); + aStateRect.SetBottom(aStateRect.Top() + aImageSize.Height() - 1); + + rDev.Push(); + rDev.SetMapMode(); + + rDev.SetLineColor(); + rDev.SetFillColor(COL_BLACK); + rDev.DrawRect(aStateRect); + aStateRect.AdjustLeft(aBrd1Size.Width()); + aStateRect.AdjustTop(aBrd1Size.Height()); + aStateRect.AdjustRight(-aBrd1Size.Width()); + aStateRect.AdjustBottom(-aBrd1Size.Height()); + if (eState == TRISTATE_INDET) + rDev.SetFillColor(COL_LIGHTGRAY); + else + rDev.SetFillColor(COL_WHITE); + rDev.DrawRect(aStateRect); + + if (eState == TRISTATE_TRUE) + { + aStateRect.AdjustLeft(aBrd2Size.Width()); + aStateRect.AdjustTop(aBrd2Size.Height()); + aStateRect.AdjustRight(-aBrd2Size.Width()); + aStateRect.AdjustBottom(-aBrd2Size.Height()); + Point aPos11(aStateRect.TopLeft()); + Point aPos12(aStateRect.BottomRight()); + Point aPos21(aStateRect.TopRight()); + Point aPos22(aStateRect.BottomLeft()); + Point aTempPos11(aPos11); + Point aTempPos12(aPos12); + Point aTempPos21(aPos21); + Point aTempPos22(aPos22); + rDev.SetLineColor(COL_BLACK); + int nDX = 0; + for (int i = 0; i < nCheckWidth; i++) + { + if ( !(i % 2) ) + { + aTempPos11.setX(aPos11.X() + nDX); + aTempPos12.setX(aPos12.X() + nDX); + aTempPos21.setX(aPos21.X() + nDX); + aTempPos22.setX(aPos22.X() + nDX); + } + else + { + nDX++; + aTempPos11.setX(aPos11.X() - nDX); + aTempPos12.setX(aPos12.X() - nDX); + aTempPos21.setX(aPos21.X() - nDX); + aTempPos22.setX(aPos22.X() - nDX); + } + rDev.DrawLine(aTempPos11, aTempPos12); + rDev.DrawLine(aTempPos21, aTempPos22); + } + } + + rDev.Pop(); + break; + } + } +} + +void DbCheckBox::updateFromModel( Reference< XPropertySet > _rxModel ) +{ + OSL_ENSURE( _rxModel.is() && m_pWindow, "DbCheckBox::updateFromModel: invalid call!" ); + + sal_Int16 nState = TRISTATE_INDET; + _rxModel->getPropertyValue( FM_PROP_STATE ) >>= nState; + static_cast< CheckBoxControl* >( m_pWindow.get() )->SetState( static_cast< TriState >( nState ) ); +} + +bool DbCheckBox::commitControl() +{ + m_rColumn.getModel()->setPropertyValue( FM_PROP_STATE, + Any( static_cast<sal_Int16>( static_cast< CheckBoxControl* >( m_pWindow.get() )->GetState() ) ) ); + return true; +} + +OUString DbCheckBox::GetFormatText(const Reference< XColumn >& /*_rxField*/, const Reference< XNumberFormatter >& /*xFormatter*/, const Color** /*ppColor*/) +{ + return OUString(); +} + +DbPatternField::DbPatternField( DbGridColumn& _rColumn, const Reference<XComponentContext>& _rContext ) + :DbCellControl( _rColumn ) + ,m_xContext( _rContext ) +{ + doPropertyListening( FM_PROP_LITERALMASK ); + doPropertyListening( FM_PROP_EDITMASK ); + doPropertyListening( FM_PROP_STRICTFORMAT ); +} + +void DbPatternField::implAdjustGenericFieldSetting( const Reference< XPropertySet >& _rxModel ) +{ + DBG_ASSERT( m_pWindow, "DbPatternField::implAdjustGenericFieldSetting: not to be called without window!" ); + DBG_ASSERT( _rxModel.is(), "DbPatternField::implAdjustGenericFieldSetting: invalid model!" ); + if ( !m_pWindow || !_rxModel.is() ) + return; + + OUString aLitMask; + OUString aEditMask; + bool bStrict = false; + + _rxModel->getPropertyValue( FM_PROP_LITERALMASK ) >>= aLitMask; + _rxModel->getPropertyValue( FM_PROP_EDITMASK ) >>= aEditMask; + _rxModel->getPropertyValue( FM_PROP_STRICTFORMAT ) >>= bStrict; + + OString aAsciiEditMask(OUStringToOString(aEditMask, RTL_TEXTENCODING_ASCII_US)); + + weld::PatternFormatter& rEditFormatter = static_cast<PatternControl*>(m_pWindow.get())->get_formatter(); + rEditFormatter.SetMask(aAsciiEditMask, aLitMask); + rEditFormatter.SetStrictFormat(bStrict); + + weld::PatternFormatter& rPaintFormatter = static_cast<PatternControl*>(m_pPainter.get())->get_formatter(); + rPaintFormatter.SetMask(aAsciiEditMask, aLitMask); + rPaintFormatter.SetStrictFormat(bStrict); +} + +void DbPatternField::Init(BrowserDataWin& rParent, const Reference< XRowSet >& xCursor) +{ + m_rColumn.SetAlignmentFromModel(-1); + + m_pWindow = VclPtr<PatternControl>::Create(&rParent); + m_pPainter= VclPtr<PatternControl>::Create(&rParent); + + Reference< XPropertySet > xModel( m_rColumn.getModel() ); + implAdjustGenericFieldSetting( xModel ); + + DbCellControl::Init( rParent, xCursor ); +} + +CellControllerRef DbPatternField::CreateController() const +{ + return new EditCellController(static_cast<PatternControl*>(m_pWindow.get())); +} + +OUString DbPatternField::impl_formatText( const OUString& _rText ) +{ + weld::PatternFormatter& rPaintFormatter = static_cast<PatternControl*>(m_pPainter.get())->get_formatter(); + rPaintFormatter.get_widget().set_text(_rText); + rPaintFormatter.ReformatAll(); + return rPaintFormatter.get_widget().get_text(); +} + +OUString DbPatternField::GetFormatText(const Reference< css::sdb::XColumn >& _rxField, const Reference< XNumberFormatter >& /*xFormatter*/, const Color** /*ppColor*/) +{ + bool bIsForPaint = _rxField != m_rColumn.GetField(); + ::std::unique_ptr< FormattedColumnValue >& rpFormatter = bIsForPaint ? m_pPaintFormatter : m_pValueFormatter; + + if (!rpFormatter) + { + rpFormatter = std::make_unique< FormattedColumnValue> ( + m_xContext, getCursor(), Reference< XPropertySet >( _rxField, UNO_QUERY ) ); + OSL_ENSURE(rpFormatter, "DbPatternField::Init: no value formatter!"); + } + else + OSL_ENSURE( rpFormatter->getColumn() == _rxField, "DbPatternField::GetFormatText: my value formatter is working for another field ...!" ); + // re-creating the value formatter here every time would be quite expensive ... + + OUString sText; + if (rpFormatter) + sText = rpFormatter->getFormattedValue(); + + return impl_formatText( sText ); +} + +void DbPatternField::UpdateFromField( const Reference< XColumn >& _rxField, const Reference< XNumberFormatter >& _rxFormatter ) +{ + weld::Entry& rEntry = static_cast<PatternControl*>(m_pWindow.get())->get_widget(); + rEntry.set_text(GetFormatText(_rxField, _rxFormatter)); + rEntry.select_region(-1, 0); +} + +void DbPatternField::updateFromModel( Reference< XPropertySet > _rxModel ) +{ + OSL_ENSURE( _rxModel.is() && m_pWindow, "DbPatternField::updateFromModel: invalid call!" ); + + OUString sText; + _rxModel->getPropertyValue( FM_PROP_TEXT ) >>= sText; + + weld::Entry& rEntry = static_cast<PatternControl*>(m_pWindow.get())->get_widget(); + rEntry.set_text(impl_formatText(sText)); + rEntry.select_region(-1, 0); +} + +bool DbPatternField::commitControl() +{ + weld::Entry& rEntry = static_cast<PatternControl*>(m_pWindow.get())->get_widget(); + m_rColumn.getModel()->setPropertyValue(FM_PROP_TEXT, Any(rEntry.get_text())); + return true; +} + +DbSpinField::DbSpinField( DbGridColumn& _rColumn, sal_Int16 _nStandardAlign ) + :DbCellControl( _rColumn ) + ,m_nStandardAlign( _nStandardAlign ) +{ +} + +void DbSpinField::Init(BrowserDataWin& _rParent, const Reference< XRowSet >& _rxCursor) +{ + m_rColumn.SetAlignmentFromModel( m_nStandardAlign ); + + Reference< XPropertySet > xModel( m_rColumn.getModel() ); + + // determine if we need a spinbutton version + bool bSpinButton(false); + if ( ::comphelper::getBOOL( xModel->getPropertyValue( FM_PROP_SPIN ) ) ) + bSpinButton = true; + // create the fields + m_pWindow = createField( &_rParent, bSpinButton, xModel ); + m_pPainter = createField( &_rParent, bSpinButton, xModel ); + + // adjust all other settings which depend on the property values + implAdjustGenericFieldSetting( xModel ); + + // call the base class + DbCellControl::Init( _rParent, _rxCursor ); +} + +CellControllerRef DbSpinField::CreateController() const +{ + return new ::svt::FormattedFieldCellController(static_cast<FormattedControlBase*>(m_pWindow.get())); +} + +DbNumericField::DbNumericField( DbGridColumn& _rColumn ) + :DbSpinField( _rColumn ) +{ + doPropertyListening( FM_PROP_DECIMAL_ACCURACY ); + doPropertyListening( FM_PROP_VALUEMIN ); + doPropertyListening( FM_PROP_VALUEMAX ); + doPropertyListening( FM_PROP_VALUESTEP ); + doPropertyListening( FM_PROP_STRICTFORMAT ); + doPropertyListening( FM_PROP_SHOWTHOUSANDSEP ); +} + +void DbNumericField::implAdjustGenericFieldSetting( const Reference< XPropertySet >& _rxModel ) +{ + DBG_ASSERT( m_pWindow, "DbNumericField::implAdjustGenericFieldSetting: not to be called without window!" ); + DBG_ASSERT( _rxModel.is(), "DbNumericField::implAdjustGenericFieldSetting: invalid model!" ); + if ( !m_pWindow || !_rxModel.is() ) + return; + + sal_Int32 nMin = static_cast<sal_Int32>(getDouble( _rxModel->getPropertyValue( FM_PROP_VALUEMIN ) )); + sal_Int32 nMax = static_cast<sal_Int32>(getDouble( _rxModel->getPropertyValue( FM_PROP_VALUEMAX ) )); + sal_Int32 nStep = static_cast<sal_Int32>(getDouble( _rxModel->getPropertyValue( FM_PROP_VALUESTEP ) )); + bool bStrict = getBOOL( _rxModel->getPropertyValue( FM_PROP_STRICTFORMAT ) ); + sal_Int16 nScale = getINT16( _rxModel->getPropertyValue( FM_PROP_DECIMAL_ACCURACY ) ); + bool bThousand = getBOOL( _rxModel->getPropertyValue( FM_PROP_SHOWTHOUSANDSEP ) ); + + Formatter& rEditFormatter = static_cast<FormattedControlBase*>(m_pWindow.get())->get_formatter(); + rEditFormatter.SetMinValue(nMin); + rEditFormatter.SetMaxValue(nMax); + rEditFormatter.SetSpinSize(nStep); + rEditFormatter.SetStrictFormat(bStrict); + + Formatter& rPaintFormatter = static_cast<FormattedControlBase*>(m_pPainter.get())->get_formatter(); + rPaintFormatter.SetMinValue(nMin); + rPaintFormatter.SetMaxValue(nMax); + rPaintFormatter.SetStrictFormat(bStrict); + + // give a formatter to the field and the painter; + // test first if I can get from the service behind a connection + Reference< css::util::XNumberFormatsSupplier > xSupplier; + Reference< XRowSet > xForm; + if ( m_rColumn.GetParent().getDataSource() ) + xForm.set( Reference< XInterface >(*m_rColumn.GetParent().getDataSource()), UNO_QUERY ); + if ( xForm.is() ) + xSupplier = getNumberFormats( getConnection( xForm ), true ); + SvNumberFormatter* pFormatterUsed = nullptr; + if ( xSupplier.is() ) + { + SvNumberFormatsSupplierObj* pImplementation = comphelper::getFromUnoTunnel<SvNumberFormatsSupplierObj>( xSupplier ); + pFormatterUsed = pImplementation ? pImplementation->GetNumberFormatter() : nullptr; + } + if ( nullptr == pFormatterUsed ) + { // the cursor didn't lead to success -> standard + pFormatterUsed = rEditFormatter.StandardFormatter(); + DBG_ASSERT( pFormatterUsed != nullptr, "DbNumericField::implAdjustGenericFieldSetting: no standard formatter given by the numeric field !" ); + } + rEditFormatter.SetFormatter( pFormatterUsed ); + rPaintFormatter.SetFormatter( pFormatterUsed ); + + // and then generate a format which has the desired length after the decimal point, etc. + LanguageType aAppLanguage = Application::GetSettings().GetUILanguageTag().getLanguageType(); + OUString sFormatString = pFormatterUsed->GenerateFormat(0, aAppLanguage, bThousand, false, nScale); + + rEditFormatter.SetFormat( sFormatString, aAppLanguage ); + rPaintFormatter.SetFormat( sFormatString, aAppLanguage ); +} + +VclPtr<svt::ControlBase> DbNumericField::createField(BrowserDataWin* pParent, bool bSpinButton, const Reference<XPropertySet>& /*rxModel*/) +{ + return VclPtr<DoubleNumericControl>::Create(pParent, bSpinButton); +} + +namespace +{ + OUString lcl_setFormattedNumeric_nothrow( FormattedControlBase& _rField, const DbCellControl& _rControl, + const Reference< XColumn >& _rxField, const Reference< XNumberFormatter >& _rxFormatter ) + { + OUString sValue; + if ( _rxField.is() ) + { + try + { + double fValue = _rControl.GetValue( _rxField, _rxFormatter ); + if ( !_rxField->wasNull() ) + { + _rField.get_formatter().SetValue(fValue); + sValue = _rField.get_widget().get_text(); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + return sValue; + } +} + +OUString DbNumericField::GetFormatText(const Reference< css::sdb::XColumn >& _rxField, const Reference< css::util::XNumberFormatter >& _rxFormatter, const Color** /*ppColor*/) +{ + return lcl_setFormattedNumeric_nothrow(dynamic_cast<FormattedControlBase&>(*m_pPainter), *this, _rxField, _rxFormatter); +} + +void DbNumericField::UpdateFromField(const Reference< css::sdb::XColumn >& _rxField, const Reference< css::util::XNumberFormatter >& _rxFormatter) +{ + lcl_setFormattedNumeric_nothrow(dynamic_cast<FormattedControlBase&>(*m_pWindow), *this, _rxField, _rxFormatter); +} + +void DbNumericField::updateFromModel( Reference< XPropertySet > _rxModel ) +{ + OSL_ENSURE( _rxModel.is() && m_pWindow, "DbNumericField::updateFromModel: invalid call!" ); + + FormattedControlBase* pControl = static_cast<FormattedControlBase*>(m_pWindow.get()); + Formatter& rFormatter = pControl->get_formatter(); + + double dValue = 0; + if ( _rxModel->getPropertyValue( FM_PROP_VALUE ) >>= dValue ) + rFormatter.SetValue(dValue); + else + { + pControl->get_widget().set_text(OUString()); + rFormatter.InvalidateValueState(); + } +} + +bool DbNumericField::commitControl() +{ + FormattedControlBase* pControl = static_cast<FormattedControlBase*>(m_pWindow.get()); + OUString aText(pControl->get_widget().get_text()); + Any aVal; + + if (!aText.isEmpty()) // not empty + { + Formatter& rFormatter = pControl->get_formatter(); + double fValue = rFormatter.GetValue(); + aVal <<= fValue; + } + m_rColumn.getModel()->setPropertyValue(FM_PROP_VALUE, aVal); + return true; +} + +DbCurrencyField::DbCurrencyField(DbGridColumn& _rColumn) + :DbSpinField( _rColumn ) +{ + doPropertyListening( FM_PROP_DECIMAL_ACCURACY ); + doPropertyListening( FM_PROP_VALUEMIN ); + doPropertyListening( FM_PROP_VALUEMAX ); + doPropertyListening( FM_PROP_VALUESTEP ); + doPropertyListening( FM_PROP_STRICTFORMAT ); + doPropertyListening( FM_PROP_SHOWTHOUSANDSEP ); + doPropertyListening( FM_PROP_CURRENCYSYMBOL ); +} + +void DbCurrencyField::implAdjustGenericFieldSetting( const Reference< XPropertySet >& _rxModel ) +{ + DBG_ASSERT( m_pWindow, "DbCurrencyField::implAdjustGenericFieldSetting: not to be called without window!" ); + DBG_ASSERT( _rxModel.is(), "DbCurrencyField::implAdjustGenericFieldSetting: invalid model!" ); + if ( !m_pWindow || !_rxModel.is() ) + return; + + sal_Int16 nScale = getINT16( _rxModel->getPropertyValue( FM_PROP_DECIMAL_ACCURACY ) ); + double nMin = getDouble( _rxModel->getPropertyValue( FM_PROP_VALUEMIN ) ); + double nMax = getDouble( _rxModel->getPropertyValue( FM_PROP_VALUEMAX ) ); + double nStep = getDouble( _rxModel->getPropertyValue( FM_PROP_VALUESTEP ) ); + bool bStrict = getBOOL( _rxModel->getPropertyValue( FM_PROP_STRICTFORMAT ) ); + bool bThousand = getBOOL( _rxModel->getPropertyValue( FM_PROP_SHOWTHOUSANDSEP ) ); + OUString aStr( getString( _rxModel->getPropertyValue(FM_PROP_CURRENCYSYMBOL ) ) ); + + Formatter& rEditFormatter = static_cast<FormattedControlBase*>(m_pWindow.get())->get_formatter(); + rEditFormatter.SetDecimalDigits(nScale); + rEditFormatter.SetMinValue(nMin); + rEditFormatter.SetMaxValue(nMax); + rEditFormatter.SetSpinSize(nStep); + rEditFormatter.SetStrictFormat(bStrict); + weld::LongCurrencyFormatter& rCurrencyEditFormatter = static_cast<weld::LongCurrencyFormatter&>(rEditFormatter); + rCurrencyEditFormatter.SetUseThousandSep(bThousand); + rCurrencyEditFormatter.SetCurrencySymbol(aStr); + + Formatter& rPaintFormatter = static_cast<FormattedControlBase*>(m_pPainter.get())->get_formatter(); + rPaintFormatter.SetDecimalDigits(nScale); + rPaintFormatter.SetMinValue(nMin); + rPaintFormatter.SetMaxValue(nMax); + rPaintFormatter.SetStrictFormat(bStrict); + weld::LongCurrencyFormatter& rPaintCurrencyFormatter = static_cast<weld::LongCurrencyFormatter&>(rPaintFormatter); + rPaintCurrencyFormatter.SetUseThousandSep(bThousand); + rPaintCurrencyFormatter.SetCurrencySymbol(aStr); +} + +VclPtr<svt::ControlBase> DbCurrencyField::createField(BrowserDataWin* pParent, bool bSpinButton, const Reference< XPropertySet >& /*rxModel*/) +{ + return VclPtr<LongCurrencyControl>::Create(pParent, bSpinButton); +} + +namespace +{ + OUString lcl_setFormattedCurrency_nothrow( FormattedControlBase& _rField, const DbCurrencyField& _rControl, + const Reference< XColumn >& _rxField, const Reference< XNumberFormatter >& _rxFormatter ) + { + OUString sValue; + if ( _rxField.is() ) + { + try + { + double fValue = _rControl.GetValue( _rxField, _rxFormatter ); + if ( !_rxField->wasNull() ) + { + _rField.get_formatter().SetValue(fValue); + sValue = _rField.get_widget().get_text(); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + return sValue; + } +} + +OUString DbCurrencyField::GetFormatText(const Reference< css::sdb::XColumn >& _rxField, const Reference< css::util::XNumberFormatter >& _rxFormatter, const Color** /*ppColor*/) +{ + return lcl_setFormattedCurrency_nothrow(dynamic_cast<FormattedControlBase&>(*m_pPainter), *this, _rxField, _rxFormatter); +} + +void DbCurrencyField::UpdateFromField(const Reference< css::sdb::XColumn >& _rxField, const Reference< css::util::XNumberFormatter >& _rxFormatter) +{ + lcl_setFormattedCurrency_nothrow(dynamic_cast<FormattedControlBase&>(*m_pWindow), *this, _rxField, _rxFormatter); +} + +void DbCurrencyField::updateFromModel( Reference< XPropertySet > _rxModel ) +{ + OSL_ENSURE( _rxModel.is() && m_pWindow, "DbCurrencyField::updateFromModel: invalid call!" ); + + FormattedControlBase* pControl = static_cast<FormattedControlBase*>(m_pWindow.get()); + Formatter& rFormatter = pControl->get_formatter(); + + double dValue = 0; + if ( _rxModel->getPropertyValue( FM_PROP_VALUE ) >>= dValue ) + rFormatter.SetValue(dValue); + else + { + pControl->get_widget().set_text(OUString()); + rFormatter.InvalidateValueState(); + } +} + +bool DbCurrencyField::commitControl() +{ + FormattedControlBase* pControl = static_cast<FormattedControlBase*>(m_pWindow.get()); + OUString aText(pControl->get_widget().get_text()); + Any aVal; + + if (!aText.isEmpty()) // not empty + { + Formatter& rFormatter = pControl->get_formatter(); + double fValue = rFormatter.GetValue(); + aVal <<= fValue; + } + m_rColumn.getModel()->setPropertyValue(FM_PROP_VALUE, aVal); + return true; +} + +DbDateField::DbDateField( DbGridColumn& _rColumn ) + :DbSpinField( _rColumn ) +{ + doPropertyListening( FM_PROP_DATEFORMAT ); + doPropertyListening( FM_PROP_DATEMIN ); + doPropertyListening( FM_PROP_DATEMAX ); + doPropertyListening( FM_PROP_STRICTFORMAT ); + doPropertyListening( FM_PROP_DATE_SHOW_CENTURY ); +} + +VclPtr<svt::ControlBase> DbDateField::createField(BrowserDataWin* pParent, bool bSpinButton, const Reference< XPropertySet >& rxModel) +{ + // check if there is a DropDown property set to TRUE + bool bDropDown = !hasProperty( FM_PROP_DROPDOWN, rxModel ) + || getBOOL( rxModel->getPropertyValue( FM_PROP_DROPDOWN ) ); + // given the apparent inability to set a custom up/down action for a gtk + // spinbutton to have different up/down dates depending on the zone the + // mouse is in, show the dropdown calendar for both the spin or dropdown case + return VclPtr<DateControl>::Create(pParent, bSpinButton || bDropDown); +} + +void DbDateField::implAdjustGenericFieldSetting( const Reference< XPropertySet >& _rxModel ) +{ + DBG_ASSERT( m_pWindow, "DbDateField::implAdjustGenericFieldSetting: not to be called without window!" ); + DBG_ASSERT( _rxModel.is(), "DbDateField::implAdjustGenericFieldSetting: invalid model!" ); + if ( !m_pWindow || !_rxModel.is() ) + return; + + sal_Int16 nFormat = getINT16( _rxModel->getPropertyValue( FM_PROP_DATEFORMAT ) ); + util::Date aMin; + OSL_VERIFY( _rxModel->getPropertyValue( FM_PROP_DATEMIN ) >>= aMin ); + util::Date aMax; + OSL_VERIFY( _rxModel->getPropertyValue( FM_PROP_DATEMAX ) >>= aMax ); + bool bStrict = getBOOL( _rxModel->getPropertyValue( FM_PROP_STRICTFORMAT ) ); + + FormattedControlBase* pControl = static_cast<FormattedControlBase*>(m_pWindow.get()); + weld::DateFormatter& rControlFormatter = static_cast<weld::DateFormatter&>(pControl->get_formatter()); + + FormattedControlBase* pPainter = static_cast<FormattedControlBase*>(m_pPainter.get()); + weld::DateFormatter& rPainterFormatter = static_cast<weld::DateFormatter&>(pPainter->get_formatter()); + + Any aCentury = _rxModel->getPropertyValue( FM_PROP_DATE_SHOW_CENTURY ); + if ( aCentury.getValueType().getTypeClass() != TypeClass_VOID ) + { + bool bShowDateCentury = getBOOL( aCentury ); + + rControlFormatter.SetShowDateCentury(bShowDateCentury); + rPainterFormatter.SetShowDateCentury(bShowDateCentury); + } + + rControlFormatter.SetExtDateFormat( static_cast<ExtDateFieldFormat>(nFormat) ); + rControlFormatter.SetMin( aMin ); + rControlFormatter.SetMax( aMax ); + rControlFormatter.SetStrictFormat( bStrict ); + rControlFormatter.EnableEmptyField( true ); + + rPainterFormatter.SetExtDateFormat( static_cast<ExtDateFieldFormat>(nFormat) ); + rPainterFormatter.SetMin( aMin ); + rPainterFormatter.SetMax( aMax ); + rPainterFormatter.SetStrictFormat( bStrict ); + rPainterFormatter.EnableEmptyField( true ); +} + +namespace +{ + OUString lcl_setFormattedDate_nothrow(DateControl& _rField, const Reference<XColumn>& _rxField) + { + OUString sDate; + if ( _rxField.is() ) + { + try + { + css::util::Date aValue = _rxField->getDate(); + if (!_rxField->wasNull()) + { + _rField.SetDate(::Date(aValue.Day, aValue.Month, aValue.Year)); + sDate = _rField.get_widget().get_text(); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + return sDate; + } +} + +OUString DbDateField::GetFormatText(const Reference< css::sdb::XColumn >& _rxField, const Reference< css::util::XNumberFormatter >& /*xFormatter*/, const Color** /*ppColor*/) +{ + return lcl_setFormattedDate_nothrow(*static_cast<DateControl*>(m_pPainter.get()), _rxField); +} + +void DbDateField::UpdateFromField(const Reference< css::sdb::XColumn >& _rxField, const Reference< XNumberFormatter >& /*xFormatter*/) +{ + lcl_setFormattedDate_nothrow(*static_cast<DateControl*>(m_pWindow.get()), _rxField); +} + +void DbDateField::updateFromModel( Reference< XPropertySet > _rxModel ) +{ + OSL_ENSURE( _rxModel.is() && m_pWindow, "DbDateField::updateFromModel: invalid call!" ); + + DateControl* pControl = static_cast<DateControl*>(m_pWindow.get()); + + util::Date aDate; + if ( _rxModel->getPropertyValue( FM_PROP_DATE ) >>= aDate ) + pControl->SetDate(::Date(aDate)); + else + pControl->get_widget().set_text(OUString()); +} + +bool DbDateField::commitControl() +{ + FormattedControlBase* pControl = static_cast<FormattedControlBase*>(m_pWindow.get()); + OUString aText(pControl->get_widget().get_text()); + Any aVal; + + if (!aText.isEmpty()) // not empty + { + weld::DateFormatter& rControlFormatter = static_cast<weld::DateFormatter&>(pControl->get_formatter()); + aVal <<= rControlFormatter.GetDate().GetUNODate(); + } + + m_rColumn.getModel()->setPropertyValue(FM_PROP_DATE, aVal); + return true; +} + +DbTimeField::DbTimeField( DbGridColumn& _rColumn ) + :DbSpinField( _rColumn, css::awt::TextAlign::LEFT ) +{ + doPropertyListening( FM_PROP_TIMEFORMAT ); + doPropertyListening( FM_PROP_TIMEMIN ); + doPropertyListening( FM_PROP_TIMEMAX ); + doPropertyListening( FM_PROP_STRICTFORMAT ); +} + +VclPtr<svt::ControlBase> DbTimeField::createField(BrowserDataWin* pParent, bool bSpinButton, const Reference< XPropertySet >& /*rxModel*/ ) +{ + return VclPtr<TimeControl>::Create(pParent, bSpinButton); +} + +void DbTimeField::implAdjustGenericFieldSetting( const Reference< XPropertySet >& _rxModel ) +{ + DBG_ASSERT( m_pWindow, "DbTimeField::implAdjustGenericFieldSetting: not to be called without window!" ); + DBG_ASSERT( _rxModel.is(), "DbTimeField::implAdjustGenericFieldSetting: invalid model!" ); + if ( !m_pWindow || !_rxModel.is() ) + return; + + sal_Int16 nFormat = getINT16( _rxModel->getPropertyValue( FM_PROP_TIMEFORMAT ) ); + util::Time aMin; + OSL_VERIFY( _rxModel->getPropertyValue( FM_PROP_TIMEMIN ) >>= aMin ); + util::Time aMax; + OSL_VERIFY( _rxModel->getPropertyValue( FM_PROP_TIMEMAX ) >>= aMax ); + bool bStrict = getBOOL( _rxModel->getPropertyValue( FM_PROP_STRICTFORMAT ) ); + + FormattedControlBase* pControl = static_cast<FormattedControlBase*>(m_pWindow.get()); + weld::TimeFormatter& rControlFormatter = static_cast<weld::TimeFormatter&>(pControl->get_formatter()); + + rControlFormatter.SetExtFormat(static_cast<ExtTimeFieldFormat>(nFormat)); + rControlFormatter.SetMin(aMin); + rControlFormatter.SetMax(aMax); + rControlFormatter.SetStrictFormat(bStrict); + rControlFormatter.EnableEmptyField(true); + + FormattedControlBase* pPainter = static_cast<FormattedControlBase*>(m_pPainter.get()); + weld::TimeFormatter& rPainterFormatter = static_cast<weld::TimeFormatter&>(pPainter->get_formatter()); + + rPainterFormatter.SetExtFormat(static_cast<ExtTimeFieldFormat>(nFormat)); + rPainterFormatter.SetMin(aMin); + rPainterFormatter.SetMax(aMax); + rPainterFormatter.SetStrictFormat(bStrict); + rPainterFormatter.EnableEmptyField(true); +} + +namespace +{ + OUString lcl_setFormattedTime_nothrow(TimeControl& _rField, const Reference<XColumn>& _rxField) + { + OUString sTime; + if ( _rxField.is() ) + { + try + { + css::util::Time aValue = _rxField->getTime(); + if (!_rxField->wasNull()) + { + static_cast<weld::TimeFormatter&>(_rField.get_formatter()).SetTime( ::tools::Time( aValue ) ); + sTime = _rField.get_widget().get_text(); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + return sTime; + } +} + +OUString DbTimeField::GetFormatText(const Reference< css::sdb::XColumn >& _rxField, const Reference< css::util::XNumberFormatter >& /*xFormatter*/, const Color** /*ppColor*/) +{ + return lcl_setFormattedTime_nothrow(*static_cast<TimeControl*>(m_pPainter.get()), _rxField); +} + +void DbTimeField::UpdateFromField(const Reference< css::sdb::XColumn >& _rxField, const Reference< XNumberFormatter >& /*xFormatter*/) +{ + lcl_setFormattedTime_nothrow(*static_cast<TimeControl*>(m_pWindow.get()), _rxField); +} + +void DbTimeField::updateFromModel( Reference< XPropertySet > _rxModel ) +{ + OSL_ENSURE( _rxModel.is() && m_pWindow, "DbTimeField::updateFromModel: invalid call!" ); + + FormattedControlBase* pControl = static_cast<FormattedControlBase*>(m_pWindow.get()); + weld::TimeFormatter& rControlFormatter = static_cast<weld::TimeFormatter&>(pControl->get_formatter()); + + util::Time aTime; + if ( _rxModel->getPropertyValue( FM_PROP_TIME ) >>= aTime ) + rControlFormatter.SetTime(::tools::Time(aTime)); + else + pControl->get_widget().set_text(OUString()); +} + +bool DbTimeField::commitControl() +{ + FormattedControlBase* pControl = static_cast<FormattedControlBase*>(m_pWindow.get()); + OUString aText(pControl->get_widget().get_text()); + Any aVal; + + if (!aText.isEmpty()) // not empty + { + weld::TimeFormatter& rControlFormatter = static_cast<weld::TimeFormatter&>(pControl->get_formatter()); + aVal <<= rControlFormatter.GetTime().GetUNOTime(); + } + + m_rColumn.getModel()->setPropertyValue(FM_PROP_TIME, aVal); + return true; +} + +DbComboBox::DbComboBox(DbGridColumn& _rColumn) + :DbCellControl(_rColumn) +{ + setAlignedController( false ); + + doPropertyListening( FM_PROP_STRINGITEMLIST ); + doPropertyListening( FM_PROP_LINECOUNT ); +} + +void DbComboBox::_propertyChanged( const PropertyChangeEvent& _rEvent ) +{ + if ( _rEvent.PropertyName == FM_PROP_STRINGITEMLIST ) + { + SetList(_rEvent.NewValue); + } + else + { + DbCellControl::_propertyChanged( _rEvent ) ; + } +} + +void DbComboBox::SetList(const Any& rItems) +{ + ComboBoxControl* pField = static_cast<ComboBoxControl*>(m_pWindow.get()); + weld::ComboBox& rComboBox = pField->get_widget(); + rComboBox.clear(); + + css::uno::Sequence<OUString> aTest; + if (rItems >>= aTest) + { + for (const OUString& rString : std::as_const(aTest)) + rComboBox.append_text(rString); + + // tell the grid control that this controller is invalid and has to be re-initialized + invalidatedController(); + } +} + +void DbComboBox::implAdjustGenericFieldSetting(const Reference<XPropertySet>&) +{ + // we no longer pay attention to FM_PROP_LINECOUNT +} + +void DbComboBox::Init(BrowserDataWin& rParent, const Reference< XRowSet >& xCursor) +{ + m_rColumn.SetAlignmentFromModel(css::awt::TextAlign::LEFT); + + m_pWindow = VclPtr<ComboBoxControl>::Create( &rParent ); + + // selection from right to left + AllSettings aSettings = m_pWindow->GetSettings(); + StyleSettings aStyleSettings = aSettings.GetStyleSettings(); + aStyleSettings.SetSelectionOptions( + aStyleSettings.GetSelectionOptions() | SelectionOptions::ShowFirst); + aSettings.SetStyleSettings(aStyleSettings); + m_pWindow->SetSettings(aSettings, true); + + // some initial properties + Reference< XPropertySet > xModel(m_rColumn.getModel()); + SetList( xModel->getPropertyValue( FM_PROP_STRINGITEMLIST ) ); + implAdjustGenericFieldSetting( xModel ); + + DbCellControl::Init( rParent, xCursor ); +} + +CellControllerRef DbComboBox::CreateController() const +{ + return new ComboBoxCellController(static_cast<ComboBoxControl*>(m_pWindow.get())); +} + +OUString DbComboBox::GetFormatText(const Reference< css::sdb::XColumn >& _rxField, const Reference< XNumberFormatter >& xFormatter, const Color** /*ppColor*/) +{ + const css::uno::Reference<css::beans::XPropertySet> xPS(_rxField, UNO_QUERY); + ::dbtools::FormattedColumnValue fmter( xFormatter, xPS ); + + return fmter.getFormattedValue(); +} + +void DbComboBox::UpdateFromField(const Reference< css::sdb::XColumn >& _rxField, const Reference< XNumberFormatter >& xFormatter) +{ + ComboBoxControl* pControl = static_cast<ComboBoxControl*>(m_pWindow.get()); + pControl->get_widget().set_entry_text(GetFormatText(_rxField, xFormatter)); +} + +void DbComboBox::updateFromModel( Reference< XPropertySet > _rxModel ) +{ + OSL_ENSURE( _rxModel.is() && m_pWindow, "DbComboBox::updateFromModel: invalid call!" ); + + OUString sText; + _rxModel->getPropertyValue( FM_PROP_TEXT ) >>= sText; + + ComboBoxControl* pControl = static_cast<ComboBoxControl*>(m_pWindow.get()); + weld::ComboBox& rComboBox = pControl->get_widget(); + + OUString sOldActive = rComboBox.get_active_text(); + rComboBox.set_entry_text(sText); + rComboBox.select_entry_region(0, -1); + + if (sOldActive != rComboBox.get_active_text()) + pControl->TriggerAuxModify(); +} + +bool DbComboBox::commitControl() +{ + ComboBoxControl* pControl = static_cast<ComboBoxControl*>(m_pWindow.get()); + weld::ComboBox& rComboBox = pControl->get_widget(); + OUString aText(rComboBox.get_active_text()); + m_rColumn.getModel()->setPropertyValue(FM_PROP_TEXT, Any(aText)); + return true; +} + + +DbListBox::DbListBox(DbGridColumn& _rColumn) + :DbCellControl(_rColumn) + ,m_bBound(false) +{ + setAlignedController( false ); + + doPropertyListening( FM_PROP_STRINGITEMLIST ); + doPropertyListening( FM_PROP_LINECOUNT ); +} + +void DbListBox::_propertyChanged( const css::beans::PropertyChangeEvent& _rEvent ) +{ + if ( _rEvent.PropertyName == FM_PROP_STRINGITEMLIST ) + { + SetList(_rEvent.NewValue); + } + else + { + DbCellControl::_propertyChanged( _rEvent ) ; + } +} + +void DbListBox::SetList(const Any& rItems) +{ + ListBoxControl* pField = static_cast<ListBoxControl*>(m_pWindow.get()); + + weld::ComboBox& rFieldList = pField->get_widget(); + + rFieldList.clear(); + m_bBound = false; + + css::uno::Sequence<OUString> aTest; + if (!(rItems >>= aTest)) + return; + + if (aTest.hasElements()) + { + for (const OUString& rString : std::as_const(aTest)) + rFieldList.append_text(rString); + + m_rColumn.getModel()->getPropertyValue(FM_PROP_VALUE_SEQ) >>= m_aValueList; + m_bBound = m_aValueList.hasElements(); + + // tell the grid control that this controller is invalid and has to be re-initialized + invalidatedController(); + } +} + +void DbListBox::Init(BrowserDataWin& rParent, const Reference< XRowSet >& xCursor) +{ + m_rColumn.SetAlignment(css::awt::TextAlign::LEFT); + + m_pWindow = VclPtr<ListBoxControl>::Create( &rParent ); + + // some initial properties + Reference< XPropertySet > xModel( m_rColumn.getModel() ); + SetList( xModel->getPropertyValue( FM_PROP_STRINGITEMLIST ) ); + implAdjustGenericFieldSetting( xModel ); + + DbCellControl::Init( rParent, xCursor ); +} + +void DbListBox::implAdjustGenericFieldSetting( const Reference< XPropertySet >& /*rxModel*/ ) +{ + // ignore FM_PROP_LINECOUNT +} + +CellControllerRef DbListBox::CreateController() const +{ + return new ListBoxCellController(static_cast<ListBoxControl*>(m_pWindow.get())); +} + +OUString DbListBox::GetFormatText(const Reference< css::sdb::XColumn >& _rxField, const Reference< XNumberFormatter >& /*xFormatter*/, const Color** /*ppColor*/) +{ + OUString sText; + if ( _rxField.is() ) + { + try + { + sText = _rxField->getString(); + if ( m_bBound ) + { + sal_Int32 nPos = ::comphelper::findValue( m_aValueList, sText ); + if ( nPos != -1 ) + sText = static_cast<svt::ListBoxControl*>(m_pWindow.get())->get_widget().get_text(nPos); + else + sText.clear(); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + return sText; +} + +void DbListBox::UpdateFromField(const Reference< css::sdb::XColumn >& _rxField, const Reference< XNumberFormatter >& xFormatter) +{ + OUString sFormattedText( GetFormatText( _rxField, xFormatter ) ); + weld::ComboBox& rComboBox = static_cast<ListBoxControl*>(m_pWindow.get())->get_widget(); + if (!sFormattedText.isEmpty()) + rComboBox.set_active_text(sFormattedText); + else + rComboBox.set_active(-1); +} + +void DbListBox::updateFromModel( Reference< XPropertySet > _rxModel ) +{ + OSL_ENSURE( _rxModel.is() && m_pWindow, "DbListBox::updateFromModel: invalid call!" ); + + Sequence< sal_Int16 > aSelection; + _rxModel->getPropertyValue( FM_PROP_SELECT_SEQ ) >>= aSelection; + + sal_Int16 nSelection = -1; + if ( aSelection.hasElements() ) + nSelection = aSelection[ 0 ]; + + ListBoxControl* pControl = static_cast<ListBoxControl*>(m_pWindow.get()); + weld::ComboBox& rComboBox = pControl->get_widget(); + + int nOldActive = rComboBox.get_active(); + if (nSelection >= 0 && nSelection < rComboBox.get_count()) + rComboBox.set_active(nSelection); + else + rComboBox.set_active(-1); + + if (nOldActive != rComboBox.get_active()) + pControl->TriggerAuxModify(); +} + +bool DbListBox::commitControl() +{ + Any aVal; + Sequence<sal_Int16> aSelectSeq; + weld::ComboBox& rComboBox = static_cast<ListBoxControl*>(m_pWindow.get())->get_widget(); + auto nActive = rComboBox.get_active(); + if (nActive != -1) + { + aSelectSeq.realloc(1); + *aSelectSeq.getArray() = static_cast<sal_Int16>(nActive); + } + aVal <<= aSelectSeq; + m_rColumn.getModel()->setPropertyValue(FM_PROP_SELECT_SEQ, aVal); + return true; +} + +DbFilterField::DbFilterField(const Reference< XComponentContext >& rxContext,DbGridColumn& _rColumn) + :DbCellControl(_rColumn) + ,OSQLParserClient(rxContext) + ,m_nControlClass(css::form::FormComponentType::TEXTFIELD) + ,m_bFilterList(false) + ,m_bFilterListFilled(false) +{ + + setAlignedController( false ); +} + +DbFilterField::~DbFilterField() +{ + if (m_nControlClass == css::form::FormComponentType::CHECKBOX) + static_cast<CheckBoxControl*>(m_pWindow.get())->SetToggleHdl(Link<weld::CheckButton&,void>()); + +} + +void DbFilterField::PaintCell(OutputDevice& rDev, const tools::Rectangle& rRect) +{ + static const DrawTextFlags nStyle = DrawTextFlags::Clip | DrawTextFlags::VCenter | DrawTextFlags::Left; + switch (m_nControlClass) + { + case FormComponentType::CHECKBOX: + { + // center the checkbox within the space available + CheckBoxControl* pControl = static_cast<CheckBoxControl*>(m_pPainter.get()); + Size aBoxSize = pControl->GetBox().get_preferred_size(); + tools::Rectangle aRect(Point(rRect.Left() + ((rRect.GetWidth() - aBoxSize.Width()) / 2), + rRect.Top() + ((rRect.GetHeight() - aBoxSize.Height()) / 2)), + aBoxSize); + + DbCellControl::PaintCell(rDev, aRect); + break; + } + case FormComponentType::LISTBOX: + rDev.DrawText(rRect, static_cast<ListBoxControl*>(m_pWindow.get())->get_widget().get_active_text(), nStyle); + break; + default: + rDev.DrawText(rRect, m_aText, nStyle); + } +} + +void DbFilterField::SetList(const Any& rItems, bool bComboBox) +{ + css::uno::Sequence<OUString> aTest; + rItems >>= aTest; + if (!aTest.hasElements()) + return; + + if (bComboBox) + { + ComboBoxControl* pField = static_cast<ComboBoxControl*>(m_pWindow.get()); + weld::ComboBox& rComboBox = pField->get_widget(); + for (const OUString& rString : std::as_const(aTest)) + rComboBox.append_text(rString); + } + else + { + ListBoxControl* pField = static_cast<ListBoxControl*>(m_pWindow.get()); + weld::ComboBox& rFieldBox = pField->get_widget(); + for (const OUString& rString : std::as_const(aTest)) + rFieldBox.append_text(rString); + + m_rColumn.getModel()->getPropertyValue(FM_PROP_VALUE_SEQ) >>= m_aValueList; + } +} + +void DbFilterField::CreateControl(BrowserDataWin* pParent, const Reference< css::beans::XPropertySet >& xModel) +{ + switch (m_nControlClass) + { + case css::form::FormComponentType::CHECKBOX: + m_pWindow = VclPtr<CheckBoxControl>::Create(pParent); + m_pWindow->SetPaintTransparent( true ); + static_cast<CheckBoxControl*>(m_pWindow.get())->SetToggleHdl(LINK(this, DbFilterField, OnToggle)); + + m_pPainter = VclPtr<CheckBoxControl>::Create(pParent); + m_pPainter->SetPaintTransparent( true ); + m_pPainter->SetBackground(); + break; + case css::form::FormComponentType::LISTBOX: + { + m_pWindow = VclPtr<ListBoxControl>::Create(pParent); + Any aItems = xModel->getPropertyValue(FM_PROP_STRINGITEMLIST); + SetList(aItems, false); + } break; + case css::form::FormComponentType::COMBOBOX: + { + m_pWindow = VclPtr<ComboBoxControl>::Create(pParent); + + AllSettings aSettings = m_pWindow->GetSettings(); + StyleSettings aStyleSettings = aSettings.GetStyleSettings(); + aStyleSettings.SetSelectionOptions( + aStyleSettings.GetSelectionOptions() | SelectionOptions::ShowFirst); + aSettings.SetStyleSettings(aStyleSettings); + m_pWindow->SetSettings(aSettings, true); + + if (!m_bFilterList) + { + Any aItems = xModel->getPropertyValue(FM_PROP_STRINGITEMLIST); + SetList(aItems, true); + } + + } break; + default: + { + m_pWindow = VclPtr<EditControl>::Create(pParent); + AllSettings aSettings = m_pWindow->GetSettings(); + StyleSettings aStyleSettings = aSettings.GetStyleSettings(); + aStyleSettings.SetSelectionOptions( + aStyleSettings.GetSelectionOptions() | SelectionOptions::ShowFirst); + aSettings.SetStyleSettings(aStyleSettings); + m_pWindow->SetSettings(aSettings, true); + } + } +} + +void DbFilterField::Init(BrowserDataWin& rParent, const Reference< XRowSet >& xCursor) +{ + Reference< css::beans::XPropertySet > xModel(m_rColumn.getModel()); + m_rColumn.SetAlignment(css::awt::TextAlign::LEFT); + + if (xModel.is()) + { + m_bFilterList = ::comphelper::hasProperty(FM_PROP_FILTERPROPOSAL, xModel) && ::comphelper::getBOOL(xModel->getPropertyValue(FM_PROP_FILTERPROPOSAL)); + if (m_bFilterList) + m_nControlClass = css::form::FormComponentType::COMBOBOX; + else + { + sal_Int16 nClassId = ::comphelper::getINT16(xModel->getPropertyValue(FM_PROP_CLASSID)); + switch (nClassId) + { + case FormComponentType::CHECKBOX: + case FormComponentType::LISTBOX: + case FormComponentType::COMBOBOX: + m_nControlClass = nClassId; + break; + default: + if (m_bFilterList) + m_nControlClass = FormComponentType::COMBOBOX; + else + m_nControlClass = FormComponentType::TEXTFIELD; + } + } + } + + CreateControl( &rParent, xModel ); + DbCellControl::Init( rParent, xCursor ); + + // filter cells are never readonly + m_pWindow->SetEditableReadOnly(false); +} + +CellControllerRef DbFilterField::CreateController() const +{ + CellControllerRef xController; + switch (m_nControlClass) + { + case css::form::FormComponentType::CHECKBOX: + xController = new CheckBoxCellController(static_cast<CheckBoxControl*>(m_pWindow.get())); + break; + case css::form::FormComponentType::LISTBOX: + xController = new ListBoxCellController(static_cast<ListBoxControl*>(m_pWindow.get())); + break; + case css::form::FormComponentType::COMBOBOX: + xController = new ComboBoxCellController(static_cast<ComboBoxControl*>(m_pWindow.get())); + break; + default: + if (m_bFilterList) + xController = new ComboBoxCellController(static_cast<ComboBoxControl*>(m_pWindow.get())); + else + xController = new EditCellController(static_cast<EditControlBase*>(m_pWindow.get())); + } + return xController; +} + +void DbFilterField::updateFromModel( Reference< XPropertySet > _rxModel ) +{ + OSL_ENSURE( _rxModel.is() && m_pWindow, "DbFilterField::updateFromModel: invalid call!" ); + + OSL_FAIL( "DbFilterField::updateFromModel: not implemented yet (how the hell did you reach this?)!" ); + // TODO: implement this. + // remember: updateFromModel should be some kind of opposite of commitControl +} + +bool DbFilterField::commitControl() +{ + OUString aText(m_aText); + switch (m_nControlClass) + { + case css::form::FormComponentType::CHECKBOX: + return true; + case css::form::FormComponentType::LISTBOX: + { + aText.clear(); + weld::ComboBox& rComboBox = static_cast<svt::ListBoxControl*>(m_pWindow.get())->get_widget(); + auto nActive = rComboBox.get_active(); + if (nActive != -1) + { + sal_Int16 nPos = static_cast<sal_Int16>(nActive); + if ( ( nPos >= 0 ) && ( nPos < m_aValueList.getLength() ) ) + aText = m_aValueList.getConstArray()[nPos]; + } + + if (m_aText != aText) + { + m_aText = aText; + m_aCommitLink.Call(*this); + } + return true; + } + case css::form::FormComponentType::COMBOBOX: + { + aText = static_cast<ComboBoxControl*>(m_pWindow.get())->get_widget().get_active_text(); + break; + } + default: + { + aText = static_cast<EditControlBase*>(m_pWindow.get())->get_widget().get_text(); + break; + } + } + + if (m_aText != aText) + { + // check the text with the SQL-Parser + OUString aNewText(comphelper::string::stripEnd(aText, ' ')); + if (!aNewText.isEmpty()) + { + OUString aErrorMsg; + Reference< XNumberFormatter > xNumberFormatter(m_rColumn.GetParent().getNumberFormatter()); + + std::unique_ptr< OSQLParseNode > pParseNode = predicateTree(aErrorMsg, aNewText,xNumberFormatter, m_rColumn.GetField()); + if (pParseNode != nullptr) + { + OUString aPreparedText; + + css::lang::Locale aAppLocale = Application::GetSettings().GetUILanguageTag().getLocale(); + + Reference< XRowSet > xDataSourceRowSet( + Reference< XInterface >(*m_rColumn.GetParent().getDataSource()), UNO_QUERY); + Reference< XConnection > xConnection(getConnection(xDataSourceRowSet)); + + pParseNode->parseNodeToPredicateStr(aPreparedText, + xConnection, + xNumberFormatter, + m_rColumn.GetField(), + OUString(), + aAppLocale, + ".", + getParseContext()); + m_aText = aPreparedText; + } + else + { + + SQLException aError(aErrorMsg, {}, {}, 0, {}); + displayException(aError, VCLUnoHelper::GetInterface(m_pWindow->GetParent())); + // TODO: transport the title + + return false; + } + } + else + m_aText = aText; + + m_pWindow->SetText(m_aText); + m_aCommitLink.Call(*this); + } + return true; +} + + +void DbFilterField::SetText(const OUString& rText) +{ + m_aText = rText; + switch (m_nControlClass) + { + case css::form::FormComponentType::CHECKBOX: + { + TriState eState; + if (rText == "1") + eState = TRISTATE_TRUE; + else if (rText == "0") + eState = TRISTATE_FALSE; + else + eState = TRISTATE_INDET; + + static_cast<CheckBoxControl*>(m_pWindow.get())->SetState(eState); + static_cast<CheckBoxControl*>(m_pPainter.get())->SetState(eState); + } break; + case css::form::FormComponentType::LISTBOX: + { + sal_Int32 nPos = ::comphelper::findValue(m_aValueList, m_aText); + static_cast<ListBoxControl*>(m_pWindow.get())->get_widget().set_active(nPos); + } break; + case css::form::FormComponentType::COMBOBOX: + { + static_cast<ComboBoxControl*>(m_pWindow.get())->get_widget().set_entry_text(m_aText); + break; + } + default: + { + static_cast<EditControlBase*>(m_pWindow.get())->get_widget().set_text(m_aText); + break; + } + } + + // now force a repaint on the window + m_rColumn.GetParent().RowModified(0); +} + + +void DbFilterField::Update() +{ + // should we fill the combobox with a filter proposal? + if (!m_bFilterList || m_bFilterListFilled) + return; + + m_bFilterListFilled = true; + Reference< css::beans::XPropertySet > xField = m_rColumn.GetField(); + if (!xField.is()) + return; + + OUString aName; + xField->getPropertyValue(FM_PROP_NAME) >>= aName; + + // the columnmodel + Reference< css::container::XChild > xModelAsChild(m_rColumn.getModel(), UNO_QUERY); + // the grid model + xModelAsChild.set(xModelAsChild->getParent(),UNO_QUERY); + Reference< XRowSet > xForm(xModelAsChild->getParent(), UNO_QUERY); + if (!xForm.is()) + return; + + Reference<XPropertySet> xFormProp(xForm,UNO_QUERY); + Reference< XTablesSupplier > xSupTab; + xFormProp->getPropertyValue("SingleSelectQueryComposer") >>= xSupTab; + + Reference< XConnection > xConnection(getConnection(xForm)); + if (!xSupTab.is()) + return; + + // search the field + Reference< XColumnsSupplier > xSupCol(xSupTab,UNO_QUERY); + Reference< css::container::XNameAccess > xFieldNames = xSupCol->getColumns(); + if (!xFieldNames->hasByName(aName)) + return; + + Reference< css::container::XNameAccess > xTablesNames = xSupTab->getTables(); + Reference< css::beans::XPropertySet > xComposerFieldAsSet(xFieldNames->getByName(aName),UNO_QUERY); + + if (!xComposerFieldAsSet.is() || + !::comphelper::hasProperty(FM_PROP_TABLENAME, xComposerFieldAsSet) || + !::comphelper::hasProperty(FM_PROP_FIELDSOURCE, xComposerFieldAsSet)) + return; + + OUString aFieldName; + OUString aTableName; + xComposerFieldAsSet->getPropertyValue(FM_PROP_FIELDSOURCE) >>= aFieldName; + xComposerFieldAsSet->getPropertyValue(FM_PROP_TABLENAME) >>= aTableName; + + // no possibility to create a select statement + // looking for the complete table name + if (!xTablesNames->hasByName(aTableName)) + return; + + // build a statement and send as query; + // Access to the connection + Reference< XStatement > xStatement; + Reference< XResultSet > xListCursor; + Reference< css::sdb::XColumn > xDataField; + + try + { + Reference< XDatabaseMetaData > xMeta = xConnection->getMetaData(); + + OUString aQuote(xMeta->getIdentifierQuoteString()); + OUStringBuffer aStatement("SELECT DISTINCT " + + quoteName(aQuote, aName)); + if (!aFieldName.isEmpty() && aName != aFieldName) + { + aStatement.append(" AS " + + quoteName(aQuote, aFieldName)); + } + + aStatement.append(" FROM "); + + Reference< XPropertySet > xTableNameAccess(xTablesNames->getByName(aTableName), UNO_QUERY_THROW); + aStatement.append(composeTableNameForSelect(xConnection, xTableNameAccess)); + + xStatement = xConnection->createStatement(); + Reference< css::beans::XPropertySet > xStatementProps(xStatement, UNO_QUERY); + xStatementProps->setPropertyValue(FM_PROP_ESCAPE_PROCESSING, Any(true)); + + xListCursor = xStatement->executeQuery(aStatement.makeStringAndClear()); + + Reference< css::sdbcx::XColumnsSupplier > xSupplyCols(xListCursor, UNO_QUERY); + Reference< css::container::XIndexAccess > xFields(xSupplyCols->getColumns(), UNO_QUERY); + xDataField.set(xFields->getByIndex(0), css::uno::UNO_QUERY); + if (!xDataField.is()) + return; + } + catch(const Exception&) + { + ::comphelper::disposeComponent(xStatement); + return; + } + + sal_Int16 i = 0; + ::std::vector< OUString > aStringList; + aStringList.reserve(16); + OUString aStr; + css::util::Date aNullDate = m_rColumn.GetParent().getNullDate(); + sal_Int32 nFormatKey = m_rColumn.GetKey(); + Reference< XNumberFormatter > xFormatter = m_rColumn.GetParent().getNumberFormatter(); + sal_Int16 nKeyType = ::comphelper::getNumberFormatType(xFormatter->getNumberFormatsSupplier()->getNumberFormats(), nFormatKey); + + while (!xListCursor->isAfterLast() && i++ < SHRT_MAX) // max number of entries + { + aStr = getFormattedValue(xDataField, xFormatter, aNullDate, nFormatKey, nKeyType); + aStringList.push_back(aStr); + (void)xListCursor->next(); + } + + ComboBoxControl* pField = static_cast<ComboBoxControl*>(m_pWindow.get()); + weld::ComboBox& rComboBox = pField->get_widget(); + // filling the entries for the combobox + for (const auto& rString : aStringList) + rComboBox.append_text(rString); +} + +OUString DbFilterField::GetFormatText(const Reference< XColumn >& /*_rxField*/, const Reference< XNumberFormatter >& /*xFormatter*/, const Color** /*ppColor*/) +{ + return OUString(); +} + +void DbFilterField::UpdateFromField(const Reference< XColumn >& /*_rxField*/, const Reference< XNumberFormatter >& /*xFormatter*/) +{ + OSL_FAIL( "DbFilterField::UpdateFromField: cannot update a filter control from a field!" ); +} + +IMPL_LINK_NOARG(DbFilterField, OnToggle, weld::CheckButton&, void) +{ + TriState eState = static_cast<CheckBoxControl*>(m_pWindow.get())->GetState(); + OUStringBuffer aTextBuf; + + Reference< XRowSet > xDataSourceRowSet( + Reference< XInterface >(*m_rColumn.GetParent().getDataSource()), UNO_QUERY); + Reference< XConnection > xConnection(getConnection(xDataSourceRowSet)); + const sal_Int32 nBooleanComparisonMode = ::dbtools::DatabaseMetaData( xConnection ).getBooleanComparisonMode(); + + switch (eState) + { + case TRISTATE_TRUE: + ::dbtools::getBooleanComparisonPredicate(u"", true, nBooleanComparisonMode, aTextBuf); + break; + case TRISTATE_FALSE: + ::dbtools::getBooleanComparisonPredicate(u"", false, nBooleanComparisonMode, aTextBuf); + break; + case TRISTATE_INDET: + break; + } + + const OUString aText(aTextBuf.makeStringAndClear()); + + if (m_aText != aText) + { + m_aText = aText; + m_aCommitLink.Call(*this); + } +} + +FmXGridCell::FmXGridCell( DbGridColumn* pColumn, std::unique_ptr<DbCellControl> _pControl ) + :OComponentHelper(m_aMutex) + ,m_pColumn(pColumn) + ,m_pCellControl( std::move(_pControl) ) + ,m_aWindowListeners( m_aMutex ) + ,m_aFocusListeners( m_aMutex ) + ,m_aKeyListeners( m_aMutex ) + ,m_aMouseListeners( m_aMutex ) + ,m_aMouseMotionListeners( m_aMutex ) +{ +} + +void FmXGridCell::init() +{ + svt::ControlBase* pEventWindow( getEventWindow() ); + if ( pEventWindow ) + { + pEventWindow->SetFocusInHdl(LINK( this, FmXGridCell, OnFocusGained)); + pEventWindow->SetFocusOutHdl(LINK( this, FmXGridCell, OnFocusLost)); + pEventWindow->SetMousePressHdl(LINK( this, FmXGridCell, OnMousePress)); + pEventWindow->SetMouseReleaseHdl(LINK( this, FmXGridCell, OnMouseRelease)); + pEventWindow->SetMouseMoveHdl(LINK( this, FmXGridCell, OnMouseMove)); + pEventWindow->SetKeyInputHdl( LINK( this, FmXGridCell, OnKeyInput) ); + pEventWindow->SetKeyReleaseHdl( LINK( this, FmXGridCell, OnKeyRelease) ); + } +} + +svt::ControlBase* FmXGridCell::getEventWindow() const +{ + if ( m_pCellControl ) + return &m_pCellControl->GetWindow(); + return nullptr; +} + +FmXGridCell::~FmXGridCell() +{ + if (!OComponentHelper::rBHelper.bDisposed) + { + acquire(); + dispose(); + } + +} + +void FmXGridCell::SetTextLineColor() +{ + if (m_pCellControl) + m_pCellControl->SetTextLineColor(); +} + +void FmXGridCell::SetTextLineColor(const Color& _rColor) +{ + if (m_pCellControl) + m_pCellControl->SetTextLineColor(_rColor); +} + +// XTypeProvider + +Sequence< Type > SAL_CALL FmXGridCell::getTypes( ) +{ + Sequence< uno::Type > aTypes = ::comphelper::concatSequences( + ::cppu::OComponentHelper::getTypes(), + FmXGridCell_Base::getTypes() + ); + if ( m_pCellControl ) + aTypes = ::comphelper::concatSequences( + aTypes, + FmXGridCell_WindowBase::getTypes() + ); + return aTypes; +} + + +IMPLEMENT_GET_IMPLEMENTATION_ID( FmXGridCell ) + +// OComponentHelper + +void FmXGridCell::disposing() +{ + lang::EventObject aEvent( *this ); + m_aWindowListeners.disposeAndClear( aEvent ); + m_aFocusListeners.disposeAndClear( aEvent ); + m_aKeyListeners.disposeAndClear( aEvent ); + m_aMouseListeners.disposeAndClear( aEvent ); + m_aMouseMotionListeners.disposeAndClear( aEvent ); + + OComponentHelper::disposing(); + m_pColumn = nullptr; + m_pCellControl.reset(); +} + + +Any SAL_CALL FmXGridCell::queryAggregation( const css::uno::Type& _rType ) +{ + Any aReturn = OComponentHelper::queryAggregation( _rType ); + + if ( !aReturn.hasValue() ) + aReturn = FmXGridCell_Base::queryInterface( _rType ); + + if ( !aReturn.hasValue() && ( m_pCellControl != nullptr ) ) + aReturn = FmXGridCell_WindowBase::queryInterface( _rType ); + + return aReturn; +} + +// css::awt::XControl + +Reference< XInterface > FmXGridCell::getContext() +{ + return Reference< XInterface > (); +} + + +Reference< css::awt::XControlModel > FmXGridCell::getModel() +{ + checkDisposed(OComponentHelper::rBHelper.bDisposed); + return Reference< css::awt::XControlModel > (m_pColumn->getModel(), UNO_QUERY); +} + +// css::form::XBoundControl + +sal_Bool FmXGridCell::getLock() +{ + checkDisposed(OComponentHelper::rBHelper.bDisposed); + return m_pColumn->isLocked(); +} + + +void FmXGridCell::setLock(sal_Bool _bLock) +{ + checkDisposed(OComponentHelper::rBHelper.bDisposed); + if (getLock() == _bLock) + return; + else + { + ::osl::MutexGuard aGuard(m_aMutex); + m_pColumn->setLock(_bLock); + } +} + + +void SAL_CALL FmXGridCell::setPosSize( ::sal_Int32, ::sal_Int32, ::sal_Int32, ::sal_Int32, ::sal_Int16 ) +{ + OSL_FAIL( "FmXGridCell::setPosSize: not implemented" ); + // not allowed to tamper with this for a grid cell +} + + +awt::Rectangle SAL_CALL FmXGridCell::getPosSize( ) +{ + OSL_FAIL( "FmXGridCell::getPosSize: not implemented" ); + return awt::Rectangle(); +} + + +void SAL_CALL FmXGridCell::setVisible( sal_Bool ) +{ + OSL_FAIL( "FmXGridCell::setVisible: not implemented" ); + // not allowed to tamper with this for a grid cell +} + + +void SAL_CALL FmXGridCell::setEnable( sal_Bool ) +{ + OSL_FAIL( "FmXGridCell::setEnable: not implemented" ); + // not allowed to tamper with this for a grid cell +} + + +void SAL_CALL FmXGridCell::setFocus( ) +{ + OSL_FAIL( "FmXGridCell::setFocus: not implemented" ); + // not allowed to tamper with this for a grid cell +} + + +void SAL_CALL FmXGridCell::addWindowListener( const Reference< awt::XWindowListener >& _rxListener ) +{ + checkDisposed(OComponentHelper::rBHelper.bDisposed); + m_aWindowListeners.addInterface( _rxListener ); +} + + +void SAL_CALL FmXGridCell::removeWindowListener( const Reference< awt::XWindowListener >& _rxListener ) +{ + checkDisposed(OComponentHelper::rBHelper.bDisposed); + m_aWindowListeners.removeInterface( _rxListener ); +} + + +void SAL_CALL FmXGridCell::addFocusListener( const Reference< awt::XFocusListener >& _rxListener ) +{ + checkDisposed(OComponentHelper::rBHelper.bDisposed); + m_aFocusListeners.addInterface( _rxListener ); +} + + +void SAL_CALL FmXGridCell::removeFocusListener( const Reference< awt::XFocusListener >& _rxListener ) +{ + checkDisposed(OComponentHelper::rBHelper.bDisposed); + m_aFocusListeners.removeInterface( _rxListener ); +} + + +void SAL_CALL FmXGridCell::addKeyListener( const Reference< awt::XKeyListener >& _rxListener ) +{ + checkDisposed(OComponentHelper::rBHelper.bDisposed); + m_aKeyListeners.addInterface( _rxListener ); +} + + +void SAL_CALL FmXGridCell::removeKeyListener( const Reference< awt::XKeyListener >& _rxListener ) +{ + checkDisposed(OComponentHelper::rBHelper.bDisposed); + m_aKeyListeners.removeInterface( _rxListener ); +} + + +void SAL_CALL FmXGridCell::addMouseListener( const Reference< awt::XMouseListener >& _rxListener ) +{ + checkDisposed(OComponentHelper::rBHelper.bDisposed); + m_aMouseListeners.addInterface( _rxListener ); +} + + +void SAL_CALL FmXGridCell::removeMouseListener( const Reference< awt::XMouseListener >& _rxListener ) +{ + checkDisposed(OComponentHelper::rBHelper.bDisposed); + m_aMouseListeners.removeInterface( _rxListener ); +} + + +void SAL_CALL FmXGridCell::addMouseMotionListener( const Reference< awt::XMouseMotionListener >& _rxListener ) +{ + checkDisposed(OComponentHelper::rBHelper.bDisposed); + m_aMouseMotionListeners.addInterface( _rxListener ); +} + + +void SAL_CALL FmXGridCell::removeMouseMotionListener( const Reference< awt::XMouseMotionListener >& _rxListener ) +{ + checkDisposed(OComponentHelper::rBHelper.bDisposed); + m_aMouseMotionListeners.removeInterface( _rxListener ); +} + +void SAL_CALL FmXGridCell::addPaintListener( const Reference< awt::XPaintListener >& ) +{ + OSL_FAIL( "FmXGridCell::addPaintListener: not implemented" ); +} + +void SAL_CALL FmXGridCell::removePaintListener( const Reference< awt::XPaintListener >& ) +{ + OSL_FAIL( "FmXGridCell::removePaintListener: not implemented" ); +} + +void FmXGridCell::onFocusGained( const awt::FocusEvent& _rEvent ) +{ + checkDisposed(OComponentHelper::rBHelper.bDisposed); + m_aFocusListeners.notifyEach( &awt::XFocusListener::focusGained, _rEvent ); +} + +void FmXGridCell::onFocusLost( const awt::FocusEvent& _rEvent ) +{ + checkDisposed(OComponentHelper::rBHelper.bDisposed); + m_aFocusListeners.notifyEach( &awt::XFocusListener::focusLost, _rEvent ); +} + +IMPL_LINK_NOARG(FmXGridCell, OnFocusGained, LinkParamNone*, void) +{ + if (!m_aFocusListeners.getLength()) + return; + + awt::FocusEvent aEvent; + aEvent.Source = *this; + aEvent.Temporary = false; + + onFocusGained(aEvent); +} + +IMPL_LINK_NOARG(FmXGridCell, OnFocusLost, LinkParamNone*, void) +{ + if (!m_aFocusListeners.getLength()) + return; + + awt::FocusEvent aEvent; + aEvent.Source = *this; + aEvent.Temporary = false; + + onFocusLost(aEvent); +} + +IMPL_LINK(FmXGridCell, OnMousePress, const MouseEvent&, rEventData, void) +{ + if (!m_aMouseListeners.getLength()) + return; + + awt::MouseEvent aEvent(VCLUnoHelper::createMouseEvent(rEventData, *this)); + m_aMouseListeners.notifyEach(&awt::XMouseListener::mousePressed, aEvent); +} + +IMPL_LINK(FmXGridCell, OnMouseRelease, const MouseEvent&, rEventData, void) +{ + if (!m_aMouseListeners.getLength()) + return; + + awt::MouseEvent aEvent(VCLUnoHelper::createMouseEvent(rEventData, *this)); + m_aMouseListeners.notifyEach(&awt::XMouseListener::mouseReleased, aEvent); +} + +IMPL_LINK(FmXGridCell, OnMouseMove, const MouseEvent&, rMouseEvent, void) +{ + if ( rMouseEvent.IsEnterWindow() || rMouseEvent.IsLeaveWindow() ) + { + if ( m_aMouseListeners.getLength() != 0 ) + { + awt::MouseEvent aEvent( VCLUnoHelper::createMouseEvent( rMouseEvent, *this ) ); + m_aMouseListeners.notifyEach( rMouseEvent.IsEnterWindow() ? &awt::XMouseListener::mouseEntered: &awt::XMouseListener::mouseExited, aEvent ); + } + } + else if ( !rMouseEvent.IsEnterWindow() && !rMouseEvent.IsLeaveWindow() ) + { + if ( m_aMouseMotionListeners.getLength() != 0 ) + { + awt::MouseEvent aEvent( VCLUnoHelper::createMouseEvent( rMouseEvent, *this ) ); + aEvent.ClickCount = 0; + const bool bSimpleMove = bool( rMouseEvent.GetMode() & MouseEventModifiers::SIMPLEMOVE ); + m_aMouseMotionListeners.notifyEach( bSimpleMove ? &awt::XMouseMotionListener::mouseMoved: &awt::XMouseMotionListener::mouseDragged, aEvent ); + } + } +} + +IMPL_LINK(FmXGridCell, OnKeyInput, const KeyEvent&, rEventData, void) +{ + if (!m_aKeyListeners.getLength()) + return; + + awt::KeyEvent aEvent(VCLUnoHelper::createKeyEvent(rEventData, *this)); + m_aKeyListeners.notifyEach(&awt::XKeyListener::keyPressed, aEvent); +} + +IMPL_LINK(FmXGridCell, OnKeyRelease, const KeyEvent&, rEventData, void) +{ + if (!m_aKeyListeners.getLength()) + return; + + awt::KeyEvent aEvent(VCLUnoHelper::createKeyEvent(rEventData, *this)); + m_aKeyListeners.notifyEach(&awt::XKeyListener::keyReleased, aEvent); +} + +void FmXDataCell::PaintFieldToCell(OutputDevice& rDev, const tools::Rectangle& rRect, + const Reference< css::sdb::XColumn >& _rxField, + const Reference< XNumberFormatter >& xFormatter) +{ + m_pCellControl->PaintFieldToCell( rDev, rRect, _rxField, xFormatter ); +} + +void FmXDataCell::UpdateFromColumn() +{ + Reference< css::sdb::XColumn > xField(m_pColumn->GetCurrentFieldValue()); + if (xField.is()) + m_pCellControl->UpdateFromField(xField, m_pColumn->GetParent().getNumberFormatter()); +} + +FmXTextCell::FmXTextCell( DbGridColumn* pColumn, std::unique_ptr<DbCellControl> pControl ) + :FmXDataCell( pColumn, std::move(pControl) ) + ,m_bIsMultiLineText(false) +{ +} + +void FmXTextCell::PaintFieldToCell(OutputDevice& rDev, + const tools::Rectangle& rRect, + const Reference< css::sdb::XColumn >& _rxField, + const Reference< XNumberFormatter >& xFormatter) +{ + DrawTextFlags nStyle = DrawTextFlags::Clip; + if ( ( rDev.GetOutDevType() == OUTDEV_WINDOW ) && !rDev.GetOwnerWindow()->IsEnabled() ) + nStyle |= DrawTextFlags::Disable; + + switch (m_pColumn->GetAlignment()) + { + case css::awt::TextAlign::RIGHT: + nStyle |= DrawTextFlags::Right; + break; + case css::awt::TextAlign::CENTER: + nStyle |= DrawTextFlags::Center; + break; + default: + nStyle |= DrawTextFlags::Left; + } + + if (!m_bIsMultiLineText) + nStyle |= DrawTextFlags::VCenter; + else + nStyle |= DrawTextFlags::Top | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak; + + try + { + const Color* pColor = nullptr; + OUString aText = GetText(_rxField, xFormatter, &pColor); + if (pColor != nullptr) + { + Color aOldTextColor( rDev.GetTextColor() ); + rDev.SetTextColor( *pColor ); + rDev.DrawText(rRect, aText, nStyle); + rDev.SetTextColor( aOldTextColor ); + } + else + rDev.DrawText(rRect, aText, nStyle); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION("svx.fmcomp", "PaintFieldToCell"); + } +} + +FmXEditCell::FmXEditCell( DbGridColumn* pColumn, std::unique_ptr<DbCellControl> pControl ) + :FmXTextCell( pColumn, std::move(pControl) ) + ,m_aTextListeners(m_aMutex) + ,m_aChangeListeners( m_aMutex ) + ,m_pEditImplementation( nullptr ) + ,m_bOwnEditImplementation( false ) +{ + + DbTextField* pTextField = dynamic_cast<DbTextField*>( m_pCellControl.get() ); + if ( pTextField ) + { + + m_pEditImplementation = pTextField->GetEditImplementation(); + m_bIsMultiLineText = pTextField->IsMultiLineEdit(); + } + else + { + m_pEditImplementation = new EntryImplementation(static_cast<EditControlBase&>(m_pCellControl->GetWindow())); + m_bOwnEditImplementation = true; + } + m_pEditImplementation->SetAuxModifyHdl(LINK(this, FmXEditCell, ModifyHdl)); +} + +FmXEditCell::~FmXEditCell() +{ + if (!OComponentHelper::rBHelper.bDisposed) + { + acquire(); + dispose(); + } +} + +// OComponentHelper +void FmXEditCell::disposing() +{ + css::lang::EventObject aEvt(*this); + m_aTextListeners.disposeAndClear(aEvt); + m_aChangeListeners.disposeAndClear(aEvt); + + if ( m_bOwnEditImplementation ) + delete m_pEditImplementation; + m_pEditImplementation = nullptr; + + FmXDataCell::disposing(); +} + +Any SAL_CALL FmXEditCell::queryAggregation( const css::uno::Type& _rType ) +{ + Any aReturn = FmXTextCell::queryAggregation( _rType ); + + if ( !aReturn.hasValue() ) + aReturn = FmXEditCell_Base::queryInterface( _rType ); + + return aReturn; +} + +Sequence< css::uno::Type > SAL_CALL FmXEditCell::getTypes( ) +{ + return ::comphelper::concatSequences( + FmXTextCell::getTypes(), + FmXEditCell_Base::getTypes() + ); +} + +IMPLEMENT_GET_IMPLEMENTATION_ID( FmXEditCell ) + +// css::awt::XTextComponent +void SAL_CALL FmXEditCell::addTextListener(const Reference< css::awt::XTextListener >& l) +{ + m_aTextListeners.addInterface( l ); +} + + +void SAL_CALL FmXEditCell::removeTextListener(const Reference< css::awt::XTextListener >& l) +{ + m_aTextListeners.removeInterface( l ); +} + +void SAL_CALL FmXEditCell::setText( const OUString& aText ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( m_pEditImplementation ) + { + m_pEditImplementation->SetText( aText ); + + // In Java, a textChanged is fired as well; not in VCL. + // css::awt::Toolkit must be Java-compliant... + onTextChanged(); + } +} + +void SAL_CALL FmXEditCell::insertText(const css::awt::Selection& rSel, const OUString& aText) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( m_pEditImplementation ) + { + m_pEditImplementation->SetSelection( Selection( rSel.Min, rSel.Max ) ); + m_pEditImplementation->ReplaceSelected( aText ); + } +} + +OUString SAL_CALL FmXEditCell::getText() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + OUString aText; + if ( m_pEditImplementation ) + { + if ( m_pEditImplementation->GetControl().IsVisible() && m_pColumn->GetParent().getDisplaySynchron()) + { + // if the display isn't sync with the cursor we can't ask the edit field + LineEnd eLineEndFormat = getModelLineEndSetting( m_pColumn->getModel() ); + aText = m_pEditImplementation->GetText( eLineEndFormat ); + } + else + { + Reference< css::sdb::XColumn > xField(m_pColumn->GetCurrentFieldValue()); + if (xField.is()) + aText = GetText(xField, m_pColumn->GetParent().getNumberFormatter()); + } + } + return aText; +} + +OUString SAL_CALL FmXEditCell::getSelectedText() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + OUString aText; + if ( m_pEditImplementation ) + { + LineEnd eLineEndFormat = m_pColumn ? getModelLineEndSetting( m_pColumn->getModel() ) : LINEEND_LF; + aText = m_pEditImplementation->GetSelected( eLineEndFormat ); + } + return aText; +} + +void SAL_CALL FmXEditCell::setSelection( const css::awt::Selection& aSelection ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( m_pEditImplementation ) + m_pEditImplementation->SetSelection( Selection( aSelection.Min, aSelection.Max ) ); +} + +css::awt::Selection SAL_CALL FmXEditCell::getSelection() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + Selection aSel; + if ( m_pEditImplementation ) + aSel = m_pEditImplementation->GetSelection(); + + return css::awt::Selection(aSel.Min(), aSel.Max()); +} + +sal_Bool SAL_CALL FmXEditCell::isEditable() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + return m_pEditImplementation && !m_pEditImplementation->IsReadOnly() && m_pEditImplementation->GetControl().IsEnabled(); +} + +void SAL_CALL FmXEditCell::setEditable( sal_Bool bEditable ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( m_pEditImplementation ) + m_pEditImplementation->SetReadOnly( !bEditable ); +} + +sal_Int16 SAL_CALL FmXEditCell::getMaxTextLen() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + return m_pEditImplementation ? m_pEditImplementation->GetMaxTextLen() : 0; +} + +void SAL_CALL FmXEditCell::setMaxTextLen( sal_Int16 nLen ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( m_pEditImplementation ) + m_pEditImplementation->SetMaxTextLen( nLen ); +} + +void SAL_CALL FmXEditCell::addChangeListener( const Reference< form::XChangeListener >& Listener ) +{ + m_aChangeListeners.addInterface( Listener ); +} + +void SAL_CALL FmXEditCell::removeChangeListener( const Reference< form::XChangeListener >& Listener ) +{ + m_aChangeListeners.removeInterface( Listener ); +} + +void FmXEditCell::onTextChanged() +{ + css::awt::TextEvent aEvent; + aEvent.Source = *this; + m_aTextListeners.notifyEach( &awt::XTextListener::textChanged, aEvent ); +} + +void FmXEditCell::onFocusGained( const awt::FocusEvent& _rEvent ) +{ + FmXTextCell::onFocusGained( _rEvent ); + m_sValueOnEnter = getText(); +} + +void FmXEditCell::onFocusLost( const awt::FocusEvent& _rEvent ) +{ + FmXTextCell::onFocusLost( _rEvent ); + + if ( getText() != m_sValueOnEnter ) + { + lang::EventObject aEvent( *this ); + m_aChangeListeners.notifyEach( &XChangeListener::changed, aEvent ); + } +} + +IMPL_LINK_NOARG(FmXEditCell, ModifyHdl, LinkParamNone*, void) +{ + if (m_aTextListeners.getLength()) + onTextChanged(); +} + +FmXCheckBoxCell::FmXCheckBoxCell( DbGridColumn* pColumn, std::unique_ptr<DbCellControl> pControl ) + :FmXDataCell( pColumn, std::move(pControl) ) + ,m_aItemListeners(m_aMutex) + ,m_aActionListeners( m_aMutex ) + ,m_pBox( & static_cast< CheckBoxControl& >( m_pCellControl->GetWindow() ) ) +{ + m_pBox->SetAuxModifyHdl(LINK(this, FmXCheckBoxCell, ModifyHdl)); +} + +FmXCheckBoxCell::~FmXCheckBoxCell() +{ + if (!OComponentHelper::rBHelper.bDisposed) + { + acquire(); + dispose(); + } +} + +// OComponentHelper +void FmXCheckBoxCell::disposing() +{ + css::lang::EventObject aEvt(*this); + m_aItemListeners.disposeAndClear(aEvt); + m_aActionListeners.disposeAndClear(aEvt); + + m_pBox->SetToggleHdl(Link<weld::CheckButton&,void>()); + m_pBox = nullptr; + + FmXDataCell::disposing(); +} + + +Any SAL_CALL FmXCheckBoxCell::queryAggregation( const css::uno::Type& _rType ) +{ + Any aReturn = FmXDataCell::queryAggregation( _rType ); + + if ( !aReturn.hasValue() ) + aReturn = FmXCheckBoxCell_Base::queryInterface( _rType ); + + return aReturn; +} + + +Sequence< css::uno::Type > SAL_CALL FmXCheckBoxCell::getTypes( ) +{ + return ::comphelper::concatSequences( + FmXDataCell::getTypes(), + FmXCheckBoxCell_Base::getTypes() + ); +} + + +IMPLEMENT_GET_IMPLEMENTATION_ID( FmXCheckBoxCell ) + +void SAL_CALL FmXCheckBoxCell::addItemListener( const Reference< css::awt::XItemListener >& l ) +{ + m_aItemListeners.addInterface( l ); +} + +void SAL_CALL FmXCheckBoxCell::removeItemListener( const Reference< css::awt::XItemListener >& l ) +{ + m_aItemListeners.removeInterface( l ); +} + +void SAL_CALL FmXCheckBoxCell::setState( sal_Int16 n ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if (m_pBox) + { + UpdateFromColumn(); + m_pBox->SetState( static_cast<TriState>(n) ); + } +} + +sal_Int16 SAL_CALL FmXCheckBoxCell::getState() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if (m_pBox) + { + UpdateFromColumn(); + return static_cast<sal_Int16>(m_pBox->GetState()); + } + return TRISTATE_INDET; +} + +void SAL_CALL FmXCheckBoxCell::enableTriState(sal_Bool b) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if (m_pBox) + m_pBox->EnableTriState( b ); +} + +void SAL_CALL FmXCheckBoxCell::addActionListener( const Reference< awt::XActionListener >& Listener ) +{ + m_aActionListeners.addInterface( Listener ); +} + + +void SAL_CALL FmXCheckBoxCell::removeActionListener( const Reference< awt::XActionListener >& Listener ) +{ + m_aActionListeners.removeInterface( Listener ); +} + +void SAL_CALL FmXCheckBoxCell::setLabel( const OUString& Label ) +{ + SolarMutexGuard aGuard; + if ( m_pColumn ) + { + DbGridControl& rGrid( m_pColumn->GetParent() ); + rGrid.SetColumnTitle( rGrid.GetColumnId( m_pColumn->GetFieldPos() ), Label ); + } +} + +void SAL_CALL FmXCheckBoxCell::setActionCommand( const OUString& Command ) +{ + m_aActionCommand = Command; +} + +IMPL_LINK_NOARG(FmXCheckBoxCell, ModifyHdl, LinkParamNone*, void) +{ + // check boxes are to be committed immediately (this holds for ordinary check box controls in + // documents, and this must hold for check boxes in grid columns, too + // 91210 - 22.08.2001 - frank.schoenheit@sun.com + m_pCellControl->Commit(); + + Reference< XWindow > xKeepAlive( this ); + if ( m_aItemListeners.getLength() && m_pBox ) + { + awt::ItemEvent aEvent; + aEvent.Source = *this; + aEvent.Highlighted = 0; + aEvent.Selected = m_pBox->GetState(); + m_aItemListeners.notifyEach( &awt::XItemListener::itemStateChanged, aEvent ); + } + if ( m_aActionListeners.getLength() ) + { + awt::ActionEvent aEvent; + aEvent.Source = *this; + aEvent.ActionCommand = m_aActionCommand; + m_aActionListeners.notifyEach( &awt::XActionListener::actionPerformed, aEvent ); + } +} + +FmXListBoxCell::FmXListBoxCell(DbGridColumn* pColumn, std::unique_ptr<DbCellControl> pControl) + : FmXTextCell(pColumn, std::move(pControl)) + , m_aItemListeners(m_aMutex) + , m_aActionListeners(m_aMutex) + , m_pBox(&static_cast<svt::ListBoxControl&>(m_pCellControl->GetWindow())) + , m_nLines(Application::GetSettings().GetStyleSettings().GetListBoxMaximumLineCount()) + , m_bMulti(false) +{ + m_pBox->SetAuxModifyHdl(LINK(this, FmXListBoxCell, ChangedHdl)); +} + +FmXListBoxCell::~FmXListBoxCell() +{ + if (!OComponentHelper::rBHelper.bDisposed) + { + acquire(); + dispose(); + } +} + +// OComponentHelper +void FmXListBoxCell::disposing() +{ + css::lang::EventObject aEvt(*this); + m_aItemListeners.disposeAndClear(aEvt); + m_aActionListeners.disposeAndClear(aEvt); + + m_pBox->SetAuxModifyHdl(Link<bool,void>()); + m_pBox = nullptr; + + FmXTextCell::disposing(); +} + +Any SAL_CALL FmXListBoxCell::queryAggregation( const css::uno::Type& _rType ) +{ + Any aReturn = FmXTextCell::queryAggregation(_rType); + + if ( !aReturn.hasValue() ) + aReturn = FmXListBoxCell_Base::queryInterface( _rType ); + + return aReturn; +} + +Sequence< css::uno::Type > SAL_CALL FmXListBoxCell::getTypes( ) +{ + return ::comphelper::concatSequences( + FmXTextCell::getTypes(), + FmXListBoxCell_Base::getTypes() + ); +} + +IMPLEMENT_GET_IMPLEMENTATION_ID( FmXListBoxCell ) + +void SAL_CALL FmXListBoxCell::addItemListener(const Reference< css::awt::XItemListener >& l) +{ + m_aItemListeners.addInterface( l ); +} + +void SAL_CALL FmXListBoxCell::removeItemListener(const Reference< css::awt::XItemListener >& l) +{ + m_aItemListeners.removeInterface( l ); +} + +void SAL_CALL FmXListBoxCell::addActionListener(const Reference< css::awt::XActionListener >& l) +{ + m_aActionListeners.addInterface( l ); +} + +void SAL_CALL FmXListBoxCell::removeActionListener(const Reference< css::awt::XActionListener >& l) +{ + m_aActionListeners.removeInterface( l ); +} + +void SAL_CALL FmXListBoxCell::addItem(const OUString& aItem, sal_Int16 nPos) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if (m_pBox) + { + weld::ComboBox& rBox = m_pBox->get_widget(); + rBox.insert_text(nPos, aItem); + } +} + +void SAL_CALL FmXListBoxCell::addItems(const css::uno::Sequence<OUString>& aItems, sal_Int16 nPos) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if (m_pBox) + { + weld::ComboBox& rBox = m_pBox->get_widget(); + sal_uInt16 nP = nPos; + for ( const auto& rItem : aItems ) + { + rBox.insert_text(nP, rItem); + if ( nPos != -1 ) // Not if 0xFFFF, because LIST_APPEND + nP++; + } + } +} + +void SAL_CALL FmXListBoxCell::removeItems(sal_Int16 nPos, sal_Int16 nCount) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if ( m_pBox ) + { + weld::ComboBox& rBox = m_pBox->get_widget(); + for ( sal_uInt16 n = nCount; n; ) + rBox.remove( nPos + (--n) ); + } +} + +sal_Int16 SAL_CALL FmXListBoxCell::getItemCount() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if (!m_pBox) + return 0; + weld::ComboBox& rBox = m_pBox->get_widget(); + return rBox.get_count(); +} + +OUString SAL_CALL FmXListBoxCell::getItem(sal_Int16 nPos) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if (!m_pBox) + return OUString(); + weld::ComboBox& rBox = m_pBox->get_widget(); + return rBox.get_text(nPos); +} + +css::uno::Sequence<OUString> SAL_CALL FmXListBoxCell::getItems() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + css::uno::Sequence<OUString> aSeq; + if (m_pBox) + { + weld::ComboBox& rBox = m_pBox->get_widget(); + const sal_Int32 nEntries = rBox.get_count(); + aSeq = css::uno::Sequence<OUString>( nEntries ); + for ( sal_Int32 n = nEntries; n; ) + { + --n; + aSeq.getArray()[n] = rBox.get_text( n ); + } + } + return aSeq; +} + +sal_Int16 SAL_CALL FmXListBoxCell::getSelectedItemPos() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if (m_pBox) + { + UpdateFromColumn(); + weld::ComboBox& rBox = m_pBox->get_widget(); + sal_Int32 nPos = rBox.get_active(); + if (nPos > SHRT_MAX || nPos < SHRT_MIN) + throw std::out_of_range("awt::XListBox::getSelectedItemPos can only return a short"); + return nPos; + } + return 0; +} + +Sequence< sal_Int16 > SAL_CALL FmXListBoxCell::getSelectedItemsPos() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if (m_pBox) + { + UpdateFromColumn(); + weld::ComboBox& rBox = m_pBox->get_widget(); + auto nActive = rBox.get_active(); + if (nActive != -1) + { + return { o3tl::narrowing<short>(nActive) }; + } + } + return {}; +} + +OUString SAL_CALL FmXListBoxCell::getSelectedItem() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + OUString aItem; + + if (m_pBox) + { + UpdateFromColumn(); + weld::ComboBox& rBox = m_pBox->get_widget(); + aItem = rBox.get_active_text(); + } + + return aItem; +} + +css::uno::Sequence<OUString> SAL_CALL FmXListBoxCell::getSelectedItems() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if (m_pBox) + { + UpdateFromColumn(); + weld::ComboBox& rBox = m_pBox->get_widget(); + auto nActive = rBox.get_active(); + if (nActive != -1) + { + return { rBox.get_text(nActive) }; + } + } + return {}; +} + +void SAL_CALL FmXListBoxCell::selectItemPos(sal_Int16 nPos, sal_Bool bSelect) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if (m_pBox) + { + weld::ComboBox& rBox = m_pBox->get_widget(); + if (bSelect) + rBox.set_active(nPos); + else if (nPos == rBox.get_active()) + rBox.set_active(-1); + } +} + +void SAL_CALL FmXListBoxCell::selectItemsPos(const Sequence< sal_Int16 >& aPositions, sal_Bool bSelect) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if (m_pBox) + { + weld::ComboBox& rBox = m_pBox->get_widget(); + for ( sal_uInt16 n = static_cast<sal_uInt16>(aPositions.getLength()); n; ) + { + auto nPos = static_cast<sal_uInt16>(aPositions.getConstArray()[--n]); + if (bSelect) + rBox.set_active(nPos); + else if (nPos == rBox.get_active()) + rBox.set_active(-1); + } + } +} + +void SAL_CALL FmXListBoxCell::selectItem(const OUString& aItem, sal_Bool bSelect) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if (m_pBox) + { + weld::ComboBox& rBox = m_pBox->get_widget(); + auto nPos = rBox.find_text(aItem); + if (bSelect) + rBox.set_active(nPos); + else if (nPos == rBox.get_active()) + rBox.set_active(-1); + } +} + +sal_Bool SAL_CALL FmXListBoxCell::isMutipleMode() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + return m_bMulti; +} + +void SAL_CALL FmXListBoxCell::setMultipleMode(sal_Bool bMulti) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + m_bMulti = bMulti; +} + +sal_Int16 SAL_CALL FmXListBoxCell::getDropDownLineCount() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + return m_nLines; +} + +void SAL_CALL FmXListBoxCell::setDropDownLineCount(sal_Int16 nLines) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + m_nLines = nLines; // just store it to return it +} + +void SAL_CALL FmXListBoxCell::makeVisible(sal_Int16 /*nEntry*/) +{ +} + +IMPL_LINK(FmXListBoxCell, ChangedHdl, bool, bInteractive, void) +{ + if (!m_pBox) + return; + + weld::ComboBox& rBox = m_pBox->get_widget(); + + if (bInteractive && !rBox.changed_by_direct_pick()) + return; + + OnDoubleClick(); + + css::awt::ItemEvent aEvent; + aEvent.Source = *this; + aEvent.Highlighted = 0; + + // with multiple selection 0xFFFF, otherwise the ID + aEvent.Selected = (rBox.get_active() != -1 ) + ? rBox.get_active() : 0xFFFF; + + m_aItemListeners.notifyEach( &awt::XItemListener::itemStateChanged, aEvent ); +} + +void FmXListBoxCell::OnDoubleClick() +{ + css::awt::ActionEvent aEvent; + aEvent.Source = *this; + weld::ComboBox& rBox = m_pBox->get_widget(); + aEvent.ActionCommand = rBox.get_active_text(); + + m_aActionListeners.notifyEach( &css::awt::XActionListener::actionPerformed, aEvent ); +} + +FmXComboBoxCell::FmXComboBoxCell( DbGridColumn* pColumn, std::unique_ptr<DbCellControl> pControl ) + :FmXTextCell( pColumn, std::move(pControl) ) + ,m_aItemListeners( m_aMutex ) + ,m_aActionListeners( m_aMutex ) + ,m_pComboBox(&static_cast<ComboBoxControl&>(m_pCellControl->GetWindow())) + ,m_nLines(Application::GetSettings().GetStyleSettings().GetListBoxMaximumLineCount()) +{ + m_pComboBox->SetAuxModifyHdl(LINK(this, FmXComboBoxCell, ChangedHdl)); +} + +FmXComboBoxCell::~FmXComboBoxCell() +{ + if ( !OComponentHelper::rBHelper.bDisposed ) + { + acquire(); + dispose(); + } + +} + +void FmXComboBoxCell::disposing() +{ + css::lang::EventObject aEvt(*this); + m_aItemListeners.disposeAndClear(aEvt); + m_aActionListeners.disposeAndClear(aEvt); + + m_pComboBox->SetAuxModifyHdl(Link<bool,void>()); + m_pComboBox = nullptr; + + FmXTextCell::disposing(); +} + +Any SAL_CALL FmXComboBoxCell::queryAggregation( const css::uno::Type& _rType ) +{ + Any aReturn = FmXTextCell::queryAggregation(_rType); + + if ( !aReturn.hasValue() ) + aReturn = FmXComboBoxCell_Base::queryInterface( _rType ); + + return aReturn; +} + +Sequence< Type > SAL_CALL FmXComboBoxCell::getTypes( ) +{ + return ::comphelper::concatSequences( + FmXTextCell::getTypes(), + FmXComboBoxCell_Base::getTypes() + ); +} + +IMPLEMENT_GET_IMPLEMENTATION_ID( FmXComboBoxCell ) + +void SAL_CALL FmXComboBoxCell::addItemListener(const Reference< awt::XItemListener >& l) +{ + m_aItemListeners.addInterface( l ); +} + +void SAL_CALL FmXComboBoxCell::removeItemListener(const Reference< awt::XItemListener >& l) +{ + m_aItemListeners.removeInterface( l ); +} + +void SAL_CALL FmXComboBoxCell::addActionListener(const Reference< awt::XActionListener >& l) +{ + m_aActionListeners.addInterface( l ); +} + + +void SAL_CALL FmXComboBoxCell::removeActionListener(const Reference< awt::XActionListener >& l) +{ + m_aActionListeners.removeInterface( l ); +} + +void SAL_CALL FmXComboBoxCell::addItem( const OUString& Item, sal_Int16 Pos ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if (!m_pComboBox) + return; + weld::ComboBox& rBox = m_pComboBox->get_widget(); + rBox.insert_text(Pos, Item); +} + +void SAL_CALL FmXComboBoxCell::addItems( const Sequence< OUString >& Items, sal_Int16 Pos ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if (!m_pComboBox) + return; + weld::ComboBox& rBox = m_pComboBox->get_widget(); + sal_uInt16 nP = Pos; + for ( const auto& rItem : Items ) + { + rBox.insert_text(nP, rItem); + if ( Pos != -1 ) + nP++; + } +} + +void SAL_CALL FmXComboBoxCell::removeItems( sal_Int16 Pos, sal_Int16 Count ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if (!m_pComboBox) + return; + weld::ComboBox& rBox = m_pComboBox->get_widget(); + for ( sal_uInt16 n = Count; n; ) + rBox.remove( Pos + (--n) ); +} + +sal_Int16 SAL_CALL FmXComboBoxCell::getItemCount() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if (!m_pComboBox) + return 0; + weld::ComboBox& rBox = m_pComboBox->get_widget(); + return rBox.get_count(); +} + +OUString SAL_CALL FmXComboBoxCell::getItem( sal_Int16 Pos ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if (!m_pComboBox) + return OUString(); + weld::ComboBox& rBox = m_pComboBox->get_widget(); + return rBox.get_text(Pos); +} + +Sequence< OUString > SAL_CALL FmXComboBoxCell::getItems() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + Sequence< OUString > aItems; + if (m_pComboBox) + { + weld::ComboBox& rBox = m_pComboBox->get_widget(); + const sal_Int32 nEntries = rBox.get_count(); + aItems.realloc( nEntries ); + OUString* pItem = aItems.getArray(); + for ( sal_Int32 n=0; n<nEntries; ++n, ++pItem ) + *pItem = rBox.get_text(n); + } + return aItems; +} + +sal_Int16 SAL_CALL FmXComboBoxCell::getDropDownLineCount() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + return m_nLines; +} + +void SAL_CALL FmXComboBoxCell::setDropDownLineCount(sal_Int16 nLines) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + m_nLines = nLines; // just store it to return it +} + +IMPL_LINK(FmXComboBoxCell, ChangedHdl, bool, bInteractive, void) +{ + if (!m_pComboBox) + return; + + weld::ComboBox& rComboBox = m_pComboBox->get_widget(); + + if (bInteractive && !rComboBox.changed_by_direct_pick()) + return; + + awt::ItemEvent aEvent; + aEvent.Source = *this; + aEvent.Highlighted = 0; + + // with invalid selection 0xFFFF, otherwise the position + aEvent.Selected = ( rComboBox.get_active() != -1 ) + ? rComboBox.get_active() + : 0xFFFF; + m_aItemListeners.notifyEach( &awt::XItemListener::itemStateChanged, aEvent ); +} + +FmXFilterCell::FmXFilterCell(DbGridColumn* pColumn, std::unique_ptr<DbFilterField> pControl ) + :FmXGridCell( pColumn, std::move(pControl) ) + ,m_aTextListeners(m_aMutex) +{ + static_cast<DbFilterField*>(m_pCellControl.get())->SetCommitHdl( LINK( this, FmXFilterCell, OnCommit ) ); +} + +FmXFilterCell::~FmXFilterCell() +{ + if (!OComponentHelper::rBHelper.bDisposed) + { + acquire(); + dispose(); + } + +} + +void FmXFilterCell::PaintCell( OutputDevice& rDev, const tools::Rectangle& rRect ) +{ + static_cast< DbFilterField* >( m_pCellControl.get() )->PaintCell( rDev, rRect ); +} + +// OComponentHelper + +void FmXFilterCell::disposing() +{ + css::lang::EventObject aEvt(*this); + m_aTextListeners.disposeAndClear(aEvt); + + static_cast<DbFilterField*>(m_pCellControl.get())->SetCommitHdl(Link<DbFilterField&,void>()); + + FmXGridCell::disposing(); +} + + +Any SAL_CALL FmXFilterCell::queryAggregation( const css::uno::Type& _rType ) +{ + Any aReturn = FmXGridCell::queryAggregation(_rType); + + if ( !aReturn.hasValue() ) + aReturn = FmXFilterCell_Base::queryInterface( _rType ); + + return aReturn; +} + + +Sequence< css::uno::Type > SAL_CALL FmXFilterCell::getTypes( ) +{ + return ::comphelper::concatSequences( + FmXGridCell::getTypes(), + FmXFilterCell_Base::getTypes() + ); +} + + +IMPLEMENT_GET_IMPLEMENTATION_ID( FmXFilterCell ) + +// css::awt::XTextComponent + +void SAL_CALL FmXFilterCell::addTextListener(const Reference< css::awt::XTextListener >& l) +{ + m_aTextListeners.addInterface( l ); +} + + +void SAL_CALL FmXFilterCell::removeTextListener(const Reference< css::awt::XTextListener >& l) +{ + m_aTextListeners.removeInterface( l ); +} + +void SAL_CALL FmXFilterCell::setText( const OUString& aText ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + static_cast<DbFilterField*>(m_pCellControl.get())->SetText(aText); +} + +void SAL_CALL FmXFilterCell::insertText( const css::awt::Selection& /*rSel*/, const OUString& /*aText*/ ) +{ +} + +OUString SAL_CALL FmXFilterCell::getText() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + return static_cast<DbFilterField*>(m_pCellControl.get())->GetText(); +} + +OUString SAL_CALL FmXFilterCell::getSelectedText() +{ + return getText(); +} + +void SAL_CALL FmXFilterCell::setSelection( const css::awt::Selection& /*aSelection*/ ) +{ +} + +css::awt::Selection SAL_CALL FmXFilterCell::getSelection() +{ + return css::awt::Selection(); +} + +sal_Bool SAL_CALL FmXFilterCell::isEditable() +{ + return true; +} + +void SAL_CALL FmXFilterCell::setEditable( sal_Bool /*bEditable*/ ) +{ +} + +sal_Int16 SAL_CALL FmXFilterCell::getMaxTextLen() +{ + return 0; +} + +void SAL_CALL FmXFilterCell::setMaxTextLen( sal_Int16 /*nLen*/ ) +{ +} + +IMPL_LINK_NOARG(FmXFilterCell, OnCommit, DbFilterField&, void) +{ + css::awt::TextEvent aEvt; + aEvt.Source = *this; + m_aTextListeners.notifyEach( &css::awt::XTextListener::textChanged, aEvt ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/fmcomp/gridcols.cxx b/svx/source/fmcomp/gridcols.cxx new file mode 100644 index 0000000000..42b9a76379 --- /dev/null +++ b/svx/source/fmcomp/gridcols.cxx @@ -0,0 +1,103 @@ +/* -*- 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 <gridcols.hxx> +#include <tools/debug.hxx> +#include <fmservs.hxx> +#include <com/sun/star/uno/Sequence.hxx> + +using namespace ::com::sun::star::uno; + + +static const css::uno::Sequence<OUString>& getColumnTypes() +{ + static css::uno::Sequence<OUString> aColumnTypes = []() + { + css::uno::Sequence<OUString> tmp(10); + OUString* pNames = tmp.getArray(); + pNames[TYPE_CHECKBOX] = FM_COL_CHECKBOX; + pNames[TYPE_COMBOBOX] = FM_COL_COMBOBOX; + pNames[TYPE_CURRENCYFIELD] = FM_COL_CURRENCYFIELD; + pNames[TYPE_DATEFIELD] = FM_COL_DATEFIELD; + pNames[TYPE_FORMATTEDFIELD] = FM_COL_FORMATTEDFIELD; + pNames[TYPE_LISTBOX] = FM_COL_LISTBOX; + pNames[TYPE_NUMERICFIELD] = FM_COL_NUMERICFIELD; + pNames[TYPE_PATTERNFIELD] = FM_COL_PATTERNFIELD; + pNames[TYPE_TEXTFIELD] = FM_COL_TEXTFIELD; + pNames[TYPE_TIMEFIELD] = FM_COL_TIMEFIELD; + return tmp; + }(); + return aColumnTypes; +} + + +extern "C" { + +// comparison of PropertyInfo +static int NameCompare(const void* pFirst, const void* pSecond) +{ + return static_cast<OUString const *>(pFirst)->compareTo(*static_cast<OUString const *>(pSecond)); +} + +} + +namespace +{ + + sal_Int32 lcl_findPos(const OUString& aStr, const Sequence< OUString>& rList) + { + const OUString* pStrList = rList.getConstArray(); + OUString* pResult = static_cast<OUString*>(bsearch(&aStr, static_cast<void const *>(pStrList), rList.getLength(), sizeof(OUString), + &NameCompare)); + + if (pResult) + return (pResult - pStrList); + else + return -1; + } +} + + +sal_Int32 getColumnTypeByModelName(const OUString& aModelName) +{ + static constexpr OUString aModelPrefix(u"com.sun.star.form.component."_ustr); + static constexpr OUString aCompatibleModelPrefix(u"stardiv.one.form.component."_ustr); + + sal_Int32 nTypeId = -1; + if (aModelName == FM_COMPONENT_EDIT) + nTypeId = TYPE_TEXTFIELD; + else + { + sal_Int32 nPrefixPos = aModelName.indexOf(aModelPrefix); +#ifdef DBG_UTIL + sal_Int32 nCompatiblePrefixPos = aModelName.indexOf(aCompatibleModelPrefix); + DBG_ASSERT( (nPrefixPos != -1) || (nCompatiblePrefixPos != -1), "::getColumnTypeByModelName() : wrong service!"); +#endif + + OUString aColumnType = (nPrefixPos != -1) + ? aModelName.copy(aModelPrefix.getLength()) + : aModelName.copy(aCompatibleModelPrefix.getLength()); + + const css::uno::Sequence<OUString>& rColumnTypes = getColumnTypes(); + nTypeId = lcl_findPos(aColumnType, rColumnTypes); + } + return nTypeId; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/fmcomp/gridctrl.cxx b/svx/source/fmcomp/gridctrl.cxx new file mode 100644 index 0000000000..627c41c546 --- /dev/null +++ b/svx/source/fmcomp/gridctrl.cxx @@ -0,0 +1,3365 @@ +/* -*- 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 <sal/log.hxx> +#include <helpids.h> +#include <svx/gridctrl.hxx> +#include <gridcell.hxx> +#include <svx/fmtools.hxx> +#include <svtools/stringtransfer.hxx> +#include <connectivity/dbtools.hxx> +#include <connectivity/dbconversion.hxx> + +#include <fmprop.hxx> +#include <com/sun/star/sdbc/ResultSetConcurrency.hpp> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/sdb/XResultSetAccess.hpp> +#include <com/sun/star/sdb/RowChangeAction.hpp> +#include <com/sun/star/sdb/XRowsChangeBroadcaster.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/sdbc/XResultSetUpdate.hpp> +#include <com/sun/star/sdbc/XRowSet.hpp> +#include <com/sun/star/sdbcx/Privilege.hpp> +#include <com/sun/star/util/NumberFormatter.hpp> +#include <com/sun/star/util/XNumberFormatsSupplier.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyChangeEvent.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <comphelper/diagnose_ex.hxx> +#include <tools/debug.hxx> +#include <vcl/settings.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/weldutils.hxx> + +#include <svx/strings.hrc> + +#include <svx/dialmgr.hxx> +#include <sdbdatacolumn.hxx> + +#include <comphelper/property.hxx> +#include <comphelper/types.hxx> +#include <cppuhelper/implbase.hxx> + +#include <algorithm> +#include <cstdlib> +#include <memory> + +using namespace ::dbtools; +using namespace ::dbtools::DBTypeConversion; +using namespace ::svxform; +using namespace ::svt; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdbcx; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::datatransfer; +using namespace ::com::sun::star::container; +using namespace com::sun::star::accessibility; + +#define ROWSTATUS(row) (!row.is() ? "NULL" : row->GetStatus() == GridRowStatus::Clean ? "CLEAN" : row->GetStatus() == GridRowStatus::Modified ? "MODIFIED" : row->GetStatus() == GridRowStatus::Deleted ? "DELETED" : "INVALID") + +constexpr auto DEFAULT_BROWSE_MODE = + BrowserMode::COLUMNSELECTION + | BrowserMode::MULTISELECTION + | BrowserMode::KEEPHIGHLIGHT + | BrowserMode::TRACKING_TIPS + | BrowserMode::HLINES + | BrowserMode::VLINES + | BrowserMode::HEADERBAR_NEW; + +class RowSetEventListener : public ::cppu::WeakImplHelper<XRowsChangeListener> +{ + VclPtr<DbGridControl> m_pControl; +public: + explicit RowSetEventListener(DbGridControl* i_pControl) : m_pControl(i_pControl) + { + } + +private: + // XEventListener + virtual void SAL_CALL disposing(const css::lang::EventObject& /*i_aEvt*/) override + { + } + virtual void SAL_CALL rowsChanged(const css::sdb::RowsChangeEvent& i_aEvt) override + { + if ( i_aEvt.Action != RowChangeAction::UPDATE ) + return; + + ::DbGridControl::GrantControlAccess aAccess; + CursorWrapper* pSeek = m_pControl->GetSeekCursor(aAccess); + const DbGridRowRef& rSeekRow = m_pControl->GetSeekRow(aAccess); + for(const Any& rBookmark : i_aEvt.Bookmarks) + { + pSeek->moveToBookmark(rBookmark); + // get the data + rSeekRow->SetState(pSeek, true); + sal_Int32 nSeekPos = pSeek->getRow() - 1; + m_pControl->SetSeekPos(nSeekPos,aAccess); + m_pControl->RowModified(nSeekPos); + } + } +}; + +class GridFieldValueListener : protected ::comphelper::OPropertyChangeListener +{ + osl::Mutex m_aMutex; + DbGridControl& m_rParent; + rtl::Reference<::comphelper::OPropertyChangeMultiplexer> m_pRealListener; + sal_uInt16 m_nId; + sal_Int16 m_nSuspended; + bool m_bDisposed : 1; + +public: + GridFieldValueListener(DbGridControl& _rParent, const Reference< XPropertySet >& xField, sal_uInt16 _nId); + virtual ~GridFieldValueListener() override; + + virtual void _propertyChanged(const PropertyChangeEvent& evt) override; + + void suspend() { ++m_nSuspended; } + void resume() { --m_nSuspended; } + + void dispose(); +}; + +GridFieldValueListener::GridFieldValueListener(DbGridControl& _rParent, const Reference< XPropertySet >& _rField, sal_uInt16 _nId) + :OPropertyChangeListener(m_aMutex) + ,m_rParent(_rParent) + ,m_nId(_nId) + ,m_nSuspended(0) + ,m_bDisposed(false) +{ + if (_rField.is()) + { + m_pRealListener = new ::comphelper::OPropertyChangeMultiplexer(this, _rField); + m_pRealListener->addProperty(FM_PROP_VALUE); + } +} + +GridFieldValueListener::~GridFieldValueListener() +{ + dispose(); +} + +void GridFieldValueListener::_propertyChanged(const PropertyChangeEvent& /*_evt*/) +{ + DBG_ASSERT(m_nSuspended>=0, "GridFieldValueListener::_propertyChanged : resume > suspend !"); + if (m_nSuspended <= 0) + m_rParent.FieldValueChanged(m_nId); +} + +void GridFieldValueListener::dispose() +{ + if (m_bDisposed) + { + DBG_ASSERT(!m_pRealListener, "GridFieldValueListener::dispose : inconsistent !"); + return; + } + + if (m_pRealListener.is()) + { + m_pRealListener->dispose(); + m_pRealListener.clear(); + } + + m_bDisposed = true; + m_rParent.FieldListenerDisposing(m_nId); +} + +class DisposeListenerGridBridge : public FmXDisposeListener +{ + DbGridControl& m_rParent; + rtl::Reference<FmXDisposeMultiplexer> m_xRealListener; + +public: + DisposeListenerGridBridge( DbGridControl& _rParent, const Reference< XComponent >& _rxObject); + virtual ~DisposeListenerGridBridge() override; + + virtual void disposing(sal_Int16 _nId) override { m_rParent.disposing(_nId); } +}; + +DisposeListenerGridBridge::DisposeListenerGridBridge(DbGridControl& _rParent, const Reference< XComponent >& _rxObject) + :FmXDisposeListener() + ,m_rParent(_rParent) +{ + + if (_rxObject.is()) + { + m_xRealListener = new FmXDisposeMultiplexer(this, _rxObject); + } +} + +DisposeListenerGridBridge::~DisposeListenerGridBridge() +{ + if (m_xRealListener.is()) + { + m_xRealListener->dispose(); + } +} + +const DbGridControlNavigationBarState ControlMap[] = + { + DbGridControlNavigationBarState::Text, + DbGridControlNavigationBarState::Absolute, + DbGridControlNavigationBarState::Of, + DbGridControlNavigationBarState::Count, + DbGridControlNavigationBarState::First, + DbGridControlNavigationBarState::Next, + DbGridControlNavigationBarState::Prev, + DbGridControlNavigationBarState::Last, + DbGridControlNavigationBarState::New, + DbGridControlNavigationBarState::NONE + }; + +bool CompareBookmark(const Any& aLeft, const Any& aRight) +{ + return aLeft == aRight; +} + +class FmXGridSourcePropListener : public ::comphelper::OPropertyChangeListener +{ + VclPtr<DbGridControl> m_pParent; + + // a DbGridControl has no mutex, so we use our own as the base class expects one + osl::Mutex m_aMutex; + sal_Int16 m_nSuspended; + +public: + explicit FmXGridSourcePropListener(DbGridControl* _pParent); + + void suspend() { ++m_nSuspended; } + void resume() { --m_nSuspended; } + + virtual void _propertyChanged(const PropertyChangeEvent& evt) override; +}; + +FmXGridSourcePropListener::FmXGridSourcePropListener(DbGridControl* _pParent) + :OPropertyChangeListener(m_aMutex) + ,m_pParent(_pParent) + ,m_nSuspended(0) +{ + DBG_ASSERT(m_pParent, "FmXGridSourcePropListener::FmXGridSourcePropListener : invalid parent !"); +} + +void FmXGridSourcePropListener::_propertyChanged(const PropertyChangeEvent& evt) +{ + DBG_ASSERT(m_nSuspended>=0, "FmXGridSourcePropListener::_propertyChanged : resume > suspend !"); + if (m_nSuspended <= 0) + m_pParent->DataSourcePropertyChanged(evt); +} + +const int nReserveNumDigits = 7; + +NavigationBar::AbsolutePos::AbsolutePos(std::unique_ptr<weld::Entry> xEntry, NavigationBar* pBar) + : RecordItemWindowBase(std::move(xEntry)) + , m_xParent(pBar) +{ +} + +bool NavigationBar::AbsolutePos::DoKeyInput(const KeyEvent& rEvt) +{ + if (rEvt.GetKeyCode() == KEY_TAB) + { + m_xParent->GetParent()->GrabFocus(); + return true; + } + return RecordItemWindowBase::DoKeyInput(rEvt); +} + +void NavigationBar::AbsolutePos::PositionFired(sal_Int64 nRecord) +{ + m_xParent->PositionDataSource(nRecord); + m_xParent->InvalidateState(DbGridControlNavigationBarState::Absolute); +} + +void NavigationBar::PositionDataSource(sal_Int32 nRecord) +{ + if (m_bPositioning) + return; + // the MoveToPosition may cause a LoseFocus which would lead to a second MoveToPosition, + // so protect against this recursion + m_bPositioning = true; + static_cast<DbGridControl*>(GetParent())->MoveToPosition(nRecord - 1); + m_bPositioning = false; +} + +NavigationBar::NavigationBar(vcl::Window* pParent) + : InterimItemWindow(pParent, "svx/ui/navigationbar.ui", "NavigationBar") + , m_xRecordText(m_xBuilder->weld_label("recordtext")) + , m_xAbsolute(new NavigationBar::AbsolutePos(m_xBuilder->weld_entry("entry-noframe"), this)) + , m_xRecordOf(m_xBuilder->weld_label("recordof")) + , m_xRecordCount(m_xBuilder->weld_label("recordcount")) + , m_xFirstBtn(m_xBuilder->weld_button("first")) + , m_xPrevBtn(m_xBuilder->weld_button("prev")) + , m_xNextBtn(m_xBuilder->weld_button("next")) + , m_xLastBtn(m_xBuilder->weld_button("last")) + , m_xNewBtn(m_xBuilder->weld_button("new")) + , m_xPrevRepeater(std::make_shared<weld::ButtonPressRepeater>(*m_xPrevBtn, LINK(this,NavigationBar,OnClick))) + , m_xNextRepeater(std::make_shared<weld::ButtonPressRepeater>(*m_xNextBtn, LINK(this,NavigationBar,OnClick))) + , m_nCurrentPos(-1) + , m_bPositioning(false) +{ + vcl::Font aApplFont(Application::GetSettings().GetStyleSettings().GetToolFont()); + m_xAbsolute->set_font(aApplFont); + aApplFont.SetTransparent(true); + m_xRecordText->set_font(aApplFont); + m_xRecordOf->set_font(aApplFont); + m_xRecordCount->set_font(aApplFont); + + m_xFirstBtn->set_help_id(HID_GRID_TRAVEL_FIRST); + m_xPrevBtn->set_help_id(HID_GRID_TRAVEL_PREV); + m_xNextBtn->set_help_id(HID_GRID_TRAVEL_NEXT); + m_xLastBtn->set_help_id(HID_GRID_TRAVEL_LAST); + m_xNewBtn->set_help_id(HID_GRID_TRAVEL_NEW); + m_xAbsolute->set_help_id(HID_GRID_TRAVEL_ABSOLUTE); + m_xRecordCount->set_help_id(HID_GRID_NUMBEROFRECORDS); + + // set handlers for buttons + m_xFirstBtn->connect_clicked(LINK(this,NavigationBar,OnClick)); + m_xLastBtn->connect_clicked(LINK(this,NavigationBar,OnClick)); + m_xNewBtn->connect_clicked(LINK(this,NavigationBar,OnClick)); + + m_xRecordText->set_label(SvxResId(RID_STR_REC_TEXT)); + m_xRecordOf->set_label(SvxResId(RID_STR_REC_FROM_TEXT)); + m_xRecordCount->set_label(OUString('?')); + + auto nReserveWidth = m_xRecordCount->get_approximate_digit_width() * nReserveNumDigits; + m_xAbsolute->GetWidget()->set_size_request(nReserveWidth, -1); + m_xRecordCount->set_size_request(nReserveWidth, -1); +} + +NavigationBar::~NavigationBar() +{ + disposeOnce(); +} + +void NavigationBar::dispose() +{ + m_xRecordText.reset(); + m_xAbsolute.reset(); + m_xRecordOf.reset(); + m_xRecordCount.reset(); + m_xFirstBtn.reset(); + m_xPrevBtn.reset(); + m_xNextBtn.reset(); + m_xLastBtn.reset(); + m_xNewBtn.reset(); + InterimItemWindow::dispose(); +} + +sal_uInt16 NavigationBar::ArrangeControls() +{ + return m_xContainer->get_preferred_size().Width(); +} + +IMPL_LINK(NavigationBar, OnClick, weld::Button&, rButton, void) +{ + DbGridControl* pParent = static_cast<DbGridControl*>(GetParent()); + + if (pParent->m_aMasterSlotExecutor.IsSet()) + { + bool lResult = false; + if (&rButton == m_xFirstBtn.get()) + lResult = pParent->m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::First); + else if( &rButton == m_xPrevBtn.get() ) + lResult = pParent->m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::Prev); + else if( &rButton == m_xNextBtn.get() ) + lResult = pParent->m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::Next); + else if( &rButton == m_xLastBtn.get() ) + lResult = pParent->m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::Last); + else if( &rButton == m_xNewBtn.get() ) + lResult = pParent->m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::New); + + if (lResult) + // the link already handled it + return; + } + + if (&rButton == m_xFirstBtn.get()) + pParent->MoveToFirst(); + else if( &rButton == m_xPrevBtn.get() ) + pParent->MoveToPrev(); + else if( &rButton == m_xNextBtn.get() ) + pParent->MoveToNext(); + else if( &rButton == m_xLastBtn.get() ) + pParent->MoveToLast(); + else if( &rButton == m_xNewBtn.get() ) + pParent->AppendNew(); +} + +void NavigationBar::InvalidateAll(sal_Int32 nCurrentPos, bool bAll) +{ + if (!(m_nCurrentPos != nCurrentPos || nCurrentPos < 0 || bAll)) + return; + + DbGridControl* pParent = static_cast<DbGridControl*>(GetParent()); + + sal_Int32 nAdjustedRowCount = pParent->GetRowCount() - ((pParent->GetOptions() & DbGridControlOptions::Insert) ? 2 : 1); + + // check if everything needs to be invalidated + bAll = bAll || m_nCurrentPos <= 0; + bAll = bAll || nCurrentPos <= 0; + bAll = bAll || m_nCurrentPos >= nAdjustedRowCount; + bAll = bAll || nCurrentPos >= nAdjustedRowCount; + + if ( bAll ) + { + m_nCurrentPos = nCurrentPos; + int i = 0; + while (ControlMap[i] != DbGridControlNavigationBarState::NONE) + SetState(ControlMap[i++]); + } + else // is in the center + { + m_nCurrentPos = nCurrentPos; + SetState(DbGridControlNavigationBarState::Count); + SetState(DbGridControlNavigationBarState::Absolute); + } +} + +bool NavigationBar::GetState(DbGridControlNavigationBarState nWhich) const +{ + DbGridControl* pParent = static_cast<DbGridControl*>(GetParent()); + + if (!pParent->IsOpen() || pParent->IsDesignMode() || !pParent->IsEnabled() + || pParent->IsFilterMode() ) + return false; + else + { + // check if we have a master state provider + if (pParent->m_aMasterStateProvider.IsSet()) + { + tools::Long nState = pParent->m_aMasterStateProvider.Call( nWhich ); + if (nState>=0) + return (nState>0); + } + + bool bAvailable = true; + + switch (nWhich) + { + case DbGridControlNavigationBarState::First: + case DbGridControlNavigationBarState::Prev: + bAvailable = m_nCurrentPos > 0; + break; + case DbGridControlNavigationBarState::Next: + if(pParent->m_bRecordCountFinal) + { + bAvailable = m_nCurrentPos < pParent->GetRowCount() - 1; + if (!bAvailable && pParent->GetOptions() & DbGridControlOptions::Insert) + bAvailable = (m_nCurrentPos == pParent->GetRowCount() - 2) && pParent->IsModified(); + } + break; + case DbGridControlNavigationBarState::Last: + if(pParent->m_bRecordCountFinal) + { + if (pParent->GetOptions() & DbGridControlOptions::Insert) + bAvailable = pParent->IsCurrentAppending() ? pParent->GetRowCount() > 1 : + m_nCurrentPos != pParent->GetRowCount() - 2; + else + bAvailable = m_nCurrentPos != pParent->GetRowCount() - 1; + } + break; + case DbGridControlNavigationBarState::New: + bAvailable = (pParent->GetOptions() & DbGridControlOptions::Insert) && pParent->GetRowCount() && m_nCurrentPos < pParent->GetRowCount() - 1; + break; + case DbGridControlNavigationBarState::Absolute: + bAvailable = pParent->GetRowCount() > 0; + break; + default: break; + } + return bAvailable; + } +} + +void NavigationBar::SetState(DbGridControlNavigationBarState nWhich) +{ + bool bAvailable = GetState(nWhich); + DbGridControl* pParent = static_cast<DbGridControl*>(GetParent()); + weld::Widget* pWnd = nullptr; + switch (nWhich) + { + case DbGridControlNavigationBarState::First: + pWnd = m_xFirstBtn.get(); + break; + case DbGridControlNavigationBarState::Prev: + pWnd = m_xPrevBtn.get(); + break; + case DbGridControlNavigationBarState::Next: + pWnd = m_xNextBtn.get(); + break; + case DbGridControlNavigationBarState::Last: + pWnd = m_xLastBtn.get(); + break; + case DbGridControlNavigationBarState::New: + pWnd = m_xNewBtn.get(); + break; + case DbGridControlNavigationBarState::Absolute: + pWnd = m_xAbsolute->GetWidget(); + if (bAvailable) + m_xAbsolute->set_text(OUString::number(m_nCurrentPos + 1)); + else + m_xAbsolute->set_text(OUString()); + break; + case DbGridControlNavigationBarState::Text: + pWnd = m_xRecordText.get(); + break; + case DbGridControlNavigationBarState::Of: + pWnd = m_xRecordOf.get(); + break; + case DbGridControlNavigationBarState::Count: + { + pWnd = m_xRecordCount.get(); + OUString aText; + if (bAvailable) + { + if (pParent->GetOptions() & DbGridControlOptions::Insert) + { + if (pParent->IsCurrentAppending() && !pParent->IsModified()) + aText = OUString::number(pParent->GetRowCount()); + else + aText = OUString::number(pParent->GetRowCount() - 1); + } + else + aText = OUString::number(pParent->GetRowCount()); + if(!pParent->m_bRecordCountFinal) + aText += " *"; + } + else + aText.clear(); + + // add the number of selected rows, if applicable + if (pParent->GetSelectRowCount()) + { + OUString aExtendedInfo = aText + " (" + + OUString::number(pParent->GetSelectRowCount()) + ")"; + m_xRecordCount->set_label(aExtendedInfo); + } + else + m_xRecordCount->set_label(aText); + + pParent->SetRealRowCount(aText); + } break; + default: break; + } + DBG_ASSERT(pWnd, "no window"); + if (!(pWnd && (pWnd->get_sensitive() != bAvailable))) + return; + + // this "pWnd->IsEnabled() != bAvailable" is a little hack : Window::Enable always generates a user + // event (ImplGenerateMouseMove) even if nothing happened. This may lead to some unwanted effects, so we + // do this check. + // For further explanation see Bug 69900. + pWnd->set_sensitive(bAvailable); + if (!bAvailable) + { + if (pWnd == m_xNextBtn.get()) + m_xNextRepeater->Stop(); + else if (pWnd == m_xPrevBtn.get()) + m_xPrevRepeater->Stop(); + } +} + +DbGridRow::DbGridRow():m_eStatus(GridRowStatus::Clean), m_bIsNew(true) +{} + +DbGridRow::DbGridRow(CursorWrapper* pCur, bool bPaintCursor) + :m_bIsNew(false) +{ + + if (pCur && pCur->Is()) + { + Reference< XIndexAccess > xColumns(pCur->getColumns(), UNO_QUERY); + for (sal_Int32 i = 0; i < xColumns->getCount(); ++i) + { + Reference< XPropertySet > xColSet( + xColumns->getByIndex(i), css::uno::UNO_QUERY); + m_aVariants.emplace_back( new DataColumn(xColSet) ); + } + + if (pCur->rowDeleted()) + m_eStatus = GridRowStatus::Deleted; + else + { + if (bPaintCursor) + m_eStatus = (pCur->isAfterLast() || pCur->isBeforeFirst()) ? GridRowStatus::Invalid : GridRowStatus::Clean; + else + { + const Reference< XPropertySet >& xSet = pCur->getPropertySet(); + if (xSet.is()) + { + m_bIsNew = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISNEW)); + if (!m_bIsNew && (pCur->isAfterLast() || pCur->isBeforeFirst())) + m_eStatus = GridRowStatus::Invalid; + else if (::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISMODIFIED))) + m_eStatus = GridRowStatus::Modified; + else + m_eStatus = GridRowStatus::Clean; + } + else + m_eStatus = GridRowStatus::Invalid; + } + } + if (!m_bIsNew && IsValid()) + m_aBookmark = pCur->getBookmark(); + else + m_aBookmark = Any(); + } + else + m_eStatus = GridRowStatus::Invalid; +} + +DbGridRow::~DbGridRow() +{ +} + +void DbGridRow::SetState(CursorWrapper* pCur, bool bPaintCursor) +{ + if (pCur && pCur->Is()) + { + if (pCur->rowDeleted()) + { + m_eStatus = GridRowStatus::Deleted; + m_bIsNew = false; + } + else + { + m_eStatus = GridRowStatus::Clean; + if (!bPaintCursor) + { + const Reference< XPropertySet >& xSet = pCur->getPropertySet(); + DBG_ASSERT(xSet.is(), "DbGridRow::SetState : invalid cursor !"); + + if (::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISMODIFIED))) + m_eStatus = GridRowStatus::Modified; + m_bIsNew = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISNEW)); + } + else + m_bIsNew = false; + } + + try + { + if (!m_bIsNew && IsValid()) + m_aBookmark = pCur->getBookmark(); + else + m_aBookmark = Any(); + } + catch(SQLException&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + m_aBookmark = Any(); + m_eStatus = GridRowStatus::Invalid; + m_bIsNew = false; + } + } + else + { + m_aBookmark = Any(); + m_eStatus = GridRowStatus::Invalid; + m_bIsNew = false; + } +} + +DbGridControl::DbGridControl( + Reference< XComponentContext > const & _rxContext, + vcl::Window* pParent, + WinBits nBits) + :EditBrowseBox(pParent, EditBrowseBoxFlags::NONE, nBits, DEFAULT_BROWSE_MODE ) + ,m_xContext(_rxContext) + ,m_aBar(VclPtr<NavigationBar>::Create(this)) + ,m_nAsynAdjustEvent(nullptr) + ,m_pDataSourcePropListener(nullptr) + ,m_pGridListener(nullptr) + ,m_nSeekPos(-1) + ,m_nTotalCount(-1) + ,m_aNullDate(::dbtools::DBTypeConversion::getStandardDate()) + ,m_nMode(DEFAULT_BROWSE_MODE) + ,m_nCurrentPos(-1) + ,m_nDeleteEvent(nullptr) + ,m_nOptions(DbGridControlOptions::Readonly) + ,m_nOptionMask(DbGridControlOptions::Insert | DbGridControlOptions::Update | DbGridControlOptions::Delete) + ,m_nLastColId(sal_uInt16(-1)) + ,m_nLastRowId(-1) + ,m_bDesignMode(false) + ,m_bRecordCountFinal(false) + ,m_bSynchDisplay(true) + ,m_bHandle(true) + ,m_bFilterMode(false) + ,m_bWantDestruction(false) + ,m_bPendingAdjustRows(false) + ,m_bHideScrollbars( false ) + ,m_bUpdating(false) +{ + m_bNavigationBar = true; + + OUString sName(SvxResId(RID_STR_NAVIGATIONBAR)); + m_aBar->SetAccessibleName(sName); + m_aBar->Show(); + ImplInitWindow( InitWindowFacet::All ); +} + +void DbGridControl::InsertHandleColumn() +{ + // BrowseBox has problems when painting without a handleColumn (hide it here) + if (HasHandle()) + BrowseBox::InsertHandleColumn(GetDefaultColumnWidth(OUString())); + else + BrowseBox::InsertHandleColumn(0); +} + +void DbGridControl::Init() +{ + VclPtr<BrowserHeader> pNewHeader = CreateHeaderBar(this); + pHeader->SetMouseTransparent(false); + + SetHeaderBar(pNewHeader); + SetMode(m_nMode); + SetCursorColor(Color(0xFF, 0, 0)); + + InsertHandleColumn(); +} + +DbGridControl::~DbGridControl() +{ + disposeOnce(); +} + +void DbGridControl::dispose() +{ + RemoveColumns(); + + m_bWantDestruction = true; + osl::MutexGuard aGuard(m_aDestructionSafety); + if (!m_aFieldListeners.empty()) + DisconnectFromFields(); + m_pCursorDisposeListener.reset(); + + if (m_nDeleteEvent) + Application::RemoveUserEvent(m_nDeleteEvent); + + if (m_pDataSourcePropMultiplexer.is()) + { + m_pDataSourcePropMultiplexer->dispose(); + m_pDataSourcePropMultiplexer.clear(); // this should delete the multiplexer + delete m_pDataSourcePropListener; + m_pDataSourcePropListener = nullptr; + } + m_xRowSetListener.clear(); + + m_pDataCursor.reset(); + m_pSeekCursor.reset(); + + m_aBar.disposeAndClear(); + + EditBrowseBox::dispose(); +} + +void DbGridControl::StateChanged( StateChangedType nType ) +{ + EditBrowseBox::StateChanged( nType ); + + switch (nType) + { + case StateChangedType::Mirroring: + ImplInitWindow( InitWindowFacet::WritingMode ); + Invalidate(); + break; + + case StateChangedType::Zoom: + { + ImplInitWindow( InitWindowFacet::Font ); + + // and give it a chance to rearrange + Point aPoint = GetControlArea().TopLeft(); + sal_uInt16 nX = static_cast<sal_uInt16>(aPoint.X()); + ArrangeControls(nX, static_cast<sal_uInt16>(aPoint.Y())); + ReserveControlArea(nX); + } + break; + case StateChangedType::ControlFont: + ImplInitWindow( InitWindowFacet::Font ); + Invalidate(); + break; + case StateChangedType::ControlForeground: + ImplInitWindow( InitWindowFacet::Foreground ); + Invalidate(); + break; + case StateChangedType::ControlBackground: + ImplInitWindow( InitWindowFacet::Background ); + Invalidate(); + break; + default:; + } +} + +void DbGridControl::DataChanged( const DataChangedEvent& rDCEvt ) +{ + EditBrowseBox::DataChanged( rDCEvt ); + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS ) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + { + ImplInitWindow( InitWindowFacet::All ); + Invalidate(); + } +} + +void DbGridControl::Select() +{ + EditBrowseBox::Select(); + + // as the selected rows may have changed, update the according display in our navigation bar + m_aBar->InvalidateState(DbGridControlNavigationBarState::Count); + + if (m_pGridListener) + m_pGridListener->selectionChanged(); +} + +void DbGridControl::ImplInitWindow( const InitWindowFacet _eInitWhat ) +{ + for (auto const & pCol : m_aColumns) + { + pCol->ImplInitWindow( GetDataWindow(), _eInitWhat ); + } + + if ( _eInitWhat & InitWindowFacet::WritingMode ) + { + if ( m_bNavigationBar ) + { + m_aBar->EnableRTL( IsRTLEnabled() ); + } + } + + if ( _eInitWhat & InitWindowFacet::Font ) + { + if ( m_bNavigationBar ) + { + if ( IsControlFont() ) + m_aBar->SetControlFont( GetControlFont() ); + else + m_aBar->SetControlFont(); + + m_aBar->SetZoom( GetZoom() ); + } + } + + if ( !(_eInitWhat & InitWindowFacet::Background) ) + return; + + if (IsControlBackground()) + { + GetDataWindow().SetBackground(GetControlBackground()); + GetDataWindow().SetControlBackground(GetControlBackground()); + GetDataWindow().GetOutDev()->SetFillColor(GetControlBackground()); + } + else + { + GetDataWindow().SetControlBackground(); + GetDataWindow().GetOutDev()->SetFillColor(GetOutDev()->GetFillColor()); + } +} + +void DbGridControl::RemoveRows(bool bNewCursor) +{ + // Did the data cursor change? + if (!bNewCursor) + { + m_pSeekCursor.reset(); + m_xPaintRow = m_xDataRow = m_xEmptyRow = m_xCurrentRow = m_xSeekRow = nullptr; + m_nCurrentPos = m_nSeekPos = -1; + m_nOptions = DbGridControlOptions::Readonly; + + RowRemoved(0, GetRowCount(), false); + m_nTotalCount = -1; + } + else + { + RemoveRows(); + } +} + +void DbGridControl::RemoveRows() +{ + // we're going to remove all columns and all row, so deactivate the current cell + if (IsEditing()) + DeactivateCell(); + + // de-initialize all columns + // if there are columns, free all controllers + for (auto const & pColumn : m_aColumns) + pColumn->Clear(); + + m_pSeekCursor.reset(); + m_pDataCursor.reset(); + + m_xPaintRow = m_xDataRow = m_xEmptyRow = m_xCurrentRow = m_xSeekRow = nullptr; + m_nCurrentPos = m_nSeekPos = m_nTotalCount = -1; + m_nOptions = DbGridControlOptions::Readonly; + + // reset number of sentences to zero in the browser + EditBrowseBox::RemoveRows(); + m_aBar->InvalidateAll(m_nCurrentPos, true); +} + +void DbGridControl::ArrangeControls(sal_uInt16& nX, sal_uInt16 nY) +{ + // positioning of the controls + if (m_bNavigationBar) + { + tools::Rectangle aRect(GetControlArea()); + m_aBar->SetPosSizePixel(Point(0, nY + 1), Size(aRect.GetSize().Width(), aRect.GetSize().Height() - 1)); + nX = m_aBar->ArrangeControls(); + } +} + +void DbGridControl::EnableHandle(bool bEnable) +{ + if (m_bHandle == bEnable) + return; + + // HandleColumn is only hidden because there are a lot of problems while painting otherwise + RemoveColumn( HandleColumnId ); + m_bHandle = bEnable; + InsertHandleColumn(); +} + +namespace +{ + bool adjustModeForScrollbars( BrowserMode& _rMode, bool _bNavigationBar, bool _bHideScrollbars ) + { + BrowserMode nOldMode = _rMode; + + if ( !_bNavigationBar ) + { + _rMode &= ~BrowserMode::AUTO_HSCROLL; + } + + if ( _bHideScrollbars ) + { + _rMode |= BrowserMode::NO_HSCROLL | BrowserMode::NO_VSCROLL; + _rMode &= ~BrowserMode( BrowserMode::AUTO_HSCROLL | BrowserMode::AUTO_VSCROLL ); + } + else + { + _rMode |= BrowserMode::AUTO_HSCROLL | BrowserMode::AUTO_VSCROLL; + _rMode &= ~BrowserMode( BrowserMode::NO_HSCROLL | BrowserMode::NO_VSCROLL ); + } + + // note: if we have a navigation bar, we always have an AUTO_HSCROLL. In particular, + // _bHideScrollbars is ignored then + if ( _bNavigationBar ) + { + _rMode |= BrowserMode::AUTO_HSCROLL; + _rMode &= ~BrowserMode::NO_HSCROLL; + } + + return nOldMode != _rMode; + } +} + +void DbGridControl::EnableNavigationBar(bool bEnable) +{ + if (m_bNavigationBar == bEnable) + return; + + m_bNavigationBar = bEnable; + + if (bEnable) + { + m_aBar->Show(); + m_aBar->Enable(); + m_aBar->InvalidateAll(m_nCurrentPos, true); + + if ( adjustModeForScrollbars( m_nMode, m_bNavigationBar, m_bHideScrollbars ) ) + SetMode( m_nMode ); + + // get size of the reserved ControlArea + Point aPoint = GetControlArea().TopLeft(); + sal_uInt16 nX = static_cast<sal_uInt16>(aPoint.X()); + + ArrangeControls(nX, static_cast<sal_uInt16>(aPoint.Y())); + ReserveControlArea(nX); + } + else + { + m_aBar->Hide(); + m_aBar->Disable(); + + if ( adjustModeForScrollbars( m_nMode, m_bNavigationBar, m_bHideScrollbars ) ) + SetMode( m_nMode ); + + ReserveControlArea(); + } +} + +DbGridControlOptions DbGridControl::SetOptions(DbGridControlOptions nOpt) +{ + DBG_ASSERT(!m_xCurrentRow.is() || !m_xCurrentRow->IsModified(), + "DbGridControl::SetOptions : please do not call when editing a record (things are much easier this way ;) !"); + + // for the next setDataSource (which is triggered by a refresh, for instance) + m_nOptionMask = nOpt; + + // normalize the new options + Reference< XPropertySet > xDataSourceSet = m_pDataCursor->getPropertySet(); + if (xDataSourceSet.is()) + { + // check what kind of options are available + sal_Int32 nPrivileges = 0; + xDataSourceSet->getPropertyValue(FM_PROP_PRIVILEGES) >>= nPrivileges; + if ((nPrivileges & Privilege::INSERT) == 0) + nOpt &= ~DbGridControlOptions::Insert; + if ((nPrivileges & Privilege::UPDATE) == 0) + nOpt &= ~DbGridControlOptions::Update; + if ((nPrivileges & Privilege::DELETE) == 0) + nOpt &= ~DbGridControlOptions::Delete; + } + else + nOpt = DbGridControlOptions::Readonly; + + // need to do something after that ? + if (nOpt == m_nOptions) + return m_nOptions; + + // the 'update' option only affects our BrowserMode (with or w/o focus rect) + BrowserMode nNewMode = m_nMode; + if (!(m_nMode & BrowserMode::CURSOR_WO_FOCUS)) + { + if (nOpt & DbGridControlOptions::Update) + nNewMode |= BrowserMode::HIDECURSOR; + else + nNewMode &= ~BrowserMode::HIDECURSOR; + } + else + nNewMode &= ~BrowserMode::HIDECURSOR; + // should not be necessary if EnablePermanentCursor is used to change the cursor behaviour, but to be sure ... + + if (nNewMode != m_nMode) + { + SetMode(nNewMode); + m_nMode = nNewMode; + } + + // _after_ setting the mode because this results in an ActivateCell + DeactivateCell(); + + bool bInsertChanged = (nOpt & DbGridControlOptions::Insert) != (m_nOptions & DbGridControlOptions::Insert); + m_nOptions = nOpt; + // we need to set this before the code below because it indirectly uses m_nOptions + + // the 'insert' option affects our empty row + if (bInsertChanged) + { + if (m_nOptions & DbGridControlOptions::Insert) + { // the insert option is to be set + m_xEmptyRow = new DbGridRow(); + RowInserted(GetRowCount()); + } + else + { // the insert option is to be reset + m_xEmptyRow = nullptr; + if ((GetCurRow() == GetRowCount() - 1) && (GetCurRow() > 0)) + GoToRowColumnId(GetCurRow() - 1, GetCurColumnId()); + RowRemoved(GetRowCount()); + } + } + + // the 'delete' options has no immediate consequences + + ActivateCell(); + Invalidate(); + return m_nOptions; +} + +void DbGridControl::ForceHideScrollbars() +{ + if ( m_bHideScrollbars ) + return; + + m_bHideScrollbars = true; + + if ( adjustModeForScrollbars( m_nMode, m_bNavigationBar, m_bHideScrollbars ) ) + SetMode( m_nMode ); +} + +void DbGridControl::EnablePermanentCursor(bool bEnable) +{ + if (IsPermanentCursorEnabled() == bEnable) + return; + + if (bEnable) + { + m_nMode &= ~BrowserMode::HIDECURSOR; // without this BrowserMode::CURSOR_WO_FOCUS won't have any affect + m_nMode |= BrowserMode::CURSOR_WO_FOCUS; + } + else + { + if (m_nOptions & DbGridControlOptions::Update) + m_nMode |= BrowserMode::HIDECURSOR; // no cursor at all + else + m_nMode &= ~BrowserMode::HIDECURSOR; // at least the "non-permanent" cursor + + m_nMode &= ~BrowserMode::CURSOR_WO_FOCUS; + } + SetMode(m_nMode); + + bool bWasEditing = IsEditing(); + DeactivateCell(); + if (bWasEditing) + ActivateCell(); +} + +bool DbGridControl::IsPermanentCursorEnabled() const +{ + return (m_nMode & BrowserMode::CURSOR_WO_FOCUS) && !(m_nMode & BrowserMode::HIDECURSOR); +} + +void DbGridControl::refreshController(sal_uInt16 _nColId, GrantControlAccess /*_aAccess*/) +{ + if ((GetCurColumnId() == _nColId) && IsEditing()) + { // the controller which is currently active needs to be refreshed + DeactivateCell(); + ActivateCell(); + } +} + +void DbGridControl::setDataSource(const Reference< XRowSet >& _xCursor, DbGridControlOptions nOpts) +{ + if (!_xCursor.is() && !m_pDataCursor) + return; + + if (m_pDataSourcePropMultiplexer.is()) + { + m_pDataSourcePropMultiplexer->dispose(); + m_pDataSourcePropMultiplexer.clear(); // this should delete the multiplexer + delete m_pDataSourcePropListener; + m_pDataSourcePropListener = nullptr; + } + m_xRowSetListener.clear(); + + // is the new cursor valid ? + // the cursor is only valid if it contains some columns + // if there is no cursor or the cursor is not valid we have to clean up and leave + if (!_xCursor.is() || !Reference< XColumnsSupplier > (_xCursor, UNO_QUERY_THROW)->getColumns()->hasElements()) + { + RemoveRows(); + return; + } + + // did the data cursor change? + sal_uInt16 nCurPos = GetColumnPos(GetCurColumnId()); + + SetUpdateMode(false); + RemoveRows(); + DisconnectFromFields(); + + m_pCursorDisposeListener.reset(); + + { + ::osl::MutexGuard aGuard(m_aAdjustSafety); + if (m_nAsynAdjustEvent) + { + // the adjust was thought to work with the old cursor which we don't have anymore + RemoveUserEvent(m_nAsynAdjustEvent); + m_nAsynAdjustEvent = nullptr; + } + } + + // get a new formatter and data cursor + m_xFormatter = nullptr; + Reference< css::util::XNumberFormatsSupplier > xSupplier = getNumberFormats(getConnection(_xCursor), true); + if (xSupplier.is()) + { + m_xFormatter = css::util::NumberFormatter::create(m_xContext); + m_xFormatter->attachNumberFormatsSupplier(xSupplier); + + // retrieve the datebase of the Numberformatter + try + { + xSupplier->getNumberFormatSettings()->getPropertyValue("NullDate") >>= m_aNullDate; + } + catch(Exception&) + { + } + } + + m_pDataCursor.reset(new CursorWrapper(_xCursor)); + + // now create a cursor for painting rows + // we need that cursor only if we are not in insert only mode + Reference< XResultSet > xClone; + Reference< XResultSetAccess > xAccess( _xCursor, UNO_QUERY ); + try + { + xClone = xAccess.is() ? xAccess->createResultSet() : Reference< XResultSet > (); + } + catch(Exception&) + { + } + if (xClone.is()) + m_pSeekCursor.reset(new CursorWrapper(xClone)); + + // property listening on the data source + // (Normally one class would be sufficient : the multiplexer which could forward the property change to us. + // But for that we would have been derived from ::comphelper::OPropertyChangeListener, which isn't exported. + // So we introduce a second class, which is a ::comphelper::OPropertyChangeListener (in the implementation file we know this class) + // and forwards the property changes to our special method "DataSourcePropertyChanged".) + if (m_pDataCursor) + { + m_pDataSourcePropListener = new FmXGridSourcePropListener(this); + m_pDataSourcePropMultiplexer = new ::comphelper::OPropertyChangeMultiplexer(m_pDataSourcePropListener, m_pDataCursor->getPropertySet() ); + m_pDataSourcePropMultiplexer->addProperty(FM_PROP_ISMODIFIED); + m_pDataSourcePropMultiplexer->addProperty(FM_PROP_ISNEW); + } + + BrowserMode nOldMode = m_nMode; + if (m_pSeekCursor) + { + try + { + Reference< XPropertySet > xSet(_xCursor, UNO_QUERY); + if (xSet.is()) + { + // check what kind of options are available + sal_Int32 nConcurrency = ResultSetConcurrency::READ_ONLY; + xSet->getPropertyValue(FM_PROP_RESULTSET_CONCURRENCY) >>= nConcurrency; + + if ( ResultSetConcurrency::UPDATABLE == nConcurrency ) + { + sal_Int32 nPrivileges = 0; + xSet->getPropertyValue(FM_PROP_PRIVILEGES) >>= nPrivileges; + + // Insert Option should be set if insert only otherwise you won't see any rows + // and no insertion is possible + if ((m_nOptionMask & DbGridControlOptions::Insert) + && ((nPrivileges & Privilege::INSERT) == Privilege::INSERT) && (nOpts & DbGridControlOptions::Insert)) + m_nOptions |= DbGridControlOptions::Insert; + if ((m_nOptionMask & DbGridControlOptions::Update) + && ((nPrivileges & Privilege::UPDATE) == Privilege::UPDATE) && (nOpts & DbGridControlOptions::Update)) + m_nOptions |= DbGridControlOptions::Update; + if ((m_nOptionMask & DbGridControlOptions::Delete) + && ((nPrivileges & Privilege::DELETE) == Privilege::DELETE) && (nOpts & DbGridControlOptions::Delete)) + m_nOptions |= DbGridControlOptions::Delete; + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + bool bPermanentCursor = IsPermanentCursorEnabled(); + m_nMode = DEFAULT_BROWSE_MODE; + + if ( bPermanentCursor ) + { + m_nMode |= BrowserMode::CURSOR_WO_FOCUS; + m_nMode &= ~BrowserMode::HIDECURSOR; + } + else + { + // updates are allowed -> no focus rectangle + if ( m_nOptions & DbGridControlOptions::Update ) + m_nMode |= BrowserMode::HIDECURSOR; + } + + m_nMode |= BrowserMode::MULTISELECTION; + + adjustModeForScrollbars( m_nMode, m_bNavigationBar, m_bHideScrollbars ); + + Reference< XColumnsSupplier > xSupplyColumns(_xCursor, UNO_QUERY); + if (xSupplyColumns.is()) + InitColumnsByFields(Reference< XIndexAccess > (xSupplyColumns->getColumns(), UNO_QUERY)); + + ConnectToFields(); + } + + sal_uInt32 nRecordCount(0); + + if (m_pSeekCursor) + { + Reference< XPropertySet > xSet = m_pDataCursor->getPropertySet(); + xSet->getPropertyValue(FM_PROP_ROWCOUNT) >>= nRecordCount; + m_bRecordCountFinal = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ROWCOUNTFINAL)); + + m_xRowSetListener = new RowSetEventListener(this); + Reference< XRowsChangeBroadcaster> xChangeBroad(xSet,UNO_QUERY); + if ( xChangeBroad.is( ) ) + xChangeBroad->addRowsChangeListener(m_xRowSetListener); + + + // insert the currently known rows + // and one row if we are able to insert rows + if (m_nOptions & DbGridControlOptions::Insert) + { + // insert the empty row for insertion + m_xEmptyRow = new DbGridRow(); + ++nRecordCount; + } + if (nRecordCount) + { + m_xPaintRow = m_xSeekRow = new DbGridRow(m_pSeekCursor.get(), true); + m_xDataRow = new DbGridRow(m_pDataCursor.get(), false); + RowInserted(0, nRecordCount, false); + + if (m_xSeekRow->IsValid()) + try + { + m_nSeekPos = m_pSeekCursor->getRow() - 1; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + m_nSeekPos = -1; + } + } + else + { + // no rows so we don't need a seekcursor + m_pSeekCursor.reset(); + } + } + + // go to the old column + if (nCurPos == BROWSER_INVALIDID || nCurPos >= ColCount()) + nCurPos = 0; + + // Column zero is a valid choice and guaranteed to exist, + // but invisible to the user; if we have at least one + // user-visible column, go to that one. + if (nCurPos == 0 && ColCount() > 1) + nCurPos = 1; + + // there are rows so go to the selected current column + if (nRecordCount) + GoToRowColumnId(0, GetColumnId(nCurPos)); + // else stop the editing if necessary + else if (IsEditing()) + DeactivateCell(); + + // now reset the mode + if (m_nMode != nOldMode) + SetMode(m_nMode); + + // RecalcRows was already called while resizing + if (!IsResizing() && GetRowCount()) + RecalcRows(GetTopRow(), GetVisibleRows(), true); + + m_aBar->InvalidateAll(m_nCurrentPos, true); + SetUpdateMode(true); + + // start listening on the seek cursor + if (m_pSeekCursor) + m_pCursorDisposeListener.reset(new DisposeListenerGridBridge(*this, Reference< XComponent > (Reference< XInterface >(*m_pSeekCursor), UNO_QUERY))); +} + +void DbGridControl::RemoveColumns() +{ + if ( !isDisposed() && IsEditing() ) + DeactivateCell(); + + m_aColumns.clear(); + + EditBrowseBox::RemoveColumns(); +} + +std::unique_ptr<DbGridColumn> DbGridControl::CreateColumn(sal_uInt16 nId) +{ + return std::unique_ptr<DbGridColumn>(new DbGridColumn(nId, *this)); +} + +sal_uInt16 DbGridControl::AppendColumn(const OUString& rName, sal_uInt16 nWidth, sal_uInt16 nModelPos, sal_uInt16 nId) +{ + DBG_ASSERT(nId == BROWSER_INVALIDID, "DbGridControl::AppendColumn : I want to set the ID myself ..."); + sal_uInt16 nRealPos = nModelPos; + if (nModelPos != HEADERBAR_APPEND) + { + // calc the view pos. we can't use our converting functions because the new column + // has no VCL-representation, yet. + sal_Int16 nViewPos = nModelPos; + while (nModelPos--) + { + if ( m_aColumns[ nModelPos ]->IsHidden() ) + --nViewPos; + } + // restore nModelPos, we need it later + nModelPos = nRealPos; + // the position the base class gets is the view pos + 1 (because of the handle column) + nRealPos = nViewPos + 1; + } + + // calculate the new id + for (nId=1; (GetModelColumnPos(nId) != GRID_COLUMN_NOT_FOUND) && size_t(nId) <= m_aColumns.size(); ++nId) + ; + DBG_ASSERT(GetViewColumnPos(nId) == GRID_COLUMN_NOT_FOUND, "DbGridControl::AppendColumn : inconsistent internal state !"); + // my column's models say "there is no column with id nId", but the view (the base class) says "there is a column ..." + + EditBrowseBox::AppendColumn(rName, nWidth, nRealPos, nId); + if (nModelPos == HEADERBAR_APPEND) + m_aColumns.push_back( CreateColumn(nId) ); + else + m_aColumns.insert( m_aColumns.begin() + nModelPos, CreateColumn(nId) ); + + return nId; +} + +void DbGridControl::RemoveColumn(sal_uInt16 nId) +{ + EditBrowseBox::RemoveColumn(nId); + + const sal_uInt16 nIndex = GetModelColumnPos(nId); + if(nIndex != GRID_COLUMN_NOT_FOUND) + { + m_aColumns.erase( m_aColumns.begin()+nIndex ); + } +} + +void DbGridControl::ColumnMoved(sal_uInt16 nId) +{ + EditBrowseBox::ColumnMoved(nId); + + // remove the col from the model + sal_uInt16 nOldModelPos = GetModelColumnPos(nId); +#ifdef DBG_UTIL + DbGridColumn* pCol = m_aColumns[ nOldModelPos ].get(); + DBG_ASSERT(!pCol->IsHidden(), "DbGridControl::ColumnMoved : moved a hidden col ? how this ?"); +#endif + + // for the new model pos we can't use GetModelColumnPos because we are altering the model at the moment + // so the method won't work (in fact it would return the old model pos) + + // the new view pos is calculated easily + sal_uInt16 nNewViewPos = GetViewColumnPos(nId); + + // from that we can compute the new model pos + size_t nNewModelPos; + for (nNewModelPos = 0; nNewModelPos < m_aColumns.size(); ++nNewModelPos) + { + if (!m_aColumns[ nNewModelPos ]->IsHidden()) + { + if (!nNewViewPos) + break; + else + --nNewViewPos; + } + } + DBG_ASSERT( nNewModelPos < m_aColumns.size(), "DbGridControl::ColumnMoved : could not find the new model position !"); + + // this will work. of course the model isn't fully consistent with our view right now, but let's + // look at the situation : a column has been moved with in the VIEW from pos m to n, say m<n (in the + // other case we can use analogue arguments). + // All cols k with m<k<=n have been shifted left on pos, the former col m now has pos n. + // In the model this affects a range of cols x to y, where x<=m and y<=n. And the number of hidden cols + // within this range is constant, so we may calculate the view pos from the model pos in the above way. + + // for instance, let's look at a grid with six columns where the third one is hidden. this will + // initially look like this : + + // +---+---+---+---+---+---+ + // model pos | 0 | 1 |*2*| 3 | 4 | 5 | + // +---+---+---+---+---+---+ + // ID | 1 | 2 | 3 | 4 | 5 | 6 | + // +---+---+---+---+---+---+ + // view pos | 0 | 1 | - | 2 | 3 | 4 | + // +---+---+---+---+---+---+ + + // if we move the column at (view) pos 1 to (view) pos 3 we have : + + // +---+---+---+---+---+---+ + // model pos | 0 | 3 |*2*| 4 | 1 | 5 | // not reflecting the changes, yet + // +---+---+---+---+---+---+ + // ID | 1 | 4 | 3 | 5 | 2 | 6 | // already reflecting the changes + // +---+---+---+---+---+---+ + // view pos | 0 | 1 | - | 2 | 3 | 4 | + // +---+---+---+---+---+---+ + + // or, sorted by the out-of-date model positions : + + // +---+---+---+---+---+---+ + // model pos | 0 | 1 |*2*| 3 | 4 | 5 | + // +---+---+---+---+---+---+ + // ID | 1 | 2 | 3 | 4 | 5 | 6 | + // +---+---+---+---+---+---+ + // view pos | 0 | 3 | - | 1 | 2 | 4 | + // +---+---+---+---+---+---+ + + // We know the new view pos (3) of the moved column because our base class tells us. So we look at our + // model for the 4th (the pos is zero-based) visible column, it is at (model) position 4. And this is + // exactly the pos where we have to re-insert our column's model, so it looks ike this : + + // +---+---+---+---+---+---+ + // model pos | 0 |*1*| 2 | 3 | 4 | 5 | + // +---+---+---+---+---+---+ + // ID | 1 | 3 | 4 | 5 | 2 | 6 | + // +---+---+---+---+---+---+ + // view pos | 0 | - | 1 | 2 | 3 | 4 | + // +---+---+---+---+---+---+ + + // Now, all is consistent again. + // (except of the hidden column : The cycling of the cols occurred on the model, not on the view. maybe + // the user expected the latter but there really is no good argument against our method ;) ...) + + // And no, this large explanation isn't just because I wanted to play a board game or something like + // that. It's because it took me a while to see it myself, and the whole theme (hidden cols, model col + // positions, view col positions) is really painful (at least for me) so the above pictures helped me a lot ;) + + auto temp = std::move(m_aColumns[ nOldModelPos ]); + m_aColumns.erase( m_aColumns.begin() + nOldModelPos ); + m_aColumns.insert( m_aColumns.begin() + nNewModelPos, std::move(temp) ); +} + +bool DbGridControl::SeekRow(sal_Int32 nRow) +{ + // in filter mode or in insert only mode we don't have any cursor! + if ( !SeekCursor( nRow ) ) + return false; + + if ( IsFilterMode() ) + { + DBG_ASSERT( IsFilterRow( nRow ), "DbGridControl::SeekRow(): No filter row, wrong mode" ); + m_xPaintRow = m_xEmptyRow; + } + else + { + // on the current position we have to take the current row for display as we want + // to have the most recent values for display + if ( ( nRow == m_nCurrentPos ) && getDisplaySynchron() ) + m_xPaintRow = m_xCurrentRow; + // seek to the empty insert row + else if ( IsInsertionRow( nRow ) ) + m_xPaintRow = m_xEmptyRow; + else + { + m_xSeekRow->SetState( m_pSeekCursor.get(), true ); + m_xPaintRow = m_xSeekRow; + } + } + + EditBrowseBox::SeekRow(nRow); + + return m_nSeekPos >= 0; +} + +// Is called whenever the visible amount of data changes +void DbGridControl::VisibleRowsChanged( sal_Int32 nNewTopRow, sal_uInt16 nLinesOnScreen ) +{ + RecalcRows(nNewTopRow, nLinesOnScreen, false); +} + +void DbGridControl::RecalcRows(sal_Int32 nNewTopRow, sal_uInt16 nLinesOnScreen, bool bUpdateCursor) +{ + // If no cursor -> no rows in the browser. + if (!m_pSeekCursor) + { + DBG_ASSERT(GetRowCount() == 0,"DbGridControl: without cursor no rows are allowed to be there"); + return; + } + + // ignore any implicitly made updates + bool bDisablePaint = !bUpdateCursor && IsPaintEnabled(); + if (bDisablePaint) + EnablePaint(false); + + // adjust cache to the visible area + Reference< XPropertySet > xSet = m_pSeekCursor->getPropertySet(); + sal_Int32 nCacheSize = 0; + xSet->getPropertyValue(FM_PROP_FETCHSIZE) >>= nCacheSize; + bool bCacheAligned = false; + // no further cursor movements after initializing (m_nSeekPos < 0) because it is already + // positioned on the first sentence + tools::Long nDelta = nNewTopRow - GetTopRow(); + // limit for relative positioning + tools::Long nLimit = nCacheSize ? nCacheSize / 2 : 0; + + // more lines on screen than in cache + if (nLimit < nLinesOnScreen) + { + Any aCacheSize; + aCacheSize <<= sal_Int32(nLinesOnScreen*2); + xSet->setPropertyValue(FM_PROP_FETCHSIZE, aCacheSize); + // here we need to update the cursor for sure + bUpdateCursor = true; + bCacheAligned = true; + nLimit = nLinesOnScreen; + } + + // In the following, all positionings are done as it is + // ensured that there are enough lines in the data cache + + // window goes downwards with less than two windows difference or + // the cache was updated and no rowcount yet + if (nDelta < nLimit && (nDelta > 0 + || (bCacheAligned && m_nTotalCount < 0)) ) + SeekCursor(nNewTopRow + nLinesOnScreen - 1); + else if (nDelta < 0 && std::abs(nDelta) < nLimit) + SeekCursor(nNewTopRow); + else if (nDelta != 0 || bUpdateCursor) + SeekCursor(nNewTopRow, true); + + AdjustRows(); + + // ignore any updates implicit made + EnablePaint(true); +} + +void DbGridControl::RowInserted(sal_Int32 nRow, sal_Int32 nNumRows, bool bDoPaint) +{ + if (!nNumRows) + return; + + if (m_bRecordCountFinal && m_nTotalCount < 0) + { + // if we have an insert row we have to reduce to count by 1 + // as the total count reflects only the existing rows in database + m_nTotalCount = GetRowCount() + nNumRows; + if (m_xEmptyRow.is()) + --m_nTotalCount; + } + else if (m_nTotalCount >= 0) + m_nTotalCount += nNumRows; + + EditBrowseBox::RowInserted(nRow, nNumRows, bDoPaint); + m_aBar->InvalidateState(DbGridControlNavigationBarState::Count); +} + +void DbGridControl::RowRemoved(sal_Int32 nRow, sal_Int32 nNumRows, bool bDoPaint) +{ + if (!nNumRows) + return; + + if (m_bRecordCountFinal && m_nTotalCount < 0) + { + m_nTotalCount = GetRowCount() - nNumRows; + // if we have an insert row reduce by 1 + if (m_xEmptyRow.is()) + --m_nTotalCount; + } + else if (m_nTotalCount >= 0) + m_nTotalCount -= nNumRows; + + EditBrowseBox::RowRemoved(nRow, nNumRows, bDoPaint); + m_aBar->InvalidateState(DbGridControlNavigationBarState::Count); +} + +void DbGridControl::AdjustRows() +{ + if (!m_pSeekCursor) + return; + + Reference< XPropertySet > xSet = m_pDataCursor->getPropertySet(); + + // refresh RecordCount + sal_Int32 nRecordCount = 0; + xSet->getPropertyValue(FM_PROP_ROWCOUNT) >>= nRecordCount; + if (!m_bRecordCountFinal) + m_bRecordCountFinal = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ROWCOUNTFINAL)); + + // Did the number of rows change? + // Here we need to consider that there might be an additional row for adding new data sets + + // add additional AppendRow for insertion + if (m_nOptions & DbGridControlOptions::Insert) + ++nRecordCount; + + // If there is currently an insertion, so do not consider this added row in RecordCount or Appendrow + if (!IsUpdating() && m_bRecordCountFinal && IsModified() && m_xCurrentRow != m_xEmptyRow && + m_xCurrentRow->IsNew()) + ++nRecordCount; + // ensured with !m_bUpdating: otherwise the edited data set (that SaveRow added and why this + // method was called) would be called twice (if m_bUpdating == sal_True): once in RecordCount + // and a second time here (60787 - FS) + + if (nRecordCount != GetRowCount()) + { + tools::Long nDelta = GetRowCount() - static_cast<tools::Long>(nRecordCount); + if (nDelta > 0) // too many + { + RowRemoved(GetRowCount() - nDelta, nDelta, false); + // some rows are gone, thus, repaint starting at the current position + Invalidate(); + + sal_Int32 nNewPos = AlignSeekCursor(); + if (m_bSynchDisplay) + EditBrowseBox::GoToRow(nNewPos); + + SetCurrent(nNewPos); + // there are rows so go to the selected current column + if (nRecordCount) + GoToRowColumnId(nNewPos, GetColumnId(GetCurColumnId())); + if (!IsResizing() && GetRowCount()) + RecalcRows(GetTopRow(), GetVisibleRows(), true); + m_aBar->InvalidateAll(m_nCurrentPos, true); + } + else // too few + RowInserted(GetRowCount(), -nDelta); + } + + if (m_bRecordCountFinal && m_nTotalCount < 0) + { + if (m_nOptions & DbGridControlOptions::Insert) + m_nTotalCount = GetRowCount() - 1; + else + m_nTotalCount = GetRowCount(); + } + m_aBar->InvalidateState(DbGridControlNavigationBarState::Count); +} + +svt::EditBrowseBox::RowStatus DbGridControl::GetRowStatus(sal_Int32 nRow) const +{ + if (IsFilterRow(nRow)) + return EditBrowseBox::FILTER; + else if (m_nCurrentPos >= 0 && nRow == m_nCurrentPos) + { + // new row + if (!IsValid(m_xCurrentRow)) + return EditBrowseBox::DELETED; + else if (IsModified()) + return EditBrowseBox::MODIFIED; + else if (m_xCurrentRow->IsNew()) + return EditBrowseBox::CURRENTNEW; + else + return EditBrowseBox::CURRENT; + } + else if (IsInsertionRow(nRow)) + return EditBrowseBox::NEW; + else if (!IsValid(m_xSeekRow)) + return EditBrowseBox::DELETED; + else + return EditBrowseBox::CLEAN; +} + +void DbGridControl::PaintCell(OutputDevice& rDev, const tools::Rectangle& rRect, sal_uInt16 nColumnId) const +{ + if (!IsValid(m_xPaintRow)) + return; + + size_t Location = GetModelColumnPos(nColumnId); + DbGridColumn* pColumn = (Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr; + if (pColumn) + { + tools::Rectangle aArea(rRect); + if ((GetMode() & BrowserMode::CURSOR_WO_FOCUS) == BrowserMode::CURSOR_WO_FOCUS) + { + aArea.AdjustTop(1 ); + aArea.AdjustBottom( -1 ); + } + pColumn->Paint(rDev, aArea, m_xPaintRow.get(), getNumberFormatter()); + } +} + +bool DbGridControl::CursorMoving(sal_Int32 nNewRow, sal_uInt16 nNewCol) +{ + + DeactivateCell( false ); + + if ( m_pDataCursor + && ( m_nCurrentPos != nNewRow ) + && !SetCurrent( nNewRow ) + ) + { + ActivateCell(); + return false; + } + + return EditBrowseBox::CursorMoving( nNewRow, nNewCol ); +} + +bool DbGridControl::SetCurrent(sal_Int32 nNewRow) +{ + // Each movement of the datacursor must start with BeginCursorAction and end with + // EndCursorAction to block all notifications during the movement + BeginCursorAction(); + + try + { + // compare positions + if (SeekCursor(nNewRow)) + { + if (IsFilterRow(nNewRow)) // special mode for filtering + { + m_xCurrentRow = m_xDataRow = m_xPaintRow = m_xEmptyRow; + m_nCurrentPos = nNewRow; + } + else + { + bool bNewRowInserted = false; + // Should we go to the insertrow ? + if (IsInsertionRow(nNewRow)) + { + // to we need to move the cursor to the insert row? + // we need to insert the if the current row isn't the insert row or if the + // cursor triggered the move by itself and we need a reinitialization of the row + Reference< XPropertySet > xCursorProps = m_pDataCursor->getPropertySet(); + if ( !::comphelper::getBOOL(xCursorProps->getPropertyValue(FM_PROP_ISNEW)) ) + { + Reference< XResultSetUpdate > xUpdateCursor(Reference< XInterface >(*m_pDataCursor), UNO_QUERY); + xUpdateCursor->moveToInsertRow(); + } + bNewRowInserted = true; + } + else + { + + if ( !m_pSeekCursor->isBeforeFirst() && !m_pSeekCursor->isAfterLast() ) + { + Any aBookmark = m_pSeekCursor->getBookmark(); + if (!m_xCurrentRow.is() || m_xCurrentRow->IsNew() || !CompareBookmark(aBookmark, m_pDataCursor->getBookmark())) + { + // adjust the cursor to the new desired row + if (!m_pDataCursor->moveToBookmark(aBookmark)) + { + EndCursorAction(); + return false; + } + } + } + } + m_xDataRow->SetState(m_pDataCursor.get(), false); + m_xCurrentRow = m_xDataRow; + + tools::Long nPaintPos = -1; + // do we have to repaint the last regular row in case of setting defaults or autovalues + if (m_nCurrentPos >= 0 && m_nCurrentPos >= (GetRowCount() - 2)) + nPaintPos = m_nCurrentPos; + + m_nCurrentPos = nNewRow; + + // repaint the new row to display all defaults + if (bNewRowInserted) + RowModified(m_nCurrentPos); + if (nPaintPos >= 0) + RowModified(nPaintPos); + } + } + else + { + OSL_FAIL("DbGridControl::SetCurrent : SeekRow failed !"); + EndCursorAction(); + return false; + } + } + catch ( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + EndCursorAction(); + return false; + } + + EndCursorAction(); + return true; +} + +void DbGridControl::CursorMoved() +{ + + // cursor movement due to deletion or insertion of rows + if (m_pDataCursor && m_nCurrentPos != GetCurRow()) + { + DeactivateCell(); + SetCurrent(GetCurRow()); + } + + EditBrowseBox::CursorMoved(); + m_aBar->InvalidateAll(m_nCurrentPos); + + // select the new column when they moved + if ( IsDesignMode() && GetSelectedColumnCount() > 0 && GetCurColumnId() ) + { + SelectColumnId( GetCurColumnId() ); + } + + if ( m_nLastColId != GetCurColumnId() ) + onColumnChange(); + m_nLastColId = GetCurColumnId(); + + if ( m_nLastRowId != GetCurRow() ) + onRowChange(); + m_nLastRowId = GetCurRow(); +} + +void DbGridControl::onRowChange() +{ + // not interested in +} + +void DbGridControl::onColumnChange() +{ + if ( m_pGridListener ) + m_pGridListener->columnChanged(); +} + +void DbGridControl::setDisplaySynchron(bool bSync) +{ + if (bSync != m_bSynchDisplay) + { + m_bSynchDisplay = bSync; + if (m_bSynchDisplay) + AdjustDataSource(); + } +} + +void DbGridControl::AdjustDataSource(bool bFull) +{ + SAL_INFO("svx.fmcomp", "DbGridControl::AdjustDataSource"); + SolarMutexGuard aGuard; + // If the current row is recalculated at the moment, do not adjust + + if (bFull) + m_xCurrentRow = nullptr; + // if we are on the same row only repaint + // but this is only possible for rows which are not inserted, in that case the comparison result + // may not be correct + else + if ( m_xCurrentRow.is() + && !m_xCurrentRow->IsNew() + && !m_pDataCursor->isBeforeFirst() + && !m_pDataCursor->isAfterLast() + && !m_pDataCursor->rowDeleted() + ) + { + bool bEqualBookmarks = CompareBookmark( m_xCurrentRow->GetBookmark(), m_pDataCursor->getBookmark() ); + + bool bDataCursorIsOnNew = false; + m_pDataCursor->getPropertySet()->getPropertyValue( FM_PROP_ISNEW ) >>= bDataCursorIsOnNew; + + if ( bEqualBookmarks && !bDataCursorIsOnNew ) + { + // position of my data cursor is the same as the position our current row points tpo + // sync the status, repaint, done + DBG_ASSERT(m_xDataRow == m_xCurrentRow, "Errors in the data row"); + SAL_INFO("svx.fmcomp", "same position, new state: " << ROWSTATUS(m_xCurrentRow)); + RowModified(m_nCurrentPos); + return; + } + } + + // away from the data cursor's row + if (m_xPaintRow == m_xCurrentRow) + m_xPaintRow = m_xSeekRow; + + // not up-to-date row, thus, adjust completely + if (!m_xCurrentRow.is()) + AdjustRows(); + + sal_Int32 nNewPos = AlignSeekCursor(); + if (nNewPos < 0)// could not find any position + return; + + if (nNewPos != m_nCurrentPos) + { + if (m_bSynchDisplay) + EditBrowseBox::GoToRow(nNewPos); + + if (!m_xCurrentRow.is()) + // Happens e.g. when deleting the n last datasets (n>1) while the cursor was positioned + // on the last one. In this case, AdjustRows deletes two rows from BrowseBox, by what + // CurrentRow is corrected to point two rows down, so that GoToRow will point into + // emptiness (since we are - purportedly - at the correct position) + SetCurrent(nNewPos); + } + else + { + SetCurrent(nNewPos); + RowModified(nNewPos); + } + + // if the data cursor was moved from outside, this section is voided + SetNoSelection(); + m_aBar->InvalidateAll(m_nCurrentPos, m_xCurrentRow.is()); +} + +sal_Int32 DbGridControl::AlignSeekCursor() +{ + // position SeekCursor onto the data cursor, no data transmission + + if (!m_pSeekCursor) + return -1; + + Reference< XPropertySet > xSet = m_pDataCursor->getPropertySet(); + + // now align the seek cursor and the data cursor + if (::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISNEW))) + m_nSeekPos = GetRowCount() - 1; + else + { + try + { + if ( m_pDataCursor->isBeforeFirst() ) + { + // this is somewhat strange, but can nevertheless happen + SAL_INFO( "svx.fmcomp", "DbGridControl::AlignSeekCursor: nobody should tamper with my cursor this way (before first)!" ); + m_pSeekCursor->first(); + m_pSeekCursor->previous(); + m_nSeekPos = -1; + } + else if ( m_pDataCursor->isAfterLast() ) + { + SAL_INFO( "svx.fmcomp", "DbGridControl::AlignSeekCursor: nobody should tamper with my cursor this way (after last)!" ); + m_pSeekCursor->last(); + m_pSeekCursor->next(); + m_nSeekPos = -1; + } + else + { + m_pSeekCursor->moveToBookmark(m_pDataCursor->getBookmark()); + if (!CompareBookmark(m_pDataCursor->getBookmark(), m_pSeekCursor->getBookmark())) + // unfortunately, moveToBookmark might lead to a re-positioning of the seek + // cursor (if the complex moveToBookmark with all its events fires an update + // somewhere) -> retry + m_pSeekCursor->moveToBookmark(m_pDataCursor->getBookmark()); + // Now there is still the chance of a failure but it is less likely. + // The alternative would be a loop until everything is fine - no good solution... + m_nSeekPos = m_pSeekCursor->getRow() - 1; + } + } + catch(Exception&) + { + } + } + return m_nSeekPos; +} + +bool DbGridControl::SeekCursor(sal_Int32 nRow, bool bAbsolute) +{ + // position SeekCursor onto the data cursor, no data transmission + + // additions for the filtermode + if (IsFilterRow(nRow)) + { + m_nSeekPos = 0; + return true; + } + + if (!m_pSeekCursor) + return false; + + // is this an insertion? + if (IsValid(m_xCurrentRow) && m_xCurrentRow->IsNew() && + nRow >= m_nCurrentPos) + { + // if so, scrolling down must be prevented as this is already the last data set! + if (nRow == m_nCurrentPos) + { + // no adjustment necessary + m_nSeekPos = nRow; + } + else if (IsInsertionRow(nRow)) // blank row for data insertion + m_nSeekPos = nRow; + } + else if (IsInsertionRow(nRow)) // blank row for data insertion + m_nSeekPos = nRow; + else if ((-1 == nRow) && (GetRowCount() == ((m_nOptions & DbGridControlOptions::Insert) ? 1 : 0)) && m_pSeekCursor->isAfterLast()) + m_nSeekPos = nRow; + else + { + bool bSuccess = false; + tools::Long nSteps = 0; + try + { + if ( m_pSeekCursor->rowDeleted() ) + { + // somebody deleted the current row of the seek cursor. Move it away from this row. + m_pSeekCursor->next(); + if ( m_pSeekCursor->isAfterLast() || m_pSeekCursor->isBeforeFirst() ) + bAbsolute = true; + } + + if ( !bAbsolute ) + { + DBG_ASSERT( !m_pSeekCursor->isAfterLast() && !m_pSeekCursor->isBeforeFirst(), + "DbGridControl::SeekCursor: how did the seek cursor get to this position?!" ); + nSteps = nRow - (m_pSeekCursor->getRow() - 1); + bAbsolute = std::abs(nSteps) > 100; + } + + if ( bAbsolute ) + { + bSuccess = m_pSeekCursor->absolute(nRow + 1); + if (bSuccess) + m_nSeekPos = nRow; + } + else + { + if (nSteps > 0) // position onto the last needed data set + { + if (m_pSeekCursor->isAfterLast()) + bSuccess = false; + else if (m_pSeekCursor->isBeforeFirst()) + bSuccess = m_pSeekCursor->absolute(nSteps); + else + bSuccess = m_pSeekCursor->relative(nSteps); + } + else if (nSteps < 0) + { + if (m_pSeekCursor->isBeforeFirst()) + bSuccess = false; + else if (m_pSeekCursor->isAfterLast()) + bSuccess = m_pSeekCursor->absolute(nSteps); + else + bSuccess = m_pSeekCursor->relative(nSteps); + } + else + { + m_nSeekPos = nRow; + return true; + } + } + } + catch(Exception&) + { + OSL_FAIL("DbGridControl::SeekCursor : failed ..."); + } + + try + { + if (!bSuccess) + { + if (bAbsolute || nSteps > 0) + { + if (m_pSeekCursor->isLast()) + bSuccess = true; + else + bSuccess = m_pSeekCursor->last(); + } + else + { + if (m_pSeekCursor->isFirst()) + bSuccess = true; + else + bSuccess = m_pSeekCursor->first(); + } + } + + if (bSuccess) + m_nSeekPos = m_pSeekCursor->getRow() - 1; + else + m_nSeekPos = -1; + } + catch(Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + OSL_FAIL("DbGridControl::SeekCursor : failed ..."); + m_nSeekPos = -1; // no further data set available + } + } + return m_nSeekPos == nRow; +} + +void DbGridControl::MoveToFirst() +{ + if (m_pSeekCursor && (GetCurRow() != 0)) + MoveToPosition(0); +} + +void DbGridControl::MoveToLast() +{ + if (!m_pSeekCursor) + return; + + if (m_nTotalCount < 0) // no RecordCount, yet + { + try + { + bool bRes = m_pSeekCursor->last(); + + if (bRes) + { + m_nSeekPos = m_pSeekCursor->getRow() - 1; + AdjustRows(); + } + } + catch(Exception&) + { + } + } + + // position onto the last data set not on a blank row + if (m_nOptions & DbGridControlOptions::Insert) + { + if ((GetRowCount() - 1) > 0) + MoveToPosition(GetRowCount() - 2); + } + else if (GetRowCount()) + MoveToPosition(GetRowCount() - 1); +} + +void DbGridControl::MoveToPrev() +{ + sal_Int32 nNewRow = std::max(GetCurRow() - 1, sal_Int32(0)); + if (GetCurRow() != nNewRow) + MoveToPosition(nNewRow); +} + +void DbGridControl::MoveToNext() +{ + if (!m_pSeekCursor) + return; + + if (m_nTotalCount > 0) + { + // move the data cursor to the right position + tools::Long nNewRow = std::min(GetRowCount() - 1, GetCurRow() + 1); + if (GetCurRow() != nNewRow) + MoveToPosition(nNewRow); + } + else + { + bool bOk = false; + try + { + // try to move to next row + // when not possible our paint cursor is already on the last row + // then we must be sure that the data cursor is on the position + // we call ourself again + bOk = m_pSeekCursor->next(); + if (bOk) + { + m_nSeekPos = m_pSeekCursor->getRow() - 1; + MoveToPosition(GetCurRow() + 1); + } + } + catch(SQLException &) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + if(!bOk) + { + AdjustRows(); + if (m_nTotalCount > 0) // only to avoid infinite recursion + MoveToNext(); + } + } +} + +void DbGridControl::MoveToPosition(sal_uInt32 nPos) +{ + if (!m_pSeekCursor) + return; + + if (m_nTotalCount < 0 && static_cast<tools::Long>(nPos) >= GetRowCount()) + { + try + { + if (!m_pSeekCursor->absolute(nPos + 1)) + { + AdjustRows(); + return; + } + else + { + m_nSeekPos = m_pSeekCursor->getRow() - 1; + AdjustRows(); + } + } + catch(Exception&) + { + return; + } + } + EditBrowseBox::GoToRow(nPos); + m_aBar->InvalidateAll(m_nCurrentPos); +} + +void DbGridControl::AppendNew() +{ + if (!m_pSeekCursor || !(m_nOptions & DbGridControlOptions::Insert)) + return; + + if (m_nTotalCount < 0) // no RecordCount, yet + { + try + { + bool bRes = m_pSeekCursor->last(); + + if (bRes) + { + m_nSeekPos = m_pSeekCursor->getRow() - 1; + AdjustRows(); + } + } + catch(Exception&) + { + return; + } + } + + tools::Long nNewRow = m_nTotalCount + 1; + if (nNewRow > 0 && GetCurRow() != nNewRow) + MoveToPosition(nNewRow - 1); +} + +void DbGridControl::SetDesignMode(bool bMode) +{ + if (IsDesignMode() == bMode) + return; + + // adjust Enable/Disable for design mode so that the headerbar remains configurable + if (bMode) + { + if (!IsEnabled()) + { + Enable(); + GetDataWindow().Disable(); + } + } + else + { + // disable completely + if (!GetDataWindow().IsEnabled()) + Disable(); + } + + m_bDesignMode = bMode; + GetDataWindow().SetMouseTransparent(bMode); + SetMouseTransparent(bMode); + + m_aBar->InvalidateAll(m_nCurrentPos, true); +} + +void DbGridControl::SetFilterMode(bool bMode) +{ + if (IsFilterMode() == bMode) + return; + + m_bFilterMode = bMode; + + if (bMode) + { + SetUpdateMode(false); + + // there is no cursor anymore + if (IsEditing()) + DeactivateCell(); + RemoveRows(false); + + m_xEmptyRow = new DbGridRow(); + + // setting the new filter controls + for (auto const & pCurCol : m_aColumns) + { + if (!pCurCol->IsHidden()) + pCurCol->UpdateControl(); + } + + // one row for filtering + RowInserted(0); + SetUpdateMode(true); + } + else + setDataSource(Reference< XRowSet > ()); +} + +OUString DbGridControl::GetCellText(sal_Int32 _nRow, sal_uInt16 _nColId) const +{ + size_t Location = GetModelColumnPos( _nColId ); + DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr; + OUString sRet; + if ( const_cast<DbGridControl*>(this)->SeekRow(_nRow) ) + sRet = GetCurrentRowCellText(pColumn, m_xPaintRow); + return sRet; +} + +OUString DbGridControl::GetCurrentRowCellText(DbGridColumn const * pColumn,const DbGridRowRef& _rRow) const +{ + // text output for a single row + OUString aText; + if ( pColumn && IsValid(_rRow) ) + aText = pColumn->GetCellText(_rRow.get(), m_xFormatter); + return aText; +} + +sal_uInt32 DbGridControl::GetTotalCellWidth(sal_Int32 nRow, sal_uInt16 nColId) +{ + if (SeekRow(nRow)) + { + size_t Location = GetModelColumnPos( nColId ); + DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr; + return GetDataWindow().GetTextWidth(GetCurrentRowCellText(pColumn,m_xPaintRow)); + } + else + return 30; // FIXME magic number for default cell width +} + +void DbGridControl::PreExecuteRowContextMenu(weld::Menu& rMenu) +{ + bool bDelete = (m_nOptions & DbGridControlOptions::Delete) && GetSelectRowCount() && !IsCurrentAppending(); + // if only a blank row is selected then do not delete + bDelete = bDelete && !((m_nOptions & DbGridControlOptions::Insert) && GetSelectRowCount() == 1 && IsRowSelected(GetRowCount() - 1)); + + rMenu.set_visible("delete", bDelete); + rMenu.set_visible("save", IsModified()); + + // the undo is more difficult + bool bCanUndo = IsModified(); + int nState = -1; + if (m_aMasterStateProvider.IsSet()) + nState = m_aMasterStateProvider.Call(DbGridControlNavigationBarState::Undo); + bCanUndo &= ( 0 != nState ); + + rMenu.set_visible("undo", bCanUndo); +} + +void DbGridControl::PostExecuteRowContextMenu(const OUString& rExecutionResult) +{ + if (rExecutionResult == "delete") + { + // delete asynchronously + if (m_nDeleteEvent) + Application::RemoveUserEvent(m_nDeleteEvent); + m_nDeleteEvent = Application::PostUserEvent(LINK(this,DbGridControl,OnDelete), nullptr, true); + } + else if (rExecutionResult == "undo") + Undo(); + else if (rExecutionResult == "save") + SaveRow(); +} + +void DbGridControl::DataSourcePropertyChanged(const PropertyChangeEvent& evt) +{ + SAL_INFO("svx.fmcomp", "DbGridControl::DataSourcePropertyChanged"); + SolarMutexGuard aGuard; + // prop "IsModified" changed ? + // during update don't care about the modified state + if (IsUpdating() || evt.PropertyName != FM_PROP_ISMODIFIED) + return; + + Reference< XPropertySet > xSource(evt.Source, UNO_QUERY); + DBG_ASSERT( xSource.is(), "DbGridControl::DataSourcePropertyChanged: invalid event source!" ); + bool bIsNew = false; + if (xSource.is()) + bIsNew = ::comphelper::getBOOL(xSource->getPropertyValue(FM_PROP_ISNEW)); + + if (bIsNew && m_xCurrentRow.is()) + { + DBG_ASSERT(::comphelper::getBOOL(xSource->getPropertyValue(FM_PROP_ROWCOUNTFINAL)), "DbGridControl::DataSourcePropertyChanged : somebody moved the form to a new record before the row count was final !"); + sal_Int32 nRecordCount = 0; + xSource->getPropertyValue(FM_PROP_ROWCOUNT) >>= nRecordCount; + if (::comphelper::getBOOL(evt.NewValue)) + { // modified state changed from sal_False to sal_True and we're on an insert row + // -> we've to add a new grid row + if ((nRecordCount == GetRowCount() - 1) && m_xCurrentRow->IsNew()) + { + RowInserted(GetRowCount()); + InvalidateStatusCell(m_nCurrentPos); + m_aBar->InvalidateAll(m_nCurrentPos); + } + } + else + { // modified state changed from sal_True to sal_False and we're on an insert row + // we have two "new row"s at the moment : the one we're editing currently (where the current + // column is the only dirty element) and a "new new" row which is completely clean. As the first + // one is about to be cleaned, too, the second one is obsolete now. + if (m_xCurrentRow->IsNew() && nRecordCount == (GetRowCount() - 2)) + { + RowRemoved(GetRowCount() - 1); + InvalidateStatusCell(m_nCurrentPos); + m_aBar->InvalidateAll(m_nCurrentPos); + } + } + } + if (m_xCurrentRow.is()) + { + m_xCurrentRow->SetStatus(::comphelper::getBOOL(evt.NewValue) ? GridRowStatus::Modified : GridRowStatus::Clean); + m_xCurrentRow->SetNew( bIsNew ); + InvalidateStatusCell(m_nCurrentPos); + SAL_INFO("svx.fmcomp", "modified flag changed, new state: " << ROWSTATUS(m_xCurrentRow)); + } +} + +void DbGridControl::StartDrag( sal_Int8 /*nAction*/, const Point& rPosPixel ) +{ + if (!m_pSeekCursor || IsResizing()) + return; + + sal_uInt16 nColId = GetColumnId(GetColumnAtXPosPixel(rPosPixel.X())); + tools::Long nRow = GetRowAtYPosPixel(rPosPixel.Y()); + if (nColId != HandleColumnId && nRow >= 0) + { + if (GetDataWindow().IsMouseCaptured()) + GetDataWindow().ReleaseMouse(); + + size_t Location = GetModelColumnPos( nColId ); + DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr; + rtl::Reference<OStringTransferable> pTransferable = new OStringTransferable(GetCurrentRowCellText(pColumn,m_xPaintRow)); + pTransferable->StartDrag(this, DND_ACTION_COPY); + } +} + +bool DbGridControl::canCopyCellText(sal_Int32 _nRow, sal_uInt16 _nColId) +{ + return (_nRow >= 0) + && (_nRow < GetRowCount()) + && (_nColId != HandleColumnId) + && (GetModelColumnPos(_nColId) != GRID_COLUMN_NOT_FOUND); +} + +void DbGridControl::copyCellText(sal_Int32 _nRow, sal_uInt16 _nColId) +{ + DBG_ASSERT(canCopyCellText(_nRow, _nColId), "DbGridControl::copyCellText: invalid call!"); + DbGridColumn* pColumn = m_aColumns[ GetModelColumnPos(_nColId) ].get(); + SeekRow(_nRow); + OStringTransfer::CopyString( GetCurrentRowCellText( pColumn,m_xPaintRow ), this ); +} + +void DbGridControl::executeRowContextMenu(const Point& _rPreferredPos) +{ + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(nullptr, "svx/ui/rowsmenu.ui")); + std::unique_ptr<weld::Menu> xContextMenu(xBuilder->weld_menu("menu")); + + tools::Rectangle aRect(_rPreferredPos, Size(1,1)); + weld::Window* pParent = weld::GetPopupParent(*this, aRect); + + PreExecuteRowContextMenu(*xContextMenu); + PostExecuteRowContextMenu(xContextMenu->popup_at_rect(pParent, aRect)); +} + +void DbGridControl::Command(const CommandEvent& rEvt) +{ + switch (rEvt.GetCommand()) + { + case CommandEventId::ContextMenu: + { + if ( !m_pSeekCursor ) + { + EditBrowseBox::Command(rEvt); + return; + } + + if ( !rEvt.IsMouseEvent() ) + { // context menu requested by keyboard + if ( GetSelectRowCount() ) + { + tools::Long nRow = FirstSelectedRow( ); + + ::tools::Rectangle aRowRect( GetRowRectPixel( nRow ) ); + executeRowContextMenu(aRowRect.LeftCenter()); + + // handled + return; + } + } + + sal_uInt16 nColId = GetColumnId(GetColumnAtXPosPixel(rEvt.GetMousePosPixel().X())); + tools::Long nRow = GetRowAtYPosPixel(rEvt.GetMousePosPixel().Y()); + + if (nColId == HandleColumnId) + { + executeRowContextMenu(rEvt.GetMousePosPixel()); + } + else if (canCopyCellText(nRow, nColId)) + { + ::tools::Rectangle aRect(rEvt.GetMousePosPixel(), Size(1, 1)); + weld::Window* pPopupParent = weld::GetPopupParent(*this, aRect); + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pPopupParent, "svx/ui/cellmenu.ui")); + std::unique_ptr<weld::Menu> xContextMenu(xBuilder->weld_menu("menu")); + if (!xContextMenu->popup_at_rect(pPopupParent, aRect).isEmpty()) + copyCellText(nRow, nColId); + } + else + { + EditBrowseBox::Command(rEvt); + return; + } + + [[fallthrough]]; + } + default: + EditBrowseBox::Command(rEvt); + } +} + +IMPL_LINK_NOARG(DbGridControl, OnDelete, void*, void) +{ + m_nDeleteEvent = nullptr; + DeleteSelectedRows(); +} + +void DbGridControl::DeleteSelectedRows() +{ + DBG_ASSERT(GetSelection(), "no selection!!!"); + + if (!m_pSeekCursor) + return; +} + +CellController* DbGridControl::GetController(sal_Int32 /*nRow*/, sal_uInt16 nColumnId) +{ + if (!IsValid(m_xCurrentRow) || !IsEnabled()) + return nullptr; + + size_t Location = GetModelColumnPos(nColumnId); + DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr; + if (!pColumn) + return nullptr; + + CellController* pReturn = nullptr; + if (IsFilterMode()) + pReturn = pColumn->GetController().get(); + else + { + if (::comphelper::hasProperty(FM_PROP_ENABLED, pColumn->getModel())) + { + if (!::comphelper::getBOOL(pColumn->getModel()->getPropertyValue(FM_PROP_ENABLED))) + return nullptr; + } + + bool bInsert = (m_xCurrentRow->IsNew() && (m_nOptions & DbGridControlOptions::Insert)); + bool bUpdate = (!m_xCurrentRow->IsNew() && (m_nOptions & DbGridControlOptions::Update)); + + if ((bInsert && !pColumn->IsAutoValue()) || bUpdate) + { + pReturn = pColumn->GetController().get(); + } + } + return pReturn; +} + +void DbGridControl::CellModified() +{ + SAL_INFO("svx.fmcomp", "DbGridControl::CellModified"); + + { + ::osl::MutexGuard aGuard(m_aAdjustSafety); + if (m_nAsynAdjustEvent) + { + SAL_INFO("svx.fmcomp", "forcing a synchron call to " << (m_bPendingAdjustRows ? "AdjustRows" : "AdustDataSource")); + RemoveUserEvent(m_nAsynAdjustEvent); + m_nAsynAdjustEvent = nullptr; + + // force the call : this should be no problem as we're probably running in the solar thread here + // (cell modified is triggered by user actions) + if (m_bPendingAdjustRows) + AdjustRows(); + else + AdjustDataSource(); + } + } + + if (IsFilterMode() || !IsValid(m_xCurrentRow) || m_xCurrentRow->IsModified()) + return; + + // enable edit mode + // a data set should be inserted + if (m_xCurrentRow->IsNew()) + { + m_xCurrentRow->SetStatus(GridRowStatus::Modified); + SAL_INFO("svx.fmcomp", "current row is new, new state: MODIFIED"); + // if no row was added yet, do it now + if (m_nCurrentPos == GetRowCount() - 1) + { + // increment RowCount + RowInserted(GetRowCount()); + InvalidateStatusCell(m_nCurrentPos); + m_aBar->InvalidateAll(m_nCurrentPos); + } + } + else if (m_xCurrentRow->GetStatus() != GridRowStatus::Modified) + { + m_xCurrentRow->SetState(m_pDataCursor.get(), false); + SAL_INFO("svx.fmcomp", "current row is not new, after SetState, new state: " << ROWSTATUS(m_xCurrentRow)); + m_xCurrentRow->SetStatus(GridRowStatus::Modified); + SAL_INFO("svx.fmcomp", "current row is not new, new state: MODIFIED"); + InvalidateStatusCell(m_nCurrentPos); + } +} + +void DbGridControl::Dispatch(sal_uInt16 nId) +{ + if (nId == BROWSER_CURSORENDOFFILE) + { + if (m_nOptions & DbGridControlOptions::Insert) + AppendNew(); + else + MoveToLast(); + } + else + EditBrowseBox::Dispatch(nId); +} + +void DbGridControl::Undo() +{ + if (IsFilterMode() || !IsValid(m_xCurrentRow) || !IsModified()) + return; + + // check if we have somebody doin' the UNDO for us + int nState = -1; + if (m_aMasterStateProvider.IsSet()) + nState = m_aMasterStateProvider.Call(DbGridControlNavigationBarState::Undo); + if (nState>0) + { // yes, we have, and the slot is enabled + DBG_ASSERT(m_aMasterSlotExecutor.IsSet(), "DbGridControl::Undo : a state, but no execute link ?"); + bool lResult = m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::Undo); + if (lResult) + // handled + return; + } + else if (nState == 0) + // yes, we have, and the slot is disabled + return; + + BeginCursorAction(); + + bool bAppending = m_xCurrentRow->IsNew(); + bool bDirty = m_xCurrentRow->IsModified(); + + try + { + // cancel editing + Reference< XResultSetUpdate > xUpdateCursor(Reference< XInterface >(*m_pDataCursor), UNO_QUERY); + // no effects if we're not updating currently + if (bAppending) + // just refresh the row + xUpdateCursor->moveToInsertRow(); + else + xUpdateCursor->cancelRowUpdates(); + + } + catch(Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + EndCursorAction(); + + m_xDataRow->SetState(m_pDataCursor.get(), false); + if (m_xPaintRow == m_xCurrentRow) + m_xPaintRow = m_xCurrentRow = m_xDataRow; + else + m_xCurrentRow = m_xDataRow; + + if (bAppending && (EditBrowseBox::IsModified() || bDirty)) + // remove the row + if (m_nCurrentPos == GetRowCount() - 2) + { // maybe we already removed it (in resetCurrentRow, called if the above moveToInsertRow + // caused our data source form to be reset - which should be the usual case...) + RowRemoved(GetRowCount() - 1); + m_aBar->InvalidateAll(m_nCurrentPos); + } + + RowModified(m_nCurrentPos); +} + +void DbGridControl::resetCurrentRow() +{ + if (IsModified()) + { + // scenario : we're on the insert row, the row is dirty, and thus there exists a "second" insert row (which + // is clean). Normally in DataSourcePropertyChanged we would remove this second row if the modified state of + // the insert row changes from sal_True to sal_False. But if our current cell is the only modified element (means the + // data source isn't modified) and we're reset this DataSourcePropertyChanged would never be called, so we + // would never delete the obsolete "second insert row". Thus in this special case this method here + // is the only possibility to determine the redundance of the row (resetCurrentRow is called when the + // "first insert row" is about to be cleaned, so of course the "second insert row" is redundant now) + Reference< XPropertySet > xDataSource = getDataSource()->getPropertySet(); + if (xDataSource.is() && !::comphelper::getBOOL(xDataSource->getPropertyValue(FM_PROP_ISMODIFIED))) + { + // are we on a new row currently ? + if (m_xCurrentRow->IsNew()) + { + if (m_nCurrentPos == GetRowCount() - 2) + { + RowRemoved(GetRowCount() - 1); + m_aBar->InvalidateAll(m_nCurrentPos); + } + } + } + + // update the rows + m_xDataRow->SetState(m_pDataCursor.get(), false); + if (m_xPaintRow == m_xCurrentRow) + m_xPaintRow = m_xCurrentRow = m_xDataRow; + else + m_xCurrentRow = m_xDataRow; + } + + RowModified(GetCurRow()); // will update the current controller if affected +} + +void DbGridControl::RowModified( sal_Int32 nRow ) +{ + if (nRow == m_nCurrentPos && IsEditing()) + { + CellControllerRef aTmpRef = Controller(); + aTmpRef->SaveValue(); + InitController(aTmpRef, m_nCurrentPos, GetCurColumnId()); + } + EditBrowseBox::RowModified(nRow); +} + +bool DbGridControl::IsModified() const +{ + return !IsFilterMode() && IsValid(m_xCurrentRow) && (m_xCurrentRow->IsModified() || EditBrowseBox::IsModified()); +} + +bool DbGridControl::IsCurrentAppending() const +{ + return m_xCurrentRow.is() && m_xCurrentRow->IsNew(); +} + +bool DbGridControl::IsInsertionRow(sal_Int32 nRow) const +{ + return (m_nOptions & DbGridControlOptions::Insert) && m_nTotalCount >= 0 && (nRow == GetRowCount() - 1); +} + +bool DbGridControl::SaveModified() +{ + SAL_INFO("svx.fmcomp", "DbGridControl::SaveModified"); + DBG_ASSERT(IsValid(m_xCurrentRow), "GridControl:: Invalid row"); + if (!IsValid(m_xCurrentRow)) + return true; + + // accept input for this field + // Where there changes at the current input field? + if (!EditBrowseBox::IsModified()) + return true; + + size_t Location = GetModelColumnPos( GetCurColumnId() ); + DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr; + bool bOK = pColumn && pColumn->Commit(); + DBG_ASSERT( Controller().is(), "DbGridControl::SaveModified: was modified, by have no controller?!" ); + if ( !Controller().is() ) + // this might happen if the callbacks implicitly triggered by Commit + // fiddled with the form or the control ... + // (Note that this here is a workaround, at most. We need a general concept how + // to treat this, you can imagine an arbitrary number of scenarios where a callback + // triggers something which leaves us in an expected state.) + // #i67147# / 2006-07-17 / frank.schoenheit@sun.com + return bOK; + + if (bOK) + { + Controller()->SaveValue(); + + if ( IsValid(m_xCurrentRow) ) + { + m_xCurrentRow->SetState(m_pDataCursor.get(), false); + SAL_INFO("svx.fmcomp", "explicit SetState, new state: " << ROWSTATUS(m_xCurrentRow)); + InvalidateStatusCell( m_nCurrentPos ); + } + else + { + SAL_INFO("svx.fmcomp", "no SetState, new state: " << ROWSTATUS(m_xCurrentRow)); + } + } + + return bOK; +} + +bool DbGridControl::SaveRow() +{ + SAL_INFO("svx.fmcomp", "DbGridControl::SaveRow"); + // valid row + if (!IsValid(m_xCurrentRow) || !IsModified()) + return true; + // value of the controller was not saved, yet + else if (Controller().is() && Controller()->IsValueChangedFromSaved()) + { + if (!SaveModified()) + return false; + } + m_bUpdating = true; + + BeginCursorAction(); + bool bAppending = m_xCurrentRow->IsNew(); + bool bSuccess = false; + try + { + Reference< XResultSetUpdate > xUpdateCursor(Reference< XInterface >(*m_pDataCursor), UNO_QUERY); + if (bAppending) + xUpdateCursor->insertRow(); + else + xUpdateCursor->updateRow(); + bSuccess = true; + } + catch(SQLException&) + { + EndCursorAction(); + m_bUpdating = false; + return false; + } + + try + { + if (bSuccess) + { + // if we are appending we still sit on the insert row + // we don't move just clear the flags not to move on the current row + m_xCurrentRow->SetState(m_pDataCursor.get(), false); + SAL_INFO("svx.fmcomp", "explicit SetState after a successful update, new state: " << ROWSTATUS(m_xCurrentRow)); + m_xCurrentRow->SetNew(false); + + // adjust the seekcursor if it is on the same position as the datacursor + if (m_nSeekPos == m_nCurrentPos || bAppending) + { + // get the bookmark to refetch the data + // in insert mode we take the new bookmark of the data cursor + Any aBookmark = bAppending ? m_pDataCursor->getBookmark() : m_pSeekCursor->getBookmark(); + m_pSeekCursor->moveToBookmark(aBookmark); + // get the data + m_xSeekRow->SetState(m_pSeekCursor.get(), true); + m_nSeekPos = m_pSeekCursor->getRow() - 1; + } + } + // and repaint the row + RowModified(m_nCurrentPos); + } + catch(Exception&) + { + } + + m_bUpdating = false; + EndCursorAction(); + + // The old code returned (nRecords != 0) here. + // Me thinks this is wrong : If something goes wrong while update the record, an exception will be thrown, + // which results in a "return sal_False" (see above). If no exception is thrown, everything is fine. If nRecords + // is zero, this simply means all fields had their original values. + // FS - 06.12.99 - 70502 + return true; +} + +bool DbGridControl::PreNotify(NotifyEvent& rEvt) +{ + // do not handle events of the Navbar + if (m_aBar->IsWindowOrChild(rEvt.GetWindow())) + return BrowseBox::PreNotify(rEvt); + + switch (rEvt.GetType()) + { + case NotifyEventType::KEYINPUT: + { + const KeyEvent* pKeyEvent = rEvt.GetKeyEvent(); + + sal_uInt16 nCode = pKeyEvent->GetKeyCode().GetCode(); + bool bShift = pKeyEvent->GetKeyCode().IsShift(); + bool bCtrl = pKeyEvent->GetKeyCode().IsMod1(); + bool bAlt = pKeyEvent->GetKeyCode().IsMod2(); + if ( ( KEY_TAB == nCode ) && bCtrl && !bAlt ) + { + // Ctrl-Tab is used to step out of the control, without traveling to the + // remaining cells first + // -> build a new key event without the Ctrl-key, and let the very base class handle it + vcl::KeyCode aNewCode( KEY_TAB, bShift, false, false, false ); + KeyEvent aNewEvent( pKeyEvent->GetCharCode(), aNewCode ); + + // call the Control - our direct base class will interpret this in a way we do not want (and do + // a cell traveling) + Control::KeyInput( aNewEvent ); + return true; + } + + if ( !bShift && !bCtrl && ( KEY_ESCAPE == nCode ) ) + { + if (IsModified()) + { + Undo(); + return true; + } + } + else if ( ( KEY_DELETE == nCode ) && !bShift && !bCtrl ) // delete rows + { + if ((m_nOptions & DbGridControlOptions::Delete) && GetSelectRowCount()) + { + // delete asynchronously + if (m_nDeleteEvent) + Application::RemoveUserEvent(m_nDeleteEvent); + m_nDeleteEvent = Application::PostUserEvent(LINK(this,DbGridControl,OnDelete), nullptr, true); + return true; + } + } + + [[fallthrough]]; + } + default: + return EditBrowseBox::PreNotify(rEvt); + } +} + +bool DbGridControl::IsTabAllowed(bool bRight) const +{ + if (bRight) + // Tab only if not on the _last_ row + return GetCurRow() < (GetRowCount() - 1) || !m_bRecordCountFinal || + GetViewColumnPos(GetCurColumnId()) < (GetViewColCount() - 1); + else + { + // Tab only if not on the _first_ row + return GetCurRow() > 0 || (GetCurColumnId() && GetViewColumnPos(GetCurColumnId()) > 0); + } +} + +void DbGridControl::KeyInput( const KeyEvent& rEvt ) +{ + if (rEvt.GetKeyCode().GetFunction() == KeyFuncType::COPY) + { + tools::Long nRow = GetCurRow(); + sal_uInt16 nColId = GetCurColumnId(); + if (nRow >= 0 && nRow < GetRowCount() && nColId < ColCount()) + { + size_t Location = GetModelColumnPos( nColId ); + DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr; + OStringTransfer::CopyString( GetCurrentRowCellText( pColumn, m_xCurrentRow ), this ); + return; + } + } + EditBrowseBox::KeyInput(rEvt); +} + +void DbGridControl::HideColumn(sal_uInt16 nId) +{ + DeactivateCell(); + + // determine the col for the focus to set to after removal + sal_uInt16 nPos = GetViewColumnPos(nId); + sal_uInt16 nNewColId = nPos == (ColCount()-1) + ? GetColumnIdFromViewPos(nPos-1) // last col is to be removed -> take the previous + : GetColumnIdFromViewPos(nPos+1); // take the next + + tools::Long lCurrentWidth = GetColumnWidth(nId); + EditBrowseBox::RemoveColumn(nId); + // don't use my own RemoveColumn, this would remove it from m_aColumns, too + + // update my model + size_t Location = GetModelColumnPos( nId ); + DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr; + DBG_ASSERT(pColumn, "DbGridControl::HideColumn : somebody did hide a nonexistent column !"); + if (pColumn) + { + pColumn->m_bHidden = true; + pColumn->m_nLastVisibleWidth = CalcReverseZoom(lCurrentWidth); + } + + // and reset the focus + if ( nId == GetCurColumnId() ) + GoToColumnId( nNewColId ); +} + +void DbGridControl::ShowColumn(sal_uInt16 nId) +{ + sal_uInt16 nPos = GetModelColumnPos(nId); + DBG_ASSERT(nPos != GRID_COLUMN_NOT_FOUND, "DbGridControl::ShowColumn : invalid argument !"); + if (nPos == GRID_COLUMN_NOT_FOUND) + return; + + DbGridColumn* pColumn = m_aColumns[ nPos ].get(); + if (!pColumn->IsHidden()) + { + DBG_ASSERT(GetViewColumnPos(nId) != GRID_COLUMN_NOT_FOUND, "DbGridControl::ShowColumn : inconsistent internal state !"); + // if the column isn't marked as hidden, it should be visible, shouldn't it ? + return; + } + DBG_ASSERT(GetViewColumnPos(nId) == GRID_COLUMN_NOT_FOUND, "DbGridControl::ShowColumn : inconsistent internal state !"); + // the opposite situation ... + + // to determine the new view position we need an adjacent non-hidden column + sal_uInt16 nNextNonHidden = BROWSER_INVALIDID; + // first search the cols to the right + for ( size_t i = nPos + 1; i < m_aColumns.size(); ++i ) + { + DbGridColumn* pCurCol = m_aColumns[ i ].get(); + if (!pCurCol->IsHidden()) + { + nNextNonHidden = i; + break; + } + } + if ((nNextNonHidden == BROWSER_INVALIDID) && (nPos > 0)) + { + // then to the left + for ( size_t i = nPos; i > 0; --i ) + { + DbGridColumn* pCurCol = m_aColumns[ i-1 ].get(); + if (!pCurCol->IsHidden()) + { + nNextNonHidden = i-1; + break; + } + } + } + sal_uInt16 nNewViewPos = (nNextNonHidden == BROWSER_INVALIDID) + ? 1 // there is no visible column -> insert behind the handle col + : GetViewColumnPos( m_aColumns[ nNextNonHidden ]->GetId() ) + 1; + // the first non-handle col has "view pos" 0, but the pos arg for InsertDataColumn expects + // a position 1 for the first non-handle col -> +1 + DBG_ASSERT(nNewViewPos != GRID_COLUMN_NOT_FOUND, "DbGridControl::ShowColumn : inconsistent internal state !"); + // we found a col marked as visible but got no view pos for it ... + + if ((nNextNonHidden<nPos) && (nNextNonHidden != BROWSER_INVALIDID)) + // nNextNonHidden is a column to the left, so we want to insert the new col _right_ beside it's pos + ++nNewViewPos; + + DeactivateCell(); + + OUString aName; + pColumn->getModel()->getPropertyValue(FM_PROP_LABEL) >>= aName; + InsertDataColumn(nId, aName, CalcZoom(pColumn->m_nLastVisibleWidth), HeaderBarItemBits::CENTER | HeaderBarItemBits::CLICKABLE, nNewViewPos); + pColumn->m_bHidden = false; + + ActivateCell(); + Invalidate(); +} + +sal_uInt16 DbGridControl::GetColumnIdFromModelPos( sal_uInt16 nPos ) const +{ + if (nPos >= m_aColumns.size()) + { + OSL_FAIL("DbGridControl::GetColumnIdFromModelPos : invalid argument !"); + return GRID_COLUMN_NOT_FOUND; + } + + DbGridColumn* pCol = m_aColumns[ nPos ].get(); +#if (OSL_DEBUG_LEVEL > 0) || defined DBG_UTIL + // in the debug version, we convert the ModelPos into a ViewPos and compare this with the + // value we will return (nId at the corresponding Col in m_aColumns) + + if (!pCol->IsHidden()) + { // makes sense only if the column is visible + sal_uInt16 nViewPos = nPos; + for ( size_t i = 0; i < m_aColumns.size() && i < nPos; ++i) + if ( m_aColumns[ i ]->IsHidden()) + --nViewPos; + + DBG_ASSERT(pCol && GetColumnIdFromViewPos(nViewPos) == pCol->GetId(), + "DbGridControl::GetColumnIdFromModelPos : this isn't consistent... did I misunderstand something ?"); + } +#endif + return pCol->GetId(); +} + +sal_uInt16 DbGridControl::GetModelColumnPos( sal_uInt16 nId ) const +{ + for ( size_t i = 0; i < m_aColumns.size(); ++i ) + if ( m_aColumns[ i ]->GetId() == nId ) + return i; + + return GRID_COLUMN_NOT_FOUND; +} + +void DbGridControl::implAdjustInSolarThread(bool _bRows) +{ + SAL_INFO("svx.fmcomp", "DbGridControl::implAdjustInSolarThread"); + ::osl::MutexGuard aGuard(m_aAdjustSafety); + if (!Application::IsMainThread()) + { + m_nAsynAdjustEvent = PostUserEvent(LINK(this, DbGridControl, OnAsyncAdjust), reinterpret_cast< void* >( _bRows ), true); + m_bPendingAdjustRows = _bRows; + if (_bRows) + SAL_INFO("svx.fmcomp", "posting an AdjustRows"); + else + SAL_INFO("svx.fmcomp", "posting an AdjustDataSource"); + } + else + { + if (_bRows) + SAL_INFO("svx.fmcomp", "doing an AdjustRows"); + else + SAL_INFO("svx.fmcomp", "doing an AdjustDataSource"); + // always adjust the rows before adjusting the data source + // If this is not necessary (because the row count did not change), nothing is done + // The problem is that we can't rely on the order of which the calls come in: If the cursor was moved + // to a position behind row count know 'til now, the cursorMoved notification may come before the + // RowCountChanged notification + // 94093 - 02.11.2001 - frank.schoenheit@sun.com + AdjustRows(); + + if ( !_bRows ) + AdjustDataSource(); + } +} + +IMPL_LINK(DbGridControl, OnAsyncAdjust, void*, pAdjustWhat, void) +{ + m_nAsynAdjustEvent = nullptr; + + AdjustRows(); + // see implAdjustInSolarThread for a comment why we do this every time + + if ( !pAdjustWhat ) + AdjustDataSource(); +} + +void DbGridControl::BeginCursorAction() +{ + for (const auto& rListener : m_aFieldListeners) + { + GridFieldValueListener* pCurrent = rListener.second; + if (pCurrent) + pCurrent->suspend(); + } + + if (m_pDataSourcePropListener) + m_pDataSourcePropListener->suspend(); +} + +void DbGridControl::EndCursorAction() +{ + for (const auto& rListener : m_aFieldListeners) + { + GridFieldValueListener* pCurrent = rListener.second; + if (pCurrent) + pCurrent->resume(); + } + + if (m_pDataSourcePropListener) + m_pDataSourcePropListener->resume(); +} + +void DbGridControl::ConnectToFields() +{ + DBG_ASSERT(m_aFieldListeners.empty(), "DbGridControl::ConnectToFields : please call DisconnectFromFields first !"); + + for (auto const & pCurrent : m_aColumns) + { + sal_uInt16 nViewPos = pCurrent ? GetViewColumnPos(pCurrent->GetId()) : GRID_COLUMN_NOT_FOUND; + if (GRID_COLUMN_NOT_FOUND == nViewPos) + continue; + + Reference< XPropertySet > xField = pCurrent->GetField(); + if (!xField.is()) + continue; + + // column is visible and bound here + GridFieldValueListener*& rpListener = m_aFieldListeners[pCurrent->GetId()]; + DBG_ASSERT(!rpListener, "DbGridControl::ConnectToFields : already a listener for this column ?!"); + rpListener = new GridFieldValueListener(*this, xField, pCurrent->GetId()); + } +} + +void DbGridControl::DisconnectFromFields() +{ + if (m_aFieldListeners.empty()) + return; + + while (!m_aFieldListeners.empty()) + { + sal_Int32 nOldSize = m_aFieldListeners.size(); + m_aFieldListeners.begin()->second->dispose(); + DBG_ASSERT(nOldSize > static_cast<sal_Int32>(m_aFieldListeners.size()), "DbGridControl::DisconnectFromFields : dispose on a listener should result in a removal from my list !"); + } +} + +void DbGridControl::FieldValueChanged(sal_uInt16 _nId) +{ + osl::MutexGuard aPreventDestruction(m_aDestructionSafety); + // needed as this may run in a thread other than the main one + if (GetRowStatus(GetCurRow()) != EditBrowseBox::MODIFIED) + // all other cases are handled elsewhere + return; + + size_t Location = GetModelColumnPos( _nId ); + DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr; + if (!pColumn) + return; + + std::unique_ptr<vcl::SolarMutexTryAndBuyGuard> pGuard; + while (!m_bWantDestruction && (!pGuard || !pGuard->isAcquired())) + pGuard.reset(new vcl::SolarMutexTryAndBuyGuard); + + if (m_bWantDestruction) + { // at this moment, within another thread, our destructor tries to destroy the listener which called this method + // => don't do anything + // 73365 - 23.02.00 - FS + return; + } + + // and finally do the update ... + pColumn->UpdateFromField(m_xCurrentRow.get(), m_xFormatter); + RowModified(GetCurRow()); +} + +void DbGridControl::FieldListenerDisposing(sal_uInt16 _nId) +{ + auto aPos = m_aFieldListeners.find(_nId); + if (aPos == m_aFieldListeners.end()) + { + OSL_FAIL("DbGridControl::FieldListenerDisposing : invalid call (did not find the listener) !"); + return; + } + + delete aPos->second; + + m_aFieldListeners.erase(aPos); +} + +void DbGridControl::disposing(sal_uInt16 _nId) +{ + if (_nId == 0) + { // the seek cursor is being disposed + ::osl::MutexGuard aGuard(m_aAdjustSafety); + setDataSource(nullptr, DbGridControlOptions::Readonly); // our clone was disposed so we set our datasource to null to avoid later access to it + if (m_nAsynAdjustEvent) + { + RemoveUserEvent(m_nAsynAdjustEvent); + m_nAsynAdjustEvent = nullptr; + } + } +} + +sal_Int32 DbGridControl::GetAccessibleControlCount() const +{ + return EditBrowseBox::GetAccessibleControlCount() + 1; // the navigation control +} + +Reference<XAccessible > DbGridControl::CreateAccessibleControl( sal_Int32 _nIndex ) +{ + Reference<XAccessible > xRet; + if ( _nIndex == EditBrowseBox::GetAccessibleControlCount() ) + { + xRet = m_aBar->GetAccessible(); + } + else + xRet = EditBrowseBox::CreateAccessibleControl( _nIndex ); + return xRet; +} + +Reference< XAccessible > DbGridControl::CreateAccessibleCell( sal_Int32 _nRow, sal_uInt16 _nColumnPos ) +{ + sal_uInt16 nColumnId = GetColumnId( _nColumnPos ); + size_t Location = GetModelColumnPos(nColumnId); + DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr; + if ( pColumn ) + { + Reference< css::awt::XControl> xInt(pColumn->GetCell()); + Reference< css::awt::XCheckBox> xBox(xInt,UNO_QUERY); + if ( xBox.is() ) + { + TriState eValue = TRISTATE_FALSE; + switch( xBox->getState() ) + { + case 0: + eValue = TRISTATE_FALSE; + break; + case 1: + eValue = TRISTATE_TRUE; + break; + case 2: + eValue = TRISTATE_INDET; + break; + } + return EditBrowseBox::CreateAccessibleCheckBoxCell( _nRow, _nColumnPos,eValue ); + } + } + return EditBrowseBox::CreateAccessibleCell( _nRow, _nColumnPos ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/fmcomp/xmlexchg.cxx b/svx/source/fmcomp/xmlexchg.cxx new file mode 100644 index 0000000000..86bc1bbf46 --- /dev/null +++ b/svx/source/fmcomp/xmlexchg.cxx @@ -0,0 +1,61 @@ +/* -*- 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 <svx/xmlexchg.hxx> +#include <sot/formats.hxx> +#include <sot/exchange.hxx> + +namespace svx +{ + + + using namespace ::com::sun::star::datatransfer; + + OXFormsTransferable::OXFormsTransferable(const std::function<OXFormsDescriptor()>& getDescriptorFunc) : + m_getDescriptorFunc(getDescriptorFunc) + { + } + + void OXFormsTransferable::AddSupportedFormats() + { + AddFormat( SotClipboardFormatId::XFORMS ); + } + + bool OXFormsTransferable::GetData( const DataFlavor& _rFlavor, const OUString& /*rDestDoc*/ ) + { + const SotClipboardFormatId nFormatId = SotExchange::GetFormat( _rFlavor ); + if ( SotClipboardFormatId::XFORMS == nFormatId ) + { + return SetString("XForms-Transferable"); + } + return false; + } + + OXFormsDescriptor OXFormsTransferable::extractDescriptor( const TransferableDataHelper &_rData ) + { + using namespace ::com::sun::star::uno; + Reference<XTransferable> &transfer = const_cast<Reference<XTransferable> &>(_rData.GetTransferable()); + XTransferable *pInterface = transfer.get(); + OXFormsTransferable& rThis = dynamic_cast<OXFormsTransferable&>(*pInterface); + return rThis.m_getDescriptorFunc(); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |