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 --- forms/source/misc/InterfaceContainer.cxx | 1315 ++++++++++++++++++++++++++++++ forms/source/misc/componenttools.cxx | 105 +++ forms/source/misc/limitedformats.cxx | 379 +++++++++ forms/source/misc/property.cxx | 220 +++++ 4 files changed, 2019 insertions(+) create mode 100644 forms/source/misc/InterfaceContainer.cxx create mode 100644 forms/source/misc/componenttools.cxx create mode 100644 forms/source/misc/limitedformats.cxx create mode 100644 forms/source/misc/property.cxx (limited to 'forms/source/misc') diff --git a/forms/source/misc/InterfaceContainer.cxx b/forms/source/misc/InterfaceContainer.cxx new file mode 100644 index 000000000..c797124bf --- /dev/null +++ b/forms/source/misc/InterfaceContainer.cxx @@ -0,0 +1,1315 @@ +/* -*- 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 + +namespace frm +{ + + +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::document; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::script; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::form; +using namespace ::com::sun::star::util; + +namespace +{ + + void lcl_throwIllegalArgumentException() + { + throw IllegalArgumentException(); + } +} + +static bool +lcl_hasVbaEvents( const Sequence< ScriptEventDescriptor >& sEvents ) +{ + for ( auto const& rDesc : sEvents ) + { + if ( rDesc.ScriptType == "VBAInterop" ) + return true; + } + return false; +} + +static Sequence< ScriptEventDescriptor > +lcl_stripVbaEvents( const Sequence< ScriptEventDescriptor >& sEvents ) +{ + Sequence< ScriptEventDescriptor > sStripped( sEvents.getLength() ); + ScriptEventDescriptor* pStripped = sStripped.getArray(); + + sal_Int32 nCopied = 0; + for ( auto const& rDesc : sEvents ) + { + if ( rDesc.ScriptType != "VBAInterop" ) + { + pStripped[ nCopied++ ] = rDesc; + } + } + sStripped.realloc( nCopied ); + return sStripped; +} + +void OInterfaceContainer::impl_addVbEvents_nolck_nothrow( const sal_Int32 i_nIndex ) +{ + // we are dealing with form controls + try + { + do + { + Reference< XModel > xDoc( getXModel( static_cast< XContainer *> ( this ) ) ); + if ( !xDoc.is() ) + break; + + Reference< XMultiServiceFactory > xDocFac( xDoc, UNO_QUERY_THROW ); + Reference< XCodeNameQuery > xNameQuery( xDocFac->createInstance("ooo.vba.VBACodeNameProvider"), UNO_QUERY ); + if ( !xNameQuery.is() ) + break; + + ::osl::MutexGuard aGuard( m_rMutex ); + bool hasVBABindings = lcl_hasVbaEvents( m_xEventAttacher->getScriptEvents( i_nIndex ) ); + if ( hasVBABindings ) + break; + + Reference< XInterface > xElement( getByIndex( i_nIndex ) , UNO_QUERY_THROW ); + Reference< XForm > xElementAsForm( xElement, UNO_QUERY ); + if ( xElementAsForm.is() ) + break; + + // Try getting the code name from the container first (faster), + // then from the element if that fails (slower). + Reference xThis = static_cast(this); + OUString sCodeName = xNameQuery->getCodeNameForContainer(xThis); + if (sCodeName.isEmpty()) + sCodeName = xNameQuery->getCodeNameForObject(xElement); + + Reference< XPropertySet > xProps( xElement, UNO_QUERY_THROW ); + OUString sServiceName; + xProps->getPropertyValue("DefaultControl") >>= sServiceName; + + Reference< ooo::vba::XVBAToOOEventDescGen > xDescSupplier( m_xContext->getServiceManager()->createInstanceWithContext("ooo.vba.VBAToOOEventDesc", m_xContext), UNO_QUERY_THROW ); + Sequence< ScriptEventDescriptor > vbaEvents = xDescSupplier->getEventDescriptions( sServiceName , sCodeName ); + + // register the vba script events + m_xEventAttacher->registerScriptEvents( i_nIndex, vbaEvents ); + } + while ( false ); + } + catch ( const ServiceNotRegisteredException& ) + { + // silence this, not all document types support the ooo.vba.VBACodeNameProvider service + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("forms.misc"); + } + +} + +ElementDescription::ElementDescription( ) +{ +} + + +OInterfaceContainer::OInterfaceContainer( + const Reference& _rxContext, + ::osl::Mutex& _rMutex, + const Type& _rElementType) + :OInterfaceContainer_BASE() + ,m_rMutex(_rMutex) + ,m_aContainerListeners(_rMutex) + ,m_aElementType(_rElementType) + ,m_xContext(_rxContext) +{ + impl_createEventAttacher_nothrow(); +} + + +OInterfaceContainer::OInterfaceContainer( ::osl::Mutex& _rMutex, const OInterfaceContainer& _cloneSource ) + :OInterfaceContainer_BASE() + ,m_rMutex( _rMutex ) + ,m_aContainerListeners( _rMutex ) + ,m_aElementType( _cloneSource.m_aElementType ) + ,m_xContext( _cloneSource.m_xContext ) +{ + impl_createEventAttacher_nothrow(); +} + +void OInterfaceContainer::clonedFrom(const OInterfaceContainer& _cloneSource) +{ + try + { + const Reference< XIndexAccess > xSourceHierarchy( const_cast< OInterfaceContainer* >( &_cloneSource ) ); + const sal_Int32 nCount = xSourceHierarchy->getCount(); + for ( sal_Int32 i=0; i xCloneable( xSourceHierarchy->getByIndex( i ), UNO_QUERY_THROW ); + Reference< XInterface > xClone( xCloneable->createClone() ); + insertByIndex( i, Any( xClone ) ); + } + } + catch (const RuntimeException&) + { + throw; + } + catch (const Exception&) + { + throw WrappedTargetRuntimeException( + "Could not clone the given interface hierarchy.", + static_cast< XIndexContainer* >( const_cast< OInterfaceContainer* >( &_cloneSource ) ), + ::cppu::getCaughtException() + ); + } +} + +void OInterfaceContainer::impl_createEventAttacher_nothrow() +{ + try + { + m_xEventAttacher.set( ::comphelper::createEventAttacherManager( m_xContext ), UNO_SET_THROW ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("forms.misc"); + } +} + + +OInterfaceContainer::~OInterfaceContainer() +{ +} + + +void OInterfaceContainer::disposing() +{ + // dispose all elements + for (sal_Int32 i = m_aItems.size(); i > 0; --i) + { + Reference xSet(m_aItems[i - 1], UNO_QUERY); + if (xSet.is()) + xSet->removePropertyChangeListener(PROPERTY_NAME, this); + + // revoke event knittings + if ( m_xEventAttacher.is() ) + { + m_xEventAttacher->detach( i - 1, Reference(xSet, UNO_QUERY) ); + m_xEventAttacher->removeEntry( i - 1 ); + } + + Reference xComponent(xSet, UNO_QUERY); + if (xComponent.is()) + xComponent->dispose(); + } + m_aMap.clear(); + m_aItems.clear(); + + css::lang::EventObject aEvt(static_cast(this)); + m_aContainerListeners.disposeAndClear(aEvt); +} + +// XPersistObject + +namespace +{ + + void lcl_saveEvents( ::std::vector< Sequence< ScriptEventDescriptor > >& _rSave, + const Reference< XEventAttacherManager >& _rxManager, const sal_Int32 _nItemCount ) + { + OSL_ENSURE( _rxManager.is(), "lcl_saveEvents: invalid event attacher manager!" ); + if ( !_rxManager.is() ) + return; + + // reserve the space needed + _rSave.reserve( _nItemCount ); + + // copy the events + for (sal_Int32 i=0; i<_nItemCount; ++i) + _rSave.push_back(_rxManager->getScriptEvents( i )); + } + + + void lcl_restoreEvents( const ::std::vector< Sequence< ScriptEventDescriptor > >& _rSave, + const Reference< XEventAttacherManager >& _rxManager ) + { + OSL_ENSURE( _rxManager.is(), "lcl_restoreEvents: invalid event attacher manager!" ); + if ( !_rxManager.is() ) + return; + + sal_Int32 i=0; + for (auto const& elem : _rSave) + { + _rxManager->revokeScriptEvents( i ); + _rxManager->registerScriptEvents(i, elem); + ++i; + } + } +} + + +void SAL_CALL OInterfaceContainer::writeEvents(const Reference& _rxOutStream) +{ + // We're writing a document in SO 5.2 format (or even from earlier versions) + // -> convert the events from the new runtime format to the format of the 5.2 files + // but before, remember the current script events set for our children + ::std::vector< Sequence< ScriptEventDescriptor > > aSave; + if ( m_xEventAttacher.is() ) + lcl_saveEvents( aSave, m_xEventAttacher, m_aItems.size() ); + + transformEvents(); + + try + { + Reference xMark(_rxOutStream, UNO_QUERY); + sal_Int32 nMark = xMark->createMark(); + + sal_Int32 nObjLen = 0; + _rxOutStream->writeLong(nObjLen); + + Reference xScripts(m_xEventAttacher, UNO_QUERY); + if (xScripts.is()) + xScripts->write(_rxOutStream); + + // Determine length + nObjLen = xMark->offsetToMark(nMark) - 4; + xMark->jumpToMark(nMark); + _rxOutStream->writeLong(nObjLen); + xMark->jumpToFurthest(); + xMark->deleteMark(nMark); + } + catch( const Exception& ) + { + // restore the events + if ( m_xEventAttacher.is() ) + lcl_restoreEvents( aSave, m_xEventAttacher ); + throw; + } + + // restore the events + if ( m_xEventAttacher.is() ) + lcl_restoreEvents( aSave, m_xEventAttacher ); +} + +namespace { + +struct TransformEventTo52Format +{ + void operator()( ScriptEventDescriptor& _rDescriptor ) + { + if ( _rDescriptor.ScriptType != "StarBasic" ) + return; + + // it's a starbasic macro + sal_Int32 nPrefixLength = _rDescriptor.ScriptCode.indexOf( ':' ); + if ( 0 <= nPrefixLength ) + { // the macro name does not already contain a : +#ifdef DBG_UTIL + const OUString sPrefix = _rDescriptor.ScriptCode.copy( 0, nPrefixLength ); + DBG_ASSERT( sPrefix == "document" + || sPrefix == "application", + "TransformEventTo52Format: invalid (unknown) prefix!" ); +#endif + // cut the prefix + _rDescriptor.ScriptCode = _rDescriptor.ScriptCode.copy( nPrefixLength + 1 ); + } + } +}; + +} + +void OInterfaceContainer::transformEvents() +{ + OSL_ENSURE( m_xEventAttacher.is(), "OInterfaceContainer::transformEvents: no event attacher manager!" ); + if ( !m_xEventAttacher.is() ) + return; + + try + { + // loop through all our children + sal_Int32 nItems = m_aItems.size(); + Sequence< ScriptEventDescriptor > aChildEvents; + + for (sal_Int32 i=0; igetScriptEvents( i ); + + if ( aChildEvents.hasElements() ) + { + // do the transformation + auto [begin, end] = asNonConstRange(aChildEvents); + ::std::for_each( begin, end, TransformEventTo52Format() ); + + // revoke the script events + m_xEventAttacher->revokeScriptEvents( i ); + // and re-register them + m_xEventAttacher->registerScriptEvents( i, aChildEvents ); + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("forms.misc"); + } +} + + +void SAL_CALL OInterfaceContainer::readEvents(const Reference& _rxInStream) +{ + ::osl::MutexGuard aGuard( m_rMutex ); + + // Read scripting info + Reference xMark(_rxInStream, UNO_QUERY); + sal_Int32 nObjLen = _rxInStream->readLong(); + if (nObjLen) + { + sal_Int32 nMark = xMark->createMark(); + Reference xObj(m_xEventAttacher, UNO_QUERY); + if (xObj.is()) + xObj->read(_rxInStream); + xMark->jumpToMark(nMark); + _rxInStream->skipBytes(nObjLen); + xMark->deleteMark(nMark); + } + + // Read Attachment + if ( m_xEventAttacher.is() ) + { + sal_Int32 i=0; + for (auto const& item : m_aItems) + { + Reference< XInterface > xAsIFace( item, UNO_QUERY ); // important to normalize this... + Reference< XPropertySet > xAsSet( xAsIFace, UNO_QUERY ); + m_xEventAttacher->attach( i++, xAsIFace, Any( xAsSet ) ); + } + } +} + + +void SAL_CALL OInterfaceContainer::write( const Reference< XObjectOutputStream >& _rxOutStream ) +{ + ::osl::MutexGuard aGuard( m_rMutex ); + sal_Int32 nLen = m_aItems.size(); + + // Write length + _rxOutStream->writeLong(nLen); + + if (!nLen) + return; + + // 1. Version + _rxOutStream->writeShort(0x0001); + + // 2. Objects + for (sal_Int32 i = 0; i < nLen; i++) + { + Reference xObj(m_aItems[i], UNO_QUERY); + if (xObj.is()) + _rxOutStream->writeObject(xObj); + else + { + // Error + } + } + + // 3. Scripts + writeEvents(_rxOutStream); +} + + +namespace +{ + Reference< XPersistObject > lcl_createPlaceHolder( const Reference< XComponentContext >& _rxORB ) + { + Reference< XPersistObject > xObject( _rxORB->getServiceManager()->createInstanceWithContext(FRM_COMPONENT_HIDDENCONTROL, _rxORB), UNO_QUERY ); + DBG_ASSERT( xObject.is(), "lcl_createPlaceHolder: could not create a substitute for the unknown object!" ); + if ( xObject.is() ) + { + // set some properties describing what we did + Reference< XPropertySet > xObjProps( xObject, UNO_QUERY ); + if ( xObject.is() ) + { + try + { + xObjProps->setPropertyValue( PROPERTY_NAME, Any( ResourceManager::loadString(RID_STR_CONTROL_SUBSTITUTED_NAME) ) ); + xObjProps->setPropertyValue( PROPERTY_TAG, Any( ResourceManager::loadString(RID_STR_CONTROL_SUBSTITUTED_EPXPLAIN) ) ); + } + catch(const Exception&) + { + } + } + } + return xObject; + } +} + + +void SAL_CALL OInterfaceContainer::read( const Reference< XObjectInputStream >& _rxInStream ) +{ + ::osl::MutexGuard aGuard( m_rMutex ); + + // after ::read the object is expected to be in the state it was when ::write was called, so we have + // to empty ourself here + while (getCount()) + removeByIndex(0); + + // Only writes depending on the length + sal_Int32 nLen = _rxInStream->readLong(); + + if (nLen) + { + // 1. Version + _rxInStream->readShort(); + + // 2. Objects + for (sal_Int32 i = 0; i < nLen; i++) + { + Reference xObj; + try + { + xObj = _rxInStream->readObject(); + } + catch(const WrongFormatException&) + { + // the object could not be read + // create an object (so the readEvents below will assign the events to the right controls) + xObj = lcl_createPlaceHolder( m_xContext ); + if ( !xObj.is() ) + // couldn't handle it + throw; + } + catch(const Exception&) + { + // Clear the map + while (!m_aItems.empty()) + removeElementsNoEvents(); + + // Rethrow the exception + throw; + } + + if ( xObj.is() ) + { + Reference< XPropertySet > xElement( xObj, UNO_QUERY ); + try + { + implInsert( + m_aItems.size(), // position + xElement, // element to insert + false, // no event attacher manager handling + nullptr, // not yet approved - let implInsert do it + true // fire the event + ); + } + catch( const Exception& ) + { + SAL_WARN("forms.misc", "OInterfaceContainerHelper3::read: reading succeeded, but not inserting!" ); + // create a placeholder + xElement.set(lcl_createPlaceHolder( m_xContext ), css::uno::UNO_QUERY); + if ( !xElement.is() ) + // couldn't handle it + throw; + // insert the placeholder + implInsert( m_aItems.size(), xElement, false, nullptr, true ); + } + } + } + + readEvents(_rxInStream); + } + else + { + try + { + m_xEventAttacher = ::comphelper::createEventAttacherManager( m_xContext ); + OSL_ENSURE( m_xEventAttacher.is(), "OInterfaceContainer::read: could not create an event attacher manager!" ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("forms.misc"); + } + } +} + +// XContainer + +void SAL_CALL OInterfaceContainer::addContainerListener(const Reference& _rxListener) +{ + m_aContainerListeners.addInterface(_rxListener); +} + + +void SAL_CALL OInterfaceContainer::removeContainerListener(const Reference& _rxListener) +{ + m_aContainerListeners.removeInterface(_rxListener); +} + +// XEventListener + +void SAL_CALL OInterfaceContainer::disposing(const css::lang::EventObject& _rSource) +{ + ::osl::MutexGuard aGuard( m_rMutex ); + + Reference< XInterface > xSource( _rSource.Source, UNO_QUERY ); + // normalized source + + OInterfaceArray::iterator j; + for ( j = m_aItems.begin(); j != m_aItems.end(); ++j ) + { + DBG_ASSERT( j->get() == Reference< XInterface >( *j, UNO_QUERY ).get(), + "OInterfaceContainer::disposing: vector element not normalized!" ); + + if ( xSource.get() == j->get() ) + // found the element + break; + } + + if ( m_aItems.end() == j ) + return; + + m_aItems.erase(j); + + // look up in, and erase from, m_aMap, too + OInterfaceMap::iterator i = m_aMap.begin(); + while ( i != m_aMap.end() ) + { + DBG_ASSERT( i->second.get() == Reference< XInterface >( i->second, UNO_QUERY ).get(), + "OInterfaceContainer::disposing: map element not normalized!" ); + + if ( i->second.get() == xSource.get() ) + { + // found it + m_aMap.erase(i); + break; + } + + ++i; + + DBG_ASSERT( i != m_aMap.end(), "OInterfaceContainer::disposing: inconsistency: the element was in m_aItems, but not in m_aMap!" ); + } +} + +// XPropertyChangeListener + +void OInterfaceContainer::propertyChange(const PropertyChangeEvent& evt) { + if (evt.PropertyName != PROPERTY_NAME) + return; + + ::osl::MutexGuard aGuard( m_rMutex ); + auto range = m_aMap.equal_range(::comphelper::getString(evt.OldValue)); + for (auto it = range.first; it != range.second; ++it) + if (it->second == evt.Source) + { + css::uno::Reference xCorrectType(it->second); + m_aMap.erase(it); + m_aMap.insert(::std::pair >(::comphelper::getString(evt.NewValue),xCorrectType)); + break; + } +} + +// XElementAccess + +sal_Bool SAL_CALL OInterfaceContainer::hasElements() +{ + return !m_aMap.empty(); +} + + +Type SAL_CALL OInterfaceContainer::getElementType() +{ + return m_aElementType; +} + +// XEnumerationAccess + +Reference SAL_CALL OInterfaceContainer::createEnumeration() +{ + ::osl::MutexGuard aGuard( m_rMutex ); + return new ::comphelper::OEnumerationByIndex(static_cast(this)); +} + +// XNameAccess + +Any SAL_CALL OInterfaceContainer::getByName( const OUString& _rName ) +{ + ::std::pair aPair = m_aMap.equal_range(_rName); + + if (aPair.first == aPair.second) + throw NoSuchElementException(); + + return (*aPair.first).second->queryInterface( m_aElementType ); +} + + +css::uno::Sequence SAL_CALL OInterfaceContainer::getElementNames() +{ + return comphelper::mapKeysToSequence(m_aMap); +} + + +sal_Bool SAL_CALL OInterfaceContainer::hasByName( const OUString& _rName ) +{ + ::std::pair aPair = m_aMap.equal_range(_rName); + return aPair.first != aPair.second; +} + +// XIndexAccess + +sal_Int32 OInterfaceContainer::getCount() +{ + return m_aItems.size(); +} + + +Any OInterfaceContainer::getByIndex(sal_Int32 _nIndex) +{ + if (_nIndex < 0 || (o3tl::make_unsigned(_nIndex) >= m_aItems.size())) + throw IndexOutOfBoundsException(); + + return m_aItems[_nIndex]->queryInterface( m_aElementType ); +} + + +void OInterfaceContainer::approveNewElement( const Reference< XPropertySet >& _rxObject, ElementDescription* _pElement ) +{ + // it has to be non-NULL + if ( !_rxObject.is() ) + throw IllegalArgumentException(ResourceManager::loadString(RID_STR_NEED_NON_NULL_OBJECT), static_cast(this), 1); + + // it has to support our element type interface + Any aCorrectType = _rxObject->queryInterface( m_aElementType ); + if ( !aCorrectType.hasValue() ) + lcl_throwIllegalArgumentException(); + + // it has to have a "Name" property + if ( !hasProperty( PROPERTY_NAME, _rxObject ) ) + lcl_throwIllegalArgumentException(); + + // it has to be a child, and it must not have a parent already + Reference< XChild > xChild( _rxObject, UNO_QUERY ); + if ( !xChild.is() || xChild->getParent().is() ) + { + lcl_throwIllegalArgumentException(); + } + + // passed all tests. cache the information we have so far + DBG_ASSERT( _pElement, "OInterfaceContainer::approveNewElement: invalid event descriptor!" ); + if ( _pElement ) + { + _pElement->xPropertySet = _rxObject; + _pElement->xChild = xChild; + _pElement->aElementTypeInterface = aCorrectType; + _pElement->xInterface = Reference< XInterface >( _rxObject, UNO_QUERY ); // normalized XInterface + } +} + + +void OInterfaceContainer::implInsert(sal_Int32 _nIndex, const Reference< XPropertySet >& _rxElement, + bool _bEvents, ElementDescription* _pApprovalResult, bool _bFire ) +{ + const bool bHandleEvents = _bEvents && m_xEventAttacher.is(); + + // SYNCHRONIZED -----> + ::osl::ClearableMutexGuard aGuard( m_rMutex ); + + std::unique_ptr< ElementDescription > aAutoDeleteMetaData; + ElementDescription* pElementMetaData = _pApprovalResult; + if ( !pElementMetaData ) + { // not yet approved by the caller -> do ourself + pElementMetaData = createElementMetaData(); + DBG_ASSERT( pElementMetaData, "OInterfaceContainer::implInsert: createElementMetaData returned nonsense!" ); + + // ensure that the meta data structure will be deleted later on + aAutoDeleteMetaData.reset( pElementMetaData ); + + // will throw an exception if necessary + approveNewElement( _rxElement, pElementMetaData ); + } + + + // approveNewElement (no matter if called here or outside) has ensure that all relevant interfaces + // exist + + // set the name, and add as change listener for the name + OUString sName; + _rxElement->getPropertyValue(PROPERTY_NAME) >>= sName; + _rxElement->addPropertyChangeListener(PROPERTY_NAME, this); + + // insert the object into our internal structures + if (_nIndex > static_cast(m_aItems.size())) // Calculate the actual index + { + _nIndex = m_aItems.size(); + m_aItems.push_back( pElementMetaData->xInterface ); + } + else + m_aItems.insert( m_aItems.begin() + _nIndex, pElementMetaData->xInterface ); + + m_aMap.insert( ::std::pair< const OUString, css::uno::Reference >( sName, pElementMetaData->xInterface ) ); + + // announce ourself as parent to the new element + pElementMetaData->xChild->setParent(static_cast(this)); + + // handle the events + if ( bHandleEvents ) + { + m_xEventAttacher->insertEntry(_nIndex); + m_xEventAttacher->attach( _nIndex, pElementMetaData->xInterface, Any( _rxElement ) ); + } + + // notify derived classes + implInserted( pElementMetaData ); + + aGuard.clear(); + // <----- SYNCHRONIZED + + // insert faked VBA events? + bool bHandleVbaEvents = false; + try + { + _rxElement->getPropertyValue("GenerateVbaEvents") >>= bHandleVbaEvents; + } + catch( const Exception& ) + { + } + if ( bHandleVbaEvents ) + { + Reference< XEventAttacherManager > xMgr ( pElementMetaData->xInterface, UNO_QUERY ); + OInterfaceContainer* pIfcMgr = xMgr.is() ? dynamic_cast(xMgr.get()) : nullptr; + if (pIfcMgr) + { + sal_Int32 nLen = pIfcMgr->getCount(); + for (sal_Int32 i = 0; i < nLen; ++i) + { + // add fake events to the control at index i + pIfcMgr->impl_addVbEvents_nolck_nothrow( i ); + } + } + else + { + // add fake events to the control at index i + impl_addVbEvents_nolck_nothrow( _nIndex ); + } + } + + // fire the notification about the change + if ( _bFire ) + { + // notify listeners + ContainerEvent aEvt; + aEvt.Source = static_cast(this); + aEvt.Accessor <<= _nIndex; + aEvt.Element = pElementMetaData->aElementTypeInterface; + + m_aContainerListeners.notifyEach( &XContainerListener::elementInserted, aEvt ); + } +} + + +void OInterfaceContainer::removeElementsNoEvents() +{ + OInterfaceArray::iterator i = m_aItems.begin(); + css::uno::Reference xElement(*i); + + OInterfaceMap::iterator j = std::find_if(m_aMap.begin(), m_aMap.end(), + [&xElement](const OInterfaceMap::value_type& rEntry) { return rEntry.second == xElement; }); + + m_aItems.erase(i); + m_aMap.erase(j); + + Reference xSet(xElement, UNO_QUERY); + if (xSet.is()) + xSet->removePropertyChangeListener(PROPERTY_NAME, this); + + Reference xChild(xElement, UNO_QUERY); + if (xChild.is()) + xChild->setParent(css::uno::Reference ()); +} + + +void OInterfaceContainer::implInserted( const ElementDescription* /*_pElement*/ ) +{ + // not interested in +} + + +void OInterfaceContainer::implRemoved( const css::uno::Reference& /*_rxObject*/ ) +{ + // not interested in +} + + +void OInterfaceContainer::impl_replacedElement( const ContainerEvent& _rEvent, ::osl::ClearableMutexGuard& _rInstanceLock ) +{ + _rInstanceLock.clear(); + m_aContainerListeners.notifyEach( &XContainerListener::elementReplaced, _rEvent ); +} + +// XIndexContainer + +void SAL_CALL OInterfaceContainer::insertByIndex( sal_Int32 _nIndex, const Any& _rElement ) +{ + Reference< XPropertySet > xElement; + _rElement >>= xElement; + implInsert( _nIndex, xElement, true /* event handling */ , nullptr /* not yet approved */ , true /* notification */ ); +} + + +void OInterfaceContainer::implReplaceByIndex( const sal_Int32 _nIndex, const Any& _rNewElement, ::osl::ClearableMutexGuard& _rClearBeforeNotify ) +{ + OSL_PRECOND( ( _nIndex >= 0 ) && ( o3tl::make_unsigned(_nIndex) < m_aItems.size() ), "OInterfaceContainer::implReplaceByIndex: precondition not met (index)!" ); + + // approve the new object + std::unique_ptr< ElementDescription > aElementMetaData( createElementMetaData() ); + DBG_ASSERT(aElementMetaData, + "OInterfaceContainer::implReplaceByIndex: createElementMetaData returned nonsense!"); + { + Reference< XPropertySet > xElementProps; + _rNewElement >>= xElementProps; + approveNewElement( xElementProps, aElementMetaData.get() ); + } + + // get the old element + css::uno::Reference xOldElement( m_aItems[ _nIndex ] ); + DBG_ASSERT( xOldElement.get() == Reference< XInterface >( xOldElement, UNO_QUERY ).get(), + "OInterfaceContainer::implReplaceByIndex: elements should be held normalized!" ); + + // locate the old element in the map + OInterfaceMap::iterator j = std::find_if(m_aMap.begin(), m_aMap.end(), + [&xOldElement](const OInterfaceMap::value_type& rEntry) { return rEntry.second.get() == xOldElement.get(); }); + + // remove event knittings + if ( m_xEventAttacher.is() ) + { + css::uno::Reference xNormalized( xOldElement, UNO_QUERY ); + m_xEventAttacher->detach( _nIndex, xNormalized ); + m_xEventAttacher->removeEntry( _nIndex ); + } + + // don't listen for property changes anymore + Reference xSet( xOldElement, UNO_QUERY ); + if (xSet.is()) + xSet->removePropertyChangeListener(PROPERTY_NAME, this); + + // give the old element a new (void) parent + Reference xChild(xOldElement, UNO_QUERY); + if (xChild.is()) + xChild->setParent(css::uno::Reference ()); + + // remove the old one + m_aMap.erase(j); + + // examine the new element + OUString sName; + DBG_ASSERT(aElementMetaData->xPropertySet.is(), + "OInterfaceContainer::implReplaceByIndex: what did approveNewElement do?"); + + aElementMetaData->xPropertySet->getPropertyValue(PROPERTY_NAME) >>= sName; + aElementMetaData->xPropertySet->addPropertyChangeListener(PROPERTY_NAME, this); + + // insert the new one + m_aMap.insert(::std::pair>( + sName, aElementMetaData->xInterface)); + m_aItems[_nIndex] = aElementMetaData->xInterface; + + aElementMetaData->xChild->setParent(static_cast(this)); + + if ( m_xEventAttacher.is() ) + { + m_xEventAttacher->insertEntry( _nIndex ); + m_xEventAttacher->attach(_nIndex, aElementMetaData->xInterface, + Any(aElementMetaData->xPropertySet)); + } + + ContainerEvent aReplaceEvent; + aReplaceEvent.Source = static_cast< XContainer* >( this ); + aReplaceEvent.Accessor <<= _nIndex; + aReplaceEvent.Element = aElementMetaData->xInterface->queryInterface(m_aElementType); + aReplaceEvent.ReplacedElement = xOldElement->queryInterface( m_aElementType ); + + impl_replacedElement( aReplaceEvent, _rClearBeforeNotify ); +} + + +void OInterfaceContainer::implCheckIndex( const sal_Int32 _nIndex ) +{ + if (_nIndex < 0 || o3tl::make_unsigned(_nIndex) >= m_aItems.size()) + throw IndexOutOfBoundsException(); +} + + +void SAL_CALL OInterfaceContainer::replaceByIndex(sal_Int32 _nIndex, const Any& Element) +{ + ::osl::ClearableMutexGuard aGuard( m_rMutex ); + // check the index + implCheckIndex( _nIndex ); + // do the replace + implReplaceByIndex( _nIndex, Element, aGuard ); +} + + +void OInterfaceContainer::implRemoveByIndex( const sal_Int32 _nIndex, ::osl::ClearableMutexGuard& _rClearBeforeNotify ) +{ + OSL_PRECOND( ( _nIndex >= 0 ) && ( o3tl::make_unsigned(_nIndex) < m_aItems.size() ), "OInterfaceContainer::implRemoveByIndex: precondition not met (index)!" ); + + OInterfaceArray::iterator i = m_aItems.begin() + _nIndex; + css::uno::Reference xElement(*i); + + OInterfaceMap::iterator j = std::find_if(m_aMap.begin(), m_aMap.end(), + [&xElement](const OInterfaceMap::value_type& rEntry) { return rEntry.second == xElement; }); + + m_aItems.erase(i); + m_aMap.erase(j); + + // remove event knittings + if ( m_xEventAttacher.is() ) + { + css::uno::Reference xNormalized( xElement, UNO_QUERY ); + m_xEventAttacher->detach( _nIndex, xNormalized ); + m_xEventAttacher->removeEntry( _nIndex ); + } + + Reference xSet(xElement, UNO_QUERY); + if (xSet.is()) + xSet->removePropertyChangeListener(PROPERTY_NAME, this); + + Reference xChild(xElement, UNO_QUERY); + if (xChild.is()) + xChild->setParent(css::uno::Reference ()); + + // notify derived classes + implRemoved(xElement); + + // notify listeners + ContainerEvent aEvt; + aEvt.Source = static_cast(this); + aEvt.Element = xElement->queryInterface( m_aElementType ); + aEvt.Accessor <<= _nIndex; + + _rClearBeforeNotify.clear(); + m_aContainerListeners.notifyEach( &XContainerListener::elementRemoved, aEvt ); +} + + +void SAL_CALL OInterfaceContainer::removeByIndex(sal_Int32 _nIndex) +{ + ::osl::ClearableMutexGuard aGuard( m_rMutex ); + // check the index + implCheckIndex( _nIndex ); + // do the removal + implRemoveByIndex( _nIndex, aGuard ); +} + + +ElementDescription* OInterfaceContainer::createElementMetaData( ) +{ + return new ElementDescription; +} + + +void SAL_CALL OInterfaceContainer::insertByName(const OUString& _rName, const Any& _rElement) +{ + Reference< XPropertySet > xElementProps; + + std::unique_ptr< ElementDescription > aElementMetaData( createElementMetaData() ); + DBG_ASSERT(aElementMetaData, + "OInterfaceContainer::insertByName: createElementMetaData returned nonsense!"); + + // ensure the correct name of the element + try + { + _rElement >>= xElementProps; + approveNewElement( xElementProps, aElementMetaData.get() ); + + xElementProps->setPropertyValue( PROPERTY_NAME, Any( _rName ) ); + } + catch( const IllegalArgumentException& ) + { + throw; // allowed to leave + } + catch( const ElementExistException& ) + { + throw; // allowed to leave + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION("forms.misc", "OInterfaceContainer::insertByName" ); + } + implInsert( m_aItems.size(), xElementProps, true, aElementMetaData.get(), true ); +} + + +void SAL_CALL OInterfaceContainer::replaceByName(const OUString& Name, const Any& Element) +{ + ::osl::ClearableMutexGuard aGuard( m_rMutex ); + ::std::pair aPair = m_aMap.equal_range(Name); + if (aPair.first == aPair.second) + throw NoSuchElementException(); + + if (Element.getValueType().getTypeClass() != TypeClass_INTERFACE) + lcl_throwIllegalArgumentException(); + + Reference xSet; + Element >>= xSet; + if (xSet.is()) + { + if (!hasProperty(PROPERTY_NAME, xSet)) + lcl_throwIllegalArgumentException(); + + xSet->setPropertyValue(PROPERTY_NAME, Any(Name)); + } + + // determine the element pos + sal_Int32 nPos = ::std::find(m_aItems.begin(), m_aItems.end(), (*aPair.first).second) - m_aItems.begin(); + + implReplaceByIndex( nPos, Element, aGuard ); +} + + +void SAL_CALL OInterfaceContainer::removeByName(const OUString& Name) +{ + ::osl::MutexGuard aGuard( m_rMutex ); + ::std::pair aPair = m_aMap.equal_range(Name); + if (aPair.first == aPair.second) + throw NoSuchElementException(); + + sal_Int32 nPos = ::std::find(m_aItems.begin(), m_aItems.end(), (*aPair.first).second) - m_aItems.begin(); + removeByIndex(nPos); +} + + +// XEventAttacherManager + +void SAL_CALL OInterfaceContainer::registerScriptEvent( sal_Int32 nIndex, const ScriptEventDescriptor& aScriptEvent ) +{ + ::osl::ClearableMutexGuard aGuard( m_rMutex ); + if ( m_xEventAttacher.is() ) + { + m_xEventAttacher->registerScriptEvent( nIndex, aScriptEvent ); + aGuard.clear(); + impl_addVbEvents_nolck_nothrow( nIndex ); // add fake vba events + } +} + + +void SAL_CALL OInterfaceContainer::registerScriptEvents( sal_Int32 nIndex, const Sequence< ScriptEventDescriptor >& aScriptEvents ) +{ + ::osl::ClearableMutexGuard aGuard( m_rMutex ); + if ( m_xEventAttacher.is() ) + { + m_xEventAttacher->registerScriptEvents( nIndex, aScriptEvents ); + aGuard.clear(); + impl_addVbEvents_nolck_nothrow( nIndex ); // add fake vba events + } +} + + +void SAL_CALL OInterfaceContainer::revokeScriptEvent( sal_Int32 nIndex, const OUString& aListenerType, const OUString& aEventMethod, const OUString& aRemoveListenerParam ) +{ + if ( m_xEventAttacher.is() ) + m_xEventAttacher->revokeScriptEvent( nIndex, aListenerType, aEventMethod, aRemoveListenerParam ); +} + + +void SAL_CALL OInterfaceContainer::revokeScriptEvents( sal_Int32 nIndex ) +{ + if ( m_xEventAttacher.is() ) + m_xEventAttacher->revokeScriptEvents( nIndex ); +} + + +void SAL_CALL OInterfaceContainer::insertEntry( sal_Int32 nIndex ) +{ + if ( m_xEventAttacher.is() ) + m_xEventAttacher->insertEntry( nIndex ); +} + + +void SAL_CALL OInterfaceContainer::removeEntry( sal_Int32 nIndex ) +{ + if ( m_xEventAttacher.is() ) + m_xEventAttacher->removeEntry( nIndex ); +} + + +Sequence< ScriptEventDescriptor > SAL_CALL OInterfaceContainer::getScriptEvents( sal_Int32 nIndex ) +{ + Sequence< ScriptEventDescriptor > aReturn; + if ( m_xEventAttacher.is() ) + { + aReturn = m_xEventAttacher->getScriptEvents( nIndex ); + if ( lcl_hasVbaEvents( aReturn ) ) + { + aReturn = lcl_stripVbaEvents( aReturn ); + } + } + return aReturn; +} + + +void SAL_CALL OInterfaceContainer::attach( sal_Int32 nIndex, const Reference< XInterface >& xObject, const Any& aHelper ) +{ + if ( m_xEventAttacher.is() ) + m_xEventAttacher->attach( nIndex, xObject, aHelper ); +} + + +void SAL_CALL OInterfaceContainer::detach( sal_Int32 nIndex, const Reference< XInterface >& xObject ) +{ + if ( m_xEventAttacher.is() ) + m_xEventAttacher->detach( nIndex, xObject ); +} + + +void SAL_CALL OInterfaceContainer::addScriptListener( const Reference< XScriptListener >& xListener ) +{ + if ( m_xEventAttacher.is() ) + m_xEventAttacher->addScriptListener( xListener ); +} + + +void SAL_CALL OInterfaceContainer::removeScriptListener( const Reference< XScriptListener >& xListener ) +{ + if ( m_xEventAttacher.is() ) + m_xEventAttacher->removeScriptListener( xListener ); +} + + +//= OFormComponents + + +Any SAL_CALL OFormComponents::queryAggregation(const Type& _rType) +{ + Any aReturn = OFormComponents_BASE::queryInterface(_rType); + if (!aReturn.hasValue()) + { + aReturn = OInterfaceContainer::queryInterface(_rType); + + if (!aReturn.hasValue()) + aReturn = ::cppu::OComponentHelper::queryAggregation(_rType); + } + + return aReturn; +} + + +Sequence SAL_CALL OFormComponents::getTypes() +{ + return ::comphelper::concatSequences(OInterfaceContainer::getTypes(), ::cppu::OComponentHelper::getTypes(), OFormComponents_BASE::getTypes()); +} + + +OFormComponents::OFormComponents(const Reference& _rxFactory) + : ::cppu::OComponentHelper( m_aMutex ) + ,OInterfaceContainer( _rxFactory, m_aMutex, cppu::UnoType::get() ) + ,OFormComponents_BASE() +{ +} + + +OFormComponents::OFormComponents( const OFormComponents& _cloneSource ) + : ::cppu::OComponentHelper( m_aMutex ) + ,OInterfaceContainer( m_aMutex, _cloneSource ) + ,OFormComponents_BASE() +{ +} + + +OFormComponents::~OFormComponents() +{ + if (! ::cppu::OComponentHelper::rBHelper.bDisposed) + { + acquire(); + dispose(); + } +} + +// OComponentHelper + +void OFormComponents::disposing() +{ + OInterfaceContainer::disposing(); + ::cppu::OComponentHelper::disposing(); + m_xParent = nullptr; +} + +//XChild + +void OFormComponents::setParent(const css::uno::Reference& Parent) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + m_xParent = Parent; +} + + +css::uno::Reference OFormComponents::getParent() +{ + return m_xParent; +} + + +} // namespace frm + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/misc/componenttools.cxx b/forms/source/misc/componenttools.cxx new file mode 100644 index 000000000..41a062188 --- /dev/null +++ b/forms/source/misc/componenttools.cxx @@ -0,0 +1,105 @@ +/* -*- 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 + + +namespace frm +{ + + + using ::com::sun::star::frame::XModel; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::container::XChild; + + TypeBag::TypeBag( const TypeSequence& _rTypes1 ) + { + addTypes( _rTypes1 ); + } + + + TypeBag::TypeBag( const TypeSequence& _rTypes1, const TypeSequence& _rTypes2 ) + { + addTypes( _rTypes1 ); + addTypes( _rTypes2 ); + } + + + TypeBag::TypeBag( const TypeSequence& _rTypes1, const TypeSequence& _rTypes2, const TypeSequence& _rTypes3 ) + { + addTypes( _rTypes1 ); + addTypes( _rTypes2 ); + addTypes( _rTypes3 ); + } + + + void TypeBag::addTypes( const TypeSequence& _rTypes ) + { + ::std::copy( + _rTypes.begin(), + _rTypes.end(), + ::std::insert_iterator< TypeSet >( m_aTypes, m_aTypes.begin() ) + ); + } + + + void TypeBag::addType( const css::uno::Type& i_rType ) + { + m_aTypes.insert( i_rType ); + } + + + void TypeBag::removeType( const css::uno::Type& i_rType ) + { + m_aTypes.erase( i_rType ); + } + + + TypeBag::TypeSequence TypeBag::getTypes() const + { + return comphelper::containerToSequence(m_aTypes); + } + + + Reference< XModel > getXModel( const Reference< XInterface >& _rxComponent ) + { + Reference< XInterface > xParent = _rxComponent; + Reference< XModel > xModel( xParent, UNO_QUERY ); + while ( xParent.is() && !xModel.is() ) + { + Reference< XChild > xChild( xParent, UNO_QUERY ); + xParent.set( xChild.is() ? xChild->getParent() : Reference< XInterface >(), UNO_QUERY ); + xModel.set( xParent, UNO_QUERY ); + } + return xModel; + } + + +} // namespace frm + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/misc/limitedformats.cxx b/forms/source/misc/limitedformats.cxx new file mode 100644 index 000000000..b7a0d5abc --- /dev/null +++ b/forms/source/misc/limitedformats.cxx @@ -0,0 +1,379 @@ +/* -*- 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 + + +namespace frm +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::util; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::form; + using namespace ::com::sun::star::beans; + + sal_Int32 OLimitedFormats::s_nInstanceCount(0); + ::osl::Mutex OLimitedFormats::s_aMutex; + Reference< XNumberFormatsSupplier > OLimitedFormats::s_xStandardFormats; + + + //= + + namespace { + + enum LocaleType + { + ltEnglishUS, + ltGerman, + ltSystem + }; + + } + + static const Locale& getLocale(LocaleType _eType) + { + static const Locale s_aEnglishUS( "en", "us", OUString() ); + static const Locale s_aGerman( "de", "DE", OUString() ); + static const Locale s_aSystem( "", "", "" ); + + switch (_eType) + { + case ltEnglishUS: + return s_aEnglishUS; + + case ltGerman: + return s_aGerman; + + case ltSystem: + return s_aSystem; + } + + OSL_FAIL("getLocale: invalid enum value!"); + return s_aSystem; + } + + namespace { + + struct FormatEntry + { + const char* pDescription; + sal_Int32 nKey; + LocaleType eLocale; + }; + + } + + static FormatEntry* lcl_getFormatTable(sal_Int16 nTableId) + { + switch (nTableId) + { + case FormComponentType::TIMEFIELD: + { + static FormatEntry s_aFormats[] = { + { "HH:MM", -1, ltEnglishUS }, + { "HH:MM:SS", -1, ltEnglishUS }, + { "HH:MM AM/PM", -1, ltEnglishUS }, + { "HH:MM:SS AM/PM", -1, ltEnglishUS }, + { nullptr, -1, ltSystem } + }; + return s_aFormats; + } + case FormComponentType::DATEFIELD: + { + static FormatEntry s_aFormats[] = { + { "T-M-JJ", -1, ltGerman }, + { "TT-MM-JJ", -1, ltGerman }, + { "TT-MM-JJJJ", -1, ltGerman }, + { "NNNNT. MMMM JJJJ", -1, ltGerman }, + + { "DD/MM/YY", -1, ltEnglishUS }, + { "MM/DD/YY", -1, ltEnglishUS }, + { "YY/MM/DD", -1, ltEnglishUS }, + { "DD/MM/YYYY", -1, ltEnglishUS }, + { "MM/DD/YYYY", -1, ltEnglishUS }, + { "YYYY/MM/DD", -1, ltEnglishUS }, + + { "JJ-MM-TT", -1, ltGerman }, + { "JJJJ-MM-TT", -1, ltGerman }, + + { nullptr, -1, ltSystem } + }; + return s_aFormats; + } + } + + OSL_FAIL("lcl_getFormatTable: invalid id!"); + return nullptr; + } + + OLimitedFormats::OLimitedFormats(const Reference< XComponentContext >& _rxContext, const sal_Int16 _nClassId) + :m_nFormatEnumPropertyHandle(-1) + ,m_nTableId(_nClassId) + { + OSL_ENSURE(_rxContext.is(), "OLimitedFormats::OLimitedFormats: invalid service factory!"); + acquireSupplier(_rxContext); + ensureTableInitialized(m_nTableId); + } + + + OLimitedFormats::~OLimitedFormats() + { + releaseSupplier(); + } + + + void OLimitedFormats::ensureTableInitialized(const sal_Int16 _nTableId) + { + FormatEntry* pFormatTable = lcl_getFormatTable(_nTableId); + if (-1 != pFormatTable->nKey) + return; + + ::osl::MutexGuard aGuard(s_aMutex); + if (-1 != pFormatTable->nKey) + return; + + // initialize the keys + Reference xStandardFormats; + if (s_xStandardFormats.is()) + xStandardFormats = s_xStandardFormats->getNumberFormats(); + OSL_ENSURE(xStandardFormats.is(), "OLimitedFormats::ensureTableInitialized: don't have a formats supplier!"); + + if (!xStandardFormats.is()) + return; + + // loop through the table + FormatEntry* pLoopFormats = pFormatTable; + while (pLoopFormats->pDescription) + { + // get the key for the description + pLoopFormats->nKey = xStandardFormats->queryKey( + OUString::createFromAscii(pLoopFormats->pDescription), + getLocale(pLoopFormats->eLocale), + false + ); + + if (-1 == pLoopFormats->nKey) + { + pLoopFormats->nKey = xStandardFormats->addNew( + OUString::createFromAscii(pLoopFormats->pDescription), + getLocale(pLoopFormats->eLocale) + ); +#ifdef DBG_UTIL + try + { + xStandardFormats->getByKey(pLoopFormats->nKey); + } + catch(const Exception&) + { + OSL_FAIL("OLimitedFormats::ensureTableInitialized: adding the key to the formats collection failed!"); + } +#endif + } + + // next + ++pLoopFormats; + } + } + + + void OLimitedFormats::clearTable(const sal_Int16 _nTableId) + { + ::osl::MutexGuard aGuard(s_aMutex); + FormatEntry* pFormats = lcl_getFormatTable(_nTableId); + FormatEntry* pResetLoop = pFormats; + while (pResetLoop->pDescription) + { + pResetLoop->nKey = -1; + ++pResetLoop; + } + } + + + void OLimitedFormats::setAggregateSet(const Reference< XFastPropertySet >& _rxAggregate, sal_Int32 _nOriginalPropertyHandle) + { + // changes (NULL -> not NULL) and (not NULL -> NULL) are allowed + OSL_ENSURE(!m_xAggregate.is() || !_rxAggregate.is(), "OLimitedFormats::setAggregateSet: already have an aggregate!"); + OSL_ENSURE(_rxAggregate.is() || m_xAggregate.is(), "OLimitedFormats::setAggregateSet: invalid new aggregate!"); + + m_xAggregate = _rxAggregate; + m_nFormatEnumPropertyHandle = _nOriginalPropertyHandle; +#ifdef DBG_UTIL + if (m_xAggregate.is()) + { + try + { + m_xAggregate->getFastPropertyValue(m_nFormatEnumPropertyHandle); + } + catch(const Exception&) + { + OSL_FAIL("OLimitedFormats::setAggregateSet: invalid handle!"); + } + } +#endif + } + + + void OLimitedFormats::getFormatKeyPropertyValue( Any& _rValue ) const + { + _rValue.clear(); + + OSL_ENSURE(m_xAggregate.is() && (-1 != m_nFormatEnumPropertyHandle), "OLimitedFormats::getFormatKeyPropertyValue: not initialized!"); + if (!m_xAggregate.is()) + return; + + // get the aggregate's enum property value + Any aEnumPropertyValue = m_xAggregate->getFastPropertyValue(m_nFormatEnumPropertyHandle); + sal_Int32 nValue = -1; + ::cppu::enum2int(nValue, aEnumPropertyValue); + + // get the translation table + const FormatEntry* pFormats = lcl_getFormatTable(m_nTableId); + + // seek to the nValue'th entry + sal_Int32 nLookup = 0; + for ( ; + (nullptr != pFormats->pDescription) && (nLookup < nValue); + ++pFormats, ++nLookup + ) + ; + OSL_ENSURE(nullptr != pFormats->pDescription, "OLimitedFormats::getFormatKeyPropertyValue: did not find the value!"); + if (pFormats->pDescription) + _rValue <<= pFormats->nKey; + + // TODO: should use a standard format for the control type we're working for + } + + + bool OLimitedFormats::convertFormatKeyPropertyValue(Any& _rConvertedValue, Any& _rOldValue, const Any& _rNewValue) + { + OSL_ENSURE(m_xAggregate.is() && (-1 != m_nFormatEnumPropertyHandle), "OLimitedFormats::convertFormatKeyPropertyValue: not initialized!"); + + if (!m_xAggregate) + return false; + + // the new format key to set + sal_Int32 nNewFormat = 0; + if (!(_rNewValue >>= nNewFormat)) + throw IllegalArgumentException(); + + // get the old (enum) value from the aggregate + Any aEnumPropertyValue = m_xAggregate->getFastPropertyValue(m_nFormatEnumPropertyHandle); + sal_Int32 nOldEnumValue = -1; + ::cppu::enum2int(nOldEnumValue, aEnumPropertyValue); + + // get the translation table + const FormatEntry* pFormats = lcl_getFormatTable(m_nTableId); + + _rOldValue.clear(); + _rConvertedValue.clear(); + + // look for the entry with the given format key + sal_Int32 nTablePosition = 0; + for ( ; + (nullptr != pFormats->pDescription) && (nNewFormat != pFormats->nKey); + ++pFormats, ++nTablePosition + ) + { + if (nTablePosition == nOldEnumValue) + _rOldValue <<= pFormats->nKey; + } + + bool bFoundIt = (nullptr != pFormats->pDescription); + bool bModified = false; + if (bFoundIt) + { + _rConvertedValue <<= static_cast(nTablePosition); + bModified = nTablePosition != nOldEnumValue; + } + + if (!_rOldValue.hasValue()) + { // did not reach the end of the table (means we found nNewFormat) + // -> go to the end to ensure that _rOldValue is set + while (pFormats->pDescription) + { + if (nTablePosition == nOldEnumValue) + { + _rOldValue <<= pFormats->nKey; + break; + } + + ++pFormats; + ++nTablePosition; + } + } + + OSL_ENSURE(_rOldValue.hasValue(), "OLimitedFormats::convertFormatKeyPropertyValue: did not find the old enum value in the table!"); + + if (!bFoundIt) + { // somebody gave us a format which we can't translate + throw IllegalArgumentException("This control supports only a very limited number of formats.", nullptr, 2); + } + + return bModified; + } + + + void OLimitedFormats::setFormatKeyPropertyValue( const Any& _rNewValue ) + { + OSL_ENSURE(m_xAggregate.is() && (-1 != m_nFormatEnumPropertyHandle), "OLimitedFormats::setFormatKeyPropertyValue: not initialized!"); + + if (m_xAggregate.is()) + { // this is to be called after convertFormatKeyPropertyValue, where + // we translated the format key into an enum value. + // So now we can simply forward this enum value to our aggregate + m_xAggregate->setFastPropertyValue(m_nFormatEnumPropertyHandle, _rNewValue); + } + } + + + void OLimitedFormats::acquireSupplier(const Reference< XComponentContext >& _rxContext) + { + ::osl::MutexGuard aGuard(s_aMutex); + if (1 == ++s_nInstanceCount) + { // create the standard formatter + s_xStandardFormats = NumberFormatsSupplier::createWithLocale(_rxContext, getLocale(ltEnglishUS)); + } + } + + + void OLimitedFormats::releaseSupplier() + { + ::osl::MutexGuard aGuard(s_aMutex); + if (0 == --s_nInstanceCount) + { + ::comphelper::disposeComponent(s_xStandardFormats); + s_xStandardFormats = nullptr; + + clearTable(FormComponentType::TIMEFIELD); + clearTable(FormComponentType::DATEFIELD); + } + } + + +} // namespace frm + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/misc/property.cxx b/forms/source/misc/property.cxx new file mode 100644 index 000000000..6200b3438 --- /dev/null +++ b/forms/source/misc/property.cxx @@ -0,0 +1,220 @@ +/* -*- 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 + +namespace frm +{ + +//= PropertyInfoService + +PropertyInfoService::PropertyMap PropertyInfoService::s_AllKnownProperties; + +sal_Int32 PropertyInfoService::getPropertyId(const OUString& _rName) +{ + initialize(); + + sal_Int32 nHandle = -1; + const auto foundProperty = s_AllKnownProperties.find(_rName); + if (foundProperty != s_AllKnownProperties.end()) + nHandle = foundProperty->second; + return nHandle; +} + + +sal_Int32 ConcreteInfoService::getPreferredPropertyId(const OUString& _rName) +{ + return PropertyInfoService::getPropertyId(_rName); +} + + +void PropertyInfoService::initialize() +{ + if (!s_AllKnownProperties.empty()) + return; + + s_AllKnownProperties.insert({ + { PROPERTY_NAME, PROPERTY_ID_NAME }, + { PROPERTY_TAG, PROPERTY_ID_TAG }, + { PROPERTY_TABINDEX, PROPERTY_ID_TABINDEX }, + { PROPERTY_CLASSID, PROPERTY_ID_CLASSID }, + { PROPERTY_ALIGN, PROPERTY_ID_ALIGN }, + { PROPERTY_FETCHSIZE, PROPERTY_ID_FETCHSIZE }, + { PROPERTY_VALUE, PROPERTY_ID_VALUE }, + { PROPERTY_VALUEMIN, PROPERTY_ID_VALUEMIN }, + { PROPERTY_VALUEMAX, PROPERTY_ID_VALUEMAX }, + { PROPERTY_VALUESTEP, PROPERTY_ID_VALUESTEP }, + { PROPERTY_TEXT, PROPERTY_ID_TEXT }, + { PROPERTY_LABEL, PROPERTY_ID_LABEL }, + { PROPERTY_NAVIGATION, PROPERTY_ID_NAVIGATION }, + { PROPERTY_CYCLE, PROPERTY_ID_CYCLE }, + { PROPERTY_CONTROLSOURCE, PROPERTY_ID_CONTROLSOURCE }, + { PROPERTY_ENABLED, PROPERTY_ID_ENABLED }, + { PROPERTY_ENABLEVISIBLE, PROPERTY_ID_ENABLEVISIBLE }, + { PROPERTY_SPIN, PROPERTY_ID_SPIN }, + { PROPERTY_READONLY, PROPERTY_ID_READONLY }, + { PROPERTY_FILTER, PROPERTY_ID_FILTER }, + { PROPERTY_WIDTH, PROPERTY_ID_WIDTH }, + { PROPERTY_SEARCHABLE, PROPERTY_ID_SEARCHABLE }, + { PROPERTY_MULTILINE, PROPERTY_ID_MULTILINE }, + { PROPERTY_TARGET_URL, PROPERTY_ID_TARGET_URL }, + { PROPERTY_DEFAULTCONTROL, PROPERTY_ID_DEFAULTCONTROL }, + { PROPERTY_MAXTEXTLEN, PROPERTY_ID_MAXTEXTLEN }, + { PROPERTY_SIZE, PROPERTY_ID_SIZE }, + { PROPERTY_DATE, PROPERTY_ID_DATE }, + { PROPERTY_TIME, PROPERTY_ID_TIME }, + { PROPERTY_STATE, PROPERTY_ID_STATE }, + { PROPERTY_TRISTATE, PROPERTY_ID_TRISTATE }, + { PROPERTY_HIDDEN_VALUE, PROPERTY_ID_HIDDEN_VALUE }, + { PROPERTY_TARGET_FRAME, PROPERTY_ID_TARGET_FRAME }, + { PROPERTY_BUTTONTYPE, PROPERTY_ID_BUTTONTYPE }, + { PROPERTY_STRINGITEMLIST, PROPERTY_ID_STRINGITEMLIST }, + { PROPERTY_TYPEDITEMLIST, PROPERTY_ID_TYPEDITEMLIST }, + { PROPERTY_DEFAULT_TEXT, PROPERTY_ID_DEFAULT_TEXT }, + { PROPERTY_DEFAULT_STATE, PROPERTY_ID_DEFAULT_STATE }, + { PROPERTY_DEFAULT_DATE, PROPERTY_ID_DEFAULT_DATE }, + { PROPERTY_DEFAULT_TIME, PROPERTY_ID_DEFAULT_TIME }, + { PROPERTY_DEFAULT_VALUE, PROPERTY_ID_DEFAULT_VALUE }, + { PROPERTY_FORMATKEY, PROPERTY_ID_FORMATKEY }, + { PROPERTY_FORMATSSUPPLIER, PROPERTY_ID_FORMATSSUPPLIER }, + { PROPERTY_SUBMIT_ACTION, PROPERTY_ID_SUBMIT_ACTION }, + { PROPERTY_SUBMIT_TARGET, PROPERTY_ID_SUBMIT_TARGET }, + { PROPERTY_SUBMIT_METHOD, PROPERTY_ID_SUBMIT_METHOD }, + { PROPERTY_SUBMIT_ENCODING, PROPERTY_ID_SUBMIT_ENCODING }, + { PROPERTY_IMAGE_URL, PROPERTY_ID_IMAGE_URL }, + { PROPERTY_GRAPHIC, PROPERTY_ID_GRAPHIC }, + { PROPERTY_EMPTY_IS_NULL, PROPERTY_ID_EMPTY_IS_NULL }, + { PROPERTY_LISTSOURCETYPE, PROPERTY_ID_LISTSOURCETYPE }, + { PROPERTY_LISTSOURCE, PROPERTY_ID_LISTSOURCE }, + { PROPERTY_SELECT_SEQ, PROPERTY_ID_SELECT_SEQ }, + { PROPERTY_VALUE_SEQ, PROPERTY_ID_VALUE_SEQ }, + { PROPERTY_SELECT_VALUE, PROPERTY_ID_SELECT_VALUE }, + { PROPERTY_SELECT_VALUE_SEQ, PROPERTY_ID_SELECT_VALUE_SEQ }, + { PROPERTY_DEFAULT_SELECT_SEQ, PROPERTY_ID_DEFAULT_SELECT_SEQ }, + { PROPERTY_MULTISELECTION, PROPERTY_ID_MULTISELECTION }, + { PROPERTY_DECIMAL_ACCURACY, PROPERTY_ID_DECIMAL_ACCURACY }, + { PROPERTY_EDITMASK, PROPERTY_ID_EDITMASK }, + { PROPERTY_ISREADONLY, PROPERTY_ID_ISREADONLY }, + { PROPERTY_FIELDTYPE, PROPERTY_ID_FIELDTYPE }, + { PROPERTY_DECIMALS, PROPERTY_ID_DECIMALS }, + { PROPERTY_REFVALUE, PROPERTY_ID_REFVALUE }, + { PROPERTY_STRICTFORMAT, PROPERTY_ID_STRICTFORMAT }, + { PROPERTY_DATASOURCE, PROPERTY_ID_DATASOURCE }, + { PROPERTY_ALLOWADDITIONS, PROPERTY_ID_ALLOWADDITIONS }, + { PROPERTY_ALLOWEDITS, PROPERTY_ID_ALLOWEDITS }, + { PROPERTY_ALLOWDELETIONS, PROPERTY_ID_ALLOWDELETIONS }, + { PROPERTY_MASTERFIELDS, PROPERTY_ID_MASTERFIELDS }, + { PROPERTY_ISPASSTHROUGH, PROPERTY_ID_ISPASSTHROUGH }, + { PROPERTY_QUERY, PROPERTY_ID_QUERY }, + { PROPERTY_LITERALMASK, PROPERTY_ID_LITERALMASK }, + { PROPERTY_SHOWTHOUSANDSEP, PROPERTY_ID_SHOWTHOUSANDSEP }, + { PROPERTY_CURRENCYSYMBOL, PROPERTY_ID_CURRENCYSYMBOL }, + { PROPERTY_DATEFORMAT, PROPERTY_ID_DATEFORMAT }, + { PROPERTY_DATEMIN, PROPERTY_ID_DATEMIN }, + { PROPERTY_DATEMAX, PROPERTY_ID_DATEMAX }, + { PROPERTY_DATE_SHOW_CENTURY, PROPERTY_ID_DATE_SHOW_CENTURY }, + { PROPERTY_TIMEFORMAT, PROPERTY_ID_TIMEFORMAT }, + { PROPERTY_TIMEMIN, PROPERTY_ID_TIMEMIN }, + { PROPERTY_TIMEMAX, PROPERTY_ID_TIMEMAX }, + { PROPERTY_LINECOUNT, PROPERTY_ID_LINECOUNT }, + { PROPERTY_BOUNDCOLUMN, PROPERTY_ID_BOUNDCOLUMN }, + { PROPERTY_HASNAVIGATION, PROPERTY_ID_HASNAVIGATION }, + { PROPERTY_FONT, PROPERTY_ID_FONT }, + { PROPERTY_BACKGROUNDCOLOR, PROPERTY_ID_BACKGROUNDCOLOR }, + { PROPERTY_FILLCOLOR, PROPERTY_ID_FILLCOLOR }, + { PROPERTY_TEXTCOLOR, PROPERTY_ID_TEXTCOLOR }, + { PROPERTY_LINECOLOR, PROPERTY_ID_LINECOLOR }, + { PROPERTY_BORDER, PROPERTY_ID_BORDER }, + { PROPERTY_DROPDOWN, PROPERTY_ID_DROPDOWN }, + { PROPERTY_HSCROLL, PROPERTY_ID_HSCROLL }, + { PROPERTY_VSCROLL, PROPERTY_ID_VSCROLL }, + { PROPERTY_TABSTOP, PROPERTY_ID_TABSTOP }, + { PROPERTY_AUTOCOMPLETE, PROPERTY_ID_AUTOCOMPLETE }, + { PROPERTY_HARDLINEBREAKS, PROPERTY_ID_HARDLINEBREAKS }, + { PROPERTY_PRINTABLE, PROPERTY_ID_PRINTABLE }, + { PROPERTY_ECHO_CHAR, PROPERTY_ID_ECHO_CHAR }, + { PROPERTY_ROWHEIGHT, PROPERTY_ID_ROWHEIGHT }, + { PROPERTY_HELPTEXT, PROPERTY_ID_HELPTEXT }, + { PROPERTY_FONT_NAME, PROPERTY_ID_FONT_NAME }, + { PROPERTY_FONT_STYLENAME, PROPERTY_ID_FONT_STYLENAME }, + { PROPERTY_FONT_FAMILY, PROPERTY_ID_FONT_FAMILY }, + { PROPERTY_FONT_CHARSET, PROPERTY_ID_FONT_CHARSET }, + { PROPERTY_FONT_HEIGHT, PROPERTY_ID_FONT_HEIGHT }, + { PROPERTY_FONT_WEIGHT, PROPERTY_ID_FONT_WEIGHT }, + { PROPERTY_FONT_SLANT, PROPERTY_ID_FONT_SLANT }, + { PROPERTY_FONT_UNDERLINE, PROPERTY_ID_FONT_UNDERLINE }, + { PROPERTY_FONT_WORDLINEMODE, PROPERTY_ID_FONT_WORDLINEMODE }, + { PROPERTY_FONT_STRIKEOUT, PROPERTY_ID_FONT_STRIKEOUT }, + { PROPERTY_TEXTLINECOLOR, PROPERTY_ID_TEXTLINECOLOR }, + { PROPERTY_FONTEMPHASISMARK, PROPERTY_ID_FONTEMPHASISMARK }, + { PROPERTY_FONTRELIEF, PROPERTY_ID_FONTRELIEF }, + { PROPERTY_HELPURL, PROPERTY_ID_HELPURL }, + { PROPERTY_RECORDMARKER, PROPERTY_ID_RECORDMARKER }, + { PROPERTY_BOUNDFIELD, PROPERTY_ID_BOUNDFIELD }, + { PROPERTY_INPUT_REQUIRED, PROPERTY_ID_INPUT_REQUIRED }, + { PROPERTY_TREATASNUMERIC, PROPERTY_ID_TREATASNUMERIC }, + { PROPERTY_EFFECTIVE_VALUE, PROPERTY_ID_EFFECTIVE_VALUE }, + { PROPERTY_EFFECTIVE_DEFAULT, PROPERTY_ID_EFFECTIVE_DEFAULT }, + { PROPERTY_EFFECTIVE_MIN, PROPERTY_ID_EFFECTIVE_MIN }, + { PROPERTY_EFFECTIVE_MAX, PROPERTY_ID_EFFECTIVE_MAX }, + { PROPERTY_HIDDEN, PROPERTY_ID_HIDDEN }, + { PROPERTY_FILTERPROPOSAL, PROPERTY_ID_FILTERPROPOSAL }, + { PROPERTY_FIELDSOURCE, PROPERTY_ID_FIELDSOURCE }, + { PROPERTY_TABLENAME, PROPERTY_ID_TABLENAME }, + { PROPERTY_CONTROLLABEL, PROPERTY_ID_CONTROLLABEL }, + { PROPERTY_CURRSYM_POSITION, PROPERTY_ID_CURRSYM_POSITION }, + { PROPERTY_CURSORCOLOR, PROPERTY_ID_CURSORCOLOR }, + { PROPERTY_ALWAYSSHOWCURSOR, PROPERTY_ID_ALWAYSSHOWCURSOR }, + { PROPERTY_DISPLAYSYNCHRON, PROPERTY_ID_DISPLAYSYNCHRON }, + { PROPERTY_ISMODIFIED, PROPERTY_ID_ISMODIFIED }, + { PROPERTY_ISNEW, PROPERTY_ID_ISNEW }, + { PROPERTY_PRIVILEGES, PROPERTY_ID_PRIVILEGES }, + { PROPERTY_DETAILFIELDS, PROPERTY_ID_DETAILFIELDS }, + { PROPERTY_COMMAND, PROPERTY_ID_COMMAND }, + { PROPERTY_COMMANDTYPE, PROPERTY_ID_COMMANDTYPE }, + { PROPERTY_RESULTSET_CONCURRENCY, PROPERTY_ID_RESULTSET_CONCURRENCY }, + { PROPERTY_INSERTONLY, PROPERTY_ID_INSERTONLY }, + { PROPERTY_RESULTSET_TYPE, PROPERTY_ID_RESULTSET_TYPE }, + { PROPERTY_ESCAPE_PROCESSING, PROPERTY_ID_ESCAPE_PROCESSING }, + { PROPERTY_APPLYFILTER, PROPERTY_ID_APPLYFILTER }, + { PROPERTY_ISNULLABLE, PROPERTY_ID_ISNULLABLE }, + { PROPERTY_ACTIVECOMMAND, PROPERTY_ID_ACTIVECOMMAND }, + { PROPERTY_ISCURRENCY, PROPERTY_ID_ISCURRENCY }, + { PROPERTY_URL, PROPERTY_ID_URL }, + { PROPERTY_TITLE, PROPERTY_ID_TITLE }, + { PROPERTY_ACTIVE_CONNECTION, PROPERTY_ID_ACTIVE_CONNECTION }, + { PROPERTY_SCALE, PROPERTY_ID_SCALE }, + { PROPERTY_SORT, PROPERTY_ID_SORT }, + { PROPERTY_PERSISTENCE_MAXTEXTLENGTH, PROPERTY_ID_PERSISTENCE_MAXTEXTLENGTH }, + { PROPERTY_SCROLL_VALUE, PROPERTY_ID_SCROLL_VALUE }, + { PROPERTY_SPIN_VALUE, PROPERTY_ID_SPIN_VALUE }, + { PROPERTY_DEFAULT_SCROLL_VALUE, PROPERTY_ID_DEFAULT_SCROLL_VALUE }, + { PROPERTY_DEFAULT_SPIN_VALUE, PROPERTY_ID_DEFAULT_SPIN_VALUE }, + { PROPERTY_WRITING_MODE , PROPERTY_ID_WRITING_MODE }, + { PROPERTY_CONTEXT_WRITING_MODE , PROPERTY_ID_CONTEXT_WRITING_MODE }, + { PROPERTY_GENERATEVBAEVENTS , PROPERTY_ID_GENERATEVBAEVENTS } + }); +} + + +} +//... namespace frm ....................................................... + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3