/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 { VclPtr 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 m_xRealListener; public: DisposeListenerGridBridge( DbGridControl& _rParent, const Reference< XComponent >& _rxObject); virtual ~DisposeListenerGridBridge() override; virtual void disposing(sal_Int16 _nId) override { m_rParent.disposing(_nId); } }; DisposeListenerGridBridge::DisposeListenerGridBridge(DbGridControl& _rParent, const Reference< XComponent >& _rxObject) :FmXDisposeListener() ,m_rParent(_rParent) { if (_rxObject.is()) { m_xRealListener = new FmXDisposeMultiplexer(this, _rxObject); } } DisposeListenerGridBridge::~DisposeListenerGridBridge() { if (m_xRealListener.is()) { m_xRealListener->dispose(); } } const DbGridControlNavigationBarState ControlMap[] = { DbGridControlNavigationBarState::Text, DbGridControlNavigationBarState::Absolute, DbGridControlNavigationBarState::Of, DbGridControlNavigationBarState::Count, DbGridControlNavigationBarState::First, DbGridControlNavigationBarState::Next, DbGridControlNavigationBarState::Prev, DbGridControlNavigationBarState::Last, DbGridControlNavigationBarState::New, DbGridControlNavigationBarState::NONE }; bool CompareBookmark(const Any& aLeft, const Any& aRight) { return aLeft == aRight; } class FmXGridSourcePropListener : public ::comphelper::OPropertyChangeListener { VclPtr 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 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(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(*m_xPrevBtn, LINK(this,NavigationBar,OnClick))) , m_xNextRepeater(std::make_shared(*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(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(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(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(GetParent()); if (!pParent->IsOpen() || pParent->IsDesignMode() || !pParent->IsEnabled() || pParent->IsFilterMode() ) return false; else { // check if we have a master state provider if (pParent->m_aMasterStateProvider.IsSet()) { tools::Long nState = pParent->m_aMasterStateProvider.Call( nWhich ); if (nState>=0) return (nState>0); } bool bAvailable = true; switch (nWhich) { case DbGridControlNavigationBarState::First: case DbGridControlNavigationBarState::Prev: bAvailable = m_nCurrentPos > 0; break; case DbGridControlNavigationBarState::Next: if(pParent->m_bRecordCountFinal) { bAvailable = m_nCurrentPos < pParent->GetRowCount() - 1; if (!bAvailable && pParent->GetOptions() & DbGridControlOptions::Insert) bAvailable = (m_nCurrentPos == pParent->GetRowCount() - 2) && pParent->IsModified(); } break; case DbGridControlNavigationBarState::Last: if(pParent->m_bRecordCountFinal) { if (pParent->GetOptions() & DbGridControlOptions::Insert) bAvailable = pParent->IsCurrentAppending() ? pParent->GetRowCount() > 1 : m_nCurrentPos != pParent->GetRowCount() - 2; else bAvailable = m_nCurrentPos != pParent->GetRowCount() - 1; } break; case DbGridControlNavigationBarState::New: bAvailable = (pParent->GetOptions() & DbGridControlOptions::Insert) && pParent->GetRowCount() && m_nCurrentPos < pParent->GetRowCount() - 1; break; case DbGridControlNavigationBarState::Absolute: bAvailable = pParent->GetRowCount() > 0; break; default: break; } return bAvailable; } } void NavigationBar::SetState(DbGridControlNavigationBarState nWhich) { bool bAvailable = GetState(nWhich); DbGridControl* pParent = static_cast(GetParent()); weld::Widget* pWnd = nullptr; switch (nWhich) { case DbGridControlNavigationBarState::First: pWnd = m_xFirstBtn.get(); break; case DbGridControlNavigationBarState::Prev: pWnd = m_xPrevBtn.get(); break; case DbGridControlNavigationBarState::Next: pWnd = m_xNextBtn.get(); break; case DbGridControlNavigationBarState::Last: pWnd = m_xLastBtn.get(); break; case DbGridControlNavigationBarState::New: pWnd = m_xNewBtn.get(); break; case DbGridControlNavigationBarState::Absolute: pWnd = m_xAbsolute->GetWidget(); if (bAvailable) m_xAbsolute->set_text(OUString::number(m_nCurrentPos + 1)); else m_xAbsolute->set_text(OUString()); break; case DbGridControlNavigationBarState::Text: pWnd = m_xRecordText.get(); break; case DbGridControlNavigationBarState::Of: pWnd = m_xRecordOf.get(); break; case DbGridControlNavigationBarState::Count: { pWnd = m_xRecordCount.get(); OUString aText; if (bAvailable) { if (pParent->GetOptions() & DbGridControlOptions::Insert) { if (pParent->IsCurrentAppending() && !pParent->IsModified()) aText = OUString::number(pParent->GetRowCount()); else aText = OUString::number(pParent->GetRowCount() - 1); } else aText = OUString::number(pParent->GetRowCount()); if(!pParent->m_bRecordCountFinal) aText += " *"; } else aText.clear(); // add the number of selected rows, if applicable if (pParent->GetSelectRowCount()) { OUString aExtendedInfo = aText + " (" + OUString::number(pParent->GetSelectRowCount()) + ")"; m_xRecordCount->set_label(aExtendedInfo); } else m_xRecordCount->set_label(aText); pParent->SetRealRowCount(aText); } break; default: break; } DBG_ASSERT(pWnd, "no window"); if (!(pWnd && (pWnd->get_sensitive() != bAvailable))) return; // this "pWnd->IsEnabled() != bAvailable" is a little hack : Window::Enable always generates a user // event (ImplGenerateMouseMove) even if nothing happened. This may lead to some unwanted effects, so we // do this check. // For further explanation see Bug 69900. pWnd->set_sensitive(bAvailable); if (!bAvailable) { if (pWnd == m_xNextBtn.get()) m_xNextRepeater->Stop(); else if (pWnd == m_xPrevBtn.get()) m_xPrevRepeater->Stop(); } } 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& 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::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 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(aPoint.X()); ArrangeControls(nX, static_cast(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; } } } catch( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); } bool bPermanentCursor = IsPermanentCursorEnabled(); m_nMode = DEFAULT_BROWSE_MODE; if ( bPermanentCursor ) { m_nMode |= BrowserMode::CURSOR_WO_FOCUS; m_nMode &= ~BrowserMode::HIDECURSOR; } else { // updates are allowed -> no focus rectangle if ( m_nOptions & DbGridControlOptions::Update ) m_nMode |= BrowserMode::HIDECURSOR; } m_nMode |= BrowserMode::MULTISELECTION; adjustModeForScrollbars( m_nMode, m_bNavigationBar, m_bHideScrollbars ); Reference< XColumnsSupplier > xSupplyColumns(_xCursor, UNO_QUERY); if (xSupplyColumns.is()) InitColumnsByFields(Reference< XIndexAccess > (xSupplyColumns->getColumns(), UNO_QUERY)); ConnectToFields(); } sal_uInt32 nRecordCount(0); if (m_pSeekCursor) { Reference< XPropertySet > xSet = m_pDataCursor->getPropertySet(); xSet->getPropertyValue(FM_PROP_ROWCOUNT) >>= nRecordCount; m_bRecordCountFinal = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ROWCOUNTFINAL)); m_xRowSetListener = new RowSetEventListener(this); Reference< XRowsChangeBroadcaster> xChangeBroad(xSet,UNO_QUERY); if ( xChangeBroad.is( ) ) xChangeBroad->addRowsChangeListener(m_xRowSetListener); // insert the currently known rows // and one row if we are able to insert rows if (m_nOptions & DbGridControlOptions::Insert) { // insert the empty row for insertion m_xEmptyRow = new DbGridRow(); ++nRecordCount; } if (nRecordCount) { m_xPaintRow = m_xSeekRow = new DbGridRow(m_pSeekCursor.get(), true); m_xDataRow = new DbGridRow(m_pDataCursor.get(), false); RowInserted(0, nRecordCount, false); if (m_xSeekRow->IsValid()) try { m_nSeekPos = m_pSeekCursor->getRow() - 1; } catch( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); m_nSeekPos = -1; } } else { // no rows so we don't need a seekcursor m_pSeekCursor.reset(); } } // go to the old column if (nCurPos == BROWSER_INVALIDID || nCurPos >= ColCount()) nCurPos = 0; // Column zero is a valid choice and guaranteed to exist, // but invisible to the user; if we have at least one // user-visible column, go to that one. if (nCurPos == 0 && ColCount() > 1) nCurPos = 1; // there are rows so go to the selected current column if (nRecordCount) GoToRowColumnId(0, GetColumnId(nCurPos)); // else stop the editing if necessary else if (IsEditing()) DeactivateCell(); // now reset the mode if (m_nMode != nOldMode) SetMode(m_nMode); // RecalcRows was already called while resizing if (!IsResizing() && GetRowCount()) RecalcRows(GetTopRow(), GetVisibleRows(), true); m_aBar->InvalidateAll(m_nCurrentPos, true); SetUpdateMode(true); // start listening on the seek cursor if (m_pSeekCursor) m_pCursorDisposeListener.reset(new DisposeListenerGridBridge(*this, Reference< XComponent > (Reference< XInterface >(*m_pSeekCursor), UNO_QUERY))); } void DbGridControl::RemoveColumns() { if ( !isDisposed() && IsEditing() ) DeactivateCell(); m_aColumns.clear(); EditBrowseBox::RemoveColumns(); } std::unique_ptr DbGridControl::CreateColumn(sal_uInt16 nId) { return std::unique_ptr(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