/* -*- 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 .
*/
void OPropertyBrowserController::impl_initializeView_nothrow()
{
OSL_PRECOND( haveView(), "OPropertyBrowserController::impl_initializeView_nothrow: not to be called when we have no view!" ); if ( !haveView() ) return;
bool OPropertyBrowserController::impl_isReadOnlyModel_throw() const
{ if ( !m_xModel.is() ) returnfalse;
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;
// 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) );
}
Reference< XObjectInspectorUI > SAL_CALL OPropertyBrowserController::getInspectorUI()
{ // we're derived from this interface, though we do not expose it in queryInterface and getTypes. returnthis;
}
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();
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 >();
}
if (_rxFrame.is() && haveView()) throw RuntimeException(u"Unable to attach to a second frame."_ustr,*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();
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() ) returnfalse;
bool OPropertyBrowserController::suspendPropertyHandlers_nothrow( bool _bSuspend )
{
PropertyHandlerArray aAllHandlers; // will contain every handler exactly once for (autoconst& 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 (autoconst& handler : aAllHandlers)
{ try
{ if ( !handler->suspend( _bSuspend ) ) if ( _bSuspend ) // if we're not suspending, but reactivating, ignore the error returnfalse;
} catch( const Exception& )
{
TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "OPropertyBrowserController::suspendPropertyHandlers_nothrow" );
}
} returntrue;
}
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 returnfalse;
}
if ( !suspendAll_nothrow() ) returnfalse;
// commit the editor's content if ( haveView() )
getPropertyBox().CommitModified();
// stop listening
stopContainerWindowListening();
// outta here returntrue;
}
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 >();
}
// 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 ) );
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 );
// 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 );
}
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 (autoconst& 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 (autoconst& propertyHandler : m_aPropertyHandlers) if ( std::find( aAllHandlers.begin(), aAllHandlers.end(), propertyHandler.second ) == aAllHandlers.end() )
aAllHandlers.push_back( propertyHandler.second );
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( std::max<size_t>(aProperties.size() + aThisHandlersProperties.size(), aProperties.size() * 2) ); for (constauto & 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 (constauto & 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 (constauto & 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 (constauto & 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 (autoconst& 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", "");
}
}
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;
}
// 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);
}
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 (autoconst& property : m_aProperties)
{
OLineDescriptor aDescriptor;
describePropertyLine( property.second, aDescriptor );
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, {});
nTargetPageId = impl_getPageIdForCategory_nothrow( aDescriptor.Category );
}
// 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 (autoconst& actuatingProperty : aActuatingProperties)
{
impl_broadcastPropertyChange_nothrow( actuatingProperty, *aPropertyValue, *aPropertyValue, true );
++aPropertyValue;
}
}
// remove any unused pages (which we did not encounter properties for)
HashString2Int16 aSurvivingPageIds; for (autoconst& 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!" );
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;
}
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;
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 );
// 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
}
// look up the property in our object properties
OrderedPropertyMap::const_iterator propertyPos; if ( !impl_findObjectProperty_nothrow( _rPropertyName, &propertyPos ) ) return;
// 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;
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;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.