/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(GetParent())->GetModelColumnPos(nId); } void FmGridHeader::notifyColumnSelect(sal_uInt16 nColumnId) { sal_uInt16 nPos = GetModelColumnPos(nColumnId); Reference< XIndexAccess > xColumns = static_cast(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(makeAny(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(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(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(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(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", makeAny(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(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 aPossibleTypes; std::vector aImgResId; std::vector 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 xBuilder(Application::CreateBuilder(nullptr, "svx/ui/colsmenu.ui")); std::unique_ptr xTypeMenu(xBuilder->weld_menu("insertmenu")); int nMenuPos = 0; std::vector::const_iterator iter; std::vector::const_iterator striter; std::vector::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, makeAny( OUString( sFieldName + sTimePostfix ) ) ); OUString sDatePostfix(SvxResId( RID_STR_POSTFIX_DATE)); xSecondCol->setPropertyValue(FM_PROP_LABEL, makeAny( OUString( sFieldName + sDatePostfix ) ) ); } else xCol->setPropertyValue(FM_PROP_LABEL, makeAny(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, makeAny(sFieldName)); if ( xSecondCol.is() ) xSecondCol->setPropertyValue(FM_PROP_CONTROLSOURCE, makeAny(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, makeAny(sRealName)); else xCol->setPropertyValue(FM_PROP_NAME, makeAny(sRealName)); } } else xCol->setPropertyValue(FM_PROP_NAME, makeAny(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, makeAny(sDatasource)); else xForm->setPropertyValue(FM_PROP_URL, makeAny(sURL)); } if (::comphelper::getString(xForm->getPropertyValue(FM_PROP_COMMAND)).isEmpty()) { xForm->setPropertyValue(FM_PROP_COMMAND, makeAny(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(GetParent())->IsDesignMode(); Reference< css::container::XIndexContainer > xCols(static_cast(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(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(makeAny(xColumn)); } // insert position, always before the current column sal_uInt16 nPos = GetModelColumnPos(nColId); bool bMarked = nColId && static_cast(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; igetCount(); ++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 pItem; SfxItemState eState = pCurrentFrame->GetBindings().QueryState(SID_FM_CTL_PROPERTIES, pItem); if (eState >= SfxItemState::DEFAULT && pItem != nullptr) { bool bChecked = dynamic_cast( pItem.get()) != nullptr && static_cast(pItem.get())->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(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, makeAny(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 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; igetCount(); ++i) { xCurCol.set(xCols->getByIndex(i), css::uno::UNO_QUERY); xCurCol->setPropertyValue(FM_PROP_HIDDEN, makeAny(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/" command (there are at most 16 such items) // search the nExecutionResult'th hidden col Reference< css::beans::XPropertySet > xCurCol; for (sal_Int32 i=0; igetCount() && 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, makeAny(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, makeAny( 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, makeAny( sLabel ) ); xNewCol->setPropertyValue( FM_PROP_NAME, makeAny( sLabel ) ); factory.initializeControlModel( DocumentClassification::classifyHostDocument( xCols ), xNewCol ); xCols->insertByIndex( nPos, makeAny( 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 xBuilder(Application::CreateBuilder(nullptr, "svx/ui/colsmenu.ui")); std::unique_ptr xContextMenu(xBuilder->weld_menu("menu")); std::unique_ptr xInsertMenu(xBuilder->weld_menu("insertmenu")); std::unique_ptr xChangeMenu(xBuilder->weld_menu("changemenu")); std::unique_ptr 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 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; igetCount(); ++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(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 aDeletedRows; SetUpdateMode( false ); try { aDeletedRows = xDeleteThem->deleteRows(aBookmarks); } catch(SQLException&) { } SetUpdateMode( true ); // how many rows are deleted? sal_Int32 nDeletedRows = static_cast(std::count_if(aDeletedRows.begin(), aDeletedRows.end(), [](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(aDeletedRows.begin(), aDeletedRows.end(), 0); if (pRow != aDeletedRows.end()) { auto i = static_cast(std::distance(aDeletedRows.begin(), 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 : 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 FmGridControl::imp_CreateHeaderBar(BrowseBox* pParent) { DBG_ASSERT( pParent == this, "FmGridControl::imp_CreateHeaderBar: parent?" ); return VclPtr::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 = makeAny( static_cast(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(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(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(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(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) { 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 bAllSuccessfull = 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 bAllSuccessfull = false; } } catch(Exception&) { OSL_FAIL("FmGridControl::selectBookmarks: could not move to one of the bookmarks!"); return false; } return bAllSuccessfull; } 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(nIdx); nIdx = NextSelectedRow(); } DBG_ASSERT(i == nSelectedRows, "FmGridControl::DeleteSelectedRows : could not collect the row indices !"); for (i=0; iSetState(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 xIndex = _pPeer->getColumns(); if ( xIndex.is() && xIndex->getCount() > _nPosition ) { Reference 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 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 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(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(makeAny(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: */