Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/svx/source/fmcomp/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 112 kB image not shown  

Quelle  gridctrl.cxx

  Sprache: C
 

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */


#include <sal/log.hxx>
#include <helpids.h>
#include <svx/gridctrl.hxx>
#include <gridcell.hxx>
#include <svx/fmtools.hxx>
#include <svtools/stringtransfer.hxx>
#include <connectivity/dbtools.hxx>
#include <connectivity/dbconversion.hxx>

#include <fmprop.hxx>
#include <com/sun/star/sdbc/ResultSetConcurrency.hpp>
#include <com/sun/star/accessibility/XAccessible.hpp>
#include <com/sun/star/sdb/XResultSetAccess.hpp>
#include <com/sun/star/sdb/RowChangeAction.hpp>
#include <com/sun/star/sdb/XRowsChangeBroadcaster.hpp>
#include <com/sun/star/sdbc/SQLException.hpp>
#include <com/sun/star/sdbc/XResultSetUpdate.hpp>
#include <com/sun/star/sdbc/XRowSet.hpp>
#include <com/sun/star/sdbcx/Privilege.hpp>
#include <com/sun/star/util/NumberFormatter.hpp>
#include <com/sun/star/util/XNumberFormatsSupplier.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/PropertyChangeEvent.hpp>
#include <com/sun/star/container/XIndexAccess.hpp>
#include <comphelper/diagnose_ex.hxx>
#include <tools/debug.hxx>
#include <tools/fract.hxx>
#include <vcl/settings.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <vcl/weldutils.hxx>

#include <svx/strings.hrc>

#include <svx/dialmgr.hxx>
#include <sdbdatacolumn.hxx>

#include <comphelper/property.hxx>
#include <comphelper/types.hxx>
#include <cppuhelper/implbase.hxx>

#include <algorithm>
#include <cstdlib>
#include <memory>

using namespace ::dbtools;
using namespace ::dbtools::DBTypeConversion;
using namespace ::svxform;
using namespace ::svt;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::sdbc;
using namespace ::com::sun::star::sdbcx;
using namespace ::com::sun::star::sdb;
using namespace ::com::sun::star::datatransfer;
using namespace ::com::sun::star::container;
using namespace com::sun::star::accessibility;

#define ROWSTATUS(row) (!row.is() ? "NULL" : row->GetStatus() == GridRowStatus::Clean ? "CLEAN" : row->GetStatus() == GridRowStatus::Modified ? "MODIFIED" : row->GetStatus() == GridRowStatus::Deleted ? "DELETED" : "INVALID")

constexpr auto DEFAULT_BROWSE_MODE =
              BrowserMode::COLUMNSELECTION
            | BrowserMode::MULTISELECTION
            | BrowserMode::KEEPHIGHLIGHT
            | BrowserMode::TRACKING_TIPS
            | BrowserMode::HLINES
            | BrowserMode::VLINES
            | BrowserMode::HEADERBAR_NEW;

class RowSetEventListener : public ::cppu::WeakImplHelper<XRowsChangeListener>
{
    VclPtr<DbGridControl> m_pControl;
public:
    explicit RowSetEventListener(DbGridControl* i_pControl) : m_pControl(i_pControl)
    {
    }

private:
    // XEventListener
    virtual void SAL_CALL disposing(const css::lang::EventObject& /*i_aEvt*/) override
    {
    }
    virtual void SAL_CALL rowsChanged(const css::sdb::RowsChangeEvent& i_aEvt) override
    {
        if ( i_aEvt.Action != RowChangeAction::UPDATE )
            return;

        ::DbGridControl::GrantControlAccess aAccess;
        CursorWrapper* pSeek = m_pControl->GetSeekCursor(aAccess);
        const DbGridRowRef& rSeekRow = m_pControl->GetSeekRow(aAccess);
        for(const Any& rBookmark : i_aEvt.Bookmarks)
        {
            pSeek->moveToBookmark(rBookmark);
            // get the data
            rSeekRow->SetState(pSeek, true);
            sal_Int32 nSeekPos = pSeek->getRow() - 1;
            m_pControl->SetSeekPos(nSeekPos,aAccess);
            m_pControl->RowModified(nSeekPos);
        }
    }
};

class GridFieldValueListener : protected ::comphelper::OPropertyChangeListener
{
    DbGridControl&                      m_rParent;
    rtl::Reference<::comphelper::OPropertyChangeMultiplexer> m_pRealListener;
    sal_uInt16                          m_nId;
    sal_Int16                           m_nSuspended;
    bool                                m_bDisposed : 1;

public:
    GridFieldValueListener(DbGridControl& _rParent, const Reference< XPropertySet >& ;xField, sal_uInt16 _nId);
    virtual ~GridFieldValueListener() override;

    virtual void _propertyChanged(const PropertyChangeEvent& evt) override;

    void suspend() { ++m_nSuspended; }
    void resume() { --m_nSuspended; }

    void dispose();
};

GridFieldValueListener::GridFieldValueListener(DbGridControl& _rParent, const Reference< XPropertySet >& _rField, sal_uInt16 _nId)
    :OPropertyChangeListener()
    ,m_rParent(_rParent)
    ,m_nId(_nId)
    ,m_nSuspended(0)
    ,m_bDisposed(false)
{
    if (_rField.is())
    {
        m_pRealListener = new ::comphelper::OPropertyChangeMultiplexer(this, _rField);
        m_pRealListener->addProperty(FM_PROP_VALUE);
    }
}

GridFieldValueListener::~GridFieldValueListener()
{
    dispose();
}

void GridFieldValueListener::_propertyChanged(const PropertyChangeEvent& /*_evt*/)
{
    DBG_ASSERT(m_nSuspended>=0, "GridFieldValueListener::_propertyChanged : resume > suspend !");
    if (m_nSuspended <= 0)
        m_rParent.FieldValueChanged(m_nId);
}

void GridFieldValueListener::dispose()
{
    if (m_bDisposed)
    {
        DBG_ASSERT(!m_pRealListener, "GridFieldValueListener::dispose : inconsistent !");
        return;
    }

    if (m_pRealListener.is())
    {
        m_pRealListener->dispose();
        m_pRealListener.clear();
    }

    m_bDisposed = true;
    m_rParent.FieldListenerDisposing(m_nId);
}

class DisposeListenerGridBridge : public FmXDisposeListener
{
    DbGridControl&          m_rParent;
    rtl::Reference<FmXDisposeMultiplexer>  m_xRealListener;

public:
    DisposeListenerGridBridge(  DbGridControl& _rParent, const Reference< XComponent >&&nbsp;_rxObject);
    virtual ~DisposeListenerGridBridge() override;

    virtual void disposing(sal_Int16 _nId) override { m_rParent.disposing(_nId); }
};

DisposeListenerGridBridge::DisposeListenerGridBridge(DbGridControl& _rParent, const Reference< XComponent >& _rxObject)
    :FmXDisposeListener()
    ,m_rParent(_rParent)
{

    if (_rxObject.is())
    {
        m_xRealListener = new FmXDisposeMultiplexer(this, _rxObject);
    }
}

DisposeListenerGridBridge::~DisposeListenerGridBridge()
{
    if (m_xRealListener.is())
    {
        m_xRealListener->dispose();
    }
}

const DbGridControlNavigationBarState ControlMap[] =
    {
        DbGridControlNavigationBarState::Text,
        DbGridControlNavigationBarState::Absolute,
        DbGridControlNavigationBarState::Of,
        DbGridControlNavigationBarState::Count,
        DbGridControlNavigationBarState::First,
        DbGridControlNavigationBarState::Next,
        DbGridControlNavigationBarState::Prev,
        DbGridControlNavigationBarState::Last,
        DbGridControlNavigationBarState::New,
        DbGridControlNavigationBarState::NONE
    };

bool CompareBookmark(const Any& aLeft, const Any& aRight)
{
    return aLeft == aRight;
}

class FmXGridSourcePropListener : public ::comphelper::OPropertyChangeListener
{
    VclPtr<DbGridControl> m_pParent;

    sal_Int16           m_nSuspended;

public:
    explicit FmXGridSourcePropListener(DbGridControl* _pParent);

    void suspend() { ++m_nSuspended; }
    void resume() { --m_nSuspended; }

    virtual void _propertyChanged(const PropertyChangeEvent& evt) override;
};

FmXGridSourcePropListener::FmXGridSourcePropListener(DbGridControl* _pParent)
    :OPropertyChangeListener()
    ,m_pParent(_pParent)
    ,m_nSuspended(0)
{
    DBG_ASSERT(m_pParent, "FmXGridSourcePropListener::FmXGridSourcePropListener : invalid parent !");
}

void FmXGridSourcePropListener::_propertyChanged(const PropertyChangeEvent& evt)
{
    DBG_ASSERT(m_nSuspended>=0, "FmXGridSourcePropListener::_propertyChanged : resume > suspend !");
    if (m_nSuspended <= 0)
        m_pParent->DataSourcePropertyChanged(evt);
}

const int nReserveNumDigits = 7;

NavigationBar::AbsolutePos::AbsolutePos(std::unique_ptr<weld::Entry> xEntry, NavigationBar* pBar)
    : RecordItemWindowBase(std::move(xEntry))
    , m_xParent(pBar)
{
}

bool NavigationBar::AbsolutePos::DoKeyInput(const KeyEvent& rEvt)
{
    if (rEvt.GetKeyCode() == KEY_TAB)
    {
        m_xParent->GetParent()->GrabFocus();
        return true;
    }
    return RecordItemWindowBase::DoKeyInput(rEvt);
}

void NavigationBar::AbsolutePos::PositionFired(sal_Int64 nRecord)
{
    m_xParent->PositionDataSource(nRecord);
    m_xParent->InvalidateState(DbGridControlNavigationBarState::Absolute);
}

void NavigationBar::PositionDataSource(sal_Int32 nRecord)
{
    if (m_bPositioning)
        return;
    // the MoveToPosition may cause a LoseFocus which would lead to a second MoveToPosition,
    // so protect against this recursion
    m_bPositioning = true;
    static_cast<DbGridControl*>(GetParent())->MoveToPosition(nRecord - 1);
    m_bPositioning = false;
}

NavigationBar::NavigationBar(DbGridControl* pParent)
    : InterimItemWindow(pParent, u"svx/ui/navigationbar.ui"_ustr, u"NavigationBar"_ustr)
    , m_xRecordText(m_xBuilder->weld_label(u"recordtext"_ustr))
    , m_xAbsolute(new NavigationBar::AbsolutePos(m_xBuilder->weld_entry(u"entry-noframe"_ustr), this))
    , m_xRecordOf(m_xBuilder->weld_label(u"recordof"_ustr))
    , m_xRecordCount(m_xBuilder->weld_label(u"recordcount"_ustr))
    , m_xFirstBtn(m_xBuilder->weld_button(u"first"_ustr))
    , m_xPrevBtn(m_xBuilder->weld_button(u"prev"_ustr))
    , m_xNextBtn(m_xBuilder->weld_button(u"next"_ustr))
    , m_xLastBtn(m_xBuilder->weld_button(u"last"_ustr))
    , m_xNewBtn(m_xBuilder->weld_button(u"new"_ustr))
    , m_xPrevRepeater(std::make_shared<weld::ButtonPressRepeater>(*m_xPrevBtn, LINK(this,NavigationBar,OnClick)))
    , m_xNextRepeater(std::make_shared<weld::ButtonPressRepeater>(*m_xNextBtn, LINK(this,NavigationBar,OnClick)))
    , m_nCurrentPos(-1)
    , m_bPositioning(false)
{
    m_xFirstBtn->set_help_id(HID_GRID_TRAVEL_FIRST);
    m_xPrevBtn->set_help_id(HID_GRID_TRAVEL_PREV);
    m_xNextBtn->set_help_id(HID_GRID_TRAVEL_NEXT);
    m_xLastBtn->set_help_id(HID_GRID_TRAVEL_LAST);
    m_xNewBtn->set_help_id(HID_GRID_TRAVEL_NEW);
    m_xAbsolute->set_help_id(HID_GRID_TRAVEL_ABSOLUTE);
    m_xRecordCount->set_help_id(HID_GRID_NUMBEROFRECORDS);

    // set handlers for buttons
    m_xFirstBtn->connect_clicked(LINK(this,NavigationBar,OnClick));
    m_xLastBtn->connect_clicked(LINK(this,NavigationBar,OnClick));
    m_xNewBtn->connect_clicked(LINK(this,NavigationBar,OnClick));

    m_xRecordText->set_label(SvxResId(RID_STR_REC_TEXT));
    m_xRecordOf->set_label(SvxResId(RID_STR_REC_FROM_TEXT));
    m_xRecordCount->set_label(OUString('?'));

    vcl::Font aApplFont(Application::GetSettings().GetStyleSettings().GetToolFont());
    SetPointFontAndZoom(aApplFont, Fraction(1, 1));

    m_xContainer->connect_size_allocate(LINK(this, NavigationBar, SizeAllocHdl));
}

IMPL_LINK(NavigationBar, SizeAllocHdl, const Size&, rSize, void)
{
    if (rSize == m_aLastAllocSize)
        return;
    m_aLastAllocSize = rSize;
    static_cast<DbGridControl*>(GetParent())->RearrangeAtIdle();
}

NavigationBar::~NavigationBar()
{
    disposeOnce();
}

void NavigationBar::dispose()
{
    m_xRecordText.reset();
    m_xAbsolute.reset();
    m_xRecordOf.reset();
    m_xRecordCount.reset();
    m_xFirstBtn.reset();
    m_xPrevBtn.reset();
    m_xNextBtn.reset();
    m_xLastBtn.reset();
    m_xNewBtn.reset();
    InterimItemWindow::dispose();
}

sal_uInt16 NavigationBar::GetPreferredWidth() const
{
    return m_xContainer->get_preferred_size().Width();
}

IMPL_LINK(NavigationBar, OnClick, weld::Button&, rButton, void)
{
    DbGridControl* pParent = static_cast<DbGridControl*>(GetParent());

    if (pParent->m_aMasterSlotExecutor.IsSet())
    {
        bool lResult = false;
        if (&rButton == m_xFirstBtn.get())
            lResult = pParent->m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::First);
        else if( &rButton == m_xPrevBtn.get() )
            lResult = pParent->m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::Prev);
        else if( &rButton == m_xNextBtn.get() )
            lResult = pParent->m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::Next);
        else if( &rButton == m_xLastBtn.get() )
            lResult = pParent->m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::Last);
        else if( &rButton == m_xNewBtn.get() )
            lResult = pParent->m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::New);

        if (lResult)
            // the link already handled it
            return;
    }

    if (&rButton == m_xFirstBtn.get())
        pParent->MoveToFirst();
    else if( &rButton == m_xPrevBtn.get() )
        pParent->MoveToPrev();
    else if( &rButton == m_xNextBtn.get() )
        pParent->MoveToNext();
    else if( &rButton == m_xLastBtn.get() )
        pParent->MoveToLast();
    else if( &rButton == m_xNewBtn.get() )
        pParent->AppendNew();
}

void NavigationBar::InvalidateAll(sal_Int32 nCurrentPos, bool bAll)
{
    if (!(m_nCurrentPos != nCurrentPos || nCurrentPos < 0 || bAll))
        return;

    DbGridControl* pParent = static_cast<DbGridControl*>(GetParent());

    sal_Int32 nAdjustedRowCount = pParent->GetRowCount() - ((pParent->GetOptions() & DbGridControlOptions::Insert) ? 2 : 1);

    // check if everything needs to be invalidated
    bAll = bAll || m_nCurrentPos <= 0;
    bAll = bAll || nCurrentPos <= 0;
    bAll = bAll || m_nCurrentPos >= nAdjustedRowCount;
    bAll = bAll || nCurrentPos >= nAdjustedRowCount;

    if ( bAll )
    {
        m_nCurrentPos = nCurrentPos;
        int i = 0;
        while (ControlMap[i] != DbGridControlNavigationBarState::NONE)
            SetState(ControlMap[i++]);
    }
    else    // is in the center
    {
        m_nCurrentPos = nCurrentPos;
        SetState(DbGridControlNavigationBarState::Count);
        SetState(DbGridControlNavigationBarState::Absolute);
    }
}

bool NavigationBar::GetState(DbGridControlNavigationBarState nWhich) const
{
    DbGridControl* pParent = static_cast<DbGridControl*>(GetParent());

    if (!pParent->IsOpen() || pParent->IsDesignMode() || !pParent->IsEnabled()
        || pParent->IsFilterMode() )
        return false;
    else
    {
        // check if we have a master state provider
        if (pParent->m_aMasterStateProvider.IsSet())
        {
            tools::Long nState = pParent->m_aMasterStateProvider.Call( nWhich );
            if (nState>=0)
                return (nState>0);
        }

        bool bAvailable = true;

        switch (nWhich)
        {
            case DbGridControlNavigationBarState::First:
            case DbGridControlNavigationBarState::Prev:
                bAvailable = m_nCurrentPos > 0;
                break;
            case DbGridControlNavigationBarState::Next:
                if(pParent->m_bRecordCountFinal)
                {
                    bAvailable = m_nCurrentPos < pParent->GetRowCount() - 1;
                    if (!bAvailable && pParent->GetOptions() & DbGridControlOptions::Insert)
                        bAvailable = (m_nCurrentPos == pParent->GetRowCount() - 2) && pParent->IsModified();
                }
                break;
            case DbGridControlNavigationBarState::Last:
                if(pParent->m_bRecordCountFinal)
                {
                    if (pParent->GetOptions() & DbGridControlOptions::Insert)
                        bAvailable = pParent->IsCurrentAppending() ? pParent->GetRowCount() > 1 :
                                     m_nCurrentPos != pParent->GetRowCount() - 2;
                    else
                        bAvailable = m_nCurrentPos != pParent->GetRowCount() - 1;
                }
                break;
            case DbGridControlNavigationBarState::New:
                bAvailable = (pParent->GetOptions() & DbGridControlOptions::Insert) && pParent->GetRowCount() && m_nCurrentPos < pParent->GetRowCount() - 1;
                break;
            case DbGridControlNavigationBarState::Absolute:
                bAvailable = pParent->GetRowCount() > 0;
                break;
            defaultbreak;
        }
        return bAvailable;
    }
}

void NavigationBar::SetState(DbGridControlNavigationBarState nWhich)
{
    bool bAvailable = GetState(nWhich);
    DbGridControl* pParent = static_cast<DbGridControl*>(GetParent());
    weld::Widget* pWnd = nullptr;
    switch (nWhich)
    {
        case DbGridControlNavigationBarState::First:
            pWnd = m_xFirstBtn.get();
            break;
        case DbGridControlNavigationBarState::Prev:
            pWnd = m_xPrevBtn.get();
            break;
        case DbGridControlNavigationBarState::Next:
            pWnd = m_xNextBtn.get();
            break;
        case DbGridControlNavigationBarState::Last:
            pWnd = m_xLastBtn.get();
            break;
        case DbGridControlNavigationBarState::New:
            pWnd = m_xNewBtn.get();
            break;
        case DbGridControlNavigationBarState::Absolute:
            pWnd = m_xAbsolute->GetWidget();
            if (bAvailable)
                m_xAbsolute->set_text(OUString::number(m_nCurrentPos + 1));
            else
                m_xAbsolute->set_text(OUString());
            break;
        case DbGridControlNavigationBarState::Text:
            pWnd = m_xRecordText.get();
            break;
        case DbGridControlNavigationBarState::Of:
            pWnd = m_xRecordOf.get();
            break;
        case DbGridControlNavigationBarState::Count:
        {
            pWnd = m_xRecordCount.get();
            OUString aText;
            if (bAvailable)
            {
                if (pParent->GetOptions() & DbGridControlOptions::Insert)
                {
                    if (pParent->IsCurrentAppending() && !pParent->IsModified())
                        aText = OUString::number(pParent->GetRowCount());
                    else
                        aText = OUString::number(pParent->GetRowCount() - 1);
                }
                else
                    aText = OUString::number(pParent->GetRowCount());
                if(!pParent->m_bRecordCountFinal)
                    aText += " *";
            }
            else
                aText.clear();

            // add the number of selected rows, if applicable
            if (pParent->GetSelectRowCount())
            {
                OUString aExtendedInfo = aText + " (" +
                    OUString::number(pParent->GetSelectRowCount()) + ")";
                m_xRecordCount->set_label(aExtendedInfo);
            }
            else
                m_xRecordCount->set_label(aText);

            pParent->SetRealRowCount(aText);
        }   break;
        defaultbreak;
    }
    DBG_ASSERT(pWnd, "no window");
    if (!(pWnd && (pWnd->get_sensitive() != bAvailable)))
        return;

    // this "pWnd->IsEnabled() != bAvailable" is a little hack : Window::Enable always generates a user
    // event (ImplGenerateMouseMove) even if nothing happened. This may lead to some unwanted effects, so we
    // do this check.
    // For further explanation see Bug 69900.
    pWnd->set_sensitive(bAvailable);
    if (!bAvailable)
    {
        if (pWnd == m_xNextBtn.get())
            m_xNextRepeater->Stop();
        else if (pWnd == m_xPrevBtn.get())
            m_xPrevRepeater->Stop();
    }
}

static void ScaleButton(weld::Button& rBtn, const Fraction& rZoom)
{
    rBtn.set_size_request(-1, -1);
    Size aPrefSize = rBtn.get_preferred_size();
    aPrefSize.setWidth(std::round(double(aPrefSize.Width() * rZoom)));
    aPrefSize.setHeight(std::round(double(aPrefSize.Height() * rZoom)));
    rBtn.set_size_request(aPrefSize.Width(), aPrefSize.Height());
}

void NavigationBar::SetPointFontAndZoom(const vcl::Font& rFont, const Fraction&&nbsp;rZoom)
{
    vcl::Font aFont(rFont);
    if (rZoom.GetNumerator() != rZoom.GetDenominator())
    {
        Size aSize = aFont.GetFontSize();
        aSize.setWidth(std::round(double(aSize.Width() * rZoom)));
        aSize.setHeight(std::round(double(aSize.Height() * rZoom)));
        aFont.SetFontSize(aSize);
    }

    m_xRecordText->set_font(aFont);
    m_xAbsolute->GetWidget()->set_font(aFont);
    m_xRecordOf->set_font(aFont);
    m_xRecordCount->set_font(aFont);

    auto nReserveWidth = m_xRecordCount->get_approximate_digit_width() * nReserveNumDigits;
    m_xAbsolute->GetWidget()->set_size_request(nReserveWidth, -1);
    m_xRecordCount->set_size_request(nReserveWidth, -1);

    ScaleButton(*m_xFirstBtn, rZoom);
    ScaleButton(*m_xPrevBtn, rZoom);
    ScaleButton(*m_xNextBtn, rZoom);
    ScaleButton(*m_xLastBtn, rZoom);
    ScaleButton(*m_xNewBtn, rZoom);

    SetZoom(rZoom);

    InvalidateChildSizeCache();
}

DbGridRow::DbGridRow():m_eStatus(GridRowStatus::Clean), m_bIsNew(true)
{}

DbGridRow::DbGridRow(CursorWrapper* pCur, bool bPaintCursor)
          :m_bIsNew(false)
{

    if (pCur && pCur->Is())
    {
        Reference< XIndexAccess >  xColumns(pCur->getColumns(), UNO_QUERY);
        for (sal_Int32 i = 0; i < xColumns->getCount(); ++i)
        {
            Reference< XPropertySet > xColSet(
                xColumns->getByIndex(i), css::uno::UNO_QUERY);
            m_aVariants.emplace_back( new DataColumn(xColSet) );
        }

        if (pCur->rowDeleted())
            m_eStatus = GridRowStatus::Deleted;
        else
        {
            if (bPaintCursor)
                m_eStatus = (pCur->isAfterLast() || pCur->isBeforeFirst()) ? GridRowStatus::Invalid : GridRowStatus::Clean;
            else
            {
                const Reference< XPropertySet >& xSet = pCur->getPropertySet();
                if (xSet.is())
                {
                    m_bIsNew = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISNEW));
                    if (!m_bIsNew && (pCur->isAfterLast() || pCur->isBeforeFirst()))
                        m_eStatus = GridRowStatus::Invalid;
                    else if (::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISMODIFIED)))
                        m_eStatus = GridRowStatus::Modified;
                    else
                        m_eStatus = GridRowStatus::Clean;
                }
                else
                    m_eStatus = GridRowStatus::Invalid;
            }
        }
        if (!m_bIsNew && IsValid())
            m_aBookmark = pCur->getBookmark();
        else
            m_aBookmark = Any();
    }
    else
        m_eStatus = GridRowStatus::Invalid;
}

DbGridRow::~DbGridRow()
{
}

void DbGridRow::SetState(CursorWrapper* pCur, bool bPaintCursor)
{
    if (pCur && pCur->Is())
    {
        if (pCur->rowDeleted())
        {
            m_eStatus = GridRowStatus::Deleted;
            m_bIsNew = false;
        }
        else
        {
            m_eStatus = GridRowStatus::Clean;
            if (!bPaintCursor)
            {
                const Reference< XPropertySet >& xSet = pCur->getPropertySet();
                DBG_ASSERT(xSet.is(), "DbGridRow::SetState : invalid cursor !");

                if (::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISMODIFIED)))
                    m_eStatus = GridRowStatus::Modified;
                m_bIsNew = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISNEW));
            }
            else
                m_bIsNew = false;
        }

        try
        {
            if (!m_bIsNew && IsValid())
                m_aBookmark = pCur->getBookmark();
            else
                m_aBookmark = Any();
        }
        catch(SQLException&)
        {
            DBG_UNHANDLED_EXCEPTION("svx");
            m_aBookmark = Any();
            m_eStatus = GridRowStatus::Invalid;
            m_bIsNew = false;
        }
    }
    else
    {
        m_aBookmark = Any();
        m_eStatus = GridRowStatus::Invalid;
        m_bIsNew = false;
    }
}

DbGridControl::DbGridControl(
                Reference< XComponentContext > const & _rxContext,
                vcl::Window* pParent,
                WinBits nBits)
            :EditBrowseBox(pParent, EditBrowseBoxFlags::NONE, nBits, DEFAULT_BROWSE_MODE )
            ,m_xContext(_rxContext)
            ,m_aBar(VclPtr<NavigationBar>::Create(this))
            ,m_nAsynAdjustEvent(nullptr)
            ,m_pDataSourcePropListener(nullptr)
            ,m_pGridListener(nullptr)
            ,m_nSeekPos(-1)
            ,m_nTotalCount(-1)
            ,m_aRearrangeIdle("DbGridControl Rearrange Idle")
            ,m_aNullDate(::dbtools::DBTypeConversion::getStandardDate())
            ,m_nMode(DEFAULT_BROWSE_MODE)
            ,m_nCurrentPos(-1)
            ,m_nDeleteEvent(nullptr)
            ,m_nOptions(DbGridControlOptions::Readonly)
            ,m_nOptionMask(DbGridControlOptions::Insert | DbGridControlOptions::Update | DbGridControlOptions::Delete)
            ,m_nLastColId(sal_uInt16(-1))
            ,m_nLastRowId(-1)
            ,m_bDesignMode(false)
            ,m_bRecordCountFinal(false)
            ,m_bSynchDisplay(true)
            ,m_bHandle(true)
            ,m_bFilterMode(false)
            ,m_bWantDestruction(false)
            ,m_bPendingAdjustRows(false)
            ,m_bHideScrollbars( false )
            ,m_bUpdating(false)
{
    m_bNavigationBar = true;

    OUString sName(SvxResId(RID_STR_NAVIGATIONBAR));
    m_aBar->SetAccessibleName(sName);
    m_aBar->Show();
    ImplInitWindow( InitWindowFacet::All );

    m_aRearrangeIdle.SetInvokeHandler(LINK(this, DbGridControl, RearrangeHdl));
}

void DbGridControl::InsertHandleColumn()
{
    // BrowseBox has problems when painting without a handleColumn (hide it here)
    if (HasHandle())
        BrowseBox::InsertHandleColumn(GetDefaultColumnWidth(OUString()));
    else
        BrowseBox::InsertHandleColumn(0);
}

void DbGridControl::Init()
{
    VclPtr<BrowserHeader> pNewHeader = CreateHeaderBar(this);
    pHeader->SetMouseTransparent(false);

    SetHeaderBar(pNewHeader);
    SetMode(m_nMode);
    SetCursorColor(Color(0xFF, 0, 0));

    InsertHandleColumn();
}

DbGridControl::~DbGridControl()
{
    disposeOnce();
}

void DbGridControl::dispose()
{
    RemoveColumns();

    m_bWantDestruction = true;
    osl::MutexGuard aGuard(m_aDestructionSafety);
    if (!m_aFieldListeners.empty())
        DisconnectFromFields();
    m_pCursorDisposeListener.reset();

    if (m_nDeleteEvent)
        Application::RemoveUserEvent(m_nDeleteEvent);

    if (m_pDataSourcePropMultiplexer.is())
    {
        m_pDataSourcePropMultiplexer->dispose();
        m_pDataSourcePropMultiplexer.clear();    // this should delete the multiplexer
        delete m_pDataSourcePropListener;
        m_pDataSourcePropListener = nullptr;
    }
    m_xRowSetListener.clear();

    m_pDataCursor.reset();
    m_pSeekCursor.reset();

    m_aBar.disposeAndClear();

    m_aRearrangeIdle.Stop();

    EditBrowseBox::dispose();
}

void DbGridControl::RearrangeAtIdle()
{
    if (isDisposed())
        return;
    m_aRearrangeIdle.Start();
}

void DbGridControl::StateChanged( StateChangedType nType )
{
    EditBrowseBox::StateChanged( nType );

    switch (nType)
    {
        case StateChangedType::Mirroring:
            ImplInitWindow( InitWindowFacet::WritingMode );
            Invalidate();
            break;

        case StateChangedType::Zoom:
        {
            ImplInitWindow( InitWindowFacet::Font );
            RearrangeAtIdle();
        }
        break;
        case StateChangedType::ControlFont:
            ImplInitWindow( InitWindowFacet::Font );
            Invalidate();
            break;
        case StateChangedType::ControlForeground:
            ImplInitWindow( InitWindowFacet::Foreground );
            Invalidate();
            break;
        case StateChangedType::ControlBackground:
            ImplInitWindow( InitWindowFacet::Background );
            Invalidate();
            break;
       default:;
    }
}

void DbGridControl::DataChanged( const DataChangedEvent& rDCEvt )
{
    EditBrowseBox::DataChanged( rDCEvt );
    if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS ) &&
         (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
    {
        ImplInitWindow( InitWindowFacet::All );
        Invalidate();
    }
}

void DbGridControl::Select()
{
    EditBrowseBox::Select();

    // as the selected rows may have changed, update the according display in our navigation bar
    m_aBar->InvalidateState(DbGridControlNavigationBarState::Count);

    if (m_pGridListener)
        m_pGridListener->selectionChanged();
}

void DbGridControl::ImplInitWindow( const InitWindowFacet _eInitWhat )
{
    for (auto const & pCol : m_aColumns)
    {
        pCol->ImplInitWindow( GetDataWindow(), _eInitWhat );
    }

    if ( _eInitWhat & InitWindowFacet::WritingMode )
    {
        if ( m_bNavigationBar )
        {
            m_aBar->EnableRTL( IsRTLEnabled() );
        }
    }

    if ( _eInitWhat & InitWindowFacet::Font )
    {
        if ( m_bNavigationBar )
        {
            const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
            vcl::Font aFont = rStyleSettings.GetToolFont();
            if (IsControlFont())
                aFont.Merge(GetControlFont());

            m_aBar->SetPointFontAndZoom(aFont, GetZoom());
        }
    }

    if ( !(_eInitWhat & InitWindowFacet::Background) )
        return;

    if (IsControlBackground())
    {
        GetDataWindow().SetBackground(GetControlBackground());
        GetDataWindow().SetControlBackground(GetControlBackground());
        GetDataWindow().GetOutDev()->SetFillColor(GetControlBackground());
    }
    else
    {
        GetDataWindow().SetControlBackground();
        GetDataWindow().GetOutDev()->SetFillColor(GetOutDev()->GetFillColor());
    }
}

void DbGridControl::RemoveRows(bool bNewCursor)
{
    // Did the data cursor change?
    if (!bNewCursor)
    {
        m_pSeekCursor.reset();
        m_xPaintRow = m_xDataRow = m_xEmptyRow  = m_xCurrentRow = m_xSeekRow = nullptr;
        m_nCurrentPos = m_nSeekPos = -1;
        m_nOptions  = DbGridControlOptions::Readonly;

        RowRemoved(0, GetRowCount(), false);
        m_nTotalCount = -1;
    }
    else
    {
        RemoveRows();
    }
}

void DbGridControl::RemoveRows()
{
    // we're going to remove all columns and all row, so deactivate the current cell
    if (IsEditing())
        DeactivateCell();

    // de-initialize all columns
    // if there are columns, free all controllers
    for (auto const & pColumn : m_aColumns)
        pColumn->Clear();

    m_pSeekCursor.reset();
    m_pDataCursor.reset();

    m_xPaintRow = m_xDataRow = m_xEmptyRow  = m_xCurrentRow = m_xSeekRow = nullptr;
    m_nCurrentPos = m_nSeekPos = m_nTotalCount  = -1;
    m_nOptions  = DbGridControlOptions::Readonly;

    // reset number of sentences to zero in the browser
    EditBrowseBox::RemoveRows();
    m_aBar->InvalidateAll(m_nCurrentPos, true);
}

void DbGridControl::ArrangeControls(sal_uInt16& nX, sal_uInt16 nY)
{
    // positioning of the controls
    if (m_bNavigationBar)
    {
        tools::Rectangle aRect(GetControlArea());
        nX = m_aBar->GetPreferredWidth();
        m_aBar->SetPosSizePixel(Point(0, nY + 1), Size(nX, aRect.GetSize().Height() - 1));
    }
}

void DbGridControl::EnableHandle(bool bEnable)
{
    if (m_bHandle == bEnable)
        return;

    // HandleColumn is only hidden because there are a lot of problems while painting otherwise
    RemoveColumn( HandleColumnId );
    m_bHandle = bEnable;
    InsertHandleColumn();
}

namespace
{
    bool adjustModeForScrollbars( BrowserMode& _rMode, bool _bNavigationBar, bool _bHideScrollbars )
    {
        BrowserMode nOldMode = _rMode;

        if ( !_bNavigationBar )
        {
            _rMode &= ~BrowserMode::AUTO_HSCROLL;
        }

        if ( _bHideScrollbars )
        {
            _rMode |= BrowserMode::NO_HSCROLL | BrowserMode::NO_VSCROLL;
            _rMode &= ~BrowserMode( BrowserMode::AUTO_HSCROLL | BrowserMode::AUTO_VSCROLL );
        }
        else
        {
            _rMode |= BrowserMode::AUTO_HSCROLL | BrowserMode::AUTO_VSCROLL;
            _rMode &= ~BrowserMode( BrowserMode::NO_HSCROLL | BrowserMode::NO_VSCROLL );
        }

        // note: if we have a navigation bar, we always have an AUTO_HSCROLL. In particular,
        // _bHideScrollbars is ignored then
        if ( _bNavigationBar )
        {
            _rMode |= BrowserMode::AUTO_HSCROLL;
            _rMode &= ~BrowserMode::NO_HSCROLL;
        }

        return nOldMode != _rMode;
    }
}

void DbGridControl::EnableNavigationBar(bool bEnable)
{
    if (m_bNavigationBar == bEnable)
        return;

    m_bNavigationBar = bEnable;

    if (bEnable)
    {
        m_aBar->Show();
        m_aBar->Enable();
        m_aBar->InvalidateAll(m_nCurrentPos, true);

        if ( adjustModeForScrollbars( m_nMode, m_bNavigationBar, m_bHideScrollbars ) )
            SetMode( m_nMode );

        // get size of the reserved ControlArea
        Point aPoint = GetControlArea().TopLeft();
        sal_uInt16 nX = static_cast<sal_uInt16>(aPoint.X());

        ArrangeControls(nX, static_cast<sal_uInt16>(aPoint.Y()));
        ReserveControlArea(nX);
    }
    else
    {
        m_aBar->Hide();
        m_aBar->Disable();

        if ( adjustModeForScrollbars( m_nMode, m_bNavigationBar, m_bHideScrollbars ) )
            SetMode( m_nMode );

        ReserveControlArea();
    }
}

DbGridControlOptions DbGridControl::SetOptions(DbGridControlOptions nOpt)
{
    DBG_ASSERT(!m_xCurrentRow.is() || !m_xCurrentRow->IsModified(),
        "DbGridControl::SetOptions : please do not call when editing a record (things are much easier this way ;) !");

    // for the next setDataSource (which is triggered by a refresh, for instance)
    m_nOptionMask = nOpt;

    // normalize the new options
    Reference< XPropertySet > xDataSourceSet = m_pDataCursor->getPropertySet();
    if (xDataSourceSet.is())
    {
        // check what kind of options are available
        sal_Int32 nPrivileges = 0;
        xDataSourceSet->getPropertyValue(FM_PROP_PRIVILEGES) >>= nPrivileges;
        if ((nPrivileges & Privilege::INSERT) == 0)
            nOpt &= ~DbGridControlOptions::Insert;
        if ((nPrivileges & Privilege::UPDATE) == 0)
            nOpt &= ~DbGridControlOptions::Update;
        if ((nPrivileges & Privilege::DELETE) == 0)
            nOpt &= ~DbGridControlOptions::Delete;
    }
    else
        nOpt = DbGridControlOptions::Readonly;

    // need to do something after that ?
    if (nOpt == m_nOptions)
        return m_nOptions;

    // the 'update' option only affects our BrowserMode (with or w/o focus rect)
    BrowserMode nNewMode = m_nMode;
    if (!(m_nMode & BrowserMode::CURSOR_WO_FOCUS))
    {
        if (nOpt & DbGridControlOptions::Update)
            nNewMode |= BrowserMode::HIDECURSOR;
        else
            nNewMode &= ~BrowserMode::HIDECURSOR;
    }
    else
        nNewMode &= ~BrowserMode::HIDECURSOR;
        // should not be necessary if EnablePermanentCursor is used to change the cursor behaviour, but to be sure ...

    if (nNewMode != m_nMode)
    {
        SetMode(nNewMode);
        m_nMode = nNewMode;
    }

    // _after_ setting the mode because this results in an ActivateCell
    DeactivateCell();

    bool bInsertChanged = (nOpt & DbGridControlOptions::Insert) != (m_nOptions & DbGridControlOptions::Insert);
    m_nOptions = nOpt;
        // we need to set this before the code below because it indirectly uses m_nOptions

    // the 'insert' option affects our empty row
    if (bInsertChanged)
    {
        if (m_nOptions & DbGridControlOptions::Insert)
        {   // the insert option is to be set
            m_xEmptyRow = new DbGridRow();
            RowInserted(GetRowCount());
        }
        else
        {   // the insert option is to be reset
            m_xEmptyRow = nullptr;
            if ((GetCurRow() == GetRowCount() - 1) && (GetCurRow() > 0))
                GoToRowColumnId(GetCurRow() - 1, GetCurColumnId());
            RowRemoved(GetRowCount());
        }
    }

    // the 'delete' options has no immediate consequences

    ActivateCell();
    Invalidate();
    return m_nOptions;
}

void DbGridControl::ForceHideScrollbars()
{
    if ( m_bHideScrollbars )
        return;

    m_bHideScrollbars = true;

    if ( adjustModeForScrollbars( m_nMode, m_bNavigationBar, m_bHideScrollbars ) )
        SetMode( m_nMode );
}

void DbGridControl::EnablePermanentCursor(bool bEnable)
{
    if (IsPermanentCursorEnabled() == bEnable)
        return;

    if (bEnable)
    {
        m_nMode &= ~BrowserMode::HIDECURSOR;     // without this BrowserMode::CURSOR_WO_FOCUS won't have any affect
        m_nMode |= BrowserMode::CURSOR_WO_FOCUS;
    }
    else
    {
        if (m_nOptions & DbGridControlOptions::Update)
            m_nMode |= BrowserMode::HIDECURSOR;      // no cursor at all
        else
            m_nMode &= ~BrowserMode::HIDECURSOR;     // at least the "non-permanent" cursor

        m_nMode &= ~BrowserMode::CURSOR_WO_FOCUS;
    }
    SetMode(m_nMode);

    bool bWasEditing = IsEditing();
    DeactivateCell();
    if (bWasEditing)
        ActivateCell();
}

bool DbGridControl::IsPermanentCursorEnabled() const
{
    return (m_nMode & BrowserMode::CURSOR_WO_FOCUS) && !(m_nMode & BrowserMode::HIDECURSOR);
}

void DbGridControl::refreshController(sal_uInt16 _nColId, GrantControlAccess /*_aAccess*/)
{
    if ((GetCurColumnId() == _nColId) && IsEditing())
    {   // the controller which is currently active needs to be refreshed
        DeactivateCell();
        ActivateCell();
    }
}

