/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
#include <svx/fmgridif.hxx>
#include <fmprop.hxx>
#include <svx/fmtools.hxx>
#include <fmservs.hxx>
#include <fmurl.hxx>
#include <formcontrolfactory.hxx>
#include <gridcell.hxx>
#include <gridcols.hxx>
#include <svx/dbaexchange.hxx>
#include <svx/dialmgr.hxx>
#include <svx/strings.hrc>
#include <svx/fmgridcl.hxx>
#include <svx/svxdlg.hxx>
#include <svx/svxids.hrc>
#include <bitmaps.hlst>
 
#include <com/sun/star/form/XConfirmDeleteListener.hpp>
#include <com/sun/star/form/XFormComponent.hpp>
#include <com/sun/star/form/XGridColumnFactory.hpp>
#include <com/sun/star/io/XPersistObject.hpp>
#include <com/sun/star/sdb/CommandType.hpp>
#include <com/sun/star/sdb/RowChangeAction.hpp>
#include <com/sun/star/sdb/XQueriesSupplier.hpp>
#include <com/sun/star/sdbc/DataType.hpp>
#include <com/sun/star/sdbc/SQLException.hpp>
#include <com/sun/star/sdbc/XPreparedStatement.hpp>
#include <com/sun/star/sdbc/XResultSetUpdate.hpp>
#include <com/sun/star/sdbcx/XColumnsSupplier.hpp>
#include <com/sun/star/sdbcx/XDeleteRows.hpp>
#include <com/sun/star/sdbcx/XTablesSupplier.hpp>
#include <com/sun/star/util/XNumberFormats.hpp>
#include <com/sun/star/util/XNumberFormatsSupplier.hpp>
#include <com/sun/star/util/URLTransformer.hpp>
#include <com/sun/star/util/XURLTransformer.hpp>
#include <com/sun/star/view/XSelectionSupplier.hpp>
#include <comphelper/processfactory.hxx>
#include <comphelper/property.hxx>
#include <comphelper/string.hxx>
#include <comphelper/types.hxx>
#include <connectivity/dbtools.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/viewfrm.hxx>
#include <svl/eitem.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/svapp.hxx>
#include <tools/debug.hxx>
#include <tools/multisel.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <vcl/help.hxx>
#include <vcl/settings.hxx>
#include <sal/log.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <memory>
 
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::view;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::sdbcx;
using namespace ::com::sun::star::sdbc;
using namespace ::com::sun::star::sdb;
using namespace ::com::sun::star::form;
using namespace ::com::sun::star::util;
using namespace ::com::sun::star::container;
using namespace ::cppu;
using namespace ::svxform;
using namespace ::svx;
using namespace ::dbtools;
 
struct FmGridHeaderData
{
    ODataAccessDescriptor   aDropData;
    Point                   aDropPosPixel;
    sal_Int8                nDropAction;
    Reference< XInterface > xDroppedStatement;
    Reference< XInterface > xDroppedResultSet;
};
 
static void InsertMenuItem(weld::Menu& rMenu, int nMenuPos, const OUString& id, const OUString& rText, const OUString& rImgId)
{
    rMenu.insert(nMenuPos, id, rText, &rImgId, nullptr, nullptr, TRISTATE_INDET);
}
 
FmGridHeader::FmGridHeader( BrowseBox* pParent, WinBits nWinBits)
        :EditBrowserHeader(pParent, nWinBits)
        ,DropTargetHelper(this)
        ,m_pImpl(new FmGridHeaderData)
{
}
 
FmGridHeader::~FmGridHeader()
{
    disposeOnce();
}
 
void FmGridHeader::dispose()
{
    m_pImpl.reset();
    DropTargetHelper::dispose();
    svt::EditBrowserHeader::dispose();
}
 
sal_uInt16 FmGridHeader::GetModelColumnPos(sal_uInt16 nId) const
{
    return static_cast<FmGridControl*>(GetParent())->GetModelColumnPos(nId);
}
 
void FmGridHeader::notifyColumnSelect(sal_uInt16 nColumnId)
{
    sal_uInt16 nPos = GetModelColumnPos(nColumnId);
    Reference< XIndexAccess >  xColumns = static_cast<FmGridControl*>(GetParent())->GetPeer()->getColumns();
    if ( nPos < xColumns->getCount() )
    {
        Reference< XSelectionSupplier >  xSelSupplier(xColumns, UNO_QUERY);
        if ( xSelSupplier.is() )
        {
            Reference< XPropertySet >  xColumn;
            xColumns->getByIndex(nPos) >>= xColumn;
            xSelSupplier->select(Any(xColumn));
        }
    }
}
 
void FmGridHeader::Select()
{
    EditBrowserHeader::Select();
    notifyColumnSelect(GetCurItemId());
}
 
void FmGridHeader::RequestHelp( const HelpEvent& rHEvt )
{
    sal_uInt16 nItemId = GetItemId( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) );
    if ( nItemId )
    {
        if ( rHEvt.GetMode() & (HelpEventMode::QUICK | HelpEventMode::BALLOON) )
        {
            tools::Rectangle aItemRect = GetItemRect( nItemId );
            Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
            aItemRect.SetLeft( aPt.X() );
            aItemRect.SetTop( aPt.Y() );
            aPt = OutputToScreenPixel( aItemRect.BottomRight() );
            aItemRect.SetRight( aPt.X() );
            aItemRect.SetBottom( aPt.Y() );
 
            sal_uInt16 nPos = GetModelColumnPos(nItemId);
            Reference< css::container::XIndexContainer >  xColumns(static_cast<FmGridControl*>(GetParent())->GetPeer()->getColumns());
            try
            {
                Reference< css::beans::XPropertySet >  xColumn(xColumns->getByIndex(nPos),UNO_QUERY);
                OUString aHelpText;
                xColumn->getPropertyValue(FM_PROP_HELPTEXT) >>= aHelpText;
                if ( aHelpText.isEmpty() )
                    xColumn->getPropertyValue(FM_PROP_DESCRIPTION) >>= aHelpText;
                if ( !aHelpText.isEmpty() )
                {
                    if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
                        Help::ShowBalloon( this, aItemRect.Center(), aItemRect, aHelpText );
                    else
                        Help::ShowQuickHelp( this, aItemRect, aHelpText );
                    return;
                }
            }
            catch(Exception&)
            {
                return;
            }
        }
    }
    EditBrowserHeader::RequestHelp( rHEvt );
}
 
sal_Int8 FmGridHeader::AcceptDrop( const AcceptDropEvent& rEvt )
{
    // drop allowed in design mode only
    if (!static_cast<FmGridControl*>(GetParent())->IsDesignMode())
        return DND_ACTION_NONE;
 
    // search for recognized formats
    const DataFlavorExVector& rFlavors = GetDataFlavorExVector();
    if (OColumnTransferable::canExtractColumnDescriptor(rFlavors, ColumnTransferFormatFlags::COLUMN_DESCRIPTOR | ColumnTransferFormatFlags::FIELD_DESCRIPTOR))
        return rEvt.mnAction;
 
    return DND_ACTION_NONE;
}
 
sal_Int8 FmGridHeader::ExecuteDrop( const ExecuteDropEvent& _rEvt )
{
    if (!static_cast<FmGridControl*>(GetParent())->IsDesignMode())
        return DND_ACTION_NONE;
 
    TransferableDataHelper aDroppedData(_rEvt.maDropEvent.Transferable);
 
    // check the formats
    bool bColumnDescriptor  = OColumnTransferable::canExtractColumnDescriptor(aDroppedData.GetDataFlavorExVector(), ColumnTransferFormatFlags::COLUMN_DESCRIPTOR);
    bool bFieldDescriptor   = OColumnTransferable::canExtractColumnDescriptor(aDroppedData.GetDataFlavorExVector(), ColumnTransferFormatFlags::FIELD_DESCRIPTOR);
    if (!bColumnDescriptor && !bFieldDescriptor)
    {
        OSL_FAIL("FmGridHeader::ExecuteDrop: should never have reached this (no extractable format)!");
        return DND_ACTION_NONE;
    }
 
    // extract the descriptor
    OUString sDatasource, sCommand, sFieldName,sDatabaseLocation;
    sal_Int32       nCommandType = CommandType::COMMAND;
    Reference< XPreparedStatement >     xStatement;
    Reference< XResultSet >             xResultSet;
    Reference< XPropertySet >           xField;
    Reference< XConnection >            xConnection;
 
    ODataAccessDescriptor aColumn = OColumnTransferable::extractColumnDescriptor(aDroppedData);
    if (aColumn.has(DataAccessDescriptorProperty::DataSource))  aColumn[DataAccessDescriptorProperty::DataSource]   >>= sDatasource;
    if (aColumn.has(DataAccessDescriptorProperty::DatabaseLocation))    aColumn[DataAccessDescriptorProperty::DatabaseLocation] >>= sDatabaseLocation;
    if (aColumn.has(DataAccessDescriptorProperty::Command))     aColumn[DataAccessDescriptorProperty::Command]      >>= sCommand;
    if (aColumn.has(DataAccessDescriptorProperty::CommandType)) aColumn[DataAccessDescriptorProperty::CommandType]  >>= nCommandType;
    if (aColumn.has(DataAccessDescriptorProperty::ColumnName))  aColumn[DataAccessDescriptorProperty::ColumnName]   >>= sFieldName;
    if (aColumn.has(DataAccessDescriptorProperty::ColumnObject))aColumn[DataAccessDescriptorProperty::ColumnObject] >>= xField;
    if (aColumn.has(DataAccessDescriptorProperty::Connection))  aColumn[DataAccessDescriptorProperty::Connection]   >>= xConnection;
 
    if  (   sFieldName.isEmpty()
        ||  sCommand.isEmpty()
        ||  (   sDatasource.isEmpty()
            &&  sDatabaseLocation.isEmpty()
            &&  !xConnection.is()
            )
        )
    {
        OSL_FAIL( "FmGridHeader::ExecuteDrop: somebody started a nonsense drag operation!!" );
        return DND_ACTION_NONE;
    }
 
    try
    {
        // need a connection
        if (!xConnection.is())
        {   // the transferable did not contain the connection -> build an own one
            try
            {
                OUString sSignificantSource( sDatasource.isEmpty() ? sDatabaseLocation : sDatasource );
                xConnection = getConnection_withFeedback(sSignificantSource, OUString(), OUString(),
                                  static_cast<FmGridControl*>(GetParent())->getContext(), nullptr );
            }
            catch(NoSuchElementException&)
            {   // allowed, means sDatasource isn't a valid data source name...
            }
            catch(Exception&)
            {
                OSL_FAIL("FmGridHeader::ExecuteDrop: could not retrieve the database access object !");
            }
 
            if (!xConnection.is())
            {
                OSL_FAIL("FmGridHeader::ExecuteDrop: could not retrieve the database access object !");
                return DND_ACTION_NONE;
            }
        }
 
        // try to obtain the column object
        if (!xField.is())
        {
#ifdef DBG_UTIL
            Reference< XServiceInfo >  xServiceInfo(xConnection, UNO_QUERY);
            DBG_ASSERT(xServiceInfo.is() && xServiceInfo->supportsService(SRV_SDB_CONNECTION), "FmGridHeader::ExecuteDrop: invalid connection (no database access connection !)");
#endif
 
            Reference< XNameAccess > xFields;
            switch (nCommandType)
            {
                case CommandType::TABLE:
                {
                    Reference< XTablesSupplier > xSupplyTables(xConnection, UNO_QUERY);
                    Reference< XColumnsSupplier >  xSupplyColumns;
                    xSupplyTables->getTables()->getByName(sCommand) >>= xSupplyColumns;
                    xFields = xSupplyColumns->getColumns();
                }
                break;
                case CommandType::QUERY:
                {
                    Reference< XQueriesSupplier > xSupplyQueries(xConnection, UNO_QUERY);
                    Reference< XColumnsSupplier > xSupplyColumns;
                    xSupplyQueries->getQueries()->getByName(sCommand) >>= xSupplyColumns;
                    xFields  = xSupplyColumns->getColumns();
                }
                break;
                default:
                {
                    xStatement = xConnection->prepareStatement(sCommand);
                    // not interested in any results
 
                    Reference< XPropertySet > xStatProps(xStatement,UNO_QUERY);
                    xStatProps->setPropertyValue(u"MaxRows"_ustr, 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 = std::move(aColumn);
        m_pImpl->aDropData[DataAccessDescriptorProperty::Connection] <<= xConnection;
        m_pImpl->aDropData[DataAccessDescriptorProperty::ColumnObject] <<= xField;
 
        m_pImpl->nDropAction = _rEvt.mnAction;
        m_pImpl->aDropPosPixel = _rEvt.maPosPixel;
        m_pImpl->xDroppedStatement = xStatement;
        m_pImpl->xDroppedResultSet = xResultSet;
 
        PostUserEvent(LINK(this, FmGridHeader, OnAsyncExecuteDrop), nullptr, true);
    }
    catch (Exception&)
    {
        TOOLS_WARN_EXCEPTION("svx", "caught an exception while creatin' the column !");
        ::comphelper::disposeComponent(xStatement);
        return DND_ACTION_NONE;
    }
 
    return DND_ACTION_LINK;
}
 
IMPL_LINK_NOARG( FmGridHeader, OnAsyncExecuteDrop, void*, void )
{
    OUString             sCommand, sFieldName,sURL;
    sal_Int32                   nCommandType = CommandType::COMMAND;
    Reference< XPropertySet >   xField;
    Reference< XConnection >    xConnection;
 
    OUString sDatasource = m_pImpl->aDropData.getDataSource();
    if ( sDatasource.isEmpty() && m_pImpl->aDropData.has(DataAccessDescriptorProperty::ConnectionResource) )
        m_pImpl->aDropData[DataAccessDescriptorProperty::ConnectionResource]    >>= sURL;
    m_pImpl->aDropData[DataAccessDescriptorProperty::Command]       >>= sCommand;
    m_pImpl->aDropData[DataAccessDescriptorProperty::CommandType]   >>= nCommandType;
    m_pImpl->aDropData[DataAccessDescriptorProperty::ColumnName]    >>= sFieldName;
    m_pImpl->aDropData[DataAccessDescriptorProperty::Connection]    >>= xConnection;
    m_pImpl->aDropData[DataAccessDescriptorProperty::ColumnObject]  >>= xField;
 
    try
    {
        // need number formats
        Reference< XNumberFormatsSupplier > xSupplier = getNumberFormats(xConnection, true);
        Reference< XNumberFormats >  xNumberFormats;
        if (xSupplier.is())
            xNumberFormats = xSupplier->getNumberFormats();
        if (!xNumberFormats.is())
        {
            ::comphelper::disposeComponent(m_pImpl->xDroppedResultSet);
            ::comphelper::disposeComponent(m_pImpl->xDroppedStatement);
            return;
        }
 
        // The field now needs two pieces of information:
        // a.) Name of the field for label and ControlSource
        // b.) FormatKey, to determine which field is to be created
        sal_Int32 nDataType = 0;
        xField->getPropertyValue(FM_PROP_FIELDTYPE) >>= nDataType;
        // these datatypes can not be processed in Gridcontrol
        switch (nDataType)
        {
            case DataType::BLOB:
            case DataType::LONGVARBINARY:
            case DataType::BINARY:
            case DataType::VARBINARY:
            case DataType::OTHER:
                ::comphelper::disposeComponent(m_pImpl->xDroppedResultSet);
                ::comphelper::disposeComponent(m_pImpl->xDroppedStatement);
                return;
        }
 
        // Creating the column
        Reference< XIndexContainer >  xCols(static_cast<FmGridControl*>(GetParent())->GetPeer()->getColumns());
        Reference< XGridColumnFactory >  xFactory(xCols, UNO_QUERY);
 
        sal_uInt16 nColId = GetItemId(m_pImpl->aDropPosPixel);
        // insert position, always before the current column
        sal_uInt16 nPos = GetModelColumnPos(nColId);
        Reference< XPropertySet >  xCol, xSecondCol;
 
        // Create Column based on type, default textfield
        std::vector<OUString> aPossibleTypes;
        std::vector<OUString> aImgResId;
        std::vector<TranslateId> aStrResId;
 
        switch (nDataType)
        {
            case DataType::BIT:
            case DataType::BOOLEAN:
                aPossibleTypes.emplace_back(FM_COL_CHECKBOX);
                aImgResId.emplace_back(RID_SVXBMP_CHECKBOX);
                aStrResId.emplace_back(RID_STR_PROPTITLE_CHECKBOX);
                break;
            case DataType::TINYINT:
            case DataType::SMALLINT:
            case DataType::INTEGER:
                aPossibleTypes.emplace_back(FM_COL_NUMERICFIELD);
                aImgResId.emplace_back(RID_SVXBMP_NUMERICFIELD);
                aStrResId.emplace_back(RID_STR_PROPTITLE_NUMERICFIELD);
                aPossibleTypes.emplace_back(FM_COL_FORMATTEDFIELD);
                aImgResId.emplace_back(RID_SVXBMP_FORMATTEDFIELD);
                aStrResId.emplace_back(RID_STR_PROPTITLE_FORMATTED);
                break;
            case DataType::REAL:
            case DataType::DOUBLE:
            case DataType::NUMERIC:
            case DataType::DECIMAL:
                aPossibleTypes.emplace_back(FM_COL_FORMATTEDFIELD);
                aImgResId.emplace_back(RID_SVXBMP_FORMATTEDFIELD);
                aStrResId.emplace_back(RID_STR_PROPTITLE_FORMATTED);
                aPossibleTypes.emplace_back(FM_COL_NUMERICFIELD);
                aImgResId.emplace_back(RID_SVXBMP_NUMERICFIELD);
                aStrResId.emplace_back(RID_STR_PROPTITLE_NUMERICFIELD);
                break;
            case DataType::TIMESTAMP:
                aPossibleTypes.emplace_back("dateandtimefield");
                aImgResId.emplace_back(RID_SVXBMP_DATE_N_TIME_FIELDS);
                aStrResId.emplace_back(RID_STR_DATE_AND_TIME);
                aPossibleTypes.emplace_back(FM_COL_DATEFIELD);
                aImgResId.emplace_back(RID_SVXBMP_DATEFIELD);
                aStrResId.emplace_back(RID_STR_PROPTITLE_DATEFIELD);
                aPossibleTypes.emplace_back(FM_COL_TIMEFIELD);
                aImgResId.emplace_back(RID_SVXBMP_TIMEFIELD);
                aStrResId.emplace_back(RID_STR_PROPTITLE_TIMEFIELD);
                aPossibleTypes.emplace_back(FM_COL_FORMATTEDFIELD);
                aImgResId.emplace_back(RID_SVXBMP_FORMATTEDFIELD);
                aStrResId.emplace_back(RID_STR_PROPTITLE_FORMATTED);
                break;
            case DataType::DATE:
                aPossibleTypes.emplace_back(FM_COL_DATEFIELD);
                aImgResId.emplace_back(RID_SVXBMP_DATEFIELD);
                aStrResId.emplace_back(RID_STR_PROPTITLE_DATEFIELD);
                aPossibleTypes.emplace_back(FM_COL_FORMATTEDFIELD);
                aImgResId.emplace_back(RID_SVXBMP_FORMATTEDFIELD);
                aStrResId.emplace_back(RID_STR_PROPTITLE_FORMATTED);
                break;
            case DataType::TIME:
                aPossibleTypes.emplace_back(FM_COL_TIMEFIELD);
                aImgResId.emplace_back(RID_SVXBMP_TIMEFIELD);
                aStrResId.emplace_back(RID_STR_PROPTITLE_TIMEFIELD);
                aPossibleTypes.emplace_back(FM_COL_FORMATTEDFIELD);
                aImgResId.emplace_back(RID_SVXBMP_FORMATTEDFIELD);
                aStrResId.emplace_back(RID_STR_PROPTITLE_FORMATTED);
                break;
            case DataType::CHAR:
            case DataType::VARCHAR:
            case DataType::LONGVARCHAR:
            default:
                aPossibleTypes.emplace_back(FM_COL_TEXTFIELD);
                aImgResId.emplace_back(RID_SVXBMP_EDITBOX);
                aStrResId.emplace_back(RID_STR_PROPTITLE_EDIT);
                aPossibleTypes.emplace_back(FM_COL_FORMATTEDFIELD);
                aImgResId.emplace_back(RID_SVXBMP_FORMATTEDFIELD);
                aStrResId.emplace_back(RID_STR_PROPTITLE_FORMATTED);
                break;
        }
        // if it's a currency field, a "currency field" option
        try
        {
            if  (   ::comphelper::hasProperty(FM_PROP_ISCURRENCY, xField)
                &&  ::comphelper::getBOOL(xField->getPropertyValue(FM_PROP_ISCURRENCY)))
            {
                aPossibleTypes.insert(aPossibleTypes.begin(), u"" FM_COL_CURRENCYFIELD ""_ustr);
                aImgResId.insert(aImgResId.begin(), RID_SVXBMP_CURRENCYFIELD);
                aStrResId.insert(aStrResId.begin(), RID_STR_PROPTITLE_CURRENCYFIELD);
            }
        }
        catch (const Exception&)
        {
            TOOLS_WARN_EXCEPTION("svx", "");
        }
 
        assert(aPossibleTypes.size() == aImgResId.size());
 
        bool bDateNTimeCol = false;
        if (!aPossibleTypes.empty())
        {
            OUString sPreferredType = aPossibleTypes[0];
            if ((m_pImpl->nDropAction == DND_ACTION_LINK) && (aPossibleTypes.size() > 1))
            {
                std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(nullptr, u"svx/ui/colsmenu.ui"_ustr));
                std::unique_ptr<weld::Menu> xTypeMenu(xBuilder->weld_menu(u"insertmenu"_ustr));
 
                int nMenuPos = 0;
                std::vector<OUString>::const_iterator iter;
                std::vector<TranslateId>::const_iterator striter;
                std::vector<OUString>::const_iterator imgiter;
                for (iter = aPossibleTypes.begin(), imgiter = aImgResId.begin(), striter = aStrResId.begin();
                     iter != aPossibleTypes.end(); ++iter, ++striter, ++imgiter)
                {
                    InsertMenuItem(*xTypeMenu, nMenuPos++, *iter, SvxResId(*striter), *imgiter);
                }
 
                ::tools::Rectangle aRect(m_pImpl->aDropPosPixel, Size(1,1));
                weld::Window* pParent = weld::GetPopupParent(*this, aRect);
                OUString sResult = xTypeMenu->popup_at_rect(pParent, aRect);
                if (!sResult.isEmpty())
                    sPreferredType = sResult;
            }
 
            bDateNTimeCol = sPreferredType == "dateandtimefield";
            sal_uInt16 nColCount = bDateNTimeCol ? 2 : 1;
            OUString sFieldService;
            while (nColCount--)
            {
                if (bDateNTimeCol)
                    sPreferredType = nColCount ? FM_COL_DATEFIELD : FM_COL_TIMEFIELD;
 
                sFieldService = sPreferredType;
                Reference< XPropertySet >  xThisRoundCol;
                if ( !sFieldService.isEmpty() )
                    xThisRoundCol = xFactory->createColumn(sFieldService);
                if (nColCount)
                    xSecondCol = std::move(xThisRoundCol);
                else
                    xCol = std::move(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++, u"" FM_COL_TEXTFIELD ""_ustr, SvxResId(RID_STR_PROPTITLE_EDIT), RID_SVXBMP_EDITBOX);
        InsertMenuItem(rInsertMenu, nMenuPos++, u"" FM_COL_CHECKBOX ""_ustr, SvxResId(RID_STR_PROPTITLE_CHECKBOX), RID_SVXBMP_CHECKBOX);
        InsertMenuItem(rInsertMenu, nMenuPos++, u"" FM_COL_COMBOBOX ""_ustr, SvxResId(RID_STR_PROPTITLE_COMBOBOX), RID_SVXBMP_COMBOBOX);
        InsertMenuItem(rInsertMenu, nMenuPos++, u"" FM_COL_LISTBOX ""_ustr, SvxResId(RID_STR_PROPTITLE_LISTBOX), RID_SVXBMP_LISTBOX);
        InsertMenuItem(rInsertMenu, nMenuPos++, u"" FM_COL_DATEFIELD ""_ustr, SvxResId(RID_STR_PROPTITLE_DATEFIELD), RID_SVXBMP_DATEFIELD);
        InsertMenuItem(rInsertMenu, nMenuPos++, u"" FM_COL_TIMEFIELD ""_ustr, SvxResId(RID_STR_PROPTITLE_TIMEFIELD), RID_SVXBMP_TIMEFIELD);
        InsertMenuItem(rInsertMenu, nMenuPos++, u"" FM_COL_NUMERICFIELD ""_ustr, SvxResId(RID_STR_PROPTITLE_NUMERICFIELD), RID_SVXBMP_NUMERICFIELD);
        InsertMenuItem(rInsertMenu, nMenuPos++, u"" FM_COL_CURRENCYFIELD ""_ustr, SvxResId(RID_STR_PROPTITLE_CURRENCYFIELD), RID_SVXBMP_CURRENCYFIELD);
        InsertMenuItem(rInsertMenu, nMenuPos++, u"" FM_COL_PATTERNFIELD ""_ustr, SvxResId(RID_STR_PROPTITLE_PATTERNFIELD), RID_SVXBMP_PATTERNFIELD);
        InsertMenuItem(rInsertMenu, nMenuPos++, u"" FM_COL_FORMATTEDFIELD ""_ustr, 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++, u"" FM_COL_TEXTFIELD"1"_ustr, SvxResId(RID_STR_PROPTITLE_EDIT), RID_SVXBMP_EDITBOX);
            if (nColType != TYPE_CHECKBOX)
                InsertMenuItem(rChangeMenu, nMenuPos++, u"" FM_COL_CHECKBOX"1"_ustr, SvxResId(RID_STR_PROPTITLE_CHECKBOX), RID_SVXBMP_CHECKBOX);
            if (nColType != TYPE_COMBOBOX)
                InsertMenuItem(rChangeMenu, nMenuPos++, u"" FM_COL_COMBOBOX"1"_ustr, SvxResId(RID_STR_PROPTITLE_COMBOBOX), RID_SVXBMP_COMBOBOX);
            if (nColType != TYPE_LISTBOX)
                InsertMenuItem(rChangeMenu, nMenuPos++, u"" FM_COL_LISTBOX"1"_ustr, SvxResId(RID_STR_PROPTITLE_LISTBOX), RID_SVXBMP_LISTBOX);
            if (nColType != TYPE_DATEFIELD)
                InsertMenuItem(rChangeMenu, nMenuPos++, u"" FM_COL_DATEFIELD"1"_ustr, SvxResId(RID_STR_PROPTITLE_DATEFIELD), RID_SVXBMP_DATEFIELD);
            if (nColType != TYPE_TIMEFIELD)
                InsertMenuItem(rChangeMenu, nMenuPos++, u"" FM_COL_TIMEFIELD"1"_ustr, SvxResId(RID_STR_PROPTITLE_TIMEFIELD), RID_SVXBMP_TIMEFIELD);
            if (nColType != TYPE_NUMERICFIELD)
                InsertMenuItem(rChangeMenu, nMenuPos++, u"" FM_COL_NUMERICFIELD"1"_ustr, SvxResId(RID_STR_PROPTITLE_NUMERICFIELD), RID_SVXBMP_NUMERICFIELD);
            if (nColType != TYPE_CURRENCYFIELD)
                InsertMenuItem(rChangeMenu, nMenuPos++, u"" FM_COL_CURRENCYFIELD"1"_ustr, SvxResId(RID_STR_PROPTITLE_CURRENCYFIELD), RID_SVXBMP_CURRENCYFIELD);
            if (nColType != TYPE_PATTERNFIELD)
                InsertMenuItem(rChangeMenu, nMenuPos++, u"" FM_COL_PATTERNFIELD"1"_ustr, SvxResId(RID_STR_PROPTITLE_PATTERNFIELD), RID_SVXBMP_PATTERNFIELD);
            if (nColType != TYPE_FORMATTEDFIELD)
                InsertMenuItem(rChangeMenu, nMenuPos++, u"" FM_COL_FORMATTEDFIELD"1"_ustr, SvxResId(RID_STR_PROPTITLE_FORMATTED), RID_SVXBMP_FORMATTEDFIELD);
        }
 
 
        rMenu.set_visible(u"change"_ustr, bDesignMode && bMarked && xCols.is());
        rMenu.set_sensitive(u"change"_ustr, bDesignMode && bMarked && xCols.is());
    }
    else
    {
        rMenu.set_visible(u"change"_ustr, false);
        rMenu.set_sensitive(u"change"_ustr, false);
    }
 
    rMenu.set_visible(u"insert"_ustr, bDesignMode && xCols.is());
    rMenu.set_sensitive(u"insert"_ustr, bDesignMode && xCols.is());
    rMenu.set_visible(u"delete"_ustr, bDesignMode && bMarked && xCols.is());
    rMenu.set_sensitive(u"delete"_ustr, bDesignMode && bMarked && xCols.is());
    rMenu.set_visible(u"column"_ustr, bDesignMode && bMarked && xCols.is());
    rMenu.set_sensitive(u"column"_ustr, 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.getValueTypeClass() == 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(u"more"_ustr, xCols.is() && (nHiddenCols > 16));
    rMenu.set_visible(u"show"_ustr, xCols.is() && (nHiddenCols > 0));
    rMenu.set_sensitive(u"show"_ustr, 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(u"hide"_ustr, bAllowHide);
    rMenu.set_sensitive(u"hide"_ustr, 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)
        {
            rMenu.set_active(u"column"_ustr, pItem->GetValue());
        }
    }
}
 
namespace {
 
enum InspectorAction { eOpenInspector, eCloseInspector, eUpdateInspector, eNone };
 
}
 
void FmGridHeader::PostExecuteColumnContextMenu(sal_uInt16 nColId, const weld::Menu& rMenu, const OUString& rExecutionResult)
{
    Reference< css::container::XIndexContainer >  xCols(static_cast<FmGridControl*>(GetParent())->GetPeer()->getColumns());
    sal_uInt16 nPos = GetModelColumnPos(nColId);
 
    OUString aFieldType;
    bool    bReplace = false;
    InspectorAction eInspectorAction = eNone;
 
    if (rExecutionResult == "delete")
    {
        Reference< XInterface > xCol(
            xCols->getByIndex(nPos), css::uno::UNO_QUERY);
        xCols->removeByIndex(nPos);
        ::comphelper::disposeComponent(xCol);
    }
    else if (rExecutionResult == "hide")
    {
        Reference< css::beans::XPropertySet > xCurCol( xCols->getByIndex(nPos), css::uno::UNO_QUERY);
        xCurCol->setPropertyValue(FM_PROP_HIDDEN, Any(true));
    }
    else if (rExecutionResult == "column")
    {
        eInspectorAction = rMenu.get_active(u"column"_ustr) ? 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, u"svx/ui/colsmenu.ui"_ustr));
    std::unique_ptr<weld::Menu> xContextMenu(xBuilder->weld_menu(u"menu"_ustr));
    std::unique_ptr<weld::Menu> xInsertMenu(xBuilder->weld_menu(u"insertmenu"_ustr));
    std::unique_ptr<weld::Menu> xChangeMenu(xBuilder->weld_menu(u"changemenu"_ustr));
    std::unique_ptr<weld::Menu> xShowMenu(xBuilder->weld_menu(u"showmenu"_ustr));
 
    // let derivatives modify the menu
    PreExecuteColumnContextMenu(nColId, *xContextMenu, *xInsertMenu, *xChangeMenu, *xShowMenu);
 
    bool bEmpty = true;
    for (int i = 0, nCount = xContextMenu->n_children(); i < nCount; ++i)
    {
        bEmpty = !xContextMenu->get_sensitive(xContextMenu->get_id(i));
        if (!bEmpty)
            break;
    }
    if (bEmpty)
        return;
 
    // execute the menu
    ::tools::Rectangle aRect(_rPreferredPos, Size(1,1));
    weld::Window* pParent = weld::GetPopupParent(*this, aRect);
    OUString sResult = xContextMenu->popup_at_rect(pParent, aRect);
 
    // let derivatives handle the result
    PostExecuteColumnContextMenu(nColId, *xContextMenu, sResult);
}
 
void FmGridHeader::Command(const CommandEvent& rEvt)
{
    switch (rEvt.GetCommand())
    {
        case CommandEventId::ContextMenu:
        {
            if (!rEvt.IsMouseEvent())
                return;
 
            triggerColumnContextMenu( rEvt.GetMousePosPixel() );
        }
        break;
        default:
            EditBrowserHeader::Command(rEvt);
    }
}
 
FmGridControl::FmGridControl(
                const Reference< css::uno::XComponentContext >& _rxContext,
                vcl::Window* pParent,
                FmXGridPeer* _pPeer,
                WinBits nBits)
        :DbGridControl(_rxContext, pParent, nBits)
        ,m_pPeer(_pPeer)
        ,m_nCurrentSelectedColumn(-1)
        ,m_nMarkedColumnId(BROWSER_INVALIDID)
        ,m_bSelecting(false)
        ,m_bInColumnMove(false)
{
    EnableInteractiveRowHeight( );
}
 
void FmGridControl::Command(const CommandEvent& _rEvt)
{
    if ( CommandEventId::ContextMenu == _rEvt.GetCommand() )
    {
        FmGridHeader* pMyHeader = static_cast< FmGridHeader* >( GetHeaderBar() );
        if ( pMyHeader && !_rEvt.IsMouseEvent() )
        {   // context menu requested by keyboard
            if  ( 1 == GetSelectColumnCount() || IsDesignMode() )
            {
                sal_uInt16 nSelId = GetColumnId(
                    sal::static_int_cast< sal_uInt16 >( FirstSelectedColumn() ) );
                ::tools::Rectangle aColRect( GetFieldRectPixel( 0, nSelId, false ) );
 
                Point aRelativePos( pMyHeader->ScreenToOutputPixel( OutputToScreenPixel( aColRect.TopCenter() ) ) );
                pMyHeader->triggerColumnContextMenu(aRelativePos);
 
                // handled
                return;
            }
        }
    }
 
    DbGridControl::Command( _rEvt );
}
 
// css::beans::XPropertyChangeListener
void FmGridControl::propertyChange(const css::beans::PropertyChangeEvent& evt)
{
    if (evt.PropertyName == FM_PROP_ROWCOUNT)
    {
        // if we're not in the main thread call AdjustRows asynchronously
        implAdjustInSolarThread(true);
        return;
    }
 
    const DbGridRowRef& xRow = GetCurrentRow();
    // no adjustment of the properties is carried out during positioning
    Reference<XPropertySet> xSet(evt.Source,UNO_QUERY);
    if (!(xRow.is() && (::cppu::any2bool(xSet->getPropertyValue(FM_PROP_ISNEW))|| CompareBookmark(getDataSource()->getBookmark(), xRow->GetBookmark()))))
        return;
 
    if (evt.PropertyName == FM_PROP_ISMODIFIED)
    {
        // modified or clean ?
        GridRowStatus eStatus = ::comphelper::getBOOL(evt.NewValue) ? GridRowStatus::Modified : GridRowStatus::Clean;
        if (eStatus != xRow->GetStatus())
        {
            xRow->SetStatus(eStatus);
            SolarMutexGuard aGuard;
            RowModified(GetCurrentPos());
        }
    }
}
 
void FmGridControl::SetDesignMode(bool bMode)
{
    bool bOldMode = IsDesignMode();
    DbGridControl::SetDesignMode(bMode);
    if (bOldMode == bMode)
        return;
 
    if (!bMode)
    {
        // cancel selection
        markColumn(USHRT_MAX);
    }
    else
    {
        Reference< css::container::XIndexContainer >  xColumns(GetPeer()->getColumns());
        Reference< css::view::XSelectionSupplier >  xSelSupplier(xColumns, UNO_QUERY);
        if (xSelSupplier.is())
        {
            Any aSelection = xSelSupplier->getSelection();
            Reference< css::beans::XPropertySet >  xColumn;
            if (aSelection.getValueTypeClass() == 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 : aDeletedRows)
                    {
                        try
                        {
                            if (!nSuccess)
                            {
                                m_pSeekCursor->moveToBookmark(m_pDataCursor->getBookmark());
                                SetSeekPos(m_pSeekCursor->getRow() - 1);
                                SelectRow(GetSeekPos());
                            }
                        }
                        catch(const Exception&)
                        {
                            // keep the seekpos in all cases
                            SetSeekPos(m_pSeekCursor->getRow() - 1);
                        }
                    }
                }
            }
 
            EndCursorAction();
            SetUpdateMode(true);
        }
        else // row could not be deleted
        {
            EndCursorAction();
            try
            {
                // currentrow is the insert row?
                if (!IsCurrentAppending())
                    getDataSource()->refreshRow();
            }
            catch(const Exception&)
            {
            }
        }
    }
 
    // if there is no selection anymore we can start editing
    if (!GetSelectRowCount())
        ActivateCell();
}
 
// XCurrentRecordListener
void FmGridControl::positioned()
{
    SAL_INFO("svx.fmcomp", "FmGridControl::positioned");
    // position on the data source (force it to be done in the main thread)
    implAdjustInSolarThread(false);
}
 
bool FmGridControl::commit()
{
    // execute commit only if an update is not already executed by the
    // css::form::component::GridControl
    if (!IsUpdating())
    {
        if (Controller().is() && Controller()->IsValueChangedFromSaved())
        {
            if (!SaveModified())
                return false;
        }
    }
    return true;
}
 
void FmGridControl::inserted()
{
    const DbGridRowRef& xRow = GetCurrentRow();
    if (!xRow.is())
        return;
 
    // line has been inserted, then reset the status and mode
    xRow->SetState(m_pDataCursor.get(), false);
    xRow->SetNew(false);
 
}
 
VclPtr<BrowserHeader> FmGridControl::imp_CreateHeaderBar(BrowseBox* pParent)
{
    DBG_ASSERT( pParent == this, "FmGridControl::imp_CreateHeaderBar: parent?" );
    return VclPtr<FmGridHeader>::Create( pParent );
}
 
void FmGridControl::markColumn(sal_uInt16 nId)
{
    if (!(GetHeaderBar() && m_nMarkedColumnId != nId))
        return;
 
    // deselect
    if (m_nMarkedColumnId != BROWSER_INVALIDID)
    {
        HeaderBarItemBits aBits = GetHeaderBar()->GetItemBits(m_nMarkedColumnId) & ~HeaderBarItemBits::FLAT;
        GetHeaderBar()->SetItemBits(m_nMarkedColumnId, aBits);
    }
 
 
    if (nId != BROWSER_INVALIDID)
    {
        HeaderBarItemBits aBits = GetHeaderBar()->GetItemBits(nId) | HeaderBarItemBits::FLAT;
        GetHeaderBar()->SetItemBits(nId, aBits);
    }
    m_nMarkedColumnId = nId;
}
 
bool FmGridControl::isColumnMarked(sal_uInt16 nId) const
{
    return m_nMarkedColumnId == nId;
}
 
tools::Long FmGridControl::QueryMinimumRowHeight()
{
    tools::Long const nMinimalLogicHeight = 20; // 0.2 cm
    tools::Long nMinimalPixelHeight = LogicToPixel(Point(0, nMinimalLogicHeight), MapMode(MapUnit::Map10thMM)).Y();
    return CalcZoom( nMinimalPixelHeight );
}
 
void FmGridControl::RowHeightChanged()
{
    DbGridControl::RowHeightChanged();
 
    Reference< XPropertySet > xModel( GetPeer()->getColumns(), UNO_QUERY );
    DBG_ASSERT( xModel.is(), "FmGridControl::RowHeightChanged: no model!" );
    if ( !xModel.is() )
        return;
 
    try
    {
        sal_Int32 nUnzoomedPixelHeight = CalcReverseZoom( GetDataRowHeight() );
        Any aProperty( static_cast<sal_Int32>(PixelToLogic( Point(0, nUnzoomedPixelHeight), MapMode(MapUnit::Map10thMM)).Y()) );
        xModel->setPropertyValue( FM_PROP_ROWHEIGHT, aProperty );
    }
    catch( const Exception& )
    {
        TOOLS_WARN_EXCEPTION( "svx", "FmGridControl::RowHeightChanged" );
    }
}
 
void FmGridControl::ColumnResized(sal_uInt16 nId)
{
    DbGridControl::ColumnResized(nId);
 
    // transfer value to the model
    DbGridColumn* pCol = DbGridControl::GetColumns()[ GetModelColumnPos(nId) ].get();
    const Reference< css::beans::XPropertySet >&  xColModel(pCol->getModel());
    if (xColModel.is())
    {
        Any aWidth;
        sal_Int32 nColumnWidth = GetColumnWidth(nId);
        nColumnWidth = CalcReverseZoom(nColumnWidth);
        // convert to 10THMM
        aWidth <<= static_cast<sal_Int32>(PixelToLogic(Point(nColumnWidth, 0), MapMode(MapUnit::Map10thMM)).X());
        xColModel->setPropertyValue(FM_PROP_WIDTH, aWidth);
    }
}
 
void FmGridControl::CellModified()
{
    DbGridControl::CellModified();
    GetPeer()->CellModified();
}
 
void FmGridControl::BeginCursorAction()
{
    DbGridControl::BeginCursorAction();
    m_pPeer->stopCursorListening();
}
 
void FmGridControl::EndCursorAction()
{
    m_pPeer->startCursorListening();
    DbGridControl::EndCursorAction();
}
 
void FmGridControl::ColumnMoved(sal_uInt16 nId)
{
    m_bInColumnMove = true;
 
    DbGridControl::ColumnMoved(nId);
    Reference< css::container::XIndexContainer >  xColumns(GetPeer()->getColumns());
 
    if (xColumns.is())
    {
        // locate the column and move in the model;
        // get ColumnPos
        DbGridColumn* pCol = DbGridControl::GetColumns()[ GetModelColumnPos(nId) ].get();
        Reference< css::beans::XPropertySet >  xCol;
 
        // inserting must be based on the column positions
        sal_Int32 i;
        Reference< XInterface > xCurrent;
        for (i = 0; !xCol.is() && i < xColumns->getCount(); i++)
        {
            xCurrent.set(xColumns->getByIndex(i), css::uno::UNO_QUERY);
            if (xCurrent == pCol->getModel())
            {
                xCol = pCol->getModel();
                break;
            }
        }
 
        DBG_ASSERT(i < xColumns->getCount(), "Wrong css::sdbcx::Index");
        xColumns->removeByIndex(i);
        Any aElement;
        aElement <<= xCol;
        xColumns->insertByIndex(GetModelColumnPos(nId), aElement);
        pCol->setModel(xCol);
        // if the column which is shown here is selected ...
        if ( isColumnSelected(pCol) )
            markColumn(nId); // ... -> mark it
    }
 
    m_bInColumnMove = false;
}
 
void FmGridControl::InitColumnsByModels(const Reference< css::container::XIndexContainer >& xColumns)
{
    // reset columns;
    // if there is only one HandleColumn, then don't
    if (GetModelColCount())
    {
        RemoveColumns();
        InsertHandleColumn();
    }
 
    if (!xColumns.is())
        return;
 
    SetUpdateMode(false);
 
    // inserting must be based on the column positions
    sal_Int32 i;
    Any aWidth;
    for (i = 0; i < xColumns->getCount(); ++i)
    {
        Reference< css::beans::XPropertySet > xCol(
            xColumns->getByIndex(i), css::uno::UNO_QUERY);
 
        OUString aName(
            comphelper::getString(xCol->getPropertyValue(FM_PROP_LABEL)));
 
        aWidth = xCol->getPropertyValue(FM_PROP_WIDTH);
        sal_Int32 nWidth = 0;
        if (aWidth >>= nWidth)
            nWidth = LogicToPixel(Point(nWidth, 0), MapMode(MapUnit::Map10thMM)).X();
 
        AppendColumn(aName, static_cast<sal_uInt16>(nWidth));
        DbGridColumn* pCol = DbGridControl::GetColumns()[ i ].get();
        pCol->setModel(xCol);
    }
 
    // and now remove the hidden columns as well
    // (we did not already make it in the upper loop, since we would then have gotten
    // problems with the IDs of the columns: AppendColumn allocates them automatically,
    // but the column _after_ a hidden one needs an ID increased by one ...)
    Any aHidden;
    for (i = 0; i < xColumns->getCount(); ++i)
    {
        Reference< css::beans::XPropertySet > xCol( xColumns->getByIndex(i), css::uno::UNO_QUERY);
        aHidden = xCol->getPropertyValue(FM_PROP_HIDDEN);
        if (::comphelper::getBOOL(aHidden))
            HideColumn(GetColumnIdFromModelPos(static_cast<sal_uInt16>(i)));
    }
 
    SetUpdateMode(true);
}
 
void FmGridControl::InitColumnByField(
    DbGridColumn* _pColumn, const Reference< XPropertySet >& _rxColumnModel,
    const Reference< XNameAccess >& _rxFieldsByNames, const Reference< XIndexAccess >& _rxFieldsByIndex )
{
    DBG_ASSERT( _rxFieldsByNames == _rxFieldsByIndex, "FmGridControl::InitColumnByField: invalid container interfaces!" );
 
    // lookup the column which belongs to the control source
    OUString sFieldName;
    _rxColumnModel->getPropertyValue( FM_PROP_CONTROLSOURCE ) >>= sFieldName;
    Reference< XPropertySet > xField;
    _rxColumnModel->getPropertyValue( FM_PROP_BOUNDFIELD ) >>= xField;
 
 
    if ( !xField.is() && /*sFieldName.getLength() && */_rxFieldsByNames->hasByName( sFieldName ) ) // #i93452# do not check for name length
        _rxFieldsByNames->getByName( sFieldName ) >>= xField;
 
    // determine the position of this column
    sal_Int32 nFieldPos = -1;
    if ( xField.is() )
    {
        Reference< XPropertySet > xCheck;
        sal_Int32 nFieldCount = _rxFieldsByIndex->getCount();
        for ( sal_Int32 i = 0; i < nFieldCount; ++i)
        {
            _rxFieldsByIndex->getByIndex( i ) >>= xCheck;
            if ( xField.get() == xCheck.get() )
            {
                nFieldPos = i;
                break;
            }
        }
    }
 
    if ( xField.is() && ( nFieldPos >= 0 ) )
    {
        // some data types are not allowed
        sal_Int32 nDataType = DataType::OTHER;
        xField->getPropertyValue( FM_PROP_FIELDTYPE ) >>= nDataType;
 
        bool bIllegalType = false;
        switch ( nDataType )
        {
            case DataType::BLOB:
            case DataType::LONGVARBINARY:
            case DataType::BINARY:
            case DataType::VARBINARY:
            case DataType::OTHER:
                bIllegalType = true;
                break;
        }
 
        if ( bIllegalType )
        {
            _pColumn->SetObject( static_cast<sal_Int16>(nFieldPos) );
            return;
        }
    }
 
    // the control type is determined by the ColumnServiceName
    static constexpr OUString s_sPropColumnServiceName = u"ColumnServiceName"_ustr;
    if ( !::comphelper::hasProperty( s_sPropColumnServiceName, _rxColumnModel ) )
        return;
 
    _pColumn->setModel( _rxColumnModel );
 
    OUString sColumnServiceName;
    _rxColumnModel->getPropertyValue( s_sPropColumnServiceName ) >>= sColumnServiceName;
 
    sal_Int32 nTypeId = getColumnTypeByModelName( sColumnServiceName );
    _pColumn->CreateControl( nFieldPos, xField, nTypeId );
}
 
void FmGridControl::InitColumnsByFields(const Reference< css::container::XIndexAccess >& _rxFields)
{
    if ( !_rxFields.is() )
        return;
 
    // initialize columns
    Reference< XIndexContainer > xColumns( GetPeer()->getColumns() );
    Reference< XNameAccess > xFieldsAsNames( _rxFields, UNO_QUERY );
 
    // inserting must be based on the column positions
    for (sal_Int32 i = 0; i < xColumns->getCount(); i++)
    {
        DbGridColumn* pCol = GetColumns()[ i ].get();
        OSL_ENSURE(pCol,"No grid column!");
        if ( pCol )
        {
            Reference< XPropertySet > xColumnModel(
                xColumns->getByIndex( i ), css::uno::UNO_QUERY);
 
            InitColumnByField( pCol, xColumnModel, xFieldsAsNames, _rxFields );
        }
    }
}
 
void FmGridControl::HideColumn(sal_uInt16 nId)
{
    DbGridControl::HideColumn(nId);
 
    sal_uInt16 nPos = GetModelColumnPos(nId);
    if (nPos == sal_uInt16(-1))
        return;
 
    DbGridColumn* pColumn = GetColumns()[ nPos ].get();
    if (pColumn->IsHidden())
        GetPeer()->columnHidden(pColumn);
 
    if (nId == m_nMarkedColumnId)
        m_nMarkedColumnId = sal_uInt16(-1);
}
 
bool FmGridControl::isColumnSelected(DbGridColumn const * _pColumn) const
{
    assert(_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: */

V530 The return value of function 'ExecuteList' is required to be utilized.

V547 Expression '2 == nCommandType' is always true.

V547 Expression 'bIllegalType' is always true.

V785 Constant expression in switch statement.

V785 Constant expression in switch statement.

V785 Constant expression in switch statement.

V785 Constant expression in switch statement.

V785 Constant expression in switch statement.