diff options
Diffstat (limited to '')
-rw-r--r-- | svx/source/fmcomp/fmgridcl.cxx | 2096 |
1 files changed, 2096 insertions, 0 deletions
diff --git a/svx/source/fmcomp/fmgridcl.cxx b/svx/source/fmcomp/fmgridcl.cxx new file mode 100644 index 000000000..aacac128e --- /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 <tools/diagnose_ex.h> +#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, std::string_view id, const OUString& rText, const OUString& rImgId) +{ + rMenu.insert(nMenuPos, OUString::fromUtf8(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<OString> 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()) + { + OString 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<OString>::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); + OString 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 = OUString::fromUtf8(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 OString& 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); + OString 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 OUStringLiteral s_sPropColumnServiceName = u"ColumnServiceName"; + 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: */ |