diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /forms/source/runtime | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | forms/source/runtime/formoperations.cxx | 1767 | ||||
-rw-r--r-- | forms/source/runtime/formoperations.hxx | 381 |
2 files changed, 2148 insertions, 0 deletions
diff --git a/forms/source/runtime/formoperations.cxx b/forms/source/runtime/formoperations.cxx new file mode 100644 index 000000000..11207991d --- /dev/null +++ b/forms/source/runtime/formoperations.cxx @@ -0,0 +1,1767 @@ +/* -*- 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 <config_features.h> +#include <config_fuzzers.h> + +#include "formoperations.hxx" +#include <frm_strings.hxx> +#include <frm_resource.hxx> +#include <strings.hrc> + +#include <com/sun/star/ucb/AlreadyInitializedException.hpp> +#include <com/sun/star/util/XModifyBroadcaster.hpp> +#include <com/sun/star/form/runtime/FormFeature.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/awt/XControl.hpp> +#include <com/sun/star/form/XGrid.hpp> +#include <com/sun/star/form/XBoundControl.hpp> +#include <com/sun/star/form/XBoundComponent.hpp> +#include <com/sun/star/sdbcx/XRowLocate.hpp> +#include <com/sun/star/form/XConfirmDeleteListener.hpp> +#include <com/sun/star/sdb/RowChangeEvent.hpp> +#include <com/sun/star/sdb/RowChangeAction.hpp> +#include <com/sun/star/sdb/OrderDialog.hpp> +#include <com/sun/star/sdb/FilterDialog.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/form/XReset.hpp> +#include <com/sun/star/beans/XMultiPropertySet.hpp> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/util/XRefreshable.hpp> + +#include <connectivity/dbtools.hxx> +#include <connectivity/dbexception.hxx> +#include <vcl/svapp.hxx> +#include <vcl/stdtext.hxx> +#include <vcl/weld.hxx> +#include <tools/diagnose_ex.h> +#include <comphelper/container.hxx> +#include <comphelper/property.hxx> +#include <comphelper/namedvaluecollection.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <osl/mutex.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> + + +namespace frm +{ + + + using ::dbtools::SQLExceptionInfo; + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::uno::RuntimeException; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::sdbc::XRowSet; + using ::com::sun::star::sdbc::XResultSetUpdate; + using ::com::sun::star::form::runtime::XFormController; + using ::com::sun::star::form::runtime::XFormOperations; + using ::com::sun::star::form::runtime::XFeatureInvalidation; + using ::com::sun::star::form::runtime::FeatureState; + using ::com::sun::star::lang::IllegalArgumentException; + using ::com::sun::star::sdbc::SQLException; + using namespace ::com::sun::star::sdbc; + using ::com::sun::star::form::XForm; + using ::com::sun::star::ucb::AlreadyInitializedException; + using ::com::sun::star::util::XModifyBroadcaster; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::lang::EventObject; + using ::com::sun::star::beans::PropertyChangeEvent; + using ::com::sun::star::lang::XMultiServiceFactory; + using ::com::sun::star::lang::DisposedException; + using ::com::sun::star::beans::XPropertySet; + using ::com::sun::star::awt::XControl; + using ::com::sun::star::form::XGrid; + using ::com::sun::star::container::XIndexAccess; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::form::XBoundControl; + using ::com::sun::star::form::XBoundComponent; + using ::com::sun::star::sdbcx::XRowLocate; + using ::com::sun::star::form::XConfirmDeleteListener; + using ::com::sun::star::sdb::RowChangeEvent; + using namespace ::com::sun::star::sdb; + using ::com::sun::star::form::XReset; + using ::com::sun::star::beans::XMultiPropertySet; + using ::com::sun::star::lang::WrappedTargetException; + using ::com::sun::star::ui::dialogs::XExecutableDialog; + using ::com::sun::star::beans::NamedValue; + using ::com::sun::star::util::XRefreshable; + using ::com::sun::star::awt::XControlModel; + + namespace FormFeature = ::com::sun::star::form::runtime::FormFeature; + namespace RowChangeAction = ::com::sun::star::sdb::RowChangeAction; + + FormOperations::FormOperations( const Reference< XComponentContext >& _rxContext ) + :FormOperations_Base( m_aMutex ) + ,m_xContext( _rxContext ) + ,m_bInitializedParser( false ) + ,m_bActiveControlModified( false ) + ,m_bConstructed( false ) + #ifdef DBG_UTIL + ,m_nMethodNestingLevel( 0 ) + #endif + { + } + + FormOperations::~FormOperations() + { + } + + void SAL_CALL FormOperations::initialize( const Sequence< Any >& _arguments ) + { + if ( m_bConstructed ) + throw AlreadyInitializedException(); + + if ( _arguments.getLength() == 1 ) + { + Reference< XFormController > xController; + Reference< XForm > xForm; + if ( _arguments[0] >>= xController ) + createWithFormController( xController ); + else if ( _arguments[0] >>= xForm ) + createWithForm( xForm ); + else + throw IllegalArgumentException( OUString(), *this, 1 ); + return; + } + + throw IllegalArgumentException( OUString(), *this, 0 ); + } + + OUString SAL_CALL FormOperations::getImplementationName( ) + { + return "com.sun.star.comp.forms.FormOperations"; + } + + sal_Bool SAL_CALL FormOperations::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService(this, ServiceName); + } + + Sequence< OUString > SAL_CALL FormOperations::getSupportedServiceNames( ) + { + return { "com.sun.star.form.runtime.FormOperations" }; + } + + Reference< XRowSet > SAL_CALL FormOperations::getCursor() + { + MethodGuard aGuard( *this ); + return m_xCursor; + } + + Reference< XResultSetUpdate > SAL_CALL FormOperations::getUpdateCursor() + { + MethodGuard aGuard( *this ); + return m_xUpdateCursor; + } + + + Reference< XFormController > SAL_CALL FormOperations::getController() + { + MethodGuard aGuard( *this ); + return m_xController; + } + + + Reference< XFeatureInvalidation > SAL_CALL FormOperations::getFeatureInvalidation() + { + MethodGuard aGuard( *this ); + return m_xFeatureInvalidation; + } + + + void SAL_CALL FormOperations::setFeatureInvalidation( const Reference< XFeatureInvalidation > & _rxFeatureInvalidation ) + { + MethodGuard aGuard( *this ); + m_xFeatureInvalidation = _rxFeatureInvalidation; + } + + + FeatureState SAL_CALL FormOperations::getState( ::sal_Int16 _nFeature ) + { + MethodGuard aGuard( *this ); + + FeatureState aState; + aState.Enabled = false; + + try + { + // some checks for basic pre-requisites + if ( !m_xLoadableForm.is() + || !m_xLoadableForm->isLoaded() + || !m_xCursorProperties.is() + ) + { + return aState; + } + + switch ( _nFeature ) + { + case FormFeature::MoveToFirst: + case FormFeature::MoveToPrevious: + aState.Enabled = impl_canMoveLeft_throw( ); + break; + + case FormFeature::MoveToNext: + aState.Enabled = impl_canMoveRight_throw(); + break; + + case FormFeature::MoveToLast: + aState.Enabled = impl_getRowCount_throw() && ( !m_xCursor->isLast() || impl_isInsertionRow_throw() ); + break; + + case FormFeature::DeleteRecord: + // already deleted ? + if ( m_xCursor->rowDeleted() ) + aState.Enabled = false; + else + { + // allowed to delete the row ? + aState.Enabled = !impl_isInsertionRow_throw() && ::dbtools::canDelete( m_xCursorProperties ); + } + break; + + case FormFeature::MoveToInsertRow: + // if we are inserting we can move to the next row if the current record or control is modified + aState.Enabled = impl_isInsertionRow_throw() + ? impl_isModifiedRow_throw() || m_bActiveControlModified + : ::dbtools::canInsert( m_xCursorProperties ); + break; + + case FormFeature::ReloadForm: + { + // there must be an active connection + aState.Enabled = ::dbtools::getConnection( m_xCursor ).is(); + + // and an active command + OUString sActiveCommand; + m_xCursorProperties->getPropertyValue( PROPERTY_ACTIVECOMMAND ) >>= sActiveCommand; + aState.Enabled = aState.Enabled && !sActiveCommand.isEmpty(); + } + break; + + case FormFeature::RefreshCurrentControl: + { + Reference< XRefreshable > xControlModelRefresh( impl_getCurrentControlModel_throw(), UNO_QUERY ); + aState.Enabled = xControlModelRefresh.is(); + } + break; + + case FormFeature::SaveRecordChanges: + case FormFeature::UndoRecordChanges: + aState.Enabled = impl_isModifiedRow_throw() || m_bActiveControlModified; + break; + + case FormFeature::RemoveFilterAndSort: + if ( impl_isParseable_throw() && impl_hasFilterOrOrder_throw() ) + aState.Enabled = !impl_isInsertOnlyForm_throw(); + break; + + case FormFeature::SortAscending: + case FormFeature::SortDescending: + case FormFeature::AutoFilter: + if ( m_xController.is() && impl_isParseable_throw() ) + { + bool bIsDeleted = m_xCursor->rowDeleted(); + + if ( !bIsDeleted && !impl_isInsertOnlyForm_throw() ) + { + Reference< XPropertySet > xBoundField = impl_getCurrentBoundField_nothrow( ); + if ( xBoundField.is() ) + xBoundField->getPropertyValue( PROPERTY_SEARCHABLE ) >>= aState.Enabled; + } + } + break; + + case FormFeature::InteractiveSort: + case FormFeature::InteractiveFilter: + if ( impl_isParseable_throw() ) + aState.Enabled = !impl_isInsertOnlyForm_throw(); + break; + + case FormFeature::ToggleApplyFilter: + { + OUString sFilter; + OUString sHaving; + m_xCursorProperties->getPropertyValue( PROPERTY_FILTER ) >>= sFilter; + m_xCursorProperties->getPropertyValue( PROPERTY_HAVINGCLAUSE ) >>= sHaving; + if ( ! (sFilter.isEmpty() && sHaving.isEmpty()) ) + { + aState.State = m_xCursorProperties->getPropertyValue( PROPERTY_APPLYFILTER ); + aState.Enabled = !impl_isInsertOnlyForm_throw(); + } + else + aState.State <<= false; + } + break; + + case FormFeature::MoveAbsolute: + { + sal_Int32 nPosition = m_xCursor->getRow(); + bool bIsNew = impl_isInsertionRow_throw(); + sal_Int32 nCount = impl_getRowCount_throw(); + bool bFinalCount = impl_isRowCountFinal_throw(); + + if ( ( nPosition >= 0 ) || bIsNew ) + { + if ( bFinalCount ) + { + // special case: there are no records at all, and we + // can't insert records -> disabled + if ( !nCount && !::dbtools::canInsert( m_xCursorProperties ) ) + { + aState.Enabled = false; + } + else + { + if ( bIsNew ) + nPosition = ++nCount; + aState.State <<= nPosition; + aState.Enabled = true; + } + } + else + { + aState.State <<= nPosition; + aState.Enabled = true; + } + } + } + break; + + case FormFeature::TotalRecords: + { + bool bIsNew = impl_isInsertionRow_throw(); + sal_Int32 nCount = impl_getRowCount_throw(); + bool bFinalCount = impl_isRowCountFinal_throw(); + + if ( bIsNew ) + ++nCount; + + OUString sValue = OUString::number( nCount ); + if ( !bFinalCount ) + sValue += " *"; + + aState.State <<= sValue; + aState.Enabled = true; + } + break; + + default: + OSL_FAIL( "FormOperations::getState: unknown feature id!" ); + break; + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "forms.runtime", "FormOperations::getState" ); + } + + return aState; + } + + + sal_Bool SAL_CALL FormOperations::isEnabled( ::sal_Int16 _nFeature ) + { + MethodGuard aGuard( *this ); + + FeatureState aState( getState( _nFeature ) ); + return aState.Enabled; + } + + + namespace + { + bool lcl_needConfirmCommit( sal_Int32 _nFeature ) + { + return ( ( _nFeature == FormFeature::ReloadForm ) + || ( _nFeature == FormFeature::RemoveFilterAndSort ) + || ( _nFeature == FormFeature::ToggleApplyFilter ) + || ( _nFeature == FormFeature::SortAscending ) + || ( _nFeature == FormFeature::SortDescending ) + || ( _nFeature == FormFeature::AutoFilter ) + || ( _nFeature == FormFeature::InteractiveSort ) + || ( _nFeature == FormFeature::InteractiveFilter ) + ); + } + bool lcl_requiresArguments( sal_Int32 _nFeature ) + { + return ( _nFeature == FormFeature::MoveAbsolute ); + } + bool lcl_isExecutableFeature( sal_Int32 _nFeature ) + { + return ( _nFeature != FormFeature::TotalRecords ); + } + + template < typename TYPE > + TYPE lcl_safeGetPropertyValue_throw( const Reference< XPropertySet >& _rxProperties, const OUString& _rPropertyName, TYPE Default ) + { + TYPE value( Default ); + OSL_PRECOND( _rxProperties.is(), "FormOperations::<foo>: no cursor (already disposed?)!" ); + if ( _rxProperties.is() ) + OSL_VERIFY( _rxProperties->getPropertyValue( _rPropertyName ) >>= value ); + return value; + } + + // returns false if parent should *abort* (user pressed cancel) + bool checkConfirmation(bool &needConfirmation, bool &shouldCommit) + { + if(!needConfirmation) + return true; + // TODO: shouldn't this be done with an interaction handler? + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Question, VclButtonsType::YesNo, + ResourceManager::loadString(RID_STR_QUERY_SAVE_MODIFIED_ROW))); + xQueryBox->add_button(GetStandardText(StandardButtonType::Cancel), RET_CANCEL); + xQueryBox->set_default_response(RET_YES); + + switch (xQueryBox->run()) + { + case RET_NO: + shouldCommit = false; + [[fallthrough]]; // don't ask again! + case RET_YES: + needConfirmation = false; + return true; + case RET_CANCEL: + return false; + } + return true; + } + + bool commit1Form(const Reference< XFormController >& xCntrl, bool &needConfirmation, bool &shouldCommit) + { + Reference< XFormOperations > xFrmOps(xCntrl->getFormOperations()); + if (!xFrmOps->commitCurrentControl()) + return false; + + if(xFrmOps->isModifiedRow()) + { + if(!checkConfirmation(needConfirmation, shouldCommit)) + return false; + sal_Bool bTmp; + if (shouldCommit && !xFrmOps->commitCurrentRecord(bTmp)) + return false; + } + return true; + } + + bool commitFormAndSubforms(const Reference< XFormController >& xCntrl, bool needConfirmation) + { + bool shouldCommit(true); + assert(xCntrl.is()); + if(xCntrl.is()) + { + const sal_Int32 cnt = xCntrl->getCount(); + for(int i=0; i < cnt; ++i) + { + Reference< XFormController > xSubForm(xCntrl->getByIndex(i), UNO_QUERY); + assert(xSubForm.is()); + if (xSubForm.is()) + { + if (!commit1Form(xSubForm, needConfirmation, shouldCommit)) + return false; + } + } + } + + return commit1Form(xCntrl, needConfirmation, shouldCommit); + } + + bool commit1Form(const Reference< XForm >& xFrm, bool &needConfirmation, bool &shouldCommit) + { + Reference< XPropertySet > xProps(xFrm, UNO_QUERY_THROW); + // nothing to do if the record is not modified + if(!lcl_safeGetPropertyValue_throw( xProps, PROPERTY_ISMODIFIED, false )) + return true; + + if(!checkConfirmation(needConfirmation, shouldCommit)) + return false; + if(shouldCommit) + { + Reference< XResultSetUpdate > xUpd(xFrm, UNO_QUERY_THROW); + // insert respectively update the row + if ( lcl_safeGetPropertyValue_throw( xProps, PROPERTY_ISNEW, false ) ) + xUpd->insertRow(); + else + xUpd->updateRow(); + } + return true; + } + + bool commitFormAndSubforms(const Reference< XForm >& xFrm, bool needConfirmation) + { + // No control... do what we can with the models + bool shouldCommit(true); + Reference< XIndexAccess > xFormComps(xFrm, UNO_QUERY_THROW); + + const sal_Int32 cnt = xFormComps->getCount(); + for(int i=0; i < cnt; ++i) + { + Reference< XForm > xSubForm(xFormComps->getByIndex(i), UNO_QUERY); + if(xSubForm.is()) + { + if(!commit1Form(xSubForm, needConfirmation, shouldCommit)) + return false; + } + } + + return commit1Form(xFrm, needConfirmation, shouldCommit); + } + } + + void SAL_CALL FormOperations::execute( ::sal_Int16 _nFeature ) + { + SolarMutexGuard aSolarGuard; + MethodGuard aGuard( *this ); + + if ( ( _nFeature != FormFeature::DeleteRecord ) && ( _nFeature != FormFeature::UndoRecordChanges ) ) + { + + + if(m_xController.is()) + { + if(!commitFormAndSubforms(m_xController, lcl_needConfirmCommit( _nFeature ))) + return; + } + else if(m_xCursor.is()) + { + Reference< XForm > xForm(m_xCursor, UNO_QUERY); + assert(xForm.is()); + if(!commitFormAndSubforms(xForm, lcl_needConfirmCommit( _nFeature ))) + return; + } + else + { + SAL_WARN( "forms.runtime", "No cursor, but trying to execute form operation " << _nFeature ); + } + } + + try + { + switch ( _nFeature ) + { + case FormFeature::MoveToFirst: + m_xCursor->first(); + break; + + case FormFeature::MoveToNext: + impl_moveRight_throw( ); + break; + + case FormFeature::MoveToPrevious: + impl_moveLeft_throw( ); + break; + + case FormFeature::MoveToLast: + { +/* + // TODO: re-implement this... + // run in an own thread if... + // ... the data source is thread safe... + sal_Bool bAllowOwnThread = sal_False; + if ( ::comphelper::hasProperty( PROPERTY_THREADSAFE, m_xCursorProperties ) ) + m_xCursorProperties->getPropertyValue( PROPERTY_THREADSAFE ) >>= bAllowOwnThread; + + // ... the record count is unknown + sal_Bool bNeedOwnThread sal_False; + if ( ::comphelper::hasProperty( PROPERTY_ROWCOUNTFINAL, m_xCursorProperties ) ) + m_xCursorProperties->getPropertyValue( PROPERTY_ROWCOUNTFINAL ) >>= bNeedOwnThread; + + if ( bNeedOwnThread && bAllowOwnThread ) + ; + else +*/ + m_xCursor->last(); + } + break; + + case FormFeature::ReloadForm: + if ( m_xLoadableForm.is() ) + { + weld::WaitObject aWO(Application::GetFrameWeld(GetDialogParent())); + m_xLoadableForm->reload(); + + // refresh all controls in the form (and sub forms) which can be refreshed + // #i90914# + ::comphelper::IndexAccessIterator aIter( m_xLoadableForm ); + Reference< XInterface > xElement( aIter.Next() ); + while ( xElement.is() ) + { + Reference< XRefreshable > xRefresh( xElement, UNO_QUERY ); + if ( xRefresh.is() ) + xRefresh->refresh(); + xElement = aIter.Next(); + } + } + break; + + case FormFeature::RefreshCurrentControl: + { + Reference< XRefreshable > xControlModelRefresh( impl_getCurrentControlModel_throw(), UNO_QUERY ); + OSL_ENSURE( xControlModelRefresh.is(), "FormOperations::execute: how did you reach this?" ); + if ( xControlModelRefresh.is() ) + xControlModelRefresh->refresh(); + } + break; + + case FormFeature::DeleteRecord: + { + sal_uInt32 nCount = impl_getRowCount_throw(); + + // next position + bool bLeft = m_xCursor->isLast() && ( nCount > 1 ); + bool bRight= !m_xCursor->isLast(); + bool bSuccess = false; + try + { + // ask for confirmation + Reference< XConfirmDeleteListener > xConfirmDelete( m_xController, UNO_QUERY ); + + if ( xConfirmDelete.is() ) + { + RowChangeEvent aEvent; + aEvent.Source.set( m_xCursor, UNO_QUERY ); + aEvent.Action = RowChangeAction::DELETE; + aEvent.Rows = 1; + bSuccess = xConfirmDelete->confirmDelete( aEvent ); + } + + // delete it + if ( bSuccess ) + m_xUpdateCursor->deleteRow(); + } + catch( const Exception& ) + { + bSuccess = false; + } + + if ( bSuccess ) + { + if ( bLeft || bRight ) + m_xCursor->relative( bRight ? 1 : -1 ); + else + { + bool bCanInsert = ::dbtools::canInsert( m_xCursorProperties ); + // is it possible to insert another record? + if ( bCanInsert ) + m_xUpdateCursor->moveToInsertRow(); + else + // move record to update status + m_xCursor->first(); + } + } + } + break; + + case FormFeature::SaveRecordChanges: + case FormFeature::UndoRecordChanges: + { + bool bInserting = impl_isInsertionRow_throw(); + + if ( FormFeature::UndoRecordChanges == _nFeature ) + { + if ( !bInserting ) + m_xUpdateCursor->cancelRowUpdates(); + + // reset all controls for this form + impl_resetAllControls_nothrow( ); + + if ( bInserting ) // back to insertion mode for this form + m_xUpdateCursor->moveToInsertRow(); + } + else + { + if ( bInserting ) + { + m_xUpdateCursor->insertRow(); + m_xCursor->last(); + } + else + m_xUpdateCursor->updateRow(); + } + } + break; + + case FormFeature::MoveToInsertRow: + // move to the last row before moving to the insert row + m_xCursor->last(); + m_xUpdateCursor->moveToInsertRow(); + break; + + case FormFeature::RemoveFilterAndSort: + { + // simultaneously reset Filter and Order property + Reference< XMultiPropertySet > xProperties( m_xCursorProperties, UNO_QUERY ); + OSL_ENSURE( xProperties.is(), "FormOperations::execute: no multi property access!" ); + if ( xProperties.is() ) + { + Sequence< OUString > aNames{ PROPERTY_FILTER, PROPERTY_HAVINGCLAUSE, + PROPERTY_SORT }; + + Sequence< Any> aValues{ Any(OUString()), Any(OUString()), Any(OUString()) }; + + weld::WaitObject aWO(Application::GetFrameWeld(GetDialogParent())); + xProperties->setPropertyValues( aNames, aValues ); + + if ( m_xLoadableForm.is() ) + m_xLoadableForm->reload(); + } + } + break; + + case FormFeature::ToggleApplyFilter: + if ( impl_commitCurrentControl_throw() && impl_commitCurrentRecord_throw() ) + { + // simply toggle the value + bool bApplied = false; + m_xCursorProperties->getPropertyValue( PROPERTY_APPLYFILTER ) >>= bApplied; + m_xCursorProperties->setPropertyValue( PROPERTY_APPLYFILTER, Any( !bApplied ) ); + + // and reload + weld::WaitObject aWO(Application::GetFrameWeld(GetDialogParent())); + m_xLoadableForm->reload(); + } + break; + + case FormFeature::SortAscending: + impl_executeAutoSort_throw( true ); + break; + + case FormFeature::SortDescending: + impl_executeAutoSort_throw( false ); + break; + + case FormFeature::AutoFilter: + impl_executeAutoFilter_throw(); + break; + + case FormFeature::InteractiveSort: + impl_executeFilterOrSort_throw( false ); + break; + + case FormFeature::InteractiveFilter: + impl_executeFilterOrSort_throw( true ); + break; + + default: + { + TranslateId pErrorResourceId = RID_STR_FEATURE_UNKNOWN; + if ( lcl_requiresArguments( _nFeature ) ) + pErrorResourceId = RID_STR_FEATURE_REQUIRES_PARAMETERS; + else if ( !lcl_isExecutableFeature( _nFeature ) ) + pErrorResourceId = RID_STR_FEATURE_NOT_EXECUTABLE; + throw IllegalArgumentException( ResourceManager::loadString(pErrorResourceId), *this, 1 ); + } + } // switch + } + catch( const RuntimeException& ) { throw; } + catch( const SQLException& ) { throw; } + catch( const Exception& ) + { + throw WrappedTargetException( OUString(), *this, ::cppu::getCaughtException() ); + } + + impl_invalidateAllSupportedFeatures_nothrow( aGuard ); + } + + + void SAL_CALL FormOperations::executeWithArguments( ::sal_Int16 _nFeature, const Sequence< NamedValue >& _rArguments ) + { + if ( !lcl_requiresArguments( _nFeature ) ) + { + execute( _nFeature ); + return; + } + + SolarMutexGuard aSolarGuard; + MethodGuard aGuard( *this ); + + // at the moment we have only one feature which supports execution parameters + if ( !lcl_isExecutableFeature( _nFeature ) ) + throw IllegalArgumentException( ResourceManager::loadString(RID_STR_FEATURE_NOT_EXECUTABLE), *this, 1 ); + + switch ( _nFeature ) + { + case FormFeature::MoveAbsolute: + { + sal_Int32 nPosition = -1; + + ::comphelper::NamedValueCollection aArguments( _rArguments ); + aArguments.get_ensureType( "Position", nPosition ); + + if ( nPosition < 1 ) + nPosition = 1; + + try + { + // commit before doing anything else + if ( m_xController.is() && !impl_commitCurrentControl_throw() ) + return; + if ( !impl_commitCurrentRecord_throw() ) + return; + + sal_Int32 nCount = impl_getRowCount_throw(); + bool bFinalCount = impl_isRowCountFinal_throw(); + + if ( bFinalCount && ( nPosition > nCount ) ) + nPosition = nCount; + + m_xCursor->absolute( nPosition ); + } + catch( const RuntimeException& ) { throw; } + catch( const SQLException& ) { throw; } + catch( const Exception& ) + { + throw WrappedTargetException( OUString(), *this, ::cppu::getCaughtException() ); + } + } + break; + default: + throw IllegalArgumentException( ResourceManager::loadString(RID_STR_FEATURE_UNKNOWN), *this, 1 ); + } // switch + } + + + sal_Bool SAL_CALL FormOperations::commitCurrentRecord( sal_Bool& _out_rRecordInserted ) + { + MethodGuard aGuard( *this ); + _out_rRecordInserted = false; + + return impl_commitCurrentRecord_throw( &_out_rRecordInserted ); + } + + + bool FormOperations::impl_commitCurrentRecord_throw( sal_Bool* _pRecordInserted ) const + { +#ifdef DBG_UTIL + DBG_ASSERT( m_nMethodNestingLevel, "FormOperations::impl_commitCurrentRecord_throw: to be called within a MethodGuard'ed section only!" ); +#endif + + if ( !impl_hasCursor_nothrow() ) + return false; + + // nothing to do if the record is not modified + bool bResult = !impl_isModifiedRow_throw(); + if ( !bResult ) + { + // insert respectively update the row + if ( impl_isInsertionRow_throw() ) + { + m_xUpdateCursor->insertRow(); + if ( _pRecordInserted ) + *_pRecordInserted = true; + } + else + m_xUpdateCursor->updateRow(); + bResult = true; + } + return bResult; + } + + + sal_Bool SAL_CALL FormOperations::commitCurrentControl() + { + MethodGuard aGuard( *this ); + return impl_commitCurrentControl_throw(); + } + + + bool FormOperations::impl_commitCurrentControl_throw() const + { +#ifdef DBG_UTIL + DBG_ASSERT( m_nMethodNestingLevel, "FormOperations::impl_commitCurrentControl_throw: to be called within a MethodGuard'ed section only!" ); +#endif + OSL_PRECOND( m_xController.is(), "FormOperations::commitCurrentControl: no controller!" ); + if ( !m_xController.is() ) + return false; + + bool bSuccess = false; + try + { + Reference< XControl > xCurrentControl( m_xController->getCurrentControl() ); + + // check whether the control is locked + Reference< XBoundControl > xCheckLock( xCurrentControl, UNO_QUERY ); + bool bControlIsLocked = xCheckLock.is() && xCheckLock->getLock(); + + // commit if necessary + bSuccess = true; + if ( xCurrentControl.is() && !bControlIsLocked ) + { + // both the control and its model can be committable, so try both + Reference< XBoundComponent > xBound( xCurrentControl, UNO_QUERY ); + if ( !xBound.is() ) + xBound.set(xCurrentControl->getModel(), css::uno::UNO_QUERY); + // and now really commit + if ( xBound.is() ) + bSuccess = xBound->commit(); + } + + } + catch( const RuntimeException& ) { throw; } + catch( const SQLException& ) { throw; } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("forms.runtime"); + bSuccess = false; + } + + return bSuccess; + } + + + sal_Bool SAL_CALL FormOperations::isInsertionRow() + { + bool bIs = false; + try + { + bIs = impl_isInsertionRow_throw(); + } + catch( const RuntimeException& ) { throw; } + catch( const Exception& ) + { + throw WrappedTargetException( OUString(), *this, ::cppu::getCaughtException() ); + } + return bIs; + } + + + sal_Bool SAL_CALL FormOperations::isModifiedRow() + { + bool bIs = false; + try + { + bIs = impl_isModifiedRow_throw(); + } + catch( const RuntimeException& ) { throw; } + catch( const Exception& ) + { + throw WrappedTargetException( OUString(), *this, ::cppu::getCaughtException() ); + } + return bIs; + } + + + void SAL_CALL FormOperations::cursorMoved( const EventObject& /*_Event*/ ) + { + MethodGuard aGuard( *this ); + m_bActiveControlModified = false; + + impl_invalidateAllSupportedFeatures_nothrow( aGuard ); + } + + + void SAL_CALL FormOperations::rowChanged( const EventObject& /*_Event*/ ) + { + // not interested in + } + + + void SAL_CALL FormOperations::rowSetChanged( const EventObject& /*_Event*/ ) + { + // not interested in + } + + + void SAL_CALL FormOperations::modified( const EventObject& /*_Source*/ ) + { + MethodGuard aGuard( *this ); + + OSL_ENSURE( m_xCursor.is(), "FormOperations::modified: already disposed!" ); + if ( !m_bActiveControlModified ) + { + m_bActiveControlModified = true; + impl_invalidateModifyDependentFeatures_nothrow( aGuard ); + } + } + + + void SAL_CALL FormOperations::propertyChange( const PropertyChangeEvent& _rEvent ) + { + MethodGuard aGuard( *this ); + + if ( m_xCursor.is() && ( m_xCursor == _rEvent.Source ) ) + { + if ( ( _rEvent.PropertyName == PROPERTY_ISMODIFIED ) + || ( _rEvent.PropertyName == PROPERTY_ISNEW ) + ) + { + bool bIs = false; + if ( ( _rEvent.NewValue >>= bIs ) && !bIs ) + m_bActiveControlModified = false; + } + impl_invalidateAllSupportedFeatures_nothrow( aGuard ); + } + + if ( !(m_xParser.is() && ( m_xCursor == _rEvent.Source )) ) + return; + + try + { + OUString sNewValue; + _rEvent.NewValue >>= sNewValue; + if ( _rEvent.PropertyName == PROPERTY_ACTIVECOMMAND ) + { + m_xParser->setElementaryQuery( sNewValue ); + } + else if ( _rEvent.PropertyName == PROPERTY_FILTER ) + { + if ( m_xParser->getFilter() != sNewValue ) + m_xParser->setFilter( sNewValue ); + } + else if ( _rEvent.PropertyName == PROPERTY_HAVINGCLAUSE ) + { + if ( m_xParser->getHavingClause() != sNewValue ) + m_xParser->setHavingClause( sNewValue ); + } + else if ( _rEvent.PropertyName == PROPERTY_SORT ) + { + _rEvent.NewValue >>= sNewValue; + if ( m_xParser->getOrder() != sNewValue ) + m_xParser->setOrder( sNewValue ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "forms.runtime", "FormOperations::propertyChange: caught an exception while updating the parser!" ); + } + impl_invalidateAllSupportedFeatures_nothrow( aGuard ); + } + + + void SAL_CALL FormOperations::disposing( const EventObject& /*_Source*/ ) + { + // TODO: should we react on this? Or is this the responsibility of our owner to dispose us? + } + + + void SAL_CALL FormOperations::disposing() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + impl_disposeParser_nothrow(); + + try + { + // revoke various listeners + if ( m_xCursor.is() ) + m_xCursor->removeRowSetListener( this ); + + if ( m_xCursorProperties.is() ) + { + m_xCursorProperties->removePropertyChangeListener( PROPERTY_ISMODIFIED,this ); + m_xCursorProperties->removePropertyChangeListener( PROPERTY_ISNEW, this ); + } + + if ( m_xController.is() ) + m_xController->removeModifyListener( this ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("forms.runtime"); + } + + m_xController.clear(); + m_xCursor.clear(); + m_xUpdateCursor.clear(); + m_xCursorProperties.clear(); + m_xLoadableForm.clear(); + m_xFeatureInvalidation.clear(); + + m_bActiveControlModified = true; + } + + + void FormOperations::impl_checkDisposed_throw() const + { + if ( !m_xCursor.is() ) + throw DisposedException( OUString(), *const_cast< FormOperations* >( this ) ); + } + + + void FormOperations::impl_initFromController_throw() + { + OSL_PRECOND( m_xController.is(), "FormOperations::impl_initFromController_throw: invalid controller!" ); + m_xCursor.set(m_xController->getModel(), css::uno::UNO_QUERY); + if ( !m_xCursor.is() ) + throw IllegalArgumentException( OUString(), *this, 0 ); + + impl_initFromForm_throw(); + + if ( m_xController.is() ) + m_xController->addModifyListener( this ); + } + + + void FormOperations::impl_initFromForm_throw() + { + OSL_PRECOND( m_xCursor.is(), "FormOperations::impl_initFromForm_throw: invalid form!" ); + m_xCursorProperties.set(m_xCursor, css::uno::UNO_QUERY); + m_xUpdateCursor.set(m_xCursor, css::uno::UNO_QUERY); + m_xLoadableForm.set(m_xCursor, css::uno::UNO_QUERY); + + if ( !m_xCursor.is() || !m_xCursorProperties.is() || !m_xLoadableForm.is() ) + throw IllegalArgumentException( OUString(), *this, 0 ); + + m_xCursor->addRowSetListener( this ); + m_xCursorProperties->addPropertyChangeListener( PROPERTY_ISMODIFIED,this ); + m_xCursorProperties->addPropertyChangeListener( PROPERTY_ISNEW, this ); + } + + + void FormOperations::createWithFormController( const Reference< XFormController >& _rxController ) + { + m_xController = _rxController; + if ( !m_xController.is() ) + throw IllegalArgumentException( OUString(), *this, 0 ); + + impl_initFromController_throw(); + + m_bConstructed = true; + } + + + void FormOperations::createWithForm( const Reference< XForm >& _rxForm ) + { + m_xCursor.set(_rxForm, css::uno::UNO_QUERY); + if ( !m_xCursor.is() ) + throw IllegalArgumentException( OUString(), *this, 0 ); + + impl_initFromForm_throw(); + + m_bConstructed = true; + } + + + void FormOperations::impl_invalidateAllSupportedFeatures_nothrow( MethodGuard& _rClearForCallback ) const + { + if ( !m_xFeatureInvalidation.is() ) + // nobody's interested in ... + return; + + Reference< XFeatureInvalidation > xInvalidation = m_xFeatureInvalidation; + _rClearForCallback.clear(); + xInvalidation->invalidateAllFeatures(); + } + + + void FormOperations::impl_invalidateModifyDependentFeatures_nothrow( MethodGuard& _rClearForCallback ) const + { + if ( !m_xFeatureInvalidation.is() ) + // nobody's interested in ... + return; + + static Sequence< sal_Int16 > const s_aModifyDependentFeatures + { + FormFeature::MoveToNext, + FormFeature::MoveToInsertRow, + FormFeature::SaveRecordChanges, + FormFeature::UndoRecordChanges + }; + + Reference< XFeatureInvalidation > xInvalidation = m_xFeatureInvalidation; + _rClearForCallback.clear(); + + xInvalidation->invalidateFeatures( s_aModifyDependentFeatures ); + } + + + void FormOperations::impl_ensureInitializedParser_nothrow() + { + OSL_PRECOND( m_xCursorProperties.is(), "FormOperations::impl_ensureInitializedParser_nothrow: we're disposed!" ); + if ( m_bInitializedParser ) + return; + + try + { + bool bUseEscapeProcessing = false; + m_xCursorProperties->getPropertyValue( PROPERTY_ESCAPE_PROCESSING ) >>= bUseEscapeProcessing; + if ( bUseEscapeProcessing ) + { + Reference< XMultiServiceFactory > xFactory( ::dbtools::getConnection( m_xCursor ), UNO_QUERY ); + if ( xFactory.is() ) + { + m_xParser.set( xFactory->createInstance("com.sun.star.sdb.SingleSelectQueryComposer"), UNO_QUERY ); + OSL_ENSURE( m_xParser.is(), "FormOperations::impl_ensureInitializedParser_nothrow: factory did not create a parser for us!" ); + } + } + + if ( m_xParser.is() ) + { + if ( m_xLoadableForm.is() && m_xLoadableForm->isLoaded() ) + { + OUString sStatement; + OUString sFilter; + OUString sHaving; + OUString sSort; + + m_xCursorProperties->getPropertyValue( PROPERTY_ACTIVECOMMAND ) >>= sStatement; + m_xCursorProperties->getPropertyValue( PROPERTY_FILTER ) >>= sFilter; + m_xCursorProperties->getPropertyValue( PROPERTY_HAVINGCLAUSE ) >>= sHaving; + m_xCursorProperties->getPropertyValue( PROPERTY_SORT ) >>= sSort; + + m_xParser->setElementaryQuery( sStatement ); + m_xParser->setFilter ( sFilter ); + m_xParser->setHavingClause ( sHaving ); + m_xParser->setOrder ( sSort ); + } + + // start listening at the order/sort properties at the form, so + // we can keep our parser in sync + m_xCursorProperties->addPropertyChangeListener( PROPERTY_ACTIVECOMMAND, this ); + m_xCursorProperties->addPropertyChangeListener( PROPERTY_FILTER, this ); + m_xCursorProperties->addPropertyChangeListener( PROPERTY_HAVINGCLAUSE, this ); + m_xCursorProperties->addPropertyChangeListener( PROPERTY_SORT, this ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "forms.runtime", "FormOperations::impl_ensureInitializedParser_nothrow" ); + } + + m_bInitializedParser = true; + } + + + void FormOperations::impl_disposeParser_nothrow() + { + try + { + // if we have a parser (and a cursor), then we're listening at the cursor's + // properties to keep the parser in sync with the cursor + if ( m_xParser.is() && m_xCursorProperties.is() ) + { + m_xCursorProperties->removePropertyChangeListener( PROPERTY_FILTER, this ); + m_xCursorProperties->removePropertyChangeListener( PROPERTY_HAVINGCLAUSE, this ); + m_xCursorProperties->removePropertyChangeListener( PROPERTY_ACTIVECOMMAND, this ); + m_xCursorProperties->removePropertyChangeListener( PROPERTY_SORT, this ); + } + + Reference< XComponent > xParserComp( m_xParser, UNO_QUERY ); + if ( xParserComp.is() ) + xParserComp->dispose(); + m_xParser.clear(); + + m_bInitializedParser = false; + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "forms.runtime", "FormOperations::impl_disposeParser_nothrow" ); + } + } + + + bool FormOperations::impl_canMoveLeft_throw( ) const + { + if ( !impl_hasCursor_nothrow() ) + return false; + + return impl_getRowCount_throw() && ( !m_xCursor->isFirst() || impl_isInsertionRow_throw() ); + } + + + bool FormOperations::impl_canMoveRight_throw( ) const + { + if ( !impl_hasCursor_nothrow() ) + return false; + + bool bIsNew = impl_isInsertionRow_throw(); + + if ( impl_getRowCount_throw() && !m_xCursor->isLast() && !bIsNew ) + return true; + + if ( ::dbtools::canInsert( m_xCursorProperties ) ) + if ( !bIsNew || impl_isModifiedRow_throw() ) + return true; + + if ( bIsNew && m_bActiveControlModified ) + return true; + + return false; + } + + + bool FormOperations::impl_isInsertionRow_throw() const + { + return lcl_safeGetPropertyValue_throw( m_xCursorProperties, PROPERTY_ISNEW, false ); + } + + + sal_Int32 FormOperations::impl_getRowCount_throw() const + { + return lcl_safeGetPropertyValue_throw( m_xCursorProperties, PROPERTY_ROWCOUNT, sal_Int32(0) ); + } + + bool FormOperations::impl_isRowCountFinal_throw() const + { + return lcl_safeGetPropertyValue_throw( m_xCursorProperties, PROPERTY_ROWCOUNTFINAL, false ); + } + + + bool FormOperations::impl_isModifiedRow_throw() const + { + return lcl_safeGetPropertyValue_throw( m_xCursorProperties, PROPERTY_ISMODIFIED, false ); + } + + + bool FormOperations::impl_isParseable_throw() const + { + const_cast< FormOperations* >( this )->impl_ensureInitializedParser_nothrow(); + return m_xParser.is() && !m_xParser->getQuery().isEmpty(); + } + + + bool FormOperations::impl_hasFilterOrOrder_throw() const + { + return impl_isParseable_throw() && ( !m_xParser->getFilter().isEmpty() || + !m_xParser->getHavingClause().isEmpty() || + !m_xParser->getOrder().isEmpty() ); + } + + + bool FormOperations::impl_isInsertOnlyForm_throw() const + { + return lcl_safeGetPropertyValue_throw( m_xCursorProperties, PROPERTY_INSERTONLY, true ); + } + + + Reference< XControlModel > FormOperations::impl_getCurrentControlModel_throw() const + { + Reference< XControl > xControl( m_xController->getCurrentControl() ); + + // special handling for grid controls + Reference< XGrid > xGrid( xControl, UNO_QUERY ); + Reference< XControlModel > xControlModel; + + if ( xGrid.is() ) + { + Reference< XIndexAccess > xColumns( xControl->getModel(), UNO_QUERY_THROW ); + sal_Int32 nCurrentPos = impl_gridView2ModelPos_nothrow( xColumns, xGrid->getCurrentColumnPosition() ); + + if ( nCurrentPos != -1 ) + xColumns->getByIndex( nCurrentPos ) >>= xControlModel; + } + else if ( xControl.is() ) + { + xControlModel = xControl->getModel(); + } + return xControlModel; + } + + + Reference< XPropertySet > FormOperations::impl_getCurrentBoundField_nothrow( ) const + { + OSL_PRECOND( m_xController.is(), "FormOperations::impl_getCurrentBoundField_nothrow: no controller -> no control!" ); + if ( !m_xController.is() ) + return nullptr; + + Reference< XPropertySet > xField; + try + { + Reference< XPropertySet > xControlModel( impl_getCurrentControlModel_throw(), UNO_QUERY ); + + if ( xControlModel.is() && ::comphelper::hasProperty( PROPERTY_BOUNDFIELD, xControlModel ) ) + xControlModel->getPropertyValue( PROPERTY_BOUNDFIELD ) >>= xField; + + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("forms.runtime"); + } + + return xField; + } + + + sal_Int32 FormOperations::impl_gridView2ModelPos_nothrow( const Reference< XIndexAccess >& _rxColumns, sal_Int16 _nViewPos ) + { + OSL_PRECOND( _rxColumns.is(), "FormOperations::impl_gridView2ModelPos_nothrow: invalid columns container!" ); + try + { + // loop through all columns + sal_Int32 col = 0; + Reference< XPropertySet > xCol; + bool bHidden( false ); + for ( col = 0; col < _rxColumns->getCount(); ++col ) + { + _rxColumns->getByIndex( col ) >>= xCol; + OSL_VERIFY( xCol->getPropertyValue( PROPERTY_HIDDEN ) >>= bHidden ); + if ( bHidden ) + continue; + + // for every visible col : if nViewPos is greater zero, decrement it, else we + // have found the model position + if ( !_nViewPos ) + break; + else + --_nViewPos; + } + if ( col < _rxColumns->getCount() ) + return col; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("forms.runtime"); + } + return -1; + } + + + void FormOperations::impl_moveLeft_throw( ) const + { + OSL_PRECOND( impl_hasCursor_nothrow(), "FormOperations::impl_moveLeft_throw: no cursor!" ); + if ( !impl_hasCursor_nothrow() ) + return; + + sal_Bool bRecordInserted = false; + bool bSuccess = impl_commitCurrentRecord_throw( &bRecordInserted ); + + if ( !bSuccess ) + return; + + if ( bRecordInserted ) + { + // retrieve the bookmark of the new record and move to the record preceding this bookmark + Reference< XRowLocate > xLocate( m_xCursor, UNO_QUERY ); + OSL_ENSURE( xLocate.is(), "FormOperations::impl_moveLeft_throw: no XRowLocate!" ); + if ( xLocate.is() ) + xLocate->moveRelativeToBookmark( xLocate->getBookmark(), -1 ); + } + else + { + if ( impl_isInsertionRow_throw() ) + { + // we assume that the inserted record is now the last record in the + // result set + m_xCursor->last(); + } + else + m_xCursor->previous(); + } + } + + + void FormOperations::impl_moveRight_throw( ) const + { + OSL_PRECOND( impl_hasCursor_nothrow(), "FormOperations::impl_moveRight_throw: no cursor!" ); + if ( !impl_hasCursor_nothrow() ) + return; + + sal_Bool bRecordInserted = false; + bool bSuccess = impl_commitCurrentRecord_throw( &bRecordInserted ); + + if ( !bSuccess ) + return; + + if ( bRecordInserted ) + { + // go to insert row + m_xUpdateCursor->moveToInsertRow(); + } + else + { + if ( m_xCursor->isLast() ) + m_xUpdateCursor->moveToInsertRow(); + else + (void)m_xCursor->next(); + } + } + + + void FormOperations::impl_resetAllControls_nothrow() const + { + Reference< XIndexAccess > xContainer( m_xCursor, UNO_QUERY ); + if ( !xContainer.is() ) + return; + + try + { + Reference< XReset > xReset; + sal_Int32 nCount( xContainer->getCount() ); + for ( sal_Int32 i = 0; i < nCount; ++i ) + { + if ( xContainer->getByIndex( i ) >>= xReset ) + { + // no resets on sub forms + Reference< XForm > xAsForm( xReset, UNO_QUERY ); + if ( !xAsForm.is() ) + xReset->reset(); + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("forms.runtime"); + } + } + + + void FormOperations::impl_executeAutoSort_throw( bool _bUp ) const + { + OSL_PRECOND( m_xController.is(), "FormOperations::impl_executeAutoSort_throw: need a controller for this!" ); + OSL_PRECOND( impl_hasCursor_nothrow(), "FormOperations::impl_executeAutoSort_throw: need a cursor for this!" ); + OSL_PRECOND( impl_isParseable_throw(), "FormOperations::impl_executeAutoSort_throw: need a parseable statement for this!" ); + if ( !m_xController.is() || !impl_hasCursor_nothrow() || !impl_isParseable_throw() ) + return; + + try + { + Reference< XControl > xControl = m_xController->getCurrentControl(); + if ( !xControl.is() || !impl_commitCurrentControl_throw() || !impl_commitCurrentRecord_throw() ) + return; + + Reference< XPropertySet > xBoundField( impl_getCurrentBoundField_nothrow() ); + if ( !xBoundField.is() ) + return; + + OUString sOriginalSort; + m_xCursorProperties->getPropertyValue( PROPERTY_SORT ) >>= sOriginalSort; + + // automatic sort by field is expected to always resets the previous sort order + m_xParser->setOrder( OUString() ); + + impl_appendOrderByColumn_throw aAction(this, xBoundField, _bUp); + impl_doActionInSQLContext_throw(std::move(aAction), RID_STR_COULD_NOT_SET_ORDER ); + + weld::WaitObject aWO(Application::GetFrameWeld(GetDialogParent())); + try + { + m_xCursorProperties->setPropertyValue( PROPERTY_SORT, Any( m_xParser->getOrder() ) ); + m_xLoadableForm->reload(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "forms.runtime", "FormOperations::impl_executeAutoSort_throw: caught an exception while setting the parser properties!" ); + } + + + if ( !m_xLoadableForm->isLoaded() ) + { // something went wrong -> restore the original state + try + { + m_xParser->setOrder( sOriginalSort ); + m_xCursorProperties->setPropertyValue( PROPERTY_SORT, Any( m_xParser->getOrder() ) ); + m_xLoadableForm->reload(); + } + catch( const Exception& ) + { + OSL_FAIL( "FormOperations::impl_executeAutoSort_throw: could not reset the form to its original state!" ); + } + + } + } + catch( const RuntimeException& ) { throw; } + catch( const SQLException& ) { throw; } + catch( const Exception& ) + { + throw WrappedTargetException( OUString(), *const_cast< FormOperations* >( this ), ::cppu::getCaughtException() ); + } + } + + + void FormOperations::impl_executeAutoFilter_throw( ) const + { + OSL_PRECOND( m_xController.is(), "FormOperations::impl_executeAutoFilter_throw: need a controller for this!" ); + OSL_PRECOND( impl_hasCursor_nothrow(), "FormOperations::impl_executeAutoFilter_throw: need a cursor for this!" ); + OSL_PRECOND( impl_isParseable_throw(), "FormOperations::impl_executeAutoFilter_throw: need a parseable statement for this!" ); + if ( !m_xController.is() || !impl_hasCursor_nothrow() || !impl_isParseable_throw() ) + return; + + try + { + Reference< XControl > xControl = m_xController->getCurrentControl(); + if ( !xControl.is() || !impl_commitCurrentControl_throw() || !impl_commitCurrentRecord_throw() ) + return; + + Reference< XPropertySet > xBoundField( impl_getCurrentBoundField_nothrow() ); + if ( !xBoundField.is() ) + return; + + OUString sOriginalFilter; + OUString sOriginalHaving; + m_xCursorProperties->getPropertyValue( PROPERTY_FILTER ) >>= sOriginalFilter; + m_xCursorProperties->getPropertyValue( PROPERTY_HAVINGCLAUSE ) >>= sOriginalHaving; + bool bApplied = true; + m_xCursorProperties->getPropertyValue( PROPERTY_APPLYFILTER ) >>= bApplied; + + // if we have a filter, but it's not applied, then we have to overwrite it, else append one + if ( !bApplied ) + { + m_xParser->setFilter( OUString() ); + m_xParser->setHavingClause( OUString() ); + } + + impl_appendFilterByColumn_throw aAction(this, m_xParser, xBoundField); + impl_doActionInSQLContext_throw( std::move(aAction), RID_STR_COULD_NOT_SET_FILTER ); + + weld::WaitObject aWO(Application::GetFrameWeld(GetDialogParent())); + try + { + m_xCursorProperties->setPropertyValue( PROPERTY_FILTER, Any( m_xParser->getFilter() ) ); + m_xCursorProperties->setPropertyValue( PROPERTY_HAVINGCLAUSE, Any( m_xParser->getHavingClause() ) ); + m_xCursorProperties->setPropertyValue( PROPERTY_APPLYFILTER, Any( true ) ); + + m_xLoadableForm->reload(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "forms.runtime", "FormOperations::impl_executeAutoFilter_throw: caught an exception while setting the parser properties!" ); + } + + + if ( !m_xLoadableForm->isLoaded() ) + { // something went wrong -> restore the original state + try + { + m_xParser->setFilter ( sOriginalFilter ); + m_xParser->setHavingClause( sOriginalHaving ); + m_xCursorProperties->setPropertyValue( PROPERTY_APPLYFILTER, Any( bApplied ) ); + m_xCursorProperties->setPropertyValue( PROPERTY_FILTER, Any( m_xParser->getFilter() ) ); + m_xCursorProperties->setPropertyValue( PROPERTY_HAVINGCLAUSE, Any( m_xParser->getHavingClause() ) ); + m_xLoadableForm->reload(); + } + catch( const Exception& ) + { + OSL_FAIL( "FormOperations::impl_executeAutoFilter_throw: could not reset the form to its original state!" ); + } + + } + } + catch( const RuntimeException& ) { throw; } + catch( const SQLException& ) { throw; } + catch( const Exception& ) + { + throw WrappedTargetException( OUString(), *const_cast< FormOperations* >( this ), ::cppu::getCaughtException() ); + } + } + + css::uno::Reference<css::awt::XWindow> FormOperations::GetDialogParent() const + { + css::uno::Reference<css::awt::XWindow> xDialogParent; + + //tdf#122152 extract parent for dialog + if (m_xController.is()) + { + css::uno::Reference<css::awt::XControl> xContainerControl(m_xController->getContainer(), css::uno::UNO_QUERY); + if (xContainerControl.is()) + { + css::uno::Reference<css::awt::XWindowPeer> xContainerPeer = xContainerControl->getPeer(); + xDialogParent = css::uno::Reference<css::awt::XWindow>(xContainerPeer, css::uno::UNO_QUERY); + } + } + + return xDialogParent; + } + + void FormOperations::impl_executeFilterOrSort_throw( bool _bFilter ) const + { + OSL_PRECOND( m_xController.is(), "FormOperations::impl_executeFilterOrSort_throw: need a controller for this!" ); + OSL_PRECOND( impl_hasCursor_nothrow(), "FormOperations::impl_executeFilterOrSort_throw: need a cursor for this!" ); + OSL_PRECOND( impl_isParseable_throw(), "FormOperations::impl_executeFilterOrSort_throw: need a parseable statement for this!" ); + if ( !m_xController.is() || !impl_hasCursor_nothrow() || !impl_isParseable_throw() ) + return; + + if ( !impl_commitCurrentControl_throw() || !impl_commitCurrentRecord_throw() ) + return; + try + { + css::uno::Reference<css::awt::XWindow> xDialogParent(GetDialogParent()); + + Reference< XExecutableDialog> xDialog; + if ( _bFilter ) + { + xDialog = css::sdb::FilterDialog::createWithQuery(m_xContext, m_xParser, m_xCursor, + xDialogParent); + } + else + { + xDialog = css::sdb::OrderDialog::createWithQuery(m_xContext, m_xParser, m_xCursorProperties, + xDialogParent); + } + + if ( RET_OK == xDialog->execute() ) + { + weld::WaitObject aWO(Application::GetFrameWeld(xDialogParent)); + if ( _bFilter ) + { + m_xCursorProperties->setPropertyValue( PROPERTY_FILTER, Any( m_xParser->getFilter() ) ); + m_xCursorProperties->setPropertyValue( PROPERTY_HAVINGCLAUSE, Any( m_xParser->getHavingClause() ) ); + } + else + m_xCursorProperties->setPropertyValue( PROPERTY_SORT, Any( m_xParser->getOrder() ) ); + m_xLoadableForm->reload(); + } + + } + catch( const RuntimeException& ) { throw; } + catch( const SQLException& ) { throw; } + catch( const Exception& ) + { + throw WrappedTargetException( OUString(), *const_cast< FormOperations* >( this ), ::cppu::getCaughtException() ); + } + } + + + template < typename FunctObj > + void FormOperations::impl_doActionInSQLContext_throw( FunctObj f, TranslateId pErrorResourceId ) const + { + try + { + f(); + } +#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS + catch( const SQLException& ) + { + if (!pErrorResourceId) // no information to prepend + throw; + + SQLExceptionInfo aInfo( ::cppu::getCaughtException() ); + OUString sAdditionalError( ResourceManager::loadString(pErrorResourceId) ); + aInfo.prepend( sAdditionalError ); + aInfo.doThrow(); + } +#endif + catch( const RuntimeException& ) { throw; } + catch( const Exception& ) + { + OUString sAdditionalError( ResourceManager::loadString(pErrorResourceId) ); + throw WrappedTargetException( sAdditionalError, *const_cast< FormOperations* >( this ), ::cppu::getCaughtException() ); + } + } + + +} // namespace frm + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_forms_FormOperations_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new frm::FormOperations(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/runtime/formoperations.hxx b/forms/source/runtime/formoperations.hxx new file mode 100644 index 000000000..182b0a7e0 --- /dev/null +++ b/forms/source/runtime/formoperations.hxx @@ -0,0 +1,381 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/form/runtime/XFormOperations.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/form/XForm.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/form/XLoadable.hpp> +#include <com/sun/star/sdb/XSingleSelectQueryComposer.hpp> +#include <com/sun/star/util/XModifyListener.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/sdb/SQLFilterOperator.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <connectivity/dbtools.hxx> +#include <tools/long.hxx> +#include <unotools/resmgr.hxx> + +namespace frm +{ + + + //= FormOperations + + typedef ::cppu::WeakComponentImplHelper < css::form::runtime::XFormOperations + , css::lang::XInitialization + , css::lang::XServiceInfo + , css::beans::XPropertyChangeListener + , css::util::XModifyListener + , css::sdbc::XRowSetListener + > FormOperations_Base; + + class FormOperations :public ::cppu::BaseMutex + ,public FormOperations_Base + { + public: + class MethodGuard; + + private: + css::uno::Reference<css::uno::XComponentContext> m_xContext; + css::uno::Reference< css::form::runtime::XFormController > m_xController; + css::uno::Reference< css::sdbc::XRowSet > m_xCursor; + css::uno::Reference< css::sdbc::XResultSetUpdate > m_xUpdateCursor; + css::uno::Reference< css::beans::XPropertySet > m_xCursorProperties; + css::uno::Reference< css::form::XLoadable > m_xLoadableForm; + css::uno::Reference< css::form::runtime::XFeatureInvalidation > m_xFeatureInvalidation; + mutable css::uno::Reference< css::sdb::XSingleSelectQueryComposer > m_xParser; + + bool m_bInitializedParser; + bool m_bActiveControlModified; + bool m_bConstructed; + #ifdef DBG_UTIL + mutable tools::Long + m_nMethodNestingLevel; + #endif + + public: + explicit FormOperations( const css::uno::Reference< css::uno::XComponentContext >& _rxContext ); + + struct MethodAccess { friend class MethodGuard; private: MethodAccess() { } }; + + void enterMethod( MethodAccess ) const + { + m_aMutex.acquire(); + impl_checkDisposed_throw(); + #ifdef DBG_UTIL + ++m_nMethodNestingLevel; + #endif + } + + void leaveMethod( MethodAccess ) const + { + m_aMutex.release(); + #ifdef DBG_UTIL + --m_nMethodNestingLevel; + #endif + } + + protected: + virtual ~FormOperations() override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // XFormOperations + virtual css::uno::Reference< css::sdbc::XRowSet > SAL_CALL getCursor() override; + virtual css::uno::Reference< css::sdbc::XResultSetUpdate > SAL_CALL getUpdateCursor() override; + virtual css::uno::Reference< css::form::runtime::XFormController > SAL_CALL getController() override; + virtual css::uno::Reference< css::form::runtime::XFeatureInvalidation > SAL_CALL getFeatureInvalidation() override; + virtual void SAL_CALL setFeatureInvalidation(const css::uno::Reference< css::form::runtime::XFeatureInvalidation > & the_value) override; + virtual css::form::runtime::FeatureState SAL_CALL getState(::sal_Int16 Feature) override; + virtual sal_Bool SAL_CALL isEnabled(::sal_Int16 Feature) override; + virtual void SAL_CALL execute(::sal_Int16 Feature) override; + virtual void SAL_CALL executeWithArguments(::sal_Int16 Feature, const css::uno::Sequence< css::beans::NamedValue >& Arguments) override; + virtual sal_Bool SAL_CALL commitCurrentRecord(sal_Bool & RecordInserted) override; + virtual sal_Bool SAL_CALL commitCurrentControl() override; + virtual sal_Bool SAL_CALL isInsertionRow() override; + virtual sal_Bool SAL_CALL isModifiedRow() override; + + // XRowSetListener + virtual void SAL_CALL cursorMoved( const css::lang::EventObject& event ) override; + virtual void SAL_CALL rowChanged( const css::lang::EventObject& event ) override; + virtual void SAL_CALL rowSetChanged( const css::lang::EventObject& event ) override; + + // XModifyListener + virtual void SAL_CALL modified( const css::lang::EventObject& _rSource ) override; + + // XPropertyChangeListener + virtual void SAL_CALL propertyChange( const css::beans::PropertyChangeEvent& evt ) override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + // XComponent/OComponentHelper + virtual void SAL_CALL disposing() override; + + private: + // service constructors + void createWithFormController( const css::uno::Reference< css::form::runtime::XFormController >& _rxController ); + void createWithForm( const css::uno::Reference< css::form::XForm >& _rxForm ); + + /** checks whether the instance is already disposed, and throws an exception if so + */ + void impl_checkDisposed_throw() const; + + /** initializes the instance after m_xController has been set + @precond + m_xController is not <NULL/> + */ + void impl_initFromController_throw(); + + /** initializes the instance after m_xCursor has been set + @precond + m_xCursor is not <NULL/> + */ + void impl_initFromForm_throw(); + + /// invalidate the full palette of features which we know + void impl_invalidateAllSupportedFeatures_nothrow( MethodGuard& _rClearForCallback ) const; + + /** invalidate the features which depend on the "modified" state of the current control + of our controller + */ + void impl_invalidateModifyDependentFeatures_nothrow( MethodGuard& _rClearForCallback ) const; + + /** ensures that our parse is initialized, or at least that we attempted to do so + @precond + we're not disposed + */ + void impl_ensureInitializedParser_nothrow(); + + /// disposes our parser, if we have one + void impl_disposeParser_nothrow(); + + /** determines whether our cursor can be moved left + @precond hasCursor() + */ + bool impl_canMoveLeft_throw() const; + + /** determines whether our cursor can be moved right + @precond hasCursor() + */ + bool impl_canMoveRight_throw() const; + + /// determines whether we're positioned on the insertion row + bool impl_isInsertionRow_throw() const; + + /// retrieves the RowCount property of the form + sal_Int32 impl_getRowCount_throw() const; + + /// retrieves the RowCountFinal property of the form + bool impl_isRowCountFinal_throw() const; + + /// retrieves the IsModified property of the form + bool impl_isModifiedRow_throw() const; + + /// determines whether we can parse the query of our form + bool impl_isParseable_throw() const; + + /// determines if we have an active filter or order condition + bool impl_hasFilterOrOrder_throw() const; + + /// determines whether our form is in "insert-only" mode + bool impl_isInsertOnlyForm_throw() const; + + /** retrieves the column to which the current control of our controller is bound + @precond + m_xController.is() + */ + css::uno::Reference< css::beans::XPropertySet > + impl_getCurrentBoundField_nothrow( ) const; + + /** returns the control model of the current control + + If the current control is a grid control, then the returned model is the + model of the current <em>column</em> in the grid. + + @precond + m_xController.is() + */ + css::uno::Reference< css::awt::XControlModel > + impl_getCurrentControlModel_throw() const; + + /// determines if we have a valid cursor + bool impl_hasCursor_nothrow() const { return m_xCursorProperties.is(); } + + /** determines the model position from a grid control column's view position + + A grid control can have columns which are currently hidden, so the index of a + column in the view is not necessarily the same as its index in the model. + */ + static sal_Int32 impl_gridView2ModelPos_nothrow( const css::uno::Reference< css::container::XIndexAccess >& _rxColumns, sal_Int16 _nViewPos ); + + /** moves our cursor one position to the left, caring for different possible + cursor states. + + Before the movement is done, the current row is saved, if necessary. + + @precond + canMoveLeft() + */ + void impl_moveLeft_throw() const; + + /** moves our cursor one position to the right, caring for different possible + cursor states. + + Before the movement is done, the current row is saved, if necessary. + + @precond + canMoveRight() + */ + void impl_moveRight_throw( ) const; + + /** impl-version of commitCurrentRecord, which can be called without caring for + an output parameter, and within const-contexts + + @precond + our mutex is locked + */ + bool impl_commitCurrentRecord_throw( sal_Bool* _pRecordInserted = nullptr ) const; + + /** impl-version of commitCurrentControl, which can be called in const-contexts + + @precond + our mutex is locked + */ + bool impl_commitCurrentControl_throw() const; + + /// resets all control models in our own form + void impl_resetAllControls_nothrow() const; + + /// executes the "auto sort ascending" and "auto sort descending" features + void impl_executeAutoSort_throw( bool _bUp ) const; + + /// executes the "auto filter" feature + void impl_executeAutoFilter_throw( ) const; + + /// executes the interactive sort resp. filter feature + void impl_executeFilterOrSort_throw( bool _bFilter ) const; + + private: + /** calls a (member) function, catches SQLExceptions, extends them with additional context information, + and rethrows them + + @param f + a functionoid with no arguments to do the work + @param pErrorResourceId + the id of the resources string to use as error message + */ + template < typename FunctObj > + void impl_doActionInSQLContext_throw( FunctObj f, TranslateId pErrorResourceId ) const; + + // functionoid to call appendOrderByColumn + class impl_appendOrderByColumn_throw + { + public: + impl_appendOrderByColumn_throw(const FormOperations *pFO, + css::uno::Reference< css::beans::XPropertySet > const & xField, + bool bUp) + : m_pFO(pFO) + , m_xField(xField) + , m_bUp(bUp) + {}; + + void operator()() { m_pFO->m_xParser->appendOrderByColumn(m_xField, m_bUp); } + private: + const FormOperations *m_pFO; + css::uno::Reference< css::beans::XPropertySet > m_xField; + bool m_bUp; + }; + + // functionoid to call appendFilterByColumn + class impl_appendFilterByColumn_throw + { + public: + impl_appendFilterByColumn_throw(const FormOperations *pFO, + css::uno::Reference< css::sdb::XSingleSelectQueryComposer > const & xParser, + css::uno::Reference< css::beans::XPropertySet > const & xField) + : m_pFO(pFO) + , m_xParser(xParser) + , m_xField(xField) + {}; + + void operator()() { + if (dbtools::isAggregateColumn( m_xParser, m_xField )) + m_pFO->m_xParser->appendHavingClauseByColumn( m_xField, true, css::sdb::SQLFilterOperator::EQUAL ); + else + m_pFO->m_xParser->appendFilterByColumn( m_xField, true, css::sdb::SQLFilterOperator::EQUAL ); + } + private: + const FormOperations *m_pFO; + css::uno::Reference< css::sdb::XSingleSelectQueryComposer > m_xParser; + css::uno::Reference< css::beans::XPropertySet > m_xField; + }; + + private: + FormOperations( const FormOperations& ) = delete; + FormOperations& operator=( const FormOperations& ) = delete; + + public: + + css::uno::Reference<css::awt::XWindow> GetDialogParent() const; + + class MethodGuard + { + FormOperations& m_rOwner; + bool m_bCleared; + + public: + explicit MethodGuard( FormOperations& _rOwner ) + :m_rOwner( _rOwner ) + ,m_bCleared( false ) + { + m_rOwner.enterMethod( FormOperations::MethodAccess() ); + } + + ~MethodGuard() + { + clear(); + } + + void clear() + { + if ( !m_bCleared ) + m_rOwner.leaveMethod( FormOperations::MethodAccess() ); + m_bCleared = true; + } + }; + }; + + +} // namespace frm + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |