diff options
Diffstat (limited to 'extensions/source/propctrlr/propcontroller.cxx')
-rw-r--r-- | extensions/source/propctrlr/propcontroller.cxx | 1652 |
1 files changed, 1652 insertions, 0 deletions
diff --git a/extensions/source/propctrlr/propcontroller.cxx b/extensions/source/propctrlr/propcontroller.cxx new file mode 100644 index 000000000..6bbcdcf5d --- /dev/null +++ b/extensions/source/propctrlr/propcontroller.cxx @@ -0,0 +1,1652 @@ +/* -*- 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 "propcontroller.hxx" +#include "handlerhelper.hxx" +#include "standardcontrol.hxx" +#include "linedescriptor.hxx" +#include <strings.hrc> +#include "propertyeditor.hxx" +#include "modulepcr.hxx" +#include "formstrings.hxx" +#include "formbrowsertools.hxx" +#include "propertycomposer.hxx" + +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/inspection/PropertyControlType.hpp> +#include <com/sun/star/ucb/AlreadyInitializedException.hpp> +#include <com/sun/star/lang/XSingleComponentFactory.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/util/VetoException.hpp> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/weldutils.hxx> +#include <osl/mutex.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <algorithm> +#include <sal/log.hxx> + +namespace pcr +{ + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::awt; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::script; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::frame; + using namespace ::com::sun::star::util; + using namespace ::com::sun::star::inspection; + using namespace ::com::sun::star::ucb; + using namespace ::comphelper; + + //= OPropertyBrowserController + OPropertyBrowserController::OPropertyBrowserController( const Reference< XComponentContext >& _rxContext ) + :m_xContext(_rxContext) + ,m_aDisposeListeners( m_aMutex ) + ,m_aControlObservers( m_aMutex ) + ,m_bContainerFocusListening( false ) + ,m_bSuspendingPropertyHandlers( false ) + ,m_bConstructed( false ) + ,m_bBindingIntrospectee( false ) + { + } + + OPropertyBrowserController::~OPropertyBrowserController() + { + // stop listening for property changes + acquire(); + stopInspection( true ); + } + + IMPLEMENT_FORWARD_REFCOUNT( OPropertyBrowserController, OPropertyBrowserController_Base ) + + Any SAL_CALL OPropertyBrowserController::queryInterface( const Type& _rType ) + { + Any aReturn = OPropertyBrowserController_Base::queryInterface( _rType ); + if ( !aReturn.hasValue() ) + aReturn = ::cppu::queryInterface( + _rType, + static_cast< XObjectInspectorUI* >( this ) + ); + return aReturn; + } + + + void OPropertyBrowserController::startContainerWindowListening() + { + if (m_bContainerFocusListening) + return; + + if (m_xFrame.is()) + { + Reference< XWindow > xContainerWindow = m_xFrame->getContainerWindow(); + if (xContainerWindow.is()) + { + xContainerWindow->addFocusListener(this); + m_bContainerFocusListening = true; + } + } + + DBG_ASSERT(m_bContainerFocusListening, "OPropertyBrowserController::startContainerWindowListening: unable to start listening (inconsistence)!"); + } + + + void OPropertyBrowserController::stopContainerWindowListening() + { + if (!m_bContainerFocusListening) + return; + + if (m_xFrame.is()) + { + Reference< XWindow > xContainerWindow = m_xFrame->getContainerWindow(); + if (xContainerWindow.is()) + { + xContainerWindow->removeFocusListener(this); + m_bContainerFocusListening = false; + } + } + + DBG_ASSERT(!m_bContainerFocusListening, "OPropertyBrowserController::stopContainerWindowListening: unable to stop listening (inconsistence)!"); + } + + + Reference< XObjectInspectorModel > SAL_CALL OPropertyBrowserController::getInspectorModel() + { + return m_xModel; + } + + + void OPropertyBrowserController::impl_initializeView_nothrow() + { + OSL_PRECOND( haveView(), "OPropertyBrowserController::impl_initializeView_nothrow: not to be called when we have no view!" ); + if ( !haveView() ) + return; + + if ( !m_xModel.is() ) + // allowed + return; + + try + { + getPropertyBox().EnableHelpSection( m_xModel->getHasHelpSection() ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + + bool OPropertyBrowserController::impl_isReadOnlyModel_throw() const + { + if ( !m_xModel.is() ) + return false; + + return m_xModel->getIsReadOnly(); + } + + + void OPropertyBrowserController::impl_startOrStopModelListening_nothrow( bool _bDoListen ) const + { + try + { + Reference< XPropertySet > xModelProperties( m_xModel, UNO_QUERY ); + if ( !xModelProperties.is() ) + // okay, so the model doesn't want to change its properties + // dynamically - fine with us + return; + + void (SAL_CALL XPropertySet::*pListenerOperation)( const OUString&, const Reference< XPropertyChangeListener >& ) + = _bDoListen ? &XPropertySet::addPropertyChangeListener : &XPropertySet::removePropertyChangeListener; + + (xModelProperties.get()->*pListenerOperation)( + OUString( "IsReadOnly" ), + const_cast< OPropertyBrowserController* >( this ) + ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + + void OPropertyBrowserController::impl_bindToNewModel_nothrow( const Reference< XObjectInspectorModel >& _rxInspectorModel ) + { + impl_startOrStopModelListening_nothrow( false ); + m_xModel = _rxInspectorModel; + impl_startOrStopModelListening_nothrow( true ); + + // initialize the view, if we already have one + if ( haveView() ) + impl_initializeView_nothrow(); + + // inspect again, if we already have inspectees + if ( !m_aInspectedObjects.empty() ) + impl_rebindToInspectee_nothrow( std::vector(m_aInspectedObjects) ); + } + + + void SAL_CALL OPropertyBrowserController::setInspectorModel( const Reference< XObjectInspectorModel >& _inspectorModel ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( m_xModel == _inspectorModel ) + return; + + impl_bindToNewModel_nothrow( _inspectorModel ); + } + + + Reference< XObjectInspectorUI > SAL_CALL OPropertyBrowserController::getInspectorUI() + { + // we're derived from this interface, though we do not expose it in queryInterface and getTypes. + return this; + } + + + void SAL_CALL OPropertyBrowserController::inspect( const Sequence< Reference< XInterface > >& _rObjects ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( m_bSuspendingPropertyHandlers || !suspendAll_nothrow() ) + { // we already are trying to suspend the component (this is somewhere up the stack) + // OR one of our property handlers raised a veto against closing. Well, we *need* to close + // it in order to inspect another object. + throw VetoException(); + } + if ( m_bBindingIntrospectee ) + throw VetoException(); + + m_bBindingIntrospectee = true; + impl_rebindToInspectee_nothrow( InterfaceArray( _rObjects.begin(), _rObjects.end() ) ); + m_bBindingIntrospectee = false; + + } + + + Reference< XDispatch > SAL_CALL OPropertyBrowserController::queryDispatch( const URL& /*URL*/, const OUString& /*TargetFrameName*/, ::sal_Int32 /*SearchFlags*/ ) + { + // we don't have any dispatches at all, right now + return Reference< XDispatch >(); + } + + + Sequence< Reference< XDispatch > > SAL_CALL OPropertyBrowserController::queryDispatches( const Sequence< DispatchDescriptor >& Requests ) + { + Sequence< Reference< XDispatch > > aReturn; + sal_Int32 nLen = Requests.getLength(); + aReturn.realloc( nLen ); + + Reference< XDispatch >* pReturn = aReturn.getArray(); + const Reference< XDispatch >* pReturnEnd = aReturn.getArray() + nLen; + const DispatchDescriptor* pDescripts = Requests.getConstArray(); + + for ( ; pReturn != pReturnEnd; ++ pReturn, ++pDescripts ) + *pReturn = queryDispatch( pDescripts->FeatureURL, pDescripts->FrameName, pDescripts->SearchFlags ); + + return aReturn; + } + + + void SAL_CALL OPropertyBrowserController::initialize( const Sequence< Any >& _arguments ) + { + if ( m_bConstructed ) + throw AlreadyInitializedException(); + + StlSyntaxSequence< Any > arguments( _arguments ); + if ( arguments.empty() ) + { // constructor: "createDefault()" + m_bConstructed = true; + return; + } + + Reference< XObjectInspectorModel > xModel; + if ( arguments.size() == 1 ) + { // constructor: "createWithModel( XObjectInspectorModel )" + if ( !( arguments[0] >>= xModel ) ) + throw IllegalArgumentException( OUString(), *this, 0 ); + createWithModel( xModel ); + return; + } + + throw IllegalArgumentException( OUString(), *this, 0 ); + } + + + void OPropertyBrowserController::createWithModel( const Reference< XObjectInspectorModel >& _rxModel ) + { + osl_atomic_increment( &m_refCount ); + { + setInspectorModel( _rxModel ); + } + osl_atomic_decrement( &m_refCount ); + + m_bConstructed = true; + } + + + void SAL_CALL OPropertyBrowserController::attachFrame( const Reference< XFrame >& _rxFrame ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + if (_rxFrame.is() && haveView()) + throw RuntimeException("Unable to attach to a second frame.",*this); + + // revoke as focus listener from the old container window + stopContainerWindowListening(); + + m_xPropView.reset(); + m_xBuilder.reset(); + + m_xFrame = _rxFrame; + if (!m_xFrame.is()) + return; + + // TODO: this construction perhaps should be done outside. Don't know the exact meaning of attachFrame. + // Maybe it is intended to only announce the frame to the controller, and the instance doing this + // announcement is responsible for calling setComponent, too. + Reference<XWindow> xContainerWindow = m_xFrame->getContainerWindow(); + + OUString sUIFile("modules/spropctrlr/ui/formproperties.ui"); + std::unique_ptr<weld::Builder> xBuilder; + + if (weld::TransportAsXWindow* pTunnel = dynamic_cast<weld::TransportAsXWindow*>(xContainerWindow.get())) + { + xBuilder = Application::CreateBuilder(pTunnel->getWidget(), sUIFile); + } + else + { + VclPtr<vcl::Window> pParentWin = VCLUnoHelper::GetWindow(xContainerWindow); + if (!pParentWin) + throw RuntimeException("The frame is invalid. Unable to extract the container window.",*this); + xBuilder = Application::CreateInterimBuilder(pParentWin, sUIFile, true); + } + + Construct(xContainerWindow, std::move(xBuilder)); + + startContainerWindowListening(); + + UpdateUI(); + } + + sal_Bool SAL_CALL OPropertyBrowserController::attachModel( const Reference< XModel >& _rxModel ) + { + Reference< XObjectInspectorModel > xModel( _rxModel, UNO_QUERY ); + if ( !xModel.is() ) + return false; + + setInspectorModel( xModel ); + return getInspectorModel() == _rxModel; + } + + + bool OPropertyBrowserController::suspendAll_nothrow() + { + // if there is a handle inside its "onInteractivePropertySelection" method, + // then veto + // Normally, we could expect every handler to do this itself, but being + // realistic, it's safer to handle this here in general. + if ( m_xInteractiveHandler.is() ) + return false; + + m_bSuspendingPropertyHandlers = true; + bool bHandlerVeto = !suspendPropertyHandlers_nothrow( true ); + m_bSuspendingPropertyHandlers = false; + return !bHandlerVeto; + } + + + bool OPropertyBrowserController::suspendPropertyHandlers_nothrow( bool _bSuspend ) + { + PropertyHandlerArray aAllHandlers; // will contain every handler exactly once + for (auto const& propertyHandler : m_aPropertyHandlers) + { + if ( std::find( aAllHandlers.begin(), aAllHandlers.end(), propertyHandler.second ) != aAllHandlers.end() ) + // already visited this particular handler (m_aPropertyHandlers usually contains + // the same handler more than once) + continue; + aAllHandlers.push_back(propertyHandler.second); + } + + for (auto const& handler : aAllHandlers) + { + try + { + if ( !handler->suspend( _bSuspend ) ) + if ( _bSuspend ) + // if we're not suspending, but reactivating, ignore the error + return false; + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "OPropertyBrowserController::suspendPropertyHandlers_nothrow" ); + } + } + return true; + } + + + sal_Bool SAL_CALL OPropertyBrowserController::suspend( sal_Bool _bSuspend ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + OSL_ENSURE( haveView(), "OPropertyBrowserController::suspend: don't have a view anymore!" ); + + if ( !_bSuspend ) + { // this means a "suspend" is to be "revoked" + suspendPropertyHandlers_nothrow( false ); + // we ourself cannot revoke our suspend + return false; + } + + if ( !suspendAll_nothrow() ) + return false; + + // commit the editor's content + if ( haveView() ) + getPropertyBox().CommitModified(); + + // stop listening + stopContainerWindowListening(); + + // outta here + return true; + } + + + Any SAL_CALL OPropertyBrowserController::getViewData( ) + { + return Any( m_sPageSelection ); + } + + + void SAL_CALL OPropertyBrowserController::restoreViewData( const Any& Data ) + { + OUString sPageSelection; + if ( ( Data >>= sPageSelection ) && !sPageSelection.isEmpty() ) + { + m_sPageSelection = sPageSelection; + selectPageFromViewData(); + } + } + + Reference< XModel > SAL_CALL OPropertyBrowserController::getModel( ) + { + // have no model + return Reference< XModel >(); + } + + Reference< XFrame > SAL_CALL OPropertyBrowserController::getFrame( ) + { + return m_xFrame; + } + + void SAL_CALL OPropertyBrowserController::dispose() + { + SolarMutexGuard aSolarGuard; + + // stop inspecting the current object + stopInspection( false ); + + // say our dispose listeners goodbye + css::lang::EventObject aEvt; + aEvt.Source = static_cast< ::cppu::OWeakObject* >(this); + m_aDisposeListeners.disposeAndClear(aEvt); + m_aControlObservers.disposeAndClear(aEvt); + + m_xPropView.reset(); + m_xBuilder.reset(); + + if ( m_xView.is() ) + m_xView->removeEventListener( static_cast< XPropertyChangeListener* >( this ) ); + m_xView.clear( ); + + m_aInspectedObjects.clear(); + impl_bindToNewModel_nothrow( nullptr ); + } + + void SAL_CALL OPropertyBrowserController::addEventListener( const Reference< XEventListener >& _rxListener ) + { + m_aDisposeListeners.addInterface(_rxListener); + } + + void SAL_CALL OPropertyBrowserController::removeEventListener( const Reference< XEventListener >& _rxListener ) + { + m_aDisposeListeners.removeInterface(_rxListener); + } + + OUString SAL_CALL OPropertyBrowserController::getImplementationName( ) + { + return "org.openoffice.comp.extensions.ObjectInspector"; + } + + sal_Bool SAL_CALL OPropertyBrowserController::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService(this, ServiceName); + } + + + Sequence< OUString > SAL_CALL OPropertyBrowserController::getSupportedServiceNames( ) + { + return { "com.sun.star.inspection.ObjectInspector" }; + } + + + void SAL_CALL OPropertyBrowserController::focusGained( const FocusEvent& _rSource ) + { + Reference< XWindow > xSourceWindow(_rSource.Source, UNO_QUERY); + Reference< XWindow > xContainerWindow; + if (m_xFrame.is()) + xContainerWindow = m_xFrame->getContainerWindow(); + + if ( xContainerWindow.get() == xSourceWindow.get() ) + { // our container window got the focus + if ( haveView() ) + getPropertyBox().GrabFocus(); + } + } + + + void SAL_CALL OPropertyBrowserController::focusLost( const FocusEvent& /*_rSource*/ ) + { + // not interested in + } + + + void SAL_CALL OPropertyBrowserController::disposing( const EventObject& _rSource ) + { + if ( m_xView.is() && ( m_xView == _rSource.Source ) ) + { + m_xView = nullptr; + m_xPropView.reset(); + m_xBuilder.reset(); + } + + auto it = std::find_if(m_aInspectedObjects.begin(), m_aInspectedObjects.end(), + [&_rSource](const InterfaceArray::value_type& rxObj) { return rxObj == _rSource.Source; }); + if (it != m_aInspectedObjects.end()) + m_aInspectedObjects.erase(it); + } + + + IMPL_LINK_NOARG(OPropertyBrowserController, OnPageActivation, LinkParamNone*, void) + { + updateViewDataFromActivePage(); + } + + + void OPropertyBrowserController::updateViewDataFromActivePage() + { + if (!haveView()) + return; + + OUString sOldSelection = m_sPageSelection; + m_sPageSelection.clear(); + + const sal_uInt16 nCurrentPage = m_xPropView->getActivePage(); + if ( sal_uInt16(-1) != nCurrentPage ) + { + for (auto const& pageId : m_aPageIds) + { + if ( nCurrentPage == pageId.second ) + { + m_sPageSelection = pageId.first; + break; + } + } + } + + if ( !m_sPageSelection.isEmpty() ) + m_sLastValidPageSelection = m_sPageSelection; + else if ( !sOldSelection.isEmpty() ) + m_sLastValidPageSelection = sOldSelection; + } + + + sal_uInt16 OPropertyBrowserController::impl_getPageIdForCategory_nothrow( const OUString& _rCategoryName ) const + { + sal_uInt16 nPageId = sal_uInt16(-1); + HashString2Int16::const_iterator pagePos = m_aPageIds.find( _rCategoryName ); + if ( pagePos != m_aPageIds.end() ) + nPageId = pagePos->second; + return nPageId; + } + + void OPropertyBrowserController::selectPageFromViewData() + { + sal_uInt16 nNewPage = impl_getPageIdForCategory_nothrow( m_sPageSelection ); + + if ( haveView() && ( nNewPage != sal_uInt16(-1) ) ) + m_xPropView->activatePage( nNewPage ); + + // just in case ... + updateViewDataFromActivePage(); + } + + void OPropertyBrowserController::Construct(const Reference<XWindow>& rContainerWindow, std::unique_ptr<weld::Builder> xBuilder) + { + DBG_ASSERT(!haveView(), "OPropertyBrowserController::Construct: already have a view!"); + assert(xBuilder && "OPropertyBrowserController::Construct: invalid parent window!"); + + m_xBuilder = std::move(xBuilder); + + m_xPropView.reset(new OPropertyBrowserView(m_xContext, *m_xBuilder)); + m_xPropView->setPageActivationHandler(LINK(this, OPropertyBrowserController, OnPageActivation)); + + // add as dispose listener for our view. The view is disposed by the frame we're plugged into, + // and this disposal _deletes_ the view, so it would be deadly if we use our m_xPropView member + // after that + m_xView = rContainerWindow; + if (m_xView.is()) + m_xView->addEventListener( static_cast< XPropertyChangeListener* >( this ) ); + + getPropertyBox().SetLineListener(this); + getPropertyBox().SetControlObserver(this); + impl_initializeView_nothrow(); + } + + void SAL_CALL OPropertyBrowserController::propertyChange( const PropertyChangeEvent& _rEvent ) + { + if ( _rEvent.Source == m_xModel ) + { + if ( _rEvent.PropertyName == "IsReadOnly" ) + // this is a huge cudgel, admitted. + // The problem is that in case we were previously read-only, all our controls + // were created read-only, too. We cannot simply switch them to not-read-only. + // Even if they had an API for this, we do not know whether they were + // originally created read-only, or if they are read-only just because + // the model was. + impl_rebindToInspectee_nothrow( std::vector(m_aInspectedObjects) ); + return; + } + + if ( m_sCommittingProperty == _rEvent.PropertyName ) + return; + + if ( !haveView() ) + return; + + Any aNewValue( _rEvent.NewValue ); + if ( impl_hasPropertyHandlerFor_nothrow( _rEvent.PropertyName ) ) + { + // forward the new value to the property box, to reflect the change in the UI + aNewValue = impl_getPropertyValue_throw( _rEvent.PropertyName ); + + // check whether the state is ambiguous. This is interesting in case we display the properties + // for multiple objects at once: In this case, we'll get a notification from one of the objects, + // but need to care for the "composed" value, which can be "ambiguous". + PropertyHandlerRef xHandler( impl_getHandlerForProperty_throw( _rEvent.PropertyName ), UNO_SET_THROW ); + PropertyState ePropertyState( xHandler->getPropertyState( _rEvent.PropertyName ) ); + bool bAmbiguousValue = ( PropertyState_AMBIGUOUS_VALUE == ePropertyState ); + + getPropertyBox().SetPropertyValue( _rEvent.PropertyName, aNewValue, bAmbiguousValue ); + } + + // if it's an actuating property, then update the UI for any dependent + // properties + if ( impl_isActuatingProperty_nothrow( _rEvent.PropertyName ) ) + impl_broadcastPropertyChange_nothrow( _rEvent.PropertyName, aNewValue, _rEvent.OldValue, false ); + } + + Reference< XPropertyControl > SAL_CALL OPropertyBrowserController::createPropertyControl( ::sal_Int16 ControlType, sal_Bool bCreateReadOnly ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + Reference< XPropertyControl > xControl; + + // read-only-ness + bCreateReadOnly |= impl_isReadOnlyModel_throw() ? 1 : 0; + + switch ( ControlType ) + { + case PropertyControlType::MultiLineTextField: + case PropertyControlType::StringListField: + { + bool bMultiLineTextField = ControlType == PropertyControlType::MultiLineTextField; + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/multiline.ui", m_xContext)); + auto pContainer = xBuilder->weld_container("multiline"); + rtl::Reference<OMultilineEditControl> pControl = new OMultilineEditControl(std::move(pContainer), std::move(xBuilder), + bMultiLineTextField ? eMultiLineText : eStringList, bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::ListBox: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/listbox.ui", m_xContext)); + auto pComboBox = xBuilder->weld_combo_box("listbox"); + rtl::Reference<OListboxControl> pControl = new OListboxControl(std::move(pComboBox), std::move(xBuilder), bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::ComboBox: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/combobox.ui", m_xContext)); + auto pComboBox = xBuilder->weld_combo_box("combobox"); + rtl::Reference<OComboboxControl> pControl = new OComboboxControl(std::move(pComboBox), std::move(xBuilder), bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::TextField: + case PropertyControlType::CharacterField: + { + bool bCharacterField = ControlType == PropertyControlType::CharacterField; + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/textfield.ui", m_xContext)); + auto pEntry = xBuilder->weld_entry("textfield"); + rtl::Reference<OEditControl> pControl = new OEditControl(std::move(pEntry), std::move(xBuilder), bCharacterField, bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::NumericField: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/numericfield.ui", m_xContext)); + auto pSpinButton = xBuilder->weld_metric_spin_button("numericfield", FieldUnit::NONE); + rtl::Reference<ONumericControl> pControl = new ONumericControl(std::move(pSpinButton), std::move(xBuilder), bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::DateTimeField: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/datetimefield.ui", m_xContext)); + auto pContainer = xBuilder->weld_container("datetimefield"); + rtl::Reference<ODateTimeControl> pControl = new ODateTimeControl(std::move(pContainer), std::move(xBuilder), bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::DateField: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/datefield.ui", m_xContext)); + auto pContainer = xBuilder->weld_container("datefield"); + rtl::Reference<ODateControl> pControl = new ODateControl(std::move(pContainer), std::move(xBuilder), bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::TimeField: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/timefield.ui", m_xContext)); + auto pTimeSpinButton = xBuilder->weld_formatted_spin_button("timefield"); + rtl::Reference<OTimeControl> pControl = new OTimeControl(std::move(pTimeSpinButton), std::move(xBuilder), bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::ColorListBox: + { + auto lambda = [this]{ return PropertyHandlerHelper::getDialogParentFrame(m_xContext); }; + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/colorlistbox.ui", m_xContext)); + auto pMenuButton = xBuilder->weld_menu_button("colorlistbox"); + rtl::Reference<OColorControl> pControl = new OColorControl(std::make_unique<ColorListBox>(std::move(pMenuButton), lambda), std::move(xBuilder), bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::HyperlinkField: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/hyperlinkfield.ui", m_xContext)); + auto pContainer = xBuilder->weld_container("hyperlinkfield"); + rtl::Reference<OHyperlinkControl> pControl = new OHyperlinkControl(std::move(pContainer), std::move(xBuilder), bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + default: + throw IllegalArgumentException( OUString(), *this, 1 ); + } + + return xControl; + } + + + void OPropertyBrowserController::impl_toggleInspecteeListening_nothrow( bool _bOn ) + { + for (auto const& inspectedObject : m_aInspectedObjects) + { + try + { + Reference< XComponent > xComp( inspectedObject, UNO_QUERY ); + if ( xComp.is() ) + { + if ( _bOn ) + xComp->addEventListener( static_cast< XPropertyChangeListener* >( this ) ); + else + xComp->removeEventListener( static_cast< XPropertyChangeListener* >( this ) ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + } + + + void OPropertyBrowserController::stopInspection( bool _bCommitModified ) + { + if ( haveView() ) + { + if ( _bCommitModified ) + // commit the editor's content + getPropertyBox().CommitModified(); + + // hide the property box so that it does not flicker + getPropertyBox().Hide(); + + // clear the property box + getPropertyBox().ClearAll(); + } + + // destroy the view first + if ( haveView() ) + { + // remove the pages + for (auto const& pageId : m_aPageIds) + getPropertyBox().RemovePage( pageId.second ); + clearContainer( m_aPageIds ); + } + + clearContainer( m_aProperties ); + + // de-register as dispose-listener from our inspected objects + impl_toggleInspecteeListening_nothrow( false ); + + // handlers are obsolete, so is our "composer" for their UI requests + if (m_pUIRequestComposer) + m_pUIRequestComposer->dispose(); + m_pUIRequestComposer.reset(); + + // clean up the property handlers + PropertyHandlerArray aAllHandlers; // will contain every handler exactly once + for (auto const& propertyHandler : m_aPropertyHandlers) + if ( std::find( aAllHandlers.begin(), aAllHandlers.end(), propertyHandler.second ) == aAllHandlers.end() ) + aAllHandlers.push_back( propertyHandler.second ); + + for (auto const& handler : aAllHandlers) + { + try + { + handler->removePropertyChangeListener( this ); + handler->dispose(); + } + catch( const DisposedException& ) + { + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + clearContainer( m_aPropertyHandlers ); + clearContainer( m_aDependencyHandlers ); + } + + + bool OPropertyBrowserController::impl_hasPropertyHandlerFor_nothrow( const OUString& _rPropertyName ) const + { + PropertyHandlerRepository::const_iterator handlerPos = m_aPropertyHandlers.find( _rPropertyName ); + return ( handlerPos != m_aPropertyHandlers.end() ); + } + + + OPropertyBrowserController::PropertyHandlerRef const & OPropertyBrowserController::impl_getHandlerForProperty_throw( const OUString& _rPropertyName ) const + { + PropertyHandlerRepository::const_iterator handlerPos = m_aPropertyHandlers.find( _rPropertyName ); + if ( handlerPos == m_aPropertyHandlers.end() ) + throw RuntimeException(); + return handlerPos->second; + } + + + Any OPropertyBrowserController::impl_getPropertyValue_throw( const OUString& _rPropertyName ) + { + PropertyHandlerRef handler = impl_getHandlerForProperty_throw( _rPropertyName ); + return handler->getPropertyValue( _rPropertyName ); + } + + + void OPropertyBrowserController::impl_rebindToInspectee_nothrow( InterfaceArray&& _rObjects ) + { + try + { + // stop inspecting the old object(s) + stopInspection( true ); + + // inspect the new object(s) + m_aInspectedObjects = std::move(_rObjects); + doInspection(); + + // update the user interface + UpdateUI(); + } + + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.propctrlr", ""); + } + } + + + void OPropertyBrowserController::doInspection() + { + try + { + + // obtain the properties of the object + std::vector< Property > aProperties; + + PropertyHandlerArray aPropertyHandlers; + getPropertyHandlers( m_aInspectedObjects, aPropertyHandlers ); + + PropertyHandlerArray::iterator aHandler( aPropertyHandlers.begin() ); + while ( aHandler != aPropertyHandlers.end() ) + { + DBG_ASSERT( aHandler->get(), "OPropertyBrowserController::doInspection: invalid handler!" ); + + StlSyntaxSequence< Property > aThisHandlersProperties( (*aHandler)->getSupportedProperties() ); + + if ( aThisHandlersProperties.empty() ) + { + // this handler doesn't know anything about the current inspectee -> ignore it + (*aHandler)->dispose(); + aHandler = aPropertyHandlers.erase( aHandler ); + continue; + } + + // append these properties to our "all properties" array + aProperties.reserve( aProperties.size() + aThisHandlersProperties.size() ); + for (const auto & aThisHandlersProperty : aThisHandlersProperties) + { + auto noPrevious = std::none_of( + aProperties.begin(), + aProperties.end(), + FindPropertyByName( aThisHandlersProperty.Name ) + ); + if ( noPrevious ) + { + aProperties.push_back( aThisHandlersProperty ); + continue; + } + + // there already was another (previous) handler which supported this property. + // Don't add it to aProperties, again. + + // Also, ensure that handlers which previously expressed interest in *changes* + // of this property are not notified. + // This is 'cause we have a new handler which is responsible for this property, + // which means it can give it a completely different meaning than the previous + // handler for this property is prepared for. + std::pair< PropertyHandlerMultiRepository::iterator, PropertyHandlerMultiRepository::iterator > + aDepHandlers = m_aDependencyHandlers.equal_range( aThisHandlersProperty.Name ); + m_aDependencyHandlers.erase( aDepHandlers.first, aDepHandlers.second ); + } + + // determine the superseded properties + StlSyntaxSequence< OUString > aSupersededByThisHandler( (*aHandler)->getSupersededProperties() ); + for (const auto & superseded : aSupersededByThisHandler) + { + std::vector< Property >::iterator existent = std::find_if( + aProperties.begin(), + aProperties.end(), + FindPropertyByName( superseded ) + ); + if ( existent != aProperties.end() ) + // one of the properties superseded by this handler was supported by a previous + // one -> erase + aProperties.erase( existent ); + } + + // be notified of changes which this handler is responsible for + (*aHandler)->addPropertyChangeListener( this ); + + // remember this handler for every of the properties which it is responsible + // for + for (const auto & aThisHandlersProperty : aThisHandlersProperties) + { + m_aPropertyHandlers[ aThisHandlersProperty.Name ] = *aHandler; + // note that this implies that if two handlers support the same property, + // the latter wins + } + + // see if the handler expresses interest in any actuating properties + StlSyntaxSequence< OUString > aInterestingActuations( (*aHandler)->getActuatingProperties() ); + for (const auto & aInterestingActuation : aInterestingActuations) + { + m_aDependencyHandlers.emplace( aInterestingActuation, *aHandler ); + } + + ++aHandler; + } + + // create a new composer for UI requests coming from the handlers + m_pUIRequestComposer.reset( new ComposedPropertyUIUpdate( getInspectorUI(), this ) ); + + // sort the properties by relative position, as indicated by the model + sal_Int32 nPos = 0; + for (auto const& sourceProps : aProperties) + { + sal_Int32 nRelativePropertyOrder = nPos; + if ( m_xModel.is() ) + nRelativePropertyOrder = m_xModel->getPropertyOrderIndex( sourceProps.Name ); + m_aProperties.emplace(nRelativePropertyOrder, sourceProps); + ++nPos; + } + + // be notified when one of our inspectees dies + impl_toggleInspecteeListening_nothrow( true ); + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.propctrlr", ""); + } + } + + + css::awt::Size SAL_CALL OPropertyBrowserController::getMinimumSize() + { + css::awt::Size aSize; + if( m_xPropView ) + return m_xPropView->getMinimumSize(); + else + return aSize; + } + + + css::awt::Size SAL_CALL OPropertyBrowserController::getPreferredSize() + { + return getMinimumSize(); + } + + + css::awt::Size SAL_CALL OPropertyBrowserController::calcAdjustedSize( const css::awt::Size& _rNewSize ) + { + awt::Size aMinSize = getMinimumSize( ); + awt::Size aAdjustedSize( _rNewSize ); + if ( aAdjustedSize.Width < aMinSize.Width ) + aAdjustedSize.Width = aMinSize.Width; + if ( aAdjustedSize.Height < aMinSize.Height ) + aAdjustedSize.Height = aMinSize.Height; + return aAdjustedSize; + } + + + void OPropertyBrowserController::describePropertyLine( const Property& _rProperty, OLineDescriptor& _rDescriptor ) + { + try + { + PropertyHandlerRepository::const_iterator handler = m_aPropertyHandlers.find( _rProperty.Name ); + if ( handler == m_aPropertyHandlers.end() ) + throw RuntimeException(); // caught below + + _rDescriptor.assignFrom( handler->second->describePropertyLine( _rProperty.Name, this ) ); + + + _rDescriptor.xPropertyHandler = handler->second; + _rDescriptor.sName = _rProperty.Name; + _rDescriptor.aValue = _rDescriptor.xPropertyHandler->getPropertyValue( _rProperty.Name ); + + if ( _rDescriptor.DisplayName.isEmpty() ) + { + #ifdef DBG_UTIL + SAL_WARN( "extensions.propctrlr", "OPropertyBrowserController::describePropertyLine: handler did not provide a display name for '" + <<_rProperty.Name << "'!" ); + #endif + _rDescriptor.DisplayName = _rProperty.Name; + } + + PropertyState ePropertyState( _rDescriptor.xPropertyHandler->getPropertyState( _rProperty.Name ) ); + if ( PropertyState_AMBIGUOUS_VALUE == ePropertyState ) + { + _rDescriptor.bUnknownValue = true; + _rDescriptor.aValue.clear(); + } + + _rDescriptor.bReadOnly = impl_isReadOnlyModel_throw(); + + // for ui-testing try and distinguish different instances of the controls + auto xWindow = _rDescriptor.Control->getControlWindow(); + if (weld::TransportAsXWindow* pTunnel = dynamic_cast<weld::TransportAsXWindow*>(xWindow.get())) + { + weld::Widget* m_pControlWindow = pTunnel->getWidget(); + if (m_pControlWindow) + m_pControlWindow->set_buildable_name(m_pControlWindow->get_buildable_name() + "-" + _rDescriptor.DisplayName.toUtf8()); + } + + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "OPropertyBrowserController::describePropertyLine" ); + } + } + + + void OPropertyBrowserController::impl_buildCategories_throw() + { + OSL_PRECOND( m_aPageIds.empty(), "OPropertyBrowserController::impl_buildCategories_throw: duplicate call!" ); + + StlSyntaxSequence< PropertyCategoryDescriptor > aCategories; + if ( m_xModel.is() ) + aCategories = StlSyntaxSequence< PropertyCategoryDescriptor >(m_xModel->describeCategories()); + + for (auto const& category : aCategories) + { + OSL_ENSURE( m_aPageIds.find( category.ProgrammaticName ) == m_aPageIds.end(), + "OPropertyBrowserController::impl_buildCategories_throw: duplicate programmatic name!" ); + + m_aPageIds[ category.ProgrammaticName ] = + getPropertyBox().AppendPage( category.UIName, HelpIdUrl::getHelpId( category.HelpURL ) ); + } + } + + + void OPropertyBrowserController::UpdateUI() + { + try + { + if ( !haveView() ) + // too early, will return later + return; + + // create our tab pages + impl_buildCategories_throw(); + // (and allow for pages to be actually unused) + std::set< sal_uInt16 > aUsedPages; + + // when building the UI below, remember which properties are actuating, + // to allow for an initial actuatingPropertyChanged call + std::vector< OUString > aActuatingProperties; + std::vector< Any > aActuatingPropertyValues; + + // ask the handlers to describe the property UI, and insert the resulting + // entries into our list boxes + for (auto const& property : m_aProperties) + { + OLineDescriptor aDescriptor; + describePropertyLine( property.second, aDescriptor ); + + bool bIsActuatingProperty = impl_isActuatingProperty_nothrow( property.second.Name ); + + SAL_WARN_IF( aDescriptor.Category.isEmpty(), "extensions.propctrlr", + "OPropertyBrowserController::UpdateUI: empty category provided for property '" + << property.second.Name << "'!"); + // finally insert this property control + sal_uInt16 nTargetPageId = impl_getPageIdForCategory_nothrow( aDescriptor.Category ); + if ( nTargetPageId == sal_uInt16(-1) ) + { + // this category does not yet exist. This is allowed, as an inspector model might be lazy, and not provide + // any category information of its own. In this case, we have a fallback ... + m_aPageIds[ aDescriptor.Category ] = + getPropertyBox().AppendPage( aDescriptor.Category, OString() ); + nTargetPageId = impl_getPageIdForCategory_nothrow( aDescriptor.Category ); + } + + getPropertyBox().InsertEntry( aDescriptor, nTargetPageId ); + aUsedPages.insert( nTargetPageId ); + + // if it's an actuating property, remember it + if ( bIsActuatingProperty ) + { + aActuatingProperties.push_back( property.second.Name ); + aActuatingPropertyValues.push_back( impl_getPropertyValue_throw( property.second.Name ) ); + } + } + + // update any dependencies for the actuating properties which we encountered + { + std::vector< Any >::const_iterator aPropertyValue = aActuatingPropertyValues.begin(); + for (auto const& actuatingProperty : aActuatingProperties) + { + impl_broadcastPropertyChange_nothrow( actuatingProperty, *aPropertyValue, *aPropertyValue, true ); + ++aPropertyValue; + } + } + + // remove any unused pages (which we did not encounter properties for) + HashString2Int16 aSurvivingPageIds; + for (auto const& pageId : m_aPageIds) + { + if ( aUsedPages.find( pageId.second ) == aUsedPages.end() ) + getPropertyBox().RemovePage( pageId.second ); + else + aSurvivingPageIds.insert(pageId); + } + m_aPageIds.swap( aSurvivingPageIds ); + + getPropertyBox().Show(); + + // activate the first page + if ( !m_aPageIds.empty() ) + { + Sequence< PropertyCategoryDescriptor > aCategories( m_xModel->describeCategories() ); + if ( aCategories.hasElements() ) + m_xPropView->activatePage( m_aPageIds[ aCategories[0].ProgrammaticName ] ); + else + // allowed: if we default-created the pages ... + m_xPropView->activatePage( m_aPageIds.begin()->second ); + } + + // activate the previously active page (if possible) + if ( !m_sLastValidPageSelection.isEmpty() ) + m_sPageSelection = m_sLastValidPageSelection; + selectPageFromViewData(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + + void OPropertyBrowserController::Clicked( const OUString& _rName, bool _bPrimary ) + { + try + { + // since the browse buttons do not get the focus when clicked with the mouse, + // we need to commit the changes in the current property field + getPropertyBox().CommitModified(); + + PropertyHandlerRepository::const_iterator handler = m_aPropertyHandlers.find( _rName ); + DBG_ASSERT( handler != m_aPropertyHandlers.end(), "OPropertyBrowserController::Clicked: a property without handler? This will crash!" ); + + ComposedUIAutoFireGuard aAutoFireGuard( *m_pUIRequestComposer ); + + Any aData; + m_xInteractiveHandler = handler->second; + InteractiveSelectionResult eResult = + handler->second->onInteractivePropertySelection( _rName, _bPrimary, aData, + m_pUIRequestComposer->getUIForPropertyHandler( handler->second ) ); + + switch ( eResult ) + { + case InteractiveSelectionResult_Cancelled: + case InteractiveSelectionResult_Success: + // okay, nothing to do + break; + case InteractiveSelectionResult_ObtainedValue: + handler->second->setPropertyValue( _rName, aData ); + break; + case InteractiveSelectionResult_Pending: + // also okay, we expect that the handler has disabled the UI as necessary + break; + default: + OSL_FAIL( "OPropertyBrowserController::Clicked: unknown result value!" ); + break; + } + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + m_xInteractiveHandler = nullptr; + } + + + bool OPropertyBrowserController::hasPropertyByName( const OUString& _rName ) + { + for (auto const& property : m_aProperties) + if ( property.second.Name == _rName ) + return true; + return false; + } + + + void OPropertyBrowserController::Commit( const OUString& rName, const Any& _rValue ) + { + try + { + OUString sPlcHolder = PcrRes(RID_EMBED_IMAGE_PLACEHOLDER); + bool bIsPlaceHolderValue = false; + + if ( rName == PROPERTY_IMAGE_URL ) + { + // if the prop value is the PlaceHolder + // can ignore it + OUString sVal; + _rValue >>= sVal; + if ( sVal == sPlcHolder ) + bIsPlaceHolderValue = true; + } + m_sCommittingProperty = rName; + + bool bIsActuatingProperty = impl_isActuatingProperty_nothrow( rName ); + + Any aOldValue; + if ( bIsActuatingProperty ) + aOldValue = impl_getPropertyValue_throw( rName ); + + // do we have a dedicated handler for this property, which we can delegate some tasks to? + PropertyHandlerRef handler = impl_getHandlerForProperty_throw( rName ); + + + // set the value ( only if it's not a placeholder ) + if ( !bIsPlaceHolderValue ) + handler->setPropertyValue( rName, _rValue ); + + + // re-retrieve the value + Any aNormalizedValue = handler->getPropertyValue( rName ); + + // care for any inter-property dependencies + if ( bIsActuatingProperty ) + impl_broadcastPropertyChange_nothrow( rName, aNormalizedValue, aOldValue, false ); + + // and display it again. This ensures proper formatting + getPropertyBox().SetPropertyValue( rName, aNormalizedValue, false ); + } + catch(const PropertyVetoException& eVetoException) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(m_xPropView->getPropertyBox().getWidget(), + VclMessageType::Info, VclButtonsType::Ok, + eVetoException.Message)); + xInfoBox->run(); + PropertyHandlerRef handler = impl_getHandlerForProperty_throw( rName ); + Any aNormalizedValue = handler->getPropertyValue( rName ); + getPropertyBox().SetPropertyValue( rName, aNormalizedValue, false ); + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.propctrlr", ""); + } + + m_sCommittingProperty.clear(); + } + + + void OPropertyBrowserController::focusGained( const Reference< XPropertyControl >& Control ) + { + m_aControlObservers.notifyEach( &XPropertyControlObserver::focusGained, Control ); + } + + + void OPropertyBrowserController::valueChanged( const Reference< XPropertyControl >& Control ) + { + m_aControlObservers.notifyEach( &XPropertyControlObserver::valueChanged, Control ); + } + + + namespace + { + Reference< XPropertyHandler > lcl_createHandler( const Reference<XComponentContext>& _rContext, const Any& _rFactoryDescriptor ) + { + Reference< XPropertyHandler > xHandler; + + OUString sServiceName; + Reference< XSingleServiceFactory > xServiceFac; + Reference< XSingleComponentFactory > xComponentFac; + + if ( _rFactoryDescriptor >>= sServiceName ) + xHandler.set( _rContext->getServiceManager()->createInstanceWithContext( sServiceName, _rContext ), UNO_QUERY ); + else if ( _rFactoryDescriptor >>= xServiceFac ) + xHandler.set(xServiceFac->createInstance(), css::uno::UNO_QUERY); + else if ( _rFactoryDescriptor >>= xComponentFac ) + xHandler.set(xComponentFac->createInstanceWithContext( _rContext ), css::uno::UNO_QUERY); + OSL_ENSURE(xHandler.is(),"lcl_createHandler: Can not create handler"); + return xHandler; + } + } + + + void OPropertyBrowserController::getPropertyHandlers( const InterfaceArray& _rObjects, PropertyHandlerArray& _rHandlers ) + { + _rHandlers.resize( 0 ); + if ( _rObjects.empty() ) + return; + + Sequence< Any > aHandlerFactories; + if ( m_xModel.is() ) + aHandlerFactories = m_xModel->getHandlerFactories(); + + for ( auto const & handlerFactory : std::as_const(aHandlerFactories) ) + { + if ( _rObjects.size() == 1 ) + { // we're inspecting only one object -> one handler + Reference< XPropertyHandler > xHandler( lcl_createHandler( m_xContext, handlerFactory ) ); + if ( xHandler.is() ) + { + xHandler->inspect( _rObjects[0] ); + _rHandlers.push_back( xHandler ); + } + } + else + { + // create a single handler for every single object + std::vector< Reference< XPropertyHandler > > aSingleHandlers( _rObjects.size() ); + std::vector< Reference< XPropertyHandler > >::iterator pHandler = aSingleHandlers.begin(); + + for (auto const& elem : _rObjects) + { + *pHandler = lcl_createHandler( m_xContext, handlerFactory ); + if ( pHandler->is() ) + { + (*pHandler)->inspect(elem); + ++pHandler; + } + } + aSingleHandlers.resize( pHandler - aSingleHandlers.begin() ); + + // then create a handler which composes information out of those single handlers + if ( !aSingleHandlers.empty() ) + _rHandlers.push_back( new PropertyComposer( std::move(aSingleHandlers) ) ); + } + } + + // note that the handlers will not be used by our caller, if they indicate that there are no + // properties they feel responsible for + } + + + bool OPropertyBrowserController::impl_findObjectProperty_nothrow( const OUString& _rName, OrderedPropertyMap::const_iterator* _pProperty ) + { + OrderedPropertyMap::const_iterator search = std::find_if(m_aProperties.begin(), m_aProperties.end(), + [&_rName](const OrderedPropertyMap::value_type& rEntry) { return rEntry.second.Name == _rName; }); + if ( _pProperty ) + *_pProperty = search; + return ( search != m_aProperties.end() ); + } + + + void OPropertyBrowserController::rebuildPropertyUI( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !haveView() ) + throw RuntimeException(); + + OrderedPropertyMap::const_iterator propertyPos; + if ( !impl_findObjectProperty_nothrow( _rPropertyName, &propertyPos ) ) + return; + + OLineDescriptor aDescriptor; + try + { + describePropertyLine( propertyPos->second, aDescriptor ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "OPropertyBrowserController::rebuildPropertyUI" ); + } + + getPropertyBox().ChangeEntry( aDescriptor ); + } + + + void OPropertyBrowserController::enablePropertyUI( const OUString& _rPropertyName, sal_Bool _bEnable ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !haveView() ) + throw RuntimeException(); + + if ( !impl_findObjectProperty_nothrow( _rPropertyName ) ) + return; + + getPropertyBox().EnablePropertyLine( _rPropertyName, _bEnable ); + } + + + void OPropertyBrowserController::enablePropertyUIElements( const OUString& _rPropertyName, sal_Int16 _nElements, sal_Bool _bEnable ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !haveView() ) + throw RuntimeException(); + + if ( !impl_findObjectProperty_nothrow( _rPropertyName ) ) + return; + + getPropertyBox().EnablePropertyControls( _rPropertyName, _nElements, _bEnable ); + } + + + void OPropertyBrowserController::showPropertyUI( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !haveView() ) + throw RuntimeException(); + + // look up the property in our object properties + OrderedPropertyMap::const_iterator propertyPos; + if ( !impl_findObjectProperty_nothrow( _rPropertyName, &propertyPos ) ) + return; + + if ( getPropertyBox().GetPropertyPos( _rPropertyName ) != EDITOR_LIST_ENTRY_NOTFOUND ) + { + rebuildPropertyUI( _rPropertyName ); + return; + } + + OLineDescriptor aDescriptor; + describePropertyLine( propertyPos->second, aDescriptor ); + + // look for the position to insert the property + + // side note: The methods GetPropertyPos and InsertEntry of the OPropertyEditor work + // only on the current page. This implies that it's impossible to use this method here + // to show property lines which are *not* on the current page. + // This is sufficient for now, but should be changed in the future. + + // by definition, the properties in m_aProperties are in the order in which they appear in the UI + // So all we need is a predecessor of pProperty in m_aProperties + sal_uInt16 nUIPos = EDITOR_LIST_ENTRY_NOTFOUND; + do + { + if ( propertyPos != m_aProperties.begin() ) + --propertyPos; + nUIPos = getPropertyBox().GetPropertyPos( propertyPos->second.Name ); + } + while ( ( nUIPos == EDITOR_LIST_ENTRY_NOTFOUND ) && ( propertyPos != m_aProperties.begin() ) ); + + if ( nUIPos == EDITOR_LIST_ENTRY_NOTFOUND ) + // insert at the very top + nUIPos = 0; + else + // insert right after the predecessor we found + ++nUIPos; + + getPropertyBox().InsertEntry( + aDescriptor, impl_getPageIdForCategory_nothrow( aDescriptor.Category ), nUIPos ); + } + + + void OPropertyBrowserController::hidePropertyUI( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !haveView() ) + throw RuntimeException(); + + if ( !impl_findObjectProperty_nothrow( _rPropertyName ) ) + return; + + getPropertyBox().RemoveEntry( _rPropertyName ); + } + + + void OPropertyBrowserController::showCategory( const OUString& rCategory, sal_Bool bShow ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !haveView() ) + throw RuntimeException(); + + sal_uInt16 nPageId = impl_getPageIdForCategory_nothrow( rCategory ); + OSL_ENSURE( nPageId != sal_uInt16(-1), "OPropertyBrowserController::showCategory: invalid category!" ); + + getPropertyBox().ShowPropertyPage( nPageId, bShow ); + } + + + Reference< XPropertyControl > SAL_CALL OPropertyBrowserController::getPropertyControl( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !haveView() ) + throw RuntimeException(); + + Reference< XPropertyControl > xControl( getPropertyBox().GetPropertyControl( _rPropertyName ) ); + return xControl; + } + + + void SAL_CALL OPropertyBrowserController::registerControlObserver( const Reference< XPropertyControlObserver >& Observer ) + { + m_aControlObservers.addInterface( Observer ); + } + + + void SAL_CALL OPropertyBrowserController::revokeControlObserver( const Reference< XPropertyControlObserver >& Observer ) + { + m_aControlObservers.removeInterface( Observer ); + } + + + void SAL_CALL OPropertyBrowserController::setHelpSectionText( const OUString& _rHelpText ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( !haveView() ) + throw DisposedException(); + + if ( !getPropertyBox().HasHelpSection() ) + throw NoSupportException(); + + getPropertyBox().SetHelpText( _rHelpText ); + } + + + void OPropertyBrowserController::impl_broadcastPropertyChange_nothrow( const OUString& _rPropertyName, const Any& _rNewValue, const Any& _rOldValue, bool _bFirstTimeInit ) const + { + // are there one or more handlers which are interested in the actuation? + std::pair< PropertyHandlerMultiRepository::const_iterator, PropertyHandlerMultiRepository::const_iterator > aInterestedHandlers = + m_aDependencyHandlers.equal_range( _rPropertyName ); + if ( aInterestedHandlers.first == aInterestedHandlers.second ) + // none of our handlers is interested in this + return; + + ComposedUIAutoFireGuard aAutoFireGuard( *m_pUIRequestComposer ); + try + { + // collect the responses from all interested handlers + PropertyHandlerMultiRepository::const_iterator handler = aInterestedHandlers.first; + while ( handler != aInterestedHandlers.second ) + { + handler->second->actuatingPropertyChanged( _rPropertyName, _rNewValue, _rOldValue, + m_pUIRequestComposer->getUIForPropertyHandler( handler->second ), + _bFirstTimeInit ); + ++handler; + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_OPropertyBrowserController_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::OPropertyBrowserController(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |