From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- svx/source/form/fmshimp.cxx | 3960 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3960 insertions(+) create mode 100644 svx/source/form/fmshimp.cxx (limited to 'svx/source/form/fmshimp.cxx') diff --git a/svx/source/form/fmshimp.cxx b/svx/source/form/fmshimp.cxx new file mode 100644 index 000000000..8eb83000b --- /dev/null +++ b/svx/source/form/fmshimp.cxx @@ -0,0 +1,3960 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 + +// is used for Invalidate -> maintain it as well +const sal_uInt16 DatabaseSlotMap[] = +{ + SID_FM_RECORD_FIRST, + SID_FM_RECORD_NEXT, + SID_FM_RECORD_PREV, + SID_FM_RECORD_LAST, + SID_FM_RECORD_NEW, + SID_FM_RECORD_DELETE, + SID_FM_RECORD_ABSOLUTE, + SID_FM_RECORD_TOTAL, + SID_FM_RECORD_SAVE, + SID_FM_RECORD_UNDO, + SID_FM_REMOVE_FILTER_SORT, + SID_FM_SORTUP, + SID_FM_SORTDOWN, + SID_FM_ORDERCRIT, + SID_FM_AUTOFILTER, + SID_FM_FORM_FILTERED, + SID_FM_REFRESH, + SID_FM_REFRESH_FORM_CONTROL, + SID_FM_SEARCH, + SID_FM_FILTER_START, + SID_FM_VIEW_AS_GRID, + 0 +}; + +// is used for Invalidate -> maintain it as well +// sort ascending !!!!!! +const sal_Int16 DlgSlotMap[] = // slots of the controller +{ + SID_FM_CTL_PROPERTIES, + SID_FM_PROPERTIES, + SID_FM_TAB_DIALOG, + SID_FM_ADD_FIELD, + SID_FM_SHOW_FMEXPLORER, + SID_FM_FIELDS_CONTROL, + SID_FM_SHOW_PROPERTIES, + SID_FM_PROPERTY_CONTROL, + SID_FM_FMEXPLORER_CONTROL, + SID_FM_SHOW_DATANAVIGATOR, + SID_FM_DATANAVIGATOR_CONTROL, + 0 +}; + +const sal_Int16 SelObjectSlotMap[] = // slots depending on the SelObject +{ + SID_FM_CONVERTTO_EDIT, + SID_FM_CONVERTTO_BUTTON, + SID_FM_CONVERTTO_FIXEDTEXT, + SID_FM_CONVERTTO_LISTBOX, + SID_FM_CONVERTTO_CHECKBOX, + SID_FM_CONVERTTO_RADIOBUTTON, + SID_FM_CONVERTTO_GROUPBOX, + SID_FM_CONVERTTO_COMBOBOX, + SID_FM_CONVERTTO_IMAGEBUTTON, + SID_FM_CONVERTTO_FILECONTROL, + SID_FM_CONVERTTO_DATE, + SID_FM_CONVERTTO_TIME, + SID_FM_CONVERTTO_NUMERIC, + SID_FM_CONVERTTO_CURRENCY, + SID_FM_CONVERTTO_PATTERN, + SID_FM_CONVERTTO_IMAGECONTROL, + SID_FM_CONVERTTO_FORMATTED, + SID_FM_CONVERTTO_SCROLLBAR, + SID_FM_CONVERTTO_SPINBUTTON, + SID_FM_CONVERTTO_NAVIGATIONBAR, + + SID_FM_FMEXPLORER_CONTROL, + SID_FM_DATANAVIGATOR_CONTROL, + + 0 +}; + +// the following arrays must be consistent, i.e., corresponding entries should +// be at the same relative position within their respective arrays +static const char* aConvertSlots[] = +{ + "ConvertToEdit", + "ConvertToButton", + "ConvertToFixed", + "ConvertToList", + "ConvertToCheckBox", + "ConvertToRadio", + "ConvertToGroup", + "ConvertToCombo", + "ConvertToImageBtn", + "ConvertToFileControl", + "ConvertToDate", + "ConvertToTime", + "ConvertToNumeric", + "ConvertToCurrency", + "ConvertToPattern", + "ConvertToImageControl", + "ConvertToFormatted", + "ConvertToScrollBar", + "ConvertToSpinButton", + "ConvertToNavigationBar" +}; + +constexpr rtl::OUStringConstExpr aImgIds[] = +{ + RID_SVXBMP_EDITBOX, + RID_SVXBMP_BUTTON, + RID_SVXBMP_FIXEDTEXT, + RID_SVXBMP_LISTBOX, + RID_SVXBMP_CHECKBOX, + RID_SVXBMP_RADIOBUTTON, + RID_SVXBMP_GROUPBOX, + RID_SVXBMP_COMBOBOX, + RID_SVXBMP_IMAGEBUTTON, + RID_SVXBMP_FILECONTROL, + RID_SVXBMP_DATEFIELD, + RID_SVXBMP_TIMEFIELD, + RID_SVXBMP_NUMERICFIELD, + RID_SVXBMP_CURRENCYFIELD, + RID_SVXBMP_PATTERNFIELD, + RID_SVXBMP_IMAGECONTROL, + RID_SVXBMP_FORMATTEDFIELD, + RID_SVXBMP_SCROLLBAR, + RID_SVXBMP_SPINBUTTON, + RID_SVXBMP_NAVIGATIONBAR +}; + +const SdrObjKind nObjectTypes[] = +{ + SdrObjKind::FormEdit, + SdrObjKind::FormButton, + SdrObjKind::FormFixedText, + SdrObjKind::FormListbox, + SdrObjKind::FormCheckbox, + SdrObjKind::FormRadioButton, + SdrObjKind::FormGroupBox, + SdrObjKind::FormCombobox, + SdrObjKind::FormImageButton, + SdrObjKind::FormFileControl, + SdrObjKind::FormDateField, + SdrObjKind::FormTimeField, + SdrObjKind::FormNumericField, + SdrObjKind::FormCurrencyField, + SdrObjKind::FormPatternField, + SdrObjKind::FormImageControl, + SdrObjKind::FormFormattedField, + SdrObjKind::FormScrollbar, + SdrObjKind::FormSpinButton, + SdrObjKind::FormNavigationBar +}; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::ui; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdbcx; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::form; +using namespace ::com::sun::star::form::binding; +using namespace ::com::sun::star::form::runtime; +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::view; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::script; +using namespace ::svxform; +using namespace ::svx; +using namespace ::dbtools; + + +//= helper + +namespace +{ + + void collectInterfacesFromMarkList( const SdrMarkList& _rMarkList, InterfaceBag& /* [out] */ _rInterfaces ) + { + _rInterfaces.clear(); + + const size_t nMarkCount = _rMarkList.GetMarkCount(); + for ( size_t i = 0; i < nMarkCount; ++i) + { + SdrObject* pCurrent = _rMarkList.GetMark( i )->GetMarkedSdrObj(); + + std::unique_ptr pGroupIterator; + if ( pCurrent->IsGroupObject() ) + { + pGroupIterator.reset(new SdrObjListIter( pCurrent->GetSubList() )); + pCurrent = pGroupIterator->IsMore() ? pGroupIterator->Next() : nullptr; + } + + while ( pCurrent ) + { + FmFormObj* pAsFormObject = FmFormObj::GetFormObject( pCurrent ); + // note this will de-reference virtual objects, if necessary/possible + if ( pAsFormObject ) + { + Reference< XInterface > xControlModel( pAsFormObject->GetUnoControlModel(), UNO_QUERY ); + // the UNO_QUERY is important for normalization + if ( xControlModel.is() ) + _rInterfaces.insert( xControlModel ); + } + + // next element + pCurrent = pGroupIterator && pGroupIterator->IsMore() ? pGroupIterator->Next() : nullptr; + } + } + } + + + sal_Int32 GridView2ModelPos(const Reference< XIndexAccess>& rColumns, sal_Int16 nViewPos) + { + try + { + if (rColumns.is()) + { + // loop through all columns + sal_Int32 i; + Reference< XPropertySet> xCur; + for (i=0; igetCount(); ++i) + { + rColumns->getByIndex(i) >>= xCur; + if (!::comphelper::getBOOL(xCur->getPropertyValue(FM_PROP_HIDDEN))) + { + // for every visible col : if nViewPos is greater zero, decrement it, else we + // have found the model position + if (!nViewPos) + break; + else + --nViewPos; + } + } + if (igetCount()) + return i; + } + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return -1; + } + + + void TransferEventScripts(const Reference< XControlModel>& xModel, const Reference< XControl>& xControl, + const Sequence< ScriptEventDescriptor>& rTransferIfAvailable) + { + // first check if we have a XEventAttacherManager for the model + Reference< XChild> xModelChild(xModel, UNO_QUERY); + if (!xModelChild.is()) + return; // nothing to do + + Reference< XEventAttacherManager> xEventManager(xModelChild->getParent(), UNO_QUERY); + if (!xEventManager.is()) + return; // nothing to do + + if (!rTransferIfAvailable.hasElements()) + return; // nothing to do + + // check for the index of the model within its parent + Reference< XIndexAccess> xParentIndex(xModelChild->getParent(), UNO_QUERY); + if (!xParentIndex.is()) + return; // nothing to do + sal_Int32 nIndex = getElementPos(xParentIndex, xModel); + if (nIndex<0 || nIndex>=xParentIndex->getCount()) + return; // nothing to do + + // then we need information about the listeners supported by the control and the model + Sequence< Type> aModelListeners; + Sequence< Type> aControlListeners; + + Reference< XIntrospection> xIntrospection = theIntrospection::get(::comphelper::getProcessComponentContext()); + + if (xModel.is()) + { + Any aModel(xModel); + aModelListeners = xIntrospection->inspect(aModel)->getSupportedListeners(); + } + + if (xControl.is()) + { + Any aControl(xControl); + aControlListeners = xIntrospection->inspect(aControl)->getSupportedListeners(); + } + + sal_Int32 nMaxNewLen = aModelListeners.getLength() + aControlListeners.getLength(); + if (!nMaxNewLen) + return; // the model and the listener don't support any listeners (or we were unable to retrieve these infos) + + Sequence< ScriptEventDescriptor> aTransferable(nMaxNewLen); + ScriptEventDescriptor* pTransferable = aTransferable.getArray(); + + for (const ScriptEventDescriptor& rCurrent : rTransferIfAvailable) + { + // search the model/control idl classes for the event described by pCurrent + for (const Sequence< Type>* pCurrentArray : { &aModelListeners, &aControlListeners }) + { + for (const Type& rCurrentListener : *pCurrentArray) + { + OUString aListener = rCurrentListener.getTypeName(); + if (!aListener.isEmpty()) + aListener = aListener.copy(aListener.lastIndexOf('.')+1); + + if (aListener == rCurrent.ListenerType) + // the current ScriptEventDescriptor doesn't match the current listeners class + continue; + + // now check the methods + Sequence< OUString> aMethodsNames = ::comphelper::getEventMethodsForType(rCurrentListener); + + if (comphelper::findValue(aMethodsNames, rCurrent.EventMethod) != -1) + { + // we can transfer the script event : the model (control) supports it + *pTransferable = rCurrent; + ++pTransferable; + break; + } + } + } + } + + sal_Int32 nRealNewLen = pTransferable - aTransferable.getArray(); + aTransferable.realloc(nRealNewLen); + + xEventManager->registerScriptEvents(nIndex, aTransferable); + } + + + OUString getServiceNameByControlType(SdrObjKind nType) + { + switch (nType) + { + case SdrObjKind::FormEdit : return FM_COMPONENT_TEXTFIELD; + case SdrObjKind::FormButton : return FM_COMPONENT_COMMANDBUTTON; + case SdrObjKind::FormFixedText : return FM_COMPONENT_FIXEDTEXT; + case SdrObjKind::FormListbox : return FM_COMPONENT_LISTBOX; + case SdrObjKind::FormCheckbox : return FM_COMPONENT_CHECKBOX; + case SdrObjKind::FormRadioButton : return FM_COMPONENT_RADIOBUTTON; + case SdrObjKind::FormGroupBox : return FM_COMPONENT_GROUPBOX; + case SdrObjKind::FormCombobox : return FM_COMPONENT_COMBOBOX; + case SdrObjKind::FormGrid : return FM_COMPONENT_GRIDCONTROL; + case SdrObjKind::FormImageButton : return FM_COMPONENT_IMAGEBUTTON; + case SdrObjKind::FormFileControl : return FM_COMPONENT_FILECONTROL; + case SdrObjKind::FormDateField : return FM_COMPONENT_DATEFIELD; + case SdrObjKind::FormTimeField : return FM_COMPONENT_TIMEFIELD; + case SdrObjKind::FormNumericField : return FM_COMPONENT_NUMERICFIELD; + case SdrObjKind::FormCurrencyField : return FM_COMPONENT_CURRENCYFIELD; + case SdrObjKind::FormPatternField : return FM_COMPONENT_PATTERNFIELD; + case SdrObjKind::FormHidden : return FM_COMPONENT_HIDDENCONTROL; + case SdrObjKind::FormImageControl : return FM_COMPONENT_IMAGECONTROL; + case SdrObjKind::FormFormattedField : return FM_COMPONENT_FORMATTEDFIELD; + case SdrObjKind::FormScrollbar : return FM_SUN_COMPONENT_SCROLLBAR; + case SdrObjKind::FormSpinButton : return FM_SUN_COMPONENT_SPINBUTTON; + case SdrObjKind::FormNavigationBar : return FM_SUN_COMPONENT_NAVIGATIONBAR; + default:; + } + return OUString(); + } + +} + + +// check if the control has one of the interfaces we can use for searching +// *_pCurrentText will be filled with the current text of the control (as used when searching this control) +bool IsSearchableControl( const css::uno::Reference< css::uno::XInterface>& _rxControl, + OUString* _pCurrentText ) +{ + if ( !_rxControl.is() ) + return false; + + Reference< XTextComponent > xAsText( _rxControl, UNO_QUERY ); + if ( xAsText.is() ) + { + if ( _pCurrentText ) + *_pCurrentText = xAsText->getText(); + return true; + } + + Reference< XListBox > xListBox( _rxControl, UNO_QUERY ); + if ( xListBox.is() ) + { + if ( _pCurrentText ) + *_pCurrentText = xListBox->getSelectedItem(); + return true; + } + + Reference< XCheckBox > xCheckBox( _rxControl, UNO_QUERY ); + if ( xCheckBox.is() ) + { + if ( _pCurrentText ) + { + switch ( static_cast<::TriState>(xCheckBox->getState()) ) + { + case TRISTATE_FALSE: *_pCurrentText = "0"; break; + case TRISTATE_TRUE: *_pCurrentText = "1"; break; + default: _pCurrentText->clear(); break; + } + } + return true; + } + + return false; +} + + +bool FmXBoundFormFieldIterator::ShouldStepInto(const Reference< XInterface>& _rContainer) const +{ + if (_rContainer == m_xStartingPoint) + // would be quite stupid to step over the root... + return true; + + return Reference< XControlModel>(_rContainer, UNO_QUERY).is(); +} + + +bool FmXBoundFormFieldIterator::ShouldHandleElement(const Reference< XInterface>& _rElement) +{ + if (!_rElement.is()) + // NULL element + return false; + + if (Reference< XForm>(_rElement, UNO_QUERY).is() || Reference< XGrid>(_rElement, UNO_QUERY).is()) + // a forms or a grid + return false; + + Reference< XPropertySet> xSet(_rElement, UNO_QUERY); + if (!xSet.is() || !::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet)) + // no "BoundField" property + return false; + + Any aVal( xSet->getPropertyValue(FM_PROP_BOUNDFIELD) ); + if (aVal.getValueTypeClass() != TypeClass_INTERFACE) + // void or invalid property value + return false; + + return aVal.hasValue(); +} + + +static bool isControlList(const SdrMarkList& rMarkList) +{ + // the list contains only controls and at least one control + const size_t nMarkCount = rMarkList.GetMarkCount(); + bool bControlList = nMarkCount != 0; + + bool bHadAnyLeafs = false; + + for (size_t i = 0; i < nMarkCount && bControlList; ++i) + { + SdrObject *pObj = rMarkList.GetMark(i)->GetMarkedSdrObj(); + E3dObject* pAs3DObject = dynamic_cast< E3dObject* >( pObj); + // E3dObject's do not contain any 2D-objects (by definition) + // we need this extra check here : an E3dObject->IsGroupObject says "YES", but an SdrObjListIter working + // with an E3dObject doesn't give me any Nodes (E3dObject has a sub list, but no members in that list, + // cause there implementation differs from the one of "normal" SdrObject's. Unfortunally SdrObject::IsGroupObject + // doesn't check the element count of the sub list, which is simply a bug in IsGroupObject we can't fix at the moment). + // So at the end of this function bControlList would have the same value it was initialized with above : sal_True + // And this would be wrong :) + // 03.02.00 - 72529 - FS + if (!pAs3DObject) + { + if (pObj->IsGroupObject()) + { + SdrObjListIter aIter(pObj->GetSubList()); + while (aIter.IsMore() && bControlList) + { + bControlList = SdrInventor::FmForm == aIter.Next()->GetObjInventor(); + bHadAnyLeafs = true; + } + } + else + { + bHadAnyLeafs = true; + bControlList = SdrInventor::FmForm == pObj->GetObjInventor(); + } + } + } + + return bControlList && bHadAnyLeafs; +} + + +static Reference< XForm > GetForm(const Reference< XInterface>& _rxElement) +{ + Reference< XForm > xForm( _rxElement, UNO_QUERY ); + if ( xForm.is() ) + return xForm; + + Reference< XChild > xChild( _rxElement, UNO_QUERY ); + if ( xChild.is() ) + return GetForm( xChild->getParent() ); + + return Reference< XForm >(); +} + +FmXFormShell_Base_Disambiguation::FmXFormShell_Base_Disambiguation( ::osl::Mutex& _rMutex ) + :FmXFormShell_BD_BASE( _rMutex ) +{ +} + +FmXFormShell::FmXFormShell( FmFormShell& _rShell, SfxViewFrame* _pViewFrame ) + :FmXFormShell_BASE(m_aMutex) + ,FmXFormShell_CFGBASE("Office.Common/Misc", ConfigItemMode::NONE) + ,m_aMarkTimer("svx::FmXFormShell m_aMarkTimer") + ,m_eNavigate( NavigationBarMode_NONE ) + ,m_nInvalidationEvent( nullptr ) + ,m_nActivationEvent( nullptr ) + ,m_pShell( &_rShell ) + ,m_pTextShell( new svx::FmTextControlShell( _pViewFrame ) ) + ,m_aActiveControllerFeatures( this ) + ,m_aNavControllerFeatures( this ) + ,m_eDocumentType( eUnknownDocumentType ) + ,m_nLockSlotInvalidation( 0 ) + ,m_bHadPropertyBrowserInDesignMode( false ) + ,m_bTrackProperties( true ) + ,m_bUseWizards( true ) + ,m_bDatabaseBar( false ) + ,m_bInActivate( false ) + ,m_bSetFocus( false ) + ,m_bFilterMode( false ) + ,m_bChangingDesignMode( false ) + ,m_bPreparedClose( false ) + ,m_bFirstActivation( true ) +{ + m_aMarkTimer.SetTimeout(100); + m_aMarkTimer.SetInvokeHandler(LINK(this, FmXFormShell, OnTimeOut_Lock)); + + m_xAttachedFrame = _pViewFrame->GetFrame().GetFrameInterface(); + + // to prevent deletion of this we acquire our refcounter once + osl_atomic_increment(&m_refCount); + + // correct the refcounter + osl_atomic_decrement(&m_refCount); + + // cache the current configuration settings we're interested in + implAdjustConfigCache_Lock(); + // and register for changes on this settings + Sequence< OUString > aNames { "FormControlPilotsEnabled" }; + EnableNotification(aNames); +} + + +FmXFormShell::~FmXFormShell() +{ +} + + +Reference< css::frame::XModel > FmXFormShell::getContextDocument_Lock() const +{ + Reference< css::frame::XModel > xModel; + + // determine the type of document we live in + try + { + Reference< css::frame::XController > xController; + if ( m_xAttachedFrame.is() ) + xController = m_xAttachedFrame->getController(); + if ( xController.is() ) + xModel = xController->getModel(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return xModel; +} + + +bool FmXFormShell::isEnhancedForm_Lock() const +{ + return getDocumentType_Lock() == eEnhancedForm; +} + + +bool FmXFormShell::impl_checkDisposed_Lock() const +{ + DBG_TESTSOLARMUTEX(); + if ( !m_pShell ) + { + OSL_FAIL( "FmXFormShell::impl_checkDisposed: already disposed!" ); + return true; + } + return false; +} + + +::svxform::DocumentType FmXFormShell::getDocumentType_Lock() const +{ + if ( m_eDocumentType != eUnknownDocumentType ) + return m_eDocumentType; + + // determine the type of document we live in + Reference xModel = getContextDocument_Lock(); + if ( xModel.is() ) + m_eDocumentType = DocumentClassification::classifyDocument( xModel ); + else + { + OSL_FAIL( "FmXFormShell::getDocumentType: can't determine the document type!" ); + m_eDocumentType = eTextDocument; + // fallback, just to have a defined state + } + + return m_eDocumentType; +} + + +bool FmXFormShell::IsReadonlyDoc_Lock() const +{ + if (impl_checkDisposed_Lock()) + return true; + + FmFormModel* pModel = m_pShell->GetFormModel(); + if ( pModel && pModel->GetObjectShell() ) + return pModel->GetObjectShell()->IsReadOnly() || pModel->GetObjectShell()->IsReadOnlyUI(); + return true; +} + +// EventListener + +void SAL_CALL FmXFormShell::disposing(const lang::EventObject& e) +{ + SolarMutexGuard g; + + if (m_xActiveController == e.Source) + { + // the controller will release, then release everything + stopListening_Lock(); + m_xActiveForm = nullptr; + m_xActiveController = nullptr; + m_xNavigationController = nullptr; + + m_aActiveControllerFeatures.dispose(); + m_aNavControllerFeatures.dispose(); + + if ( m_pShell ) + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().InvalidateShell(*m_pShell); + } + + if (e.Source != m_xExternalViewController) + return; + + Reference< runtime::XFormController > xFormController( m_xExternalViewController, UNO_QUERY ); + OSL_ENSURE( xFormController.is(), "FmXFormShell::disposing: invalid external view controller!" ); + if (xFormController.is()) + xFormController->removeActivateListener(static_cast(this)); + + if (m_xExternalViewController.is()) + m_xExternalViewController->removeEventListener(static_cast(static_cast(this))); + + m_xExternalViewController = nullptr; + m_xExternalDisplayedForm = nullptr; + m_xExtViewTriggerController = nullptr; + + InvalidateSlot_Lock( SID_FM_VIEW_AS_GRID, false ); +} + + +void SAL_CALL FmXFormShell::propertyChange(const PropertyChangeEvent& evt) +{ + SolarMutexGuard g; + + if (impl_checkDisposed_Lock()) + return; + + if (evt.PropertyName == FM_PROP_ROWCOUNT) + { + // The update following this forces a re-painting of the corresponding + // slots. But if I am not in the MainThread of the application (because, + // for example, a cursor is counting data sets at the moment and always + // gives me this PropertyChanges), this can clash with normal paints in + // the MainThread of the application. (Such paints happen, for example, + // if one simply places another application over the office and switches + // back again). + // Therefore the use of the SolarMutex, which safeguards that. + comphelper::SolarMutex& rSolarSafety = Application::GetSolarMutex(); + if (rSolarSafety.tryToAcquire()) + { + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().Invalidate(SID_FM_RECORD_TOTAL, true); + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().Update(SID_FM_RECORD_TOTAL); + rSolarSafety.release(); + } + else + { + // with the following the slot is invalidated asynchron + LockSlotInvalidation_Lock(true); + InvalidateSlot_Lock(SID_FM_RECORD_TOTAL, false); + LockSlotInvalidation_Lock(false); + } + } + + // this may be called from a non-main-thread so invalidate the shell asynchronously + LockSlotInvalidation_Lock(true); + InvalidateSlot_Lock(0, false); // special meaning : invalidate m_pShell + LockSlotInvalidation_Lock(false); +} + + +void FmXFormShell::invalidateFeatures( const ::std::vector< sal_Int32 >& _rFeatures ) +{ + SolarMutexGuard g; + + if (impl_checkDisposed_Lock()) + return; + + OSL_ENSURE( !_rFeatures.empty(), "FmXFormShell::invalidateFeatures: invalid arguments!" ); + + if ( !(m_pShell->GetViewShell() && m_pShell->GetViewShell()->GetViewFrame()) ) + return; + + // unfortunately, SFX requires sal_uInt16 + ::std::vector< sal_uInt16 > aSlotIds( _rFeatures.begin(), _rFeatures.end() ); + + // furthermore, SFX wants a terminating 0 + aSlotIds.push_back( 0 ); + + // and, last but not least, SFX wants the ids to be sorted + ::std::sort( aSlotIds.begin(), aSlotIds.end() - 1 ); + + sal_uInt16 *pSlotIds = aSlotIds.data(); + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().Invalidate( pSlotIds ); +} + + +void SAL_CALL FmXFormShell::formActivated(const lang::EventObject& rEvent) +{ + SolarMutexGuard g; + + if (impl_checkDisposed_Lock()) + return; + + Reference< runtime::XFormController > xController( rEvent.Source, UNO_QUERY_THROW ); + m_pTextShell->formActivated( xController ); + setActiveController_Lock(xController); +} + + +void SAL_CALL FmXFormShell::formDeactivated(const lang::EventObject& rEvent) +{ + SolarMutexGuard g; + + if (impl_checkDisposed_Lock()) + return; + + Reference< runtime::XFormController > xController( rEvent.Source, UNO_QUERY_THROW ); + m_pTextShell->formDeactivated( xController ); +} + + +void FmXFormShell::disposing() +{ + SolarMutexGuard g; + + FmXFormShell_BASE::disposing(); + + if ( m_pShell && !m_pShell->IsDesignMode() ) + setActiveController_Lock(nullptr, true); + // do NOT save the content of the old form (the second parameter tells this) + // if we're here, then we expect that PrepareClose has been called, and thus the user + // got a chance to commit or reject any changes. So in case we're here and there + // are still uncommitted changes, the user explicitly wanted this. + + m_pTextShell->dispose(); + + m_xAttachedFrame = nullptr; + + CloseExternalFormViewer_Lock(); + + while ( !m_aLoadingPages.empty() ) + { + Application::RemoveUserEvent( m_aLoadingPages.front().nEventId ); + m_aLoadingPages.pop(); + } + + { + if (m_nInvalidationEvent) + { + Application::RemoveUserEvent(m_nInvalidationEvent); + m_nInvalidationEvent = nullptr; + } + if ( m_nActivationEvent ) + { + Application::RemoveUserEvent( m_nActivationEvent ); + m_nActivationEvent = nullptr; + } + } + + { + DBG_ASSERT(!m_nInvalidationEvent, "FmXFormShell::~FmXFormShell : still have an invalidation event !"); + // should have been deleted while being disposed + + m_aMarkTimer.Stop(); + } + + DisableNotification(); + + RemoveElement_Lock(m_xForms); + m_xForms.clear(); + + impl_switchActiveControllerListening_Lock(false); + m_xActiveController = nullptr; + m_xActiveForm = nullptr; + + m_pShell = nullptr; + m_xNavigationController = nullptr; + m_xCurrentForm = nullptr; + m_xLastGridFound = nullptr; + m_xAttachedFrame = nullptr; + m_xExternalViewController = nullptr; + m_xExtViewTriggerController = nullptr; + m_xExternalDisplayedForm = nullptr; + + InterfaceBag().swap(m_aCurrentSelection); + + m_aActiveControllerFeatures.dispose(); + m_aNavControllerFeatures.dispose(); +} + + +void FmXFormShell::UpdateSlot_Lock(sal_Int16 _nId) +{ + if (impl_checkDisposed_Lock()) + return; + + if ( m_nLockSlotInvalidation ) + { + OSL_FAIL( "FmXFormShell::UpdateSlot: cannot update if invalidation is currently locked!" ); + InvalidateSlot_Lock(_nId, false); + } + else + { + OSL_ENSURE( _nId, "FmXFormShell::UpdateSlot: can't update the complete shell!" ); + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().Invalidate( _nId, true, true ); + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().Update( _nId ); + } +} + + +void FmXFormShell::InvalidateSlot_Lock(sal_Int16 nId, bool bWithId) +{ + if (impl_checkDisposed_Lock()) + return; + + if (m_nLockSlotInvalidation) + { + sal_uInt8 nFlags = ( bWithId ? 0x01 : 0 ); + m_arrInvalidSlots.emplace_back(nId, nFlags ); + } + else + if (nId) + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().Invalidate(nId, true, bWithId); + else + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().InvalidateShell(*m_pShell); +} + + +void FmXFormShell::LockSlotInvalidation_Lock(bool bLock) +{ + if (impl_checkDisposed_Lock()) + return; + + DBG_ASSERT(bLock || m_nLockSlotInvalidation>0, "FmXFormShell::LockSlotInvalidation : invalid call !"); + + if (bLock) + ++m_nLockSlotInvalidation; + else if (!--m_nLockSlotInvalidation) + { + // (asynchronously) invalidate everything accumulated during the locked phase + if (!m_nInvalidationEvent) + m_nInvalidationEvent = Application::PostUserEvent(LINK(this, FmXFormShell, OnInvalidateSlots_Lock)); + } +} + + +IMPL_LINK_NOARG(FmXFormShell, OnInvalidateSlots_Lock, void*,void) +{ + if (impl_checkDisposed_Lock()) + return; + + m_nInvalidationEvent = nullptr; + + for (const auto& rInvalidSlot : m_arrInvalidSlots) + { + if (rInvalidSlot.id) + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().Invalidate(rInvalidSlot.id, true, (rInvalidSlot.flags & 0x01)); + else + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().InvalidateShell(*m_pShell); + } + m_arrInvalidSlots.clear(); +} + + +void FmXFormShell::ForceUpdateSelection_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + if (IsSelectionUpdatePending_Lock()) + { + m_aMarkTimer.Stop(); + + // optionally turn off the invalidation of slots which is implicitly done by SetSelection + LockSlotInvalidation_Lock(true); + + SetSelection_Lock(m_pShell->GetFormView()->GetMarkedObjectList()); + + LockSlotInvalidation_Lock(false); + } +} + +void FmXFormShell::GetConversionMenu_Lock(weld::Menu& rNewMenu) +{ + for (size_t i = 0; i < SAL_N_ELEMENTS(aConvertSlots); ++i) + { + // the corresponding image at it + rNewMenu.append(OUString::createFromAscii(aConvertSlots[i]), SvxResId(RID_SVXSW_CONVERTMENU[i]), aImgIds[i]); + } +} + +OString FmXFormShell::SlotToIdent(sal_uInt16 nSlot) +{ + static_assert(SAL_N_ELEMENTS(SelObjectSlotMap) >= SAL_N_ELEMENTS(aConvertSlots)); + + for (size_t i = 0; i < SAL_N_ELEMENTS(aConvertSlots); ++i) + { + if (nSlot == SelObjectSlotMap[i]) + return aConvertSlots[i]; + } + + return OString(); +} + +bool FmXFormShell::isControlConversionSlot(std::string_view rIdent) +{ + for (const auto& rConvertSlot : aConvertSlots) + if (rIdent == rConvertSlot) + return true; + return false; +} + +void FmXFormShell::executeControlConversionSlot_Lock(std::string_view rIdent) +{ + OSL_PRECOND( canConvertCurrentSelectionToControl_Lock(rIdent), "FmXFormShell::executeControlConversionSlot: illegal call!" ); + InterfaceBag::const_iterator aSelectedElement = m_aCurrentSelection.begin(); + if ( aSelectedElement == m_aCurrentSelection.end() ) + return; + + executeControlConversionSlot_Lock(Reference(*aSelectedElement, UNO_QUERY), rIdent); +} + +bool FmXFormShell::executeControlConversionSlot_Lock(const Reference& _rxObject, std::string_view rIdent) +{ + if (impl_checkDisposed_Lock()) + return false; + + OSL_ENSURE( _rxObject.is(), "FmXFormShell::executeControlConversionSlot: invalid object!" ); + if ( !_rxObject.is() ) + return false; + + SdrPage* pPage = m_pShell->GetCurPage(); + FmFormPage* pFormPage = dynamic_cast< FmFormPage* >( pPage ); + OSL_ENSURE( pFormPage, "FmXFormShell::executeControlConversionSlot: no current (form) page!" ); + if ( !pFormPage ) + return false; + + OSL_ENSURE( isSolelySelected_Lock(_rxObject), + "FmXFormShell::executeControlConversionSlot: hmm ... shouldn't this parameter be redundant?" ); + + for (size_t lookupSlot = 0; lookupSlot < SAL_N_ELEMENTS(aConvertSlots); ++lookupSlot) + { + if (rIdent == aConvertSlots[lookupSlot]) + { + Reference< XInterface > xNormalizedObject( _rxObject, UNO_QUERY ); + + FmFormObj* pFormObject = nullptr; + SdrObjListIter aPageIter( pFormPage ); + while ( aPageIter.IsMore() ) + { + SdrObject* pCurrent = aPageIter.Next(); + pFormObject = FmFormObj::GetFormObject( pCurrent ); + if ( !pFormObject ) + continue; + + Reference< XInterface > xCurrentNormalized( pFormObject->GetUnoControlModel(), UNO_QUERY ); + if ( xCurrentNormalized.get() == xNormalizedObject.get() ) + break; + + pFormObject = nullptr; + } + + if ( !pFormObject ) + return false; + + OUString sNewName( getServiceNameByControlType( nObjectTypes[ lookupSlot ] ) ); + Reference xContext = comphelper::getProcessComponentContext(); + Reference< XControlModel> xNewModel( xContext->getServiceManager()->createInstanceWithContext(sNewName, xContext), UNO_QUERY ); + if (!xNewModel.is()) + return false; + + Reference< XControlModel> xOldModel( pFormObject->GetUnoControlModel() ); + + // transfer properties + Reference< XPropertySet> xOldSet(xOldModel, UNO_QUERY); + Reference< XPropertySet> xNewSet(xNewModel, UNO_QUERY); + + + lang::Locale aNewLanguage = Application::GetSettings().GetUILanguageTag().getLocale(); + TransferFormComponentProperties(xOldSet, xNewSet, aNewLanguage); + + Sequence< css::script::ScriptEventDescriptor> aOldScripts; + Reference< XChild> xChild(xOldModel, UNO_QUERY); + if (xChild.is()) + { + Reference< XIndexAccess> xParent(xChild->getParent(), UNO_QUERY); + + // remember old script events + Reference< css::script::XEventAttacherManager> xEvManager(xChild->getParent(), UNO_QUERY); + if (xParent.is() && xEvManager.is()) + { + sal_Int32 nIndex = getElementPos(xParent, xOldModel); + if (nIndex>=0 && nIndexgetCount()) + aOldScripts = xEvManager->getScriptEvents(nIndex); + } + + // replace the model within the parent container + Reference< XIndexContainer> xIndexParent(xChild->getParent(), UNO_QUERY); + if (xIndexParent.is()) + { + // the form container works with FormComponents + Reference< XFormComponent> xComponent(xNewModel, UNO_QUERY); + DBG_ASSERT(xComponent.is(), "FmXFormShell::executeControlConversionSlot: the new model is no form component !"); + Any aNewModel(xComponent); + try + { + + sal_Int32 nIndex = getElementPos(xParent, xOldModel); + if (nIndex>=0 && nIndexgetCount()) + xIndexParent->replaceByIndex(nIndex, aNewModel); + else + { + OSL_FAIL("FmXFormShell::executeControlConversionSlot: could not replace the model !"); + Reference< css::lang::XComponent> xNewComponent(xNewModel, UNO_QUERY); + if (xNewComponent.is()) + xNewComponent->dispose(); + return false; + } + } + catch(Exception&) + { + OSL_FAIL("FmXFormShell::executeControlConversionSlot: could not replace the model !"); + Reference< css::lang::XComponent> xNewComponent(xNewModel, UNO_QUERY); + if (xNewComponent.is()) + xNewComponent->dispose(); + return false; + } + + } + } + + // special handling for the LabelControl-property : can only be set when the model is placed + // within the forms hierarchy + if (::comphelper::hasProperty(FM_PROP_CONTROLLABEL, xOldSet) && ::comphelper::hasProperty(FM_PROP_CONTROLLABEL, xNewSet)) + { + try + { + xNewSet->setPropertyValue(FM_PROP_CONTROLLABEL, xOldSet->getPropertyValue(FM_PROP_CONTROLLABEL)); + } + catch(Exception&) + { + } + + } + + // set new model + pFormObject->SetChanged(); + pFormObject->SetUnoControlModel(xNewModel); + + // transfer script events + // (do this _after_ SetUnoControlModel as we need the new (implicitly created) control) + if (aOldScripts.hasElements()) + { + // find the control for the model + Reference xControlContainer(getControlContainerForView_Lock()); + + const Sequence< Reference< XControl> > aControls( xControlContainer->getControls() ); + + Reference< XControl> xControl; + auto pControl = std::find_if(aControls.begin(), aControls.end(), + [&xNewModel](const Reference< XControl>& rControl) { return rControl->getModel() == xNewModel; }); + if (pControl != aControls.end()) + xControl = *pControl; + TransferEventScripts(xNewModel, xControl, aOldScripts); + } + + // transfer value bindings, if possible + { + Reference< XBindableValue > xOldBindable( xOldModel, UNO_QUERY ); + Reference< XBindableValue > xNewBindable( xNewModel, UNO_QUERY ); + if ( xOldBindable.is() ) + { + try + { + if ( xNewBindable.is() ) + xNewBindable->setValueBinding( xOldBindable->getValueBinding() ); + xOldBindable->setValueBinding( nullptr ); + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + } + // same for list entry sources + { + Reference< XListEntrySink > xOldSink( xOldModel, UNO_QUERY ); + Reference< XListEntrySink > xNewSink( xNewModel, UNO_QUERY ); + if ( xOldSink.is() ) + { + try + { + if ( xNewSink.is() ) + xNewSink->setListEntrySource( xOldSink->getListEntrySource() ); + xOldSink->setListEntrySource( nullptr ); + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + } + + // create an undo action + FmFormModel* pModel = m_pShell->GetFormModel(); + DBG_ASSERT(pModel != nullptr, "FmXFormShell::executeControlConversionSlot: my shell has no model !"); + if (pModel && pModel->IsUndoEnabled() ) + { + pModel->AddUndo(std::make_unique(*pModel, pFormObject, xOldModel)); + } + else + { + FmUndoModelReplaceAction::DisposeElement( xOldModel ); + } + + return true; + } + } + return false; +} + +bool FmXFormShell::canConvertCurrentSelectionToControl_Lock(std::string_view rIdent) +{ + if ( m_aCurrentSelection.empty() ) + return false; + + InterfaceBag::const_iterator aCheck = m_aCurrentSelection.begin(); + Reference< lang::XServiceInfo > xElementInfo( *aCheck, UNO_QUERY ); + if ( !xElementInfo.is() ) + // no service info -> cannot determine this + return false; + + if ( ++aCheck != m_aCurrentSelection.end() ) + // more than one element + return false; + + if ( Reference< XForm >::query( xElementInfo ).is() ) + // it's a form + return false; + + SdrObjKind nObjectType = getControlTypeByObject( xElementInfo ); + + if ( ( SdrObjKind::FormHidden == nObjectType ) + || ( SdrObjKind::FormControl == nObjectType ) + || ( SdrObjKind::FormGrid == nObjectType ) + ) + return false; // those types cannot be converted + + DBG_ASSERT(SAL_N_ELEMENTS(aConvertSlots) == SAL_N_ELEMENTS(nObjectTypes), + "FmXFormShell::canConvertCurrentSelectionToControl: aConvertSlots & nObjectTypes must have the same size !"); + + for (size_t i = 0; i < SAL_N_ELEMENTS(aConvertSlots); ++i) + if (rIdent == aConvertSlots[i]) + return nObjectTypes[i] != nObjectType; + + return true; // all other slots: assume "yes" +} + +void FmXFormShell::checkControlConversionSlotsForCurrentSelection_Lock(weld::Menu& rMenu) +{ + for (int i = 0, nCount = rMenu.n_children(); i < nCount; ++i) + { + // the context is already of a type that corresponds to the entry -> disable + OString sIdent(aConvertSlots[i]); + rMenu.set_sensitive(sIdent, canConvertCurrentSelectionToControl_Lock(sIdent)); + } +} + +void FmXFormShell::LoopGrids_Lock(LoopGridsSync nSync, LoopGridsFlags nFlags) +{ + if (impl_checkDisposed_Lock()) + return; + + Reference< XIndexContainer> xControlModels(m_xActiveForm, UNO_QUERY); + if (!xControlModels.is()) + return; + + for (sal_Int32 i=0; igetCount(); ++i) + { + Reference< XPropertySet> xModelSet; + xControlModels->getByIndex(i) >>= xModelSet; + if (!xModelSet.is()) + continue; + + if (!::comphelper::hasProperty(FM_PROP_CLASSID, xModelSet)) + continue; + sal_Int16 nClassId = ::comphelper::getINT16(xModelSet->getPropertyValue(FM_PROP_CLASSID)); + if (FormComponentType::GRIDCONTROL != nClassId) + continue; + + if (!::comphelper::hasProperty(FM_PROP_CURSORCOLOR, xModelSet) || !::comphelper::hasProperty(FM_PROP_ALWAYSSHOWCURSOR, xModelSet) || !::comphelper::hasProperty(FM_PROP_DISPLAYSYNCHRON, xModelSet)) + continue; + + switch (nSync) + { + case LoopGridsSync::DISABLE_SYNC: + { + xModelSet->setPropertyValue(FM_PROP_DISPLAYSYNCHRON, Any(false)); + } + break; + case LoopGridsSync::FORCE_SYNC: + { + Any aOldVal( xModelSet->getPropertyValue(FM_PROP_DISPLAYSYNCHRON) ); + xModelSet->setPropertyValue(FM_PROP_DISPLAYSYNCHRON, Any(true)); + xModelSet->setPropertyValue(FM_PROP_DISPLAYSYNCHRON, aOldVal); + } + break; + case LoopGridsSync::ENABLE_SYNC: + { + xModelSet->setPropertyValue(FM_PROP_DISPLAYSYNCHRON, Any(true)); + } + break; + } + + if (nFlags & LoopGridsFlags::DISABLE_ROCTRLR) + { + xModelSet->setPropertyValue(FM_PROP_ALWAYSSHOWCURSOR, Any(false)); + Reference< XPropertyState> xModelPropState(xModelSet, UNO_QUERY); + if (xModelPropState.is()) + xModelPropState->setPropertyToDefault(FM_PROP_CURSORCOLOR); + else + xModelSet->setPropertyValue(FM_PROP_CURSORCOLOR, Any()); // this should be the default + } + } +} + + +Reference< XControlContainer > FmXFormShell::getControlContainerForView_Lock() const +{ + if (impl_checkDisposed_Lock()) + return nullptr; + + SdrPageView* pPageView = nullptr; + if ( m_pShell && m_pShell->GetFormView() ) + pPageView = m_pShell->GetFormView()->GetSdrPageView(); + + Reference< XControlContainer> xControlContainer; + if ( pPageView ) + xControlContainer = pPageView->GetPageWindow(0)->GetControlContainer(); + + return xControlContainer; +} + + +void FmXFormShell::ExecuteTabOrderDialog_Lock(const Reference& _rxForForm) +{ + if (impl_checkDisposed_Lock()) + return; + + OSL_PRECOND( _rxForForm.is(), "FmXFormShell::ExecuteTabOrderDialog: invalid tabbing model!" ); + if ( !_rxForForm.is() ) + return; + + try + { + Reference< XWindow > xParentWindow; + if ( m_pShell->GetViewShell() && m_pShell->GetViewShell()->GetViewFrame() ) + xParentWindow = VCLUnoHelper::GetInterface ( &m_pShell->GetViewShell()->GetViewFrame()->GetWindow() ); + + Reference< dialogs::XExecutableDialog > xDialog = form::TabOrderDialog::createWithModel( + comphelper::getProcessComponentContext(), + _rxForForm, getControlContainerForView_Lock(), xParentWindow + ); + + xDialog->execute(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "FmXFormShell::ExecuteTabOrderDialog" ); + } +} + + +void FmXFormShell::ExecuteSearch_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + // a collection of all (logical) forms + FmFormArray().swap(m_aSearchForms); + ::std::vector< OUString > aContextNames; + impl_collectFormSearchContexts_nothrow_Lock( + m_pShell->GetCurPage()->GetForms(), u"", + m_aSearchForms, aContextNames); + + if ( m_aSearchForms.size() != aContextNames.size() ) + { + SAL_WARN ( "svx.form", "FmXFormShell::ExecuteSearch: nonsense!" ); + return; + } + + // filter out the forms which do not contain valid controls at all + { + FmFormArray aValidForms; + ::std::vector< OUString > aValidContexts; + FmFormArray::const_iterator form = m_aSearchForms.begin(); + ::std::vector< OUString >::const_iterator contextName = aContextNames.begin(); + for ( ; form != m_aSearchForms.end(); ++form, ++contextName ) + { + FmSearchContext aTestContext; + aTestContext.nContext = static_cast< sal_Int16 >( form - m_aSearchForms.begin() ); + sal_uInt32 nValidControls = OnSearchContextRequest_Lock(aTestContext); + if ( nValidControls > 0 ) + { + aValidForms.push_back( *form ); + aValidContexts.push_back( *contextName ); + } + } + + m_aSearchForms.swap( aValidForms ); + aContextNames.swap( aValidContexts ); + } + + if (m_aSearchForms.empty() ) + { + // there are no controls that meet all the conditions for a search + std::unique_ptr xBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Warning, VclButtonsType::Ok, + SvxResId(RID_STR_NODATACONTROLS))); + xBox->run(); + return; + } + + // now I need another 'initial context' + sal_Int16 nInitialContext = 0; + Reference xActiveForm(getActiveForm_Lock()); + for ( size_t i=0; i(i); + break; + } + } + + // If the dialog should initially offer the text of the active control, + // this must have an XTextComponent interface. An addition, this makes + // sense only if the current field is also bound to a table (or whatever) field. + OUString strActiveField; + OUString strInitialText; + // ... this I get from my FormController + DBG_ASSERT(m_xActiveController.is(), "FmXFormShell::ExecuteSearch : no active controller !"); + Reference< XControl> xActiveControl( m_xActiveController->getCurrentControl()); + if (xActiveControl.is()) + { + // the control can tell me its model ... + Reference< XControlModel> xActiveModel( xActiveControl->getModel()); + DBG_ASSERT(xActiveModel.is(), "FmXFormShell::ExecuteSearch : active control has no model !"); + + // I ask the model for the ControlSource property ... + Reference< XPropertySet> xProperties(xActiveControl->getModel(), UNO_QUERY); + if (::comphelper::hasProperty(FM_PROP_CONTROLSOURCE, xProperties) && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xProperties)) + { + Reference< XPropertySet> xField; + xProperties->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField; + if (xField.is()) // (only when the thing is really bound) + { + // and the control itself for a TextComponent interface (so that I can pick up the text there) + Reference< XTextComponent> xText(xActiveControl, UNO_QUERY); + if (xText.is()) + { + strActiveField = getLabelName(xProperties); + strInitialText = xText->getText(); + } + } + } + else + { + // the control itself has no ControlSource, but maybe it is a GridControl + Reference< XGrid> xGrid(xActiveControl, UNO_QUERY); + if (xGrid.is()) + { + // for strActiveField I need the ControlSource of the column, + // for that the columns container, for that the GridPeer + Reference< XGridPeer> xGridPeer(xActiveControl->getPeer(), UNO_QUERY); + Reference< XIndexAccess> xColumns; + if (xGridPeer.is()) + xColumns = xGridPeer->getColumns(); + + sal_Int16 nViewCol = xGrid->getCurrentColumnPosition(); + sal_Int32 nModelCol = GridView2ModelPos(xColumns, nViewCol); + Reference< XPropertySet> xCurrentCol; + if(xColumns.is()) + xColumns->getByIndex(nModelCol) >>= xCurrentCol; + if (xCurrentCol.is()) + strActiveField = ::comphelper::getString(xCurrentCol->getPropertyValue(FM_PROP_LABEL)); + + // the text of the current column + Reference< XIndexAccess> xColControls(xGridPeer, UNO_QUERY); + Reference< XInterface> xCurControl; + xColControls->getByIndex(nViewCol) >>= xCurControl; + OUString sInitialText; + if (IsSearchableControl(xCurControl, &sInitialText)) + strInitialText = sInitialText; + } + } + } + + // taking care of possible GridControls that I know + LoopGrids_Lock(LoopGridsSync::DISABLE_SYNC); + + // Now I am ready for the dialogue. + // When the potential deadlocks caused by the use of the solar mutex in + // MTs VCLX... classes are eventually cleared, an SM_USETHREAD should be + // placed here, because the search in a separate thread is nevertheless + // somewhat more fluid. Should be, however, somehow made dependent of the + // underlying cursor. DAO for example is not thread-safe. + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + ScopedVclPtr pDialog( + pFact->CreateFmSearchDialog( + m_pShell->GetViewShell()->GetViewFrame()->GetFrameWeld(), + strInitialText, aContextNames, nInitialContext, + LINK(this, FmXFormShell, OnSearchContextRequest_Lock) )); + pDialog->SetActiveField( strActiveField ); + pDialog->SetFoundHandler(LINK(this, FmXFormShell, OnFoundData_Lock)); + pDialog->SetCanceledNotFoundHdl(LINK(this, FmXFormShell, OnCanceledNotFound_Lock)); + pDialog->Execute(); + pDialog.disposeAndClear(); + + // restore GridControls again + LoopGrids_Lock(LoopGridsSync::ENABLE_SYNC, LoopGridsFlags::DISABLE_ROCTRLR); + + m_pShell->GetFormView()->UnMarkAll(m_pShell->GetFormView()->GetSdrPageView()); + // because I marked controls in OnFoundData (if I was there) +} + + +bool FmXFormShell::GetY2KState_Lock(sal_uInt16& n) +{ + if (impl_checkDisposed_Lock()) + return false; + + if (m_pShell->IsDesignMode()) + // in the design mode (without active controls) the main document is to take care of it + return false; + + Reference xForm(getActiveForm_Lock()); + if (!xForm.is()) + // no current form (in particular no current control) -> the main document is to take care + return false; + + Reference< XRowSet> xDB(xForm, UNO_QUERY); + DBG_ASSERT(xDB.is(), "FmXFormShell::GetY2KState : current form has no dbform-interface !"); + + Reference< XNumberFormatsSupplier> xSupplier( getNumberFormats(getConnection(xDB))); + if (xSupplier.is()) + { + Reference< XPropertySet> xSet(xSupplier->getNumberFormatSettings()); + if (xSet.is()) + { + try + { + Any aVal( xSet->getPropertyValue("TwoDigitDateStart") ); + aVal >>= n; + return true; + } + catch(Exception&) + { + } + + } + } + return false; +} + + +void FmXFormShell::SetY2KState_Lock(sal_uInt16 n) +{ + if (impl_checkDisposed_Lock()) + return; + + Reference xActiveForm(getActiveForm_Lock()); + Reference< XRowSet > xActiveRowSet( xActiveForm, UNO_QUERY ); + if ( xActiveRowSet.is() ) + { + Reference< XNumberFormatsSupplier > xSupplier( getNumberFormats( getConnection( xActiveRowSet ) ) ); + if (xSupplier.is()) + { + Reference< XPropertySet> xSet(xSupplier->getNumberFormatSettings()); + if (xSet.is()) + { + try + { + xSet->setPropertyValue("TwoDigitDateStart", Any(sal_uInt16(n))); + } + catch(Exception&) + { + TOOLS_WARN_EXCEPTION("svx.form", ""); + } + + } + return; + } + } + + // no active form found -> iterate through all current forms + Reference< XIndexAccess> xCurrentForms( m_xForms); + if (!xCurrentForms.is()) + { // in the alive mode, my forms are not set, but the ones on the page are + if (m_pShell->GetCurPage()) + xCurrentForms = m_pShell->GetCurPage()->GetForms( false ); + } + if (!xCurrentForms.is()) + return; + + ::comphelper::IndexAccessIterator aIter(xCurrentForms); + Reference< XInterface> xCurrentElement( aIter.Next()); + while (xCurrentElement.is()) + { + // is the current element a DatabaseForm? + Reference< XRowSet> xElementAsRowSet( xCurrentElement, UNO_QUERY ); + if ( xElementAsRowSet.is() ) + { + Reference< XNumberFormatsSupplier > xSupplier( getNumberFormats( getConnection( xElementAsRowSet ) ) ); + if (!xSupplier.is()) + continue; + + Reference< XPropertySet> xSet(xSupplier->getNumberFormatSettings()); + if (xSet.is()) + { + try + { + xSet->setPropertyValue("TwoDigitDateStart", Any(sal_uInt16(n))); + } + catch(Exception&) + { + TOOLS_WARN_EXCEPTION("svx.form", ""); + } + + } + } + xCurrentElement = aIter.Next(); + } +} + + +void FmXFormShell::CloseExternalFormViewer_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + if (!m_xExternalViewController.is()) + return; + + Reference< css::frame::XFrame> xExternalViewFrame( m_xExternalViewController->getFrame()); + Reference< css::frame::XDispatchProvider> xCommLink(xExternalViewFrame, UNO_QUERY); + if (!xCommLink.is()) + return; + + xExternalViewFrame->setComponent(nullptr,nullptr); + ::comphelper::disposeComponent(xExternalViewFrame); + m_xExternalViewController = nullptr; + m_xExtViewTriggerController = nullptr; + m_xExternalDisplayedForm = nullptr; +} + + +Reference FmXFormShell::getInternalForm_Lock(const Reference& _xForm) const +{ + if (impl_checkDisposed_Lock()) + return nullptr; + + Reference< runtime::XFormController> xExternalCtrlr(m_xExternalViewController, UNO_QUERY); + if (xExternalCtrlr.is() && (_xForm == xExternalCtrlr->getModel())) + { + DBG_ASSERT(m_xExternalDisplayedForm.is(), "FmXFormShell::getInternalForm : invalid external form !"); + return m_xExternalDisplayedForm; + } + return _xForm; +} + + +Reference FmXFormShell::getInternalForm_Lock(const Reference& _xForm) const +{ + if (impl_checkDisposed_Lock()) + return nullptr; + + Reference< runtime::XFormController > xExternalCtrlr(m_xExternalViewController, UNO_QUERY); + if (xExternalCtrlr.is() && (_xForm == xExternalCtrlr->getModel())) + { + DBG_ASSERT(m_xExternalDisplayedForm.is(), "FmXFormShell::getInternalForm : invalid external form !"); + return Reference< XForm>(m_xExternalDisplayedForm, UNO_QUERY); + } + return _xForm; +} + + +namespace +{ + bool lcl_isNavigationRelevant( sal_Int32 _nWhich ) + { + return ( _nWhich == SID_FM_RECORD_FIRST ) + || ( _nWhich == SID_FM_RECORD_PREV ) + || ( _nWhich == SID_FM_RECORD_NEXT ) + || ( _nWhich == SID_FM_RECORD_LAST ) + || ( _nWhich == SID_FM_RECORD_NEW ); + } +} + + +bool FmXFormShell::IsFormSlotEnabled( sal_Int32 _nSlot, FeatureState* _pCompleteState ) const +{ + const svx::ControllerFeatures& rController = + lcl_isNavigationRelevant( _nSlot ) + ? getNavControllerFeatures_Lock() + : getActiveControllerFeatures_Lock(); + + if ( !_pCompleteState ) + return rController->isEnabled( _nSlot ); + + rController->getState( _nSlot, *_pCompleteState ); + return _pCompleteState->Enabled; +} + + +void FmXFormShell::ExecuteFormSlot_Lock( sal_Int32 _nSlot ) +{ + const svx::ControllerFeatures& rController = + lcl_isNavigationRelevant( _nSlot ) + ? getNavControllerFeatures_Lock() + : getActiveControllerFeatures_Lock(); + + rController->execute( _nSlot ); + + if ( _nSlot != SID_FM_RECORD_UNDO ) + return; + + // if we're doing an UNDO, *and* if the affected form is the form which we also display + // as external view, then we need to reset the controls of the external form, too + if (getInternalForm_Lock(getActiveForm_Lock()) != m_xExternalDisplayedForm) + return; + + Reference< XIndexAccess > xContainer( m_xExternalDisplayedForm, UNO_QUERY ); + if ( !xContainer.is() ) + return; + + Reference< XReset > xReset; + for ( sal_Int32 i = 0; i < xContainer->getCount(); ++i ) + { + if ( ( xContainer->getByIndex( i ) >>= xReset ) && xReset.is() ) + { + // no resets on sub forms + Reference< XForm > xAsForm( xReset, UNO_QUERY ); + if ( !xAsForm.is() ) + xReset->reset(); + } + } +} + + +void FmXFormShell::impl_switchActiveControllerListening_Lock(const bool _bListen) +{ + if ( !m_xActiveController.is() ) + return; + + if ( _bListen ) + m_xActiveController->addEventListener( static_cast(this) ); + else + m_xActiveController->removeEventListener( static_cast(this) ); +} + + +void FmXFormShell::setActiveController_Lock(const Reference& xController, bool _bNoSaveOldContent) +{ + if (impl_checkDisposed_Lock()) + return; + + if (m_bChangingDesignMode) + return; + DBG_ASSERT(!m_pShell->IsDesignMode(), "only to be used in alive mode"); + + // if the routine has been called a second time, + // the focus should no longer be transferred + if (m_bInActivate) + { + m_bSetFocus = xController != m_xActiveController; + return; + } + + if (xController == m_xActiveController) + return; + + // switch all nav dispatchers belonging to the form of the current nav controller to 'non active' + Reference< XResultSet> xNavigationForm; + if (m_xNavigationController.is()) + xNavigationForm.set(m_xNavigationController->getModel(), UNO_QUERY); + + m_bInActivate = true; + + // check if the 2 controllers serve different forms + Reference< XResultSet> xOldForm; + if (m_xActiveController.is()) + xOldForm.set(m_xActiveController->getModel(), UNO_QUERY); + Reference< XResultSet> xNewForm; + if (xController.is()) + xNewForm = Reference< XResultSet>(xController->getModel(), UNO_QUERY); + xOldForm = getInternalForm_Lock(xOldForm); + xNewForm = getInternalForm_Lock(xNewForm); + + bool bDifferentForm = ( xOldForm.get() != xNewForm.get() ); + bool bNeedSave = bDifferentForm && !_bNoSaveOldContent; + // we save the content of the old form if we move to a new form, and saving old content is allowed + + if ( m_xActiveController.is() && bNeedSave ) + { + // save content on change of the controller; a commit has already been executed + if ( m_aActiveControllerFeatures->commitCurrentControl() ) + { + m_bSetFocus = true; + if ( m_aActiveControllerFeatures->isModifiedRow() ) + { + bool bIsNew = m_aActiveControllerFeatures->isInsertionRow(); + bool bResult = m_aActiveControllerFeatures->commitCurrentRecord(); + if ( !bResult && m_bSetFocus ) + { + // if we couldn't save the current record, set the focus back to the + // current control + Reference< XWindow > xWindow( m_xActiveController->getCurrentControl(), UNO_QUERY ); + if ( xWindow.is() ) + xWindow->setFocus(); + m_bInActivate = false; + return; + } + else if ( bResult && bIsNew ) + { + Reference< XResultSet > xCursor( m_aActiveControllerFeatures->getCursor() ); + if ( xCursor.is() ) + { + DO_SAFE( xCursor->last(); ); + } + } + } + } + } + + stopListening_Lock(); + + impl_switchActiveControllerListening_Lock(false); + + m_aActiveControllerFeatures.dispose(); + m_xActiveController = xController; + if ( m_xActiveController.is() ) + m_aActiveControllerFeatures.assign( m_xActiveController ); + + impl_switchActiveControllerListening_Lock(true); + + if ( m_xActiveController.is() ) + m_xActiveForm = getInternalForm_Lock(Reference(m_xActiveController->getModel(), UNO_QUERY)); + else + m_xActiveForm = nullptr; + + startListening_Lock(); + + // activate all dispatchers belonging to form of the new navigation controller + xNavigationForm = nullptr; + if (m_xNavigationController.is()) + xNavigationForm.set(m_xNavigationController->getModel(), UNO_QUERY); + + m_bInActivate = false; + + m_pShell->UIFeatureChanged(); + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().InvalidateShell(*m_pShell); + + InvalidateSlot_Lock(SID_FM_FILTER_NAVIGATOR_CONTROL, true); +} + + +void FmXFormShell::getCurrentSelection_Lock(InterfaceBag& /* [out] */ _rSelection) const +{ + _rSelection = m_aCurrentSelection; +} + + +bool FmXFormShell::setCurrentSelectionFromMark_Lock(const SdrMarkList& _rMarkList) +{ + m_aLastKnownMarkedControls.clear(); + + if ( ( _rMarkList.GetMarkCount() > 0 ) && isControlList( _rMarkList ) ) + collectInterfacesFromMarkList( _rMarkList, m_aLastKnownMarkedControls ); + + return setCurrentSelection_Lock(o3tl::sorted_vector(m_aLastKnownMarkedControls)); +} + + +bool FmXFormShell::selectLastMarkedControls_Lock() +{ + return setCurrentSelection_Lock(o3tl::sorted_vector(m_aLastKnownMarkedControls)); +} + + +bool FmXFormShell::setCurrentSelection_Lock( InterfaceBag&& _rSelection ) +{ + if (impl_checkDisposed_Lock()) + return false; + + DBG_ASSERT( m_pShell->IsDesignMode(), "FmXFormShell::setCurrentSelection: only to be used in design mode!" ); + + if ( _rSelection.empty() && m_aCurrentSelection.empty() ) + // nothing to do + return false; + + if ( _rSelection.size() == m_aCurrentSelection.size() ) + { + InterfaceBag::const_iterator aNew = _rSelection.begin(); + InterfaceBag::const_iterator aOld = m_aCurrentSelection.begin(); + for ( ; aNew != _rSelection.end(); ++aNew, ++aOld ) + { + OSL_ENSURE( Reference< XInterface >( *aNew, UNO_QUERY ).get() == aNew->get(), "FmXFormShell::setCurrentSelection: new interface not normalized!" ); + OSL_ENSURE( Reference< XInterface >( *aOld, UNO_QUERY ).get() == aOld->get(), "FmXFormShell::setCurrentSelection: old interface not normalized!" ); + + if ( aNew->get() != aOld->get() ) + break; + } + + if ( aNew == _rSelection.end() ) + // both bags equal + return false; + } + + // the following is some strange code to ensure that when you have two grid controls in a document, + // only one of them can have a selected column. + // TODO: this should happen elsewhere, but not here - shouldn't it? + if ( !m_aCurrentSelection.empty() ) + { + Reference< XChild > xCur; if ( m_aCurrentSelection.size() == 1 ) xCur.set(*m_aCurrentSelection.begin(), css::uno::UNO_QUERY); + Reference< XChild > xNew; if ( _rSelection.size() == 1 ) xNew.set(*_rSelection.begin(), css::uno::UNO_QUERY); + + // is there nothing to be selected, or the parents differ, and the parent of the current object + // is a selection supplier, then deselect + if ( xCur.is() && ( !xNew.is() || ( xCur->getParent() != xNew->getParent() ) ) ) + { + Reference< XSelectionSupplier > xSel( xCur->getParent(), UNO_QUERY ); + if ( xSel.is() ) + xSel->select( Any() ); + } + } + + m_aCurrentSelection = _rSelection; + + // determine the form which all the selected objects belong to, if any + Reference< XForm > xNewCurrentForm; + for (const auto& rpSelection : m_aCurrentSelection) + { + Reference< XForm > xThisRoundsForm( GetForm( rpSelection ) ); + OSL_ENSURE( xThisRoundsForm.is(), "FmXFormShell::setCurrentSelection: *everything* should belong to a form!" ); + + if ( !xNewCurrentForm.is() ) + { // the first form we encountered + xNewCurrentForm = xThisRoundsForm; + } + else if ( xNewCurrentForm != xThisRoundsForm ) + { // different forms -> no "current form" at all + xNewCurrentForm.clear(); + break; + } + } + + if ( !m_aCurrentSelection.empty() ) + impl_updateCurrentForm_Lock(xNewCurrentForm); + + // ensure some slots are updated + for (sal_Int16 i : SelObjectSlotMap) + InvalidateSlot_Lock(i, false); + + return true; +} + + +bool FmXFormShell::isSolelySelected_Lock(const Reference& _rxObject) +{ + return ( m_aCurrentSelection.size() == 1 ) && ( *m_aCurrentSelection.begin() == _rxObject ); +} + + +void FmXFormShell::forgetCurrentForm_Lock() +{ + if ( !m_xCurrentForm.is() ) + return; + + // reset ... + impl_updateCurrentForm_Lock(nullptr); + + // ... and try finding a new current form + // #i88186# / 2008-04-12 / frank.schoenheit@sun.com + impl_defaultCurrentForm_nothrow_Lock(); +} + + +void FmXFormShell::impl_updateCurrentForm_Lock(const Reference& _rxNewCurForm) +{ + if (impl_checkDisposed_Lock()) + return; + + m_xCurrentForm = _rxNewCurForm; + + // propagate to the FormPage(Impl) + FmFormPage* pPage = m_pShell->GetCurPage(); + if ( pPage ) + pPage->GetImpl().setCurForm( m_xCurrentForm ); + + // ensure the UI which depends on the current form is up-to-date + for (sal_Int16 i : DlgSlotMap) + InvalidateSlot_Lock(i, false); +} + + +void FmXFormShell::startListening_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + Reference< XRowSet> xDatabaseForm(m_xActiveForm, UNO_QUERY); + if (xDatabaseForm.is() && getConnection(xDatabaseForm).is()) + { + Reference< XPropertySet> xActiveFormSet(m_xActiveForm, UNO_QUERY); + if (xActiveFormSet.is()) + { + // if there is a data source, then build the listener + // TODO: this is strange - shouldn't this depend on a isLoaded instead of + // a "has command value"? Finally, the command value only means that it was + // intended to be loaded, not that it actually *is* loaded + OUString aSource = ::comphelper::getString(xActiveFormSet->getPropertyValue(FM_PROP_COMMAND)); + if (!aSource.isEmpty()) + { + m_bDatabaseBar = true; + + xActiveFormSet->getPropertyValue(FM_PROP_NAVIGATION) >>= m_eNavigate; + + switch (m_eNavigate) + { + case NavigationBarMode_PARENT: + { + // search for the controller via which navigation is possible + Reference< XChild> xChild = m_xActiveController; + Reference< runtime::XFormController > xParent; + while (xChild.is()) + { + xChild.set(xChild->getParent(), UNO_QUERY); + xParent.set(xChild, UNO_QUERY); + Reference< XPropertySet> xParentSet; + if (xParent.is()) + xParentSet.set(xParent->getModel(), UNO_QUERY); + if (xParentSet.is()) + { + xParentSet->getPropertyValue(FM_PROP_NAVIGATION) >>= m_eNavigate; + if (m_eNavigate == NavigationBarMode_CURRENT) + break; + } + } + m_xNavigationController = xParent; + } + break; + + case NavigationBarMode_CURRENT: + m_xNavigationController = m_xActiveController; + break; + + default: + m_xNavigationController = nullptr; + m_bDatabaseBar = false; + } + + m_aNavControllerFeatures.dispose(); + if ( m_xNavigationController.is() && ( m_xNavigationController != m_xActiveController ) ) + m_aNavControllerFeatures.assign( m_xNavigationController ); + + // because of RecordCount, listen at the controller which controls the navigation + Reference< XPropertySet> xNavigationSet; + if (m_xNavigationController.is()) + { + xNavigationSet.set(m_xNavigationController->getModel(), UNO_QUERY); + if (xNavigationSet.is()) + xNavigationSet->addPropertyChangeListener(FM_PROP_ROWCOUNT,this); + } + return; + } + } + } + + m_eNavigate = NavigationBarMode_NONE; + m_bDatabaseBar = false; + m_xNavigationController = nullptr; +} + + +void FmXFormShell::stopListening_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + Reference< XRowSet> xDatabaseForm(m_xActiveForm, UNO_QUERY); + if ( xDatabaseForm.is() ) + { + if (m_xNavigationController.is()) + { + Reference< XPropertySet> xSet(m_xNavigationController->getModel(), UNO_QUERY); + if (xSet.is()) + xSet->removePropertyChangeListener(FM_PROP_ROWCOUNT, this); + + } + } + + m_bDatabaseBar = false; + m_eNavigate = NavigationBarMode_NONE; + m_xNavigationController = nullptr; +} + + +void FmXFormShell::ShowSelectionProperties_Lock(bool bShow) +{ + if (impl_checkDisposed_Lock()) + return; + + // if the window is already visible, only update the state + bool bHasChild = m_pShell->GetViewShell()->GetViewFrame()->HasChildWindow( SID_FM_SHOW_PROPERTIES ); + if ( bHasChild && bShow ) + UpdateSlot_Lock(SID_FM_PROPERTY_CONTROL); + + // else toggle state + else + m_pShell->GetViewShell()->GetViewFrame()->ToggleChildWindow(SID_FM_SHOW_PROPERTIES); + + InvalidateSlot_Lock(SID_FM_PROPERTIES, false); + InvalidateSlot_Lock(SID_FM_CTL_PROPERTIES, false); +} + + +IMPL_LINK(FmXFormShell, OnFoundData_Lock, FmFoundRecordInformation&, rfriWhere, void) +{ + if (impl_checkDisposed_Lock()) + return; + + DBG_ASSERT((rfriWhere.nContext >= 0) && (o3tl::make_unsigned(rfriWhere.nContext) < m_aSearchForms.size()), + "FmXFormShell::OnFoundData : invalid context!"); + Reference< XForm> xForm( m_aSearchForms.at(rfriWhere.nContext)); + DBG_ASSERT(xForm.is(), "FmXFormShell::OnFoundData : invalid form!"); + + Reference< XRowLocate> xCursor(xForm, UNO_QUERY); + if (!xCursor.is()) + return; // what should I do there? + + // to the record + try + { + xCursor->moveToBookmark(rfriWhere.aPosition); + } + catch(const SQLException&) + { + OSL_FAIL("Can position on bookmark!"); + } + + LoopGrids_Lock(LoopGridsSync::FORCE_SYNC); + + // and to the field (for that, I collected the XVclComponent interfaces before the start of the search) + SAL_WARN_IF(o3tl::make_unsigned(rfriWhere.nFieldPos) >= + m_arrSearchedControls.size(), + "svx.form", "FmXFormShell::OnFoundData : invalid index!"); + SdrObject* pObject = m_arrSearchedControls.at(rfriWhere.nFieldPos); + + m_pShell->GetFormView()->UnMarkAll(m_pShell->GetFormView()->GetSdrPageView()); + m_pShell->GetFormView()->MarkObj(pObject, m_pShell->GetFormView()->GetSdrPageView()); + + FmFormObj* pFormObject = FmFormObj::GetFormObject( pObject ); + Reference< XControlModel > xControlModel( pFormObject ? pFormObject->GetUnoControlModel() : Reference< XControlModel >() ); + DBG_ASSERT( xControlModel.is(), "FmXFormShell::OnFoundData: invalid control!" ); + if ( !xControlModel.is() ) + return; + + // disable the permanent cursor for the last grid we found a record + if (m_xLastGridFound.is() && (m_xLastGridFound != xControlModel)) + { + Reference< XPropertySet> xOldSet(m_xLastGridFound, UNO_QUERY); + xOldSet->setPropertyValue(FM_PROP_ALWAYSSHOWCURSOR, Any( false ) ); + Reference< XPropertyState> xOldSetState(xOldSet, UNO_QUERY); + if (xOldSetState.is()) + xOldSetState->setPropertyToDefault(FM_PROP_CURSORCOLOR); + else + xOldSet->setPropertyValue(FM_PROP_CURSORCOLOR, Any()); + } + + // if the field is in a GridControl, I have to additionally go into the corresponding column there + sal_Int32 nGridColumn = m_arrRelativeGridColumn[rfriWhere.nFieldPos]; + if (nGridColumn != -1) + { // unfortunately, I have to first get the control again + Reference xControl(pFormObject ? impl_getControl_Lock(xControlModel, *pFormObject) : Reference()); + Reference< XGrid> xGrid(xControl, UNO_QUERY); + DBG_ASSERT(xGrid.is(), "FmXFormShell::OnFoundData : invalid control!"); + // if one of the asserts fires, I probably did something wrong on building of m_arrSearchedControls + + // enable a permanent cursor for the grid so we can see the found text + Reference< XPropertySet> xModelSet(xControlModel, UNO_QUERY); + DBG_ASSERT(xModelSet.is(), "FmXFormShell::OnFoundData : invalid control model (no property set) !"); + xModelSet->setPropertyValue( FM_PROP_ALWAYSSHOWCURSOR, Any( true ) ); + xModelSet->setPropertyValue( FM_PROP_CURSORCOLOR, Any( COL_LIGHTRED ) ); + m_xLastGridFound = xControlModel; + + if ( xGrid.is() ) + xGrid->setCurrentColumnPosition(static_cast(nGridColumn)); + } + + // As the cursor has been repositioned, I have (in positioned) invalidated + // my form bar slots. But that does not take effect here unfortunately, as + // generally the (modal) search dialog is of course at the top ... So, force ... + sal_uInt16 nPos = 0; + while (DatabaseSlotMap[nPos]) + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().Update(DatabaseSlotMap[nPos++]); + // unfortunately the update goes against the invalidate with only individual slots +} + + +IMPL_LINK(FmXFormShell, OnCanceledNotFound_Lock, FmFoundRecordInformation&, rfriWhere, void) +{ + if (impl_checkDisposed_Lock()) + return; + + DBG_ASSERT((rfriWhere.nContext >= 0) && (o3tl::make_unsigned(rfriWhere.nContext) < m_aSearchForms.size()), + "FmXFormShell::OnCanceledNotFound : invalid context!"); + Reference< XForm> xForm( m_aSearchForms.at(rfriWhere.nContext)); + DBG_ASSERT(xForm.is(), "FmXFormShell::OnCanceledNotFound : invalid form!"); + + Reference< XRowLocate> xCursor(xForm, UNO_QUERY); + if (!xCursor.is()) + return; // what should I do there? + + // to the record + try + { + xCursor->moveToBookmark(rfriWhere.aPosition); + } + catch(const SQLException&) + { + OSL_FAIL("Can position on bookmark!"); + } + + + m_pShell->GetFormView()->UnMarkAll(m_pShell->GetFormView()->GetSdrPageView()); +} + + +IMPL_LINK(FmXFormShell, OnSearchContextRequest_Lock, FmSearchContext&, rfmscContextInfo, sal_uInt32) +{ + if (impl_checkDisposed_Lock()) + return 0; + + DBG_ASSERT(rfmscContextInfo.nContext < static_cast(m_aSearchForms.size()), "FmXFormShell::OnSearchContextRequest : invalid parameter !"); + Reference< XForm> xForm( m_aSearchForms.at(rfmscContextInfo.nContext)); + DBG_ASSERT(xForm.is(), "FmXFormShell::OnSearchContextRequest : unexpected : invalid context !"); + + Reference< XResultSet> xIter(xForm, UNO_QUERY); + DBG_ASSERT(xIter.is(), "FmXFormShell::OnSearchContextRequest : unexpected : context has no iterator !"); + + + // assemble the list of fields to involve (that is, the ControlSources of all fields that have such a property) + OUString strFieldList, sFieldDisplayNames; + m_arrSearchedControls.clear(); + m_arrRelativeGridColumn.clear(); + + // small problem: To mark found fields, I need SdrObjects. To determine which controls + // to include in the search, I need Controls (that is, XControl interfaces). So I have + // to iterate over one of them and get the other in some way. Unfortunately, there is + // no direct connection between the two worlds (except from a GetUnoControl to a + // SdrUnoObject, but this requires an OutputDevice I can not do anything with. + // However I can get to the Model from the Control and also from the SdrObject, and in + // this way the assignment SdrObject<->Control is possible with a double loop. + // The alternative to this (ugly but certainly not entirely fixable) solution would be + // to renounce the caching of the SdrObjects, which would lead to significant extra + // work in OnFoundData (since there I'd have to get the SdrObject first thing every + // time). But since OnFoundData is usually called more often than ExecuteSearch, I'll + // do that here. + + Reference< XNameAccess> xValidFormFields; + Reference< XColumnsSupplier> xSupplyCols(xIter, UNO_QUERY); + DBG_ASSERT(xSupplyCols.is(), "FmXFormShell::OnSearchContextRequest : invalid cursor : no columns supplier !"); + if (xSupplyCols.is()) + xValidFormFields = xSupplyCols->getColumns(); + DBG_ASSERT(xValidFormFields.is(), "FmXFormShell::OnSearchContextRequest : form has no fields !"); + + // current Page/Controller + FmFormPage* pCurrentPage = m_pShell->GetCurPage(); + assert(pCurrentPage && "FmXFormShell::OnSearchContextRequest : no page !"); + // Search all SdrControls of this page... + OUString sControlSource, aName; + + SdrObjListIter aPageIter( pCurrentPage ); + while ( aPageIter.IsMore() ) + { + SdrObject* pCurrent = aPageIter.Next(); + FmFormObj* pFormObject = FmFormObj::GetFormObject( pCurrent ); + // note that in case pCurrent is a virtual object, pFormObject points to the referenced object + + if ( !pFormObject ) + continue; + + // the current object's model, in different tastes + Reference< XControlModel> xControlModel( pFormObject->GetUnoControlModel() ); + Reference< XFormComponent > xCurrentFormComponent( xControlModel, UNO_QUERY ); + DBG_ASSERT( xCurrentFormComponent.is(), "FmXFormShell::OnSearchContextRequest: invalid objects!" ); + if ( !xCurrentFormComponent.is() ) + continue; + + // does the component belong to the form which we're interested in? + if ( xCurrentFormComponent->getParent() != xForm ) + continue; + + // ... ask for the ControlSource property + SearchableControlIterator iter( xCurrentFormComponent ); + Reference< XControl> xControl; + // the control that has model xControlModel + // (the following while can be passed through several times, without the Control + // being modified, so I don't have to search every time from scratch) + + Reference< XInterface > xSearchable( iter.Next() ); + while ( xSearchable.is() ) + { + sControlSource = iter.getCurrentValue(); + if ( sControlSource.isEmpty() ) + { + // the current element has no ControlSource, so it is a GridControl (that + // is the only thing that still permits the SearchableControlIteratore) + xControl = impl_getControl_Lock(xControlModel, *pFormObject); + DBG_ASSERT(xControl.is(), "FmXFormShell::OnSearchContextRequest : didn't ::std::find a control with requested model !"); + + Reference< XGridPeer> xGridPeer; + if ( xControl.is() ) + xGridPeer.set( xControl->getPeer(), UNO_QUERY ); + do + { + if (!xGridPeer.is()) + break; + + Reference< XIndexAccess> xPeerContainer(xGridPeer, UNO_QUERY); + if (!xPeerContainer.is()) + break; + + Reference< XIndexAccess> xModelColumns = xGridPeer->getColumns(); + DBG_ASSERT(xModelColumns.is(), "FmXFormShell::OnSearchContextRequest : there is a grid control without columns !"); + // the case 'no columns' should be indicated with an empty container, I think ... + DBG_ASSERT(xModelColumns->getCount() >= xPeerContainer->getCount(), "FmXFormShell::OnSearchContextRequest : impossible : have more view than model columns !"); + + Reference< XInterface> xCurrentColumn; + for (sal_Int32 nViewPos=0; nViewPosgetCount(); ++nViewPos) + { + xPeerContainer->getByIndex(nViewPos) >>= xCurrentColumn; + if (!xCurrentColumn.is()) + continue; + + // can we use this column control for searching ? + if (!IsSearchableControl(xCurrentColumn)) + continue; + + sal_Int32 nModelPos = GridView2ModelPos(xModelColumns, nViewPos); + Reference< XPropertySet> xCurrentColModel; + xModelColumns->getByIndex(nModelPos) >>= xCurrentColModel; + aName = ::comphelper::getString(xCurrentColModel->getPropertyValue(FM_PROP_CONTROLSOURCE)); + // the cursor has a field matching the control source ? + if (xValidFormFields->hasByName(aName)) + { + strFieldList += aName + ";"; + + sFieldDisplayNames += + ::comphelper::getString(xCurrentColModel->getPropertyValue(FM_PROP_LABEL)) + + ";"; + + rfmscContextInfo.arrFields.push_back(xCurrentColumn); + + // and the SdrOject to the Field + m_arrSearchedControls.push_back(pCurrent); + // the number of the column + m_arrRelativeGridColumn.push_back(nViewPos); + } + } + } while (false); + } + else + { + if (!sControlSource.isEmpty() && xValidFormFields->hasByName(sControlSource)) + { + // now I need the Control to SdrObject + if (!xControl.is()) + { + xControl = impl_getControl_Lock(xControlModel, *pFormObject); + DBG_ASSERT(xControl.is(), "FmXFormShell::OnSearchContextRequest : didn't ::std::find a control with requested model !"); + } + + if (IsSearchableControl(xControl)) + { + // all tests passed -> take along in the list + strFieldList += sControlSource + ";"; + + // the label which should appear for the control : + sFieldDisplayNames += + getLabelName(Reference< XPropertySet>(xControlModel, UNO_QUERY)) + + ";"; + + // mark the SdrObject (accelerates the treatment in OnFoundData) + m_arrSearchedControls.push_back(pCurrent); + + // the number of the column (here a dummy, since it is only interesting for GridControls) + m_arrRelativeGridColumn.push_back(-1); + + // and for the formatted search... + rfmscContextInfo.arrFields.emplace_back( xControl, UNO_QUERY ); + } + } + } + + xSearchable = iter.Next(); + } + } + + strFieldList = comphelper::string::stripEnd(strFieldList, ';'); + sFieldDisplayNames = comphelper::string::stripEnd(sFieldDisplayNames, ';'); + + if (rfmscContextInfo.arrFields.empty()) + { + rfmscContextInfo.arrFields.clear(); + rfmscContextInfo.xCursor = nullptr; + rfmscContextInfo.strUsedFields.clear(); + return 0; + } + + rfmscContextInfo.xCursor = xIter; + rfmscContextInfo.strUsedFields = strFieldList; + rfmscContextInfo.sFieldDisplayNames = sFieldDisplayNames; + + // 66463 - 31.05.99 - FS + // when the cursor is a non-STANDARD RecordMode, set it back + Reference< XPropertySet> xCursorSet(rfmscContextInfo.xCursor, UNO_QUERY); + Reference< XResultSetUpdate> xUpdateCursor(rfmscContextInfo.xCursor, UNO_QUERY); + if (xUpdateCursor.is() && xCursorSet.is()) + { + if (::comphelper::getBOOL(xCursorSet->getPropertyValue(FM_PROP_ISNEW))) + xUpdateCursor->moveToCurrentRow(); + else if (::comphelper::getBOOL(xCursorSet->getPropertyValue(FM_PROP_ISMODIFIED))) + xUpdateCursor->cancelRowUpdates(); + } + + return rfmscContextInfo.arrFields.size(); +} + + // XContainerListener + +void SAL_CALL FmXFormShell::elementInserted(const ContainerEvent& evt) +{ + SolarMutexGuard g; + + if (impl_checkDisposed_Lock()) + return; + + // new object to listen to + Reference< XInterface> xTemp; + evt.Element >>= xTemp; + AddElement_Lock(xTemp); + + m_pShell->DetermineForms(true); +} + + +void SAL_CALL FmXFormShell::elementReplaced(const ContainerEvent& evt) +{ + SolarMutexGuard g; + + if (impl_checkDisposed_Lock() ) + return; + + Reference< XInterface> xTemp; + evt.ReplacedElement >>= xTemp; + RemoveElement_Lock(xTemp); + evt.Element >>= xTemp; + AddElement_Lock(xTemp); +} + + +void SAL_CALL FmXFormShell::elementRemoved(const ContainerEvent& evt) +{ + SolarMutexGuard g; + + if (impl_checkDisposed_Lock()) + return; + + Reference< XInterface> xTemp; + evt.Element >>= xTemp; + RemoveElement_Lock(xTemp); + + m_pShell->DetermineForms(true); +} + + +void FmXFormShell::UpdateForms_Lock(bool _bInvalidate) +{ + if (impl_checkDisposed_Lock()) + return; + + Reference< XIndexAccess > xForms; + + FmFormPage* pPage = m_pShell->GetCurPage(); + if ( pPage && m_pShell->m_bDesignMode ) + xForms = pPage->GetForms( false ); + + if ( m_xForms != xForms ) + { + RemoveElement_Lock( m_xForms ); + m_xForms = xForms; + AddElement_Lock(m_xForms); + } + + SolarMutexGuard g; + m_pShell->DetermineForms( _bInvalidate ); +} + + +void FmXFormShell::AddElement_Lock(const Reference& _xElement) +{ + if (impl_checkDisposed_Lock()) + return; + impl_AddElement_nothrow(_xElement); +} + +void FmXFormShell::impl_AddElement_nothrow(const Reference< XInterface>& Element) +{ + // listen at the container + const Reference< XIndexContainer> xContainer(Element, UNO_QUERY); + if (xContainer.is()) + { + const sal_uInt32 nCount = xContainer->getCount(); + Reference< XInterface> xElement; + for (sal_uInt32 i = 0; i < nCount; ++i) + { + xElement.set(xContainer->getByIndex(i),UNO_QUERY); + impl_AddElement_nothrow(xElement); + } + + const Reference< XContainer> xCont(Element, UNO_QUERY); + if (xCont.is()) + xCont->addContainerListener(this); + } + + const Reference< css::view::XSelectionSupplier> xSelSupplier(Element, UNO_QUERY); + if (xSelSupplier.is()) + xSelSupplier->addSelectionChangeListener(this); +} + + +void FmXFormShell::RemoveElement_Lock(const Reference& Element) +{ + if (impl_checkDisposed_Lock()) + return; + impl_RemoveElement_nothrow_Lock(Element); +} + +void FmXFormShell::impl_RemoveElement_nothrow_Lock(const Reference& Element) +{ + const Reference< css::view::XSelectionSupplier> xSelSupplier(Element, UNO_QUERY); + if (xSelSupplier.is()) + xSelSupplier->removeSelectionChangeListener(this); + + // remove connection to children + const Reference< XIndexContainer> xContainer(Element, UNO_QUERY); + if (xContainer.is()) + { + const Reference< XContainer> xCont(Element, UNO_QUERY); + if (xCont.is()) + xCont->removeContainerListener(this); + + const sal_uInt32 nCount = xContainer->getCount(); + Reference< XInterface> xElement; + for (sal_uInt32 i = 0; i < nCount; i++) + { + xElement.set(xContainer->getByIndex(i),UNO_QUERY); + impl_RemoveElement_nothrow_Lock(xElement); + } + } + + auto wasSelectedPos = m_aCurrentSelection.find( Element ); + if ( wasSelectedPos != m_aCurrentSelection.end() ) + m_aCurrentSelection.erase( wasSelectedPos ); +} + + +void SAL_CALL FmXFormShell::selectionChanged(const lang::EventObject& rEvent) +{ + SolarMutexGuard g; + + if (impl_checkDisposed_Lock()) + return; + + Reference< XSelectionSupplier > xSupplier( rEvent.Source, UNO_QUERY ); + Reference< XInterface > xSelObj( xSupplier->getSelection(), UNO_QUERY ); + // a selection was removed, this can only be done by the shell + if ( !xSelObj.is() ) + return; + + EnableTrackProperties_Lock(false); + + bool bMarkChanged = m_pShell->GetFormView()->checkUnMarkAll(rEvent.Source); + + InterfaceBag aNewSelection; + aNewSelection.insert( Reference( xSelObj, UNO_QUERY ) ); + + if (setCurrentSelection_Lock(std::move(aNewSelection)) && IsPropBrwOpen_Lock()) + ShowSelectionProperties_Lock(true); + + EnableTrackProperties_Lock(true); + + if ( bMarkChanged ) + m_pShell->NotifyMarkListChanged( m_pShell->GetFormView() ); +} + + +IMPL_LINK_NOARG(FmXFormShell, OnTimeOut_Lock, Timer*, void) +{ + if (impl_checkDisposed_Lock()) + return; + + if (m_pShell->IsDesignMode() && m_pShell->GetFormView()) + SetSelection_Lock(m_pShell->GetFormView()->GetMarkedObjectList()); +} + + +void FmXFormShell::SetSelectionDelayed_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + if (m_pShell->IsDesignMode() && IsTrackPropertiesEnabled_Lock() && !m_aMarkTimer.IsActive()) + m_aMarkTimer.Start(); +} + + +void FmXFormShell::SetSelection_Lock(const SdrMarkList& rMarkList) +{ + if (impl_checkDisposed_Lock()) + return; + + DetermineSelection_Lock(rMarkList); + m_pShell->NotifyMarkListChanged(m_pShell->GetFormView()); +} + + +void FmXFormShell::DetermineSelection_Lock(const SdrMarkList& rMarkList) +{ + if (setCurrentSelectionFromMark_Lock(rMarkList) && IsPropBrwOpen_Lock()) + ShowSelectionProperties_Lock(true); +} + + +bool FmXFormShell::IsPropBrwOpen_Lock() const +{ + if (impl_checkDisposed_Lock()) + return false; + + return m_pShell->GetViewShell() && m_pShell->GetViewShell()->GetViewFrame() + && m_pShell->GetViewShell()->GetViewFrame()->HasChildWindow(SID_FM_SHOW_PROPERTIES); +} + + +class FmXFormShell::SuspendPropertyTracking +{ +private: + FmXFormShell& m_rShell; + bool m_bEnabled; + +public: + explicit SuspendPropertyTracking( FmXFormShell& _rShell ) + :m_rShell( _rShell ) + ,m_bEnabled( false ) + { + if (m_rShell.IsTrackPropertiesEnabled_Lock()) + { + m_rShell.EnableTrackProperties_Lock(false); + m_bEnabled = true; + } + } + + ~SuspendPropertyTracking( ) + { + if ( m_bEnabled ) // note that ( false != m_bEnabled ) implies ( NULL != m_pShell ) + m_rShell.EnableTrackProperties_Lock(true); + } +}; + + +void FmXFormShell::SetDesignMode_Lock(bool bDesign) +{ + if (impl_checkDisposed_Lock()) + return; + + DBG_ASSERT(m_pShell->GetFormView(), "FmXFormShell::SetDesignMode : invalid call (have no shell or no view) !"); + m_bChangingDesignMode = true; + + // 67506 - 15.07.99 - FS + // if we're switching off the design mode we have to force the property browser to be closed + // so it can commit it's changes _before_ we load the forms + if (!bDesign) + { + m_bHadPropertyBrowserInDesignMode = m_pShell->GetViewShell()->GetViewFrame()->HasChildWindow(SID_FM_SHOW_PROPERTIES); + if (m_bHadPropertyBrowserInDesignMode) + m_pShell->GetViewShell()->GetViewFrame()->ToggleChildWindow(SID_FM_SHOW_PROPERTIES); + } + + FmFormView* pFormView = m_pShell->GetFormView(); + if (bDesign) + { + // we are currently filtering, so stop filtering + if (m_bFilterMode) + stopFiltering_Lock(false); + + // unsubscribe from the objects of my MarkList + pFormView->GetImpl()->stopMarkListWatching(); + } + else + { + m_aMarkTimer.Stop(); + + SuspendPropertyTracking aSuspend( *this ); + pFormView->GetImpl()->saveMarkList(); + } + + if (bDesign && m_xExternalViewController.is()) + CloseExternalFormViewer_Lock(); + + pFormView->ChangeDesignMode(bDesign); + + // notify listeners + FmDesignModeChangedHint aChangedHint( bDesign ); + m_pShell->Broadcast(aChangedHint); + + m_pShell->m_bDesignMode = bDesign; + UpdateForms_Lock(false); + + m_pTextShell->designModeChanged(); + + if (bDesign) + { + SdrMarkList aList; + { + // during changing the mark list, don't track the selected objects in the property browser + SuspendPropertyTracking aSuspend( *this ); + // restore the marks + pFormView->GetImpl()->restoreMarkList( aList ); + } + + // synchronize with the restored mark list + if ( aList.GetMarkCount() ) + SetSelection_Lock(aList); + } + else + { + // subscribe to the model of the view (so that I'm informed when someone deletes + // during the alive mode controls that I had saved in the saveMarklist (60343) + pFormView->GetImpl()->startMarkListWatching(); + } + + m_pShell->UIFeatureChanged(); + + // 67506 - 15.07.99 - FS + if (bDesign && m_bHadPropertyBrowserInDesignMode) + { + // The UIFeatureChanged performs an update (a check of the available features) asynchronously. + // So we can't call ShowSelectionProperties directly as the according feature isn't enabled yet. + // That's why we use an asynchron execution on the dispatcher. + // (And that's why this has to be done AFTER the UIFeatureChanged.) + m_pShell->GetViewShell()->GetViewFrame()->GetDispatcher()->Execute( SID_FM_SHOW_PROPERTY_BROWSER, SfxCallMode::ASYNCHRON ); + } + m_bChangingDesignMode = false; +} + + +Reference< XControl> FmXFormShell::impl_getControl_Lock(const Reference& i_rxModel, const FmFormObj& i_rKnownFormObj) +{ + if (impl_checkDisposed_Lock()) + return nullptr; + + Reference< XControl > xControl; + try + { + Reference< XControlContainer> xControlContainer(getControlContainerForView_Lock(), UNO_SET_THROW); + + const Sequence< Reference< XControl > > seqControls( xControlContainer->getControls() ); + // ... that I can then search + for (Reference< XControl > const & control : seqControls) + { + xControl.set( control, UNO_SET_THROW ); + Reference< XControlModel > xCurrentModel( xControl->getModel() ); + if ( xCurrentModel == i_rxModel ) + break; + xControl.clear(); + } + + if ( !xControl.is() ) + { + // fallback (some controls might not have been created, yet, since they were never visible so far) + Reference< XControl > xContainerControl( xControlContainer, UNO_QUERY_THROW ); + const vcl::Window* pContainerWindow = VCLUnoHelper::GetWindow( xContainerControl->getPeer() ); + ENSURE_OR_THROW( pContainerWindow, "unexpected control container implementation" ); + + const SdrView* pSdrView = m_pShell ? m_pShell->GetFormView() : nullptr; + ENSURE_OR_THROW( pSdrView, "no current view" ); + + xControl.set( i_rKnownFormObj.GetUnoControl( *pSdrView, *pContainerWindow->GetOutDev() ), UNO_SET_THROW ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + OSL_ENSURE( xControl.is(), "FmXFormShell::impl_getControl: no control found!" ); + return xControl; +} + +// note: _out_rForms is a member so needs lock +void FmXFormShell::impl_collectFormSearchContexts_nothrow_Lock( const Reference& _rxStartingPoint, + std::u16string_view _rCurrentLevelPrefix, FmFormArray& _out_rForms, ::std::vector< OUString >& _out_rNames ) +{ + try + { + Reference< XIndexAccess> xContainer( _rxStartingPoint, UNO_QUERY ); + if ( !xContainer.is() ) + return; + + sal_Int32 nCount( xContainer->getCount() ); + if ( nCount == 0 ) + return; + + OUString sCurrentFormName; + OUStringBuffer aNextLevelPrefix; + for ( sal_Int32 i=0; i xCurrentAsForm( xContainer->getByIndex(i), UNO_QUERY ); + if ( !xCurrentAsForm.is() ) + continue; + + Reference< XNamed > xNamed( xCurrentAsForm, UNO_QUERY_THROW ); + sCurrentFormName = xNamed->getName(); + + // the name of the current form + OUString sCompleteCurrentName( sCurrentFormName ); + if ( !_rCurrentLevelPrefix.empty() ) + { + sCompleteCurrentName += OUString::Concat(" (") + _rCurrentLevelPrefix + ")"; + } + + // the prefix for the next level + aNextLevelPrefix = _rCurrentLevelPrefix; + if ( !_rCurrentLevelPrefix.empty() ) + aNextLevelPrefix.append( '/' ); + aNextLevelPrefix.append( sCurrentFormName ); + + // remember both the form and its "display name" + _out_rForms.push_back( xCurrentAsForm ); + _out_rNames.push_back( sCompleteCurrentName ); + + // and descend + impl_collectFormSearchContexts_nothrow_Lock( + xCurrentAsForm, aNextLevelPrefix, + _out_rForms, _out_rNames); + aNextLevelPrefix.setLength(0); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +void FmXFormShell::startFiltering_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + // setting all forms in filter mode + FmXFormView* pXView = m_pShell->GetFormView()->GetImpl(); + + // if the active controller is our external one we have to use the trigger controller + Reference< XControlContainer> xContainer; + if (getActiveController_Lock() == m_xExternalViewController) + { + DBG_ASSERT(m_xExtViewTriggerController.is(), "FmXFormShell::startFiltering : inconsistent : active external controller, but no one triggered this !"); + xContainer = m_xExtViewTriggerController->getContainer(); + } + else + xContainer = getActiveController_Lock()->getContainer(); + + rtl::Reference< FormViewPageWindowAdapter > pAdapter = pXView->findWindow( xContainer ); + if ( pAdapter.is() ) + { + const ::std::vector< Reference< runtime::XFormController> >& rControllerList = pAdapter->GetList(); + for (const auto& rpController : rControllerList) + { + Reference< XModeSelector> xModeSelector(rpController, UNO_QUERY); + if (xModeSelector.is()) + xModeSelector->setMode( "FilterMode" ); + } + } + + m_bFilterMode = true; + + m_pShell->UIFeatureChanged(); + SfxViewFrame* pViewFrame = m_pShell->GetViewShell()->GetViewFrame(); + pViewFrame->GetBindings().InvalidateShell( *m_pShell ); + + if ( pViewFrame->KnowsChildWindow( SID_FM_FILTER_NAVIGATOR ) + && !pViewFrame->HasChildWindow( SID_FM_FILTER_NAVIGATOR ) + ) + { + pViewFrame->ToggleChildWindow( SID_FM_FILTER_NAVIGATOR ); + } +} + + +static void saveFilter(const Reference< runtime::XFormController >& _rxController) +{ + Reference< XPropertySet> xFormAsSet(_rxController->getModel(), UNO_QUERY); + Reference< XPropertySet> xControllerAsSet(_rxController, UNO_QUERY); + + // call the subcontroller + Reference< runtime::XFormController > xController; + for (sal_Int32 i = 0, nCount = _rxController->getCount(); i < nCount; ++i) + { + _rxController->getByIndex(i) >>= xController; + saveFilter(xController); + } + + try + { + + xFormAsSet->setPropertyValue(FM_PROP_FILTER, xControllerAsSet->getPropertyValue(FM_PROP_FILTER)); + xFormAsSet->setPropertyValue(FM_PROP_APPLYFILTER, Any( true ) ); + } + catch (const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + +} + + +void FmXFormShell::stopFiltering_Lock(bool bSave) +{ + if (impl_checkDisposed_Lock()) + return; + + m_bFilterMode = false; + + FmXFormView* pXView = m_pShell->GetFormView()->GetImpl(); + + // if the active controller is our external one we have to use the trigger controller + Reference< XControlContainer> xContainer; + if (getActiveController_Lock() == m_xExternalViewController) + { + DBG_ASSERT(m_xExtViewTriggerController.is(), "FmXFormShell::stopFiltering : inconsistent : active external controller, but no one triggered this !"); + xContainer = m_xExtViewTriggerController->getContainer(); + } + else + xContainer = getActiveController_Lock()->getContainer(); + + rtl::Reference< FormViewPageWindowAdapter > pAdapter = pXView->findWindow(xContainer); + if ( pAdapter.is() ) + { + const ::std::vector< Reference< runtime::XFormController > >& rControllerList = pAdapter->GetList(); + ::std::vector < OUString > aOriginalFilters; + ::std::vector < bool > aOriginalApplyFlags; + + if (bSave) + { + for (const auto& rpController : rControllerList) + { + // remember the current filter settings in case we're going to reload the forms below (which may fail) + try + { + Reference< XPropertySet > xFormAsSet(rpController->getModel(), UNO_QUERY); + aOriginalFilters.push_back(::comphelper::getString(xFormAsSet->getPropertyValue(FM_PROP_FILTER))); + aOriginalApplyFlags.push_back(::comphelper::getBOOL(xFormAsSet->getPropertyValue(FM_PROP_APPLYFILTER))); + } + catch(Exception&) + { + OSL_FAIL("FmXFormShell::stopFiltering : could not get the original filter !"); + // put dummies into the arrays so the they have the right size + + if (aOriginalFilters.size() == aOriginalApplyFlags.size()) + // the first getPropertyValue failed -> use two dummies + aOriginalFilters.emplace_back( ); + aOriginalApplyFlags.push_back( false ); + } + saveFilter(rpController); + } + } + for (const auto& rController : rControllerList) + { + + Reference< XModeSelector> xModeSelector(rController, UNO_QUERY); + if (xModeSelector.is()) + xModeSelector->setMode( "DataMode" ); + } + if (bSave) // execute the filter + { + const ::std::vector< Reference< runtime::XFormController > > & rControllers = pAdapter->GetList(); + for (::std::vector< Reference< runtime::XFormController > > ::const_iterator j = rControllers.begin(); + j != rControllers.end(); ++j) + { + Reference< XLoadable> xReload((*j)->getModel(), UNO_QUERY); + if (!xReload.is()) + continue; + Reference< XPropertySet > xFormSet(xReload, UNO_QUERY); + + try + { + xReload->reload(); + } + catch(Exception&) + { + TOOLS_WARN_EXCEPTION("svx.form", ""); + } + + if (!isRowSetAlive(xFormSet)) + { // something went wrong -> restore the original state + OUString sOriginalFilter = aOriginalFilters[ j - rControllers.begin() ]; + bool bOriginalApplyFlag = aOriginalApplyFlags[ j - rControllers.begin() ]; + try + { + xFormSet->setPropertyValue(FM_PROP_FILTER, Any(sOriginalFilter)); + xFormSet->setPropertyValue(FM_PROP_APPLYFILTER, Any(bOriginalApplyFlag)); + xReload->reload(); + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + } + } + } + + m_pShell->UIFeatureChanged(); + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().InvalidateShell(*m_pShell); +} + + +void FmXFormShell::CreateExternalView_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + DBG_ASSERT(m_xAttachedFrame.is(), "FmXFormShell::CreateExternalView : no frame !"); + + // the frame the external view is displayed in + bool bAlreadyExistent = m_xExternalViewController.is(); + Reference< css::frame::XFrame> xExternalViewFrame; + + Reference xCurrentNavController(getNavController_Lock()); + // the creation of the "partwindow" may cause a deactivate of the document which will result in our nav controller to be set to NULL + + // _first_ check if we have any valid fields we can use for the grid view + // FS - 21.10.99 - 69219 + { + FmXBoundFormFieldIterator aModelIterator(xCurrentNavController->getModel()); + bool bHaveUsableControls = false; + for (;;) + { + Reference< XPropertySet> xCurrentModelSet(aModelIterator.Next(), UNO_QUERY); + if (!xCurrentModelSet.is()) + break; + // the FmXBoundFormFieldIterator only supplies controls with a valid control source + // so we just have to check the field type + sal_Int16 nClassId = ::comphelper::getINT16(xCurrentModelSet->getPropertyValue(FM_PROP_CLASSID)); + switch (nClassId) + { + case FormComponentType::IMAGECONTROL: + case FormComponentType::CONTROL: + continue; + } + bHaveUsableControls = true; + break; + } + + if (!bHaveUsableControls) + { + std::unique_ptr xBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Warning, VclButtonsType::Ok, + SvxResId(RID_STR_NOCONTROLS_FOR_EXTERNALDISPLAY))); + xBox->run(); + return; + } + } + + // load the component for external form views + if (!bAlreadyExistent) + { + OUString sFrameName("_beamer"); + URL aWantToDispatch; + aWantToDispatch.Complete = FMURL_COMPONENT_FORMGRIDVIEW; + + Reference< css::frame::XDispatchProvider> xProv(m_xAttachedFrame, UNO_QUERY); + Reference< css::frame::XDispatch> xDisp; + if (xProv.is()) + xDisp = xProv->queryDispatch(aWantToDispatch, sFrameName, + css::frame::FrameSearchFlag::CHILDREN | css::frame::FrameSearchFlag::CREATE); + if (xDisp.is()) + { + xDisp->dispatch(aWantToDispatch, Sequence< PropertyValue>()); + } + + // with this the component should be loaded, now search the frame where it resides in + xExternalViewFrame = m_xAttachedFrame->findFrame(sFrameName, css::frame::FrameSearchFlag::CHILDREN); + if (xExternalViewFrame.is()) + { + m_xExternalViewController = xExternalViewFrame->getController(); + if (m_xExternalViewController.is()) + m_xExternalViewController->addEventListener(static_cast(static_cast(this))); + } + } + else + { + xExternalViewFrame = m_xExternalViewController->getFrame(); + Reference< css::frame::XDispatchProvider> xCommLink(xExternalViewFrame, UNO_QUERY); + + // if we display the active form we interpret the slot as "remove it" + Reference< XForm> xCurrentModel(xCurrentNavController->getModel(), UNO_QUERY); + if ((xCurrentModel == m_xExternalDisplayedForm) || (getInternalForm_Lock(xCurrentModel) == m_xExternalDisplayedForm)) + { + if (m_xExternalViewController == getActiveController_Lock()) + { + Reference< runtime::XFormController > xAsFormController( m_xExternalViewController, UNO_QUERY ); + ControllerFeatures aHelper( xAsFormController ); + (void)aHelper->commitCurrentControl(); + } + + Reference< runtime::XFormController > xNewController(m_xExtViewTriggerController); + CloseExternalFormViewer_Lock(); + setActiveController_Lock(xNewController); + return; + } + + URL aClearURL; + aClearURL.Complete = FMURL_GRIDVIEW_CLEARVIEW; + + Reference< css::frame::XDispatch> xClear( xCommLink->queryDispatch(aClearURL, OUString(), 0)); + if (xClear.is()) + xClear->dispatch(aClearURL, Sequence< PropertyValue>()); + } + + // TODO: We need an interceptor at the xSupplier, which forwards all queryDispatch requests to the FormController + // instance for which this "external view" was triggered + + // get the dispatch interface of the frame so we can communicate (interceptable) with the controller + Reference< css::frame::XDispatchProvider> xCommLink(xExternalViewFrame, UNO_QUERY); + + if (m_xExternalViewController.is()) + { + DBG_ASSERT(xCommLink.is(), "FmXFormShell::CreateExternalView : the component doesn't have the necessary interfaces !"); + // collect the dispatchers we will need + URL aAddColumnURL; + aAddColumnURL.Complete = FMURL_GRIDVIEW_ADDCOLUMN; + Reference< css::frame::XDispatch> xAddColumnDispatch( xCommLink->queryDispatch(aAddColumnURL, OUString(), 0)); + URL aAttachURL; + aAttachURL.Complete = FMURL_GRIDVIEW_ATTACHTOFORM; + Reference< css::frame::XDispatch> xAttachDispatch( xCommLink->queryDispatch(aAttachURL, OUString(), 0)); + + if (xAddColumnDispatch.is() && xAttachDispatch.is()) + { + DBG_ASSERT(xCurrentNavController.is(), "FmXFormShell::CreateExternalView : invalid call : have no nav controller !"); + // first : dispatch the descriptions for the columns to add + sal_Int16 nAddedColumns = 0; + + // for radio buttons we need some special structures + typedef std::map< OUString, Sequence< OUString> > MapUString2UstringSeq; + typedef std::map< OUString, OUString > FmMapUString2UString; + typedef std::map< OUString, sal_Int16 > FmMapUString2Int16; + + MapUString2UstringSeq aRadioValueLists; + MapUString2UstringSeq aRadioListSources; + FmMapUString2UString aRadioControlSources; + FmMapUString2Int16 aRadioPositions; + + FmXBoundFormFieldIterator aModelIterator(xCurrentNavController->getModel()); + OUString sColumnType,aGroupName,sControlSource; + Sequence< Property> aProps; + for (;;) + { + Reference< XPropertySet> xCurrentModelSet(aModelIterator.Next(), UNO_QUERY); + if (!xCurrentModelSet.is()) + break; + OSL_ENSURE(xCurrentModelSet.is(),"xCurrentModelSet is null!"); + // create a description of the column to be created + // first : determine it's type + + sal_Int16 nClassId = ::comphelper::getINT16(xCurrentModelSet->getPropertyValue(FM_PROP_CLASSID)); + switch (nClassId) + { + case FormComponentType::RADIOBUTTON: + { + // get the label of the button (this is the access key for our structures) + aGroupName = getLabelName(xCurrentModelSet); + + // add the reference value of the radio button to the list source sequence + Sequence< OUString>& aThisGroupLabels = aRadioListSources[aGroupName]; + sal_Int32 nNewSizeL = aThisGroupLabels.getLength() + 1; + aThisGroupLabels.realloc(nNewSizeL); + aThisGroupLabels.getArray()[nNewSizeL - 1] = ::comphelper::getString(xCurrentModelSet->getPropertyValue(FM_PROP_REFVALUE)); + + // add the label to the value list sequence + Sequence< OUString>& aThisGroupControlSources = aRadioValueLists[aGroupName]; + sal_Int32 nNewSizeC = aThisGroupControlSources.getLength() + 1; + aThisGroupControlSources.realloc(nNewSizeC); + aThisGroupControlSources.getArray()[nNewSizeC - 1] = ::comphelper::getString(xCurrentModelSet->getPropertyValue(FM_PROP_LABEL)); + + // remember the controls source of the radio group + sControlSource = ::comphelper::getString(xCurrentModelSet->getPropertyValue(FM_PROP_CONTROLSOURCE)); + if (aRadioControlSources.find(aGroupName) == aRadioControlSources.end()) + aRadioControlSources[aGroupName] = sControlSource; +#ifdef DBG_UTIL + else + DBG_ASSERT(aRadioControlSources[aGroupName] == sControlSource, + "FmXFormShell::CreateExternalView : inconsistent radio buttons detected !"); + // (radio buttons with the same name should have the same control source) +#endif + // remember the position within the columns + if (aRadioPositions.find(aGroupName) == aRadioPositions.end()) + aRadioPositions[aGroupName] = nAddedColumns; + + // any further handling is done below + } + continue; + + case FormComponentType::IMAGECONTROL: + case FormComponentType::CONTROL: + // no grid columns for these types (though they have a control source) + continue; + case FormComponentType::CHECKBOX: + sColumnType = FM_COL_CHECKBOX; break; + case FormComponentType::LISTBOX: + sColumnType = FM_COL_LISTBOX; break; + case FormComponentType::COMBOBOX: + sColumnType = FM_COL_COMBOBOX; break; + case FormComponentType::DATEFIELD: + sColumnType = FM_COL_DATEFIELD; break; + case FormComponentType::TIMEFIELD: + sColumnType = FM_COL_TIMEFIELD; break; + case FormComponentType::NUMERICFIELD: + sColumnType = FM_COL_NUMERICFIELD; break; + case FormComponentType::CURRENCYFIELD: + sColumnType = FM_COL_CURRENCYFIELD; break; + case FormComponentType::PATTERNFIELD: + sColumnType = FM_COL_PATTERNFIELD; break; + + case FormComponentType::TEXTFIELD: + { + sColumnType = FM_COL_TEXTFIELD; + // we know at least two different controls which are TextFields : the basic edit field and the formatted + // field. we distinguish them by their service name + Reference< lang::XServiceInfo> xInfo(xCurrentModelSet, UNO_QUERY); + if (xInfo.is()) + { + SdrObjKind nObjectType = getControlTypeByObject(xInfo); + if (SdrObjKind::FormFormattedField == nObjectType) + sColumnType = FM_COL_FORMATTEDFIELD; + } + } + break; + default: + sColumnType = FM_COL_TEXTFIELD; break; + } + + const sal_Int16 nDispatchArgs = 3; + Sequence< PropertyValue> aDispatchArgs(nDispatchArgs); + PropertyValue* pDispatchArgs = aDispatchArgs.getArray(); + + // properties describing "meta data" about the column + // the type + pDispatchArgs->Name = FMARG_ADDCOL_COLUMNTYPE; + pDispatchArgs->Value <<= sColumnType; + ++pDispatchArgs; + + // the pos : append the col + pDispatchArgs->Name = FMARG_ADDCOL_COLUMNPOS; + pDispatchArgs->Value <<= nAddedColumns; + ++pDispatchArgs; + + // the properties to forward to the new column + Sequence< PropertyValue> aColumnProps(1); + PropertyValue* pColumnProps = aColumnProps.getArray(); + + // the label + pColumnProps->Name = FM_PROP_LABEL; + pColumnProps->Value <<= getLabelName(xCurrentModelSet); + ++pColumnProps; + + // for all other props : transfer them + Reference< XPropertySetInfo> xControlModelInfo( xCurrentModelSet->getPropertySetInfo()); + DBG_ASSERT(xControlModelInfo.is(), "FmXFormShell::CreateExternalView : the control model has no property info ! This will crash !"); + aProps = xControlModelInfo->getProperties(); + + // realloc the control description sequence + sal_Int32 nExistentDescs = pColumnProps - aColumnProps.getArray(); + aColumnProps.realloc(nExistentDescs + aProps.getLength()); + pColumnProps = aColumnProps.getArray() + nExistentDescs; + + for (const Property& rProp : std::as_const(aProps)) + { + if (rProp.Name == FM_PROP_LABEL) + // already set + continue; + if (rProp.Name == FM_PROP_DEFAULTCONTROL) + // allow the column's own "default control" + continue; + if (rProp.Attributes & PropertyAttribute::READONLY) + // assume that properties which are readonly for the control are ro for the column to be created, too + continue; + + pColumnProps->Name = rProp.Name; + pColumnProps->Value = xCurrentModelSet->getPropertyValue(rProp.Name); + ++pColumnProps; + } + aColumnProps.realloc(pColumnProps - aColumnProps.getArray()); + + // columns props are a dispatch argument + pDispatchArgs->Name = "ColumnProperties"; // TODO : fmurl.* + pDispatchArgs->Value <<= aColumnProps; + ++pDispatchArgs; + DBG_ASSERT(nDispatchArgs == (pDispatchArgs - aDispatchArgs.getConstArray()), + "FmXFormShell::CreateExternalView : forgot to adjust nDispatchArgs ?"); + + // dispatch the "add column" + xAddColumnDispatch->dispatch(aAddColumnURL, aDispatchArgs); + ++nAddedColumns; + } + + // now for the radio button handling + sal_Int16 nOffset(0); + // properties describing the "direct" column properties + const sal_Int16 nListBoxDescription = 6; + Sequence< PropertyValue> aListBoxDescription(nListBoxDescription); + for (const auto& rCtrlSource : aRadioControlSources) + { + PropertyValue* pListBoxDescription = aListBoxDescription.getArray(); + // label + pListBoxDescription->Name = FM_PROP_LABEL; + pListBoxDescription->Value <<= rCtrlSource.first; + ++pListBoxDescription; + + // control source + pListBoxDescription->Name = FM_PROP_CONTROLSOURCE; + pListBoxDescription->Value <<= rCtrlSource.second; + ++pListBoxDescription; + + // bound column + pListBoxDescription->Name = FM_PROP_BOUNDCOLUMN; + pListBoxDescription->Value <<= sal_Int16(1); + ++pListBoxDescription; + + // content type + pListBoxDescription->Name = FM_PROP_LISTSOURCETYPE; + pListBoxDescription->Value <<= ListSourceType_VALUELIST; + ++pListBoxDescription; + + // list source + MapUString2UstringSeq::const_iterator aCurrentListSource = aRadioListSources.find(rCtrlSource.first); + DBG_ASSERT(aCurrentListSource != aRadioListSources.end(), + "FmXFormShell::CreateExternalView : inconsistent radio descriptions !"); + pListBoxDescription->Name = FM_PROP_LISTSOURCE; + pListBoxDescription->Value <<= (*aCurrentListSource).second; + ++pListBoxDescription; + + // value list + MapUString2UstringSeq::const_iterator aCurrentValueList = aRadioValueLists.find(rCtrlSource.first); + DBG_ASSERT(aCurrentValueList != aRadioValueLists.end(), + "FmXFormShell::CreateExternalView : inconsistent radio descriptions !"); + pListBoxDescription->Name = FM_PROP_STRINGITEMLIST; + pListBoxDescription->Value <<= (*aCurrentValueList).second; + ++pListBoxDescription; + + DBG_ASSERT(nListBoxDescription == (pListBoxDescription - aListBoxDescription.getConstArray()), + "FmXFormShell::CreateExternalView : forgot to adjust nListBoxDescription ?"); + + // properties describing the column "meta data" + const sal_Int16 nDispatchArgs = 3; + Sequence< PropertyValue> aDispatchArgs(nDispatchArgs); + PropertyValue* pDispatchArgs = aDispatchArgs.getArray(); + + // column type : listbox + pDispatchArgs->Name = FMARG_ADDCOL_COLUMNTYPE; + pDispatchArgs->Value <<= OUString(FM_COL_LISTBOX); +// pDispatchArgs->Value <<= (OUString)FM_COL_LISTBOX; + ++pDispatchArgs; + + // column position + pDispatchArgs->Name = FMARG_ADDCOL_COLUMNPOS; + FmMapUString2Int16::const_iterator aOffset = aRadioPositions.find(rCtrlSource.first); + DBG_ASSERT(aOffset != aRadioPositions.end(), + "FmXFormShell::CreateExternalView : inconsistent radio descriptions !"); + sal_Int16 nPosition = (*aOffset).second; + nPosition = nPosition + nOffset; + // we already inserted nOffset additional columns... + pDispatchArgs->Value <<= nPosition; + ++pDispatchArgs; + + // the + pDispatchArgs->Name = "ColumnProperties"; // TODO : fmurl.* + pDispatchArgs->Value <<= aListBoxDescription; + ++pDispatchArgs; + DBG_ASSERT(nDispatchArgs == (pDispatchArgs - aDispatchArgs.getConstArray()), + "FmXFormShell::CreateExternalView : forgot to adjust nDispatchArgs ?"); + + // dispatch the "add column" + xAddColumnDispatch->dispatch(aAddColumnURL, aDispatchArgs); + ++nAddedColumns; + ++nOffset; + } + + + DBG_ASSERT(nAddedColumns > 0, "FmXFormShell::CreateExternalView : no controls (inconsistent) !"); + // we should have checked if we have any usable controls (see above). + + // "load" the "form" of the external view + PropertyValue aArg; + aArg.Name = FMARG_ATTACHTO_MASTERFORM; + Reference< XResultSet> xForm(xCurrentNavController->getModel(), UNO_QUERY); + aArg.Value <<= xForm; + + m_xExternalDisplayedForm = xForm; + // do this before dispatching the "attach" command, as the attach may result in a call to our queryDispatch (for the FormSlots) + // which needs the m_xExternalDisplayedForm + + xAttachDispatch->dispatch(aAttachURL, Sequence< PropertyValue>(&aArg, 1)); + + m_xExtViewTriggerController = xCurrentNavController; + + // we want to know modifications done in the external view + // if the external controller is a XFormController we can use all our default handlings for it + Reference< runtime::XFormController > xFormController( m_xExternalViewController, UNO_QUERY ); + OSL_ENSURE( xFormController.is(), "FmXFormShell::CreateExternalView:: invalid external view controller!" ); + if (xFormController.is()) + xFormController->addActivateListener(static_cast(this)); + } + } +#ifdef DBG_UTIL + else + { + OSL_FAIL("FmXFormShell::CreateExternalView : could not create the external form view !"); + } +#endif + InvalidateSlot_Lock(SID_FM_VIEW_AS_GRID, false); +} + + +void FmXFormShell::implAdjustConfigCache_Lock() +{ + // get (cache) the wizard usage flag + Sequence< OUString > aNames { "FormControlPilotsEnabled" }; + Sequence< Any > aFlags = GetProperties(aNames); + if (1 == aFlags.getLength()) + m_bUseWizards = ::cppu::any2bool(aFlags[0]); +} + + +void FmXFormShell::Notify( const css::uno::Sequence< OUString >& _rPropertyNames) +{ + DBG_TESTSOLARMUTEX(); + if (impl_checkDisposed_Lock()) + return; + + for (const OUString& rName : _rPropertyNames) + if (rName == "FormControlPilotsEnabled") + { + implAdjustConfigCache_Lock(); + InvalidateSlot_Lock(SID_FM_USE_WIZARDS, true); + } +} + +void FmXFormShell::ImplCommit() +{ +} + + +void FmXFormShell::SetWizardUsing_Lock(bool _bUseThem) +{ + m_bUseWizards = _bUseThem; + + Sequence< OUString > aNames { "FormControlPilotsEnabled" }; + Sequence< Any > aValues{ Any(m_bUseWizards) }; + PutProperties(aNames, aValues); +} + + +void FmXFormShell::viewDeactivated_Lock(FmFormView& _rCurrentView, bool _bDeactivateController) +{ + + if ( _rCurrentView.GetImpl() && !_rCurrentView.IsDesignMode() ) + { + _rCurrentView.GetImpl()->Deactivate( _bDeactivateController ); + } + + // if we have an async load operation pending for the 0-th page for this view, + // we need to cancel this + if (FmFormPage* pPage = _rCurrentView.GetCurPage()) + { + // move all events from our queue to a new one, omit the events for the deactivated + // page + ::std::queue< FmLoadAction > aNewEvents; + while ( !m_aLoadingPages.empty() ) + { + FmLoadAction aAction = m_aLoadingPages.front(); + m_aLoadingPages.pop(); + if ( pPage != aAction.pPage ) + { + aNewEvents.push( aAction ); + } + else + { + Application::RemoveUserEvent( aAction.nEventId ); + } + } + m_aLoadingPages = aNewEvents; + + // remove callbacks at the page + pPage->GetImpl().SetFormsCreationHdl( Link() ); + } + UpdateForms_Lock(true); +} + + +IMPL_LINK_NOARG( FmXFormShell, OnFirstTimeActivation_Lock, void*, void ) +{ + if (impl_checkDisposed_Lock()) + return; + + m_nActivationEvent = nullptr; + SfxObjectShell* pDocument = m_pShell->GetObjectShell(); + + if ( pDocument && !pDocument->HasName() ) + { + if (isEnhancedForm_Lock()) + { + // show the data navigator + if ( !m_pShell->GetViewShell()->GetViewFrame()->HasChildWindow( SID_FM_SHOW_DATANAVIGATOR ) ) + m_pShell->GetViewShell()->GetViewFrame()->ToggleChildWindow( SID_FM_SHOW_DATANAVIGATOR ); + } + } +} + + +IMPL_LINK_NOARG( FmXFormShell, OnFormsCreated_Lock, FmFormPageImpl&, void ) +{ + UpdateForms_Lock(true); +} + + +void FmXFormShell::viewActivated_Lock(FmFormView& _rCurrentView, bool _bSyncAction) +{ + FmFormPage* pPage = _rCurrentView.GetCurPage(); + + // activate our view if we are activated ourself + // FS - 30.06.99 - 67308 + if ( _rCurrentView.GetImpl() && !_rCurrentView.IsDesignMode() ) + { + // load forms for the page the current view belongs to + if ( pPage ) + { + if ( !pPage->GetImpl().hasEverBeenActivated() ) + loadForms_Lock(pPage, LoadFormsFlags::Load + | (_bSyncAction ? LoadFormsFlags::Sync + : LoadFormsFlags::Async)); + pPage->GetImpl().setHasBeenActivated( ); + } + + // first-time initializations for the views + if ( !_rCurrentView.GetImpl()->hasEverBeenActivated( ) ) + { + _rCurrentView.GetImpl()->onFirstViewActivation( dynamic_cast( _rCurrentView.GetModel() ) ); + _rCurrentView.GetImpl()->setHasBeenActivated( ); + } + + // activate the current view + _rCurrentView.GetImpl()->Activate( _bSyncAction ); + } + + // set callbacks at the page + if ( pPage ) + { + pPage->GetImpl().SetFormsCreationHdl(LINK(this, FmXFormShell, OnFormsCreated_Lock)); + } + + UpdateForms_Lock(true); + + if ( m_bFirstActivation ) + { + m_nActivationEvent = Application::PostUserEvent(LINK(this, FmXFormShell, OnFirstTimeActivation_Lock)); + m_bFirstActivation = false; + } + + // find a default "current form", if there is none, yet + // #i88186# / 2008-04-12 / frank.schoenheit@sun.com + impl_defaultCurrentForm_nothrow_Lock(); +} + + +void FmXFormShell::impl_defaultCurrentForm_nothrow_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + if ( m_xCurrentForm.is() ) + // no action required + return; + + FmFormView* pFormView = m_pShell->GetFormView(); + FmFormPage* pPage = pFormView ? pFormView->GetCurPage() : nullptr; + if ( !pPage ) + return; + + try + { + Reference< XIndexAccess > xForms = pPage->GetForms( false ); + if ( !xForms.is() || !xForms->hasElements() ) + return; + + Reference< XForm > xNewCurrentForm( xForms->getByIndex(0), UNO_QUERY_THROW ); + impl_updateCurrentForm_Lock(xNewCurrentForm); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +void FmXFormShell::smartControlReset( const Reference< XIndexAccess >& _rxModels ) +{ + if (!_rxModels.is()) + { + OSL_FAIL("FmXFormShell::smartControlReset: invalid container!"); + return; + } + + sal_Int32 nCount = _rxModels->getCount(); + Reference< XPropertySet > xCurrent; + Reference< XPropertySetInfo > xCurrentInfo; + Reference< XPropertySet > xBoundField; + + for (sal_Int32 i=0; igetByIndex(i) >>= xCurrent; + if (xCurrent.is()) + xCurrentInfo = xCurrent->getPropertySetInfo(); + else + xCurrentInfo.clear(); + if (!xCurrentInfo.is()) + continue; + + if (xCurrentInfo->hasPropertyByName(FM_PROP_CLASSID)) + { // it's a control model + + // check if this control is bound to a living database field + if (xCurrentInfo->hasPropertyByName(FM_PROP_BOUNDFIELD)) + xCurrent->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xBoundField; + else + xBoundField.clear(); + + // reset only if it's *not* bound + bool bReset = !xBoundField.is(); + + // and additionally, check if it has an external value binding + Reference< XBindableValue > xBindable( xCurrent, UNO_QUERY ); + if ( xBindable.is() && xBindable->getValueBinding().is() ) + bReset = false; + + if ( bReset ) + { + Reference< XReset > xControlReset( xCurrent, UNO_QUERY ); + if ( xControlReset.is() ) + xControlReset->reset(); + } + } + else + { + Reference< XIndexAccess > xContainer(xCurrent, UNO_QUERY); + if (xContainer.is()) + smartControlReset(xContainer); + } + } +} + + +IMPL_LINK_NOARG( FmXFormShell, OnLoadForms_Lock, void*, void ) +{ + FmLoadAction aAction = m_aLoadingPages.front(); + m_aLoadingPages.pop(); + + loadForms_Lock(aAction.pPage, aAction.nFlags & ~LoadFormsFlags::Async); +} + + +namespace +{ + bool lcl_isLoadable( const Reference< XInterface >& _rxLoadable ) + { + // determines whether a form should be loaded or not + // if there is no datasource or connection there is no reason to load a form + Reference< XPropertySet > xSet( _rxLoadable, UNO_QUERY ); + if ( !xSet.is() ) + return false; + try + { + Reference< XConnection > xConn; + if ( isEmbeddedInDatabase( _rxLoadable, xConn ) ) + return true; + + // is there already an active connection + xSet->getPropertyValue(FM_PROP_ACTIVE_CONNECTION) >>= xConn; + if ( xConn.is() ) + return true; + + OUString sPropertyValue; + OSL_VERIFY( xSet->getPropertyValue( FM_PROP_DATASOURCE ) >>= sPropertyValue ); + if ( !sPropertyValue.isEmpty() ) + return true; + + OSL_VERIFY( xSet->getPropertyValue( FM_PROP_URL ) >>= sPropertyValue ); + if ( !sPropertyValue.isEmpty() ) + return true; + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return false; + } +} + + +void FmXFormShell::loadForms_Lock(FmFormPage* _pPage, const LoadFormsFlags _nBehaviour /* LoadFormsFlags::Load | LoadFormsFlags::Sync */) +{ + DBG_ASSERT( ( _nBehaviour & ( LoadFormsFlags::Async | LoadFormsFlags::Unload ) ) != ( LoadFormsFlags::Async | LoadFormsFlags::Unload ), + "FmXFormShell::loadForms: async loading not supported - this will heavily fail!" ); + + if ( _nBehaviour & LoadFormsFlags::Async ) + { + m_aLoadingPages.push( FmLoadAction( + _pPage, + _nBehaviour, + Application::PostUserEvent(LINK(this, FmXFormShell, OnLoadForms_Lock), _pPage) + ) ); + return; + } + + DBG_ASSERT( _pPage, "FmXFormShell::loadForms: invalid page!" ); + if ( !_pPage ) + return; + + // lock the undo env so the forms can change non-transient properties while loading + // (without this my doc's modified flag would be set) + FmFormModel& rFmFormModel(dynamic_cast< FmFormModel& >(_pPage->getSdrModelFromSdrPage())); + rFmFormModel.GetUndoEnv().Lock(); + + // load all forms + Reference< XIndexAccess > xForms = _pPage->GetForms( false ); + + if ( xForms.is() ) + { + Reference< XLoadable > xForm; + for ( sal_Int32 j = 0, nCount = xForms->getCount(); j < nCount; ++j ) + { + xForms->getByIndex( j ) >>= xForm; + bool bFormWasLoaded = false; + // a database form must be loaded for + try + { + if ( !( _nBehaviour & LoadFormsFlags::Unload ) ) + { + if ( lcl_isLoadable( xForm ) && !xForm->isLoaded() ) + xForm->load(); + } + else + { + if ( xForm->isLoaded() ) + { + bFormWasLoaded = true; + xForm->unload(); + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + // reset the form if it was loaded + if ( bFormWasLoaded ) + { + Reference< XIndexAccess > xContainer( xForm, UNO_QUERY ); + DBG_ASSERT( xContainer.is(), "FmXFormShell::loadForms: the form is no container!" ); + if ( xContainer.is() ) + smartControlReset( xContainer ); + } + } + } + + // unlock the environment + rFmFormModel.GetUndoEnv().UnLock(); +} + + +void FmXFormShell::ExecuteTextAttribute_Lock(SfxRequest& _rReq) +{ + DBG_TESTSOLARMUTEX(); + m_pTextShell->ExecuteTextAttribute( _rReq ); +} + + +void FmXFormShell::GetTextAttributeState_Lock(SfxItemSet& _rSet) +{ + DBG_TESTSOLARMUTEX(); + m_pTextShell->GetTextAttributeState( _rSet ); +} + + +bool FmXFormShell::IsActiveControl_Lock(bool _bCountRichTextOnly ) const +{ + DBG_TESTSOLARMUTEX(); + return m_pTextShell->IsActiveControl( _bCountRichTextOnly ); +} + + +void FmXFormShell::ForgetActiveControl_Lock() +{ + DBG_TESTSOLARMUTEX(); + m_pTextShell->ForgetActiveControl(); +} + + +void FmXFormShell::SetControlActivationHandler_Lock(const Link& _rHdl) +{ + DBG_TESTSOLARMUTEX(); + m_pTextShell->SetControlActivationHandler( _rHdl ); +} + +void FmXFormShell::handleShowPropertiesRequest_Lock() +{ + if (onlyControlsAreMarked_Lock()) + ShowSelectionProperties_Lock( true ); +} + + +void FmXFormShell::handleMouseButtonDown_Lock(const SdrViewEvent& _rViewEvent) +{ + // catch simple double clicks + if (_rViewEvent.mnMouseClicks == 2 && _rViewEvent.mnMouseCode == MOUSE_LEFT) + { + if ( _rViewEvent.meHit == SdrHitKind::MarkedObject ) + { + if (onlyControlsAreMarked_Lock()) + ShowSelectionProperties_Lock( true ); + } + } +} + + +bool FmXFormShell::HasControlFocus_Lock() const +{ + bool bHasControlFocus = false; + + try + { + Reference xController(getActiveController_Lock()); + Reference< XControl > xCurrentControl; + if ( xController.is() ) + xCurrentControl.set( xController->getCurrentControl() ); + if ( xCurrentControl.is() ) + { + Reference< XWindow2 > xPeerWindow( xCurrentControl->getPeer(), UNO_QUERY_THROW ); + bHasControlFocus = xPeerWindow->hasFocus(); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + return bHasControlFocus; +} + + +SearchableControlIterator::SearchableControlIterator(Reference< XInterface> const & xStartingPoint) + :IndexAccessIterator(xStartingPoint) +{ +} + + +bool SearchableControlIterator::ShouldHandleElement(const Reference< XInterface>& xElement) +{ + // if the thing has a ControlSource and a BoundField property + Reference< XPropertySet> xProperties(xElement, UNO_QUERY); + if (::comphelper::hasProperty(FM_PROP_CONTROLSOURCE, xProperties) && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xProperties)) + { + // and the BoundField is valid + Reference< XPropertySet> xField; + xProperties->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField; + if (xField.is()) + { + // we take it + m_sCurrentValue = ::comphelper::getString(xProperties->getPropertyValue(FM_PROP_CONTROLSOURCE)); + return true; + } + } + + // if it is a grid control + if (::comphelper::hasProperty(FM_PROP_CLASSID, xProperties)) + { + Any aClassId( xProperties->getPropertyValue(FM_PROP_CLASSID) ); + if (::comphelper::getINT16(aClassId) == FormComponentType::GRIDCONTROL) + { + m_sCurrentValue.clear(); + return true; + } + } + + return false; +} + + +bool SearchableControlIterator::ShouldStepInto(const Reference< XInterface>& /*xContainer*/) const +{ + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3