void DbGridControl::setDataSource(const Reference< XRowSet >& _xCursor, DbGridControlOptions nOpts)
{
    if (!_xCursor.is() && !m_pDataCursor)
        return;

    if (m_pDataSourcePropMultiplexer.is())
    {
        m_pDataSourcePropMultiplexer->dispose();
        m_pDataSourcePropMultiplexer.clear();    // this should delete the multiplexer
        delete m_pDataSourcePropListener;
        m_pDataSourcePropListener = nullptr;
    }
    m_xRowSetListener.clear();

    // is the new cursor valid ?
    // the cursor is only valid if it contains some columns
    // if there is no cursor or the cursor is not valid we have to clean up and leave
    if (!_xCursor.is() || !Reference< XColumnsSupplier > (_xCursor, UNO_QUERY_THROW)->getColumns()->hasElements())
    {
        RemoveRows();
        return;
    }

    // did the data cursor change?
    sal_uInt16 nCurPos = GetColumnPos(GetCurColumnId());

    SetUpdateMode(false);
    RemoveRows();
    DisconnectFromFields();

    m_pCursorDisposeListener.reset();

    {
        ::osl::MutexGuard aGuard(m_aAdjustSafety);
        if (m_nAsynAdjustEvent)
        {
            // the adjust was thought to work with the old cursor which we don't have anymore
            RemoveUserEvent(m_nAsynAdjustEvent);
            m_nAsynAdjustEvent = nullptr;
        }
    }

    // get a new formatter and data cursor
    m_xFormatter = nullptr;
    Reference< css::util::XNumberFormatsSupplier >  xSupplier = getNumberFormats(getConnection(_xCursor), true);
    if (xSupplier.is())
    {
        m_xFormatter = css::util::NumberFormatter::create(m_xContext);
        m_xFormatter->attachNumberFormatsSupplier(xSupplier);

        // retrieve the datebase of the Numberformatter
        try
        {
            xSupplier->getNumberFormatSettings()->getPropertyValue(u"NullDate"_ustr) >>= m_aNullDate;
        }
        catch(Exception&)
        {
        }
    }

    m_pDataCursor.reset(new CursorWrapper(_xCursor));

    // now create a cursor for painting rows
    // we need that cursor only if we are not in insert only mode
    Reference< XResultSet > xClone;
    Reference< XResultSetAccess > xAccess( _xCursor, UNO_QUERY );
    try
    {
        xClone = xAccess.is() ? xAccess->createResultSet() : Reference< XResultSet > ();
    }
    catch(Exception&)
    {
    }
    if (xClone.is())
        m_pSeekCursor.reset(new CursorWrapper(xClone));

    // property listening on the data source
    // (Normally one class would be sufficient : the multiplexer which could forward the property change to us.
    // But for that we would have been derived from ::comphelper::OPropertyChangeListener, which isn't exported.
    // So we introduce a second class, which is a ::comphelper::OPropertyChangeListener (in the implementation file we know this class)
    // and forwards the property changes to our special method "DataSourcePropertyChanged".)
    if (m_pDataCursor)
    {
        m_pDataSourcePropListener = new FmXGridSourcePropListener(this);
        m_pDataSourcePropMultiplexer = new ::comphelper::OPropertyChangeMultiplexer(m_pDataSourcePropListener, m_pDataCursor->getPropertySet() );
        m_pDataSourcePropMultiplexer->addProperty(FM_PROP_ISMODIFIED);
        m_pDataSourcePropMultiplexer->addProperty(FM_PROP_ISNEW);
    }

    BrowserMode nOldMode = m_nMode;
    if (m_pSeekCursor)
    {
        try
        {
            Reference< XPropertySet >  xSet(_xCursor, UNO_QUERY);
            if (xSet.is())
            {
                // check what kind of options are available
                sal_Int32 nConcurrency = ResultSetConcurrency::READ_ONLY;
                xSet->getPropertyValue(FM_PROP_RESULTSET_CONCURRENCY) >>= nConcurrency;

                if ( ResultSetConcurrency::UPDATABLE == nConcurrency )
                {
                    sal_Int32 nPrivileges = 0;
                    xSet->getPropertyValue(FM_PROP_PRIVILEGES) >>= nPrivileges;

                    // Insert Option should be set if insert only otherwise you won't see any rows
                    // and no insertion is possible
                    if ((m_nOptionMask & DbGridControlOptions::Insert)
                        && ((nPrivileges & Privilege::INSERT) == Privilege::INSERT) && (nOpts & DbGridControlOptions::Insert))
                        m_nOptions |= DbGridControlOptions::Insert;
                    if ((m_nOptionMask & DbGridControlOptions::Update)
                        && ((nPrivileges & Privilege::UPDATE) == Privilege::UPDATE) && (nOpts & DbGridControlOptions::Update))
                        m_nOptions |= DbGridControlOptions::Update;
                    if ((m_nOptionMask & DbGridControlOptions::Delete)
                        && ((nPrivileges & Privilege::DELETE) == Privilege::DELETE) && (nOpts & DbGridControlOptions::Delete))
                        m_nOptions |= DbGridControlOptions::Delete;
                }
            }
        }
        catchconst Exception& )
        {
            DBG_UNHANDLED_EXCEPTION("svx");
        }

        bool bPermanentCursor = IsPermanentCursorEnabled();
        m_nMode = DEFAULT_BROWSE_MODE;

        if ( bPermanentCursor )
        {
            m_nMode |= BrowserMode::CURSOR_WO_FOCUS;
            m_nMode &= ~BrowserMode::HIDECURSOR;
        }
        else
        {
            // updates are allowed -> no focus rectangle
            if ( m_nOptions & DbGridControlOptions::Update )
                m_nMode |= BrowserMode::HIDECURSOR;
        }

        m_nMode |= BrowserMode::MULTISELECTION;

        adjustModeForScrollbars( m_nMode, m_bNavigationBar, m_bHideScrollbars );

        Reference< XColumnsSupplier >  xSupplyColumns(_xCursor, UNO_QUERY);
        if (xSupplyColumns.is())
            InitColumnsByFields(Reference< XIndexAccess > (xSupplyColumns->getColumns(), UNO_QUERY));

        ConnectToFields();
    }

    sal_uInt32 nRecordCount(0);

    if (m_pSeekCursor)
    {
        Reference< XPropertySet > xSet = m_pDataCursor->getPropertySet();
        xSet->getPropertyValue(FM_PROP_ROWCOUNT) >>= nRecordCount;
        m_bRecordCountFinal = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ROWCOUNTFINAL));

        m_xRowSetListener = new RowSetEventListener(this);
        Reference< XRowsChangeBroadcaster> xChangeBroad(xSet,UNO_QUERY);
        if ( xChangeBroad.is( ) )
            xChangeBroad->addRowsChangeListener(m_xRowSetListener);


        // insert the currently known rows
        // and one row if we are able to insert rows
        if (m_nOptions & DbGridControlOptions::Insert)
        {
            // insert the empty row for insertion
            m_xEmptyRow = new DbGridRow();
            ++nRecordCount;
        }
        if (nRecordCount)
        {
            m_xPaintRow = m_xSeekRow = new DbGridRow(m_pSeekCursor.get(), true);
            m_xDataRow  = new DbGridRow(m_pDataCursor.get(), false);
            RowInserted(0, nRecordCount, false);

            if (m_xSeekRow->IsValid())
                try
                {
                    m_nSeekPos = m_pSeekCursor->getRow() - 1;
                }
                catchconst Exception& )
                {
                    DBG_UNHANDLED_EXCEPTION("svx");
                    m_nSeekPos = -1;
                }
        }
        else
        {
            // no rows so we don't need a seekcursor
            m_pSeekCursor.reset();
        }
    }

    // go to the old column
    if (nCurPos == BROWSER_INVALIDID || nCurPos >= ColCount())
        nCurPos = 0;

    // Column zero is a valid choice and guaranteed to exist,
    // but invisible to the user; if we have at least one
    // user-visible column, go to that one.
    if (nCurPos == 0 && ColCount() > 1)
        nCurPos = 1;

    // there are rows so go to the selected current column
    if (nRecordCount)
        GoToRowColumnId(0, GetColumnId(nCurPos));
    // else stop the editing if necessary
    else if (IsEditing())
        DeactivateCell();

    // now reset the mode
    if (m_nMode != nOldMode)
        SetMode(m_nMode);

    // RecalcRows was already called while resizing
    if (!IsResizing() && GetRowCount())
        RecalcRows(GetTopRow(), GetVisibleRows(), true);

    m_aBar->InvalidateAll(m_nCurrentPos, true);
    SetUpdateMode(true);

    // start listening on the seek cursor
    if (m_pSeekCursor)
        m_pCursorDisposeListener.reset(new DisposeListenerGridBridge(*this, Reference< XComponent > (Reference< XInterface >(*m_pSeekCursor), UNO_QUERY)));
}

void DbGridControl::RemoveColumns()
{
    if ( !isDisposed() && IsEditing() )
        DeactivateCell();

    m_aColumns.clear();

    EditBrowseBox::RemoveColumns();
}

std::unique_ptr<DbGridColumn> DbGridControl::CreateColumn(sal_uInt16 nId)
{
    return std::unique_ptr<DbGridColumn>(new DbGridColumn(nId, *this));
}

sal_uInt16 DbGridControl::AppendColumn(const OUString& rName, sal_uInt16 nWidth, sal_uInt16 nModelPos, sal_uInt16 nId)
{
    DBG_ASSERT(nId == BROWSER_INVALIDID, "DbGridControl::AppendColumn : I want to set the ID myself ...");
    sal_uInt16 nRealPos = nModelPos;
    if (nModelPos != HEADERBAR_APPEND)
    {
        // calc the view pos. we can't use our converting functions because the new column
        // has no VCL-representation, yet.
        sal_Int16 nViewPos = nModelPos;
        while (nModelPos--)
        {
            if ( m_aColumns[ nModelPos ]->IsHidden() )
                --nViewPos;
        }
        // restore nModelPos, we need it later
        nModelPos = nRealPos;
        // the position the base class gets is the view pos + 1 (because of the handle column)
        nRealPos = nViewPos + 1;
    }

    // calculate the new id
    for (nId=1; (GetModelColumnPos(nId) != GRID_COLUMN_NOT_FOUND) && size_t(nId) <= m_aColumns.size(); ++nId)
        ;
    DBG_ASSERT(GetViewColumnPos(nId) == GRID_COLUMN_NOT_FOUND, "DbGridControl::AppendColumn : inconsistent internal state !");
        // my column's models say "there is no column with id nId", but the view (the base class) says "there is a column ..."

    EditBrowseBox::AppendColumn(rName, nWidth, nRealPos, nId);
    if (nModelPos == HEADERBAR_APPEND)
        m_aColumns.push_back( CreateColumn(nId) );
    else
        m_aColumns.insert( m_aColumns.begin() + nModelPos, CreateColumn(nId) );

    return nId;
}

void DbGridControl::RemoveColumn(sal_uInt16 nId)
{
    EditBrowseBox::RemoveColumn(nId);

    const sal_uInt16 nIndex = GetModelColumnPos(nId);
    if(nIndex != GRID_COLUMN_NOT_FOUND)
    {
        m_aColumns.erase( m_aColumns.begin()+nIndex );
    }
}

void DbGridControl::ColumnMoved(sal_uInt16 nId)
{
    EditBrowseBox::ColumnMoved(nId);

    // remove the col from the model
    sal_uInt16 nOldModelPos = GetModelColumnPos(nId);
#ifdef DBG_UTIL
    DbGridColumn* pCol = m_aColumns[ nOldModelPos ].get();
    DBG_ASSERT(!pCol->IsHidden(), "DbGridControl::ColumnMoved : moved a hidden col ? how this ?");
#endif

    // for the new model pos we can't use GetModelColumnPos because we are altering the model at the moment
    // so the method won't work (in fact it would return the old model pos)

    // the new view pos is calculated easily
    sal_uInt16 nNewViewPos = GetViewColumnPos(nId);

    // from that we can compute the new model pos
    size_t nNewModelPos;
    for (nNewModelPos = 0; nNewModelPos < m_aColumns.size(); ++nNewModelPos)
    {
        if (!m_aColumns[ nNewModelPos ]->IsHidden())
        {
            if (!nNewViewPos)
                break;
            else
                --nNewViewPos;
        }
    }
    DBG_ASSERT( nNewModelPos < m_aColumns.size(), "DbGridControl::ColumnMoved : could not find the new model position !");

    // this will work. of course the model isn't fully consistent with our view right now, but let's
    // look at the situation : a column has been moved with in the VIEW from pos m to n, say m<n (in the
    // other case we can use analogue arguments).
    // All cols k with m<k<=n have been shifted left on pos, the former col m now has pos n.
    // In the model this affects a range of cols x to y, where x<=m and y<=n. And the number of hidden cols
    // within this range is constant, so we may calculate the view pos from the model pos in the above way.

    // for instance, let's look at a grid with six columns where the third one is hidden. this will
    // initially look like this :

    //              +---+---+---+---+---+---+
    // model pos    | 0 | 1 |*2*| 3 | 4 | 5 |
    //              +---+---+---+---+---+---+
    // ID           | 1 | 2 | 3 | 4 | 5 | 6 |
    //              +---+---+---+---+---+---+
    // view pos     | 0 | 1 | - | 2 | 3 | 4 |
    //              +---+---+---+---+---+---+

    // if we move the column at (view) pos 1 to (view) pos 3 we have :

    //              +---+---+---+---+---+---+
    // model pos    | 0 | 3 |*2*| 4 | 1 | 5 |   // not reflecting the changes, yet
    //              +---+---+---+---+---+---+
    // ID           | 1 | 4 | 3 | 5 | 2 | 6 |   // already reflecting the changes
    //              +---+---+---+---+---+---+
    // view pos     | 0 | 1 | - | 2 | 3 | 4 |
    //              +---+---+---+---+---+---+

    // or, sorted by the out-of-date model positions :

    //              +---+---+---+---+---+---+
    // model pos    | 0 | 1 |*2*| 3 | 4 | 5 |
    //              +---+---+---+---+---+---+
    // ID           | 1 | 2 | 3 | 4 | 5 | 6 |
    //              +---+---+---+---+---+---+
    // view pos     | 0 | 3 | - | 1 | 2 | 4 |
    //              +---+---+---+---+---+---+

    // We know the new view pos (3) of the moved column because our base class tells us. So we look at our
    // model for the 4th (the pos is zero-based) visible column, it is at (model) position 4. And this is
    // exactly the pos where we have to re-insert our column's model, so it looks ike this :

    //              +---+---+---+---+---+---+
    // model pos    | 0 |*1*| 2 | 3 | 4 | 5 |
    //              +---+---+---+---+---+---+
    // ID           | 1 | 3 | 4 | 5 | 2 | 6 |
    //              +---+---+---+---+---+---+
    // view pos     | 0 | - | 1 | 2 | 3 | 4 |
    //              +---+---+---+---+---+---+

    // Now, all is consistent again.
    // (except of the hidden column : The cycling of the cols occurred on the model, not on the view. maybe
    // the user expected the latter but there really is no good argument against our method ;) ...)

    // And no, this large explanation isn't just because I wanted to play a board game or something like
    // that. It's because it took me a while to see it myself, and the whole theme (hidden cols, model col
    // positions, view col positions)  is really painful (at least for me) so the above pictures helped me a lot ;)

    auto temp = std::move(m_aColumns[ nOldModelPos ]);
    m_aColumns.erase( m_aColumns.begin() + nOldModelPos );
    m_aColumns.insert( m_aColumns.begin() + nNewModelPos, std::move(temp) );
}

bool DbGridControl::SeekRow(sal_Int32 nRow)
{
    // in filter mode or in insert only mode we don't have any cursor!
    if ( !SeekCursor( nRow ) )
        return false;

    if ( IsFilterMode() )
    {
        DBG_ASSERT( IsFilterRow( nRow ), "DbGridControl::SeekRow(): No filter row, wrong mode" );
        m_xPaintRow = m_xEmptyRow;
    }
    else
    {
        // on the current position we have to take the current row for display as we want
        // to have the most recent values for display
        if ( ( nRow == m_nCurrentPos ) && getDisplaySynchron() )
            m_xPaintRow = m_xCurrentRow;
        // seek to the empty insert row
        else if ( IsInsertionRow( nRow ) )
            m_xPaintRow = m_xEmptyRow;
        else
        {
            m_xSeekRow->SetState( m_pSeekCursor.get(), true );
            m_xPaintRow = m_xSeekRow;
        }
    }

    EditBrowseBox::SeekRow(nRow);

    return m_nSeekPos >= 0;
}

// Is called whenever the visible amount of data changes
void DbGridControl::VisibleRowsChanged( sal_Int32 nNewTopRow, sal_uInt16 nLinesOnScreen )
{
    RecalcRows(nNewTopRow, nLinesOnScreen, false);
}

void DbGridControl::RecalcRows(sal_Int32 nNewTopRow, sal_uInt16 nLinesOnScreen, bool bUpdateCursor)
{
    // If no cursor -> no rows in the browser.
    if (!m_pSeekCursor)
    {
        DBG_ASSERT(GetRowCount() == 0,"DbGridControl: without cursor no rows are allowed to be there");
        return;
    }

    // ignore any implicitly made updates
    bool bDisablePaint = !bUpdateCursor && IsPaintEnabled();
    if (bDisablePaint)
        EnablePaint(false);

    // adjust cache to the visible area
    Reference< XPropertySet > xSet = m_pSeekCursor->getPropertySet();
    sal_Int32 nCacheSize = 0;
    xSet->getPropertyValue(FM_PROP_FETCHSIZE) >>= nCacheSize;
    bool bCacheAligned   = false;
    // no further cursor movements after initializing (m_nSeekPos < 0) because it is already
    // positioned on the first sentence
    tools::Long nDelta = nNewTopRow - GetTopRow();
    // limit for relative positioning
    tools::Long nLimit = nCacheSize ? nCacheSize / 2 : 0;

    // more lines on screen than in cache
    if (nLimit < nLinesOnScreen)
    {
        Any aCacheSize;
        aCacheSize <<= sal_Int32(nLinesOnScreen*2);
        xSet->setPropertyValue(FM_PROP_FETCHSIZE, aCacheSize);
        // here we need to update the cursor for sure
        bUpdateCursor = true;
        bCacheAligned = true;
        nLimit = nLinesOnScreen;
    }

    // In the following, all positionings are done as it is
    // ensured that there are enough lines in the data cache

    // window goes downwards with less than two windows difference or
    // the cache was updated and no rowcount yet
    if (nDelta < nLimit && (nDelta > 0
        || (bCacheAligned && m_nTotalCount < 0)) )
        SeekCursor(nNewTopRow + nLinesOnScreen - 1);
    else if (nDelta < 0 && std::abs(nDelta) < nLimit)
        SeekCursor(nNewTopRow);
    else if (nDelta != 0 || bUpdateCursor)
        SeekCursor(nNewTopRow, true);

    AdjustRows();

    // ignore any updates implicit made
    EnablePaint(true);
}

void DbGridControl::RowInserted(sal_Int32 nRow, sal_Int32 nNumRows, bool bDoPaint)
{
    if (!nNumRows)
        return;

    if (m_bRecordCountFinal && m_nTotalCount < 0)
    {
        // if we have an insert row we have to reduce to count by 1
        // as the total count reflects only the existing rows in database
        m_nTotalCount = GetRowCount() + nNumRows;
        if (m_xEmptyRow.is())
            --m_nTotalCount;
    }
    else if (m_nTotalCount >= 0)
        m_nTotalCount += nNumRows;

    EditBrowseBox::RowInserted(nRow, nNumRows, bDoPaint);
    m_aBar->InvalidateState(DbGridControlNavigationBarState::Count);
}

void DbGridControl::RowRemoved(sal_Int32 nRow, sal_Int32 nNumRows, bool bDoPaint)
{
    if (!nNumRows)
        return;

    if (m_bRecordCountFinal && m_nTotalCount < 0)
    {
        m_nTotalCount = GetRowCount() - nNumRows;
        // if we have an insert row reduce by 1
        if (m_xEmptyRow.is())
            --m_nTotalCount;
    }
    else if (m_nTotalCount >= 0)
        m_nTotalCount -= nNumRows;

    EditBrowseBox::RowRemoved(nRow, nNumRows, bDoPaint);
    m_aBar->InvalidateState(DbGridControlNavigationBarState::Count);
}

void DbGridControl::AdjustRows()
{
    if (!m_pSeekCursor)
        return;

    Reference< XPropertySet > xSet = m_pDataCursor->getPropertySet();

    // refresh RecordCount
    sal_Int32 nRecordCount = 0;
    xSet->getPropertyValue(FM_PROP_ROWCOUNT) >>= nRecordCount;
    if (!m_bRecordCountFinal)
        m_bRecordCountFinal = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ROWCOUNTFINAL));

    // Did the number of rows change?
    // Here we need to consider that there might be an additional row for adding new data sets

    // add additional AppendRow for insertion
    if (m_nOptions & DbGridControlOptions::Insert)
        ++nRecordCount;

    // If there is currently an insertion, so do not consider this added row in RecordCount or Appendrow
    if (!IsUpdating() && m_bRecordCountFinal && IsModified() && m_xCurrentRow != m_xEmptyRow &&
        m_xCurrentRow->IsNew())
        ++nRecordCount;
    // ensured with !m_bUpdating: otherwise the edited data set (that SaveRow added and why this
    // method was called) would be called twice (if m_bUpdating == sal_True): once in RecordCount
    // and a second time here (60787 - FS)

    if (nRecordCount != GetRowCount())
    {
        tools::Long nDelta = GetRowCount() - static_cast<tools::Long>(nRecordCount);
        if (nDelta > 0) // too many
        {
            RowRemoved(GetRowCount() - nDelta, nDelta, false);
            // some rows are gone, thus, repaint starting at the current position
            Invalidate();

            sal_Int32 nNewPos = AlignSeekCursor();
            if (m_bSynchDisplay)
                EditBrowseBox::GoToRow(nNewPos);

            SetCurrent(nNewPos);
            // there are rows so go to the selected current column
            if (nRecordCount)
                GoToRowColumnId(nNewPos, GetColumnId(GetCurColumnId()));
            if (!IsResizing() && GetRowCount())
                RecalcRows(GetTopRow(), GetVisibleRows(), true);
            m_aBar->InvalidateAll(m_nCurrentPos, true);
        }
        else  // too few
            RowInserted(GetRowCount(), -nDelta);
    }

    if (m_bRecordCountFinal && m_nTotalCount < 0)
    {
        if (m_nOptions & DbGridControlOptions::Insert)
            m_nTotalCount = GetRowCount() - 1;
        else
            m_nTotalCount = GetRowCount();
    }
    m_aBar->InvalidateState(DbGridControlNavigationBarState::Count);
}

svt::EditBrowseBox::RowStatus DbGridControl::GetRowStatus(sal_Int32 nRow) const
{
    if (IsFilterRow(nRow))
        return EditBrowseBox::FILTER;
    else if (m_nCurrentPos >= 0 && nRow == m_nCurrentPos)
    {
        // new row
        if (!IsValid(m_xCurrentRow))
            return EditBrowseBox::DELETED;
        else if (IsModified())
            return EditBrowseBox::MODIFIED;
        else if (m_xCurrentRow->IsNew())
            return EditBrowseBox::CURRENTNEW;
        else
            return EditBrowseBox::CURRENT;
    }
    else if (IsInsertionRow(nRow))
        return EditBrowseBox::NEW;
    else if (!IsValid(m_xSeekRow))
        return EditBrowseBox::DELETED;
    else
        return EditBrowseBox::CLEAN;
}

void DbGridControl::PaintCell(OutputDevice& rDev, const tools::Rectangle& rRect, sal_uInt16 nColumnId) const
{
    if (!IsValid(m_xPaintRow))
        return;

    size_t Location = GetModelColumnPos(nColumnId);
    DbGridColumn* pColumn = (Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
    if (pColumn)
    {
        tools::Rectangle aArea(rRect);
        if ((GetMode() & BrowserMode::CURSOR_WO_FOCUS) == BrowserMode::CURSOR_WO_FOCUS)
        {
            aArea.AdjustTop(1 );
            aArea.AdjustBottom( -1 );
        }
        pColumn->Paint(rDev, aArea, m_xPaintRow.get(), getNumberFormatter());
    }
}

bool DbGridControl::CursorMoving(sal_Int32 nNewRow, sal_uInt16 nNewCol)
{

    DeactivateCell( false );

    if  (   m_pDataCursor
        &&  ( m_nCurrentPos != nNewRow )
        && !SetCurrent( nNewRow )
        )
    {
        ActivateCell();
        return false;
    }

    return EditBrowseBox::CursorMoving( nNewRow, nNewCol );
}

bool DbGridControl::SetCurrent(sal_Int32 nNewRow)
{
    // Each movement of the datacursor must start with BeginCursorAction and end with
    // EndCursorAction to block all notifications during the movement
    BeginCursorAction();

    try
    {
        // compare positions
        if (SeekCursor(nNewRow))
        {
            if (IsFilterRow(nNewRow))   // special mode for filtering
            {
                m_xCurrentRow = m_xDataRow = m_xPaintRow = m_xEmptyRow;
                m_nCurrentPos = nNewRow;
            }
            else
            {
                bool bNewRowInserted = false;
                // Should we go to the insertrow ?
                if (IsInsertionRow(nNewRow))
                {
                    // to we need to move the cursor to the insert row?
                    // we need to insert the if the current row isn't the insert row or if the
                    // cursor triggered the move by itself and we need a reinitialization of the row
                    Reference< XPropertySet > xCursorProps = m_pDataCursor->getPropertySet();
                    if ( !::comphelper::getBOOL(xCursorProps->getPropertyValue(FM_PROP_ISNEW)) )
                    {
                        Reference< XResultSetUpdate > xUpdateCursor(Reference< XInterface >(*m_pDataCursor), UNO_QUERY);
                        xUpdateCursor->moveToInsertRow();
                    }
                    bNewRowInserted = true;
                }
                else
                {

                    if ( !m_pSeekCursor->isBeforeFirst() && !m_pSeekCursor->isAfterLast() )
                    {
                        Any aBookmark = m_pSeekCursor->getBookmark();
                        if (!m_xCurrentRow.is() || m_xCurrentRow->IsNew() || !CompareBookmark(aBookmark, m_pDataCursor->getBookmark()))
                        {
                            // adjust the cursor to the new desired row
                            if (!m_pDataCursor->moveToBookmark(aBookmark))
                            {
                                EndCursorAction();
                                return false;
                            }
                        }
                    }
                }
                m_xDataRow->SetState(m_pDataCursor.get(), false);
                m_xCurrentRow = m_xDataRow;

                tools::Long nPaintPos = -1;
                // do we have to repaint the last regular row in case of setting defaults or autovalues
                if (m_nCurrentPos >= 0 && m_nCurrentPos >= (GetRowCount() - 2))
                    nPaintPos = m_nCurrentPos;

                m_nCurrentPos = nNewRow;

                // repaint the new row to display all defaults
                if (bNewRowInserted)
                    RowModified(m_nCurrentPos);
                if (nPaintPos >= 0)
                    RowModified(nPaintPos);
            }
        }
        else
        {
            OSL_FAIL("DbGridControl::SetCurrent : SeekRow failed !");
            EndCursorAction();
            return false;
        }
    }
    catch ( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("svx");
        EndCursorAction();
        return false;
    }

    EndCursorAction();
    return true;
}

void DbGridControl::CursorMoved()
{

    // cursor movement due to deletion or insertion of rows
    if (m_pDataCursor && m_nCurrentPos != GetCurRow())
    {
        DeactivateCell();
        SetCurrent(GetCurRow());
    }

    EditBrowseBox::CursorMoved();
    m_aBar->InvalidateAll(m_nCurrentPos);

    // select the new column when they moved
    if ( IsDesignMode() && GetSelectedColumnCount() > 0 && GetCurColumnId() )
    {
        SelectColumnId( GetCurColumnId() );
    }

    if ( m_nLastColId != GetCurColumnId() )
        onColumnChange();
    m_nLastColId = GetCurColumnId();

    if ( m_nLastRowId != GetCurRow() )
        onRowChange();
    m_nLastRowId = GetCurRow();
}

void DbGridControl::onRowChange()
{
    // not interested in
}

void DbGridControl::onColumnChange()
{
    if ( m_pGridListener )
        m_pGridListener->columnChanged();
}

void DbGridControl::setDisplaySynchron(bool bSync)
{
    if (bSync != m_bSynchDisplay)
    {
        m_bSynchDisplay = bSync;
        if (m_bSynchDisplay)
            AdjustDataSource();
    }
}

void DbGridControl::AdjustDataSource(bool bFull)
{
    SAL_INFO("svx.fmcomp""DbGridControl::AdjustDataSource");
    SolarMutexGuard aGuard;
    // If the current row is recalculated at the moment, do not adjust

    if (bFull)
        m_xCurrentRow = nullptr;
    // if we are on the same row only repaint
    // but this is only possible for rows which are not inserted, in that case the comparison result
    // may not be correct
    else
        if  (   m_xCurrentRow.is()
            &&  !m_xCurrentRow->IsNew()
            &&  !m_pDataCursor->isBeforeFirst()
            &&  !m_pDataCursor->isAfterLast()
            &&  !m_pDataCursor->rowDeleted()
            )
        {
            bool bEqualBookmarks = CompareBookmark( m_xCurrentRow->GetBookmark(), m_pDataCursor->getBookmark() );

            bool bDataCursorIsOnNew = false;
            m_pDataCursor->getPropertySet()->getPropertyValue( FM_PROP_ISNEW ) >>= bDataCursorIsOnNew;

            if ( bEqualBookmarks && !bDataCursorIsOnNew )
            {
                // position of my data cursor is the same as the position our current row points tpo
                // sync the status, repaint, done
                DBG_ASSERT(m_xDataRow == m_xCurrentRow, "Errors in the data row");
                SAL_INFO("svx.fmcomp""same position, new state: " << ROWSTATUS(m_xCurrentRow));
                RowModified(m_nCurrentPos);
                return;
            }
        }

    // away from the data cursor's row
    if (m_xPaintRow == m_xCurrentRow)
        m_xPaintRow = m_xSeekRow;

    // not up-to-date row, thus, adjust completely
    if (!m_xCurrentRow.is())
        AdjustRows();

    sal_Int32 nNewPos = AlignSeekCursor();
    if (nNewPos < 0)// could not find any position
        return;

    if (nNewPos != m_nCurrentPos)
    {
        if (m_bSynchDisplay)
            EditBrowseBox::GoToRow(nNewPos);

        if (!m_xCurrentRow.is())
            // Happens e.g. when deleting the n last datasets (n>1) while the cursor was positioned
            // on the last one. In this case, AdjustRows deletes two rows from BrowseBox, by what
            // CurrentRow is corrected to point two rows down, so that GoToRow will point into
            // emptiness (since we are - purportedly - at the correct position)
            SetCurrent(nNewPos);
    }
    else
    {
        SetCurrent(nNewPos);
        RowModified(nNewPos);
    }

    // if the data cursor was moved from outside, this section is voided
    SetNoSelection();
    m_aBar->InvalidateAll(m_nCurrentPos, m_xCurrentRow.is());
}

sal_Int32 DbGridControl::AlignSeekCursor()
{
    // position SeekCursor onto the data cursor, no data transmission

    if (!m_pSeekCursor)
        return -1;

    Reference< XPropertySet > xSet = m_pDataCursor->getPropertySet();

    // now align the seek cursor and the data cursor
    if (::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISNEW)))
        m_nSeekPos = GetRowCount() - 1;
    else
    {
        try
        {
            if ( m_pDataCursor->isBeforeFirst() )
            {
                // this is somewhat strange, but can nevertheless happen
                SAL_INFO( "svx.fmcomp""DbGridControl::AlignSeekCursor: nobody should tamper with my cursor this way (before first)!" );
                m_pSeekCursor->first();
                m_pSeekCursor->previous();
                m_nSeekPos = -1;
            }
            else if ( m_pDataCursor->isAfterLast() )
            {
                SAL_INFO( "svx.fmcomp""DbGridControl::AlignSeekCursor: nobody should tamper with my cursor this way (after last)!" );
                m_pSeekCursor->last();
                m_pSeekCursor->next();
                m_nSeekPos = -1;
            }
            else
            {
                m_pSeekCursor->moveToBookmark(m_pDataCursor->getBookmark());
                if (!CompareBookmark(m_pDataCursor->getBookmark(), m_pSeekCursor->getBookmark()))
                    // unfortunately, moveToBookmark might lead to a re-positioning of the seek
                    // cursor (if the complex moveToBookmark with all its events fires an update
                    // somewhere) -> retry
                    m_pSeekCursor->moveToBookmark(m_pDataCursor->getBookmark());
                    // Now there is still the chance of a failure but it is less likely.
                    // The alternative would be a loop until everything is fine - no good solution...
                m_nSeekPos = m_pSeekCursor->getRow() - 1;
            }
        }
        catch(Exception&)
        {
        }
    }
    return m_nSeekPos;
}

bool DbGridControl::SeekCursor(sal_Int32 nRow, bool bAbsolute)
{
    // position SeekCursor onto the data cursor, no data transmission

    // additions for the filtermode
    if (IsFilterRow(nRow))
    {
        m_nSeekPos = 0;
        return true;
    }

    if (!m_pSeekCursor)
        return false;

    // is this an insertion?
    if (IsValid(m_xCurrentRow) && m_xCurrentRow->IsNew() &&
        nRow >= m_nCurrentPos)
    {
        // if so, scrolling down must be prevented as this is already the last data set!
        if (nRow == m_nCurrentPos)
        {
            // no adjustment necessary
            m_nSeekPos = nRow;
        }
        else if (IsInsertionRow(nRow)) // blank row for data insertion
            m_nSeekPos = nRow;
    }
    else if (IsInsertionRow(nRow)) // blank row for data insertion
        m_nSeekPos = nRow;
    else if ((-1 == nRow) && (GetRowCount() == ((m_nOptions & DbGridControlOptions::Insert) ? 1 : 0)) && m_pSeekCursor->isAfterLast())
        m_nSeekPos = nRow;
    else
    {
        bool bSuccess = false;
        tools::Long nSteps = 0;
        try
        {
            if ( m_pSeekCursor->rowDeleted() )
            {
                // somebody deleted the current row of the seek cursor. Move it away from this row.
                m_pSeekCursor->next();
                if ( m_pSeekCursor->isAfterLast() || m_pSeekCursor->isBeforeFirst() )
                    bAbsolute = true;
            }

            if ( !bAbsolute )
            {
                DBG_ASSERT( !m_pSeekCursor->isAfterLast() && !m_pSeekCursor->isBeforeFirst(),
                    "DbGridControl::SeekCursor: how did the seek cursor get to this position?!" );
                nSteps = nRow - (m_pSeekCursor->getRow() - 1);
                bAbsolute = std::abs(nSteps) > 100;
            }

            if ( bAbsolute )
            {
                bSuccess = m_pSeekCursor->absolute(nRow + 1);
                if (bSuccess)
                    m_nSeekPos = nRow;
            }
            else
            {
                if (nSteps > 0) // position onto the last needed data set
                {
                    if (m_pSeekCursor->isAfterLast())
                        bSuccess = false;
                    else if (m_pSeekCursor->isBeforeFirst())
                        bSuccess = m_pSeekCursor->absolute(nSteps);
                    else
                        bSuccess = m_pSeekCursor->relative(nSteps);
                }
                else if (nSteps < 0)
                {
                    if (m_pSeekCursor->isBeforeFirst())
                        bSuccess = false;
                    else if (m_pSeekCursor->isAfterLast())
                        bSuccess = m_pSeekCursor->absolute(nSteps);
                    else
                        bSuccess = m_pSeekCursor->relative(nSteps);
                }
                else
                {
                    m_nSeekPos = nRow;
                    return true;
                }
            }
        }
        catch(Exception&)
        {
            OSL_FAIL("DbGridControl::SeekCursor : failed ...");
        }

        try
        {
            if (!bSuccess)
            {
                if (bAbsolute || nSteps > 0)
                {
                    if (m_pSeekCursor->isLast())
                        bSuccess = true;
                    else
                        bSuccess = m_pSeekCursor->last();
                }
                else
                {
                    if (m_pSeekCursor->isFirst())
                        bSuccess = true;
                    else
                        bSuccess = m_pSeekCursor->first();
                }
            }

            if (bSuccess)
                m_nSeekPos = m_pSeekCursor->getRow() - 1;
            else
                m_nSeekPos = -1;
        }
        catch(Exception&)
        {
            DBG_UNHANDLED_EXCEPTION("svx");
            OSL_FAIL("DbGridControl::SeekCursor : failed ...");
            m_nSeekPos = -1; // no further data set available
        }
    }
    return m_nSeekPos == nRow;
}

void DbGridControl::MoveToFirst()
{
    if (m_pSeekCursor && (GetCurRow() != 0))
        MoveToPosition(0);
}

void DbGridControl::MoveToLast()
{
    if (!m_pSeekCursor)
        return;

    if (m_nTotalCount < 0) // no RecordCount, yet
    {
        try
        {
            bool bRes = m_pSeekCursor->last();

            if (bRes)
            {
                m_nSeekPos = m_pSeekCursor->getRow() - 1;
                AdjustRows();
            }
        }
        catch(Exception&)
        {
        }
    }

    // position onto the last data set not on a blank row
    if (m_nOptions & DbGridControlOptions::Insert)
    {
        if ((GetRowCount() - 1) > 0)
            MoveToPosition(GetRowCount() - 2);
    }
    else if (GetRowCount())
        MoveToPosition(GetRowCount() - 1);
}

void DbGridControl::MoveToPrev()
{
    sal_Int32 nNewRow = std::max(GetCurRow() - 1, sal_Int32(0));
    if (GetCurRow() != nNewRow)
        MoveToPosition(nNewRow);
}

void DbGridControl::MoveToNext()
{
    if (!m_pSeekCursor)
        return;

    if (m_nTotalCount > 0)
    {
        // move the data cursor to the right position
        tools::Long nNewRow = std::min(GetRowCount() - 1, GetCurRow() + 1);
        if (GetCurRow() != nNewRow)
            MoveToPosition(nNewRow);
    }
    else
    {
        bool bOk = false;
        try
        {
            // try to move to next row
            // when not possible our paint cursor is already on the last row
            // then we must be sure that the data cursor is on the position
            // we call ourself again
            bOk = m_pSeekCursor->next();
            if (bOk)
            {
                m_nSeekPos = m_pSeekCursor->getRow() - 1;
                MoveToPosition(GetCurRow() + 1);
            }
        }
        catch(SQLException &)
        {
            DBG_UNHANDLED_EXCEPTION("svx");
        }

        if(!bOk)
        {
            AdjustRows();
            if (m_nTotalCount > 0) // only to avoid infinite recursion
                MoveToNext();
        }
    }
}

void DbGridControl::MoveToPosition(sal_uInt32 nPos)
{
    if (!m_pSeekCursor)
        return;

    if (m_nTotalCount < 0 && static_cast<tools::Long>(nPos) >= GetRowCount())
    {
        try
        {
            if (!m_pSeekCursor->absolute(nPos + 1))
            {
                AdjustRows();
                return;
            }
            else
            {
                m_nSeekPos = m_pSeekCursor->getRow() - 1;
                AdjustRows();
            }
        }
        catch(Exception&)
        {
            return;
        }
    }
    EditBrowseBox::GoToRow(nPos);
    m_aBar->InvalidateAll(m_nCurrentPos);
}

void DbGridControl::AppendNew()
{
    if (!m_pSeekCursor || !(m_nOptions & DbGridControlOptions::Insert))
        return;

    if (m_nTotalCount < 0) // no RecordCount, yet
    {
        try
        {
            bool bRes = m_pSeekCursor->last();

            if (bRes)
            {
                m_nSeekPos = m_pSeekCursor->getRow() - 1;
                AdjustRows();
            }
        }
        catch(Exception&)
        {
            return;
        }
    }

    tools::Long nNewRow = m_nTotalCount + 1;
    if (nNewRow > 0 && GetCurRow() != nNewRow)
        MoveToPosition(nNewRow - 1);
}

void DbGridControl::SetDesignMode(bool bMode)
{
    if (IsDesignMode() == bMode)
        return;

    // adjust Enable/Disable for design mode so that the headerbar remains configurable
    if (bMode)
    {
        if (!IsEnabled())
        {
            Enable();
            GetDataWindow().Disable();
        }
    }
    else
    {
        // disable completely
        if (!GetDataWindow().IsEnabled())
            Disable();
    }

    m_bDesignMode = bMode;
    GetDataWindow().SetMouseTransparent(bMode);
    SetMouseTransparent(bMode);

    m_aBar->InvalidateAll(m_nCurrentPos, true);
}

void DbGridControl::SetFilterMode(bool bMode)
{
    if (IsFilterMode() == bMode)
        return;

    m_bFilterMode = bMode;

    if (bMode)
    {
        SetUpdateMode(false);

        // there is no cursor anymore
        if (IsEditing())
            DeactivateCell();
        RemoveRows(false);

        m_xEmptyRow = new DbGridRow();

        // setting the new filter controls
        for (auto const & pCurCol : m_aColumns)
        {
            if (!pCurCol->IsHidden())
                pCurCol->UpdateControl();
        }

        // one row for filtering
        RowInserted(0);
        SetUpdateMode(true);
    }
    else
        setDataSource(Reference< XRowSet > ());
}

OUString DbGridControl::GetCellText(sal_Int32 _nRow, sal_uInt16 _nColId) const
{
    size_t Location = GetModelColumnPos( _nColId );
    DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
    OUString sRet;
    if ( const_cast<DbGridControl*>(this)->SeekRow(_nRow) )
        sRet = GetCurrentRowCellText(pColumn, m_xPaintRow);
    return sRet;
}

OUString DbGridControl::GetCurrentRowCellText(DbGridColumn const * pColumn,const DbGridRowRef& _rRow) const
{
    // text output for a single row
    OUString aText;
    if ( pColumn && IsValid(_rRow) )
        aText = pColumn->GetCellText(_rRow.get(), m_xFormatter);
    return aText;
}

sal_uInt32 DbGridControl::GetTotalCellWidth(sal_Int32 nRow, sal_uInt16 nColId)
{
    if (SeekRow(nRow))
    {
        size_t Location = GetModelColumnPos( nColId );
        DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
        return GetDataWindow().GetTextWidth(GetCurrentRowCellText(pColumn,m_xPaintRow));
    }
    else
        return 30;  // FIXME magic number for default cell width
}

void DbGridControl::PreExecuteRowContextMenu(weld::Menu& rMenu)
{
    bool bDelete = (m_nOptions & DbGridControlOptions::Delete) && GetSelectRowCount() && !IsCurrentAppending();
    // if only a blank row is selected then do not delete
    bDelete = bDelete && !((m_nOptions & DbGridControlOptions::Insert) && GetSelectRowCount() == 1 && IsRowSelected(GetRowCount() - 1));

    rMenu.set_visible(u"delete"_ustr, bDelete);
    rMenu.set_visible(u"save"_ustr, IsModified());

    // the undo is more difficult
    bool bCanUndo = IsModified();
    int nState = -1;
    if (m_aMasterStateProvider.IsSet())
        nState = m_aMasterStateProvider.Call(DbGridControlNavigationBarState::Undo);
    bCanUndo &= ( 0 != nState );

    rMenu.set_visible(u"undo"_ustr, bCanUndo);
}

void DbGridControl::PostExecuteRowContextMenu(const OUString& rExecutionResult)
{
    if (rExecutionResult == "delete")
    {
        // delete asynchronously
        if (m_nDeleteEvent)
            Application::RemoveUserEvent(m_nDeleteEvent);
        m_nDeleteEvent = Application::PostUserEvent(LINK(this,DbGridControl,OnDelete), nullptr, true);
    }
    else if (rExecutionResult == "undo")
        Undo();
    else if (rExecutionResult == "save")
        SaveRow();
}

void DbGridControl::DataSourcePropertyChanged(const PropertyChangeEvent& evt)
{
    SAL_INFO("svx.fmcomp""DbGridControl::DataSourcePropertyChanged");
    SolarMutexGuard aGuard;
    // prop "IsModified" changed ?
    // during update don't care about the modified state
    if (IsUpdating() || evt.PropertyName != FM_PROP_ISMODIFIED)
        return;

    Reference< XPropertySet > xSource(evt.Source, UNO_QUERY);
    DBG_ASSERT( xSource.is(), "DbGridControl::DataSourcePropertyChanged: invalid event source!" );
    bool bIsNew = false;
    if (xSource.is())
        bIsNew = ::comphelper::getBOOL(xSource->getPropertyValue(FM_PROP_ISNEW));

    if (bIsNew && m_xCurrentRow.is())
    {
        DBG_ASSERT(::comphelper::getBOOL(xSource->getPropertyValue(FM_PROP_ROWCOUNTFINAL)), "DbGridControl::DataSourcePropertyChanged : somebody moved the form to a new record before the row count was final !");
        sal_Int32 nRecordCount = 0;
        xSource->getPropertyValue(FM_PROP_ROWCOUNT) >>= nRecordCount;
        if (::comphelper::getBOOL(evt.NewValue))
        {   // modified state changed from sal_False to sal_True and we're on an insert row
            // -> we've to add a new grid row
            if ((nRecordCount == GetRowCount() - 1)  && m_xCurrentRow->IsNew())
            {
                RowInserted(GetRowCount());
                InvalidateStatusCell(m_nCurrentPos);
                m_aBar->InvalidateAll(m_nCurrentPos);
            }
        }
        else
        {   // modified state changed from sal_True to sal_False and we're on an insert row
            // we have two "new row"s at the moment : the one we're editing currently (where the current
            // column is the only dirty element) and a "new new" row which is completely clean. As the first
            // one is about to be cleaned, too, the second one is obsolete now.
            if (m_xCurrentRow->IsNew() && nRecordCount == (GetRowCount() - 2))
            {
                RowRemoved(GetRowCount() - 1);
                InvalidateStatusCell(m_nCurrentPos);
                m_aBar->InvalidateAll(m_nCurrentPos);
            }
        }
    }
    if (m_xCurrentRow.is())
    {
        m_xCurrentRow->SetStatus(::comphelper::getBOOL(evt.NewValue) ? GridRowStatus::Modified : GridRowStatus::Clean);
        m_xCurrentRow->SetNew( bIsNew );
        InvalidateStatusCell(m_nCurrentPos);
        SAL_INFO("svx.fmcomp""modified flag changed, new state: " << ROWSTATUS(m_xCurrentRow));
    }
}

void DbGridControl::StartDrag( sal_Int8 /*nAction*/, const Point& rPosPixel )
{
    if (!m_pSeekCursor || IsResizing())
        return;

    sal_uInt16 nColId = GetColumnId(GetColumnAtXPosPixel(rPosPixel.X()));
    tools::Long   nRow = GetRowAtYPosPixel(rPosPixel.Y());
    if (nColId != HandleColumnId && nRow >= 0)
    {
        if (GetDataWindow().IsMouseCaptured())
            GetDataWindow().ReleaseMouse();

        size_t Location = GetModelColumnPos( nColId );
        DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
        rtl::Reference<OStringTransferable> pTransferable = new OStringTransferable(GetCurrentRowCellText(pColumn,m_xPaintRow));
        pTransferable->StartDrag(this, DND_ACTION_COPY);
    }
}

bool DbGridControl::canCopyCellText(sal_Int32 _nRow, sal_uInt16 _nColId)
{
    return  (_nRow >= 0)
        &&  (_nRow < GetRowCount())
        &&  (_nColId != HandleColumnId)
        &&  (GetModelColumnPos(_nColId) != GRID_COLUMN_NOT_FOUND);
}

void DbGridControl::copyCellText(sal_Int32 _nRow, sal_uInt16 _nColId)
{
    DBG_ASSERT(canCopyCellText(_nRow, _nColId), "DbGridControl::copyCellText: invalid call!");
    DbGridColumn* pColumn = m_aColumns[ GetModelColumnPos(_nColId) ].get();
    SeekRow(_nRow);
    OStringTransfer::CopyString( GetCurrentRowCellText( pColumn,m_xPaintRow ), this );
}

void DbGridControl::executeRowContextMenu(const Point& _rPreferredPos)
{
    std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(nullptr, u"svx/ui/rowsmenu.ui"_ustr));
    std::unique_ptr<weld::Menu> xContextMenu(xBuilder->weld_menu(u"menu"_ustr));

    tools::Rectangle aRect(_rPreferredPos, Size(1,1));
    weld::Window* pParent = weld::GetPopupParent(*this, aRect);

    PreExecuteRowContextMenu(*xContextMenu);
    PostExecuteRowContextMenu(xContextMenu->popup_at_rect(pParent, aRect));
}

void DbGridControl::Command(const CommandEvent& rEvt)
{
    switch (rEvt.GetCommand())
    {
        case CommandEventId::ContextMenu:
        {
            if ( !m_pSeekCursor )
            {
                EditBrowseBox::Command(rEvt);
                return;
            }

            if ( !rEvt.IsMouseEvent() )
            {   // context menu requested by keyboard
                if ( GetSelectRowCount() )
                {
                    tools::Long nRow = FirstSelectedRow( );

                    ::tools::Rectangle aRowRect( GetRowRectPixel( nRow ) );
                    executeRowContextMenu(aRowRect.LeftCenter());

                    // handled
                    return;
                }
            }

            sal_uInt16 nColId = GetColumnId(GetColumnAtXPosPixel(rEvt.GetMousePosPixel().X()));
            tools::Long   nRow = GetRowAtYPosPixel(rEvt.GetMousePosPixel().Y());

            if (nColId == HandleColumnId)
            {
                executeRowContextMenu(rEvt.GetMousePosPixel());
            }
            else if (canCopyCellText(nRow, nColId))
            {
                ::tools::Rectangle aRect(rEvt.GetMousePosPixel(), Size(1, 1));
                weld::Window* pPopupParent = weld::GetPopupParent(*this, aRect);
                std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pPopupParent, u"svx/ui/cellmenu.ui"_ustr));
                std::unique_ptr<weld::Menu> xContextMenu(xBuilder->weld_menu(u"menu"_ustr));
                if (!xContextMenu->popup_at_rect(pPopupParent, aRect).isEmpty())
                    copyCellText(nRow, nColId);
            }
            else
            {
                EditBrowseBox::Command(rEvt);
                return;
            }

            [[fallthrough]];
        }
        default:
            EditBrowseBox::Command(rEvt);
    }
}

IMPL_LINK_NOARG(DbGridControl, OnDelete, void*, void)
{
    m_nDeleteEvent = nullptr;
    DeleteSelectedRows();
}

IMPL_LINK_NOARG(DbGridControl, RearrangeHdl, Timer*, void)
{
    if (isDisposed())
        return;

    // and give it a chance to rearrange
    Point aPoint = GetControlArea().TopLeft();
    sal_uInt16 nX = static_cast<sal_uInt16>(aPoint.X());
    ArrangeControls(nX, aPoint.Y());
    // tdf#155364 like tdf#97731 if the reserved area changed size, give
    // the controls a chance to adapt to the new size
    bool bChanged = ReserveControlArea(nX);
    if (bChanged)
    {
        ArrangeControls(nX, aPoint.Y());
        Invalidate();
    }
}

void DbGridControl::DeleteSelectedRows()
{
    DBG_ASSERT(GetSelection(), "no selection!!!");

    if (!m_pSeekCursor)
        return;
}

CellController* DbGridControl::GetController(sal_Int32 /*nRow*/, sal_uInt16 nColumnId)
{
    if (!IsValid(m_xCurrentRow) || !IsEnabled())
        return nullptr;

    size_t Location = GetModelColumnPos(nColumnId);
    DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
    if (!pColumn)
        return nullptr;

    CellController* pReturn = nullptr;
    if (IsFilterMode())
        pReturn = pColumn->GetController().get();
    else
    {
        if (::comphelper::hasProperty(FM_PROP_ENABLED, pColumn->getModel()))
        {
            if (!::comphelper::getBOOL(pColumn->getModel()->getPropertyValue(FM_PROP_ENABLED)))
                return nullptr;
        }

        bool bInsert = (m_xCurrentRow->IsNew() && (m_nOptions & DbGridControlOptions::Insert));
        bool bUpdate = (!m_xCurrentRow->IsNew() && (m_nOptions & DbGridControlOptions::Update));

        if ((bInsert && !pColumn->IsAutoValue()) || bUpdate)
        {
            pReturn = pColumn->GetController().get();
        }
    }
    return pReturn;
}

void DbGridControl::CellModified()
{
    SAL_INFO("svx.fmcomp""DbGridControl::CellModified");

    {
        ::osl::MutexGuard aGuard(m_aAdjustSafety);
        if (m_nAsynAdjustEvent)
        {
            SAL_INFO("svx.fmcomp""forcing a synchron call to " << (m_bPendingAdjustRows ? "AdjustRows" "AdustDataSource"));
            RemoveUserEvent(m_nAsynAdjustEvent);
            m_nAsynAdjustEvent = nullptr;

            // force the call : this should be no problem as we're probably running in the solar thread here
            // (cell modified is triggered by user actions)
            if (m_bPendingAdjustRows)
                AdjustRows();
            else
                AdjustDataSource();
        }
    }

    if (IsFilterMode() || !IsValid(m_xCurrentRow) || m_xCurrentRow->IsModified())
        return;

    // enable edit mode
    // a data set should be inserted
    if (m_xCurrentRow->IsNew())
    {
        m_xCurrentRow->SetStatus(GridRowStatus::Modified);
        SAL_INFO("svx.fmcomp""current row is new, new state: MODIFIED");
        // if no row was added yet, do it now
        if (m_nCurrentPos == GetRowCount() - 1)
        {
            // increment RowCount
            RowInserted(GetRowCount());
            InvalidateStatusCell(m_nCurrentPos);
            m_aBar->InvalidateAll(m_nCurrentPos);
        }
    }
    else if (m_xCurrentRow->GetStatus() != GridRowStatus::Modified)
    {
        m_xCurrentRow->SetState(m_pDataCursor.get(), false);
        SAL_INFO("svx.fmcomp""current row is not new, after SetState, new state: " << ROWSTATUS(m_xCurrentRow));
        m_xCurrentRow->SetStatus(GridRowStatus::Modified);
        SAL_INFO("svx.fmcomp""current row is not new, new state: MODIFIED");
        InvalidateStatusCell(m_nCurrentPos);
    }
}

void DbGridControl::Dispatch(BrowserDispatchId eId)
{
    if (eId == BrowserDispatchId::CURSORENDOFFILE)
    {
        if (m_nOptions & DbGridControlOptions::Insert)
            AppendNew();
        else
            MoveToLast();
    }
    else
        EditBrowseBox::Dispatch(eId);
}

void DbGridControl::Undo()
{
    if (IsFilterMode() || !IsValid(m_xCurrentRow) || !IsModified())
        return;

    // check if we have somebody doin' the UNDO for us
    int nState = -1;
    if (m_aMasterStateProvider.IsSet())
        nState = m_aMasterStateProvider.Call(DbGridControlNavigationBarState::Undo);
    if (nState>0)
    {   // yes, we have, and the slot is enabled
        DBG_ASSERT(m_aMasterSlotExecutor.IsSet(), "DbGridControl::Undo : a state, but no execute link ?");
        bool lResult = m_aMasterSlotExecutor.Call(DbGridControlNavigationBarState::Undo);
        if (lResult)
            // handled
            return;
    }
    else if (nState == 0)
        // yes, we have, and the slot is disabled
        return;

    BeginCursorAction();

    bool bAppending = m_xCurrentRow->IsNew();
    bool bDirty     = m_xCurrentRow->IsModified();

    try
    {
        // cancel editing
        Reference< XResultSetUpdate >  xUpdateCursor(Reference< XInterface >(*m_pDataCursor), UNO_QUERY);
        // no effects if we're not updating currently
        if (bAppending)
            // just refresh the row
            xUpdateCursor->moveToInsertRow();
        else
            xUpdateCursor->cancelRowUpdates();

    }
    catch(Exception&)
    {
        DBG_UNHANDLED_EXCEPTION("svx");
    }

    EndCursorAction();

    m_xDataRow->SetState(m_pDataCursor.get(), false);
    if (m_xPaintRow == m_xCurrentRow)
        m_xPaintRow = m_xCurrentRow = m_xDataRow;
    else
        m_xCurrentRow = m_xDataRow;

    if (bAppending && (EditBrowseBox::IsModified() || bDirty))
        // remove the row
        if (m_nCurrentPos == GetRowCount() - 2)
        {   // maybe we already removed it (in resetCurrentRow, called if the above moveToInsertRow
            // caused our data source form to be reset - which should be the usual case...)
            RowRemoved(GetRowCount() - 1);
            m_aBar->InvalidateAll(m_nCurrentPos);
        }

    RowModified(m_nCurrentPos);
}

void DbGridControl::resetCurrentRow()
{
    if (IsModified())
    {
        // scenario : we're on the insert row, the row is dirty, and thus there exists a "second" insert row (which
        // is clean). Normally in DataSourcePropertyChanged we would remove this second row if the modified state of
        // the insert row changes from sal_True to sal_False. But if our current cell is the only modified element (means the
        // data source isn't modified) and we're reset this DataSourcePropertyChanged would never be called, so we
        // would never delete the obsolete "second insert row". Thus in this special case this method here
        // is the only possibility to determine the redundance of the row (resetCurrentRow is called when the
        // "first insert row" is about to be cleaned, so of course the "second insert row" is redundant now)
        Reference< XPropertySet > xDataSource = getDataSource()->getPropertySet();
        if (xDataSource.is() && !::comphelper::getBOOL(xDataSource->getPropertyValue(FM_PROP_ISMODIFIED)))
        {
            // are we on a new row currently ?
            if (m_xCurrentRow->IsNew())
            {
                if (m_nCurrentPos == GetRowCount() - 2)
                {
                    RowRemoved(GetRowCount() - 1);
                    m_aBar->InvalidateAll(m_nCurrentPos);
                }
            }
        }

        // update the rows
        m_xDataRow->SetState(m_pDataCursor.get(), false);
        if (m_xPaintRow == m_xCurrentRow)
            m_xPaintRow = m_xCurrentRow = m_xDataRow;
        else
            m_xCurrentRow = m_xDataRow;
    }

    RowModified(GetCurRow()); // will update the current controller if affected
}

void DbGridControl::RowModified( sal_Int32 nRow )
{
    if (nRow == m_nCurrentPos && IsEditing())
    {
        CellControllerRef aTmpRef = Controller();
        aTmpRef->SaveValue();
        InitController(aTmpRef, m_nCurrentPos, GetCurColumnId());
    }
    EditBrowseBox::RowModified(nRow);
}

bool DbGridControl::IsModified() const
{
    return !IsFilterMode() && IsValid(m_xCurrentRow) && (m_xCurrentRow->IsModified() || EditBrowseBox::IsModified());
}

bool DbGridControl::IsCurrentAppending() const
{
    return m_xCurrentRow.is() && m_xCurrentRow->IsNew();
}

bool DbGridControl::IsInsertionRow(sal_Int32 nRow) const
{
    return (m_nOptions & DbGridControlOptions::Insert) && m_nTotalCount >= 0 && (nRow == GetRowCount() - 1);
}

bool DbGridControl::SaveModified()
{
    SAL_INFO("svx.fmcomp""DbGridControl::SaveModified");
    DBG_ASSERT(IsValid(m_xCurrentRow), "GridControl:: Invalid row");
    if (!IsValid(m_xCurrentRow))
        return true;

    // accept input for this field
    // Where there changes at the current input field?
    if (!EditBrowseBox::IsModified())
        return true;

    size_t Location = GetModelColumnPos( GetCurColumnId() );
    DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
    bool bOK = pColumn && pColumn->Commit();
    DBG_ASSERT( Controller().is(), "DbGridControl::SaveModified: was modified, by have no controller?!" );
    if ( !Controller().is() )
        // this might happen if the callbacks implicitly triggered by Commit
        // fiddled with the form or the control ...
        // (Note that this here is a workaround, at most. We need a general concept how
        // to treat this, you can imagine an arbitrary number of scenarios where a callback
        // triggers something which leaves us in an expected state.)
        // #i67147# / 2006-07-17 / frank.schoenheit@sun.com
        return bOK;

    if (bOK)
    {
        Controller()->SaveValue();

        if ( IsValid(m_xCurrentRow) )
        {
            m_xCurrentRow->SetState(m_pDataCursor.get(), false);
            SAL_INFO("svx.fmcomp""explicit SetState, new state: " << ROWSTATUS(m_xCurrentRow));
            InvalidateStatusCell( m_nCurrentPos );
        }
        else
        {
            SAL_INFO("svx.fmcomp""no SetState, new state: " << ROWSTATUS(m_xCurrentRow));
        }
    }

    return bOK;
}

bool DbGridControl::SaveRow()
{
    SAL_INFO("svx.fmcomp""DbGridControl::SaveRow");
    // valid row
    if (!IsValid(m_xCurrentRow) || !IsModified())
        return true;
    // value of the controller was not saved, yet
    else if (Controller().is() && Controller()->IsValueChangedFromSaved())
    {
        if (!SaveModified())
            return false;
    }
    m_bUpdating = true;

    BeginCursorAction();
    bool bAppending = m_xCurrentRow->IsNew();
    bool bSuccess = false;
    try
    {
        Reference< XResultSetUpdate >  xUpdateCursor(Reference< XInterface >(*m_pDataCursor), UNO_QUERY);
        if (bAppending)
            xUpdateCursor->insertRow();
        else
            xUpdateCursor->updateRow();
        bSuccess = true;
    }
    catch(SQLException&)
    {
        EndCursorAction();
        m_bUpdating = false;
        return false;
    }

    try
    {
        if (bSuccess)
        {
            // if we are appending we still sit on the insert row
            // we don't move just clear the flags not to move on the current row
            m_xCurrentRow->SetState(m_pDataCursor.get(), false);
            SAL_INFO("svx.fmcomp""explicit SetState after a successful update, new state: " << ROWSTATUS(m_xCurrentRow));
            m_xCurrentRow->SetNew(false);

            // adjust the seekcursor if it is on the same position as the datacursor
            if (m_nSeekPos == m_nCurrentPos || bAppending)
            {
                // get the bookmark to refetch the data
                // in insert mode we take the new bookmark of the data cursor
                Any aBookmark = bAppending ? m_pDataCursor->getBookmark() : m_pSeekCursor->getBookmark();
                m_pSeekCursor->moveToBookmark(aBookmark);
                // get the data
                m_xSeekRow->SetState(m_pSeekCursor.get(), true);
                m_nSeekPos = m_pSeekCursor->getRow() - 1;
            }
        }
        // and repaint the row
        RowModified(m_nCurrentPos);
    }
    catch(Exception&)
    {
    }

    m_bUpdating = false;
    EndCursorAction();

    // The old code returned (nRecords != 0) here.
    // Me thinks this is wrong : If something goes wrong while update the record, an exception will be thrown,
    // which results in a "return sal_False" (see above). If no exception is thrown, everything is fine. If nRecords
    // is zero, this simply means all fields had their original values.
    // FS - 06.12.99 - 70502
    return true;
}

bool DbGridControl::PreNotify(NotifyEvent& rEvt)
{
    // do not handle events of the Navbar
    if (m_aBar->IsWindowOrChild(rEvt.GetWindow()))
        return BrowseBox::PreNotify(rEvt);

    switch (rEvt.GetType())
    {
        case NotifyEventType::KEYINPUT:
        {
            const KeyEvent* pKeyEvent = rEvt.GetKeyEvent();

            sal_uInt16 nCode = pKeyEvent->GetKeyCode().GetCode();
            bool   bShift = pKeyEvent->GetKeyCode().IsShift();
            bool   bCtrl = pKeyEvent->GetKeyCode().IsMod1();
            bool   bAlt = pKeyEvent->GetKeyCode().IsMod2();
            if ( ( KEY_TAB == nCode ) && bCtrl && !bAlt )
            {
                // Ctrl-Tab is used to step out of the control, without traveling to the
                // remaining cells first
                // -> build a new key event without the Ctrl-key, and let the very base class handle it
                vcl::KeyCode aNewCode( KEY_TAB, bShift, falsefalsefalse );
                KeyEvent aNewEvent( pKeyEvent->GetCharCode(), aNewCode );

                // call the Control - our direct base class will interpret this in a way we do not want (and do
                // a cell traveling)
                Control::KeyInput( aNewEvent );
                return true;
            }

            if ( !bShift && !bCtrl && ( KEY_ESCAPE == nCode ) )
            {
                if (IsModified())
                {
                    Undo();
                    return true;
                }
            }
            else if ( ( KEY_DELETE == nCode ) && !bShift && !bCtrl )    // delete rows
            {
                if ((m_nOptions & DbGridControlOptions::Delete) && GetSelectRowCount())
                {
                    // delete asynchronously
                    if (m_nDeleteEvent)
                        Application::RemoveUserEvent(m_nDeleteEvent);
                    m_nDeleteEvent = Application::PostUserEvent(LINK(this,DbGridControl,OnDelete), nullptr, true);
                    return true;
                }
            }

            [[fallthrough]];
        }
        default:
            return EditBrowseBox::PreNotify(rEvt);
    }
}

bool DbGridControl::IsTabAllowed(bool bRight) const
{
    if (bRight)
        // Tab only if not on the _last_ row
        return GetCurRow() < (GetRowCount() - 1) || !m_bRecordCountFinal ||
               GetViewColumnPos(GetCurColumnId()) < (GetViewColCount() - 1);
    else
    {
        // Tab only if not on the _first_ row
        return GetCurRow() > 0 || (GetCurColumnId() && GetViewColumnPos(GetCurColumnId()) > 0);
    }
}

void DbGridControl::KeyInput( const KeyEvent& rEvt )
{
    if (rEvt.GetKeyCode().GetFunction() == KeyFuncType::COPY)
    {
        tools::Long nRow = GetCurRow();
        sal_uInt16 nColId = GetCurColumnId();
        if (nRow >= 0 && nRow < GetRowCount() && nColId < ColCount())
        {
            size_t Location = GetModelColumnPos( nColId );
            DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
            OStringTransfer::CopyString( GetCurrentRowCellText( pColumn, m_xCurrentRow ), this );
            return;
        }
    }
    EditBrowseBox::KeyInput(rEvt);
}

void DbGridControl::HideColumn(sal_uInt16 nId)
{
    DeactivateCell();

    // determine the col for the focus to set to after removal
    sal_uInt16 nPos = GetViewColumnPos(nId);
    sal_uInt16 nNewColId = nPos == (ColCount()-1)
        ? GetColumnIdFromViewPos(nPos-1)    // last col is to be removed -> take the previous
        : GetColumnIdFromViewPos(nPos+1);   // take the next

    tools::Long lCurrentWidth = GetColumnWidth(nId);
    EditBrowseBox::RemoveColumn(nId);
        // don't use my own RemoveColumn, this would remove it from m_aColumns, too

    // update my model
    size_t Location = GetModelColumnPos( nId );
    DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
    DBG_ASSERT(pColumn, "DbGridControl::HideColumn : somebody did hide a nonexistent column !");
    if (pColumn)
    {
        pColumn->m_bHidden = true;
        pColumn->m_nLastVisibleWidth = CalcReverseZoom(lCurrentWidth);
    }

    // and reset the focus
    if ( nId == GetCurColumnId() )
        GoToColumnId( nNewColId );
}

void DbGridControl::ShowColumn(sal_uInt16 nId)
{
    sal_uInt16 nPos = GetModelColumnPos(nId);
    DBG_ASSERT(nPos != GRID_COLUMN_NOT_FOUND, "DbGridControl::ShowColumn : invalid argument !");
    if (nPos == GRID_COLUMN_NOT_FOUND)
        return;

    DbGridColumn* pColumn = m_aColumns[ nPos ].get();
    if (!pColumn->IsHidden())
    {
        DBG_ASSERT(GetViewColumnPos(nId) != GRID_COLUMN_NOT_FOUND, "DbGridControl::ShowColumn : inconsistent internal state !");
            // if the column isn't marked as hidden, it should be visible, shouldn't it ?
        return;
    }
    DBG_ASSERT(GetViewColumnPos(nId) == GRID_COLUMN_NOT_FOUND, "DbGridControl::ShowColumn : inconsistent internal state !");
        // the opposite situation ...

    // to determine the new view position we need an adjacent non-hidden column
    sal_uInt16 nNextNonHidden = BROWSER_INVALIDID;
    // first search the cols to the right
    for ( size_t i = nPos + 1; i < m_aColumns.size(); ++i )
    {
        DbGridColumn* pCurCol = m_aColumns[ i ].get();
        if (!pCurCol->IsHidden())
        {
            nNextNonHidden = i;
            break;
        }
    }
    if ((nNextNonHidden == BROWSER_INVALIDID) && (nPos > 0))
    {
        // then to the left
        for ( size_t i = nPos; i > 0; --i )
        {
            DbGridColumn* pCurCol = m_aColumns[ i-1 ].get();
            if (!pCurCol->IsHidden())
            {
                nNextNonHidden = i-1;
                break;
            }
        }
    }
    sal_uInt16 nNewViewPos = (nNextNonHidden == BROWSER_INVALIDID)
        ? 1 // there is no visible column -> insert behind the handle col
        : GetViewColumnPos( m_aColumns[ nNextNonHidden ]->GetId() ) + 1;
            // the first non-handle col has "view pos" 0, but the pos arg for InsertDataColumn expects
            // a position 1 for the first non-handle col -> +1
    DBG_ASSERT(nNewViewPos != GRID_COLUMN_NOT_FOUND, "DbGridControl::ShowColumn : inconsistent internal state !");
        // we found a col marked as visible but got no view pos for it ...

    if ((nNextNonHidden<nPos) && (nNextNonHidden != BROWSER_INVALIDID))
        // nNextNonHidden is a column to the left, so we want to insert the new col _right_ beside it's pos
        ++nNewViewPos;

    DeactivateCell();

    OUString aName;
    pColumn->getModel()->getPropertyValue(FM_PROP_LABEL) >>= aName;
    InsertDataColumn(nId, aName, CalcZoom(pColumn->m_nLastVisibleWidth), HeaderBarItemBits::CENTER | HeaderBarItemBits::CLICKABLE, nNewViewPos);
    pColumn->m_bHidden = false;

    ActivateCell();
    Invalidate();
}

sal_uInt16 DbGridControl::GetColumnIdFromModelPos( sal_uInt16 nPos ) const
{
    if (nPos >= m_aColumns.size())
    {
        OSL_FAIL("DbGridControl::GetColumnIdFromModelPos : invalid argument !");
        return GRID_COLUMN_NOT_FOUND;
    }

    DbGridColumn* pCol = m_aColumns[ nPos ].get();
#if (OSL_DEBUG_LEVEL > 0) || defined DBG_UTIL
    // in the debug version, we convert the ModelPos into a ViewPos and compare this with the
    // value we will return (nId at the corresponding Col in m_aColumns)

    if (!pCol->IsHidden())
    {   // makes sense only if the column is visible
        sal_uInt16 nViewPos = nPos;
        for ( size_t i = 0; i < m_aColumns.size() && i < nPos; ++i)
            if ( m_aColumns[ i ]->IsHidden())
                --nViewPos;

        DBG_ASSERT(GetColumnIdFromViewPos(nViewPos) == pCol->GetId(),
            "DbGridControl::GetColumnIdFromModelPos : this isn't consistent... did I misunderstand something ?");
    }
#endif
    return pCol->GetId();
}

sal_uInt16 DbGridControl::GetModelColumnPos( sal_uInt16 nId ) const
{
    for ( size_t i = 0; i < m_aColumns.size(); ++i )
        if ( m_aColumns[ i ]->GetId() == nId )
            return i;

    return GRID_COLUMN_NOT_FOUND;
}

void DbGridControl::implAdjustInSolarThread(bool _bRows)
{
    SAL_INFO("svx.fmcomp""DbGridControl::implAdjustInSolarThread");
    ::osl::MutexGuard aGuard(m_aAdjustSafety);
    if (!Application::IsMainThread())
    {
        m_nAsynAdjustEvent = PostUserEvent(LINK(this, DbGridControl, OnAsyncAdjust), reinterpret_castvoid* >( _bRows ), true);
        m_bPendingAdjustRows = _bRows;
        if (_bRows)
            SAL_INFO("svx.fmcomp""posting an AdjustRows");
        else
            SAL_INFO("svx.fmcomp""posting an AdjustDataSource");
    }
    else
    {
        if (_bRows)
            SAL_INFO("svx.fmcomp""doing an AdjustRows");
        else
            SAL_INFO("svx.fmcomp""doing an AdjustDataSource");
        // always adjust the rows before adjusting the data source
        // If this is not necessary (because the row count did not change), nothing is done
        // The problem is that we can't rely on the order of which the calls come in: If the cursor was moved
        // to a position behind row count know 'til now, the cursorMoved notification may come before the
        // RowCountChanged notification
        // 94093 - 02.11.2001 - frank.schoenheit@sun.com
        AdjustRows();

        if ( !_bRows )
            AdjustDataSource();
    }
}

IMPL_LINK(DbGridControl, OnAsyncAdjust, void*, pAdjustWhat, void)
{
    m_nAsynAdjustEvent = nullptr;

    AdjustRows();
        // see implAdjustInSolarThread for a comment why we do this every time

    if ( !pAdjustWhat )
        AdjustDataSource();
}

void DbGridControl::BeginCursorAction()
{
    for (const auto& rListener : m_aFieldListeners)
    {
        GridFieldValueListener* pCurrent = rListener.second;
        if (pCurrent)
            pCurrent->suspend();
    }

    if (m_pDataSourcePropListener)
        m_pDataSourcePropListener->suspend();
}

void DbGridControl::EndCursorAction()
{
    for (const auto& rListener : m_aFieldListeners)
    {
        GridFieldValueListener* pCurrent = rListener.second;
        if (pCurrent)
            pCurrent->resume();
    }

    if (m_pDataSourcePropListener)
        m_pDataSourcePropListener->resume();
}

void DbGridControl::ConnectToFields()
{
    DBG_ASSERT(m_aFieldListeners.empty(), "DbGridControl::ConnectToFields : please call DisconnectFromFields first !");

    for (auto const & pCurrent : m_aColumns)
    {
        sal_uInt16 nViewPos = pCurrent ? GetViewColumnPos(pCurrent->GetId()) : GRID_COLUMN_NOT_FOUND;
        if (GRID_COLUMN_NOT_FOUND == nViewPos)
            continue;

        Reference< XPropertySet >  xField = pCurrent->GetField();
        if (!xField.is())
            continue;

        // column is visible and bound here
        GridFieldValueListener*& rpListener = m_aFieldListeners[pCurrent->GetId()];
        DBG_ASSERT(!rpListener, "DbGridControl::ConnectToFields : already a listener for this column ?!");
        rpListener = new GridFieldValueListener(*this, xField, pCurrent->GetId());
    }
}

void DbGridControl::DisconnectFromFields()
{
    if (m_aFieldListeners.empty())
        return;

    while (!m_aFieldListeners.empty())
    {
        sal_Int32 nOldSize = m_aFieldListeners.size();
        m_aFieldListeners.begin()->second->dispose();
        DBG_ASSERT(nOldSize > static_cast<sal_Int32>(m_aFieldListeners.size()), "DbGridControl::DisconnectFromFields : dispose on a listener should result in a removal from my list !");
    }
}

void DbGridControl::FieldValueChanged(sal_uInt16 _nId)
{
    osl::MutexGuard aPreventDestruction(m_aDestructionSafety);
    // needed as this may run in a thread other than the main one
    if (GetRowStatus(GetCurRow()) != EditBrowseBox::MODIFIED)
        // all other cases are handled elsewhere
        return;

    size_t Location = GetModelColumnPos( _nId );
    DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
    if (!pColumn)
        return;

    std::unique_ptr<vcl::SolarMutexTryAndBuyGuard> pGuard;
    while (!m_bWantDestruction && (!pGuard || !pGuard->isAcquired()))
        pGuard.reset(new vcl::SolarMutexTryAndBuyGuard);

    if (m_bWantDestruction)
    {   // at this moment, within another thread, our destructor tries to destroy the listener which called this method
        // => don't do anything
        // 73365 - 23.02.00 - FS
        return;
    }

    // and finally do the update ...
    pColumn->UpdateFromField(m_xCurrentRow.get(), m_xFormatter);
    RowModified(GetCurRow());
}

void DbGridControl::FieldListenerDisposing(sal_uInt16 _nId)
{
    auto aPos = m_aFieldListeners.find(_nId);
    if (aPos == m_aFieldListeners.end())
    {
        OSL_FAIL("DbGridControl::FieldListenerDisposing : invalid call (did not find the listener) !");
        return;
    }

    delete aPos->second;

    m_aFieldListeners.erase(aPos);
}

void DbGridControl::disposing(sal_uInt16 _nId)
{
    if (_nId == 0)
    {   // the seek cursor is being disposed
        ::osl::MutexGuard aGuard(m_aAdjustSafety);
        setDataSource(nullptr, DbGridControlOptions::Readonly); // our clone was disposed so we set our datasource to null to avoid later access to it
        if (m_nAsynAdjustEvent)
        {
            RemoveUserEvent(m_nAsynAdjustEvent);
            m_nAsynAdjustEvent = nullptr;
        }
    }
}

sal_Int32 DbGridControl::GetAccessibleControlCount() const
{
    return EditBrowseBox::GetAccessibleControlCount() + 1; // the navigation control
}

Reference<XAccessible > DbGridControl::CreateAccessibleControl( sal_Int32 _nIndex )
{
    Reference<XAccessible > xRet;
    if ( _nIndex == EditBrowseBox::GetAccessibleControlCount() )
    {
        xRet = m_aBar->GetAccessible();
    }
    else
        xRet = EditBrowseBox::CreateAccessibleControl( _nIndex );
    return xRet;
}

Reference< XAccessible > DbGridControl::CreateAccessibleCell( sal_Int32 _nRow, sal_uInt16 _nColumnPos )
{
    sal_uInt16 nColumnId = GetColumnId( _nColumnPos );
    size_t Location = GetModelColumnPos(nColumnId);
    DbGridColumn* pColumn = ( Location < m_aColumns.size() ) ? m_aColumns[ Location ].get() : nullptr;
    if ( pColumn )
    {
        Reference< css::awt::XControl> xInt(pColumn->GetCell());
        Reference< css::awt::XCheckBox> xBox(xInt,UNO_QUERY);
        if ( xBox.is() )
        {
            TriState eValue = TRISTATE_FALSE;
            switch( xBox->getState() )
            {
                case 0:
                    eValue = TRISTATE_FALSE;
                    break;
                case 1:
                    eValue = TRISTATE_TRUE;
                    break;
                case 2:
                    eValue = TRISTATE_INDET;
                    break;
            }
            return EditBrowseBox::CreateAccessibleCheckBoxCell( _nRow, _nColumnPos,eValue );
        }
    }
    return EditBrowseBox::CreateAccessibleCell( _nRow, _nColumnPos );
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Messung V0.5 in Prozent
C=93 H=96 G=94

¤ Dauer der Verarbeitung: 0.51 Sekunden  (vorverarbeitet am  2026-05-08) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.