diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /svx/source/form | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'svx/source/form')
50 files changed, 34325 insertions, 0 deletions
diff --git a/svx/source/form/ParseContext.cxx b/svx/source/form/ParseContext.cxx new file mode 100644 index 000000000..e16b614ef --- /dev/null +++ b/svx/source/form/ParseContext.cxx @@ -0,0 +1,200 @@ +/* -*- 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 <sal/macros.h> +#include <svx/ParseContext.hxx> +#include <svx/strings.hrc> + +#include <svx/dialmgr.hxx> + +#include <i18nlangtag/languagetag.hxx> +#include <unotools/syslocale.hxx> +#include <vcl/svapp.hxx> +#include <osl/diagnose.h> +#include <osl/mutex.hxx> +#include <fmstring.hrc> + +using namespace svxform; +using namespace ::connectivity; + +OSystemParseContext::OSystemParseContext() + : IParseContext() +{ + for (size_t i = 0; i < SAL_N_ELEMENTS(RID_RSC_SQL_INTERNATIONAL); ++i) + m_aLocalizedKeywords.push_back(SvxResId(RID_RSC_SQL_INTERNATIONAL[i])); +} + +OSystemParseContext::~OSystemParseContext() +{ +} + +css::lang::Locale OSystemParseContext::getPreferredLocale( ) const +{ + return SvtSysLocale().GetLanguageTag().getLocale(); +} + +OUString OSystemParseContext::getErrorMessage(ErrorCode _eCode) const +{ + OUString aMsg; + SolarMutexGuard aGuard; + switch (_eCode) + { + case ErrorCode::General: aMsg = SvxResId(RID_STR_SVT_SQL_SYNTAX_ERROR); break; + case ErrorCode::ValueNoLike: aMsg = SvxResId(RID_STR_SVT_SQL_SYNTAX_VALUE_NO_LIKE); break; + case ErrorCode::FieldNoLike: aMsg = SvxResId(RID_STR_SVT_SQL_SYNTAX_FIELD_NO_LIKE); break; + case ErrorCode::InvalidCompare: aMsg = SvxResId(RID_STR_SVT_SQL_SYNTAX_CRIT_NO_COMPARE); break; + case ErrorCode::InvalidIntCompare: aMsg = SvxResId(RID_STR_SVT_SQL_SYNTAX_INT_NO_VALID); break; + case ErrorCode::InvalidDateCompare: aMsg = SvxResId(RID_STR_SVT_SQL_SYNTAX_ACCESS_DAT_NO_VALID); break; + case ErrorCode::InvalidRealCompare: aMsg = SvxResId(RID_STR_SVT_SQL_SYNTAX_REAL_NO_VALID); break; + case ErrorCode::InvalidTableNosuch: aMsg = SvxResId(RID_STR_SVT_SQL_SYNTAX_TABLE); break; + case ErrorCode::InvalidTableOrQuery: aMsg = SvxResId(RID_STR_SVT_SQL_SYNTAX_TABLE_OR_QUERY); break; + case ErrorCode::InvalidColumn: aMsg = SvxResId(RID_STR_SVT_SQL_SYNTAX_COLUMN); break; + case ErrorCode::InvalidTableExist: aMsg = SvxResId(RID_STR_SVT_SQL_SYNTAX_TABLE_EXISTS); break; + case ErrorCode::InvalidQueryExist: aMsg = SvxResId(RID_STR_SVT_SQL_SYNTAX_QUERY_EXISTS); break; + default: break; + } + return aMsg; +} + +OString OSystemParseContext::getIntlKeywordAscii(InternationalKeyCode _eKey) const +{ + size_t nIndex = 0; + switch ( _eKey ) + { + case InternationalKeyCode::Like: nIndex = 0; break; + case InternationalKeyCode::Not: nIndex = 1; break; + case InternationalKeyCode::Null: nIndex = 2; break; + case InternationalKeyCode::True: nIndex = 3; break; + case InternationalKeyCode::False: nIndex = 4; break; + case InternationalKeyCode::Is: nIndex = 5; break; + case InternationalKeyCode::Between: nIndex = 6; break; + case InternationalKeyCode::Or: nIndex = 7; break; + case InternationalKeyCode::And: nIndex = 8; break; + case InternationalKeyCode::Avg: nIndex = 9; break; + case InternationalKeyCode::Count: nIndex = 10; break; + case InternationalKeyCode::Max: nIndex = 11; break; + case InternationalKeyCode::Min: nIndex = 12; break; + case InternationalKeyCode::Sum: nIndex = 13; break; + case InternationalKeyCode::Every: nIndex = 14; break; + case InternationalKeyCode::Any: nIndex = 15; break; + case InternationalKeyCode::Some: nIndex = 16; break; + case InternationalKeyCode::StdDevPop: nIndex = 17; break; + case InternationalKeyCode::StdDevSamp: nIndex = 18; break; + case InternationalKeyCode::VarSamp: nIndex = 19; break; + case InternationalKeyCode::VarPop: nIndex = 20; break; + case InternationalKeyCode::Collect: nIndex = 21; break; + case InternationalKeyCode::Fusion: nIndex = 22; break; + case InternationalKeyCode::Intersection: nIndex = 23; break; + case InternationalKeyCode::None: + OSL_FAIL( "OSystemParseContext::getIntlKeywordAscii: illegal argument!" ); + break; + } + + OSL_ENSURE( nIndex < m_aLocalizedKeywords.size(), "OSystemParseContext::getIntlKeywordAscii: invalid index!" ); + + OString sKeyword; + if ( nIndex < m_aLocalizedKeywords.size() ) + sKeyword = OUStringToOString(m_aLocalizedKeywords[nIndex], RTL_TEXTENCODING_UTF8); + return sKeyword; +} + + +IParseContext::InternationalKeyCode OSystemParseContext::getIntlKeyCode(const OString& rToken) const +{ + static const IParseContext::InternationalKeyCode Intl_TokenID[] = + { + InternationalKeyCode::Like, InternationalKeyCode::Not, InternationalKeyCode::Null, InternationalKeyCode::True, + InternationalKeyCode::False, InternationalKeyCode::Is, InternationalKeyCode::Between, InternationalKeyCode::Or, + InternationalKeyCode::And, InternationalKeyCode::Avg, InternationalKeyCode::Count, InternationalKeyCode::Max, + InternationalKeyCode::Min, InternationalKeyCode::Sum, InternationalKeyCode::Every, + InternationalKeyCode::Any, InternationalKeyCode::Some, InternationalKeyCode::StdDevPop, + InternationalKeyCode::StdDevSamp, InternationalKeyCode::VarSamp, InternationalKeyCode::VarPop, + InternationalKeyCode::Collect, InternationalKeyCode::Fusion, InternationalKeyCode::Intersection + }; + + sal_uInt32 const nCount = SAL_N_ELEMENTS(Intl_TokenID); + for (sal_uInt32 i = 0; i < nCount; i++) + { + OString aKey = getIntlKeywordAscii(Intl_TokenID[i]); + if (rToken.equalsIgnoreAsciiCase(aKey)) + return Intl_TokenID[i]; + } + + return InternationalKeyCode::None; +} + + +namespace +{ + + ::osl::Mutex& getSafteyMutex() + { + static ::osl::Mutex s_aSafety; + return s_aSafety; + } + + oslInterlockedCount& getCounter() + { + static oslInterlockedCount s_nCounter; + return s_nCounter; + } + + OSystemParseContext* getSharedContext(OSystemParseContext* _pContext, bool _bSet) + { + static OSystemParseContext* s_pSharedContext = nullptr; + if ( _pContext && !s_pSharedContext ) + { + s_pSharedContext = _pContext; + return s_pSharedContext; + } + if ( _bSet ) + { + OSystemParseContext* pReturn = _pContext ? _pContext : s_pSharedContext; + s_pSharedContext = _pContext; + return pReturn; + } + return s_pSharedContext; + } + +} + +OParseContextClient::OParseContextClient() +{ + ::osl::MutexGuard aGuard( getSafteyMutex() ); + if ( 1 == osl_atomic_increment( &getCounter() ) ) + { // first instance + getSharedContext( new OSystemParseContext, false ); + } +} + + +OParseContextClient::~OParseContextClient() +{ + ::osl::MutexGuard aGuard( getSafteyMutex() ); + if ( 0 == osl_atomic_decrement( &getCounter() ) ) + delete getSharedContext(nullptr,true); +} + +const OSystemParseContext* OParseContextClient::getParseContext() const +{ + return getSharedContext(nullptr, false); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/dataaccessdescriptor.cxx b/svx/source/form/dataaccessdescriptor.cxx new file mode 100644 index 000000000..78538fbeb --- /dev/null +++ b/svx/source/form/dataaccessdescriptor.cxx @@ -0,0 +1,356 @@ +/* -*- 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 <svx/dataaccessdescriptor.hxx> +#include <osl/diagnose.h> +#include <com/sun/star/ucb/XContent.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <tools/urlobj.hxx> +#include <map> + +namespace svx +{ + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::ucb; + + typedef std::pair<OUString const, DataAccessDescriptorProperty> PropertyMapEntry; + + class ODADescriptorImpl + { + protected: + bool m_bSetOutOfDate : 1; + bool m_bSequenceOutOfDate : 1; + + public: + typedef ::std::map< DataAccessDescriptorProperty, Any > DescriptorValues; + DescriptorValues m_aValues; + Sequence< PropertyValue > m_aAsSequence; + + typedef ::std::map< OUString, DataAccessDescriptorProperty > MapString2PropertyEntry; + + public: + ODADescriptorImpl(); + ODADescriptorImpl(const ODADescriptorImpl& _rSource); + + void invalidateExternRepresentations(); + + void updateSequence(); + + /** builds the descriptor from a property value sequence + @return <TRUE/> + if and only if the sequence contained valid properties only + */ + bool buildFrom( const Sequence< PropertyValue >& _rValues ); + + /** builds the descriptor from a property set + @return <TRUE/> + if and only if the set contained valid properties only + */ + bool buildFrom( const Reference< XPropertySet >& _rValues ); + + protected: + static PropertyValue buildPropertyValue( const DescriptorValues::const_iterator& _rPos ); + static const MapString2PropertyEntry& getPropertyMap( ); + static PropertyMapEntry const * getPropertyMapEntry( const DescriptorValues::const_iterator& _rPos ); + }; + + ODADescriptorImpl::ODADescriptorImpl() + :m_bSetOutOfDate(true) + ,m_bSequenceOutOfDate(true) + { + } + + ODADescriptorImpl::ODADescriptorImpl(const ODADescriptorImpl& _rSource) + :m_bSetOutOfDate( _rSource.m_bSetOutOfDate ) + ,m_bSequenceOutOfDate( _rSource.m_bSequenceOutOfDate ) + ,m_aValues( _rSource.m_aValues ) + { + if (!m_bSequenceOutOfDate) + m_aAsSequence = _rSource.m_aAsSequence; + } + + bool ODADescriptorImpl::buildFrom( const Sequence< PropertyValue >& _rValues ) + { + const MapString2PropertyEntry& rProperties = getPropertyMap(); + + bool bValidPropsOnly = true; + + // loop through the sequence, and fill our m_aValues + for (const PropertyValue& rValue : _rValues) + { + MapString2PropertyEntry::const_iterator aPropPos = rProperties.find( rValue.Name ); + if ( aPropPos != rProperties.end() ) + { + DataAccessDescriptorProperty eProperty = aPropPos->second; + m_aValues[eProperty] = rValue.Value; + } + else + // unknown property + bValidPropsOnly = false; + } + + if (bValidPropsOnly) + { + m_aAsSequence = _rValues; + m_bSequenceOutOfDate = false; + } + else + m_bSequenceOutOfDate = true; + + return bValidPropsOnly; + } + + bool ODADescriptorImpl::buildFrom( const Reference< XPropertySet >& _rxValues ) + { + Reference< XPropertySetInfo > xPropInfo; + if (_rxValues.is()) + xPropInfo = _rxValues->getPropertySetInfo(); + if (!xPropInfo.is()) + { + OSL_FAIL("ODADescriptorImpl::buildFrom: invalid property set!"); + return false; + } + + // build a PropertyValue sequence with the current values + const Sequence< Property > aProperties = xPropInfo->getProperties(); + + Sequence< PropertyValue > aValues(aProperties.getLength()); + PropertyValue* pValues = aValues.getArray(); + + for (const Property& rProperty : aProperties) + { + pValues->Name = rProperty.Name; + pValues->Value = _rxValues->getPropertyValue(rProperty.Name); + ++pValues; + } + + bool bValidPropsOnly = buildFrom(aValues); + m_bSetOutOfDate = !bValidPropsOnly; + + return bValidPropsOnly; + } + + void ODADescriptorImpl::invalidateExternRepresentations() + { + m_bSetOutOfDate = true; + m_bSequenceOutOfDate = true; + } + + const ODADescriptorImpl::MapString2PropertyEntry& ODADescriptorImpl::getPropertyMap( ) + { + // the properties we know + static MapString2PropertyEntry s_aProperties + { + { OUString("ActiveConnection"), DataAccessDescriptorProperty::Connection, }, + { OUString("BookmarkSelection"), DataAccessDescriptorProperty::BookmarkSelection, }, + { OUString("Column"), DataAccessDescriptorProperty::ColumnObject, }, + { OUString("ColumnName"), DataAccessDescriptorProperty::ColumnName, }, + { OUString("Command"), DataAccessDescriptorProperty::Command, }, + { OUString("CommandType"), DataAccessDescriptorProperty::CommandType, }, + { OUString("Component"), DataAccessDescriptorProperty::Component, }, + { OUString("ConnectionResource"), DataAccessDescriptorProperty::ConnectionResource, }, + { OUString("Cursor"), DataAccessDescriptorProperty::Cursor, }, + { OUString("DataSourceName"), DataAccessDescriptorProperty::DataSource, }, + { OUString("DatabaseLocation"), DataAccessDescriptorProperty::DatabaseLocation, }, + { OUString("EscapeProcessing"), DataAccessDescriptorProperty::EscapeProcessing, }, + { OUString("Filter"), DataAccessDescriptorProperty::Filter, }, + { OUString("Selection"), DataAccessDescriptorProperty::Selection, } + }; + + return s_aProperties; + } + + PropertyMapEntry const * ODADescriptorImpl::getPropertyMapEntry( const DescriptorValues::const_iterator& _rPos ) + { + const MapString2PropertyEntry& rProperties = getPropertyMap(); + + DataAccessDescriptorProperty nNeededHandle = _rPos->first; + + auto loop = std::find_if(rProperties.begin(), rProperties.end(), + [&nNeededHandle](const MapString2PropertyEntry::value_type& rProp) { return nNeededHandle == rProp.second; }); + if (loop != rProperties.end()) + return &*loop; + throw RuntimeException(); + } + + PropertyValue ODADescriptorImpl::buildPropertyValue( const DescriptorValues::const_iterator& _rPos ) + { + // the map entry + PropertyMapEntry const * pProperty = getPropertyMapEntry( _rPos ); + + // build the property value + PropertyValue aReturn; + aReturn.Name = pProperty->first; + aReturn.Handle = static_cast<sal_Int32>(pProperty->second); + aReturn.Value = _rPos->second; + aReturn.State = PropertyState_DIRECT_VALUE; + + // outta here + return aReturn; + } + + void ODADescriptorImpl::updateSequence() + { + if (!m_bSequenceOutOfDate) + return; + + m_aAsSequence.realloc(m_aValues.size()); + PropertyValue* pValue = m_aAsSequence.getArray(); + + // loop through all our values + for ( DescriptorValues::const_iterator aLoop = m_aValues.begin(); + aLoop != m_aValues.end(); + ++aLoop, ++pValue + ) + { + *pValue = buildPropertyValue(aLoop); + } + + // don't need to rebuild next time + m_bSequenceOutOfDate = false; + } + + ODataAccessDescriptor::ODataAccessDescriptor() + :m_pImpl(new ODADescriptorImpl) + { + } + + ODataAccessDescriptor::ODataAccessDescriptor( const ODataAccessDescriptor& _rSource ) + :m_pImpl(new ODADescriptorImpl(*_rSource.m_pImpl)) + { + } + + ODataAccessDescriptor::ODataAccessDescriptor(ODataAccessDescriptor&& _rSource) noexcept + :m_pImpl(std::move(_rSource.m_pImpl)) + { + } + + ODataAccessDescriptor& ODataAccessDescriptor::operator=(const ODataAccessDescriptor& _rSource) + { + if (this != &_rSource) + m_pImpl.reset(new ODADescriptorImpl(*_rSource.m_pImpl)); + return *this; + } + + ODataAccessDescriptor& ODataAccessDescriptor::operator=(ODataAccessDescriptor&& _rSource) noexcept + { + m_pImpl = std::move(_rSource.m_pImpl); + return *this; + } + + ODataAccessDescriptor::ODataAccessDescriptor( const Reference< XPropertySet >& _rValues ) + :m_pImpl(new ODADescriptorImpl) + { + m_pImpl->buildFrom(_rValues); + } + + ODataAccessDescriptor::ODataAccessDescriptor( const Any& _rValues ) + :m_pImpl(new ODADescriptorImpl) + { + // check if we know the format in the Any + Sequence< PropertyValue > aValues; + Reference< XPropertySet > xValues; + if ( _rValues >>= aValues ) + m_pImpl->buildFrom( aValues ); + else if ( _rValues >>= xValues ) + m_pImpl->buildFrom( xValues ); + } + + ODataAccessDescriptor::ODataAccessDescriptor( const Sequence< PropertyValue >& _rValues ) + :m_pImpl(new ODADescriptorImpl) + { + m_pImpl->buildFrom(_rValues); + } + + ODataAccessDescriptor::~ODataAccessDescriptor() + { + } + + void ODataAccessDescriptor::clear() + { + m_pImpl->m_aValues.clear(); + } + + void ODataAccessDescriptor::erase(DataAccessDescriptorProperty _eWhich) + { + OSL_ENSURE(has(_eWhich), "ODataAccessDescriptor::erase: invalid call!"); + if (has(_eWhich)) + m_pImpl->m_aValues.erase(_eWhich); + } + + bool ODataAccessDescriptor::has(DataAccessDescriptorProperty _eWhich) const + { + return m_pImpl->m_aValues.find(_eWhich) != m_pImpl->m_aValues.end(); + } + + const Any& ODataAccessDescriptor::operator [] ( DataAccessDescriptorProperty _eWhich ) const + { + if (!has(_eWhich)) + { + OSL_FAIL("ODataAccessDescriptor::operator[]: invalid accessor!"); + static const Any aDummy; + return aDummy; + } + + return m_pImpl->m_aValues[_eWhich]; + } + + Any& ODataAccessDescriptor::operator[] ( DataAccessDescriptorProperty _eWhich ) + { + m_pImpl->invalidateExternRepresentations(); + return m_pImpl->m_aValues[_eWhich]; + } + + void ODataAccessDescriptor::initializeFrom(const Sequence< PropertyValue >& _rValues) + { + clear(); + m_pImpl->buildFrom(_rValues); + } + + Sequence< PropertyValue > const & ODataAccessDescriptor::createPropertyValueSequence() + { + m_pImpl->updateSequence(); + return m_pImpl->m_aAsSequence; + } + + OUString ODataAccessDescriptor::getDataSource() const + { + OUString sDataSourceName; + if ( has(DataAccessDescriptorProperty::DataSource) ) + (*this)[DataAccessDescriptorProperty::DataSource] >>= sDataSourceName; + else if ( has(DataAccessDescriptorProperty::DatabaseLocation) ) + (*this)[DataAccessDescriptorProperty::DatabaseLocation] >>= sDataSourceName; + return sDataSourceName; + } + + void ODataAccessDescriptor::setDataSource(const OUString& _sDataSourceNameOrLocation) + { + if ( !_sDataSourceNameOrLocation.isEmpty() ) + { + INetURLObject aURL(_sDataSourceNameOrLocation); + (*this)[ (( aURL.GetProtocol() == INetProtocol::File ) ? DataAccessDescriptorProperty::DatabaseLocation : DataAccessDescriptorProperty::DataSource)] <<= _sDataSourceNameOrLocation; + } + else + (*this)[ DataAccessDescriptorProperty::DataSource ] <<= OUString(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/databaselocationinput.cxx b/svx/source/form/databaselocationinput.cxx new file mode 100644 index 000000000..3e28e2154 --- /dev/null +++ b/svx/source/form/databaselocationinput.cxx @@ -0,0 +1,249 @@ +/* -*- 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 <svx/databaselocationinput.hxx> +#include <svx/dialmgr.hxx> + +#include <svx/strings.hrc> + +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <comphelper/namedvaluecollection.hxx> +#include <rtl/ustrbuf.hxx> +#include <sfx2/filedlghelper.hxx> +#include <svl/filenotation.hxx> +#include <svtools/inettbc.hxx> +#include <tools/diagnose_ex.h> +#include <unotools/confignode.hxx> +#include <unotools/ucbhelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +namespace svx +{ + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::container::XNameAccess; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::uno::Exception; + + namespace TemplateDescription = ::com::sun::star::ui::dialogs::TemplateDescription; + + class DatabaseLocationInputController_Impl + { + public: + DatabaseLocationInputController_Impl( + const Reference<XComponentContext>& _rContext, + SvtURLBox& _rLocationInput, + weld::Button& _rBrowseButton, + weld::Window& _rDialog + ); + + bool prepareCommit(); + void setURL( const OUString& _rURL ); + OUString getURL() const; + + private: + void impl_initFilterProperties_nothrow(); + void impl_onBrowseButtonClicked(); + OUString impl_getCurrentURL() const; + + DECL_LINK( OnButtonAction, weld::Button&, void ); + + private: + const Reference<XComponentContext> m_xContext; + SvtURLBox& m_rLocationInput; + weld::Window& m_rDialog; + Sequence< OUString > m_aFilterExtensions; + OUString m_sFilterUIName; + bool m_bNeedExistenceCheck; + }; + + DatabaseLocationInputController_Impl::DatabaseLocationInputController_Impl(const Reference<XComponentContext>& _rContext, + SvtURLBox& _rLocationInput, weld::Button& _rBrowseButton, weld::Window& _rDialog) + :m_xContext( _rContext ) + ,m_rLocationInput( _rLocationInput ) + ,m_rDialog( _rDialog ) + ,m_aFilterExtensions() + ,m_sFilterUIName() + ,m_bNeedExistenceCheck( true ) + { + impl_initFilterProperties_nothrow(); + + // forward the allowed extensions to the input control + OUStringBuffer aExtensionList; + for ( auto const & extension : std::as_const(m_aFilterExtensions) ) + { + aExtensionList.append( extension ); + aExtensionList.append( ';' ); + } + m_rLocationInput.SetFilter( aExtensionList.makeStringAndClear() ); + _rBrowseButton.connect_clicked(LINK(this, DatabaseLocationInputController_Impl, OnButtonAction)); + } + + bool DatabaseLocationInputController_Impl::prepareCommit() + { + OUString sURL( impl_getCurrentURL() ); + if ( sURL.isEmpty() ) + return false; + + // check if the name exists + if ( m_bNeedExistenceCheck ) + { + if ( ::utl::UCBContentHelper::Exists( sURL ) ) + { + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(m_rLocationInput.getWidget(), + VclMessageType::Question, VclButtonsType::YesNo, + SvxResId(RID_STR_ALREADYEXISTOVERWRITE))); + if (xQueryBox->run() != RET_YES) + return false; + } + } + + return true; + } + + void DatabaseLocationInputController_Impl::setURL( const OUString& _rURL ) + { + ::svt::OFileNotation aTransformer( _rURL ); + m_rLocationInput.set_entry_text( aTransformer.get( ::svt::OFileNotation::N_SYSTEM ) ); + } + + OUString DatabaseLocationInputController_Impl::getURL() const + { + return impl_getCurrentURL(); + } + + void DatabaseLocationInputController_Impl::impl_initFilterProperties_nothrow() + { + try + { + // get the name of the default filter for database documents + ::utl::OConfigurationTreeRoot aConfig( + ::utl::OConfigurationTreeRoot::createWithComponentContext( + m_xContext, + "/org.openoffice.Setup/Office/Factories/com.sun.star.sdb.OfficeDatabaseDocument" + ) ); + OUString sDatabaseFilter; + OSL_VERIFY( aConfig.getNodeValue( "ooSetupFactoryActualFilter" ) >>= sDatabaseFilter ); + + // get the type this filter is responsible for + Reference< XNameAccess > xFilterFactory( + m_xContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.FilterFactory", m_xContext), + UNO_QUERY_THROW ); + ::comphelper::NamedValueCollection aFilterProperties( xFilterFactory->getByName( sDatabaseFilter ) ); + OUString sDocumentType = aFilterProperties.getOrDefault( "Type", OUString() ); + + // get the extension(s) for this type + Reference< XNameAccess > xTypeDetection( + m_xContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.TypeDetection", m_xContext), + UNO_QUERY_THROW ); + + ::comphelper::NamedValueCollection aTypeProperties( xTypeDetection->getByName( sDocumentType ) ); + m_aFilterExtensions = aTypeProperties.getOrDefault( "Extensions", m_aFilterExtensions ); + m_sFilterUIName = aTypeProperties.getOrDefault( "UIName", m_sFilterUIName ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + // ensure we have at least one extension + OSL_ENSURE( m_aFilterExtensions.hasElements(), + "DatabaseLocationInputController_Impl::impl_initFilterProperties_nothrow: unable to determine the file extension(s)!" ); + if ( !m_aFilterExtensions.hasElements() ) + { + m_aFilterExtensions.realloc(1); + m_aFilterExtensions[0] = "*.odb"; + } + } + + IMPL_LINK_NOARG(DatabaseLocationInputController_Impl, OnButtonAction, weld::Button&, void) + { + impl_onBrowseButtonClicked(); + } + + OUString DatabaseLocationInputController_Impl::impl_getCurrentURL() const + { + OUString sCurrentFile( m_rLocationInput.get_active_text() ); + if ( !sCurrentFile.isEmpty() ) + { + ::svt::OFileNotation aCurrentFile( sCurrentFile ); + sCurrentFile = aCurrentFile.get( ::svt::OFileNotation::N_URL ); + } + return sCurrentFile; + } + + void DatabaseLocationInputController_Impl::impl_onBrowseButtonClicked() + { + ::sfx2::FileDialogHelper aFileDlg( + TemplateDescription::FILESAVE_AUTOEXTENSION, + FileDialogFlags::NONE, + &m_rDialog + ); + aFileDlg.SetDisplayDirectory( impl_getCurrentURL() ); + + aFileDlg.AddFilter( m_sFilterUIName, "*." + m_aFilterExtensions[0] ); + aFileDlg.SetCurrentFilter( m_sFilterUIName ); + + if ( aFileDlg.Execute() == ERRCODE_NONE ) + { + INetURLObject aURL( aFileDlg.GetPath() ); + if( aURL.GetProtocol() != INetProtocol::NotValid ) + { + ::svt::OFileNotation aFileNotation( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + m_rLocationInput.set_entry_text(aFileNotation.get(::svt::OFileNotation::N_SYSTEM)); + m_rLocationInput.trigger_changed(); + // the dialog already checked for the file's existence, so we don't need to, again + m_bNeedExistenceCheck = false; + } + } + } + + DatabaseLocationInputController::DatabaseLocationInputController( const Reference<XComponentContext>& _rContext, + SvtURLBox& _rLocationInput, weld::Button& _rBrowseButton, weld::Window& _rDialog ) + :m_pImpl( new DatabaseLocationInputController_Impl( _rContext, _rLocationInput, _rBrowseButton, _rDialog ) ) + { + } + + DatabaseLocationInputController::~DatabaseLocationInputController() + { + } + + bool DatabaseLocationInputController::prepareCommit() + { + return m_pImpl->prepareCommit(); + } + + void DatabaseLocationInputController::setURL( const OUString& _rURL ) + { + m_pImpl->setURL( _rURL ); + } + + OUString DatabaseLocationInputController::getURL() const + { + return m_pImpl->getURL(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/datalistener.cxx b/svx/source/form/datalistener.cxx new file mode 100644 index 000000000..fda74a92c --- /dev/null +++ b/svx/source/form/datalistener.cxx @@ -0,0 +1,91 @@ +/* -*- 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 <datalistener.hxx> +#include <datanavi.hxx> + +#include <sal/log.hxx> + +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::dom::events; + + +namespace svxform +{ + + + DataListener::DataListener( DataNavigatorWindow* pNaviWin ) : + + m_pNaviWin( pNaviWin ) + + { + DBG_ASSERT( m_pNaviWin, "DataListener::Ctor(): no navigator win" ); + } + + DataListener::~DataListener() + { + } + + // XContainerListener + void SAL_CALL DataListener::elementInserted( const ContainerEvent& /*Event*/ ) + { + m_pNaviWin->NotifyChanges(); + } + + void SAL_CALL DataListener::elementRemoved( const ContainerEvent& /*Event*/ ) + { + m_pNaviWin->NotifyChanges(); + } + + void SAL_CALL DataListener::elementReplaced( const ContainerEvent& /*Event*/ ) + { + m_pNaviWin->NotifyChanges(); + } + + // XFrameActionListener + void SAL_CALL DataListener::frameAction( const FrameActionEvent& rActionEvt ) + { + if ( FrameAction_COMPONENT_ATTACHED == rActionEvt.Action || + FrameAction_COMPONENT_REATTACHED == rActionEvt.Action ) + { + m_pNaviWin->NotifyChanges( FrameAction_COMPONENT_REATTACHED == rActionEvt.Action ); + } + } + + // xml::dom::events::XEventListener + void SAL_CALL DataListener::handleEvent( const Reference< XEvent >& /*evt*/ ) + { + m_pNaviWin->NotifyChanges(); + } + + // lang::XEventListener + void SAL_CALL DataListener::disposing( const EventObject& /*Source*/ ) + { + SAL_WARN( "svx.form", "disposing" ); + } + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/datanavi.cxx b/svx/source/form/datanavi.cxx new file mode 100644 index 000000000..acf51c0ad --- /dev/null +++ b/svx/source/form/datanavi.cxx @@ -0,0 +1,3229 @@ +/* -*- 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 <sal/config.h> + +#include <memory> + +#include <sal/log.hxx> +#include <datanavi.hxx> +#include <fmservs.hxx> + +#include <bitmaps.hlst> +#include <fpicker/strings.hrc> +#include <svx/svxids.hrc> +#include <tools/diagnose_ex.h> +#include <unotools/resmgr.hxx> +#include <svx/xmlexchg.hxx> +#include <svtools/miscopt.hxx> +#include <unotools/pathoptions.hxx> +#include <unotools/viewoptions.hxx> +#include <vcl/treelistentry.hxx> +#include <sfx2/filedlghelper.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <vcl/weld.hxx> +#include <vcl/builderfactory.hxx> +#include <vcl/event.hxx> +#include <vcl/layout.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/container/XSet.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/xforms/XFormsSupplier.hpp> +#include <com/sun/star/xml/dom/XDocument.hpp> +#include <comphelper/string.hxx> + +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::datatransfer; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::dom::events; +using namespace ::svx; + +#define CFGNAME_DATANAVIGATOR "DataNavigator" +#define CFGNAME_SHOWDETAILS "ShowDetails" +#define MSG_VARIABLE "%1" +#define MODELNAME "$MODELNAME" +#define INSTANCENAME "$INSTANCENAME" +#define ELEMENTNAME "$ELEMENTNAME" +#define ATTRIBUTENAME "$ATTRIBUTENAME" +#define SUBMISSIONNAME "$SUBMISSIONNAME" +#define BINDINGNAME "$BINDINGNAME" + + +namespace svxform +{ + + // properties of instance + #define PN_INSTANCE_MODEL "Instance" + #define PN_INSTANCE_ID "ID" + #define PN_INSTANCE_URL "URL" + + // properties of binding + #define PN_BINDING_ID "BindingID" + #define PN_BINDING_EXPR "BindingExpression" + #define PN_BINDING_MODEL "Model" + #define PN_BINDING_NAMESPACES "ModelNamespaces" + #define PN_READONLY_EXPR "ReadonlyExpression" + #define PN_RELEVANT_EXPR "RelevantExpression" + #define PN_REQUIRED_EXPR "RequiredExpression" + #define PN_CONSTRAINT_EXPR "ConstraintExpression" + #define PN_CALCULATE_EXPR "CalculateExpression" + #define PN_BINDING_TYPE "Type" + + // properties of submission + #define PN_SUBMISSION_ID "ID" + #define PN_SUBMISSION_BIND "Bind" + #define PN_SUBMISSION_REF "Ref" + #define PN_SUBMISSION_ACTION "Action" + #define PN_SUBMISSION_METHOD "Method" + #define PN_SUBMISSION_REPLACE "Replace" + + // other const strings + #define TRUE_VALUE "true()" + #define NEW_ELEMENT "newElement" + #define NEW_ATTRIBUTE "newAttribute" + #define EVENTTYPE_CHARDATA "DOMCharacterDataModified" + #define EVENTTYPE_ATTR "DOMAttrModified" + + #define MIN_PAGE_COUNT 3 // at least one instance, one submission and one binding page + + struct ItemNode + { + Reference< css::xml::dom::XNode > m_xNode; + Reference< XPropertySet > m_xPropSet; + + explicit ItemNode( const Reference< css::xml::dom::XNode >& _rxNode ) : + m_xNode( _rxNode ) {} + explicit ItemNode( const Reference< XPropertySet >& _rxSet ) : + m_xPropSet( _rxSet ) {} + }; + + DataTreeListBox::DataTreeListBox(vcl::Window* pParent, WinBits nBits) + : SvTreeListBox(pParent, nBits) + , m_pXFormsPage(nullptr) + , m_eGroup(DGTUnknown) + , m_nAddId(0) + , m_nAddElementId(0) + , m_nAddAttributeId(0) + , m_nEditId(0) + , m_nRemoveId(0) + { + EnableContextMenuHandling(); + + if ( DGTInstance == m_eGroup ) + SetDragDropMode( DragDropMode::CTRL_MOVE |DragDropMode::CTRL_COPY | DragDropMode::APP_COPY ); + } + + DataTreeListBox::~DataTreeListBox() + { + disposeOnce(); + } + + void DataTreeListBox::dispose() + { + DeleteAndClear(); + m_xMenu.clear(); + m_xBuilder.reset(); + m_pXFormsPage.clear(); + SvTreeListBox::dispose(); + } + + sal_Int8 DataTreeListBox::AcceptDrop( const AcceptDropEvent& /*rEvt*/ ) + { + return DND_ACTION_NONE; + } + + sal_Int8 DataTreeListBox::ExecuteDrop( const ExecuteDropEvent& /*rEvt*/ ) + { + return DND_ACTION_NONE; + } + + void DataTreeListBox::StartDrag( sal_Int8 /*_nAction*/, const Point& /*_rPosPixel*/ ) + { + SvTreeListEntry* pSelected = FirstSelected(); + if ( !pSelected ) + // no drag without an entry + return; + + if ( m_eGroup == DGTBinding ) + // for the moment, bindings cannot be dragged. + // #i59395# / 2005-12-15 / frank.schoenheit@sun.com + return; + + // GetServiceNameForNode() requires a datatype repository which + // will be automatically build if requested??? + Reference< css::xforms::XModel > xModel( m_pXFormsPage->GetXFormsHelper(), UNO_QUERY ); + Reference< css::xforms::XDataTypeRepository > xDataTypes = + xModel->getDataTypeRepository(); + if(!xDataTypes.is()) + return; + + ItemNode *pItemNode = static_cast<ItemNode*>(pSelected->GetUserData()); + + if ( !pItemNode ) + { + // the only known (and allowed?) case where this happens are sub-entries of a submission + // entry + DBG_ASSERT( DGTSubmission == m_eGroup, "DataTreeListBox::StartDrag: how this?" ); + pSelected = GetParent( pSelected ); + DBG_ASSERT( pSelected && !GetParent( pSelected ), "DataTreeListBox::StartDrag: what kind of entry *is* this?" ); + // on the submission page, we have only top-level entries (the submission themself) + // plus direct children of those (facets of a submission) + pItemNode = pSelected ? static_cast< ItemNode* >( pSelected->GetUserData() ) : nullptr; + if ( !pItemNode ) + return; + } + + OXFormsDescriptor desc; + desc.szName = GetEntryText(pSelected); + if(pItemNode->m_xNode.is()) { + // a valid node interface tells us that we need to create a control from a binding + desc.szServiceName = m_pXFormsPage->GetServiceNameForNode(pItemNode->m_xNode); + desc.xPropSet = m_pXFormsPage->GetBindingForNode(pItemNode->m_xNode); + DBG_ASSERT( desc.xPropSet.is(), "DataTreeListBox::StartDrag(): invalid node binding" ); + } + else { + desc.szServiceName = FM_COMPONENT_COMMANDBUTTON; + desc.xPropSet = pItemNode->m_xPropSet; + } + rtl::Reference<OXFormsTransferable> pTransferable = new OXFormsTransferable(desc); + EndSelection(); + pTransferable->StartDrag( this, DND_ACTION_COPY ); + } + + VclPtr<PopupMenu> DataTreeListBox::CreateContextMenu() + { + m_xMenu.disposeAndClear(); + m_xBuilder.reset(new VclBuilder(nullptr, VclBuilderContainer::getUIRootDir(), "svx/ui/formdatamenu.ui", "")); + m_xMenu.set(m_xBuilder->get_menu("menu")); + + if ( DGTInstance == m_eGroup ) + m_xMenu->RemoveItem(m_xMenu->GetItemPos(m_xMenu->GetItemId("additem"))); + else + { + m_xMenu->RemoveItem(m_xMenu->GetItemPos(m_xMenu->GetItemId("addelement"))); + m_xMenu->RemoveItem(m_xMenu->GetItemPos(m_xMenu->GetItemId("addattribute"))); + + if ( DGTSubmission == m_eGroup ) + { + m_xMenu->SetItemText(m_xMenu->GetItemId("additem"), SvxResId(RID_STR_DATANAV_ADD_SUBMISSION)); + m_xMenu->SetItemText(m_xMenu->GetItemId("edit"), SvxResId(RID_STR_DATANAV_EDIT_SUBMISSION)); + m_xMenu->SetItemText(m_xMenu->GetItemId("delete"), SvxResId(RID_STR_DATANAV_REMOVE_SUBMISSION)); + } + else + { + m_xMenu->SetItemText(m_xMenu->GetItemId("additem"), SvxResId(RID_STR_DATANAV_ADD_BINDING)); + m_xMenu->SetItemText(m_xMenu->GetItemId("edit"), SvxResId(RID_STR_DATANAV_EDIT_BINDING)); + m_xMenu->SetItemText(m_xMenu->GetItemId("delete"), SvxResId(RID_STR_DATANAV_REMOVE_BINDING)); + } + } + m_pXFormsPage->EnableMenuItems(m_xMenu.get()); + return m_xMenu; + } + + void DataTreeListBox::ExecuteContextMenuAction( sal_uInt16 _nSelectedPopupEntry ) + { + if (m_xMenu->GetItemId("additem") == _nSelectedPopupEntry) + m_pXFormsPage->DoMenuAction(m_nAddId); + else if (m_xMenu->GetItemId("addelement") == _nSelectedPopupEntry) + m_pXFormsPage->DoMenuAction(m_nAddElementId); + else if (m_xMenu->GetItemId("addattribute") == _nSelectedPopupEntry) + m_pXFormsPage->DoMenuAction(m_nAddAttributeId); + else if (m_xMenu->GetItemId("edit") == _nSelectedPopupEntry) + m_pXFormsPage->DoMenuAction(m_nEditId); + else if (m_xMenu->GetItemId("delete") == _nSelectedPopupEntry) + m_pXFormsPage->DoMenuAction(m_nRemoveId); + } + + void DataTreeListBox::RemoveEntry( SvTreeListEntry const * _pEntry ) + { + if ( _pEntry ) + { + delete static_cast< ItemNode* >( _pEntry->GetUserData() ); + SvTreeListBox::GetModel()->Remove( _pEntry ); + } + } + + void DataTreeListBox::SetGroup(DataGroupType _eGroup) + { + m_eGroup = _eGroup; + } + + void DataTreeListBox::SetXFormsPage(XFormsPage* _pPage) + { + m_pXFormsPage = _pPage; + } + + void DataTreeListBox::SetToolBoxItemIds(sal_uInt16 _nAddId, + sal_uInt16 _nAddElementId, + sal_uInt16 _nAddAttributeId, + sal_uInt16 _nEditId, + sal_uInt16 _nRemoveId) + { + m_nAddId = _nAddId; + m_nAddElementId = _nAddElementId; + m_nAddAttributeId = _nAddAttributeId; + m_nEditId = _nEditId; + m_nRemoveId = _nRemoveId; + } + + void DataTreeListBox::DeleteAndClear() + { + sal_uIntPtr i, nCount = GetEntryCount(); + for ( i = 0; i < nCount; ++i ) + { + SvTreeListEntry* pEntry = GetEntry(i); + if ( pEntry ) + delete static_cast< ItemNode* >( pEntry->GetUserData() ); + } + + Clear(); + } + + + + VCL_BUILDER_FACTORY_ARGS(DataTreeListBox, WB_BORDER) + + XFormsPage::XFormsPage( vcl::Window* pParent, DataNavigatorWindow* _pNaviWin, DataGroupType _eGroup ) : + + TabPage( pParent, "XFormsPage", "svx/ui/xformspage.ui" ), + m_pNaviWin ( _pNaviWin ), + m_bHasModel ( false ), + m_eGroup ( _eGroup ), + m_bLinkOnce ( false ) + + { + get(m_pToolBox, "toolbar"); + get(m_pItemList, "items"); + Size aSize(LogicToPixel(Size(63, 100), MapMode(MapUnit::MapAppFont))); + m_pItemList->set_width_request(aSize.Width()); + m_pItemList->set_height_request(aSize.Height()); + + m_pItemList->SetGroup(_eGroup); + m_pItemList->SetXFormsPage( this ); + + m_nAddId = m_pToolBox->GetItemId("TBI_ITEM_ADD"); + m_nAddElementId = m_pToolBox->GetItemId("TBI_ITEM_ADD_ELEMENT"); + m_nAddAttributeId = m_pToolBox->GetItemId("TBI_ITEM_ADD_ATTRIBUTE"); + m_nEditId = m_pToolBox->GetItemId("TBI_ITEM_EDIT"); + m_nRemoveId = m_pToolBox->GetItemId("TBI_ITEM_REMOVE"); + + m_pItemList->SetToolBoxItemIds(m_nAddId, m_nAddElementId, m_nAddAttributeId, m_nEditId, m_nRemoveId); + + m_pToolBox->InsertSeparator(4,5); + m_pToolBox->SetItemImage(m_nAddId, Image(StockImage::Yes, RID_SVXBMP_ADD)); + m_pToolBox->SetItemImage(m_nAddElementId, Image(StockImage::Yes, RID_SVXBMP_ADD_ELEMENT)); + m_pToolBox->SetItemImage(m_nAddAttributeId, Image(StockImage::Yes, RID_SVXBMP_ADD_ATTRIBUTE)); + m_pToolBox->SetItemImage(m_nEditId, Image(StockImage::Yes, RID_SVXBMP_EDIT)); + m_pToolBox->SetItemImage(m_nRemoveId, Image(StockImage::Yes, RID_SVXBMP_REMOVE)); + + if ( DGTInstance == m_eGroup ) + m_pToolBox->RemoveItem( m_pToolBox->GetItemPos( m_nAddId ) ); + else + { + m_pToolBox->RemoveItem( m_pToolBox->GetItemPos( m_nAddElementId ) ); + m_pToolBox->RemoveItem( m_pToolBox->GetItemPos( m_nAddAttributeId ) ); + + if ( DGTSubmission == m_eGroup ) + { + m_pToolBox->SetItemText( m_nAddId, SvxResId( RID_STR_DATANAV_ADD_SUBMISSION ) ); + m_pToolBox->SetItemText( m_nEditId, SvxResId( RID_STR_DATANAV_EDIT_SUBMISSION ) ); + m_pToolBox->SetItemText( m_nRemoveId, SvxResId( RID_STR_DATANAV_REMOVE_SUBMISSION ) ); + } + else + { + m_pToolBox->SetItemText( m_nAddId, SvxResId( RID_STR_DATANAV_ADD_BINDING ) ); + m_pToolBox->SetItemText( m_nEditId, SvxResId( RID_STR_DATANAV_EDIT_BINDING ) ); + m_pToolBox->SetItemText( m_nRemoveId, SvxResId( RID_STR_DATANAV_REMOVE_BINDING ) ); + } + } + + const Size aTbxSz( m_pToolBox->CalcWindowSizePixel() ); + m_pToolBox->SetSizePixel( aTbxSz ); + m_pToolBox->SetOutStyle( SvtMiscOptions().GetToolboxStyle() ); + m_pToolBox->SetSelectHdl( LINK( this, XFormsPage, TbxSelectHdl ) ); + Point aPos = m_pItemList->GetPosPixel(); + aPos.setY( aTbxSz.Height() ); + m_pItemList->SetPosPixel( aPos ); + + m_pItemList->SetSelectHdl( LINK( this, XFormsPage, ItemSelectHdl ) ); + m_pItemList->SetNodeDefaultImages(); + WinBits nBits = WB_BORDER | WB_TABSTOP | WB_HIDESELECTION | WB_NOINITIALSELECTION; + if ( DGTInstance == m_eGroup || DGTSubmission == m_eGroup ) + nBits |= WB_HASBUTTONS | WB_HASLINES | WB_HASLINESATROOT | WB_HASBUTTONSATROOT; + m_pItemList->SetStyle( m_pItemList->GetStyle() | nBits ); + m_pItemList->Show(); + ItemSelectHdl( m_pItemList ); + } + + XFormsPage::~XFormsPage() + { + disposeOnce(); + } + + void XFormsPage::dispose() + { + m_pToolBox.clear(); + m_pItemList.clear(); + m_pNaviWin.clear(); + TabPage::dispose(); + } + + IMPL_LINK_NOARG(XFormsPage, TbxSelectHdl, ToolBox *, void) + { + DoToolBoxAction( m_pToolBox->GetCurItemId() ); + } + + IMPL_LINK_NOARG(XFormsPage, ItemSelectHdl, SvTreeListBox*, void) + { + EnableMenuItems( nullptr ); + } + + void XFormsPage::AddChildren(SvTreeListEntry* _pParent, + const Reference< css::xml::dom::XNode >& _xNode) + { + DBG_ASSERT( m_xUIHelper.is(), "XFormsPage::AddChildren(): invalid UIHelper" ); + + try + { + Reference< css::xml::dom::XNodeList > xNodeList = _xNode->getChildNodes(); + if ( xNodeList.is() ) + { + bool bShowDetails = m_pNaviWin->IsShowDetails(); + sal_Int32 i, nNodeCount = xNodeList->getLength(); + for ( i = 0; i < nNodeCount; ++i ) + { + Reference< css::xml::dom::XNode > xChild = xNodeList->item(i); + css::xml::dom::NodeType eChildType = xChild->getNodeType(); + Image aExpImg, aCollImg; + switch ( eChildType ) + { + case css::xml::dom::NodeType_ATTRIBUTE_NODE: + aExpImg = aCollImg = Image(StockImage::Yes, RID_SVXBMP_ATTRIBUTE); + break; + case css::xml::dom::NodeType_ELEMENT_NODE: + aExpImg = aCollImg = Image(StockImage::Yes, RID_SVXBMP_ELEMENT); + break; + case css::xml::dom::NodeType_TEXT_NODE: + aExpImg = aCollImg = Image(StockImage::Yes, RID_SVXBMP_TEXT); + break; + default: + aExpImg = aCollImg = Image(StockImage::Yes, RID_SVXBMP_OTHER); + } + + OUString sName = m_xUIHelper->getNodeDisplayName( xChild, bShowDetails ); + if ( !sName.isEmpty() ) + { + ItemNode* pNode = new ItemNode( xChild ); + SvTreeListEntry* pEntry = m_pItemList->InsertEntry( + sName, aExpImg, aCollImg, _pParent, false, TREELIST_APPEND, pNode ); + if ( xChild->hasAttributes() ) + { + Reference< css::xml::dom::XNamedNodeMap > xMap = xChild->getAttributes(); + if ( xMap.is() ) + { + aExpImg = aCollImg = Image(StockImage::Yes, RID_SVXBMP_ATTRIBUTE); + sal_Int32 j, nMapLen = xMap->getLength(); + for ( j = 0; j < nMapLen; ++j ) + { + Reference< css::xml::dom::XNode > xAttr = xMap->item(j); + pNode = new ItemNode( xAttr ); + OUString sAttrName = + m_xUIHelper->getNodeDisplayName( xAttr, bShowDetails ); + m_pItemList->InsertEntry( + sAttrName, aExpImg, aCollImg, + pEntry, false, TREELIST_APPEND, pNode ); + } + } + } + if ( xChild->hasChildNodes() ) + AddChildren(pEntry, xChild); + } + } + } + } + catch( Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + bool XFormsPage::DoToolBoxAction( sal_uInt16 _nToolBoxID ) { + + bool bHandled = false; + bool bIsDocModified = false; + m_pNaviWin->DisableNotify( true ); + + if(_nToolBoxID == m_nAddId || _nToolBoxID == m_nAddElementId || _nToolBoxID == m_nAddAttributeId) + { + bHandled = true; + Reference< css::xforms::XModel > xModel( m_xUIHelper, UNO_QUERY ); + DBG_ASSERT( xModel.is(), "XFormsPage::DoToolBoxAction(): Action without model" ); + if ( DGTSubmission == m_eGroup ) + { + AddSubmissionDialog aDlg(GetFrameWeld(), nullptr, m_xUIHelper); + if ( aDlg.run() == RET_OK && aDlg.GetNewSubmission().is() ) + { + try + { + Reference< css::xforms::XSubmission > xNewSubmission = aDlg.GetNewSubmission(); + Reference< XSet > xSubmissions = xModel->getSubmissions(); + xSubmissions->insert( makeAny( xNewSubmission ) ); + SvTreeListEntry* pEntry = AddEntry( xNewSubmission ); + m_pItemList->Select( pEntry ); + bIsDocModified = true; + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::DoToolBoxAction()" ); + } + } + } + else + { + DataItemType eType = DITElement; + SvTreeListEntry* pEntry = m_pItemList->FirstSelected(); + std::unique_ptr<ItemNode> pNode; + Reference< css::xml::dom::XNode > xParentNode; + Reference< XPropertySet > xNewBinding; + const char* pResId = nullptr; + bool bIsElement = true; + if ( DGTInstance == m_eGroup ) + { + if ( !m_sInstanceURL.isEmpty() ) + { + LinkedInstanceWarningBox aMsgBox(GetFrameWeld()); + if (aMsgBox.run() != RET_OK) + return bHandled; + } + + DBG_ASSERT( pEntry, "XFormsPage::DoToolBoxAction(): no entry" ); + ItemNode* pParentNode = static_cast< ItemNode* >( pEntry->GetUserData() ); + DBG_ASSERT( pParentNode, "XFormsPage::DoToolBoxAction(): no parent node" ); + xParentNode = pParentNode->m_xNode; + Reference< css::xml::dom::XNode > xNewNode; + if ( m_nAddElementId == _nToolBoxID ) + { + try + { + pResId = RID_STR_DATANAV_ADD_ELEMENT; + xNewNode = m_xUIHelper->createElement( xParentNode, NEW_ELEMENT ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::DoToolBoxAction(): exception while create element" ); + } + } + else + { + pResId = RID_STR_DATANAV_ADD_ATTRIBUTE; + bIsElement = false; + eType = DITAttribute; + try + { + xNewNode = m_xUIHelper->createAttribute( xParentNode, NEW_ATTRIBUTE ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::DoToolBoxAction(): exception while create attribute" ); + } + } + + try + { + xNewNode = xParentNode->appendChild( xNewNode ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::DoToolBoxAction(): exception while append child" ); + } + + try + { + Reference< css::xml::dom::XNode > xPNode; + if ( xNewNode.is() ) + xPNode = xNewNode->getParentNode(); + // attributes don't have parents in the DOM model + DBG_ASSERT( m_nAddAttributeId == _nToolBoxID + || xPNode.is(), "XFormsPage::DoToolboxAction(): node not added" ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::DoToolboxAction()" ); + } + + try + { + m_xUIHelper->getBindingForNode( xNewNode, true ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::DoToolBoxAction(): exception while get binding for node" ); + } + pNode.reset(new ItemNode( xNewNode )); + } + else + { + try + { + pResId = RID_STR_DATANAV_ADD_BINDING; + xNewBinding = xModel->createBinding(); + Reference< XSet > xBindings = xModel->getBindings(); + xBindings->insert( makeAny( xNewBinding ) ); + pNode.reset(new ItemNode( xNewBinding )); + eType = DITBinding; + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::DoToolBoxAction(): exception while adding binding" ); + } + } + + AddDataItemDialog aDlg(GetFrameWeld(), pNode.get(), m_xUIHelper); + aDlg.set_title(SvxResId(pResId)); + aDlg.InitText( eType ); + short nReturn = aDlg.run(); + if ( DGTInstance == m_eGroup ) + { + if ( RET_OK == nReturn ) + { + SvTreeListEntry* pNewEntry = AddEntry( std::move(pNode), bIsElement ); + m_pItemList->MakeVisible( pNewEntry ); + m_pItemList->Select( pNewEntry ); + bIsDocModified = true; + } + else + { + try + { + Reference< css::xml::dom::XNode > xPNode; + Reference< css::xml::dom::XNode > xNode = + xParentNode->removeChild( pNode->m_xNode ); + if ( xNode.is() ) + xPNode = xNode->getParentNode(); + DBG_ASSERT( !xPNode.is(), "XFormsPage::RemoveEntry(): node not removed" ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::DoToolboxAction()" ); + } + } + } + else + { + if ( RET_OK == nReturn ) + { + SvTreeListEntry* pNewEntry = AddEntry( xNewBinding ); + m_pItemList->Select( pNewEntry ); + bIsDocModified = true; + } + else + { + try + { + Reference< XSet > xBindings = xModel->getBindings(); + xBindings->remove( makeAny( xNewBinding ) ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::DoToolboxAction()" ); + } + } + } + } + } + else if(_nToolBoxID == m_nEditId) + { + bHandled = true; + SvTreeListEntry* pEntry = m_pItemList->FirstSelected(); + if ( pEntry ) + { + if ( DGTSubmission == m_eGroup && m_pItemList->GetParent( pEntry ) ) + pEntry = m_pItemList->GetParent( pEntry ); + ItemNode* pNode = static_cast< ItemNode* >( pEntry->GetUserData() ); + if ( DGTInstance == m_eGroup || DGTBinding == m_eGroup ) + { + if ( DGTInstance == m_eGroup && !m_sInstanceURL.isEmpty() ) + { + LinkedInstanceWarningBox aMsgBox(GetFrameWeld()); + if (aMsgBox.run() != RET_OK) + return bHandled; + } + + AddDataItemDialog aDlg(GetFrameWeld(), pNode, m_xUIHelper); + DataItemType eType = DITElement; + const char* pResId = RID_STR_DATANAV_EDIT_ELEMENT; + if ( pNode && pNode->m_xNode.is() ) + { + try + { + css::xml::dom::NodeType eChildType = pNode->m_xNode->getNodeType(); + if ( eChildType == css::xml::dom::NodeType_ATTRIBUTE_NODE ) + { + pResId = RID_STR_DATANAV_EDIT_ATTRIBUTE; + eType = DITAttribute; + } + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::DoToolboxAction()" ); + } + } + else if ( DGTBinding == m_eGroup ) + { + pResId = RID_STR_DATANAV_EDIT_BINDING; + eType = DITBinding; + } + aDlg.set_title(SvxResId(pResId)); + aDlg.InitText( eType ); + if (aDlg.run() == RET_OK) + { + // Set the new name + OUString sNewName; + if ( DGTInstance == m_eGroup ) + { + try + { + sNewName = m_xUIHelper->getNodeDisplayName( + pNode->m_xNode, m_pNaviWin->IsShowDetails() ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::DoToolboxAction()" ); + } + } + else if (pNode) + { + try + { + OUString sTemp; + pNode->m_xPropSet->getPropertyValue( PN_BINDING_ID ) >>= sTemp; + sNewName += sTemp + ": "; + pNode->m_xPropSet->getPropertyValue( PN_BINDING_EXPR ) >>= sTemp; + sNewName += sTemp; + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::DoToolboxAction()" ); + } + } + + m_pItemList->SetEntryText( pEntry, sNewName ); + bIsDocModified = true; + } + } + else + { + AddSubmissionDialog aDlg(GetFrameWeld(), pNode, m_xUIHelper); + aDlg.set_title(SvxResId(RID_STR_DATANAV_EDIT_SUBMISSION)); + if (aDlg.run() == RET_OK) + { + EditEntry( pNode->m_xPropSet ); + bIsDocModified = true; + } + } + } + } + else if(_nToolBoxID == m_nRemoveId) + { + bHandled = true; + if ( DGTInstance == m_eGroup && !m_sInstanceURL.isEmpty() ) + { + LinkedInstanceWarningBox aMsgBox(GetFrameWeld()); + if (aMsgBox.run() != RET_OK) + return bHandled; + } + bIsDocModified = RemoveEntry(); + } + else + { + OSL_FAIL( "XFormsPage::DoToolboxAction: unknown ID!" ); + } + + m_pNaviWin->DisableNotify( false ); + EnableMenuItems( nullptr ); + if ( bIsDocModified ) + svxform::DataNavigatorWindow::SetDocModified(); + return bHandled; + } + + SvTreeListEntry* XFormsPage::AddEntry( std::unique_ptr<ItemNode> _pNewNode, bool _bIsElement ) + { + SvTreeListEntry* pParent = m_pItemList->FirstSelected(); + Image aImage(StockImage::Yes, _bIsElement ? OUString(RID_SVXBMP_ELEMENT) : OUString(RID_SVXBMP_ATTRIBUTE)); + OUString sName; + try + { + sName = m_xUIHelper->getNodeDisplayName( + _pNewNode->m_xNode, m_pNaviWin->IsShowDetails() ); + } + catch ( Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return m_pItemList->InsertEntry( + sName, aImage, aImage, pParent, false, TREELIST_APPEND, _pNewNode.release() ); + } + + SvTreeListEntry* XFormsPage::AddEntry( const Reference< XPropertySet >& _rEntry ) + { + SvTreeListEntry* pEntry = nullptr; + Image aImage(StockImage::Yes, RID_SVXBMP_ELEMENT); + + ItemNode* pNode = new ItemNode( _rEntry ); + OUString sTemp; + + if ( DGTSubmission == m_eGroup ) + { + try + { + // ID + _rEntry->getPropertyValue( PN_SUBMISSION_ID ) >>= sTemp; + pEntry = m_pItemList->InsertEntry( sTemp, aImage, aImage, nullptr, false, TREELIST_APPEND, pNode ); + // Action + _rEntry->getPropertyValue( PN_SUBMISSION_ACTION ) >>= sTemp; + OUString sEntry = SvxResId( RID_STR_DATANAV_SUBM_ACTION ) + sTemp; + m_pItemList->InsertEntry( sEntry, aImage, aImage, pEntry ); + // Method + _rEntry->getPropertyValue( PN_SUBMISSION_METHOD ) >>= sTemp; + sEntry = SvxResId( RID_STR_DATANAV_SUBM_METHOD ) + + m_aMethodString.toUI( sTemp ); + m_pItemList->InsertEntry( sEntry, aImage, aImage, pEntry ); + // Ref + _rEntry->getPropertyValue( PN_SUBMISSION_REF ) >>= sTemp; + sEntry = SvxResId( RID_STR_DATANAV_SUBM_REF ) + sTemp; + m_pItemList->InsertEntry( sEntry, aImage, aImage, pEntry ); + // Bind + _rEntry->getPropertyValue( PN_SUBMISSION_BIND ) >>= sTemp; + sEntry = SvxResId( RID_STR_DATANAV_SUBM_BIND ) + sTemp; + m_pItemList->InsertEntry( sEntry, aImage, aImage, pEntry ); + // Replace + _rEntry->getPropertyValue( PN_SUBMISSION_REPLACE ) >>= sTemp; + sEntry = SvxResId( RID_STR_DATANAV_SUBM_REPLACE ) + + m_aReplaceString.toUI( sTemp ); + m_pItemList->InsertEntry( sEntry, aImage, aImage, pEntry ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::AddEntry(Ref)" ); + } + } + else // then Binding Page + { + try + { + OUString sName; + _rEntry->getPropertyValue( PN_BINDING_ID ) >>= sTemp; + sName += sTemp + ": "; + _rEntry->getPropertyValue( PN_BINDING_EXPR ) >>= sTemp; + sName += sTemp; + pEntry = m_pItemList->InsertEntry( + sName, aImage, aImage, nullptr, false, TREELIST_APPEND, pNode ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::AddEntry(Ref)" ); + } + } + + return pEntry; + } + + + void XFormsPage::EditEntry( const Reference< XPropertySet >& _rEntry ) + { + OUString sTemp; + + if ( DGTSubmission == m_eGroup ) + { + try + { + SvTreeListEntry* pEntry = m_pItemList->FirstSelected(); + + // #i36262# may be called for submission entry *or* for + // submission children. If we don't have any children, we + // assume the latter case and use the parent + if( m_pItemList->GetEntry( pEntry, 0 ) == nullptr ) + { + pEntry = m_pItemList->GetModel()->GetParent( pEntry ); + } + + _rEntry->getPropertyValue( PN_SUBMISSION_ID ) >>= sTemp; + m_pItemList->SetEntryText( pEntry, sTemp ); + + _rEntry->getPropertyValue( PN_SUBMISSION_BIND ) >>= sTemp; + OUString sEntry = SvxResId( RID_STR_DATANAV_SUBM_BIND ) + sTemp; + sal_uLong nPos = 0; + SvTreeListEntry* pChild = m_pItemList->GetEntry( pEntry, nPos++ ); + m_pItemList->SetEntryText( pChild, sEntry ); + _rEntry->getPropertyValue( PN_SUBMISSION_REF ) >>= sTemp; + sEntry = SvxResId( RID_STR_DATANAV_SUBM_REF ) + sTemp; + pChild = m_pItemList->GetEntry( pEntry, nPos++ ); + m_pItemList->SetEntryText( pChild, sEntry ); + _rEntry->getPropertyValue( PN_SUBMISSION_ACTION ) >>= sTemp; + sEntry = SvxResId( RID_STR_DATANAV_SUBM_ACTION ) + sTemp; + pChild = m_pItemList->GetEntry( pEntry, nPos++ ); + m_pItemList->SetEntryText( pChild, sEntry ); + _rEntry->getPropertyValue( PN_SUBMISSION_METHOD ) >>= sTemp; + sEntry = SvxResId( RID_STR_DATANAV_SUBM_METHOD ) + + m_aMethodString.toUI( sTemp ); + pChild = m_pItemList->GetEntry( pEntry, nPos++ ); + m_pItemList->SetEntryText( pChild, sEntry ); + _rEntry->getPropertyValue( PN_SUBMISSION_REPLACE ) >>= sTemp; + sEntry = SvxResId( RID_STR_DATANAV_SUBM_REPLACE ) + + m_aReplaceString.toUI( sTemp ); + pChild = m_pItemList->GetEntry( pEntry, nPos++ ); + m_pItemList->SetEntryText( pChild, sEntry ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::EditEntry()" ); + } + } + } + + + bool XFormsPage::RemoveEntry() + { + bool bRet = false; + SvTreeListEntry* pEntry = m_pItemList->FirstSelected(); + if ( pEntry && + ( DGTInstance != m_eGroup || m_pItemList->GetParent( pEntry ) ) ) + { + Reference< css::xforms::XModel > xModel( m_xUIHelper, UNO_QUERY ); + DBG_ASSERT( xModel.is(), "XFormsPage::RemoveEntry(): no model" ); + ItemNode* pNode = static_cast< ItemNode* >( pEntry->GetUserData() ); + DBG_ASSERT( pNode, "XFormsPage::RemoveEntry(): no node" ); + + if ( DGTInstance == m_eGroup ) + { + try + { + DBG_ASSERT( pNode->m_xNode.is(), "XFormsPage::RemoveEntry(): no XNode" ); + css::xml::dom::NodeType eChildType = pNode->m_xNode->getNodeType(); + bool bIsElement = ( eChildType == css::xml::dom::NodeType_ELEMENT_NODE ); + const char* pResId = bIsElement ? RID_STR_QRY_REMOVE_ELEMENT : RID_STR_QRY_REMOVE_ATTRIBUTE; + OUString sVar = bIsElement ? OUString(ELEMENTNAME) : OUString(ATTRIBUTENAME); + std::unique_ptr<weld::MessageDialog> xQBox(Application::CreateMessageDialog(GetFrameWeld(), + VclMessageType::Question, VclButtonsType::YesNo, + SvxResId(pResId))); + OUString sMessText = xQBox->get_primary_text(); + sMessText = sMessText.replaceFirst( + sVar, m_xUIHelper->getNodeDisplayName( pNode->m_xNode, false ) ); + xQBox->set_primary_text(sMessText); + if (xQBox->run() == RET_YES) + { + SvTreeListEntry* pParent = m_pItemList->GetParent( pEntry ); + DBG_ASSERT( pParent, "XFormsPage::RemoveEntry(): no parent entry" ); + ItemNode* pParentNode = static_cast< ItemNode* >( pParent->GetUserData() ); + DBG_ASSERT( pParentNode && pParentNode->m_xNode.is(), "XFormsPage::RemoveEntry(): no parent XNode" ); + + Reference< css::xml::dom::XNode > xPNode; + Reference< css::xml::dom::XNode > xNode = + pParentNode->m_xNode->removeChild( pNode->m_xNode ); + if ( xNode.is() ) + xPNode = xNode->getParentNode(); + DBG_ASSERT( !xPNode.is(), "XFormsPage::RemoveEntry(): node not removed" ); + bRet = true; + } + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::RemoveEntry()" ); + } + } + else + { + DBG_ASSERT( pNode->m_xPropSet.is(), "XFormsPage::RemoveEntry(): no propset" ); + bool bSubmission = ( DGTSubmission == m_eGroup ); + const char* pResId = bSubmission ? RID_STR_QRY_REMOVE_SUBMISSION : RID_STR_QRY_REMOVE_BINDING; + OUString sProperty = bSubmission ? OUString(PN_SUBMISSION_ID) : OUString(PN_BINDING_ID); + OUString sSearch = bSubmission ? OUString(SUBMISSIONNAME) : OUString(BINDINGNAME); + OUString sName; + try + { + pNode->m_xPropSet->getPropertyValue( sProperty ) >>= sName; + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::RemoveEntry()" ); + } + std::unique_ptr<weld::MessageDialog> xQBox(Application::CreateMessageDialog(GetFrameWeld(), + VclMessageType::Question, VclButtonsType::YesNo, + SvxResId(pResId))); + OUString sMessText = xQBox->get_primary_text(); + sMessText = sMessText.replaceFirst( sSearch, sName); + xQBox->set_primary_text(sMessText); + if (xQBox->run() == RET_YES) + { + try + { + if ( bSubmission ) + xModel->getSubmissions()->remove( makeAny( pNode->m_xPropSet ) ); + else // then Binding Page + xModel->getBindings()->remove( makeAny( pNode->m_xPropSet ) ); + bRet = true; + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::RemoveEntry()" ); + } + } + } + + if ( bRet ) + m_pItemList->RemoveEntry( pEntry ); + } + + return bRet; + } + + + bool XFormsPage::EventNotify( NotifyEvent& rNEvt ) + { + bool bHandled = false; + + if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ) + { + sal_uInt16 nCode = rNEvt.GetKeyEvent()->GetKeyCode().GetCode(); + + switch ( nCode ) + { + case KEY_DELETE: + bHandled = DoMenuAction( m_nRemoveId ); + break; + } + } + + return bHandled || Window::EventNotify( rNEvt ); + } + + void XFormsPage::Resize() + { + Size aSize = GetOutputSizePixel(); + Size aTbxSize = m_pToolBox->GetSizePixel(); + aTbxSize.setWidth( aSize.Width() ); + m_pToolBox->SetSizePixel( aTbxSize ); + aSize.AdjustWidth( -4 ); + aSize.AdjustHeight( -( 4 + aTbxSize.Height() ) ); + m_pItemList->SetPosSizePixel( Point( 2, 2 + aTbxSize.Height() ), aSize ); + } + + OUString XFormsPage::SetModel( const Reference< css::xforms::XModel >& _xModel, sal_uInt16 _nPagePos ) + { + DBG_ASSERT( _xModel.is(), "XFormsPage::SetModel(): invalid model" ); + + m_xUIHelper.set( _xModel, UNO_QUERY ); + OUString sRet; + m_bHasModel = true; + + switch ( m_eGroup ) + { + case DGTInstance : + { + DBG_ASSERT( _nPagePos != TAB_PAGE_NOTFOUND, "XFormsPage::SetModel(): invalid page position" ); + try + { + Reference< XContainer > xContainer( _xModel->getInstances(), UNO_QUERY ); + if ( xContainer.is() ) + m_pNaviWin->AddContainerBroadcaster( xContainer ); + + Reference< XEnumerationAccess > xNumAccess = _xModel->getInstances(); + if ( xNumAccess.is() ) + { + Reference < XEnumeration > xNum = xNumAccess->createEnumeration(); + if ( xNum.is() && xNum->hasMoreElements() ) + { + sal_uInt16 nIter = 0; + while ( xNum->hasMoreElements() ) + { + if ( nIter == _nPagePos ) + { + Sequence< PropertyValue > xPropSeq; + Any aAny = xNum->nextElement(); + if ( aAny >>= xPropSeq ) + sRet = LoadInstance(xPropSeq); + else + { + SAL_WARN( "svx.form", "XFormsPage::SetModel(): invalid instance" ); + } + break; + } + else + { + xNum->nextElement(); + ++nIter; + } + } + } + } + } + catch( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::SetModel()" ); + } + break; + } + + case DGTSubmission : + { + DBG_ASSERT( TAB_PAGE_NOTFOUND == _nPagePos, "XFormsPage::SetModel(): invalid page position" ); + try + { + Reference< XContainer > xContainer( _xModel->getSubmissions(), UNO_QUERY ); + if ( xContainer.is() ) + m_pNaviWin->AddContainerBroadcaster( xContainer ); + + Reference< XEnumerationAccess > xNumAccess = _xModel->getSubmissions(); + if ( xNumAccess.is() ) + { + Reference < XEnumeration > xNum = xNumAccess->createEnumeration(); + if ( xNum.is() && xNum->hasMoreElements() ) + { + while ( xNum->hasMoreElements() ) + { + Reference< XPropertySet > xPropSet; + Any aAny = xNum->nextElement(); + if ( aAny >>= xPropSet ) + AddEntry( xPropSet ); + } + } + } + } + catch( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::SetModel()" ); + } + break; + } + + case DGTBinding : + { + DBG_ASSERT( TAB_PAGE_NOTFOUND == _nPagePos, "XFormsPage::SetModel(): invalid page position" ); + try + { + Reference< XContainer > xContainer( _xModel->getBindings(), UNO_QUERY ); + if ( xContainer.is() ) + m_pNaviWin->AddContainerBroadcaster( xContainer ); + + Reference< XEnumerationAccess > xNumAccess = _xModel->getBindings(); + if ( xNumAccess.is() ) + { + Reference < XEnumeration > xNum = xNumAccess->createEnumeration(); + if ( xNum.is() && xNum->hasMoreElements() ) + { + Image aImage(StockImage::Yes, RID_SVXBMP_ELEMENT); + while ( xNum->hasMoreElements() ) + { + Reference< XPropertySet > xPropSet; + Any aAny = xNum->nextElement(); + if ( aAny >>= xPropSet ) + { + OUString sEntry; + OUString sTemp; + xPropSet->getPropertyValue( PN_BINDING_ID ) >>= sTemp; + sEntry += sTemp + ": "; + xPropSet->getPropertyValue( PN_BINDING_EXPR ) >>= sTemp; + sEntry += sTemp; + + ItemNode* pNode = new ItemNode( xPropSet ); + m_pItemList->InsertEntry( + sEntry, aImage, aImage, nullptr, false, TREELIST_APPEND, pNode ); + } + } + } + } + } + catch( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::SetModel()" ); + } + break; + } + default: + OSL_FAIL( "XFormsPage::SetModel: unknown group!" ); + break; + } + + EnableMenuItems( nullptr ); + + return sRet; + } + + void XFormsPage::ClearModel() + { + m_bHasModel = false; + m_pItemList->DeleteAndClear(); + } + + OUString XFormsPage::LoadInstance(const Sequence< PropertyValue >& _xPropSeq) + { + OUString sRet; + OUString sTemp; + OUString sInstModel = PN_INSTANCE_MODEL; + OUString sInstName = PN_INSTANCE_ID; + OUString sInstURL = PN_INSTANCE_URL; + for ( const PropertyValue& rProp : _xPropSeq ) + { + if ( sInstModel == rProp.Name ) + { + Reference< css::xml::dom::XNode > xRoot; + if ( rProp.Value >>= xRoot ) + { + try + { + Reference< XEventTarget > xTarget( xRoot, UNO_QUERY ); + if ( xTarget.is() ) + m_pNaviWin->AddEventBroadcaster( xTarget ); + + OUString sNodeName = + m_xUIHelper->getNodeDisplayName( xRoot, m_pNaviWin->IsShowDetails() ); + if ( sNodeName.isEmpty() ) + sNodeName = xRoot->getNodeName(); + if ( xRoot->hasChildNodes() ) + AddChildren(nullptr, xRoot); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::LoadInstance()" ); + } + } + } + else if ( sInstName == rProp.Name && ( rProp.Value >>= sTemp ) ) + m_sInstanceName = sRet = sTemp; + else if ( sInstURL == rProp.Name && ( rProp.Value >>= sTemp ) ) + m_sInstanceURL = sTemp; + } + + return sRet; + } + + + bool XFormsPage::DoMenuAction( sal_uInt16 _nMenuID ) + { + return DoToolBoxAction( _nMenuID ); + } + + + void XFormsPage::EnableMenuItems( Menu* _pMenu ) + { + bool bEnableAdd = false; + bool bEnableEdit = false; + bool bEnableRemove = false; + + SvTreeListEntry* pEntry = m_pItemList->FirstSelected(); + if ( pEntry ) + { + bEnableAdd = true; + bool bSubmitChild = false; + if ( DGTSubmission == m_eGroup && m_pItemList->GetParent( pEntry ) ) + { + pEntry = m_pItemList->GetParent( pEntry ); + bSubmitChild = true; + } + ItemNode* pNode = static_cast< ItemNode* >( pEntry->GetUserData() ); + if ( pNode && ( pNode->m_xNode.is() || pNode->m_xPropSet.is() ) ) + { + bEnableEdit = true; + bEnableRemove = !bSubmitChild; + if ( DGTInstance == m_eGroup && !m_pItemList->GetParent( pEntry ) ) + bEnableRemove = false; + if ( pNode->m_xNode.is() ) + { + try + { + css::xml::dom::NodeType eChildType = pNode->m_xNode->getNodeType(); + if ( eChildType != css::xml::dom::NodeType_ELEMENT_NODE + && eChildType != css::xml::dom::NodeType_DOCUMENT_NODE ) + { + bEnableAdd = false; + } + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::EnableMenuItems()" ); + } + } + } + } + else if ( m_eGroup != DGTInstance ) + bEnableAdd = true; + + m_pToolBox->EnableItem( m_nAddId, bEnableAdd ); + m_pToolBox->EnableItem( m_nAddElementId, bEnableAdd ); + m_pToolBox->EnableItem( m_nAddAttributeId, bEnableAdd ); + m_pToolBox->EnableItem( m_nEditId, bEnableEdit ); + m_pToolBox->EnableItem( m_nRemoveId, bEnableRemove ); + + if ( _pMenu ) + { + _pMenu->EnableItem(_pMenu->GetItemId("additem"), bEnableAdd); + _pMenu->EnableItem(_pMenu->GetItemId("addelement"), bEnableAdd); + _pMenu->EnableItem(_pMenu->GetItemId("addattribute"), bEnableAdd); + _pMenu->EnableItem(_pMenu->GetItemId("edit"), bEnableEdit); + _pMenu->EnableItem(_pMenu->GetItemId("delete"), bEnableRemove); + } + if ( DGTInstance == m_eGroup ) + { + const char* pResId1 = RID_STR_DATANAV_EDIT_ELEMENT; + const char* pResId2 = RID_STR_DATANAV_REMOVE_ELEMENT; + if ( pEntry ) + { + ItemNode* pNode = static_cast< ItemNode* >( pEntry->GetUserData() ); + if ( pNode && pNode->m_xNode.is() ) + { + try + { + css::xml::dom::NodeType eChildType = pNode->m_xNode->getNodeType(); + if ( eChildType == css::xml::dom::NodeType_ATTRIBUTE_NODE ) + { + pResId1 = RID_STR_DATANAV_EDIT_ATTRIBUTE; + pResId2 = RID_STR_DATANAV_REMOVE_ATTRIBUTE; + } + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "XFormsPage::EnableMenuItems()" ); + } + } + } + m_pToolBox->SetItemText( m_nEditId, SvxResId( pResId1 ) ); + m_pToolBox->SetItemText( m_nRemoveId, SvxResId( pResId2 ) ); + if ( _pMenu ) + { + _pMenu->SetItemText(_pMenu->GetItemId("edit"), SvxResId( pResId1 ) ); + _pMenu->SetItemText(_pMenu->GetItemId("delete"), SvxResId( pResId2 ) ); + } + } + } + + DataNavigatorWindow::DataNavigatorWindow(vcl::Window* pParent, SfxBindings const * pBindings) + : Window(pParent) + , m_pInstPage(nullptr) + , m_pSubmissionPage(nullptr) + , m_pBindingPage(nullptr) + , m_nLastSelectedPos(LISTBOX_ENTRY_NOTFOUND) + , m_bShowDetails(false) + , m_bIsNotifyDisabled(false) + , m_xDataListener(new DataListener(this)) + { + m_pUIBuilder.reset(new VclBuilder(this, getUIRootDir(), "svx/ui/datanavigator.ui", "DataNavigator")); + get(m_pModelsBox, "modelslist"); + get(m_pModelBtn, "modelsbutton"); + get(m_pTabCtrl, "tabcontrol"); + get(m_pInstanceBtn, "instances"); + + // handler + m_pModelsBox->SetSelectHdl( LINK( this, DataNavigatorWindow, ModelSelectListBoxHdl ) ); + Link<MenuButton *, void> aLink1 = LINK( this, DataNavigatorWindow, MenuSelectHdl ); + m_pModelBtn->SetSelectHdl( aLink1 ); + m_pInstanceBtn->SetSelectHdl( aLink1 ); + Link<MenuButton*,void> aLink2 = LINK( this, DataNavigatorWindow, MenuActivateHdl ); + m_pModelBtn->SetActivateHdl( aLink2 ); + m_pInstanceBtn->SetActivateHdl( aLink2 ); + m_pTabCtrl->SetActivatePageHdl( LINK( this, DataNavigatorWindow, ActivatePageHdl ) ); + m_aUpdateTimer.SetTimeout( 2000 ); + m_aUpdateTimer.SetInvokeHandler( LINK( this, DataNavigatorWindow, UpdateHdl ) ); + + // init tabcontrol + m_pTabCtrl->Show(); + OString sPageId("instance"); + SvtViewOptions aViewOpt( EViewType::TabDialog, CFGNAME_DATANAVIGATOR ); + if ( aViewOpt.Exists() ) + { + sPageId = aViewOpt.GetPageID(); + aViewOpt.GetUserItem(CFGNAME_SHOWDETAILS) >>= m_bShowDetails; + } + + Menu* pMenu = m_pInstanceBtn->GetPopupMenu(); + sal_uInt16 nInstancesDetailsId = pMenu->GetItemId("instancesdetails"); + pMenu->SetItemBits(nInstancesDetailsId, MenuItemBits::CHECKABLE ); + pMenu->CheckItem(nInstancesDetailsId, m_bShowDetails ); + + m_pTabCtrl->SetCurPageId(m_pTabCtrl->GetPageId(sPageId)); + ActivatePageHdl(m_pTabCtrl); + + // get our frame + DBG_ASSERT( pBindings != nullptr, + "DataNavigatorWindow::LoadModels(): no SfxBindings; can't get frame" ); + m_xFrame = pBindings->GetDispatcher()->GetFrame()->GetFrame().GetFrameInterface(); + DBG_ASSERT( m_xFrame.is(), "DataNavigatorWindow::LoadModels(): no frame" ); + // add frameaction listener + Reference< XFrameActionListener > xListener( + static_cast< XFrameActionListener* >( m_xDataListener.get() ), UNO_QUERY ); + m_xFrame->addFrameActionListener( xListener ); + + // load xforms models of the current document + LoadModels(); + } + + DataNavigatorWindow::~DataNavigatorWindow() + { + disposeOnce(); + } + + void DataNavigatorWindow::dispose() + { + SvtViewOptions aViewOpt( EViewType::TabDialog, CFGNAME_DATANAVIGATOR ); + aViewOpt.SetPageID(m_pTabCtrl->GetPageName(m_pTabCtrl->GetCurPageId())); + aViewOpt.SetUserItem(CFGNAME_SHOWDETAILS, Any(m_bShowDetails)); + + m_pInstPage.disposeAndClear(); + m_pSubmissionPage.disposeAndClear(); + m_pBindingPage.disposeAndClear(); + + sal_Int32 i, nCount = m_aPageList.size(); + for ( i = 0; i < nCount; ++i ) + m_aPageList[i].disposeAndClear(); + m_aPageList.clear(); + + Reference< XFrameActionListener > xListener( + static_cast< XFrameActionListener* >( m_xDataListener.get() ), UNO_QUERY ); + m_xFrame->removeFrameActionListener( xListener ); + RemoveBroadcaster(); + m_xDataListener.clear(); + disposeBuilder(); + m_pModelsBox.clear(); + m_pModelBtn.clear(); + m_pTabCtrl.clear(); + m_pInstanceBtn.clear(); + vcl::Window::dispose(); + } + + + IMPL_LINK( DataNavigatorWindow, ModelSelectListBoxHdl, ListBox&, rBox, void ) + { + ModelSelectHdl(&rBox); + } + void DataNavigatorWindow::ModelSelectHdl(ListBox const * pBox) + { + sal_Int32 nPos = m_pModelsBox->GetSelectedEntryPos(); + // pBox == NULL, if you want to force a new fill. + if ( nPos != m_nLastSelectedPos || !pBox ) + { + m_nLastSelectedPos = nPos; + ClearAllPageModels( pBox != nullptr ); + InitPages(); + SetPageModel(); + } + } + + IMPL_LINK( DataNavigatorWindow, MenuSelectHdl, MenuButton *, pBtn, void ) + { + bool bIsDocModified = false; + Reference< css::xforms::XFormsUIHelper1 > xUIHelper; + sal_Int32 nSelectedPos = m_pModelsBox->GetSelectedEntryPos(); + OUString sSelectedModel( m_pModelsBox->GetEntry( nSelectedPos ) ); + Reference< css::xforms::XModel > xModel; + try + { + Any aAny = m_xDataContainer->getByName( sSelectedModel ); + if ( aAny >>= xModel ) + xUIHelper.set( xModel, UNO_QUERY ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "DataNavigatorWindow::MenuSelectHdl()" ); + } + DBG_ASSERT( xUIHelper.is(), "DataNavigatorWindow::MenuSelectHdl(): no UIHelper" ); + + m_bIsNotifyDisabled = true; + + if (m_pModelBtn == pBtn) + { + OString sIdent(pBtn->GetCurItemIdent()); + if (sIdent == "modelsadd") + { + AddModelDialog aDlg(GetFrameWeld(), false); + bool bShowDialog = true; + while ( bShowDialog ) + { + bShowDialog = false; + if (aDlg.run() == RET_OK) + { + OUString sNewName = aDlg.GetName(); + bool bDocumentData = aDlg.GetModifyDoc(); + + if ( m_pModelsBox->GetEntryPos( sNewName ) != LISTBOX_ENTRY_NOTFOUND ) + { + // error: model name already exists + std::unique_ptr<weld::MessageDialog> xErrBox(Application::CreateMessageDialog(GetFrameWeld(), + VclMessageType::Warning, VclButtonsType::Ok, + SvxResId(RID_STR_DOUBLE_MODELNAME))); + xErrBox->set_primary_text(xErrBox->get_primary_text().replaceFirst(MSG_VARIABLE, sNewName)); + xErrBox->run(); + bShowDialog = true; + } + else + { + try + { + // add new model to frame model + Reference< css::xforms::XModel > xNewModel( + xUIHelper->newModel( m_xFrameModel, sNewName ), UNO_SET_THROW ); + + Reference< XPropertySet > xModelProps( xNewModel, UNO_QUERY_THROW ); + xModelProps->setPropertyValue("ExternalData", makeAny( !bDocumentData ) ); + + sal_Int32 nNewPos = m_pModelsBox->InsertEntry( sNewName ); + m_pModelsBox->SelectEntryPos( nNewPos ); + ModelSelectHdl(m_pModelsBox); + bIsDocModified = true; + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "DataNavigatorWindow::MenuSelectHdl()" ); + } + } + } + } + } + else if (sIdent == "modelsedit") + { + AddModelDialog aDlg(GetFrameWeld(), true); + aDlg.SetName( sSelectedModel ); + + bool bDocumentData( false ); + try + { + Reference< css::xforms::XFormsSupplier > xFormsSupp( m_xFrameModel, UNO_QUERY_THROW ); + Reference< XNameContainer > xXForms( xFormsSupp->getXForms(), UNO_SET_THROW ); + Reference< XPropertySet > xModelProps( xXForms->getByName( sSelectedModel ), UNO_QUERY_THROW ); + bool bExternalData = false; + OSL_VERIFY( xModelProps->getPropertyValue( "ExternalData" ) >>= bExternalData ); + bDocumentData = !bExternalData; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + aDlg.SetModifyDoc( bDocumentData ); + + if (aDlg.run() == RET_OK) + { + if ( aDlg.GetModifyDoc() != bDocumentData ) + { + bDocumentData = aDlg.GetModifyDoc(); + try + { + Reference< css::xforms::XFormsSupplier > xFormsSupp( m_xFrameModel, UNO_QUERY_THROW ); + Reference< XNameContainer > xXForms( xFormsSupp->getXForms(), UNO_SET_THROW ); + Reference< XPropertySet > xModelProps( xXForms->getByName( sSelectedModel ), UNO_QUERY_THROW ); + xModelProps->setPropertyValue( "ExternalData", makeAny( !bDocumentData ) ); + bIsDocModified = true; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + OUString sNewName = aDlg.GetName(); + if ( !sNewName.isEmpty() && ( sNewName != sSelectedModel ) ) + { + try + { + xUIHelper->renameModel( m_xFrameModel, sSelectedModel, sNewName ); + + m_pModelsBox->RemoveEntry( nSelectedPos ); + nSelectedPos = m_pModelsBox->InsertEntry( sNewName ); + m_pModelsBox->SelectEntryPos( nSelectedPos ); + bIsDocModified = true; + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "DataNavigatorWindow::MenuSelectHdl()" ); + } + } + } + } + else if (sIdent == "modelsremove") + { + std::unique_ptr<weld::MessageDialog> xQBox(Application::CreateMessageDialog(GetFrameWeld(), + VclMessageType::Question, VclButtonsType::YesNo, + SvxResId( RID_STR_QRY_REMOVE_MODEL))); + OUString sText = xQBox->get_primary_text(); + sText = sText.replaceFirst( MODELNAME, sSelectedModel ); + xQBox->set_primary_text(sText); + if (xQBox->run() == RET_YES) + { + try + { + xUIHelper->removeModel( m_xFrameModel, sSelectedModel ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "DataNavigatorWindow::MenuSelectHdl()" ); + } + m_pModelsBox->RemoveEntry( nSelectedPos ); + if ( m_pModelsBox->GetEntryCount() <= nSelectedPos ) + nSelectedPos = m_pModelsBox->GetEntryCount() - 1; + m_pModelsBox->SelectEntryPos( nSelectedPos ); + ModelSelectHdl(m_pModelsBox); + bIsDocModified = true; + } + } + else + { + SAL_WARN( "svx.form", "DataNavigatorWindow::MenuSelectHdl(): wrong menu item" ); + } + } + else if (m_pInstanceBtn == pBtn) + { + OString sIdent(pBtn->GetCurItemIdent()); + if (sIdent == "instancesadd") + { + AddInstanceDialog aDlg(GetFrameWeld(), false); + if (aDlg.run() == RET_OK) + { + sal_uInt16 nInst = GetNewPageId(); + OUString sName = aDlg.GetName(); + OUString sURL = aDlg.GetURL(); + bool bLinkOnce = aDlg.IsLinkInstance(); + try + { + xUIHelper->newInstance( sName, sURL, !bLinkOnce ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "DataNavigatorWindow::MenuSelectHdl()" ); + } + ModelSelectHdl( nullptr ); + m_pTabCtrl->SetCurPageId( nInst ); + XFormsPage* pPage = GetCurrentPage( nInst ); + pPage->SetInstanceName(sName); + pPage->SetInstanceURL(sURL); + pPage->SetLinkOnce(bLinkOnce); + ActivatePageHdl(m_pTabCtrl); + bIsDocModified = true; + } + } + else if (sIdent == "instancesedit") + { + sal_uInt16 nId = 0; + XFormsPage* pPage = GetCurrentPage( nId ); + if ( pPage ) + { + AddInstanceDialog aDlg(GetFrameWeld(), true); + aDlg.SetName( pPage->GetInstanceName() ); + aDlg.SetURL( pPage->GetInstanceURL() ); + aDlg.SetLinkInstance( pPage->GetLinkOnce() ); + OUString sOldName = aDlg.GetName(); + if (aDlg.run() == RET_OK) + { + OUString sNewName = aDlg.GetName(); + OUString sURL = aDlg.GetURL(); + bool bLinkOnce = aDlg.IsLinkInstance(); + try + { + xUIHelper->renameInstance( sOldName, + sNewName, + sURL, + !bLinkOnce ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "DataNavigatorWindow::MenuSelectHdl()" ); + } + pPage->SetInstanceName(sNewName); + pPage->SetInstanceURL(sURL); + pPage->SetLinkOnce(bLinkOnce); + m_pTabCtrl->SetPageText( nId, sNewName ); + bIsDocModified = true; + } + } + } + else if (sIdent == "instancesremove") + { + sal_uInt16 nId = 0; + VclPtr<XFormsPage> pPage = GetCurrentPage( nId ); + if ( pPage ) + { + OUString sInstName = pPage->GetInstanceName(); + std::unique_ptr<weld::MessageDialog> xQBox(Application::CreateMessageDialog(GetFrameWeld(), + VclMessageType::Question, VclButtonsType::YesNo, + SvxResId(RID_STR_QRY_REMOVE_INSTANCE))); + OUString sMessText = xQBox->get_primary_text(); + sMessText = sMessText.replaceFirst( INSTANCENAME, sInstName ); + xQBox->set_primary_text(sMessText); + if (xQBox->run() == RET_YES) + { + bool bDoRemove = false; + if (IsAdditionalPage(nId)) + { + auto aPageListEnd = m_aPageList.end(); + auto aFoundPage = std::find( m_aPageList.begin(), aPageListEnd, pPage ); + if ( aFoundPage != aPageListEnd ) + { + m_aPageList.erase( aFoundPage ); + pPage.disposeAndClear() ; + bDoRemove = true; + } + } + else + { + m_pInstPage.disposeAndClear(); + bDoRemove = true; + } + + if ( bDoRemove ) + { + try + { + xUIHelper->removeInstance( sInstName ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "svx.form", "DataNavigatorWindow::MenuSelectHdl()" ); + } + m_pTabCtrl->RemovePage( nId ); + m_pTabCtrl->SetCurPageId(m_pTabCtrl->GetPageId("instance")); + ModelSelectHdl( nullptr ); + bIsDocModified = true; + } + } + } + } + else if (sIdent == "instancesdetails") + { + m_bShowDetails = !m_bShowDetails; + PopupMenu* pMenu = m_pInstanceBtn->GetPopupMenu(); + pMenu->CheckItem("instancesdetails", m_bShowDetails ); + ModelSelectHdl(m_pModelsBox); + } + else + { + SAL_WARN( "svx.form", "DataNavigatorWindow::MenuSelectHdl(): wrong menu item" ); + } + } + else + { + SAL_WARN( "svx.form", "DataNavigatorWindow::MenuSelectHdl(): wrong button" ); + } + + m_bIsNotifyDisabled = false; + + if ( bIsDocModified ) + SetDocModified(); + } + + bool DataNavigatorWindow::IsAdditionalPage(sal_uInt16 nId) const + { + return m_pTabCtrl->GetPageName(nId).isEmpty(); + } + + IMPL_LINK( DataNavigatorWindow, MenuActivateHdl, MenuButton *, pBtn, void ) + { + Menu* pMenu = pBtn->GetPopupMenu(); + + if (m_pInstanceBtn == pBtn) + { + sal_uInt16 nId(m_pTabCtrl->GetCurPageId()); + bool bIsInstPage = (IsAdditionalPage(nId) || m_pTabCtrl->GetPageName(nId) == "instance"); + pMenu->EnableItem( "instancesedit", bIsInstPage ); + pMenu->EnableItem( "instancesremove", + bIsInstPage && m_pTabCtrl->GetPageCount() > MIN_PAGE_COUNT ); + pMenu->EnableItem( "instancesdetails", bIsInstPage ); + } + else if (m_pModelBtn == pBtn) + { + // we need at least one model! + pMenu->EnableItem("modelsremove", m_pModelsBox->GetEntryCount() > 1 ); + } + else + { + SAL_WARN( "svx.form", "DataNavigatorWindow::MenuActivateHdl(): wrong button" ); + } + } + + IMPL_LINK_NOARG(DataNavigatorWindow, ActivatePageHdl, TabControl*, void) + { + sal_uInt16 nId = 0; + XFormsPage* pPage = GetCurrentPage( nId ); + if ( pPage ) + { + m_pTabCtrl->SetTabPage( nId, pPage ); + if ( m_xDataContainer.is() && !pPage->HasModel() ) + SetPageModel(); + } + } + + IMPL_LINK_NOARG(DataNavigatorWindow, UpdateHdl, Timer *, void) + { + ModelSelectHdl( nullptr ); + } + + XFormsPage* DataNavigatorWindow::GetCurrentPage( sal_uInt16& rCurId ) + { + rCurId = m_pTabCtrl->GetCurPageId(); + VclPtr<XFormsPage> pPage; + OString sName(m_pTabCtrl->GetPageName(rCurId)); + if (sName == "submissions") + { + if ( !m_pSubmissionPage ) + m_pSubmissionPage = VclPtr<XFormsPage>::Create(m_pTabCtrl, this, DGTSubmission); + pPage = m_pSubmissionPage; + } + else if (sName == "bindings") + { + if ( !m_pBindingPage ) + m_pBindingPage = VclPtr<XFormsPage>::Create(m_pTabCtrl, this, DGTBinding); + pPage = m_pBindingPage; + } + else if (sName == "instance") + { + if ( !m_pInstPage ) + m_pInstPage = VclPtr<XFormsPage>::Create(m_pTabCtrl, this, DGTInstance); + pPage = m_pInstPage; + } + else + { + sal_uInt16 nPos = m_pTabCtrl->GetPagePos( rCurId ); + if ( HasFirstInstancePage() && nPos > 0 ) + nPos--; + if ( m_aPageList.size() > nPos ) + pPage = m_aPageList[nPos]; + else + { + pPage = VclPtr<XFormsPage>::Create(m_pTabCtrl, this, DGTInstance); + m_aPageList.push_back( pPage ); + } + } + + return pPage; + } + + void DataNavigatorWindow::LoadModels() + { + if ( !m_xFrameModel.is() ) + { + // get model of active frame + Reference< XController > xCtrl = m_xFrame->getController(); + if ( xCtrl.is() ) + { + try + { + m_xFrameModel = xCtrl->getModel(); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "DataNavigatorWindow::LoadModels()" ); + } + } + } + + if ( m_xFrameModel.is() ) + { + try + { + Reference< css::xforms::XFormsSupplier > xFormsSupp( m_xFrameModel, UNO_QUERY ); + if ( xFormsSupp.is() ) + { + Reference< XNameContainer > xContainer = xFormsSupp->getXForms(); + if ( xContainer.is() ) + { + m_xDataContainer = xContainer; + const Sequence< OUString > aNameList = m_xDataContainer->getElementNames(); + for ( const OUString& rName : aNameList ) + { + Any aAny = m_xDataContainer->getByName( rName ); + Reference< css::xforms::XModel > xFormsModel; + if ( aAny >>= xFormsModel ) + m_pModelsBox->InsertEntry( xFormsModel->getID() ); + } + } + } + } + catch( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "DataNavigatorWindow::LoadModels()" ); + } + } + + if ( m_pModelsBox->GetEntryCount() > 0 ) + { + m_pModelsBox->SelectEntryPos(0); + ModelSelectHdl(m_pModelsBox); + } + } + + void DataNavigatorWindow::SetPageModel() + { + OUString sModel( m_pModelsBox->GetSelectedEntry() ); + try + { + Any aAny = m_xDataContainer->getByName( sModel ); + Reference< css::xforms::XModel > xFormsModel; + if ( aAny >>= xFormsModel ) + { + sal_uInt16 nPagePos = TAB_PAGE_NOTFOUND; + sal_uInt16 nId = 0; + XFormsPage* pPage = GetCurrentPage( nId ); + DBG_ASSERT( pPage, "DataNavigatorWindow::SetPageModel(): no page" ); + if (IsAdditionalPage(nId) || m_pTabCtrl->GetPageName(nId) == "instance") + { + // instance page + nPagePos = m_pTabCtrl->GetPagePos( nId ); + } + m_bIsNotifyDisabled = true; + OUString sText = pPage->SetModel( xFormsModel, nPagePos ); + m_bIsNotifyDisabled = false; + if ( !sText.isEmpty() ) + m_pTabCtrl->SetPageText( nId, sText ); + } + } + catch (const NoSuchElementException& ) + { + SAL_WARN( "svx.form", "DataNavigatorWindow::SetPageModel(): no such element" ); + } + catch( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "DataNavigatorWindow::SetPageModel()" ); + } + } + + void DataNavigatorWindow::InitPages() + { + OUString sModel( m_pModelsBox->GetSelectedEntry() ); + try + { + Any aAny = m_xDataContainer->getByName( sModel ); + Reference< css::xforms::XModel > xModel; + if ( aAny >>= xModel ) + { + Reference< XEnumerationAccess > xNumAccess = xModel->getInstances(); + if ( xNumAccess.is() ) + { + Reference < XEnumeration > xNum = xNumAccess->createEnumeration(); + if ( xNum.is() && xNum->hasMoreElements() ) + { + sal_Int32 nAlreadyLoadedCount = m_aPageList.size(); + if ( !HasFirstInstancePage() && nAlreadyLoadedCount > 0 ) + nAlreadyLoadedCount--; + sal_Int32 nIdx = 0; + while ( xNum->hasMoreElements() ) + { + if ( nIdx > nAlreadyLoadedCount ) + { + Sequence< PropertyValue > xPropSeq; + if ( xNum->nextElement() >>= xPropSeq ) + CreateInstancePage( xPropSeq ); + else + { + SAL_WARN( "svx.form", "DataNavigator::InitPages(): invalid instance" ); + } + } + else + xNum->nextElement(); + nIdx++; + } + } + } + } + } + catch ( NoSuchElementException& ) + { + SAL_WARN( "svx.form", "DataNavigatorWindow::SetPageModel(): no such element" ); + } + catch( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "DataNavigatorWindow::SetPageModel()" ); + } + } + + void DataNavigatorWindow::ClearAllPageModels( bool bClearPages ) + { + if ( m_pInstPage ) + m_pInstPage->ClearModel(); + if ( m_pSubmissionPage ) + m_pSubmissionPage->ClearModel(); + if ( m_pBindingPage ) + m_pBindingPage->ClearModel(); + + sal_Int32 i, nCount = m_aPageList.size(); + for ( i = 0; i < nCount; ++i ) + { + VclPtr<XFormsPage> pPage = m_aPageList[i]; + pPage->ClearModel(); + if ( bClearPages ) + pPage.disposeAndClear(); + } + + if ( bClearPages ) + { + m_aPageList.clear(); + while ( m_pTabCtrl->GetPageCount() > MIN_PAGE_COUNT ) + m_pTabCtrl->RemovePage( m_pTabCtrl->GetPageId( 1 ) ); + } + } + + void DataNavigatorWindow::CreateInstancePage( const Sequence< PropertyValue >& _xPropSeq ) + { + OUString sInstName; + auto pProp = std::find_if(_xPropSeq.begin(), _xPropSeq.end(), + [](const PropertyValue& rProp) { return PN_INSTANCE_ID == rProp.Name; }); + if (pProp != _xPropSeq.end()) + pProp->Value >>= sInstName; + + sal_uInt16 nPageId = GetNewPageId(); + if ( sInstName.isEmpty() ) + { + SAL_WARN( "svx.form", "DataNavigatorWindow::CreateInstancePage(): instance without name" ); + sInstName = "untitled" + OUString::number( nPageId ); + } + m_pTabCtrl->InsertPage( nPageId, sInstName, m_pTabCtrl->GetPageCount() - 2 ); + } + + bool DataNavigatorWindow::HasFirstInstancePage() const + { + return (m_pTabCtrl->GetPageName(m_pTabCtrl->GetPageId(0)) == "instance"); + } + + sal_uInt16 DataNavigatorWindow::GetNewPageId() const + { + sal_uInt16 i, nMax = 0, nCount = m_pTabCtrl->GetPageCount(); + for ( i = 0; i < nCount; ++i ) + { + if ( nMax < m_pTabCtrl->GetPageId(i) ) + nMax = m_pTabCtrl->GetPageId(i); + } + return ( nMax + 1 ); + } + + void DataNavigatorWindow::Resize() + { + vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); + if (!pChild) + return; + VclContainer::setLayoutAllocation(*pChild, Point(0,0), GetSizePixel()); + } + + Size DataNavigatorWindow::GetOptimalSize() const + { + const vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); + if (!pChild) + return Window::GetOptimalSize(); + return VclContainer::getLayoutRequisition(*pChild); + } + + void DataNavigatorWindow::SetDocModified() + { + SfxObjectShell* pCurrentDoc = SfxObjectShell::Current(); + DBG_ASSERT( pCurrentDoc, "DataNavigatorWindow::SetDocModified(): no objectshell" ); + if ( !pCurrentDoc->IsModified() && pCurrentDoc->IsEnableSetModified() ) + pCurrentDoc->SetModified(); + } + + void DataNavigatorWindow::NotifyChanges( bool _bLoadAll ) + { + if ( !m_bIsNotifyDisabled ) + { + if ( _bLoadAll ) + { + // reset all members + RemoveBroadcaster(); + m_xDataContainer.clear(); + m_xFrameModel.clear(); + m_pModelsBox->Clear(); + m_nLastSelectedPos = LISTBOX_ENTRY_NOTFOUND; + // for a reload + LoadModels(); + } + else + m_aUpdateTimer.Start(); + } + } + + + void DataNavigatorWindow::AddContainerBroadcaster( const css::uno::Reference< css::container::XContainer >& xContainer ) + { + Reference< XContainerListener > xListener( + static_cast< XContainerListener* >( m_xDataListener.get() ), UNO_QUERY ); + xContainer->addContainerListener( xListener ); + m_aContainerList.push_back( xContainer ); + } + + + void DataNavigatorWindow::AddEventBroadcaster( const css::uno::Reference< css::xml::dom::events::XEventTarget >& xTarget ) + { + Reference< XEventListener > xListener( + static_cast< XEventListener* >( m_xDataListener.get() ), UNO_QUERY ); + xTarget->addEventListener( EVENTTYPE_CHARDATA, xListener, true ); + xTarget->addEventListener( EVENTTYPE_CHARDATA, xListener, false ); + xTarget->addEventListener( EVENTTYPE_ATTR, xListener, true ); + xTarget->addEventListener( EVENTTYPE_ATTR, xListener, false ); + m_aEventTargetList.push_back( xTarget ); + } + + + void DataNavigatorWindow::RemoveBroadcaster() + { + Reference< XContainerListener > xContainerListener( + static_cast< XContainerListener* >( m_xDataListener.get() ), UNO_QUERY ); + sal_Int32 i, nCount = m_aContainerList.size(); + for ( i = 0; i < nCount; ++i ) + m_aContainerList[i]->removeContainerListener( xContainerListener ); + Reference< XEventListener > xEventListener( + static_cast< XEventListener* >( m_xDataListener.get() ), UNO_QUERY ); + nCount = m_aEventTargetList.size(); + for ( i = 0; i < nCount; ++i ) + { + m_aEventTargetList[i]->removeEventListener( EVENTTYPE_CHARDATA, xEventListener, true ); + m_aEventTargetList[i]->removeEventListener( EVENTTYPE_CHARDATA, xEventListener, false ); + m_aEventTargetList[i]->removeEventListener( EVENTTYPE_ATTR, xEventListener, true ); + m_aEventTargetList[i]->removeEventListener( EVENTTYPE_ATTR, xEventListener, false ); + } + } + + DataNavigator::DataNavigator( SfxBindings* _pBindings, SfxChildWindow* _pMgr, vcl::Window* _pParent ) : + + SfxDockingWindow( _pBindings, _pMgr, _pParent, + WinBits(WB_STDMODELESS|WB_SIZEABLE|WB_ROLLABLE|WB_3DLOOK|WB_DOCKABLE) ), + SfxControllerItem( SID_FM_DATANAVIGATOR_CONTROL, *_pBindings ), + + m_aDataWin( VclPtr<DataNavigatorWindow>::Create(this, _pBindings) ) + + { + + SetText( SvxResId( RID_STR_DATANAVIGATOR ) ); + + Size aSize = m_aDataWin->GetOutputSizePixel(); + Size aLogSize = PixelToLogic(aSize, MapMode(MapUnit::MapAppFont)); + SfxDockingWindow::SetFloatingSize( aLogSize ); + + m_aDataWin->Show(); + } + + + DataNavigator::~DataNavigator() + { + disposeOnce(); + } + + void DataNavigator::dispose() + { + m_aDataWin.disposeAndClear(); + ::SfxControllerItem::dispose(); + SfxDockingWindow::dispose(); + } + + void DataNavigator::StateChanged( sal_uInt16 , SfxItemState , const SfxPoolItem* ) + { + } + + + Size DataNavigator::CalcDockingSize( SfxChildAlignment eAlign ) + { + if ( ( eAlign == SfxChildAlignment::TOP ) || ( eAlign == SfxChildAlignment::BOTTOM ) ) + return Size(); + + return SfxDockingWindow::CalcDockingSize( eAlign ); + } + + + SfxChildAlignment DataNavigator::CheckAlignment( SfxChildAlignment eActAlign, SfxChildAlignment eAlign ) + { + switch ( eAlign ) + { + case SfxChildAlignment::LEFT: + case SfxChildAlignment::RIGHT: + case SfxChildAlignment::NOALIGNMENT: + return eAlign; + default: + break; + } + return eActAlign; + } + + + void DataNavigator::Resize() + { + SfxDockingWindow::Resize(); + + Size aLogOutputSize = PixelToLogic(GetOutputSizePixel(), MapMode(MapUnit::MapAppFont)); + Size aLogExplSize = aLogOutputSize; + aLogExplSize.AdjustWidth( -2 ); + aLogExplSize.AdjustHeight( -2 ); + + Point aExplPos = LogicToPixel(Point(1,1), MapMode(MapUnit::MapAppFont)); + Size aExplSize = LogicToPixel(aLogExplSize, MapMode(MapUnit::MapAppFont)); + + m_aDataWin->SetPosSizePixel( aExplPos, aExplSize ); + } + + + + + SFX_IMPL_DOCKINGWINDOW( DataNavigatorManager, SID_FM_SHOW_DATANAVIGATOR ) + + + DataNavigatorManager::DataNavigatorManager( + vcl::Window* _pParent, sal_uInt16 _nId, SfxBindings* _pBindings, SfxChildWinInfo* _pInfo ) : + + SfxChildWindow( _pParent, _nId ) + + { + SetWindow( VclPtr<DataNavigator>::Create( _pBindings, this, _pParent ) ); + SetAlignment(SfxChildAlignment::RIGHT); + GetWindow()->SetSizePixel( Size( 250, 400 ) ); + static_cast<SfxDockingWindow*>(GetWindow())->Initialize( _pInfo ); + } + + AddDataItemDialog::AddDataItemDialog(weld::Window* pParent, ItemNode* _pNode, + const Reference< css::xforms::XFormsUIHelper1 >& _rUIHelper) + : GenericDialogController(pParent, "svx/ui/adddataitemdialog.ui", "AddDataItemDialog") + , m_xUIHelper(_rUIHelper) + , m_pItemNode(_pNode) + , m_eItemType(DITNone) + , m_sFL_Element(SvxResId(RID_STR_ELEMENT)) + , m_sFL_Attribute(SvxResId(RID_STR_ATTRIBUTE)) + , m_sFL_Binding(SvxResId(RID_STR_BINDING)) + , m_sFT_BindingExp(SvxResId(RID_STR_BINDING_EXPR)) + , m_xItemFrame(m_xBuilder->weld_frame("itemframe")) + , m_xNameFT(m_xBuilder->weld_label("nameft")) + , m_xNameED(m_xBuilder->weld_entry("name")) + , m_xDefaultFT(m_xBuilder->weld_label("valueft")) + , m_xDefaultED(m_xBuilder->weld_entry("value")) + , m_xDefaultBtn(m_xBuilder->weld_button("browse")) + , m_xSettingsFrame(m_xBuilder->weld_widget("settingsframe")) + , m_xDataTypeFT(m_xBuilder->weld_label("datatypeft")) + , m_xDataTypeLB(m_xBuilder->weld_combo_box("datatype")) + , m_xRequiredCB(m_xBuilder->weld_check_button("required")) + , m_xRequiredBtn(m_xBuilder->weld_button("requiredcond")) + , m_xRelevantCB(m_xBuilder->weld_check_button("relevant")) + , m_xRelevantBtn(m_xBuilder->weld_button("relevantcond")) + , m_xConstraintCB(m_xBuilder->weld_check_button("constraint")) + , m_xConstraintBtn(m_xBuilder->weld_button("constraintcond")) + , m_xReadonlyCB(m_xBuilder->weld_check_button("readonly")) + , m_xReadonlyBtn(m_xBuilder->weld_button("readonlycond")) + , m_xCalculateCB(m_xBuilder->weld_check_button("calculate")) + , m_xCalculateBtn(m_xBuilder->weld_button("calculatecond")) + , m_xOKBtn(m_xBuilder->weld_button("ok")) + { + InitDialog(); + InitFromNode(); + InitDataTypeBox(); + Check(nullptr); + } + + AddDataItemDialog::~AddDataItemDialog() + { + if ( m_xTempBinding.is() ) + { + Reference< css::xforms::XModel > xModel( m_xUIHelper, UNO_QUERY ); + if ( xModel.is() ) + { + try + { + Reference < XSet > xBindings = xModel->getBindings(); + if ( xBindings.is() ) + xBindings->remove( makeAny( m_xTempBinding ) ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "svx.form", "AddDataItemDialog::Dtor()" ); + } + } + } + if( m_xUIHelper.is() && m_xBinding.is() ) + { + // remove binding, if it does not convey 'useful' information + m_xUIHelper->removeBindingIfUseless( m_xBinding ); + } + } + + IMPL_LINK(AddDataItemDialog, CheckHdl, weld::ToggleButton&, rBox, void) + { + Check(&rBox); + } + + void AddDataItemDialog::Check(const weld::ToggleButton* pBox) + { + // Condition buttons are only enable if their check box is checked + m_xReadonlyBtn->set_sensitive( m_xReadonlyCB->get_active() ); + m_xRequiredBtn->set_sensitive( m_xRequiredCB->get_active() ); + m_xRelevantBtn->set_sensitive( m_xRelevantCB->get_active() ); + m_xConstraintBtn->set_sensitive( m_xConstraintCB->get_active() ); + m_xCalculateBtn->set_sensitive( m_xCalculateCB->get_active() ); + + if ( pBox && m_xTempBinding.is() ) + { + OUString sTemp, sPropName; + if ( m_xRequiredCB.get() == pBox ) + sPropName = PN_REQUIRED_EXPR; + else if ( m_xRelevantCB.get() == pBox ) + sPropName = PN_RELEVANT_EXPR; + else if ( m_xConstraintCB.get() == pBox ) + sPropName = PN_CONSTRAINT_EXPR; + else if ( m_xReadonlyCB.get() == pBox ) + sPropName = PN_READONLY_EXPR; + else if ( m_xCalculateCB.get() == pBox ) + sPropName = PN_CALCULATE_EXPR; + bool bIsChecked = pBox->get_active(); + m_xTempBinding->getPropertyValue( sPropName ) >>= sTemp; + if ( bIsChecked && sTemp.isEmpty() ) + sTemp = TRUE_VALUE; + else if ( !bIsChecked && !sTemp.isEmpty() ) + sTemp.clear(); + m_xTempBinding->setPropertyValue( sPropName, makeAny( sTemp ) ); + } + } + + IMPL_LINK(AddDataItemDialog, ConditionHdl, weld::Button&, rBtn, void) + { + OUString sTemp, sPropName; + if ( m_xDefaultBtn.get() == &rBtn ) + sPropName = PN_BINDING_EXPR; + else if ( m_xRequiredBtn.get() == &rBtn ) + sPropName = PN_REQUIRED_EXPR; + else if ( m_xRelevantBtn.get() == &rBtn ) + sPropName = PN_RELEVANT_EXPR; + else if ( m_xConstraintBtn.get() == &rBtn ) + sPropName = PN_CONSTRAINT_EXPR; + else if (m_xReadonlyBtn.get() == &rBtn) + sPropName = PN_READONLY_EXPR; + else if (m_xCalculateBtn.get() == &rBtn) + sPropName = PN_CALCULATE_EXPR; + AddConditionDialog aDlg(m_xDialog.get(), sPropName, m_xTempBinding); + bool bIsDefBtn = ( m_xDefaultBtn.get() == &rBtn ); + OUString sCondition; + if ( bIsDefBtn ) + sCondition = m_xDefaultED->get_text(); + else + { + m_xTempBinding->getPropertyValue( sPropName ) >>= sTemp; + if ( sTemp.isEmpty() ) + sTemp = TRUE_VALUE; + sCondition = sTemp; + } + aDlg.SetCondition( sCondition ); + + if (aDlg.run() == RET_OK) + { + OUString sNewCondition = aDlg.GetCondition(); + if ( bIsDefBtn ) + m_xDefaultED->set_text(sNewCondition); + else + { + + m_xTempBinding->setPropertyValue( + sPropName, makeAny( sNewCondition ) ); + } + } + } + + static void copyPropSet( const Reference< XPropertySet >& xFrom, Reference< XPropertySet > const & xTo ) + { + DBG_ASSERT( xFrom.is(), "copyPropSet(): no source" ); + DBG_ASSERT( xTo.is(), "copyPropSet(): no target" ); + + try + { + // get property names & infos, and iterate over target properties + const Sequence< Property > aProperties = xTo->getPropertySetInfo()->getProperties(); + Reference< XPropertySetInfo > xFromInfo = xFrom->getPropertySetInfo(); + for ( const Property& rProperty : aProperties ) + { + const OUString& rName = rProperty.Name; + + // if both set have the property, copy the value + // (catch and ignore exceptions, if any) + if ( xFromInfo->hasPropertyByName( rName ) ) + { + // don't set readonly properties + Property aProperty = xFromInfo->getPropertyByName( rName ); + if ( ( aProperty.Attributes & PropertyAttribute::READONLY ) == 0 ) + xTo->setPropertyValue(rName, xFrom->getPropertyValue( rName )); + } + // else: no property? then ignore. + } + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "copyPropSet()" ); + } + } + + IMPL_LINK_NOARG(AddDataItemDialog, OKHdl, weld::Button&, void) + { + bool bIsHandleBinding = ( DITBinding == m_eItemType ); + bool bIsHandleText = ( DITText == m_eItemType ); + OUString sNewName( m_xNameED->get_text() ); + + if ( ( !bIsHandleBinding && !bIsHandleText && !m_xUIHelper->isValidXMLName( sNewName ) ) || + ( bIsHandleBinding && sNewName.isEmpty() ) ) + { + // Error and don't close the dialog + std::unique_ptr<weld::MessageDialog> xErrBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, + SvxResId(RID_STR_INVALID_XMLNAME))); + xErrBox->set_primary_text(xErrBox->get_primary_text().replaceFirst(MSG_VARIABLE, sNewName)); + xErrBox->run(); + return; + } + + OUString sDataType( m_xDataTypeLB->get_active_text() ); + m_xTempBinding->setPropertyValue( PN_BINDING_TYPE, makeAny( sDataType ) ); + + if ( bIsHandleBinding ) + { + // copy properties from temp binding to original binding + copyPropSet( m_xTempBinding, m_pItemNode->m_xPropSet ); + try + { + OUString sValue = m_xNameED->get_text(); + m_pItemNode->m_xPropSet->setPropertyValue( PN_BINDING_ID, makeAny( sValue ) ); + sValue = m_xDefaultED->get_text(); + m_pItemNode->m_xPropSet->setPropertyValue( PN_BINDING_EXPR, makeAny( sValue ) ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "AddDataDialog::OKHdl()" ); + } + } + else + { + // copy properties from temp binding to original binding + copyPropSet( m_xTempBinding, m_xBinding ); + try + { + if ( bIsHandleText ) + m_xUIHelper->setNodeValue( m_pItemNode->m_xNode, m_xDefaultED->get_text() ); + else + { + Reference< css::xml::dom::XNode > xNewNode = + m_xUIHelper->renameNode( m_pItemNode->m_xNode, m_xNameED->get_text() ); + m_xUIHelper->setNodeValue( xNewNode, m_xDefaultED->get_text() ); + m_pItemNode->m_xNode = xNewNode; + } + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "AddDataDialog::OKHdl()" ); + } + } + // then close the dialog + m_xDialog->response(RET_OK); + } + + void AddDataItemDialog::InitDialog() + { + // set handler + Link<weld::ToggleButton&,void> aLink = LINK( this, AddDataItemDialog, CheckHdl ); + m_xRequiredCB->connect_toggled( aLink ); + m_xRelevantCB->connect_toggled( aLink ); + m_xConstraintCB->connect_toggled( aLink ); + m_xReadonlyCB->connect_toggled( aLink ); + m_xCalculateCB->connect_toggled( aLink ); + + Link<weld::Button&,void> aLink2 = LINK( this, AddDataItemDialog, ConditionHdl ); + m_xDefaultBtn->connect_clicked( aLink2 ); + m_xRequiredBtn->connect_clicked( aLink2 ); + m_xRelevantBtn->connect_clicked( aLink2 ); + m_xConstraintBtn->connect_clicked( aLink2 ); + m_xReadonlyBtn->connect_clicked( aLink2 ); + m_xCalculateBtn->connect_clicked( aLink2 ); + + m_xOKBtn->connect_clicked( LINK( this, AddDataItemDialog, OKHdl ) ); + } + + void AddDataItemDialog::InitFromNode() + { + if ( m_pItemNode ) + { + if ( m_pItemNode->m_xNode.is() ) + { + try + { + // detect type of the node + css::xml::dom::NodeType eChildType = m_pItemNode->m_xNode->getNodeType(); + switch ( eChildType ) + { + case css::xml::dom::NodeType_ATTRIBUTE_NODE: + m_eItemType = DITAttribute; + break; + case css::xml::dom::NodeType_ELEMENT_NODE: + m_eItemType = DITElement; + break; + case css::xml::dom::NodeType_TEXT_NODE: + m_eItemType = DITText; + break; + default: + OSL_FAIL( "AddDataItemDialog::InitFronNode: cannot handle this node type!" ); + break; + } + + /** Get binding of the node and clone it + Then use this temporary binding in the dialog. + When the user click OK the temporary binding will be copied + into the original binding. + */ + + Reference< css::xml::dom::XNode > xNode = m_pItemNode->m_xNode; + m_xBinding = m_xUIHelper->getBindingForNode( xNode, true ); + if ( m_xBinding.is() ) + { + Reference< css::xforms::XModel > xModel( m_xUIHelper, UNO_QUERY ); + if ( xModel.is() ) + { + m_xTempBinding = m_xUIHelper->cloneBindingAsGhost( m_xBinding ); + Reference < XSet > xBindings = xModel->getBindings(); + if ( xBindings.is() ) + xBindings->insert( makeAny( m_xTempBinding ) ); + } + } + + if ( m_eItemType != DITText ) + { + OUString sName( m_xUIHelper->getNodeName( m_pItemNode->m_xNode ) ); + m_xNameED->set_text( sName ); + } + m_xDefaultED->set_text( m_pItemNode->m_xNode->getNodeValue() ); + } + catch( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "AddDataItemDialog::InitFromNode()" ); + } + } + else if ( m_pItemNode->m_xPropSet.is() ) + { + m_eItemType = DITBinding; + Reference< css::xforms::XModel > xModel( m_xUIHelper, UNO_QUERY ); + if ( xModel.is() ) + { + try + { + m_xTempBinding = m_xUIHelper->cloneBindingAsGhost( m_pItemNode->m_xPropSet ); + Reference < XSet > xBindings = xModel->getBindings(); + if ( xBindings.is() ) + xBindings->insert( makeAny( m_xTempBinding ) ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "AddDataItemDialog::InitFromNode()" ); + } + } + OUString sTemp; + try + { + Reference< XPropertySetInfo > xInfo = m_pItemNode->m_xPropSet->getPropertySetInfo(); + if ( xInfo->hasPropertyByName( PN_BINDING_ID ) ) + { + m_pItemNode->m_xPropSet->getPropertyValue( PN_BINDING_ID ) >>= sTemp; + m_xNameED->set_text( sTemp ); + m_pItemNode->m_xPropSet->getPropertyValue( PN_BINDING_EXPR ) >>= sTemp; + m_xDefaultED->set_text( sTemp ); + } + else if ( xInfo->hasPropertyByName( PN_SUBMISSION_BIND ) ) + { + m_pItemNode->m_xPropSet->getPropertyValue( PN_SUBMISSION_ID ) >>= sTemp; + m_xNameED->set_text( sTemp ); + } + } + catch( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "AddDataItemDialog::InitFromNode()" ); + } + + m_xDefaultBtn->show(); + } + + if ( m_xTempBinding.is() ) + { + OUString sTemp; + try + { + if ( ( m_xTempBinding->getPropertyValue( PN_REQUIRED_EXPR ) >>= sTemp ) + && !sTemp.isEmpty() ) + m_xRequiredCB->set_active(true); + if ( ( m_xTempBinding->getPropertyValue( PN_RELEVANT_EXPR ) >>= sTemp ) + && !sTemp.isEmpty() ) + m_xRelevantCB->set_active(true); + if ( ( m_xTempBinding->getPropertyValue( PN_CONSTRAINT_EXPR ) >>= sTemp ) + && !sTemp.isEmpty() ) + m_xConstraintCB->set_active(true); + if ( ( m_xTempBinding->getPropertyValue( PN_READONLY_EXPR ) >>= sTemp ) + && !sTemp.isEmpty() ) + m_xReadonlyCB->set_active(true); + if ( ( m_xTempBinding->getPropertyValue( PN_CALCULATE_EXPR ) >>= sTemp ) + && !sTemp.isEmpty() ) + m_xCalculateCB->set_active(true); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "svx.form", "AddDataItemDialog::InitFromNode()" ); + } + } + } + + if ( DITText == m_eItemType ) + { + m_xSettingsFrame->hide(); + m_xNameFT->set_sensitive(false); + m_xNameED->set_sensitive(false); + } + } + + void AddDataItemDialog::InitDataTypeBox() + { + if ( m_eItemType != DITText ) + { + Reference< css::xforms::XModel > xModel( m_xUIHelper, UNO_QUERY ); + if ( xModel.is() ) + { + try + { + Reference< css::xforms::XDataTypeRepository > xDataTypes = + xModel->getDataTypeRepository(); + if ( xDataTypes.is() ) + { + const Sequence< OUString > aNameList = xDataTypes->getElementNames(); + for ( const OUString& rName : aNameList ) + m_xDataTypeLB->append_text(rName); + } + + if ( m_xTempBinding.is() ) + { + OUString sTemp; + if ( m_xTempBinding->getPropertyValue( PN_BINDING_TYPE ) >>= sTemp ) + { + int nPos = m_xDataTypeLB->find_text(sTemp); + if (nPos == -1) + { + m_xDataTypeLB->append_text(sTemp); + nPos = m_xDataTypeLB->get_count() - 1; + } + m_xDataTypeLB->set_active(nPos); + } + } + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "AddDataItemDialog::InitDataTypeBox()" ); + } + } + } + } + + void AddDataItemDialog::InitText( DataItemType _eType ) + { + OUString sText; + + switch ( _eType ) + { + case DITAttribute : + { + sText = m_sFL_Attribute; + break; + } + + case DITBinding : + { + sText = m_sFL_Binding; + m_xDefaultFT->set_label(m_sFT_BindingExp); + break; + } + + default: + { + sText = m_sFL_Element; + } + } + + m_xItemFrame->set_label(sText); + } + + AddConditionDialog::AddConditionDialog(weld::Window* pParent, + const OUString& _rPropertyName, + const Reference< XPropertySet >& _rPropSet) + : GenericDialogController(pParent, "svx/ui/addconditiondialog.ui", "AddConditionDialog") + , m_sPropertyName(_rPropertyName) + , m_xBinding(_rPropSet) + , m_xConditionED(m_xBuilder->weld_text_view("condition")) + , m_xResultWin(m_xBuilder->weld_text_view("result")) + , m_xEditNamespacesBtn(m_xBuilder->weld_button("edit")) + , m_xOKBtn(m_xBuilder->weld_button("ok")) + { + DBG_ASSERT( m_xBinding.is(), "AddConditionDialog::Ctor(): no Binding" ); + + m_xConditionED->set_size_request(m_xConditionED->get_approximate_digit_width() * 52, + m_xConditionED->get_height_rows(4)); + m_xResultWin->set_size_request(m_xResultWin->get_approximate_digit_width() * 52, + m_xResultWin->get_height_rows(4)); + + m_xConditionED->connect_changed( LINK( this, AddConditionDialog, ModifyHdl ) ); + m_xEditNamespacesBtn->connect_clicked( LINK( this, AddConditionDialog, EditHdl ) ); + m_xOKBtn->connect_clicked( LINK( this, AddConditionDialog, OKHdl ) ); + m_aResultIdle.SetPriority( TaskPriority::LOWEST ); + m_aResultIdle.SetInvokeHandler( LINK( this, AddConditionDialog, ResultHdl ) ); + + if ( !m_sPropertyName.isEmpty() ) + { + try + { + OUString sTemp; + if ( ( m_xBinding->getPropertyValue( m_sPropertyName ) >>= sTemp ) + && !sTemp.isEmpty() ) + { + m_xConditionED->set_text( sTemp ); + } + else + { +//! m_xBinding->setPropertyValue( m_sPropertyName, makeAny( TRUE_VALUE ) ); + m_xConditionED->set_text( TRUE_VALUE ); + } + + Reference< css::xforms::XModel > xModel; + if ( ( m_xBinding->getPropertyValue( PN_BINDING_MODEL ) >>= xModel ) && xModel.is() ) + m_xUIHelper.set( xModel, UNO_QUERY ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "svx.form", "AddConditionDialog::Ctor()" ); + } + } + + DBG_ASSERT( m_xUIHelper.is(), "AddConditionDialog::Ctor(): no UIHelper" ); + ResultHdl( &m_aResultIdle ); + } + + AddConditionDialog::~AddConditionDialog() + { + } + + IMPL_LINK_NOARG(AddConditionDialog, EditHdl, weld::Button&, void) + { + Reference< XNameContainer > xNameContnr; + try + { + m_xBinding->getPropertyValue( PN_BINDING_NAMESPACES ) >>= xNameContnr; + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "AddDataItemDialog::EditHdl()" ); + } + NamespaceItemDialog aDlg(this, xNameContnr); + aDlg.run(); + try + { + m_xBinding->setPropertyValue( PN_BINDING_NAMESPACES, makeAny( xNameContnr ) ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "AddDataItemDialog::EditHdl()" ); + } + } + + IMPL_LINK_NOARG(AddConditionDialog, OKHdl, weld::Button&, void) + { + m_xDialog->response(RET_OK); + } + + IMPL_LINK_NOARG(AddConditionDialog, ModifyHdl, weld::TextView&, void) + { + m_aResultIdle.Start(); + } + + IMPL_LINK_NOARG(AddConditionDialog, ResultHdl, Timer *, void) + { + OUString sCondition = comphelper::string::strip(m_xConditionED->get_text(), ' '); + OUString sResult; + if ( !sCondition.isEmpty() ) + { + try + { + sResult = m_xUIHelper->getResultForExpression( m_xBinding, ( m_sPropertyName == PN_BINDING_EXPR ), sCondition ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "AddConditionDialog::ResultHdl()" ); + } + } + m_xResultWin->set_text(sResult); + } + + NamespaceItemDialog::NamespaceItemDialog(AddConditionDialog* pCondDlg, Reference<XNameContainer>& rContainer) + : GenericDialogController(pCondDlg->getDialog(), "svx/ui/namespacedialog.ui", "NamespaceDialog") + , m_pConditionDlg(pCondDlg) + , m_rNamespaces(rContainer) + , m_xNamespacesList(m_xBuilder->weld_tree_view("namespaces")) + , m_xAddNamespaceBtn(m_xBuilder->weld_button("add")) + , m_xEditNamespaceBtn(m_xBuilder->weld_button("edit")) + , m_xDeleteNamespaceBtn(m_xBuilder->weld_button("delete")) + , m_xOKBtn(m_xBuilder->weld_button("ok")) + { + m_xNamespacesList->set_size_request(m_xNamespacesList->get_approximate_digit_width() * 80, + m_xNamespacesList->get_height_rows(8)); + + std::vector<int> aWidths; + aWidths.push_back(m_xNamespacesList->get_approximate_digit_width() * 20); + m_xNamespacesList->set_column_fixed_widths(aWidths); + + m_xNamespacesList->connect_changed( LINK( this, NamespaceItemDialog, SelectHdl ) ); + Link<weld::Button&,void> aLink = LINK( this, NamespaceItemDialog, ClickHdl ); + m_xAddNamespaceBtn->connect_clicked( aLink ); + m_xEditNamespaceBtn->connect_clicked( aLink ); + m_xDeleteNamespaceBtn->connect_clicked( aLink ); + m_xOKBtn->connect_clicked( LINK( this, NamespaceItemDialog, OKHdl ) ); + + LoadNamespaces(); + SelectHdl(*m_xNamespacesList); + } + + NamespaceItemDialog::~NamespaceItemDialog() + { + } + + IMPL_LINK_NOARG( NamespaceItemDialog, SelectHdl, weld::TreeView&, void) + { + bool bEnable = m_xNamespacesList->get_selected_index() != -1; + m_xEditNamespaceBtn->set_sensitive( bEnable ); + m_xDeleteNamespaceBtn->set_sensitive( bEnable ); + } + + IMPL_LINK( NamespaceItemDialog, ClickHdl, weld::Button&, rButton, void ) + { + if (m_xAddNamespaceBtn.get() == &rButton) + { + ManageNamespaceDialog aDlg(m_xDialog.get(), m_pConditionDlg, false); + if (aDlg.run() == RET_OK) + { + m_xNamespacesList->append_text(aDlg.GetPrefix()); + int nRow = m_xNamespacesList->n_children(); + m_xNamespacesList->set_text(nRow - 1, aDlg.GetURL(), 1); + } + } + else if (m_xEditNamespaceBtn.get() == &rButton) + { + ManageNamespaceDialog aDlg(m_xDialog.get(), m_pConditionDlg, true); + int nEntry = m_xNamespacesList->get_selected_index(); + DBG_ASSERT( nEntry != -1, "NamespaceItemDialog::ClickHdl(): no entry" ); + OUString sPrefix(m_xNamespacesList->get_text(nEntry, 0)); + aDlg.SetNamespace(sPrefix, m_xNamespacesList->get_text(nEntry, 1)); + if (aDlg.run() == RET_OK) + { + // if a prefix was changed, mark the old prefix as 'removed' + if( sPrefix != aDlg.GetPrefix() ) + m_aRemovedList.push_back( sPrefix ); + + m_xNamespacesList->set_text(nEntry, aDlg.GetPrefix(), 0); + m_xNamespacesList->set_text(nEntry, aDlg.GetURL(), 1); + } + } + else if (m_xDeleteNamespaceBtn.get() == &rButton) + { + int nEntry = m_xNamespacesList->get_selected_index(); + DBG_ASSERT( nEntry != -1, "NamespaceItemDialog::ClickHdl(): no entry" ); + OUString sPrefix(m_xNamespacesList->get_text(nEntry, 0)); + m_aRemovedList.push_back( sPrefix ); + m_xNamespacesList->remove(nEntry); + } + else + { + SAL_WARN( "svx.form", "NamespaceItemDialog::ClickHdl(): invalid button" ); + } + + SelectHdl(*m_xNamespacesList); + } + + IMPL_LINK_NOARG(NamespaceItemDialog, OKHdl, weld::Button&, void) + { + try + { + // update namespace container + sal_Int32 i, nRemovedCount = m_aRemovedList.size(); + for( i = 0; i < nRemovedCount; ++i ) + m_rNamespaces->removeByName( m_aRemovedList[i] ); + + sal_Int32 nEntryCount = m_xNamespacesList->n_children(); + for( i = 0; i < nEntryCount; ++i ) + { + OUString sPrefix(m_xNamespacesList->get_text(i, 0)); + OUString sURL(m_xNamespacesList->get_text(i, 1)); + + if ( m_rNamespaces->hasByName( sPrefix ) ) + m_rNamespaces->replaceByName( sPrefix, makeAny( sURL ) ); + else + m_rNamespaces->insertByName( sPrefix, makeAny( sURL ) ); + } + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "NamespaceItemDialog::OKHdl()" ); + } + // and close the dialog + m_xDialog->response(RET_OK); + } + + void NamespaceItemDialog::LoadNamespaces() + { + try + { + int nRow = 0; + const Sequence< OUString > aAllNames = m_rNamespaces->getElementNames(); + for ( const OUString& sPrefix : aAllNames ) + { + if ( m_rNamespaces->hasByName( sPrefix ) ) + { + OUString sURL; + Any aAny = m_rNamespaces->getByName( sPrefix ); + if (aAny >>= sURL) + { + m_xNamespacesList->append_text(sPrefix); + m_xNamespacesList->set_text(nRow, sURL, 1); + ++nRow; + } + } + } + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "NamespaceItemDialog::LoadNamespaces()" ); + } + } + + ManageNamespaceDialog::ManageNamespaceDialog(weld::Window* pParent, AddConditionDialog* pCondDlg, bool bIsEdit) + : GenericDialogController(pParent, "svx/ui/addnamespacedialog.ui", "AddNamespaceDialog") + , m_pConditionDlg(pCondDlg) + , m_xPrefixED(m_xBuilder->weld_entry("prefix")) + , m_xUrlED(m_xBuilder->weld_entry("url")) + , m_xOKBtn(m_xBuilder->weld_button("ok")) + , m_xAltTitle(m_xBuilder->weld_label("alttitle")) + { + if (bIsEdit) + m_xDialog->set_title(m_xAltTitle->get_label()); + + m_xOKBtn->connect_clicked(LINK(this, ManageNamespaceDialog, OKHdl)); + } + + ManageNamespaceDialog::~ManageNamespaceDialog() + { + } + + IMPL_LINK_NOARG(ManageNamespaceDialog, OKHdl, weld::Button&, void) + { + OUString sPrefix = m_xPrefixED->get_text(); + + try + { + if (!m_pConditionDlg->GetUIHelper()->isValidPrefixName(sPrefix)) + { + std::unique_ptr<weld::MessageDialog> xErrBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, + SvxResId(RID_STR_INVALID_XMLPREFIX))); + xErrBox->set_primary_text(xErrBox->get_primary_text().replaceFirst(MSG_VARIABLE, sPrefix)); + xErrBox->run(); + return; + } + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "ManageNamespacesDialog::OKHdl()" ); + } + + // no error so close the dialog + m_xDialog->response(RET_OK); + } + + AddSubmissionDialog::AddSubmissionDialog( + weld::Window* pParent, ItemNode* _pNode, + const Reference< css::xforms::XFormsUIHelper1 >& _rUIHelper) + : GenericDialogController(pParent, "svx/ui/addsubmissiondialog.ui", "AddSubmissionDialog") + , m_pItemNode(_pNode) + , m_xUIHelper(_rUIHelper) + , m_xNameED(m_xBuilder->weld_entry("name")) + , m_xActionED(m_xBuilder->weld_entry("action")) + , m_xMethodLB(m_xBuilder->weld_combo_box("method")) + , m_xRefED(m_xBuilder->weld_entry("expression")) + , m_xRefBtn(m_xBuilder->weld_button("browse")) + , m_xBindLB(m_xBuilder->weld_combo_box("binding")) + , m_xReplaceLB(m_xBuilder->weld_combo_box("replace")) + , m_xOKBtn(m_xBuilder->weld_button("ok")) + { + FillAllBoxes(); + + m_xRefBtn->connect_clicked( LINK( this, AddSubmissionDialog, RefHdl ) ); + m_xOKBtn->connect_clicked( LINK( this, AddSubmissionDialog, OKHdl ) ); + } + + AddSubmissionDialog::~AddSubmissionDialog() + { + // #i38991# if we have added a binding, we need to remove it as well. + if( m_xCreatedBinding.is() && m_xUIHelper.is() ) + m_xUIHelper->removeBindingIfUseless( m_xCreatedBinding ); + } + + IMPL_LINK_NOARG(AddSubmissionDialog, RefHdl, weld::Button&, void) + { + AddConditionDialog aDlg(m_xDialog.get(), PN_BINDING_EXPR, m_xTempBinding ); + aDlg.SetCondition( m_xRefED->get_text() ); + if ( aDlg.run() == RET_OK ) + m_xRefED->set_text(aDlg.GetCondition()); + } + + IMPL_LINK_NOARG(AddSubmissionDialog, OKHdl, weld::Button&, void) + { + OUString sName(m_xNameED->get_text()); + if(sName.isEmpty()) + { + std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, + SvxResId(RID_STR_EMPTY_SUBMISSIONNAME))); + xErrorBox->run(); + return; + } + + if ( !m_xSubmission.is() ) + { + DBG_ASSERT( !m_xNewSubmission.is(), + "AddSubmissionDialog::OKHdl(): new submission already exists" ); + + // add a new submission + Reference< css::xforms::XModel > xModel( m_xUIHelper, UNO_QUERY ); + if ( xModel.is() ) + { + try + { + m_xNewSubmission = xModel->createSubmission(); + m_xSubmission = m_xNewSubmission; + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "AddSubmissionDialog::OKHdl()" ); + } + } + } + + if ( m_xSubmission.is() ) + { + OUString sTemp = m_xNameED->get_text(); + try + { + m_xSubmission->setPropertyValue( PN_SUBMISSION_ID, makeAny( sTemp ) ); + sTemp = m_xActionED->get_text(); + m_xSubmission->setPropertyValue( PN_SUBMISSION_ACTION, makeAny( sTemp ) ); + sTemp = m_aMethodString.toAPI( m_xMethodLB->get_active_text() ); + m_xSubmission->setPropertyValue( PN_SUBMISSION_METHOD, makeAny( sTemp ) ); + sTemp = m_xRefED->get_text(); + m_xSubmission->setPropertyValue( PN_SUBMISSION_REF, makeAny( sTemp ) ); + OUString sEntry = m_xBindLB->get_active_text(); + sal_Int32 nColonIdx = sEntry.indexOf(':'); + if (nColonIdx != -1) + sEntry = sEntry.copy(0, nColonIdx); + sTemp = sEntry; + m_xSubmission->setPropertyValue( PN_SUBMISSION_BIND, makeAny( sTemp ) ); + sTemp = m_aReplaceString.toAPI( m_xReplaceLB->get_active_text() ); + m_xSubmission->setPropertyValue( PN_SUBMISSION_REPLACE, makeAny( sTemp ) ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "AddSubmissionDialog::OKHdl()" ); + } + } + + m_xDialog->response(RET_OK); + } + + void AddSubmissionDialog::FillAllBoxes() + { + // method box + m_xMethodLB->append_text(SvxResId(RID_STR_METHOD_POST)); + m_xMethodLB->append_text(SvxResId(RID_STR_METHOD_PUT)); + m_xMethodLB->append_text(SvxResId(RID_STR_METHOD_GET)); + m_xMethodLB->set_active(0); + + // binding box + Reference< css::xforms::XModel > xModel( m_xUIHelper, UNO_QUERY ); + if ( xModel.is() ) + { + try + { + Reference< XEnumerationAccess > xNumAccess = xModel->getBindings(); + if ( xNumAccess.is() ) + { + Reference < XEnumeration > xNum = xNumAccess->createEnumeration(); + if ( xNum.is() && xNum->hasMoreElements() ) + { + while ( xNum->hasMoreElements() ) + { + Reference< XPropertySet > xPropSet; + Any aAny = xNum->nextElement(); + if ( aAny >>= xPropSet ) + { + OUString sEntry; + OUString sTemp; + xPropSet->getPropertyValue( PN_BINDING_ID ) >>= sTemp; + sEntry += sTemp + ": "; + xPropSet->getPropertyValue( PN_BINDING_EXPR ) >>= sTemp; + sEntry += sTemp; + m_xBindLB->append_text(sEntry); + + if ( !m_xTempBinding.is() ) + m_xTempBinding = xPropSet; + } + } + } + } + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "AddSubmissionDialog::FillAllBoxes()" ); + } + } + + // #i36342# we need a temporary binding; create one if no existing binding + // is found + if( !m_xTempBinding.is() ) + { + m_xCreatedBinding = m_xUIHelper->getBindingForNode( + Reference<css::xml::dom::XNode>( + xModel->getDefaultInstance()->getDocumentElement(), + UNO_QUERY_THROW ), + true ); + m_xTempBinding = m_xCreatedBinding; + } + + // replace box + m_xReplaceLB->append_text(SvxResId(RID_STR_REPLACE_NONE)); + m_xReplaceLB->append_text(SvxResId(RID_STR_REPLACE_INST)); + m_xReplaceLB->append_text(SvxResId(RID_STR_REPLACE_DOC)); + + + // init the controls with the values of the submission + if ( m_pItemNode && m_pItemNode->m_xPropSet.is() ) + { + m_xSubmission = m_pItemNode->m_xPropSet; + OUString sTemp; + try + { + m_xSubmission->getPropertyValue( PN_SUBMISSION_ID ) >>= sTemp; + m_xNameED->set_text( sTemp ); + m_xSubmission->getPropertyValue( PN_SUBMISSION_ACTION ) >>= sTemp; + m_xActionED->set_text( sTemp ); + m_xSubmission->getPropertyValue( PN_SUBMISSION_REF ) >>= sTemp; + m_xRefED->set_text(sTemp); + + m_xSubmission->getPropertyValue( PN_SUBMISSION_METHOD ) >>= sTemp; + sTemp = m_aMethodString.toUI( sTemp ); + sal_Int32 nPos = m_xMethodLB->find_text( sTemp ); + if (nPos == -1) + { + m_xMethodLB->append_text( sTemp ); + nPos = m_xMethodLB->get_count() - 1; + } + m_xMethodLB->set_active( nPos ); + + m_xSubmission->getPropertyValue( PN_SUBMISSION_BIND ) >>= sTemp; + nPos = m_xBindLB->find_text(sTemp); + if (nPos == -1) + { + m_xBindLB->append_text(sTemp); + nPos = m_xBindLB->get_count() - 1; + } + m_xBindLB->set_active(nPos); + + m_xSubmission->getPropertyValue( PN_SUBMISSION_REPLACE ) >>= sTemp; + sTemp = m_aReplaceString.toUI( sTemp ); + if ( sTemp.isEmpty() ) + sTemp = m_xReplaceLB->get_text(0); // first entry == "none" + nPos = m_xReplaceLB->find_text(sTemp); + if (nPos == -1) + { + m_xReplaceLB->append_text(sTemp); + nPos = m_xReplaceLB->get_count() - 1; + } + m_xReplaceLB->set_active(nPos); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "svx.form", "AddSubmissionDialog::FillAllBoxes()" ); + } + } + + m_xRefBtn->set_sensitive(m_xTempBinding.is()); + } + + AddModelDialog::AddModelDialog(weld::Window* pParent, bool bIsEdit) + : GenericDialogController(pParent, "svx/ui/addmodeldialog.ui", "AddModelDialog") + , m_xNameED(m_xBuilder->weld_entry("name")) + , m_xModifyCB(m_xBuilder->weld_check_button("modify")) + , m_xAltTitle(m_xBuilder->weld_label("alttitle")) + { + if (bIsEdit) + m_xDialog->set_title(m_xAltTitle->get_label()); + } + + AddModelDialog::~AddModelDialog() + { + } + + AddInstanceDialog::AddInstanceDialog(weld::Window* pParent, bool _bEdit) + : GenericDialogController(pParent, "svx/ui/addinstancedialog.ui", "AddInstanceDialog") + , m_xNameED(m_xBuilder->weld_entry("name")) + , m_xURLFT(m_xBuilder->weld_label("urlft")) + , m_xURLED(new SvtURLBox(m_xBuilder->weld_combo_box("url"))) + , m_xFilePickerBtn(m_xBuilder->weld_button("browse")) + , m_xLinkInstanceCB(m_xBuilder->weld_check_button("link")) + , m_xAltTitle(m_xBuilder->weld_label("alttitle")) + { + if (_bEdit) + m_xDialog->set_title(m_xAltTitle->get_label()); + + m_xURLED->DisableHistory(); + m_xFilePickerBtn->connect_clicked(LINK(this, AddInstanceDialog, FilePickerHdl)); + + // load the filter name from fps resource + m_sAllFilterName = Translate::get(STR_FILTERNAME_ALL, Translate::Create("fps")); + } + + AddInstanceDialog::~AddInstanceDialog() + { + } + + IMPL_LINK_NOARG(AddInstanceDialog, FilePickerHdl, weld::Button&, void) + { + ::sfx2::FileDialogHelper aDlg( + css::ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE, + FileDialogFlags::NONE, m_xDialog.get()); + INetURLObject aFile( SvtPathOptions().GetWorkPath() ); + + aDlg.AddFilter( m_sAllFilterName, FILEDIALOG_FILTER_ALL ); + OUString sFilterName( "XML" ); + aDlg.AddFilter( sFilterName, "*.xml" ); + aDlg.SetCurrentFilter( sFilterName ); + aDlg.SetDisplayDirectory( aFile.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + + if (aDlg.Execute() == ERRCODE_NONE) + m_xURLED->set_entry_text(aDlg.GetPath()); + } + + LinkedInstanceWarningBox::LinkedInstanceWarningBox(weld::Widget* pParent) + : MessageDialogController(pParent, "svx/ui/formlinkwarndialog.ui", + "FormLinkWarnDialog") + { + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/dbcharsethelper.cxx b/svx/source/form/dbcharsethelper.cxx new file mode 100644 index 000000000..a20ffa7c1 --- /dev/null +++ b/svx/source/form/dbcharsethelper.cxx @@ -0,0 +1,48 @@ +/* -*- 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 <dbcharsethelper.hxx> + +#include <connectivity/dbcharset.hxx> + +using namespace ::dbtools; + +namespace svxform::charset_helper +{ + + sal_Int32 getSupportedTextEncodings( ::std::vector< rtl_TextEncoding >& _rEncs ) + { + OCharsetMap aCharsetInfo; + _rEncs.clear(); + + OCharsetMap::const_iterator aLoop = aCharsetInfo.begin(); + OCharsetMap::const_iterator aLoopEnd = aCharsetInfo.end(); + while (aLoop != aLoopEnd) + { + _rEncs.push_back( (*aLoop).getEncoding() ); + ++aLoop; + } + + return _rEncs.size(); + } + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/delayedevent.cxx b/svx/source/form/delayedevent.cxx new file mode 100644 index 000000000..8a9cb218d --- /dev/null +++ b/svx/source/form/delayedevent.cxx @@ -0,0 +1,48 @@ +/* -*- 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 <delayedevent.hxx> +#include <vcl/svapp.hxx> +#include <sal/log.hxx> + +namespace svxform +{ + void DelayedEvent::Call() + { + CancelPendingCall(); + SAL_WARN_IF( m_nEventId != nullptr, "svx.form", "DelayedEvent::Call: CancelPendingCall did not work!" ); + + m_nEventId = Application::PostUserEvent( LINK( this, DelayedEvent, OnCall ) ); + } + + void DelayedEvent::CancelPendingCall() + { + if ( m_nEventId ) + Application::RemoveUserEvent( m_nEventId ); + m_nEventId = nullptr; + } + + IMPL_LINK( DelayedEvent, OnCall, void*, _pArg, void ) + { + m_nEventId = nullptr; + m_aHandler.Call( _pArg ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/filtnav.cxx b/svx/source/form/filtnav.cxx new file mode 100644 index 000000000..2b6dee83e --- /dev/null +++ b/svx/source/form/filtnav.cxx @@ -0,0 +1,1926 @@ +/* -*- 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 <memory> +#include <filtnav.hxx> +#include <fmexch.hxx> +#include <helpids.h> +#include <fmprop.hxx> +#include <svx/strings.hrc> + +#include <com/sun/star/awt/XControl.hpp> +#include <com/sun/star/form/runtime/XFormController.hpp> +#include <com/sun/star/util/NumberFormatter.hpp> +#include <com/sun/star/sdb/SQLContext.hpp> + +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <connectivity/dbtools.hxx> +#include <connectivity/sqlnode.hxx> +#include <cppuhelper/implbase.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <fmshimp.hxx> +#include <o3tl/safeint.hxx> +#include <sfx2/objitem.hxx> +#include <sfx2/request.hxx> +#include <svx/dialmgr.hxx> +#include <svx/fmshell.hxx> +#include <svx/fmtools.hxx> +#include <svx/svxids.hrc> +#include <vcl/settings.hxx> +#include <tools/diagnose_ex.h> +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/svlbitm.hxx> +#include <vcl/treelistentry.hxx> +#include <vcl/viewdataentry.hxx> +#include <vcl/svapp.hxx> + +#include <bitmaps.hlst> + +#include <functional> + +#define DROP_ACTION_TIMER_INITIAL_TICKS 10 + // it takes this long for the scrolling to begin +#define DROP_ACTION_TIMER_SCROLL_TICKS 3 + // a line is scrolled in these intervals +#define DROP_ACTION_TIMER_TICK_BASE 10 + // this is the basis for multiplying both figures (in ms) + +using namespace ::svxform; +using namespace ::connectivity; +using namespace ::dbtools; + + +namespace svxform +{ + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::container::XIndexAccess; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::beans::XPropertySet; + using ::com::sun::star::form::runtime::XFormController; + using ::com::sun::star::form::runtime::XFilterController; + using ::com::sun::star::form::runtime::XFilterControllerListener; + using ::com::sun::star::form::runtime::FilterEvent; + using ::com::sun::star::lang::EventObject; + using ::com::sun::star::form::XForm; + using ::com::sun::star::container::XChild; + using ::com::sun::star::awt::XControl; + using ::com::sun::star::sdbc::XConnection; + using ::com::sun::star::util::XNumberFormatsSupplier; + using ::com::sun::star::util::XNumberFormatter; + using ::com::sun::star::util::NumberFormatter; + using ::com::sun::star::sdbc::XRowSet; + using ::com::sun::star::lang::Locale; + using ::com::sun::star::sdb::SQLContext; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::uno::UNO_SET_THROW; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::uno::Sequence; + + +OFilterItemExchange::OFilterItemExchange() + : m_pFormItem(nullptr) +{ +} + +void OFilterItemExchange::AddSupportedFormats() +{ + AddFormat(getFormatId()); +} + + +SotClipboardFormatId OFilterItemExchange::getFormatId() +{ + static SotClipboardFormatId s_nFormat = + SotExchange::RegisterFormatName("application/x-openoffice;windows_formatname=\"form.FilterControlExchange\""); + DBG_ASSERT(static_cast<SotClipboardFormatId>(-1) != s_nFormat, "OFilterExchangeHelper::getFormatId: bad exchange id!"); + return s_nFormat; +} + + +OLocalExchange* OFilterExchangeHelper::createExchange() const +{ + return new OFilterItemExchange; +} + + +Image FmFilterData::GetImage() const +{ + return Image(); +} + +FmParentData::~FmParentData() +{ +} + +Image FmFormItem::GetImage() const +{ + return Image(StockImage::Yes, RID_SVXBMP_FORM); +} + +FmFilterItem* FmFilterItems::Find( const ::sal_Int32 _nFilterComponentIndex ) const +{ + for ( auto & pData : m_aChildren ) + { + FmFilterItem& rCondition = dynamic_cast<FmFilterItem&>(*pData); + if ( _nFilterComponentIndex == rCondition.GetComponentIndex() ) + return &rCondition; + } + return nullptr; +} + +Image FmFilterItems::GetImage() const +{ + return Image(StockImage::Yes, RID_SVXBMP_FILTER); +} + +FmFilterItem::FmFilterItem( FmFilterItems* pParent, + const OUString& aFieldName, + const OUString& aText, + const sal_Int32 _nComponentIndex ) + :FmFilterData(pParent, aText) + ,m_aFieldName(aFieldName) + ,m_nComponentIndex( _nComponentIndex ) +{ +} + +Image FmFilterItem::GetImage() const +{ + return Image(StockImage::Yes, RID_SVXBMP_FIELD); +} + +// Hints for communication between model and view + +namespace { + +class FmFilterHint : public SfxHint +{ + FmFilterData* m_pData; + +public: + explicit FmFilterHint(FmFilterData* pData):m_pData(pData){} + FmFilterData* GetData() const { return m_pData; } +}; + +class FmFilterInsertedHint : public FmFilterHint +{ + size_t m_nPos; // Position relative to the parent of the data + +public: + FmFilterInsertedHint(FmFilterData* pData, size_t nRelPos) + :FmFilterHint(pData) + ,m_nPos(nRelPos){} + + size_t GetPos() const { return m_nPos; } +}; + +class FmFilterRemovedHint : public FmFilterHint +{ +public: + explicit FmFilterRemovedHint(FmFilterData* pData) + :FmFilterHint(pData){} +}; + + +class FmFilterTextChangedHint : public FmFilterHint +{ +public: + explicit FmFilterTextChangedHint(FmFilterData* pData) + :FmFilterHint(pData){} +}; + +class FilterClearingHint : public SfxHint +{ +public: + FilterClearingHint(){} +}; + +class FmFilterCurrentChangedHint : public SfxHint +{ +public: + FmFilterCurrentChangedHint(){} +}; + +} + +// class FmFilterAdapter, listener at the FilterControls +class FmFilterAdapter : public ::cppu::WeakImplHelper< XFilterControllerListener > +{ + FmFilterModel* m_pModel; + Reference< XIndexAccess > m_xControllers; + +public: + FmFilterAdapter(FmFilterModel* pModel, const Reference< XIndexAccess >& xControllers); + +// XEventListener + virtual void SAL_CALL disposing(const EventObject& Source) override; + +// XFilterControllerListener + virtual void SAL_CALL predicateExpressionChanged( const FilterEvent& Event ) override; + virtual void SAL_CALL disjunctiveTermRemoved( const FilterEvent& Event ) override; + virtual void SAL_CALL disjunctiveTermAdded( const FilterEvent& Event ) override; + +// helpers + /// @throws RuntimeException + void dispose(); + + void AddOrRemoveListener( const Reference< XIndexAccess >& _rxControllers, const bool _bAdd ); + + static void setText(sal_Int32 nPos, + const FmFilterItem* pFilterItem, + const OUString& rText); +}; + + +FmFilterAdapter::FmFilterAdapter(FmFilterModel* pModel, const Reference< XIndexAccess >& xControllers) + :m_pModel( pModel ) + ,m_xControllers( xControllers ) +{ + AddOrRemoveListener( m_xControllers, true ); +} + + +void FmFilterAdapter::dispose() +{ + AddOrRemoveListener( m_xControllers, false ); +} + + +void FmFilterAdapter::AddOrRemoveListener( const Reference< XIndexAccess >& _rxControllers, const bool _bAdd ) +{ + for (sal_Int32 i = 0, nLen = _rxControllers->getCount(); i < nLen; ++i) + { + Reference< XIndexAccess > xElement( _rxControllers->getByIndex(i), UNO_QUERY ); + + // step down + AddOrRemoveListener( xElement, _bAdd ); + + // handle this particular controller + Reference< XFilterController > xController( xElement, UNO_QUERY ); + OSL_ENSURE( xController.is(), "FmFilterAdapter::InsertElements: no XFilterController, cannot sync data!" ); + if ( xController.is() ) + { + if ( _bAdd ) + xController->addFilterControllerListener( this ); + else + xController->removeFilterControllerListener( this ); + } + } +} + + +void FmFilterAdapter::setText(sal_Int32 nRowPos, + const FmFilterItem* pFilterItem, + const OUString& rText) +{ + FmFormItem* pFormItem = dynamic_cast<FmFormItem*>( pFilterItem->GetParent()->GetParent() ); + assert(pFormItem); + try + { + Reference< XFilterController > xController( pFormItem->GetController(), UNO_QUERY_THROW ); + xController->setPredicateExpression( pFilterItem->GetComponentIndex(), nRowPos, rText ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +// XEventListener + +void SAL_CALL FmFilterAdapter::disposing(const EventObject& /*e*/) +{ +} + + +namespace +{ + OUString lcl_getLabelName_nothrow( const Reference< XControl >& _rxControl ) + { + OUString sLabelName; + try + { + Reference< XPropertySet > xModel( _rxControl->getModel(), UNO_QUERY_THROW ); + sLabelName = getLabelName( xModel ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return sLabelName; + } + + Reference< XPropertySet > lcl_getBoundField_nothrow( const Reference< XControl >& _rxControl ) + { + Reference< XPropertySet > xField; + try + { + Reference< XPropertySet > xModelProps( _rxControl->getModel(), UNO_QUERY_THROW ); + xField.set( xModelProps->getPropertyValue( FM_PROP_BOUNDFIELD ), UNO_QUERY_THROW ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return xField; + } +} + +// XFilterControllerListener + +void FmFilterAdapter::predicateExpressionChanged( const FilterEvent& Event ) +{ + SolarMutexGuard aGuard; + + if ( !m_pModel ) + return; + + // the controller which sent the event + Reference< XFormController > xController( Event.Source, UNO_QUERY_THROW ); + Reference< XFilterController > xFilterController( Event.Source, UNO_QUERY_THROW ); + Reference< XForm > xForm( xController->getModel(), UNO_QUERY_THROW ); + + FmFormItem* pFormItem = m_pModel->Find( m_pModel->m_aChildren, xForm ); + OSL_ENSURE( pFormItem, "FmFilterAdapter::predicateExpressionChanged: don't know this form!" ); + if ( !pFormItem ) + return; + + const sal_Int32 nActiveTerm( xFilterController->getActiveTerm() ); + + FmFilterData* pData = pFormItem->GetChildren()[nActiveTerm].get(); + FmFilterItems& rFilter = dynamic_cast<FmFilterItems&>(*pData); + FmFilterItem* pFilterItem = rFilter.Find( Event.FilterComponent ); + if ( pFilterItem ) + { + if ( !Event.PredicateExpression.isEmpty()) + { + pFilterItem->SetText( Event.PredicateExpression ); + // notify the UI + FmFilterTextChangedHint aChangeHint(pFilterItem); + m_pModel->Broadcast( aChangeHint ); + } + else + { + // no text anymore so remove the condition + m_pModel->Remove(pFilterItem); + } + } + else + { + // searching the component by field name + OUString aFieldName( lcl_getLabelName_nothrow( xFilterController->getFilterComponent( Event.FilterComponent ) ) ); + + std::unique_ptr<FmFilterItem> pNewFilterItem(new FmFilterItem(&rFilter, aFieldName, Event.PredicateExpression, Event.FilterComponent)); + m_pModel->Insert(rFilter.GetChildren().end(), std::move(pNewFilterItem)); + } + + // ensure there's one empty term in the filter, just in case the active term was previously empty + m_pModel->EnsureEmptyFilterRows( *pFormItem ); +} + + +void SAL_CALL FmFilterAdapter::disjunctiveTermRemoved( const FilterEvent& Event ) +{ + SolarMutexGuard aGuard; + + Reference< XFormController > xController( Event.Source, UNO_QUERY_THROW ); + Reference< XFilterController > xFilterController( Event.Source, UNO_QUERY_THROW ); + Reference< XForm > xForm( xController->getModel(), UNO_QUERY_THROW ); + + FmFormItem* pFormItem = m_pModel->Find( m_pModel->m_aChildren, xForm ); + OSL_ENSURE( pFormItem, "FmFilterAdapter::disjunctiveTermRemoved: don't know this form!" ); + if ( !pFormItem ) + return; + + auto& rTermItems = pFormItem->GetChildren(); + const bool bValidIndex = ( Event.DisjunctiveTerm >= 0 ) && ( o3tl::make_unsigned(Event.DisjunctiveTerm) < rTermItems.size() ); + OSL_ENSURE( bValidIndex, "FmFilterAdapter::disjunctiveTermRemoved: invalid term index!" ); + if ( !bValidIndex ) + return; + + // if the first term was removed, then the to-be first term needs its text updated + if ( Event.DisjunctiveTerm == 0 ) + { + rTermItems[1]->SetText( SvxResId(RID_STR_FILTER_FILTER_FOR)); + FmFilterTextChangedHint aChangeHint( rTermItems[1].get() ); + m_pModel->Broadcast( aChangeHint ); + } + + // finally remove the entry from the model + m_pModel->Remove( rTermItems.begin() + Event.DisjunctiveTerm ); + + // ensure there's one empty term in the filter, just in case the currently removed one was the last empty one + m_pModel->EnsureEmptyFilterRows( *pFormItem ); +} + + +void SAL_CALL FmFilterAdapter::disjunctiveTermAdded( const FilterEvent& Event ) +{ + SolarMutexGuard aGuard; + + Reference< XFormController > xController( Event.Source, UNO_QUERY_THROW ); + Reference< XFilterController > xFilterController( Event.Source, UNO_QUERY_THROW ); + Reference< XForm > xForm( xController->getModel(), UNO_QUERY_THROW ); + + FmFormItem* pFormItem = m_pModel->Find( m_pModel->m_aChildren, xForm ); + OSL_ENSURE( pFormItem, "FmFilterAdapter::disjunctiveTermAdded: don't know this form!" ); + if ( !pFormItem ) + return; + + const sal_Int32 nInsertPos = Event.DisjunctiveTerm; + bool bValidIndex = ( nInsertPos >= 0 ) && ( o3tl::make_unsigned(nInsertPos) <= pFormItem->GetChildren().size() ); + if ( !bValidIndex ) + { + OSL_FAIL( "FmFilterAdapter::disjunctiveTermAdded: invalid index!" ); + return; + } + + auto insertPos = pFormItem->GetChildren().begin() + nInsertPos; + + // "Filter for" for first position, "Or" for the other positions + std::unique_ptr<FmFilterItems> pFilterItems(new FmFilterItems(pFormItem, (nInsertPos?SvxResId(RID_STR_FILTER_FILTER_OR):SvxResId(RID_STR_FILTER_FILTER_FOR)))); + m_pModel->Insert( insertPos, std::move(pFilterItems) ); +} + + +FmFilterModel::FmFilterModel() + :FmParentData(nullptr, OUString()) + ,OSQLParserClient(comphelper::getProcessComponentContext()) + ,m_pCurrentItems(nullptr) +{ +} + + +FmFilterModel::~FmFilterModel() +{ + Clear(); +} + + +void FmFilterModel::Clear() +{ + // notify + FilterClearingHint aClearedHint; + Broadcast( aClearedHint ); + + // lose endings + if (m_pAdapter.is()) + { + m_pAdapter->dispose(); + m_pAdapter.clear(); + } + + m_pCurrentItems = nullptr; + m_xController = nullptr; + m_xControllers = nullptr; + + m_aChildren.clear(); +} + + +void FmFilterModel::Update(const Reference< XIndexAccess > & xControllers, const Reference< XFormController > & xCurrent) +{ + if ( xCurrent == m_xController ) + return; + + if (!xControllers.is()) + { + Clear(); + return; + } + + // there is only a new current controller + if ( m_xControllers != xControllers ) + { + Clear(); + + m_xControllers = xControllers; + Update(m_xControllers, this); + + DBG_ASSERT(xCurrent.is(), "FmFilterModel::Update(...) no current controller"); + + // Listening for TextChanges + m_pAdapter = new FmFilterAdapter(this, xControllers); + + SetCurrentController(xCurrent); + EnsureEmptyFilterRows( *this ); + } + else + SetCurrentController(xCurrent); +} + + +void FmFilterModel::Update(const Reference< XIndexAccess > & xControllers, FmParentData* pParent) +{ + try + { + sal_Int32 nCount = xControllers->getCount(); + for ( sal_Int32 i = 0; i < nCount; ++i ) + { + Reference< XFormController > xController( xControllers->getByIndex(i), UNO_QUERY_THROW ); + + Reference< XPropertySet > xFormProperties( xController->getModel(), UNO_QUERY_THROW ); + OUString aName; + OSL_VERIFY( xFormProperties->getPropertyValue( FM_PROP_NAME ) >>= aName ); + + // Insert a new item for the form + FmFormItem* pFormItem = new FmFormItem( pParent, xController, aName ); + Insert( pParent->GetChildren().end(), std::unique_ptr<FmFilterData>(pFormItem) ); + + Reference< XFilterController > xFilterController( pFormItem->GetFilterController(), UNO_SET_THROW ); + + // insert the existing filters for the form + OUString aTitle(SvxResId(RID_STR_FILTER_FILTER_FOR)); + + const Sequence< Sequence< OUString > > aExpressions = xFilterController->getPredicateExpressions(); + for ( auto const & conjunctionTerm : aExpressions ) + { + // we always display one row, even if there's no term to be displayed + FmFilterItems* pFilterItems = new FmFilterItems( pFormItem, aTitle ); + Insert( pFormItem->GetChildren().end(), std::unique_ptr<FmFilterData>(pFilterItems) ); + + const Sequence< OUString >& rDisjunction( conjunctionTerm ); + sal_Int32 nComponentIndex = -1; + for ( const OUString& rDisjunctiveTerm : rDisjunction ) + { + ++nComponentIndex; + + if ( rDisjunctiveTerm.isEmpty() ) + // no condition for this particular component in this particular conjunction term + continue; + + // determine the display name of the control + const Reference< XControl > xFilterControl( xFilterController->getFilterComponent( nComponentIndex ) ); + const OUString sDisplayName( lcl_getLabelName_nothrow( xFilterControl ) ); + + // insert a new entry + std::unique_ptr<FmFilterItem> pANDCondition(new FmFilterItem( pFilterItems, sDisplayName, rDisjunctiveTerm, nComponentIndex )); + Insert( pFilterItems->GetChildren().end(), std::move(pANDCondition) ); + } + + // title for the next conditions + aTitle = SvxResId( RID_STR_FILTER_FILTER_OR ); + } + + // now add dependent controllers + Update( xController, pFormItem ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +FmFormItem* FmFilterModel::Find(const ::std::vector<std::unique_ptr<FmFilterData>>& rItems, const Reference< XFormController > & xController) const +{ + for (const auto& rItem : rItems) + { + FmFormItem* pForm = dynamic_cast<FmFormItem*>( rItem.get() ); + if (pForm) + { + if ( xController == pForm->GetController() ) + return pForm; + else + { + pForm = Find(pForm->GetChildren(), xController); + if (pForm) + return pForm; + } + } + } + return nullptr; +} + + +FmFormItem* FmFilterModel::Find(const ::std::vector<std::unique_ptr<FmFilterData>>& rItems, const Reference< XForm >& xForm) const +{ + for (const auto& rItem : rItems) + { + FmFormItem* pForm = dynamic_cast<FmFormItem*>( rItem.get() ); + if (pForm) + { + if (xForm == pForm->GetController()->getModel()) + return pForm; + else + { + pForm = Find(pForm->GetChildren(), xForm); + if (pForm) + return pForm; + } + } + } + return nullptr; +} + +void FmFilterModel::SetCurrentController(const Reference< XFormController > & xCurrent) +{ + if ( xCurrent == m_xController ) + return; + + m_xController = xCurrent; + + FmFormItem* pItem = Find( m_aChildren, xCurrent ); + if ( !pItem ) + return; + + try + { + Reference< XFilterController > xFilterController( m_xController, UNO_QUERY_THROW ); + const sal_Int32 nActiveTerm( xFilterController->getActiveTerm() ); + if (nActiveTerm != -1 && pItem->GetChildren().size() > o3tl::make_unsigned(nActiveTerm)) + { + SetCurrentItems( static_cast< FmFilterItems* >( pItem->GetChildren()[ nActiveTerm ].get() ) ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + +void FmFilterModel::AppendFilterItems( FmFormItem& _rFormItem ) +{ + // insert the condition behind the last filter items + auto iter = std::find_if(_rFormItem.GetChildren().rbegin(), _rFormItem.GetChildren().rend(), + [](const std::unique_ptr<FmFilterData>& rChild) { return dynamic_cast<const FmFilterItems*>(rChild.get()) != nullptr; }); + + sal_Int32 nInsertPos = iter.base() - _rFormItem.GetChildren().begin(); + // delegate this to the FilterController, it will notify us, which will let us update our model + try + { + Reference< XFilterController > xFilterController( _rFormItem.GetFilterController(), UNO_SET_THROW ); + if ( nInsertPos >= xFilterController->getDisjunctiveTerms() ) + xFilterController->appendEmptyDisjunctiveTerm(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + +void FmFilterModel::Insert(const ::std::vector<std::unique_ptr<FmFilterData>>::iterator& rPos, std::unique_ptr<FmFilterData> pData) +{ + auto pTemp = pData.get(); + size_t nPos; + ::std::vector<std::unique_ptr<FmFilterData>>& rItems = pData->GetParent()->GetChildren(); + if (rPos == rItems.end()) + { + nPos = rItems.size(); + rItems.push_back(std::move(pData)); + } + else + { + nPos = rPos - rItems.begin(); + rItems.insert(rPos, std::move(pData)); + } + + // notify the UI + FmFilterInsertedHint aInsertedHint(pTemp, nPos); + Broadcast( aInsertedHint ); +} + +void FmFilterModel::Remove(FmFilterData* pData) +{ + FmParentData* pParent = pData->GetParent(); + ::std::vector<std::unique_ptr<FmFilterData>>& rItems = pParent->GetChildren(); + + // erase the item from the model + auto i = ::std::find_if(rItems.begin(), rItems.end(), + [&](const std::unique_ptr<FmFilterData>& p) { return p.get() == pData; } ); + DBG_ASSERT(i != rItems.end(), "FmFilterModel::Remove(): unknown Item"); + // position within the parent + sal_Int32 nPos = i - rItems.begin(); + if (dynamic_cast<const FmFilterItems*>( pData) != nullptr) + { + FmFormItem* pFormItem = static_cast<FmFormItem*>(pParent); + + try + { + Reference< XFilterController > xFilterController( pFormItem->GetFilterController(), UNO_SET_THROW ); + + bool bEmptyLastTerm = ( ( nPos == 0 ) && xFilterController->getDisjunctiveTerms() == 1 ); + if ( bEmptyLastTerm ) + { + // remove all children (by setting an empty predicate expression) + ::std::vector< std::unique_ptr<FmFilterData> >& rChildren = static_cast<FmFilterItems*>(pData)->GetChildren(); + while ( !rChildren.empty() ) + { + auto removePos = rChildren.end() - 1; + if (FmFilterItem* pFilterItem = dynamic_cast<FmFilterItem*>( removePos->get() )) + { + FmFilterAdapter::setText( nPos, pFilterItem, OUString() ); + } + Remove( removePos ); + } + } + else + { + xFilterController->removeDisjunctiveTerm( nPos ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + else // FormItems can not be deleted + { + FmFilterItem& rFilterItem = dynamic_cast<FmFilterItem&>(*pData); + + // if it's the last condition remove the parent + if (rItems.size() == 1) + Remove(rFilterItem.GetParent()); + else + { + // find the position of the father within his father + ::std::vector<std::unique_ptr<FmFilterData>>& rParentParentItems = pData->GetParent()->GetParent()->GetChildren(); + auto j = ::std::find_if(rParentParentItems.begin(), rParentParentItems.end(), + [&](const std::unique_ptr<FmFilterData>& p) { return p.get() == rFilterItem.GetParent(); }); + DBG_ASSERT(j != rParentParentItems.end(), "FmFilterModel::Remove(): unknown Item"); + sal_Int32 nParentPos = j - rParentParentItems.begin(); + + // EmptyText removes the filter + FmFilterAdapter::setText(nParentPos, &rFilterItem, OUString()); + Remove( i ); + } + } +} + +void FmFilterModel::Remove( const ::std::vector<std::unique_ptr<FmFilterData>>::iterator& rPos ) +{ + // remove from parent's child list + std::unique_ptr<FmFilterData> pData = std::move(*rPos); + pData->GetParent()->GetChildren().erase( rPos ); + + // notify the view, this will remove the actual SvTreeListEntry + FmFilterRemovedHint aRemoveHint( pData.get() ); + Broadcast( aRemoveHint ); +} + + +bool FmFilterModel::ValidateText(FmFilterItem const * pItem, OUString& rText, OUString& rErrorMsg) const +{ + FmFormItem* pFormItem = dynamic_cast<FmFormItem*>( pItem->GetParent()->GetParent() ); + assert(pFormItem); + try + { + Reference< XFormController > xFormController( pFormItem->GetController() ); + // obtain the connection of the form belonging to the controller + Reference< XRowSet > xRowSet( xFormController->getModel(), UNO_QUERY_THROW ); + Reference< XConnection > xConnection( getConnection( xRowSet ) ); + + // obtain a number formatter for this connection + // TODO: shouldn't this be cached? + Reference< XNumberFormatsSupplier > xFormatSupplier = getNumberFormats( xConnection, true ); + Reference< XNumberFormatter > xFormatter( NumberFormatter::create( comphelper::getProcessComponentContext() ), UNO_QUERY_THROW ); + xFormatter->attachNumberFormatsSupplier( xFormatSupplier ); + + // get the field (database column) which the item is responsible for + Reference< XFilterController > xFilterController( xFormController, UNO_QUERY_THROW ); + Reference< XPropertySet > xField( lcl_getBoundField_nothrow( xFilterController->getFilterComponent( pItem->GetComponentIndex() ) ), UNO_SET_THROW ); + + // parse the given text as filter predicate + OUString aErr, aTxt( rText ); + std::unique_ptr< OSQLParseNode > pParseNode = predicateTree( aErr, aTxt, xFormatter, xField ); + rErrorMsg = aErr; + rText = aTxt; + if ( pParseNode != nullptr ) + { + OUString aPreparedText; + Locale aAppLocale = Application::GetSettings().GetUILanguageTag().getLocale(); + pParseNode->parseNodeToPredicateStr( + aPreparedText, xConnection, xFormatter, xField, OUString(), aAppLocale, OUString("."), getParseContext() ); + rText = aPreparedText; + return true; + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + return false; +} + + +void FmFilterModel::Append(FmFilterItems* pItems, std::unique_ptr<FmFilterItem> pFilterItem) +{ + Insert(pItems->GetChildren().end(), std::move(pFilterItem)); +} + + +void FmFilterModel::SetTextForItem(FmFilterItem* pItem, const OUString& rText) +{ + ::std::vector<std::unique_ptr<FmFilterData>>& rItems = pItem->GetParent()->GetParent()->GetChildren(); + auto i = ::std::find_if(rItems.begin(), rItems.end(), + [&](const std::unique_ptr<FmFilterData>& p) { return p.get() == pItem->GetParent(); }); + sal_Int32 nParentPos = i - rItems.begin(); + + FmFilterAdapter::setText(nParentPos, pItem, rText); + + if (rText.isEmpty()) + Remove(pItem); + else + { + // Change the text + pItem->SetText(rText); + FmFilterTextChangedHint aChangeHint(pItem); + Broadcast( aChangeHint ); + } +} + + +void FmFilterModel::SetCurrentItems(FmFilterItems* pCurrent) +{ + if (m_pCurrentItems == pCurrent) + return; + + // search for the condition + if (pCurrent) + { + FmFormItem* pFormItem = static_cast<FmFormItem*>(pCurrent->GetParent()); + ::std::vector<std::unique_ptr<FmFilterData>>& rItems = pFormItem->GetChildren(); + auto i = ::std::find_if(rItems.begin(), rItems.end(), + [&](const std::unique_ptr<FmFilterData>& p) { return p.get() == pCurrent; }); + + if (i != rItems.end()) + { + // determine the filter position + sal_Int32 nPos = i - rItems.begin(); + try + { + Reference< XFilterController > xFilterController( pFormItem->GetFilterController(), UNO_SET_THROW ); + xFilterController->setActiveTerm( nPos ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + if ( m_xController != pFormItem->GetController() ) + // calls SetCurrentItems again + SetCurrentController( pFormItem->GetController() ); + else + m_pCurrentItems = pCurrent; + } + else + m_pCurrentItems = nullptr; + } + else + m_pCurrentItems = nullptr; + + + // notify the UI + FmFilterCurrentChangedHint aHint; + Broadcast( aHint ); +} + + +void FmFilterModel::EnsureEmptyFilterRows( FmParentData& _rItem ) +{ + // checks whether for each form there's one free level for input + ::std::vector< std::unique_ptr<FmFilterData> >& rChildren = _rItem.GetChildren(); + bool bAppendLevel = dynamic_cast<const FmFormItem*>(&_rItem) != nullptr; + + for ( const auto& rpChild : rChildren ) + { + FmFilterItems* pItems = dynamic_cast<FmFilterItems*>( rpChild.get() ); + if ( pItems && pItems->GetChildren().empty() ) + { + bAppendLevel = false; + break; + } + + FmFormItem* pFormItem = dynamic_cast<FmFormItem*>( rpChild.get() ); + if (pFormItem) + { + EnsureEmptyFilterRows( *pFormItem ); + continue; + } + } + + if ( bAppendLevel ) + { + FmFormItem* pFormItem = dynamic_cast<FmFormItem*>( &_rItem ); + OSL_ENSURE( pFormItem, "FmFilterModel::EnsureEmptyFilterRows: no FmFormItem, but a FmFilterItems child?" ); + if ( pFormItem ) + AppendFilterItems( *pFormItem ); + } +} + +namespace { + +class FmFilterItemsString : public SvLBoxString +{ +public: + explicit FmFilterItemsString(const OUString& rStr) + : SvLBoxString(rStr) + { + } + + virtual void Paint(const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext, + const SvViewDataEntry* pView, const SvTreeListEntry& rEntry) override; + virtual void InitViewData( SvTreeListBox* pView,SvTreeListEntry* pEntry, SvViewDataItem* pViewData = nullptr) override; +}; + +} + +const int nxDBmp = 12; + +void FmFilterItemsString::Paint(const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext, + const SvViewDataEntry* /*pView*/, const SvTreeListEntry& rEntry) +{ + FmFilterItems* pRow = static_cast<FmFilterItems*>(rEntry.GetUserData()); + FmFormItem* pForm = static_cast<FmFormItem*>(pRow->GetParent()); + + // current filter is significant painted + const bool bIsCurrentFilter = pForm->GetChildren()[ pForm->GetFilterController()->getActiveTerm() ].get() == pRow; + if (bIsCurrentFilter) + { + rRenderContext.Push(PushFlags::LINECOLOR); + rRenderContext.SetLineColor(rRenderContext.GetTextColor()); + + Size aSize(GetWidth(&rDev, &rEntry), GetHeight(&rDev, &rEntry)); + tools::Rectangle aRect(rPos, aSize); + Point aFirst(rPos.X(), aRect.Bottom() - 6); + Point aSecond(aFirst .X() + 2, aFirst.Y() + 3); + + rRenderContext.DrawLine(aFirst, aSecond); + + aFirst = aSecond; + aFirst.AdjustX(1 ); + aSecond.AdjustX(6 ); + aSecond.AdjustY( -5 ); + + rRenderContext.DrawLine(aFirst, aSecond); + rRenderContext.Pop(); + } + + rRenderContext.DrawText(Point(rPos.X() + nxDBmp, rPos.Y()), GetText()); +} + + +void FmFilterItemsString::InitViewData( SvTreeListBox* pView,SvTreeListEntry* pEntry, SvViewDataItem* pViewData) +{ + if( !pViewData ) + pViewData = pView->GetViewDataItem( pEntry, this ); + + Size aSize(pView->GetTextWidth(GetText()), pView->GetTextHeight()); + aSize.AdjustWidth(nxDBmp ); + pViewData->mnWidth = aSize.Width(); + pViewData->mnHeight = aSize.Height(); +} + +namespace { + +class FmFilterString : public SvLBoxString +{ + OUString m_aName; + +public: + FmFilterString( const OUString& rStr, const OUString& aName) + : SvLBoxString(rStr) + , m_aName(aName) + { + m_aName += ": "; + } + + virtual void Paint(const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext, + const SvViewDataEntry* pView, const SvTreeListEntry& rEntry) override; + virtual void InitViewData( SvTreeListBox* pView,SvTreeListEntry* pEntry, SvViewDataItem* pViewData = nullptr) override; +}; + +} + +const int nxD = 4; + +void FmFilterString::InitViewData( SvTreeListBox* pView,SvTreeListEntry* pEntry, SvViewDataItem* pViewData) +{ + if( !pViewData ) + pViewData = pView->GetViewDataItem( pEntry, this ); + + vcl::Font aOldFont( pView->GetFont()); + vcl::Font aFont( aOldFont ); + aFont.SetWeight(WEIGHT_BOLD); + pView->Control::SetFont( aFont ); + + Size aSize(pView->GetTextWidth(m_aName), pView->GetTextHeight()); + pView->Control::SetFont( aOldFont ); + aSize.AdjustWidth(pView->GetTextWidth(GetText()) + nxD ); + pViewData->mnWidth = aSize.Width(); + pViewData->mnHeight = aSize.Height(); +} + +void FmFilterString::Paint(const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext, + const SvViewDataEntry* /*pView*/, const SvTreeListEntry& /*rEntry*/) +{ + rRenderContext.Push(PushFlags::FONT); + vcl::Font aFont(rRenderContext.GetFont()); + aFont.SetWeight(WEIGHT_BOLD); + rRenderContext.SetFont(aFont); + + Point aPos(rPos); + rRenderContext.DrawText(aPos, m_aName); + + // position for the second text + aPos.AdjustX(rDev.GetTextWidth(m_aName) + nxD ); + rRenderContext.Pop(); + rDev.DrawText(aPos, GetText()); +} + +FmFilterNavigator::FmFilterNavigator( vcl::Window* pParent ) + :SvTreeListBox( pParent, WB_HASBUTTONS|WB_HASLINES|WB_BORDER|WB_HASBUTTONSATROOT ) + ,m_pEditingCurrently( nullptr ) + ,m_aControlExchange( this ) + ,m_aTimerCounter( 0 ) + ,m_aDropActionType( DA_SCROLLUP ) +{ + SetHelpId( HID_FILTER_NAVIGATOR ); + + SetNodeBitmaps( + Image(StockImage::Yes, RID_SVXBMP_COLLAPSEDNODE), + Image(StockImage::Yes, RID_SVXBMP_EXPANDEDNODE) + ); + + m_pModel.reset( new FmFilterModel() ); + StartListening( *m_pModel ); + + EnableInplaceEditing( true ); + SetSelectionMode(SelectionMode::Multiple); + + SetDragDropMode(DragDropMode::ALL); + + m_aDropActionTimer.SetInvokeHandler(LINK(this, FmFilterNavigator, OnDropActionTimer)); +} + + +FmFilterNavigator::~FmFilterNavigator() +{ + disposeOnce(); +} + +void FmFilterNavigator::dispose() +{ + EndListening( *m_pModel ); + m_pModel.reset(); + SvTreeListBox::dispose(); +} + + +void FmFilterNavigator::UpdateContent(const Reference< XIndexAccess > & xControllers, const Reference< XFormController > & xCurrent) +{ + if (xCurrent == m_pModel->GetCurrentController()) + return; + + m_pModel->Update(xControllers, xCurrent); + + // expand the filters for the current controller + SvTreeListEntry* pEntry = FindEntry(m_pModel->GetCurrentForm()); + if (pEntry && !IsExpanded(pEntry)) + { + SelectAll(false); + + if (!IsExpanded(pEntry)) + Expand(pEntry); + + pEntry = FindEntry(m_pModel->GetCurrentItems()); + if (pEntry) + { + if (!IsExpanded(pEntry)) + Expand(pEntry); + Select(pEntry); + } + } +} + + +bool FmFilterNavigator::EditingEntry( SvTreeListEntry* pEntry, Selection& rSelection ) +{ + m_pEditingCurrently = pEntry; + if (!SvTreeListBox::EditingEntry( pEntry, rSelection )) + return false; + + return pEntry && dynamic_cast<const FmFilterItem*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr; +} + + +bool FmFilterNavigator::EditedEntry( SvTreeListEntry* pEntry, const OUString& rNewText ) +{ + DBG_ASSERT(pEntry == m_pEditingCurrently, "FmFilterNavigator::EditedEntry: suspicious entry!"); + m_pEditingCurrently = nullptr; + + if (EditingCanceled()) + return true; + + DBG_ASSERT(dynamic_cast<const FmFilterItem*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr, + "FmFilterNavigator::EditedEntry() wrong entry"); + + OUString aText(comphelper::string::strip(rNewText, ' ')); + if (aText.isEmpty()) + { + // deleting the entry asynchron + PostUserEvent(LINK(this, FmFilterNavigator, OnRemove), pEntry, true); + } + else + { + OUString aErrorMsg; + + if (m_pModel->ValidateText(static_cast<FmFilterItem*>(pEntry->GetUserData()), aText, aErrorMsg)) + { + GrabFocus(); + // this will set the text at the FmFilterItem, as well as update any filter controls + // which are connected to this particular entry + m_pModel->SetTextForItem( static_cast< FmFilterItem* >( pEntry->GetUserData() ), aText ); + + SetCursor( pEntry, true ); + SetEntryText( pEntry, aText ); + } + else + { + // display the error and return sal_False + SQLContext aError; + aError.Message = SvxResId(RID_STR_SYNTAXERROR); + aError.Details = aErrorMsg; + displayException(aError, this); + + return false; + } + } + return true; +} + + +IMPL_LINK( FmFilterNavigator, OnRemove, void*, p, void ) +{ + SvTreeListEntry* pEntry = static_cast<SvTreeListEntry*>(p); + // now remove the entry + m_pModel->Remove(static_cast<FmFilterData*>(pEntry->GetUserData())); +} + + +IMPL_LINK_NOARG(FmFilterNavigator, OnDropActionTimer, Timer *, void) +{ + if (--m_aTimerCounter > 0) + return; + + switch (m_aDropActionType) + { + case DA_SCROLLUP : + ScrollOutputArea(1); + m_aTimerCounter = DROP_ACTION_TIMER_SCROLL_TICKS; + break; + case DA_SCROLLDOWN : + ScrollOutputArea(-1); + m_aTimerCounter = DROP_ACTION_TIMER_SCROLL_TICKS; + break; + case DA_EXPANDNODE: + { + SvTreeListEntry* pToExpand = GetEntry(m_aTimerTriggered); + if (pToExpand && (GetChildCount(pToExpand) > 0) && !IsExpanded(pToExpand)) + Expand(pToExpand); + m_aDropActionTimer.Stop(); + } + break; + } +} + + +sal_Int8 FmFilterNavigator::AcceptDrop( const AcceptDropEvent& rEvt ) +{ + Point aDropPos = rEvt.maPosPixel; + + // possible DropActions scroll and expand + if (rEvt.mbLeaving) + { + if (m_aDropActionTimer.IsActive()) + m_aDropActionTimer.Stop(); + } + else + { + bool bNeedTrigger = false; + // first entry ? + if ((aDropPos.Y() >= 0) && (aDropPos.Y() < GetEntryHeight())) + { + m_aDropActionType = DA_SCROLLUP; + bNeedTrigger = true; + } + else + { + if ((aDropPos.Y() < GetSizePixel().Height()) && (aDropPos.Y() >= GetSizePixel().Height() - GetEntryHeight())) + { + m_aDropActionType = DA_SCROLLDOWN; + bNeedTrigger = true; + } + else + { // is it an entry with children, and not yet expanded? + SvTreeListEntry* pDroppedOn = GetEntry(aDropPos); + if (pDroppedOn && (GetChildCount(pDroppedOn) > 0) && !IsExpanded(pDroppedOn)) + { + // -> expand + m_aDropActionType = DA_EXPANDNODE; + bNeedTrigger = true; + } + } + } + if (bNeedTrigger && (m_aTimerTriggered != aDropPos)) + { + m_aTimerCounter = DROP_ACTION_TIMER_INITIAL_TICKS; + // remember DropPos because there are QueryDrops even though the mouse was not moved + m_aTimerTriggered = aDropPos; + if (!m_aDropActionTimer.IsActive()) + { + m_aDropActionTimer.SetTimeout(DROP_ACTION_TIMER_TICK_BASE); + m_aDropActionTimer.Start(); + } + } + else if (!bNeedTrigger) + m_aDropActionTimer.Stop(); + } + + if (!m_aControlExchange.isDragSource()) + return DND_ACTION_NONE; + + if (!OFilterItemExchange::hasFormat(GetDataFlavorExVector())) + return DND_ACTION_NONE; + + // do we contain the formitem? + if (!FindEntry(m_aControlExchange->getFormItem())) + return DND_ACTION_NONE; + + SvTreeListEntry* pDropTarget = GetEntry(aDropPos); + if (!pDropTarget) + return DND_ACTION_NONE; + + FmFilterData* pData = static_cast<FmFilterData*>(pDropTarget->GetUserData()); + FmFormItem* pForm = nullptr; + if (dynamic_cast<const FmFilterItem*>(pData) != nullptr) + { + pForm = dynamic_cast<FmFormItem*>( pData->GetParent()->GetParent() ); + if (pForm != m_aControlExchange->getFormItem()) + return DND_ACTION_NONE; + } + else if (dynamic_cast<const FmFilterItems*>( pData) != nullptr) + { + pForm = dynamic_cast<FmFormItem*>( pData->GetParent() ); + if (pForm != m_aControlExchange->getFormItem()) + return DND_ACTION_NONE; + } + else + return DND_ACTION_NONE; + + return rEvt.mnAction; +} + +namespace +{ + FmFilterItems* getTargetItems(SvTreeListEntry const * _pTarget) + { + FmFilterData* pData = static_cast<FmFilterData*>(_pTarget->GetUserData()); + FmFilterItems* pTargetItems = dynamic_cast<FmFilterItems*>(pData); + if (!pTargetItems) + pTargetItems = dynamic_cast<FmFilterItems*>(pData->GetParent()); + return pTargetItems; + } +} + +sal_Int8 FmFilterNavigator::ExecuteDrop( const ExecuteDropEvent& rEvt ) +{ + // you can't scroll after dropping... + if (m_aDropActionTimer.IsActive()) + m_aDropActionTimer.Stop(); + + if (!m_aControlExchange.isDragSource()) + return DND_ACTION_NONE; + + Point aDropPos = rEvt.maPosPixel; + SvTreeListEntry* pDropTarget = GetEntry( aDropPos ); + if (!pDropTarget) + return DND_ACTION_NONE; + + // search the container where to add the items + FmFilterItems* pTargetItems = getTargetItems(pDropTarget); + SelectAll(false); + SvTreeListEntry* pEntry = FindEntry(pTargetItems); + Select(pEntry); + SetCurEntry(pEntry); + + insertFilterItem(m_aControlExchange->getDraggedEntries(),pTargetItems,DND_ACTION_COPY == rEvt.mnAction); + + return DND_ACTION_COPY; +} + + +void FmFilterNavigator::InitEntry(SvTreeListEntry* pEntry, + const OUString& rStr, + const Image& rImg1, + const Image& rImg2) +{ + SvTreeListBox::InitEntry( pEntry, rStr, rImg1, rImg2 ); + std::unique_ptr<SvLBoxString> pString; + + if (dynamic_cast<const FmFilterItem*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr) + pString.reset(new FmFilterString(rStr, + static_cast<FmFilterItem*>(pEntry->GetUserData())->GetFieldName())); + else if (dynamic_cast<const FmFilterItems*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr) + pString.reset(new FmFilterItemsString(rStr)); + + if (pString) + pEntry->ReplaceItem(std::move(pString), 1 ); +} + + +bool FmFilterNavigator::Select( SvTreeListEntry* pEntry, bool bSelect ) +{ + if (bSelect == IsSelected(pEntry)) // This happens sometimes. I think the basic class errs too much on the side of caution. ;) + return true; + + if (SvTreeListBox::Select(pEntry, bSelect)) + { + if (bSelect) + { + FmFormItem* pFormItem = nullptr; + if ( dynamic_cast<const FmFilterItem*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr) + pFormItem = static_cast<FmFormItem*>(static_cast<FmFilterItem*>(pEntry->GetUserData())->GetParent()->GetParent()); + else if (dynamic_cast<const FmFilterItems*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr) + pFormItem = static_cast<FmFormItem*>(static_cast<FmFilterItem*>(pEntry->GetUserData())->GetParent()->GetParent()); + else if (dynamic_cast<const FmFormItem*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr) + pFormItem = static_cast<FmFormItem*>(pEntry->GetUserData()); + + if (pFormItem) + { + // will the controller be exchanged? + if (dynamic_cast<const FmFilterItem*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr) + m_pModel->SetCurrentItems(static_cast<FmFilterItems*>(static_cast<FmFilterItem*>(pEntry->GetUserData())->GetParent())); + else if (dynamic_cast<const FmFilterItems*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr) + m_pModel->SetCurrentItems(static_cast<FmFilterItems*>(pEntry->GetUserData())); + else if (dynamic_cast<const FmFormItem*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr) + m_pModel->SetCurrentController(static_cast<FmFormItem*>(pEntry->GetUserData())->GetController()); + } + } + return true; + } + else + return false; +} + +void FmFilterNavigator::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint ) +{ + if (const FmFilterInsertedHint* pInsertHint = dynamic_cast<const FmFilterInsertedHint*>(&rHint)) + { + Insert(pInsertHint->GetData(), pInsertHint->GetPos()); + } + else if( dynamic_cast<const FilterClearingHint*>(&rHint) ) + { + SvTreeListBox::Clear(); + } + else if (const FmFilterRemovedHint* pRemoveHint = dynamic_cast<const FmFilterRemovedHint*>(&rHint)) + { + Remove(pRemoveHint->GetData()); + } + else if (const FmFilterTextChangedHint *pChangeHint = dynamic_cast<const FmFilterTextChangedHint*>(&rHint)) + { + SvTreeListEntry* pEntry = FindEntry(pChangeHint->GetData()); + if (pEntry) + SetEntryText( pEntry, pChangeHint->GetData()->GetText()); + } + else if( dynamic_cast<const FmFilterCurrentChangedHint*>(&rHint) ) + { + // invalidate the entries + for (SvTreeListEntry* pEntry = First(); pEntry != nullptr; + pEntry = Next(pEntry)) + GetModel()->InvalidateEntry( pEntry ); + } +} + +SvTreeListEntry* FmFilterNavigator::FindEntry(const FmFilterData* pItem) const +{ + SvTreeListEntry* pEntry = nullptr; + if (pItem) + { + for (pEntry = First(); pEntry != nullptr; pEntry = Next( pEntry )) + { + FmFilterData* pEntryItem = static_cast<FmFilterData*>(pEntry->GetUserData()); + if (pEntryItem == pItem) + break; + } + } + return pEntry; +} + + +void FmFilterNavigator::Insert(FmFilterData* pItem, sal_uLong nPos) +{ + const FmParentData* pParent = pItem->GetParent() ? pItem->GetParent() : m_pModel.get(); + + // insert the item + SvTreeListEntry* pParentEntry = FindEntry( pParent ); + InsertEntry( pItem->GetText(), pItem->GetImage(), pItem->GetImage(), pParentEntry, false, nPos, pItem ); + if ( pParentEntry ) + Expand( pParentEntry ); +} + + +void FmFilterNavigator::Remove(FmFilterData const * pItem) +{ + // the entry for the data + SvTreeListEntry* pEntry = FindEntry(pItem); + + if (pEntry == m_pEditingCurrently) + // cancel editing + EndEditing(true); + + if (pEntry) + GetModel()->Remove( pEntry ); +} + +FmFormItem* FmFilterNavigator::getSelectedFilterItems(::std::vector<FmFilterItem*>& _rItemList) +{ + // be sure that the data is only used within only one form! + FmFormItem* pFirstItem = nullptr; + + bool bHandled = true; + bool bFoundSomething = false; + for (SvTreeListEntry* pEntry = FirstSelected(); + bHandled && pEntry != nullptr; + pEntry = NextSelected(pEntry)) + { + FmFilterItem* pFilter = dynamic_cast<FmFilterItem*>( static_cast<FmFilterData*>(pEntry->GetUserData()) ); + if (pFilter) + { + FmFormItem* pForm = dynamic_cast<FmFormItem*>( pFilter->GetParent()->GetParent() ); + if (!pForm) + bHandled = false; + else if (!pFirstItem) + pFirstItem = pForm; + else if (pFirstItem != pForm) + bHandled = false; + + if (bHandled) + { + _rItemList.push_back(pFilter); + bFoundSomething = true; + } + } + } + if ( !bHandled || !bFoundSomething ) + pFirstItem = nullptr; + return pFirstItem; +} + +void FmFilterNavigator::insertFilterItem(const ::std::vector<FmFilterItem*>& _rFilterList,FmFilterItems* _pTargetItems,bool _bCopy) +{ + for (FmFilterItem* pLookupItem : _rFilterList) + { + if ( pLookupItem->GetParent() == _pTargetItems ) + continue; + + FmFilterItem* pFilterItem = _pTargetItems->Find( pLookupItem->GetComponentIndex() ); + OUString aText = pLookupItem->GetText(); + if ( !pFilterItem ) + { + pFilterItem = new FmFilterItem( _pTargetItems, pLookupItem->GetFieldName(), aText, pLookupItem->GetComponentIndex() ); + m_pModel->Append( _pTargetItems, std::unique_ptr<FmFilterItem>(pFilterItem) ); + } + + if ( !_bCopy ) + m_pModel->Remove( pLookupItem ); + + // now set the text for the new dragged item + m_pModel->SetTextForItem( pFilterItem, aText ); + } + + m_pModel->EnsureEmptyFilterRows( *_pTargetItems->GetParent() ); +} + + +void FmFilterNavigator::StartDrag( sal_Int8 /*_nAction*/, const Point& /*_rPosPixel*/ ) +{ + EndSelection(); + + // be sure that the data is only used within an only one form! + m_aControlExchange.prepareDrag(); + + ::std::vector<FmFilterItem*> aItemList; + if ( FmFormItem* pFirstItem = getSelectedFilterItems(aItemList) ) + { + m_aControlExchange->setDraggedEntries(aItemList); + m_aControlExchange->setFormItem(pFirstItem); + m_aControlExchange.startDrag( DND_ACTION_COPYMOVE ); + } +} + + +void FmFilterNavigator::Command( const CommandEvent& rEvt ) +{ + bool bHandled = false; + switch (rEvt.GetCommand()) + { + case CommandEventId::ContextMenu: + { + // the place where it was clicked + Point aWhere; + SvTreeListEntry* pClicked = nullptr; + if (rEvt.IsMouseEvent()) + { + aWhere = rEvt.GetMousePosPixel(); + pClicked = GetEntry(aWhere); + if (pClicked == nullptr) + break; + + if (!IsSelected(pClicked)) + { + SelectAll(false); + Select(pClicked); + SetCurEntry(pClicked); + } + } + else + { + pClicked = GetCurEntry(); + if (!pClicked) + break; + aWhere = GetEntryPosition( pClicked ); + } + + ::std::vector<FmFilterData*> aSelectList; + for (SvTreeListEntry* pEntry = FirstSelected(); + pEntry != nullptr; + pEntry = NextSelected(pEntry)) + { + // don't delete forms + FmFormItem* pForm = dynamic_cast<FmFormItem*>( static_cast<FmFilterData*>(pEntry->GetUserData()) ); + if (!pForm) + aSelectList.push_back(static_cast<FmFilterData*>(pEntry->GetUserData())); + } + if (aSelectList.size() == 1) + { + // don't delete the only empty row of a form + FmFilterItems* pFilterItems = dynamic_cast<FmFilterItems*>( aSelectList[0] ); + if (pFilterItems && pFilterItems->GetChildren().empty() + && pFilterItems->GetParent()->GetChildren().size() == 1) + aSelectList.clear(); + } + + VclBuilder aBuilder(nullptr, VclBuilderContainer::getUIRootDir(), "svx/ui/filtermenu.ui", ""); + VclPtr<PopupMenu> aContextMenu(aBuilder.get_menu("menu")); + + // every condition could be deleted except the first one if it's the only one + aContextMenu->EnableItem(aContextMenu->GetItemId("delete"), !aSelectList.empty()); + + + bool bEdit = dynamic_cast<FmFilterItem*>( static_cast<FmFilterData*>(pClicked->GetUserData()) ) != nullptr && + IsSelected(pClicked) && GetSelectionCount() == 1; + + aContextMenu->EnableItem(aContextMenu->GetItemId("edit"), bEdit); + aContextMenu->EnableItem(aContextMenu->GetItemId("isnull"), bEdit); + aContextMenu->EnableItem(aContextMenu->GetItemId("isnotnull"), bEdit); + + aContextMenu->RemoveDisabledEntries(true, true); + aContextMenu->Execute(this, aWhere); + OString sIdent = aContextMenu->GetCurItemIdent(); + if (sIdent == "edit") + EditEntry( pClicked ); + else if (sIdent == "isnull") + { + OUString aErrorMsg; + OUString aText = "IS NULL"; + m_pModel->ValidateText(static_cast<FmFilterItem*>(pClicked->GetUserData()), + aText, aErrorMsg); + m_pModel->SetTextForItem(static_cast<FmFilterItem*>(pClicked->GetUserData()), aText); + } + else if (sIdent == "isnotnull") + { + OUString aErrorMsg; + OUString aText = "IS NOT NULL"; + + m_pModel->ValidateText(static_cast<FmFilterItem*>(pClicked->GetUserData()), + aText, aErrorMsg); + m_pModel->SetTextForItem(static_cast<FmFilterItem*>(pClicked->GetUserData()), aText); + } + else if (sIdent == "delete") + { + DeleteSelection(); + } + bHandled = true; + } + break; + default: break; + } + + if (!bHandled) + SvTreeListBox::Command( rEvt ); +} + +SvTreeListEntry* FmFilterNavigator::getNextEntry(SvTreeListEntry* _pStartWith) +{ + SvTreeListEntry* pEntry = _pStartWith ? _pStartWith : LastSelected(); + pEntry = Next(pEntry); + // we need the next filter entry + while( pEntry && GetChildCount( pEntry ) == 0 && pEntry != Last() ) + pEntry = Next(pEntry); + return pEntry; +} + +SvTreeListEntry* FmFilterNavigator::getPrevEntry(SvTreeListEntry* _pStartWith) +{ + SvTreeListEntry* pEntry = _pStartWith ? _pStartWith : FirstSelected(); + pEntry = Prev(pEntry); + // check if the previous entry is a filter, if so get the next prev + if ( pEntry && GetChildCount( pEntry ) != 0 ) + { + pEntry = Prev(pEntry); + // if the entry is still no leaf return + if ( pEntry && GetChildCount( pEntry ) != 0 ) + pEntry = nullptr; + } + return pEntry; +} + +void FmFilterNavigator::KeyInput(const KeyEvent& rKEvt) +{ + const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); + + switch ( rKeyCode.GetCode() ) + { + case KEY_UP: + case KEY_DOWN: + { + if ( !rKeyCode.IsMod1() || !rKeyCode.IsMod2() || rKeyCode.IsShift() ) + break; + + ::std::vector<FmFilterItem*> aItemList; + if ( !getSelectedFilterItems( aItemList ) ) + break; + + ::std::function<SvTreeListEntry*(FmFilterNavigator *, SvTreeListEntry*)> getter = ::std::mem_fn(&FmFilterNavigator::getNextEntry); + if ( rKeyCode.GetCode() == KEY_UP ) + getter = ::std::mem_fn(&FmFilterNavigator::getPrevEntry); + + SvTreeListEntry* pTarget = getter( this, nullptr ); + if ( !pTarget ) + break; + + FmFilterItems* pTargetItems = getTargetItems( pTarget ); + if ( !pTargetItems ) + break; + + ::std::vector<FmFilterItem*>::const_iterator aEnd = aItemList.end(); + bool bNextTargetItem = true; + while ( bNextTargetItem ) + { + ::std::vector<FmFilterItem*>::const_iterator i = aItemList.begin(); + for (; i != aEnd; ++i) + { + if ( (*i)->GetParent() == pTargetItems ) + { + pTarget = getter(this,pTarget); + if ( !pTarget ) + return; + pTargetItems = getTargetItems( pTarget ); + break; + } + else + { + FmFilterItem* pFilterItem = pTargetItems->Find( (*i)->GetComponentIndex() ); + // we found the text component so jump above + if ( pFilterItem ) + { + pTarget = getter( this, pTarget ); + if ( !pTarget ) + return; + + pTargetItems = getTargetItems( pTarget ); + break; + } + } + } + bNextTargetItem = i != aEnd && pTargetItems; + } + + if ( pTargetItems ) + { + insertFilterItem( aItemList, pTargetItems, false ); + return; + } + } + break; + + case KEY_DELETE: + { + if ( rKeyCode.GetModifier() ) + break; + + if ( !IsSelected( First() ) || GetEntryCount() > 1 ) + DeleteSelection(); + return; + } + } + + SvTreeListBox::KeyInput(rKEvt); +} + + +void FmFilterNavigator::DeleteSelection() +{ + // to avoid the deletion of an entry twice (e.g. deletion of a parent and afterward + // the deletion of its child, I have to shrink the selection list + ::std::vector<SvTreeListEntry*> aEntryList; + for (SvTreeListEntry* pEntry = FirstSelected(); + pEntry != nullptr; + pEntry = NextSelected(pEntry)) + { + FmFilterItem* pFilterItem = dynamic_cast<FmFilterItem*>( static_cast<FmFilterData*>(pEntry->GetUserData()) ); + if (pFilterItem && IsSelected(GetParent(pEntry))) + continue; + + FmFormItem* pForm = dynamic_cast<FmFormItem*>( static_cast<FmFilterData*>(pEntry->GetUserData()) ); + if (!pForm) + aEntryList.push_back(pEntry); + } + + // Remove the selection + SelectAll(false); + + for (::std::vector<SvTreeListEntry*>::reverse_iterator i = aEntryList.rbegin(); + // link problems with operator == + i.base() != aEntryList.rend().base(); ++i) + { + m_pModel->Remove(static_cast<FmFilterData*>((*i)->GetUserData())); + } +} + +FmFilterNavigatorWin::FmFilterNavigatorWin( SfxBindings* _pBindings, SfxChildWindow* _pMgr, + vcl::Window* _pParent ) + :SfxDockingWindow( _pBindings, _pMgr, _pParent, WinBits(WB_STDMODELESS|WB_SIZEABLE|WB_ROLLABLE|WB_3DLOOK|WB_DOCKABLE) ) + ,SfxControllerItem( SID_FM_FILTER_NAVIGATOR_CONTROL, *_pBindings ) +{ + SetHelpId( HID_FILTER_NAVIGATOR_WIN ); + + m_pNavigator = VclPtr<FmFilterNavigator>::Create( this ); + m_pNavigator->Show(); + SetText( SvxResId(RID_STR_FILTER_NAVIGATOR) ); + SfxDockingWindow::SetFloatingSize( Size(200,200) ); +} + + +FmFilterNavigatorWin::~FmFilterNavigatorWin() +{ + disposeOnce(); +} + +void FmFilterNavigatorWin::dispose() +{ + m_pNavigator.disposeAndClear(); + ::SfxControllerItem::dispose(); + SfxDockingWindow::dispose(); +} + + +void FmFilterNavigatorWin::UpdateContent(FmFormShell const * pFormShell) +{ + if (!m_pNavigator) + return; + + if (!pFormShell) + m_pNavigator->UpdateContent( nullptr, nullptr ); + else + { + Reference<XFormController> const xController(pFormShell->GetImpl()->getActiveInternalController_Lock()); + Reference< XIndexAccess > xContainer; + if (xController.is()) + { + Reference< XChild > xChild = xController; + for (Reference< XInterface > xParent(xChild->getParent()); + xParent.is(); + xParent = xChild.is() ? xChild->getParent() : Reference< XInterface > ()) + { + xContainer.set(xParent, UNO_QUERY); + xChild.set(xParent, UNO_QUERY); + } + } + m_pNavigator->UpdateContent(xContainer, xController); + } +} + + +void FmFilterNavigatorWin::StateChanged( sal_uInt16 nSID, SfxItemState eState, const SfxPoolItem* pState ) +{ + if( !pState || SID_FM_FILTER_NAVIGATOR_CONTROL != nSID ) + return; + + if( eState >= SfxItemState::DEFAULT ) + { + FmFormShell* pShell = dynamic_cast<FmFormShell*>( static_cast<const SfxObjectItem*>(pState)->GetShell() ); + UpdateContent( pShell ); + } + else + UpdateContent( nullptr ); +} + + +bool FmFilterNavigatorWin::Close() +{ + if ( m_pNavigator && m_pNavigator->IsEditingActive() ) + m_pNavigator->EndEditing(); + + if ( m_pNavigator && m_pNavigator->IsEditingActive() ) + // the EndEditing was vetoed (perhaps of an syntax error or such) + return false; + + UpdateContent( nullptr ); + return SfxDockingWindow::Close(); +} + + +void FmFilterNavigatorWin::FillInfo( SfxChildWinInfo& rInfo ) const +{ + SfxDockingWindow::FillInfo( rInfo ); + rInfo.bVisible = false; +} + + +Size FmFilterNavigatorWin::CalcDockingSize( SfxChildAlignment eAlign ) +{ + if ( ( eAlign == SfxChildAlignment::TOP ) || ( eAlign == SfxChildAlignment::BOTTOM ) ) + return Size(); + + return SfxDockingWindow::CalcDockingSize( eAlign ); +} + + +SfxChildAlignment FmFilterNavigatorWin::CheckAlignment( SfxChildAlignment eActAlign, SfxChildAlignment eAlign ) +{ + switch (eAlign) + { + case SfxChildAlignment::LEFT: + case SfxChildAlignment::RIGHT: + case SfxChildAlignment::NOALIGNMENT: + return eAlign; + default: + break; + } + + return eActAlign; +} + + +void FmFilterNavigatorWin::Resize() +{ + SfxDockingWindow::Resize(); + + Size aLogOutputSize = PixelToLogic(GetOutputSizePixel(), MapMode(MapUnit::MapAppFont)); + Size aLogExplSize = aLogOutputSize; + aLogExplSize.AdjustWidth( -6 ); + aLogExplSize.AdjustHeight( -6 ); + + Point aExplPos = LogicToPixel(Point(3,3), MapMode(MapUnit::MapAppFont)); + Size aExplSize = LogicToPixel(aLogExplSize, MapMode(MapUnit::MapAppFont)); + + m_pNavigator->SetPosSizePixel( aExplPos, aExplSize ); +} + +void FmFilterNavigatorWin::GetFocus() +{ + // oj #97405# + if ( m_pNavigator ) + m_pNavigator->GrabFocus(); +} + +SFX_IMPL_DOCKINGWINDOW( FmFilterNavigatorWinMgr, SID_FM_FILTER_NAVIGATOR ) + + +FmFilterNavigatorWinMgr::FmFilterNavigatorWinMgr( vcl::Window *_pParent, sal_uInt16 _nId, + SfxBindings *_pBindings, SfxChildWinInfo* _pInfo ) + :SfxChildWindow( _pParent, _nId ) +{ + SetWindow( VclPtr<FmFilterNavigatorWin>::Create( _pBindings, this, _pParent ) ); + static_cast<SfxDockingWindow*>(GetWindow())->Initialize( _pInfo ); +} + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmPropBrw.cxx b/svx/source/form/fmPropBrw.cxx new file mode 100644 index 000000000..f77b79f40 --- /dev/null +++ b/svx/source/form/fmPropBrw.cxx @@ -0,0 +1,580 @@ +/* -*- 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 <sal/macros.h> + +#include <fmprop.hxx> +#include <fmPropBrw.hxx> +#include <svx/strings.hrc> +#include <fmservs.hxx> +#include <fmshimp.hxx> +#include <fmpgeimp.hxx> + +#include <svx/dialmgr.hxx> +#include <svx/fmpage.hxx> +#include <svx/fmshell.hxx> +#include <svx/fmview.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svxids.hrc> + +#include <com/sun/star/awt/XControlContainer.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/form/XForm.hpp> +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/form/inspection/DefaultFormComponentInspectorModel.hpp> +#include <com/sun/star/frame/Frame.hpp> +#include <com/sun/star/inspection/ObjectInspector.hpp> +#include <com/sun/star/inspection/XObjectInspectorUI.hpp> +#include <com/sun/star/inspection/DefaultHelpProvider.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/util/VetoException.hpp> + +#include <comphelper/processfactory.hxx> +#include <comphelper/property.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/types.hxx> +#include <cppuhelper/component_context.hxx> +#include <o3tl/deleter.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/childwin.hxx> +#include <sfx2/objitem.hxx> +#include <sfx2/objsh.hxx> +#include <tools/diagnose_ex.h> +#include <unotools/confignode.hxx> +#include <vcl/stdtext.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/weldutils.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::form; +using namespace ::com::sun::star::form::inspection; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::inspection; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::util; +using namespace ::svxform; +using ::com::sun::star::awt::XWindow; + +//= FmPropBrwMgr +SFX_IMPL_MODELESSDIALOGCONTOLLER(FmPropBrwMgr, SID_FM_SHOW_PROPERTIES) + +FmPropBrwMgr::FmPropBrwMgr( vcl::Window* _pParent, sal_uInt16 _nId, + SfxBindings* _pBindings, SfxChildWinInfo* _pInfo) + :SfxChildWindow(_pParent, _nId) +{ + std::shared_ptr<FmPropBrw> xControl(new FmPropBrw(::comphelper::getProcessComponentContext(), _pBindings, + this, _pParent->GetFrameWeld(), _pInfo), o3tl::default_delete<FmPropBrw>()); + SetController(std::move(xControl)); + static_cast<FmPropBrw*>(GetController().get())->Initialize( _pInfo ); +} + +static OUString GetUIHeadlineName(sal_Int16 nClassId, const Any& aUnoObj) +{ + const char* pClassNameResourceId = nullptr; + + switch ( nClassId ) + { + case FormComponentType::TEXTFIELD: + { + Reference< XInterface > xIFace; + aUnoObj >>= xIFace; + pClassNameResourceId = RID_STR_PROPTITLE_EDIT; + if (xIFace.is()) + { // we have a chance to check if it's a formatted field model + Reference< XServiceInfo > xInfo(xIFace, UNO_QUERY); + if (xInfo.is() && (xInfo->supportsService(FM_SUN_COMPONENT_FORMATTEDFIELD))) + pClassNameResourceId = RID_STR_PROPTITLE_FORMATTED; + else if (!xInfo.is()) + { + // couldn't distinguish between formatted and edit with the service name, so try with the properties + Reference< XPropertySet > xProps(xIFace, UNO_QUERY); + if (xProps.is()) + { + Reference< XPropertySetInfo > xPropsInfo = xProps->getPropertySetInfo(); + if (xPropsInfo.is() && xPropsInfo->hasPropertyByName(FM_PROP_FORMATSSUPPLIER)) + pClassNameResourceId = RID_STR_PROPTITLE_FORMATTED; + } + } + } + } + break; + + case FormComponentType::COMMANDBUTTON: + pClassNameResourceId = RID_STR_PROPTITLE_PUSHBUTTON; break; + case FormComponentType::RADIOBUTTON: + pClassNameResourceId = RID_STR_PROPTITLE_RADIOBUTTON; break; + case FormComponentType::CHECKBOX: + pClassNameResourceId = RID_STR_PROPTITLE_CHECKBOX; break; + case FormComponentType::LISTBOX: + pClassNameResourceId = RID_STR_PROPTITLE_LISTBOX; break; + case FormComponentType::COMBOBOX: + pClassNameResourceId = RID_STR_PROPTITLE_COMBOBOX; break; + case FormComponentType::GROUPBOX: + pClassNameResourceId = RID_STR_PROPTITLE_GROUPBOX; break; + case FormComponentType::IMAGEBUTTON: + pClassNameResourceId = RID_STR_PROPTITLE_IMAGEBUTTON; break; + case FormComponentType::FIXEDTEXT: + pClassNameResourceId = RID_STR_PROPTITLE_FIXEDTEXT; break; + case FormComponentType::GRIDCONTROL: + pClassNameResourceId = RID_STR_PROPTITLE_DBGRID; break; + case FormComponentType::FILECONTROL: + pClassNameResourceId = RID_STR_PROPTITLE_FILECONTROL; break; + case FormComponentType::DATEFIELD: + pClassNameResourceId = RID_STR_PROPTITLE_DATEFIELD; break; + case FormComponentType::TIMEFIELD: + pClassNameResourceId = RID_STR_PROPTITLE_TIMEFIELD; break; + case FormComponentType::NUMERICFIELD: + pClassNameResourceId = RID_STR_PROPTITLE_NUMERICFIELD; break; + case FormComponentType::CURRENCYFIELD: + pClassNameResourceId = RID_STR_PROPTITLE_CURRENCYFIELD; break; + case FormComponentType::PATTERNFIELD: + pClassNameResourceId = RID_STR_PROPTITLE_PATTERNFIELD; break; + case FormComponentType::IMAGECONTROL: + pClassNameResourceId = RID_STR_PROPTITLE_IMAGECONTROL; break; + case FormComponentType::HIDDENCONTROL: + pClassNameResourceId = RID_STR_PROPTITLE_HIDDEN; break; + case FormComponentType::SCROLLBAR: + pClassNameResourceId = RID_STR_PROPTITLE_SCROLLBAR; break; + case FormComponentType::SPINBUTTON: + pClassNameResourceId = RID_STR_PROPTITLE_SPINBUTTON; break; + case FormComponentType::NAVIGATIONBAR: + pClassNameResourceId = RID_STR_PROPTITLE_NAVBAR; break; + case FormComponentType::CONTROL: + default: + pClassNameResourceId = RID_STR_CONTROL; break; + } + + return SvxResId(pClassNameResourceId); +} + +FmPropBrw::FmPropBrw(const Reference< XComponentContext >& _xORB, SfxBindings* _pBindings, + SfxChildWindow* _pMgr, weld::Window* _pParent, const SfxChildWinInfo* _pInfo) + : SfxModelessDialogController(_pBindings, _pMgr, _pParent, "svx/ui/formpropertydialog.ui", "FormPropertyDialog") + , SfxControllerItem(SID_FM_PROPERTY_CONTROL, *_pBindings) + , m_bInitialStateChange(true) + , m_pParent(_pParent) + , m_nAsyncGetFocusId(nullptr) + , m_xContainer(m_xBuilder->weld_container("container")) + , m_xORB(_xORB) +{ + m_xContainer->set_size_request(m_xContainer->get_approximate_digit_width() * 72, m_xContainer->get_text_height() * 20); + + try + { + // create a frame wrapper for myself + m_xMeAsFrame = Frame::create(m_xORB); + + // transport the container area of this dialog to be the container window of the frame + css::uno::Reference<css::awt::XWindow> xFrameContainerWindow(new weld::TransportAsXWindow(m_xContainer.get())); + m_xMeAsFrame->initialize(xFrameContainerWindow); + m_xMeAsFrame->setName("form property browser"); + } + catch (const Exception&) + { + OSL_FAIL("FmPropBrw::FmPropBrw: could not create/initialize my frame!"); + m_xMeAsFrame.clear(); + } + + if ( _pInfo ) + m_sLastActivePage = _pInfo->aExtraString; +} + +FmPropBrw::~FmPropBrw() +{ + if (m_nAsyncGetFocusId) + { + Application::RemoveUserEvent(m_nAsyncGetFocusId); + m_nAsyncGetFocusId = nullptr; + } + + if (m_xBrowserController.is()) + implDetachController(); + try + { + // remove our own properties from the component context. We cannot ensure that the component context + // is freed (there might be refcount problems :-\), so at least ensure the context itself + // does hold the objects anymore + Reference<XNameContainer> xName(m_xInspectorContext,uno::UNO_QUERY); + if ( xName.is() ) + { + const OUString pProps[] = { OUString( "ContextDocument" ) + , OUString( "DialogParentWindow" ) + , OUString( "ControlContext" ) + , OUString( "ControlShapeAccess" ) }; + for (const auto & i : pProps) + xName->removeByName( i ); + } + } + catch (const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + ::SfxControllerItem::dispose(); +} + +OUString FmPropBrw::getCurrentPage() const +{ + OUString sCurrentPage; + try + { + if ( m_xBrowserController.is() ) + { + OSL_VERIFY( m_xBrowserController->getViewData() >>= sCurrentPage ); + } + + if ( sCurrentPage.isEmpty() ) + sCurrentPage = m_sLastActivePage; + } + catch( const Exception& ) + { + OSL_FAIL( "FmPropBrw::getCurrentPage: caught an exception while retrieving the current page!" ); + } + return sCurrentPage; +} + +void FmPropBrw::implDetachController() +{ + m_sLastActivePage = getCurrentPage(); + + implSetNewSelection( InterfaceBag() ); + + if ( m_xMeAsFrame.is() ) + { + try + { + m_xMeAsFrame->setComponent(nullptr, nullptr); + } + catch( const Exception& ) + { + OSL_FAIL( "FmPropBrw::implDetachController: caught an exception while resetting the component!" ); + } + } + + // we attached a frame to the controller manually, so we need to manually tell it that it's detached, too + if ( m_xBrowserController.is() ) + { + m_xBrowserController->attachFrame( nullptr ); + } + + m_xBrowserController.clear(); + m_xInspectorModel.clear(); + m_xMeAsFrame.clear(); +} + +void FmPropBrw::Close() +{ + // suspend the controller (it is allowed to veto) + if ( m_xMeAsFrame.is() ) + { + try + { + Reference< XController > xController( m_xMeAsFrame->getController() ); + if ( xController.is() && !xController->suspend( true ) ) + return; + } + catch( const Exception& ) + { + OSL_FAIL( "FmPropBrw::Close: caught an exception while asking the controller!" ); + } + } + + implDetachController(); + + // remember our bindings: while we're closed, we're deleted, too, so accessing the bindings after this + // would be deadly + // 10/19/00 - 79321 - FS + SfxBindings& rBindings = SfxControllerItem::GetBindings(); + + SfxModelessDialogController::Close(); + + rBindings.Invalidate(SID_FM_CTL_PROPERTIES); + rBindings.Invalidate(SID_FM_PROPERTIES); +} + +bool FmPropBrw::implIsReadOnlyModel() const +{ + try + { + if ( m_xInspectorModel.is() ) + return m_xInspectorModel->getIsReadOnly(); + return false; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return true; +} + + +void FmPropBrw::implSetNewSelection( const InterfaceBag& _rSelection ) +{ + if ( m_xBrowserController.is() ) + { + try + { + Reference< XObjectInspector > xInspector( m_xBrowserController, UNO_QUERY_THROW ); + + // tell it the objects to inspect + xInspector->inspect( comphelper::containerToSequence(_rSelection) ); + } + catch( const VetoException& ) + { + return; + } + catch( const Exception& ) + { + OSL_FAIL( "FmPropBrw::implSetNewSelection: caught an unexpected exception!" ); + return; + } + + // set the new title according to the selected object + OUString sTitle; + + if ( _rSelection.empty() ) + { + sTitle = SvxResId(RID_STR_NO_PROPERTIES); + } + else if ( _rSelection.size() > 1 ) + { + // no form component and (no form or no name) -> Multiselection + sTitle = SvxResId(RID_STR_PROPERTIES_CONTROL) + + SvxResId(RID_STR_PROPTITLE_MULTISELECT); + } + else + { + Reference< XPropertySet > xSingleSelection( *_rSelection.begin(), UNO_QUERY); + if ( ::comphelper::hasProperty( FM_PROP_CLASSID, xSingleSelection ) ) + { + sal_Int16 nClassID = FormComponentType::CONTROL; + xSingleSelection->getPropertyValue( FM_PROP_CLASSID ) >>= nClassID; + + sTitle = SvxResId(RID_STR_PROPERTIES_CONTROL) + + GetUIHeadlineName(nClassID, makeAny(xSingleSelection)); + } + else if ( Reference< XForm >( xSingleSelection, UNO_QUERY ).is() ) + sTitle = SvxResId(RID_STR_PROPERTIES_FORM); + } + + if ( implIsReadOnlyModel() ) + sTitle += SvxResId(RID_STR_READONLY_VIEW); + + m_xDialog->set_title(sTitle); + } +} + +void FmPropBrw::FillInfo( SfxChildWinInfo& rInfo ) const +{ + rInfo.bVisible = false; + rInfo.aExtraString = getCurrentPage(); +} + +IMPL_LINK_NOARG( FmPropBrw, OnAsyncGetFocus, void*, void ) +{ + m_nAsyncGetFocusId = nullptr; +} + +namespace +{ + bool lcl_shouldEnableHelpSection( const Reference< XComponentContext >& _rxContext ) + { + ::utl::OConfigurationTreeRoot aConfiguration( + ::utl::OConfigurationTreeRoot::createWithComponentContext( + _rxContext, + "/org.openoffice.Office.Common/Forms/PropertyBrowser/" ) ); + + bool bEnabled = false; + OSL_VERIFY( aConfiguration.getNodeValue( "DirectHelp" ) >>= bEnabled ); + return bEnabled; + } +} + +void FmPropBrw::impl_createPropertyBrowser_throw( FmFormShell* _pFormShell ) +{ + // the document in which we live + Reference< XInterface > xDocument; + if ( _pFormShell && _pFormShell->GetObjectShell() ) + xDocument = _pFormShell->GetObjectShell()->GetModel(); + + // the context of the controls in our document + Reference< awt::XControlContainer > xControlContext; + if ( _pFormShell && _pFormShell->GetFormView() ) + { + SdrPageView* pPageView = _pFormShell->GetFormView()->GetSdrPageView(); + + if(pPageView) + { + SdrPageWindow* pPageWindow = pPageView->GetPageWindow(0); + + if(pPageWindow) + { + xControlContext = pPageWindow->GetControlContainer(); + } + } + } + + // the default parent window for message boxes + Reference< XWindow > xParentWindow(m_xDialog->GetXWindow()); + + // the mapping from control models to control shapes + Reference< XMap > xControlMap; + FmFormPage* pFormPage = _pFormShell ? _pFormShell->GetCurPage() : nullptr; + if ( pFormPage ) + xControlMap = pFormPage->GetImpl().getControlToShapeMap(); + + // our own component context + + // a ComponentContext for the + ::cppu::ContextEntry_Init aHandlerContextInfo[] = + { + ::cppu::ContextEntry_Init( "ContextDocument", makeAny( xDocument ) ), + ::cppu::ContextEntry_Init( "DialogParentWindow", makeAny( xParentWindow ) ), + ::cppu::ContextEntry_Init( "ControlContext", makeAny( xControlContext ) ), + ::cppu::ContextEntry_Init( "ControlShapeAccess", makeAny( xControlMap ) ) + }; + m_xInspectorContext.set( + ::cppu::createComponentContext( aHandlerContextInfo, SAL_N_ELEMENTS( aHandlerContextInfo ), + m_xORB ) ); + + bool bEnableHelpSection = lcl_shouldEnableHelpSection( m_xORB ); + + // an object inspector model + m_xInspectorModel = + bEnableHelpSection + ? DefaultFormComponentInspectorModel::createWithHelpSection( m_xInspectorContext, 3, 5 ) + : DefaultFormComponentInspectorModel::createDefault( m_xInspectorContext ); + + // an object inspector + m_xBrowserController = + ObjectInspector::createWithModel( + m_xInspectorContext, m_xInspectorModel + ); + + if ( !m_xBrowserController.is() ) + { + ShowServiceNotAvailableError(m_pParent, "com.sun.star.inspection.ObjectInspector", true); + } + else + { + m_xBrowserController->attachFrame( Reference<XFrame>(m_xMeAsFrame,UNO_QUERY_THROW) ); + } + + if ( bEnableHelpSection ) + { + Reference< XObjectInspector > xInspector( m_xBrowserController, UNO_QUERY_THROW ); + Reference< XObjectInspectorUI > xInspectorUI( xInspector->getInspectorUI() ); + DefaultHelpProvider::create( m_xInspectorContext, xInspectorUI ); + } +} + + +void FmPropBrw::impl_ensurePropertyBrowser_nothrow( FmFormShell* _pFormShell ) +{ + // the document in which we live + Reference< XInterface > xDocument; + SfxObjectShell* pObjectShell = _pFormShell ? _pFormShell->GetObjectShell() : nullptr; + if ( pObjectShell ) + xDocument = pObjectShell->GetModel(); + if ( ( xDocument == m_xLastKnownDocument ) && m_xBrowserController.is() ) + // nothing to do + return; + + try + { + // clean up any previous instances of the object inspector + if ( m_xMeAsFrame.is() ) + m_xMeAsFrame->setComponent( nullptr, nullptr ); + else + ::comphelper::disposeComponent( m_xBrowserController ); + m_xBrowserController.clear(); + m_xInspectorModel.clear(); + + // and create a new one + impl_createPropertyBrowser_throw( _pFormShell ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + m_xLastKnownDocument = xDocument; +} + + +void FmPropBrw::StateChanged(sal_uInt16 nSID, SfxItemState eState, const SfxPoolItem* pState) +{ + if (!pState || SID_FM_PROPERTY_CONTROL != nSID) + return; + + try + { + if (eState >= SfxItemState::DEFAULT) + { + FmFormShell* pShell = dynamic_cast<FmFormShell*>( static_cast<const SfxObjectItem*>(pState)->GetShell() ); + InterfaceBag aSelection; + if ( pShell ) + pShell->GetImpl()->getCurrentSelection_Lock(aSelection); + + impl_ensurePropertyBrowser_nothrow( pShell ); + + // set the new object to inspect + implSetNewSelection( aSelection ); + + // if this is the first time we're here, some additional things need to be done ... + if ( m_bInitialStateChange ) + { + // if we're just newly created, we want to have the focus + m_nAsyncGetFocusId = Application::PostUserEvent(LINK(this, FmPropBrw, OnAsyncGetFocus)); + + // and additionally, we want to show the page which was active during + // our previous incarnation + if ( !m_sLastActivePage.isEmpty() ) + { + try + { + if ( m_xBrowserController.is() ) + m_xBrowserController->restoreViewData( makeAny( m_sLastActivePage ) ); + } + catch( const Exception& ) + { + OSL_FAIL( "FmPropBrw::StateChanged: caught an exception while setting the initial page!" ); + } + } + + m_bInitialStateChange = false; + } + + } + else + { + implSetNewSelection( InterfaceBag() ); + } + } + catch (Exception&) + { + OSL_FAIL("FmPropBrw::StateChanged: Exception occurred!"); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmcontrolbordermanager.cxx b/svx/source/form/fmcontrolbordermanager.cxx new file mode 100644 index 000000000..92da9708b --- /dev/null +++ b/svx/source/form/fmcontrolbordermanager.cxx @@ -0,0 +1,425 @@ +/* -*- 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 <fmcontrolbordermanager.hxx> + +#include <fmprop.hxx> + +#include <com/sun/star/form/validation/XValidatableFormComponent.hpp> +#include <com/sun/star/awt/XTextComponent.hpp> +#include <com/sun/star/awt/XListBox.hpp> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <osl/diagnose.h> + + +namespace svxform +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::awt; + using namespace ::com::sun::star::form::validation; + + + //= helper + + + static void setUnderline( const Reference< XVclWindowPeer >& _rxPeer, const UnderlineDescriptor& _rUnderline ) + { + OSL_ENSURE( _rxPeer.is(), "setUnderline: invalid peer!" ); + + // the underline type is an aspect of the font + FontDescriptor aFont; + OSL_VERIFY( _rxPeer->getProperty( FM_PROP_FONT ) >>= aFont ); + aFont.Underline = _rUnderline.nUnderlineType; + _rxPeer->setProperty( FM_PROP_FONT, makeAny( aFont ) ); + // the underline color is a separate property + _rxPeer->setProperty( FM_PROP_TEXTLINECOLOR, makeAny( _rUnderline.nUnderlineColor ) ); + } + + + static void getUnderline( const Reference< XVclWindowPeer >& _rxPeer, UnderlineDescriptor& _rUnderline ) + { + OSL_ENSURE( _rxPeer.is(), "getUnderline: invalid peer!" ); + + FontDescriptor aFont; + OSL_VERIFY( _rxPeer->getProperty( FM_PROP_FONT ) >>= aFont ); + _rUnderline.nUnderlineType = aFont.Underline; + + OSL_VERIFY( _rxPeer->getProperty( FM_PROP_TEXTLINECOLOR ) >>= _rUnderline.nUnderlineColor ); + } + + + static void getBorder( const Reference< XVclWindowPeer >& _rxPeer, BorderDescriptor& _rBorder ) + { + OSL_ENSURE( _rxPeer.is(), "getBorder: invalid peer!" ); + + OSL_VERIFY( _rxPeer->getProperty( FM_PROP_BORDER ) >>= _rBorder.nBorderType ); + OSL_VERIFY( _rxPeer->getProperty( FM_PROP_BORDERCOLOR ) >>= _rBorder.nBorderColor ); + } + + + static void setBorder( const Reference< XVclWindowPeer >& _rxPeer, const BorderDescriptor& _rBorder ) + { + OSL_ENSURE( _rxPeer.is(), "setBorder: invalid peer!" ); + + _rxPeer->setProperty( FM_PROP_BORDER, makeAny( _rBorder.nBorderType ) ); + _rxPeer->setProperty( FM_PROP_BORDERCOLOR, makeAny( _rBorder.nBorderColor ) ); + } + + ControlBorderManager::ControlBorderManager() + :m_nFocusColor ( 0x000000FF ) + ,m_nMouseHoveColor( 0x007098BE ) + ,m_nInvalidColor ( 0x00FF0000 ) + ,m_bDynamicBorderColors( false ) + { + } + + + ControlBorderManager::~ControlBorderManager() + { + } + + + bool ControlBorderManager::canColorBorder( const Reference< XVclWindowPeer >& _rxPeer ) + { + OSL_PRECOND( _rxPeer.is(), "ControlBorderManager::canColorBorder: invalid peer!" ); + + PeerBag::const_iterator aPos = m_aColorableControls.find( _rxPeer ); + if ( aPos != m_aColorableControls.end() ) + return true; + + aPos = m_aNonColorableControls.find( _rxPeer ); + if ( aPos != m_aNonColorableControls.end() ) + return false; + + // this peer is not yet known + + // no border coloring for controls which are not for text input + // #i37434# / 2004-11-19 / frank.schoenheit@sun.com + Reference< XTextComponent > xText( _rxPeer, UNO_QUERY ); + Reference< XListBox > xListBox( _rxPeer, UNO_QUERY ); + if ( xText.is() || xListBox.is() ) + { + sal_Int16 nBorderStyle = VisualEffect::NONE; + OSL_VERIFY( _rxPeer->getProperty( FM_PROP_BORDER ) >>= nBorderStyle ); + if ( nBorderStyle == VisualEffect::FLAT ) + // if you change this to also accept LOOK3D, then this would also work, but look ugly + { + m_aColorableControls.insert( _rxPeer ); + return true; + } + } + + m_aNonColorableControls.insert( _rxPeer ); + return false; + } + + + ControlStatus ControlBorderManager::getControlStatus( const Reference< XControl >& _rxControl ) + { + ControlStatus nStatus = ControlStatus::NONE; + + if ( _rxControl.get() == m_aFocusControl.xControl.get() ) + nStatus |= ControlStatus::Focused; + + if ( _rxControl.get() == m_aMouseHoverControl.xControl.get() ) + nStatus |= ControlStatus::MouseHover; + + if ( m_aInvalidControls.find( ControlData( _rxControl ) ) != m_aInvalidControls.end() ) + nStatus |= ControlStatus::Invalid; + + return nStatus; + } + + + Color ControlBorderManager::getControlColorByStatus( ControlStatus _nStatus ) + { + // "invalid" is ranked highest + if ( _nStatus & ControlStatus::Invalid ) + return m_nInvalidColor; + + // then, "focused" is more important than ... + if ( _nStatus & ControlStatus::Focused ) + return m_nFocusColor; + + // ... "mouse over" + if ( _nStatus & ControlStatus::MouseHover ) + return m_nMouseHoveColor; + + OSL_FAIL( "ControlBorderManager::getControlColorByStatus: invalid status!" ); + return Color(0); + } + + + void ControlBorderManager::updateBorderStyle( const Reference< XControl >& _rxControl, const Reference< XVclWindowPeer >& _rxPeer, const BorderDescriptor& _rFallback ) + { + OSL_PRECOND( _rxControl.is() && _rxPeer.is(), "ControlBorderManager::updateBorderStyle: invalid parameters!" ); + + ControlStatus nStatus = getControlStatus( _rxControl ); + BorderDescriptor aBorder; + aBorder.nBorderType = ( nStatus == ControlStatus::NONE ) + ? _rFallback.nBorderType + : VisualEffect::FLAT; + aBorder.nBorderColor = ( nStatus == ControlStatus::NONE ) + ? _rFallback.nBorderColor + : getControlColorByStatus( nStatus ); + setBorder( _rxPeer, aBorder ); + } + + + void ControlBorderManager::determineOriginalBorderStyle( const Reference< XControl >& _rxControl, BorderDescriptor& _rData ) const + { + _rData = ControlData(); + if ( m_aFocusControl.xControl.get() == _rxControl.get() ) + { + _rData = m_aFocusControl; + } + else if ( m_aMouseHoverControl.xControl.get() == _rxControl.get() ) + { + _rData = m_aMouseHoverControl; + } + else + { + ControlBag::const_iterator aPos = m_aInvalidControls.find( _rxControl ); + if ( aPos != m_aInvalidControls.end() ) + { + _rData = *aPos; + } + else + { + Reference< XVclWindowPeer > xPeer( _rxControl->getPeer(), UNO_QUERY ); + getBorder( xPeer, _rData ); + } + } + } + + + void ControlBorderManager::controlStatusGained( const Reference< XInterface >& _rxControl, ControlData& _rControlData ) + { + if ( _rxControl == _rControlData.xControl ) + // nothing to do - though suspicious + return; + + Reference< XControl > xAsControl( _rxControl, UNO_QUERY ); + DBG_ASSERT( xAsControl.is(), "ControlBorderManager::controlStatusGained: invalid control!" ); + if ( !xAsControl.is() ) + return; + + try + { + Reference< XVclWindowPeer > xPeer( xAsControl->getPeer(), UNO_QUERY ); + if ( xPeer.is() && canColorBorder( xPeer ) ) + { + // remember the control and its current border color + _rControlData.xControl.clear(); // so determineOriginalBorderStyle doesn't get confused + + determineOriginalBorderStyle( xAsControl, _rControlData ); + + _rControlData.xControl = xAsControl; + + updateBorderStyle( xAsControl, xPeer, _rControlData ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "ControlBorderManager::controlStatusGained" ); + } + } + + + void ControlBorderManager::controlStatusLost( const Reference< XInterface >& _rxControl, ControlData& _rControlData ) + { + if ( _rxControl != _rControlData.xControl ) + // nothing to do + return; + + OSL_PRECOND( _rControlData.xControl.is(), "ControlBorderManager::controlStatusLost: invalid control data - this will crash!" ); + try + { + Reference< XVclWindowPeer > xPeer( _rControlData.xControl->getPeer(), UNO_QUERY ); + if ( xPeer.is() && canColorBorder( xPeer ) ) + { + ControlData aPreviousStatus( _rControlData ); + _rControlData = ControlData(); + updateBorderStyle( aPreviousStatus.xControl, xPeer, aPreviousStatus ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "ControlBorderManager::controlStatusLost" ); + } + } + + + void ControlBorderManager::enableDynamicBorderColor( ) + { + m_bDynamicBorderColors = true; + } + + + void ControlBorderManager::disableDynamicBorderColor( ) + { + m_bDynamicBorderColors = false; + restoreAll(); + } + + + void ControlBorderManager::setStatusColor( ControlStatus _nStatus, Color _nColor ) + { + switch ( _nStatus ) + { + case ControlStatus::Focused: + m_nFocusColor = _nColor; + break; + case ControlStatus::MouseHover: + m_nMouseHoveColor = _nColor; + break; + case ControlStatus::Invalid: + m_nInvalidColor = _nColor; + break; + default: + OSL_FAIL( "ControlBorderManager::setStatusColor: invalid status!" ); + } + } + + + void ControlBorderManager::restoreAll() + { + if ( m_aFocusControl.xControl.is() ) + controlStatusLost( m_aFocusControl.xControl, m_aFocusControl ); + if ( m_aMouseHoverControl.xControl.is() ) + controlStatusLost( m_aMouseHoverControl.xControl, m_aMouseHoverControl ); + + ControlBag aInvalidControls; + m_aInvalidControls.swap( aInvalidControls ); + + for (const auto& rControl : aInvalidControls) + { + Reference< XVclWindowPeer > xPeer( rControl.xControl->getPeer(), UNO_QUERY ); + if ( xPeer.is() ) + { + updateBorderStyle( rControl.xControl, xPeer, rControl ); + xPeer->setProperty( FM_PROP_HELPTEXT, makeAny( rControl.sOriginalHelpText ) ); + setUnderline( xPeer, rControl ); + } + } + } + + + void ControlBorderManager::focusGained( const Reference< XInterface >& _rxControl ) + { + if ( m_bDynamicBorderColors ) + controlStatusGained( _rxControl, m_aFocusControl ); + } + + + void ControlBorderManager::focusLost( const Reference< XInterface >& _rxControl ) + { + if ( m_bDynamicBorderColors ) + controlStatusLost( _rxControl, m_aFocusControl ); + } + + + void ControlBorderManager::mouseEntered( const Reference< XInterface >& _rxControl ) + { + if ( m_bDynamicBorderColors ) + controlStatusGained( _rxControl, m_aMouseHoverControl ); + } + + + void ControlBorderManager::mouseExited( const Reference< XInterface >& _rxControl ) + { + if ( m_bDynamicBorderColors ) + controlStatusLost( _rxControl, m_aMouseHoverControl ); + } + + + void ControlBorderManager::validityChanged( const Reference< XControl >& _rxControl, const Reference< XValidatableFormComponent >& _rxValidatable ) + { + try + { + OSL_ENSURE( _rxControl.is(), "ControlBorderManager::validityChanged: invalid control!" ); + OSL_ENSURE( _rxValidatable.is(), "ControlBorderManager::validityChanged: invalid validatable!" ); + + Reference< XVclWindowPeer > xPeer( _rxControl.is() ? _rxControl->getPeer() : Reference< XWindowPeer >(), UNO_QUERY ); + if ( !xPeer.is() || !_rxValidatable.is() ) + return; + + ControlData aData( _rxControl ); + + if ( _rxValidatable->isValid() ) + { + ControlBag::iterator aPos = m_aInvalidControls.find( aData ); + if ( aPos != m_aInvalidControls.end() ) + { // invalid before, valid now + ControlData aOriginalLayout( *aPos ); + m_aInvalidControls.erase( aPos ); + + // restore all the things we used to indicate invalidity + if ( m_bDynamicBorderColors ) + updateBorderStyle( _rxControl, xPeer, aOriginalLayout ); + xPeer->setProperty( FM_PROP_HELPTEXT, makeAny( aOriginalLayout.sOriginalHelpText ) ); + setUnderline( xPeer, aOriginalLayout ); + } + return; + } + + // we're here in the INVALID case + if ( m_aInvalidControls.find( _rxControl ) == m_aInvalidControls.end() ) + { // valid before, invalid now + + // remember the current border + determineOriginalBorderStyle( _rxControl, aData ); + // and tool tip + xPeer->getProperty( FM_PROP_HELPTEXT ) >>= aData.sOriginalHelpText; + // and font + getUnderline( xPeer, aData ); + + m_aInvalidControls.insert( aData ); + + // update the border to the new invalidity + if ( m_bDynamicBorderColors && canColorBorder( xPeer ) ) + updateBorderStyle( _rxControl, xPeer, aData ); + else + { + // and also the new font + setUnderline( xPeer, UnderlineDescriptor( css::awt::FontUnderline::WAVE, m_nInvalidColor ) ); + } + } + + // update the explanation for invalidity (this is always done, even if the validity did not change) + Reference< XValidator > xValidator = _rxValidatable->getValidator(); + OSL_ENSURE( xValidator.is(), "ControlBorderManager::validityChanged: invalid, but no validator?" ); + OUString sExplainInvalidity = xValidator.is() ? xValidator->explainInvalid( _rxValidatable->getCurrentValue() ) : OUString(); + xPeer->setProperty( FM_PROP_HELPTEXT, makeAny( sExplainInvalidity ) ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "ControlBorderManager::validityChanged" ); + } + } + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmcontrollayout.cxx b/svx/source/form/fmcontrollayout.cxx new file mode 100644 index 000000000..154ffa0bc --- /dev/null +++ b/svx/source/form/fmcontrollayout.cxx @@ -0,0 +1,313 @@ +/* -*- 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 <fmcontrollayout.hxx> +#include <fmprop.hxx> + +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/awt/VisualEffect.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/awt/FontDescriptor.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/container/XChild.hpp> + +#include <comphelper/processfactory.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <unotools/confignode.hxx> +#include <unotools/syslocale.hxx> +#include <unotools/localedatawrapper.hxx> + +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <vcl/outdev.hxx> + + +namespace svxform +{ + + + using namespace ::utl; + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::uno::UNO_SET_THROW; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::uno::RuntimeException; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::makeAny; + using ::com::sun::star::beans::XPropertySet; + using ::com::sun::star::beans::XPropertySetInfo; + using ::com::sun::star::lang::Locale; + using ::com::sun::star::awt::FontDescriptor; + using ::com::sun::star::style::XStyleFamiliesSupplier; + using ::com::sun::star::lang::XServiceInfo; + using ::com::sun::star::container::XNameAccess; + using ::com::sun::star::container::XChild; + + namespace FormComponentType = ::com::sun::star::form::FormComponentType; + namespace VisualEffect = ::com::sun::star::awt::VisualEffect; + namespace ScriptType = ::com::sun::star::i18n::ScriptType; + + + namespace + { + ::utl::OConfigurationNode getLayoutSettings( DocumentType _eDocType ) + { + OUString sConfigName = "/org.openoffice.Office.Common/Forms/ControlLayout/" + + DocumentClassification::getModuleIdentifierForDocumentType( _eDocType ); + return OConfigurationTreeRoot::createWithComponentContext( + ::comphelper::getProcessComponentContext(), // TODO + sConfigName ); + } + + template< class INTERFACE_TYPE > + Reference< INTERFACE_TYPE > getTypedModelNode( const Reference< XInterface >& _rxModelNode ) + { + Reference< INTERFACE_TYPE > xTypedNode( _rxModelNode, UNO_QUERY ); + if ( xTypedNode.is() ) + return xTypedNode; + else + { + Reference< XChild > xChild( _rxModelNode, UNO_QUERY ); + if ( xChild.is() ) + return getTypedModelNode< INTERFACE_TYPE >( xChild->getParent() ); + else + return nullptr; + } + } + + + bool lcl_getDocumentDefaultStyleAndFamily( const Reference< XInterface >& _rxDocument, OUString& _rFamilyName, OUString& _rStyleName ) + { + bool bSuccess = true; + Reference< XServiceInfo > xDocumentSI( _rxDocument, UNO_QUERY ); + if ( xDocumentSI.is() ) + { + if ( xDocumentSI->supportsService("com.sun.star.text.TextDocument") + || xDocumentSI->supportsService("com.sun.star.text.WebDocument") + ) + { + _rFamilyName = "ParagraphStyles"; + _rStyleName = "Standard"; + } + else if ( xDocumentSI->supportsService("com.sun.star.sheet.SpreadsheetDocument") ) + { + _rFamilyName = "CellStyles"; + _rStyleName = "Default"; + } + else if ( xDocumentSI->supportsService("com.sun.star.drawing.DrawingDocument") + || xDocumentSI->supportsService("com.sun.star.presentation.PresentationDocument") + ) + { + _rFamilyName = "graphics"; + _rStyleName = "standard"; + } + else + bSuccess = false; + } + return bSuccess; + } + + + void lcl_initializeControlFont( const Reference< XPropertySet >& _rxModel ) + { + try + { + Reference< XPropertySet > xStyle( ControlLayouter::getDefaultDocumentTextStyle( _rxModel ), UNO_SET_THROW ); + Reference< XPropertySetInfo > xStylePSI( xStyle->getPropertySetInfo(), UNO_SET_THROW ); + + // determine the script type associated with the system locale + const SvtSysLocale aSysLocale; + const LocaleDataWrapper& rSysLocaleData = aSysLocale.GetLocaleData(); + const sal_Int16 eSysLocaleScriptType = MsLangId::getScriptType( rSysLocaleData.getLanguageTag().getLanguageType() ); + + // depending on this script type, use the right property from the document's style which controls the + // default locale for document content + const char* pCharLocalePropertyName = "CharLocale"; + switch ( eSysLocaleScriptType ) + { + case ScriptType::LATIN: + // already defaulted above + break; + case ScriptType::ASIAN: + pCharLocalePropertyName = "CharLocaleAsian"; + break; + case ScriptType::COMPLEX: + pCharLocalePropertyName = "CharLocaleComplex"; + break; + default: + OSL_FAIL( "lcl_initializeControlFont: unexpected script type for system locale!" ); + break; + } + + OUString sCharLocalePropertyName = OUString::createFromAscii( pCharLocalePropertyName ); + Locale aDocumentCharLocale; + if ( xStylePSI->hasPropertyByName( sCharLocalePropertyName ) ) + { + OSL_VERIFY( xStyle->getPropertyValue( sCharLocalePropertyName ) >>= aDocumentCharLocale ); + } + // fall back to CharLocale property at the style + if ( aDocumentCharLocale.Language.isEmpty() ) + { + sCharLocalePropertyName = "CharLocale"; + if ( xStylePSI->hasPropertyByName( sCharLocalePropertyName ) ) + { + OSL_VERIFY( xStyle->getPropertyValue( sCharLocalePropertyName ) >>= aDocumentCharLocale ); + } + } + // fall back to the system locale + if ( aDocumentCharLocale.Language.isEmpty() ) + { + aDocumentCharLocale = rSysLocaleData.getLanguageTag().getLocale(); + } + + // retrieve a default font for this locale, and set it at the control + vcl::Font aFont = OutputDevice::GetDefaultFont( DefaultFontType::SANS, LanguageTag::convertToLanguageType( aDocumentCharLocale ), GetDefaultFontFlags::OnlyOne ); + FontDescriptor aFontDesc = VCLUnoHelper::CreateFontDescriptor( aFont ); + _rxModel->setPropertyValue("FontDescriptor", makeAny( aFontDesc ) + ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + } + + + //= ControlLayouter + + + Reference< XPropertySet > ControlLayouter::getDefaultDocumentTextStyle( const Reference< XPropertySet >& _rxModel ) + { + // the style family collection + Reference< XStyleFamiliesSupplier > xSuppStyleFamilies( getTypedModelNode< XStyleFamiliesSupplier >( _rxModel.get() ), UNO_SET_THROW ); + Reference< XNameAccess > xStyleFamilies( xSuppStyleFamilies->getStyleFamilies(), UNO_SET_THROW ); + + // the names of the family, and the style - depends on the document type we live in + OUString sFamilyName, sStyleName; + if ( !lcl_getDocumentDefaultStyleAndFamily( xSuppStyleFamilies.get(), sFamilyName, sStyleName ) ) + throw RuntimeException("unknown document type!"); + + // the concrete style + Reference< XNameAccess > xStyleFamily( xStyleFamilies->getByName( sFamilyName ), UNO_QUERY_THROW ); + return Reference< XPropertySet >( xStyleFamily->getByName( sStyleName ), UNO_QUERY_THROW ); + } + + + void ControlLayouter::initializeControlLayout( const Reference< XPropertySet >& _rxControlModel, DocumentType _eDocType ) + { + DBG_ASSERT( _rxControlModel.is(), "ControlLayouter::initializeControlLayout: invalid model!" ); + if ( !_rxControlModel.is() ) + return; + + try + { + Reference< XPropertySetInfo > xPSI( _rxControlModel->getPropertySetInfo(), UNO_SET_THROW ); + + // the control type + sal_Int16 nClassId = FormComponentType::CONTROL; + _rxControlModel->getPropertyValue( FM_PROP_CLASSID ) >>= nClassId; + + // the document type + if ( _eDocType == eUnknownDocumentType ) + _eDocType = DocumentClassification::classifyHostDocument( _rxControlModel.get() ); + + // let's see what the configuration says about the visual effect + OConfigurationNode aConfig = getLayoutSettings( _eDocType ); + Any aVisualEffect = aConfig.getNodeValue( OUString( "VisualEffect" ) ); + if ( aVisualEffect.hasValue() ) + { + OUString sVisualEffect; + OSL_VERIFY( aVisualEffect >>= sVisualEffect ); + + sal_Int16 nVisualEffect = VisualEffect::NONE; + if ( sVisualEffect == "flat" ) + nVisualEffect = VisualEffect::FLAT; + else if ( sVisualEffect == "3D" ) + nVisualEffect = VisualEffect::LOOK3D; + + if ( xPSI->hasPropertyByName( FM_PROP_BORDER ) ) + { + if ( ( nClassId != FormComponentType::COMMANDBUTTON ) + && ( nClassId != FormComponentType::RADIOBUTTON ) + && ( nClassId != FormComponentType::CHECKBOX ) + && ( nClassId != FormComponentType::GROUPBOX ) + && ( nClassId != FormComponentType::FIXEDTEXT ) + && ( nClassId != FormComponentType::SCROLLBAR ) + && ( nClassId != FormComponentType::SPINBUTTON ) + ) + { + _rxControlModel->setPropertyValue( FM_PROP_BORDER, makeAny( nVisualEffect ) ); + if ( ( nVisualEffect == VisualEffect::FLAT ) + && ( xPSI->hasPropertyByName( FM_PROP_BORDERCOLOR ) ) + ) + // light gray flat border + _rxControlModel->setPropertyValue( FM_PROP_BORDERCOLOR, makeAny( sal_Int32(0x00C0C0C0) ) ); + } + } + if ( xPSI->hasPropertyByName( FM_PROP_VISUALEFFECT ) ) + _rxControlModel->setPropertyValue( FM_PROP_VISUALEFFECT, makeAny( nVisualEffect ) ); + } + + // the font (only if we use the document's ref devices for rendering control text, otherwise, the + // default font of VCL controls is assumed to be fine) + if ( useDocumentReferenceDevice( _eDocType ) + && xPSI->hasPropertyByName( FM_PROP_FONT ) + ) + lcl_initializeControlFont( _rxControlModel ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "ControlLayouter::initializeControlLayout" ); + } + } + + bool ControlLayouter::useDynamicBorderColor( DocumentType _eDocType ) + { + OConfigurationNode aConfig = getLayoutSettings( _eDocType ); + Any aDynamicBorderColor = aConfig.getNodeValue( OUString( "DynamicBorderColors" ) ); + bool bDynamicBorderColor = false; + OSL_VERIFY( aDynamicBorderColor >>= bDynamicBorderColor ); + return bDynamicBorderColor; + } + + + bool ControlLayouter::useDocumentReferenceDevice( DocumentType _eDocType ) + { + if ( _eDocType == eUnknownDocumentType ) + return false; + OConfigurationNode aConfig = getLayoutSettings( _eDocType ); + Any aUseRefDevice = aConfig.getNodeValue( OUString( "UseDocumentTextMetrics" ) ); + bool bUseRefDevice = false; + OSL_VERIFY( aUseRefDevice >>= bUseRefDevice ); + return bUseRefDevice; + } + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmdmod.cxx b/svx/source/form/fmdmod.cxx new file mode 100644 index 000000000..f7c71024d --- /dev/null +++ b/svx/source/form/fmdmod.cxx @@ -0,0 +1,90 @@ +/* -*- 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 <sal/macros.h> +#include <svx/fmdmod.hxx> +#include <fmservs.hxx> +#include <fmobj.hxx> +#include <svx/unoshape.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> + +using namespace ::svxform; + + +::com::sun::star::uno::Reference< ::com::sun::star::uno::XInterface > SAL_CALL SvxFmMSFactory::createInstance(const OUString& rServiceSpecifier) +{ + ::com::sun::star::uno::Reference< ::com::sun::star::uno::XInterface > xRet; + + if ( rServiceSpecifier.startsWith( "com.sun.star.form.component." ) ) + { + css::uno::Reference<css::uno::XComponentContext> xContext = comphelper::getProcessComponentContext(); + xRet = xContext->getServiceManager()->createInstanceWithContext(rServiceSpecifier, xContext); + } + else if ( rServiceSpecifier == "com.sun.star.drawing.ControlShape" ) + { + SdrModel& rTargetModel(getSdrModelFromUnoModel()); + SdrObject* pObj = new FmFormObj(rTargetModel); + xRet = static_cast<cppu::OWeakObject*>(static_cast<SvxShape_UnoImplHelper*>(new SvxShapeControl(pObj))); + } + + if (!xRet.is()) + { + xRet = SvxUnoDrawMSFactory::createInstance(rServiceSpecifier); + } + + return xRet; +} + + +::com::sun::star::uno::Sequence< OUString > SAL_CALL SvxFmMSFactory::getAvailableServiceNames() +{ + static const OUStringLiteral aSvxComponentServiceNameList[] = + { + FM_SUN_COMPONENT_TEXTFIELD, + FM_SUN_COMPONENT_FORM, + FM_SUN_COMPONENT_LISTBOX, + FM_SUN_COMPONENT_COMBOBOX, + FM_SUN_COMPONENT_RADIOBUTTON, + FM_SUN_COMPONENT_GROUPBOX, + FM_SUN_COMPONENT_FIXEDTEXT, + FM_SUN_COMPONENT_COMMANDBUTTON, + FM_SUN_COMPONENT_CHECKBOX, + FM_SUN_COMPONENT_GRIDCONTROL, + FM_SUN_COMPONENT_IMAGEBUTTON, + FM_SUN_COMPONENT_FILECONTROL, + FM_SUN_COMPONENT_TIMEFIELD, + FM_SUN_COMPONENT_DATEFIELD, + FM_SUN_COMPONENT_NUMERICFIELD, + FM_SUN_COMPONENT_CURRENCYFIELD, + FM_SUN_COMPONENT_PATTERNFIELD, + FM_SUN_COMPONENT_HIDDENCONTROL, + FM_SUN_COMPONENT_IMAGECONTROL + }; + + static const sal_uInt16 nSvxComponentServiceNameListCount = SAL_N_ELEMENTS(aSvxComponentServiceNameList); + + auto aSeq( comphelper::arrayToSequence< OUString >(aSvxComponentServiceNameList, nSvxComponentServiceNameListCount) ); + + ::com::sun::star::uno::Sequence< OUString > aParentSeq( SvxUnoDrawMSFactory::getAvailableServiceNames() ); + return comphelper::concatSequences( aParentSeq, aSeq ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmdocumentclassification.cxx b/svx/source/form/fmdocumentclassification.cxx new file mode 100644 index 000000000..43e485d3c --- /dev/null +++ b/svx/source/form/fmdocumentclassification.cxx @@ -0,0 +1,196 @@ +/* -*- 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 <fmdocumentclassification.hxx> + +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/xforms/XFormsSupplier.hpp> +#include <com/sun/star/frame/XModule.hpp> + +#include <tools/diagnose_ex.h> + + +namespace svxform +{ + + + namespace + { + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::container::XChild; + using ::com::sun::star::frame::XModel; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::frame::XModule; + + + template< class TYPE > + Reference< TYPE > getTypedModelNode( const Reference< XInterface >& _rxModelNode ) + { + Reference< TYPE > xTypedNode( _rxModelNode, UNO_QUERY ); + if ( xTypedNode.is() ) + return xTypedNode; + else + { + Reference< XChild > xChild( _rxModelNode, UNO_QUERY ); + if ( xChild.is() ) + return getTypedModelNode< TYPE >( xChild->getParent() ); + else + return Reference< TYPE >(); + } + } + + + Reference< XModel > getDocument( const Reference< XInterface >& _rxModelNode ) + { + return getTypedModelNode< XModel >( _rxModelNode ); + } + } + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::frame; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::xforms; + using namespace ::com::sun::star::container; + + + namespace + { + + struct ModuleInfo + { + const char* pAsciiModuleOrServiceName; + DocumentType eType; + }; + + + const ModuleInfo* lcl_getModuleInfo() + { + static const ModuleInfo aModuleInfo[] = + { + { "com.sun.star.text.TextDocument", eTextDocument }, + { "com.sun.star.text.WebDocument", eWebDocument }, + { "com.sun.star.sheet.SpreadsheetDocument", eSpreadsheetDocument }, + { "com.sun.star.drawing.DrawingDocument", eDrawingDocument }, + { "com.sun.star.presentation.PresentationDocument", ePresentationDocument }, + { "com.sun.star.xforms.XMLFormDocument", eEnhancedForm }, + { "com.sun.star.sdb.FormDesign", eDatabaseForm }, + { "com.sun.star.sdb.TextReportDesign", eDatabaseReport }, + { "com.sun.star.text.GlobalDocument", eTextDocument }, + { nullptr, eUnknownDocumentType } + }; + return aModuleInfo; + } + } + + + //= DocumentClassification + + + DocumentType DocumentClassification::classifyDocument( const Reference< XModel >& _rxDocumentModel ) + { + DocumentType eType( eUnknownDocumentType ); + + OSL_ENSURE( _rxDocumentModel.is(), "DocumentClassification::classifyDocument: invalid document!" ); + if ( !_rxDocumentModel.is() ) + return eType; + + try + { + // first, check whether the document has a ModuleIdentifier which we know + Reference< XModule > xModule( _rxDocumentModel, UNO_QUERY ); + if ( xModule.is() ) + eType = getDocumentTypeForModuleIdentifier( xModule->getIdentifier() ); + if ( eType != eUnknownDocumentType ) + return eType; + + // second, check whether it supports one of the services we know + Reference< XServiceInfo > xSI( _rxDocumentModel, UNO_QUERY_THROW ); + const ModuleInfo* pModuleInfo = lcl_getModuleInfo(); + while ( pModuleInfo->pAsciiModuleOrServiceName ) + { + if ( xSI->supportsService( OUString::createFromAscii( pModuleInfo->pAsciiModuleOrServiceName ) ) ) + return pModuleInfo->eType; + ++pModuleInfo; + } + + // last: uhm, there is no last resort + OSL_FAIL( "DocumentClassification::classifyDocument: unknown document!" ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + return eType; + } + + + DocumentType DocumentClassification::classifyHostDocument( const Reference< XInterface >& _rxFormComponent ) + { + DocumentType eType( eUnknownDocumentType ); + + try + { + Reference< XModel > xDocument( getDocument( _rxFormComponent.get() ) ); + if ( !xDocument.is() ) + return eUnknownDocumentType; + eType = classifyDocument( xDocument ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "DocumentClassification::classifyHostDocument" ); + } + + return eType; + } + + + DocumentType DocumentClassification::getDocumentTypeForModuleIdentifier( const OUString& _rModuleIdentifier ) + { + const ModuleInfo* pModuleInfo = lcl_getModuleInfo(); + while ( pModuleInfo->pAsciiModuleOrServiceName ) + { + if ( _rModuleIdentifier.equalsAscii( pModuleInfo->pAsciiModuleOrServiceName ) ) + return pModuleInfo->eType; + ++pModuleInfo; + } + return eUnknownDocumentType; + } + + + OUString DocumentClassification::getModuleIdentifierForDocumentType( DocumentType _eType ) + { + const ModuleInfo* pModuleInfo = lcl_getModuleInfo(); + while ( pModuleInfo->pAsciiModuleOrServiceName ) + { + if ( pModuleInfo->eType == _eType ) + return OUString::createFromAscii( pModuleInfo->pAsciiModuleOrServiceName ); + ++pModuleInfo; + } + return OUString(); + } + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmdpage.cxx b/svx/source/form/fmdpage.cxx new file mode 100644 index 000000000..17ec9151c --- /dev/null +++ b/svx/source/form/fmdpage.cxx @@ -0,0 +1,115 @@ +/* -*- 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 <svx/fmpage.hxx> +#include <fmobj.hxx> +#include <svx/fmdpage.hxx> +#include <svx/unoshape.hxx> +#include <vcl/svapp.hxx> +#include <cppuhelper/queryinterface.hxx> + +using ::com::sun::star::uno::Any; +using ::com::sun::star::form::XFormsSupplier2; + +SvxFmDrawPage::SvxFmDrawPage( SdrPage* pInPage ) : + SvxDrawPage( pInPage ) +{ +} + +SvxFmDrawPage::~SvxFmDrawPage() throw () +{ +} + +css::uno::Sequence< sal_Int8 > SAL_CALL SvxFmDrawPage::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +Any SAL_CALL SvxFmDrawPage::queryAggregation( const css::uno::Type& _rType ) +{ + Any aRet = ::cppu::queryInterface ( _rType + , static_cast< XFormsSupplier2* >( this ) + , static_cast< XFormsSupplier* >( this ) + ); + if ( !aRet.hasValue() ) + aRet = SvxDrawPage::queryAggregation( _rType ); + + return aRet; +} + +css::uno::Sequence< css::uno::Type > SAL_CALL SvxFmDrawPage::getTypes( ) +{ + return comphelper::concatSequences(SvxDrawPage::getTypes(), + css::uno::Sequence { cppu::UnoType<css::form::XFormsSupplier>::get() }); +} + +SdrObject *SvxFmDrawPage::CreateSdrObject_( const css::uno::Reference< css::drawing::XShape > & xDescr ) +{ + OUString aShapeType( xDescr->getShapeType() ); + + if ( aShapeType == "com.sun.star.drawing.ShapeControl" // compatibility + || aShapeType == "com.sun.star.drawing.ControlShape" + ) + { + return new FmFormObj(GetSdrPage()->getSdrModelFromSdrPage()); + } + else + { + return SvxDrawPage::CreateSdrObject_( xDescr ); + } +} + +css::uno::Reference< css::drawing::XShape > SvxFmDrawPage::CreateShape( SdrObject *pObj ) const +{ + if( SdrInventor::FmForm == pObj->GetObjInventor() ) + { + css::uno::Reference< css::drawing::XShape > xShape = static_cast<SvxShape*>(new SvxShapeControl( pObj )); + return xShape; + } + else + return SvxDrawPage::CreateShape( pObj ); +} + +// XFormsSupplier +css::uno::Reference< css::container::XNameContainer > SAL_CALL SvxFmDrawPage::getForms() +{ + SolarMutexGuard g; + + css::uno::Reference< css::container::XNameContainer > xForms; + + FmFormPage *pFmPage = dynamic_cast<FmFormPage*>( GetSdrPage() ); + if( pFmPage ) + xForms.set( pFmPage->GetForms(), css::uno::UNO_QUERY_THROW ); + + return xForms; +} + +// XFormsSupplier2 +sal_Bool SAL_CALL SvxFmDrawPage::hasForms() +{ + SolarMutexGuard g; + + bool bHas = false; + FmFormPage* pFormPage = dynamic_cast<FmFormPage*>( GetSdrPage() ); + if ( pFormPage ) + bHas = pFormPage->GetForms( false ).is(); + return bHas; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmexch.cxx b/svx/source/form/fmexch.cxx new file mode 100644 index 000000000..c5957cef4 --- /dev/null +++ b/svx/source/form/fmexch.cxx @@ -0,0 +1,386 @@ +/* -*- 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 <fmexch.hxx> + +#include <sot/formats.hxx> +#include <vcl/treelistbox.hxx> +#include <vcl/treelistentry.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> + + +namespace svxform +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::datatransfer; + + OLocalExchange::OLocalExchange( ) + :m_bDragging( false ) + ,m_bClipboardOwner( false ) + { + } + + + void OLocalExchange::copyToClipboard( vcl::Window* _pWindow, const GrantAccess& ) + { + if ( m_bClipboardOwner ) + { // simulate a lostOwnership to notify parties interested in + m_aClipboardListener.Call( *this ); + } + + m_bClipboardOwner = true; + CopyToClipboard( _pWindow ); + } + + + void OLocalExchange::clear() + { + if ( isClipboardOwner() ) + { + try + { + Reference< clipboard::XClipboard > xClipBoard( getOwnClipboard() ); + if ( xClipBoard.is() ) + xClipBoard->setContents( nullptr, nullptr ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + m_bClipboardOwner = false; + } + } + + + void SAL_CALL OLocalExchange::lostOwnership( const Reference< clipboard::XClipboard >& _rxClipboard, const Reference< XTransferable >& _rxTrans ) + { + TransferableHelper::implCallOwnLostOwnership( _rxClipboard, _rxTrans ); + m_bClipboardOwner = false; + + m_aClipboardListener.Call( *this ); + } + + + void OLocalExchange::startDrag( vcl::Window* _pWindow, sal_Int8 _nDragSourceActions, const GrantAccess& ) + { + m_bDragging = true; + StartDrag( _pWindow, _nDragSourceActions ); + } + + + void OLocalExchange::DragFinished( sal_Int8 nDropAction ) + { + TransferableHelper::DragFinished( nDropAction ); + m_bDragging = false; + } + + + bool OLocalExchange::hasFormat( const DataFlavorExVector& _rFormats, SotClipboardFormatId _nFormatId ) + { + return std::any_of(_rFormats.begin(), _rFormats.end(), + [&_nFormatId](const DataFlavorEx& rFormat) { return rFormat.mnSotId == _nFormatId; }); + } + + + bool OLocalExchange::GetData( const css::datatransfer::DataFlavor& /*_rFlavor*/, const OUString& /*rDestDoc*/ ) + { + return false; // do not have any formats by default + } + + OControlTransferData::OControlTransferData( ) + :m_pFocusEntry( nullptr ) + { + } + + + OControlTransferData::OControlTransferData( const Reference< XTransferable >& _rxTransferable ) + :m_pFocusEntry( nullptr ) + { + TransferableDataHelper aExchangedData( _rxTransferable ); + + // try the formats we know + if ( OControlExchange::hasControlPathFormat( aExchangedData.GetDataFlavorExVector() ) ) + { // paths to the controls, relative to a root + Sequence< Any > aControlPathData; + if ( aExchangedData.GetAny(OControlExchange::getControlPathFormatId(), OUString()) >>= aControlPathData ) + { + DBG_ASSERT( aControlPathData.getLength() >= 2, "OControlTransferData::OControlTransferData: invalid data for the control path format!" ); + if ( aControlPathData.getLength() >= 2 ) + { + aControlPathData[0] >>= m_xFormsRoot; + aControlPathData[1] >>= m_aControlPaths; + } + } + else + { + OSL_FAIL( "OControlTransferData::OControlTransferData: invalid data for the control path format (2)!" ); + } + } + if ( OControlExchange::hasHiddenControlModelsFormat( aExchangedData.GetDataFlavorExVector() ) ) + { // sequence of models of hidden controls + aExchangedData.GetAny(OControlExchange::getHiddenControlModelsFormatId(), OUString()) >>= m_aHiddenControlModels; + } + + updateFormats( ); + } + + + static bool lcl_fillDataFlavorEx( SotClipboardFormatId nId, DataFlavorEx& _rFlavor ) + { + _rFlavor.mnSotId = nId; + return SotExchange::GetFormatDataFlavor( _rFlavor.mnSotId, _rFlavor ); + } + + + void OControlTransferData::updateFormats( ) + { + m_aCurrentFormats.clear(); + m_aCurrentFormats.reserve( 3 ); + + DataFlavorEx aFlavor; + + if ( m_aHiddenControlModels.hasElements() ) + { + if ( lcl_fillDataFlavorEx( OControlExchange::getHiddenControlModelsFormatId(), aFlavor ) ) + m_aCurrentFormats.push_back( aFlavor ); + } + + if ( m_xFormsRoot.is() && m_aControlPaths.hasElements() ) + { + if ( lcl_fillDataFlavorEx( OControlExchange::getControlPathFormatId(), aFlavor ) ) + m_aCurrentFormats.push_back( aFlavor ); + } + + if ( !m_aSelectedEntries.empty() ) + { + if ( lcl_fillDataFlavorEx( OControlExchange::getFieldExchangeFormatId(), aFlavor ) ) + m_aCurrentFormats.push_back( aFlavor ); + } + } + + + size_t OControlTransferData::onEntryRemoved( SvTreeListEntry* _pEntry ) + { + m_aSelectedEntries.erase( _pEntry ); + return m_aSelectedEntries.size(); + } + + + void OControlTransferData::addSelectedEntry( SvTreeListEntry* _pEntry ) + { + m_aSelectedEntries.insert( _pEntry ); + } + + + void OControlTransferData::setFocusEntry( SvTreeListEntry* _pFocusEntry ) + { + m_pFocusEntry = _pFocusEntry; + } + + + void OControlTransferData::addHiddenControlsFormat(const css::uno::Sequence< css::uno::Reference< css::uno::XInterface > >& seqInterfaces) + { + m_aHiddenControlModels = seqInterfaces; + } + + + void OControlTransferData::buildPathFormat(SvTreeListBox const * pTreeBox, SvTreeListEntry const * pRoot) + { + m_aControlPaths.realloc(0); + + sal_Int32 nEntryCount = m_aSelectedEntries.size(); + if (nEntryCount == 0) + return; + + m_aControlPaths.realloc(nEntryCount); + css::uno::Sequence<sal_uInt32>* pAllPaths = m_aControlPaths.getArray(); + for (SvTreeListEntry* pCurrentEntry : m_aSelectedEntries) + { + // first we collect the path in an array + ::std::vector< sal_uInt32 > aCurrentPath; + + SvTreeListEntry* pLoop = pCurrentEntry; + while (pLoop != pRoot) + { + aCurrentPath.push_back(pLoop->GetChildListPos()); + pLoop = pTreeBox->GetParent(pLoop); + DBG_ASSERT((pLoop != nullptr) || (pRoot == nullptr), "OControlTransferData::buildPathFormat: invalid root or entry !"); + // pLoop == NULL means that I am at the top end, then the whole + // thing should abort, which will only be the case with pRoot == NULL + } + + // then we can transfer it into css::uno::Sequence + Sequence<sal_uInt32>& rCurrentPath = *pAllPaths; + sal_Int32 nDepth = aCurrentPath.size(); + + rCurrentPath.realloc(nDepth); + sal_uInt32* pSeq = rCurrentPath.getArray(); + sal_Int32 j,k; + for (j = nDepth - 1, k = 0; k<nDepth; --j, ++k) + pSeq[j] = aCurrentPath[k]; + ++pAllPaths; + } + } + + + void OControlTransferData::buildListFromPath(SvTreeListBox const * pTreeBox, SvTreeListEntry* pRoot) + { + ListBoxEntrySet aEmpty; + m_aSelectedEntries.swap( aEmpty ); + + for (const css::uno::Sequence<sal_uInt32>& rPaths : std::as_const(m_aControlPaths)) + { + SvTreeListEntry* pSearch = pRoot; + for (const sal_uInt32 nThisPath : rPaths) + pSearch = pTreeBox->GetEntry(pSearch, nThisPath); + + m_aSelectedEntries.insert( pSearch ); + } + } + + OControlExchange::OControlExchange( ) + { + } + + + bool OControlExchange::GetData( const DataFlavor& _rFlavor, const OUString& rDestDoc ) + { + const SotClipboardFormatId nFormatId = SotExchange::GetFormat( _rFlavor ); + + if ( getControlPathFormatId( ) == nFormatId ) + { + // ugly. We have to pack all the info into one object + Sequence< Any > aCompleteInfo( 2 ); + OSL_ENSURE( m_xFormsRoot.is(), "OLocalExchange::GetData: invalid forms root for this format!" ); + aCompleteInfo.getArray()[ 0 ] <<= m_xFormsRoot; + aCompleteInfo.getArray()[ 1 ] <<= m_aControlPaths; + + SetAny( makeAny( aCompleteInfo ) ); + } + else if ( getHiddenControlModelsFormatId() == nFormatId ) + { + // just need to transfer the models + SetAny( makeAny( m_aHiddenControlModels ) ); + } + else + return OLocalExchange::GetData(_rFlavor, rDestDoc); + + return true; + } + + + void OControlExchange::AddSupportedFormats() + { + if (m_pFocusEntry && !m_aSelectedEntries.empty()) + AddFormat(getFieldExchangeFormatId()); + + if (m_aControlPaths.hasElements()) + AddFormat(getControlPathFormatId()); + + if (m_aHiddenControlModels.hasElements()) + AddFormat(getHiddenControlModelsFormatId()); + } + + + SotClipboardFormatId OControlExchange::getControlPathFormatId() + { + static SotClipboardFormatId s_nFormat = + SotExchange::RegisterFormatName("application/x-openoffice;windows_formatname=\"svxform.ControlPathExchange\""); + DBG_ASSERT(static_cast<SotClipboardFormatId>(-1) != s_nFormat, "OControlExchange::getControlPathFormatId: bad exchange id!"); + return s_nFormat; + } + + + SotClipboardFormatId OControlExchange::getHiddenControlModelsFormatId() + { + static SotClipboardFormatId s_nFormat = + SotExchange::RegisterFormatName("application/x-openoffice;windows_formatname=\"svxform.HiddenControlModelsExchange\""); + DBG_ASSERT(static_cast<SotClipboardFormatId>(-1) != s_nFormat, "OControlExchange::getHiddenControlModelsFormatId: bad exchange id!"); + return s_nFormat; + } + + + SotClipboardFormatId OControlExchange::getFieldExchangeFormatId() + { + static SotClipboardFormatId s_nFormat = + SotExchange::RegisterFormatName("application/x-openoffice;windows_formatname=\"svxform.FieldNameExchange\""); + DBG_ASSERT(static_cast<SotClipboardFormatId>(-1) != s_nFormat, "OControlExchange::getFieldExchangeFormatId: bad exchange id!"); + return s_nFormat; + } + + + //= OControlExchangeHelper + + OLocalExchange* OControlExchangeHelper::createExchange() const + { + return new OControlExchange; + } + + OLocalExchangeHelper::OLocalExchangeHelper(vcl::Window* _pDragSource) + :m_pDragSource(_pDragSource) + { + } + + + OLocalExchangeHelper::~OLocalExchangeHelper() + { + implReset(); + } + + + void OLocalExchangeHelper::startDrag( sal_Int8 nDragSourceActions ) + { + DBG_ASSERT(m_xTransferable.is(), "OLocalExchangeHelper::startDrag: not prepared!"); + m_xTransferable->startDrag( m_pDragSource, nDragSourceActions, OLocalExchange::GrantAccess() ); + } + + + void OLocalExchangeHelper::copyToClipboard( ) const + { + DBG_ASSERT( m_xTransferable.is(), "OLocalExchangeHelper::copyToClipboard: not prepared!" ); + m_xTransferable->copyToClipboard( m_pDragSource, OLocalExchange::GrantAccess() ); + } + + + void OLocalExchangeHelper::implReset() + { + if (m_xTransferable.is()) + { + m_xTransferable->setClipboardListener( Link<OLocalExchange&,void>() ); + m_xTransferable.clear(); + } + } + + + void OLocalExchangeHelper::prepareDrag( ) + { + DBG_ASSERT(!m_xTransferable.is() || !m_xTransferable->isDragging(), "OLocalExchangeHelper::prepareDrag: recursive DnD?"); + + implReset(); + m_xTransferable = createExchange(); + } + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmexpl.cxx b/svx/source/form/fmexpl.cxx new file mode 100644 index 000000000..6f585dbec --- /dev/null +++ b/svx/source/form/fmexpl.cxx @@ -0,0 +1,554 @@ +/* -*- 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 <svx/strings.hrc> +#include <fmexpl.hxx> + +#include <helpids.h> +#include <svx/fmglob.hxx> +#include <svx/fmtools.hxx> +#include <fmexch.hxx> + +#include <svx/svxids.hrc> + +#include <fmprop.hxx> +#include <bitmaps.hlst> +#include <svx/dialmgr.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <sfx2/objitem.hxx> + +#include <svx/fmshell.hxx> +#include <comphelper/types.hxx> + +using namespace ::svxform; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::form; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::container; + +FmNavInsertedHint::FmNavInsertedHint( FmEntryData* pInsertedEntryData, sal_uInt32 nRelPos ) + :pEntryData( pInsertedEntryData ) + ,nPos( nRelPos ) + +{ +} + + +FmNavInsertedHint::~FmNavInsertedHint() +{ +} + + + +FmNavModelReplacedHint::FmNavModelReplacedHint( FmEntryData* pAffectedEntryData ) + :pEntryData( pAffectedEntryData ) +{ +} + + +FmNavModelReplacedHint::~FmNavModelReplacedHint() +{ +} + +FmNavRemovedHint::FmNavRemovedHint( FmEntryData* pRemovedEntryData ) + :pEntryData( pRemovedEntryData ) +{ +} + + +FmNavRemovedHint::~FmNavRemovedHint() +{ +} + +FmNavNameChangedHint::FmNavNameChangedHint( FmEntryData* pData, const OUString& rNewName ) + :pEntryData( pData ) + ,aNewName( rNewName ) +{ +} + + +FmNavNameChangedHint::~FmNavNameChangedHint() +{ +} + +FmNavClearedHint::FmNavClearedHint() +{ +} + + +FmNavClearedHint::~FmNavClearedHint() +{ +} + + +FmEntryDataList::FmEntryDataList() +{ +} + + +FmEntryDataList::~FmEntryDataList() +{ +} + + +void FmEntryDataList::removeNoDelete( FmEntryData* pItem ) +{ + auto it = std::find_if(maEntryDataList.begin(), maEntryDataList.end(), + [&pItem](const std::unique_ptr<FmEntryData>& rEntryData) { return rEntryData.get() == pItem; }); + if (it != maEntryDataList.end()) + { + it->release(); + maEntryDataList.erase( it ); + return; + } + assert(false); +} + + +void FmEntryDataList::insert( std::unique_ptr<FmEntryData> pItem, size_t Index ) +{ + if ( Index < maEntryDataList.size() ) + { + maEntryDataList.insert( maEntryDataList.begin() + Index, std::move(pItem) ); + } + else + maEntryDataList.push_back( std::move(pItem) ); +} + + +void FmEntryDataList::clear() +{ + maEntryDataList.clear(); +} + + +FmEntryData::FmEntryData( FmEntryData* pParentData, const Reference< XInterface >& _rxIFace ) + :pParent( pParentData ) +{ + pChildList.reset( new FmEntryDataList() ); + + newObject( _rxIFace ); +} + + +FmEntryData::~FmEntryData() +{ + pChildList->clear(); +} + + +void FmEntryData::newObject( const css::uno::Reference< css::uno::XInterface >& _rxIFace ) +{ + // do not just copy, normalize it + m_xNormalizedIFace.set( _rxIFace, UNO_QUERY ); + m_xProperties.set(m_xNormalizedIFace, css::uno::UNO_QUERY); + m_xChild.set(m_xNormalizedIFace, css::uno::UNO_QUERY); +} + + +FmEntryData::FmEntryData( const FmEntryData& rEntryData ) +{ + pChildList.reset( new FmEntryDataList() ); + aText = rEntryData.GetText(); + m_aNormalImage = rEntryData.GetNormalImage(); + pParent = rEntryData.GetParent(); + + FmEntryData* pChildData; + size_t nEntryCount = rEntryData.GetChildList()->size(); + for( size_t i = 0; i < nEntryCount; i++ ) + { + pChildData = rEntryData.GetChildList()->at( i ); + std::unique_ptr<FmEntryData> pNewChildData = pChildData->Clone(); + pChildList->insert( std::move(pNewChildData), size_t(-1) ); + } + + m_xNormalizedIFace = rEntryData.m_xNormalizedIFace; + m_xProperties = rEntryData.m_xProperties; + m_xChild = rEntryData.m_xChild; +} + + + +bool FmEntryData::IsEqualWithoutChildren( FmEntryData* pEntryData ) +{ + if(this == pEntryData) + return true; + + if( !pEntryData ) + return false; + + if( aText != pEntryData->GetText() ) + return false; + + if( !pEntryData->GetParent() && pParent ) + return false; + + if( pEntryData->GetParent() && !pParent ) + return false; + + if( !pEntryData->GetParent() && !pParent ) + return true; + + if( !pParent->IsEqualWithoutChildren(pEntryData->GetParent()) ) + return false; + + return true; +} + +FmFormData::FmFormData(const Reference< XForm >& _rxForm, FmFormData* _pParent) + : FmEntryData(_pParent, _rxForm) + , m_xForm(_rxForm) +{ + // set images + m_aNormalImage = Image(RID_SVXBMP_FORM); + + // set title + if (m_xForm.is()) + { + Reference< XPropertySet > xSet(m_xForm, UNO_QUERY); + if (xSet.is()) + { + OUString aEntryName(::comphelper::getString(xSet->getPropertyValue( FM_PROP_NAME ))); + SetText(aEntryName); + } + } + else + SetText( OUString() ); +} + +FmFormData::~FmFormData() +{ +} + +FmFormData::FmFormData( const FmFormData& rFormData ) + :FmEntryData( rFormData ) +{ + m_xForm = rFormData.GetFormIface(); +} + + +std::unique_ptr<FmEntryData> FmFormData::Clone() +{ + return std::unique_ptr<FmEntryData>(new FmFormData( *this )); +} + + +bool FmFormData::IsEqualWithoutChildren( FmEntryData* pEntryData ) +{ + if(this == pEntryData) + return true; + if( dynamic_cast<const FmFormData*>( pEntryData) == nullptr ) + return false; + FmFormData* pFormData = static_cast<FmFormData*>(pEntryData); + if( m_xForm.get() != pFormData->GetFormIface().get() ) + return false; + + return FmEntryData::IsEqualWithoutChildren( pFormData ); +} + +FmControlData::FmControlData(const Reference< XFormComponent >& _rxComponent, FmFormData* _pParent) +: FmEntryData( _pParent, _rxComponent ), + m_xFormComponent( _rxComponent ) +{ + + // set images + m_aNormalImage = GetImage(); + + + // set title + Reference< XPropertySet > xSet(m_xFormComponent, UNO_QUERY); + if( xSet.is() ) + { + SetText( ::comphelper::getString(xSet->getPropertyValue( FM_PROP_NAME ))); + } +} + + +FmControlData::~FmControlData() +{ +} + + +FmControlData::FmControlData( const FmControlData& rControlData ) + :FmEntryData( rControlData ) +{ + m_xFormComponent = rControlData.GetFormComponent(); +} + + +std::unique_ptr<FmEntryData> FmControlData::Clone() +{ + return std::unique_ptr<FmEntryData>(new FmControlData( *this )); +} + + +Image FmControlData::GetImage() const +{ + // Default-Image + Image aImage(StockImage::Yes, RID_SVXBMP_CONTROL); + + Reference< XServiceInfo > xInfo( m_xFormComponent, UNO_QUERY ); + if (!m_xFormComponent.is()) + return aImage; + + + // Spezielle Control-Images + sal_Int16 nObjectType = getControlTypeByObject(xInfo); + switch (nObjectType) + { + case OBJ_FM_BUTTON: + aImage = Image(StockImage::Yes, RID_SVXBMP_BUTTON); + break; + + case OBJ_FM_FIXEDTEXT: + aImage = Image(StockImage::Yes, RID_SVXBMP_FIXEDTEXT); + break; + + case OBJ_FM_EDIT: + aImage = Image(StockImage::Yes, RID_SVXBMP_EDITBOX); + break; + + case OBJ_FM_RADIOBUTTON: + aImage = Image(StockImage::Yes, RID_SVXBMP_RADIOBUTTON); + break; + + case OBJ_FM_CHECKBOX: + aImage = Image(StockImage::Yes, RID_SVXBMP_CHECKBOX); + break; + + case OBJ_FM_LISTBOX: + aImage = Image(StockImage::Yes, RID_SVXBMP_LISTBOX); + break; + + case OBJ_FM_COMBOBOX: + aImage = Image(StockImage::Yes, RID_SVXBMP_COMBOBOX); + break; + + case OBJ_FM_NAVIGATIONBAR: + aImage = Image(StockImage::Yes, RID_SVXBMP_NAVIGATIONBAR); + break; + + case OBJ_FM_GROUPBOX: + aImage = Image(StockImage::Yes, RID_SVXBMP_GROUPBOX); + break; + + case OBJ_FM_IMAGEBUTTON: + aImage = Image(StockImage::Yes, RID_SVXBMP_IMAGEBUTTON); + break; + + case OBJ_FM_FILECONTROL: + aImage = Image(StockImage::Yes, RID_SVXBMP_FILECONTROL); + break; + + case OBJ_FM_HIDDEN: + aImage = Image(StockImage::Yes, RID_SVXBMP_HIDDEN); + break; + + case OBJ_FM_DATEFIELD: + aImage = Image(StockImage::Yes, RID_SVXBMP_DATEFIELD); + break; + + case OBJ_FM_TIMEFIELD: + aImage = Image(StockImage::Yes, RID_SVXBMP_TIMEFIELD); + break; + + case OBJ_FM_NUMERICFIELD: + aImage = Image(StockImage::Yes, RID_SVXBMP_NUMERICFIELD); + break; + + case OBJ_FM_CURRENCYFIELD: + aImage = Image(StockImage::Yes, RID_SVXBMP_CURRENCYFIELD); + break; + + case OBJ_FM_PATTERNFIELD: + aImage = Image(StockImage::Yes, RID_SVXBMP_PATTERNFIELD); + break; + + case OBJ_FM_IMAGECONTROL: + aImage = Image(StockImage::Yes, RID_SVXBMP_IMAGECONTROL); + break; + + case OBJ_FM_FORMATTEDFIELD: + aImage = Image(StockImage::Yes, RID_SVXBMP_FORMATTEDFIELD); + break; + + case OBJ_FM_GRID: + aImage = Image(StockImage::Yes, RID_SVXBMP_GRID); + break; + + case OBJ_FM_SCROLLBAR: + aImage = Image(StockImage::Yes, RID_SVXBMP_SCROLLBAR); + break; + + case OBJ_FM_SPINBUTTON: + aImage = Image(StockImage::Yes, RID_SVXBMP_SPINBUTTON); + break; + } + + return aImage; +} + +bool FmControlData::IsEqualWithoutChildren( FmEntryData* pEntryData ) +{ + if(this == pEntryData) + return true; + + if( dynamic_cast<const FmControlData*>( pEntryData) == nullptr ) + return false; + FmControlData* pControlData = static_cast<FmControlData*>(pEntryData); + + if( m_xFormComponent.get() != pControlData->GetFormComponent().get() ) + return false; + + return FmEntryData::IsEqualWithoutChildren( pControlData ); +} + +void FmControlData::ModelReplaced(const Reference< XFormComponent >& _rxNew) +{ + m_xFormComponent = _rxNew; + newObject( m_xFormComponent ); + // set images anew + m_aNormalImage = GetImage(); +} + +namespace svxform +{ + + NavigatorFrame::NavigatorFrame( SfxBindings* _pBindings, SfxChildWindow* _pMgr, + vcl::Window* _pParent ) + :SfxDockingWindow( _pBindings, _pMgr, _pParent, WinBits(WB_STDMODELESS|WB_SIZEABLE|WB_ROLLABLE|WB_3DLOOK|WB_DOCKABLE) ) + ,SfxControllerItem( SID_FM_FMEXPLORER_CONTROL, *_pBindings ) + { + SetHelpId( HID_FORM_NAVIGATOR_WIN ); + + m_pNavigatorTree = VclPtr<NavigatorTree>::Create( this ); + m_pNavigatorTree->Show(); + SetText( SvxResId(RID_STR_FMEXPLORER) ); + SfxDockingWindow::SetFloatingSize( Size(200,200) ); + } + + + NavigatorFrame::~NavigatorFrame() + { + disposeOnce(); + } + + void NavigatorFrame::dispose() + { + m_pNavigatorTree.disposeAndClear(); + ::SfxControllerItem::dispose(); + SfxDockingWindow::dispose(); + } + + + void NavigatorFrame::UpdateContent( FmFormShell* pFormShell ) + { + m_pNavigatorTree->UpdateContent( pFormShell ); + } + + + void NavigatorFrame::StateChanged( sal_uInt16 nSID, SfxItemState eState, const SfxPoolItem* pState ) + { + if( !pState || SID_FM_FMEXPLORER_CONTROL != nSID ) + return; + + if( eState >= SfxItemState::DEFAULT ) + { + FmFormShell* pShell = dynamic_cast<FmFormShell*>( static_cast<const SfxObjectItem*>(pState)->GetShell() ); + UpdateContent( pShell ); + } + else + UpdateContent( nullptr ); + } + + + void NavigatorFrame::GetFocus() + { + if ( m_pNavigatorTree ) + m_pNavigatorTree->GrabFocus(); + else + SfxDockingWindow::GetFocus(); + } + + + bool NavigatorFrame::Close() + { + UpdateContent( nullptr ); + return SfxDockingWindow::Close(); + } + + + void NavigatorFrame::FillInfo( SfxChildWinInfo& rInfo ) const + { + SfxDockingWindow::FillInfo( rInfo ); + rInfo.bVisible = false; + } + + + Size NavigatorFrame::CalcDockingSize( SfxChildAlignment eAlign ) + { + if ( ( eAlign == SfxChildAlignment::TOP ) || ( eAlign == SfxChildAlignment::BOTTOM ) ) + return Size(); + + return SfxDockingWindow::CalcDockingSize( eAlign ); + } + + + SfxChildAlignment NavigatorFrame::CheckAlignment( SfxChildAlignment _eActAlign, SfxChildAlignment _eAlign ) + { + if ( ( _eAlign == SfxChildAlignment::LEFT ) || ( _eAlign == SfxChildAlignment::RIGHT ) || ( _eAlign == SfxChildAlignment::NOALIGNMENT ) ) + return _eAlign; + return _eActAlign; + } + + + void NavigatorFrame::Resize() + { + SfxDockingWindow::Resize(); + + Size aLogOutputSize = PixelToLogic(GetOutputSizePixel(), MapMode(MapUnit::MapAppFont)); + Size aLogExplSize = aLogOutputSize; + aLogExplSize.AdjustWidth( -6 ); + aLogExplSize.AdjustHeight( -6 ); + + Point aExplPos = LogicToPixel(Point(3, 3), MapMode(MapUnit::MapAppFont)); + Size aExplSize = LogicToPixel(aLogExplSize, MapMode(MapUnit::MapAppFont)); + + m_pNavigatorTree->SetPosSizePixel( aExplPos, aExplSize ); + } + + + + + SFX_IMPL_DOCKINGWINDOW( NavigatorFrameManager, SID_FM_SHOW_FMEXPLORER ) + + + NavigatorFrameManager::NavigatorFrameManager( vcl::Window* _pParent, sal_uInt16 _nId, + SfxBindings* _pBindings, SfxChildWinInfo* _pInfo ) + :SfxChildWindow( _pParent, _nId ) + { + SetWindow( VclPtr<NavigatorFrame>::Create( _pBindings, this, _pParent ) ); + static_cast<SfxDockingWindow*>(GetWindow())->Initialize( _pInfo ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmmodel.cxx b/svx/source/form/fmmodel.cxx new file mode 100644 index 000000000..690ffb778 --- /dev/null +++ b/svx/source/form/fmmodel.cxx @@ -0,0 +1,208 @@ +/* -*- 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 <fmundo.hxx> +#include <fmdocumentclassification.hxx> +#include <fmcontrollayout.hxx> + +#include <com/sun/star/form/XForms.hpp> +#include <svx/fmmodel.hxx> +#include <svx/fmpage.hxx> + +#include <sfx2/objsh.hxx> + +#include <optional> + +using ::com::sun::star::uno::Reference; +using ::com::sun::star::container::XNameContainer; +using namespace svxform; + + +struct FmFormModelImplData +{ + rtl::Reference<FmXUndoEnvironment> mxUndoEnv; + bool bOpenInDesignIsDefaulted; + std::optional<bool> aControlsUseRefDevice; + + FmFormModelImplData() + :bOpenInDesignIsDefaulted( true ) + { + } +}; + +FmFormModel::FmFormModel( + SfxItemPool* pPool, + SfxObjectShell* pPers) +: SdrModel( + pPool, + pPers) + , m_pObjShell(nullptr) + , m_bOpenInDesignMode(false) + , m_bAutoControlFocus(false) +{ + m_pImpl.reset( new FmFormModelImplData ); + m_pImpl->mxUndoEnv = new FmXUndoEnvironment(*this); +} + +FmFormModel::~FmFormModel() +{ + if (m_pObjShell && m_pImpl->mxUndoEnv->IsListening(*m_pObjShell)) + SetObjectShell(nullptr); + + ClearUndoBuffer(); + // minimum limit for undos + SetMaxUndoActionCount(1); +} + +SdrPage* FmFormModel::AllocPage(bool bMasterPage) +{ + return new FmFormPage(*this, bMasterPage); +} + +void FmFormModel::InsertPage(SdrPage* pPage, sal_uInt16 nPos) +{ + // hack for as long as the method is internal + if (m_pObjShell && !m_pImpl->mxUndoEnv->IsListening( *m_pObjShell )) + SetObjectShell(m_pObjShell); + + SdrModel::InsertPage( pPage, nPos ); +} + +SdrPage* FmFormModel::RemovePage(sal_uInt16 nPgNum) +{ + FmFormPage* pToBeRemovedPage = dynamic_cast< FmFormPage* >( GetPage( nPgNum ) ); + OSL_ENSURE( pToBeRemovedPage, "FmFormModel::RemovePage: *which page*?" ); + + if ( pToBeRemovedPage ) + { + Reference< XNameContainer > xForms( pToBeRemovedPage->GetForms( false ) ); + if ( xForms.is() ) + m_pImpl->mxUndoEnv->RemoveForms( xForms ); + } + + FmFormPage* pRemovedPage = static_cast<FmFormPage*>(SdrModel::RemovePage(nPgNum)); + OSL_ENSURE( pRemovedPage == pToBeRemovedPage, "FmFormModel::RemovePage: inconsistency!" ); + return pRemovedPage; +} + +void FmFormModel::InsertMasterPage(SdrPage* pPage, sal_uInt16 nPos) +{ + // hack for as long as the method is internal + if (m_pObjShell && !m_pImpl->mxUndoEnv->IsListening( *m_pObjShell )) + SetObjectShell(m_pObjShell); + + SdrModel::InsertMasterPage(pPage, nPos); +} + +SdrPage* FmFormModel::RemoveMasterPage(sal_uInt16 nPgNum) +{ + FmFormPage* pPage = static_cast<FmFormPage*>(SdrModel::RemoveMasterPage(nPgNum)); + + if ( pPage ) + { + Reference< XNameContainer > xForms( pPage->GetForms( false ) ); + if ( xForms.is() ) + m_pImpl->mxUndoEnv->RemoveForms( xForms ); + } + + return pPage; +} + + +void FmFormModel::implSetOpenInDesignMode( bool _bOpenDesignMode ) +{ + if( _bOpenDesignMode != m_bOpenInDesignMode ) + { + m_bOpenInDesignMode = _bOpenDesignMode; + + if ( m_pObjShell ) + m_pObjShell->SetModified(); + } + // no matter if we really did it or not - from now on, it does not count as defaulted anymore + m_pImpl->bOpenInDesignIsDefaulted = false; +} + + +void FmFormModel::SetOpenInDesignMode( bool bOpenDesignMode ) +{ + implSetOpenInDesignMode( bOpenDesignMode ); +} + + +bool FmFormModel::OpenInDesignModeIsDefaulted( ) +{ + return m_pImpl->bOpenInDesignIsDefaulted; +} + + +bool FmFormModel::ControlsUseRefDevice() const +{ + if ( !m_pImpl->aControlsUseRefDevice ) + { + DocumentType eDocType = eUnknownDocumentType; + if ( m_pObjShell ) + eDocType = DocumentClassification::classifyHostDocument( m_pObjShell->GetModel() ); + m_pImpl->aControlsUseRefDevice = ControlLayouter::useDocumentReferenceDevice(eDocType); + } + return *m_pImpl->aControlsUseRefDevice; +} + + +void FmFormModel::SetAutoControlFocus( bool _bAutoControlFocus ) +{ + if( _bAutoControlFocus != m_bAutoControlFocus ) + { + m_bAutoControlFocus = _bAutoControlFocus; + m_pObjShell->SetModified(); + } +} + + +void FmFormModel::SetObjectShell( SfxObjectShell* pShell ) +{ + if (pShell == m_pObjShell) + return; + + if (m_pObjShell) + { + m_pImpl->mxUndoEnv->EndListening( *this ); + m_pImpl->mxUndoEnv->EndListening( *m_pObjShell ); + } + + m_pObjShell = pShell; + + if (m_pObjShell) + { + m_pImpl->mxUndoEnv->SetReadOnly( m_pObjShell->IsReadOnly() || m_pObjShell->IsReadOnlyUI(), FmXUndoEnvironment::Accessor() ); + + if (!m_pImpl->mxUndoEnv->IsReadOnly()) + m_pImpl->mxUndoEnv->StartListening(*this); + + m_pImpl->mxUndoEnv->StartListening( *m_pObjShell ); + } +} + + +FmXUndoEnvironment& FmFormModel::GetUndoEnv() +{ + return *m_pImpl->mxUndoEnv; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmobj.cxx b/svx/source/form/fmobj.cxx new file mode 100644 index 000000000..abb18c7f2 --- /dev/null +++ b/svx/source/form/fmobj.cxx @@ -0,0 +1,670 @@ +/* -*- 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 <fmobj.hxx> +#include <fmprop.hxx> +#include <fmvwimp.hxx> +#include <fmpgeimp.hxx> +#include <svx/fmview.hxx> +#include <svx/fmpage.hxx> +#include <svx/svdovirt.hxx> +#include <svx/fmmodel.hxx> + +#include <com/sun/star/awt/XDevice.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/form/Forms.hpp> +#include <com/sun/star/script/XEventAttacherManager.hpp> +#include <svx/fmtools.hxx> + +#include <comphelper/property.hxx> +#include <comphelper/processfactory.hxx> +#include <toolkit/awt/vclxdevice.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> + +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::form; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::script; +using namespace ::com::sun::star::container; +using namespace ::svxform; + + +FmFormObj::FmFormObj( + SdrModel& rSdrModel, + const OUString& rModelName) +: SdrUnoObj(rSdrModel, rModelName) + ,m_nPos(-1) + ,m_pLastKnownRefDevice(nullptr) +{ + // normally, this is done in SetUnoControlModel, but if the call happened in the base class ctor, + // then our incarnation of it was not called (since we were not constructed at this time). + impl_checkRefDevice_nothrow( true ); +} + +FmFormObj::FmFormObj(SdrModel& rSdrModel) +: SdrUnoObj(rSdrModel, "") + ,m_nPos(-1) + ,m_pLastKnownRefDevice(nullptr) +{ + // Stuff that old SetModel also did: + impl_checkRefDevice_nothrow(); +} + +FmFormObj::~FmFormObj() +{ + + if (m_xEnvironmentHistory.is()) + m_xEnvironmentHistory->dispose(); + + m_xEnvironmentHistory = nullptr; + m_aEventsHistory.realloc(0); +} + + +void FmFormObj::SetObjEnv(const Reference< XIndexContainer > & xForm, const sal_Int32 nIdx, + const Sequence< ScriptEventDescriptor >& rEvts) +{ + m_xParent = xForm; + aEvts = rEvts; + m_nPos = nIdx; +} + + +void FmFormObj::ClearObjEnv() +{ + m_xParent.clear(); + aEvts.realloc( 0 ); + m_nPos = -1; +} + + +void FmFormObj::impl_checkRefDevice_nothrow( bool _force ) +{ + const FmFormModel* pFormModel = dynamic_cast<FmFormModel*>(&getSdrModelFromSdrObject()); + if ( !pFormModel || !pFormModel->ControlsUseRefDevice() ) + return; + + OutputDevice* pCurrentRefDevice = pFormModel->GetRefDevice(); + if ( ( m_pLastKnownRefDevice.get() == pCurrentRefDevice ) && !_force ) + return; + + Reference< XControlModel > xControlModel( GetUnoControlModel() ); + if ( !xControlModel.is() ) + return; + + m_pLastKnownRefDevice = pCurrentRefDevice; + if ( !m_pLastKnownRefDevice ) + return; + + try + { + Reference< XPropertySet > xModelProps( GetUnoControlModel(), UNO_QUERY_THROW ); + Reference< XPropertySetInfo > xPropertyInfo( xModelProps->getPropertySetInfo(), UNO_SET_THROW ); + + static const char sRefDevicePropName[] = "ReferenceDevice"; + if ( xPropertyInfo->hasPropertyByName( sRefDevicePropName ) ) + { + VCLXDevice* pUnoRefDevice = new VCLXDevice; + pUnoRefDevice->SetOutputDevice( m_pLastKnownRefDevice ); + Reference< XDevice > xRefDevice( pUnoRefDevice ); + xModelProps->setPropertyValue( sRefDevicePropName, makeAny( xRefDevice ) ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +void FmFormObj::impl_isolateControlModel_nothrow() +{ + try + { + Reference< XChild > xControlModel( GetUnoControlModel(), UNO_QUERY ); + if ( xControlModel.is() ) + { + Reference< XIndexContainer> xParent( xControlModel->getParent(), UNO_QUERY ); + if ( xParent.is() ) + { + sal_Int32 nPos = getElementPos( xParent.get(), xControlModel ); + xParent->removeByIndex( nPos ); + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +void FmFormObj::handlePageChange(SdrPage* pOldPage, SdrPage* pNewPage) +{ + FmFormPage* pOldFormPage(dynamic_cast< FmFormPage* >(getSdrPageFromSdrObject())); + if ( pOldFormPage ) + pOldFormPage->GetImpl().formObjectRemoved( *this ); + + FmFormPage* pNewFormPage = dynamic_cast<FmFormPage*>( pNewPage ); + if ( !pNewFormPage ) + { + // Maybe it makes sense to create an environment history here : if somebody set's our page to NULL, and we have a valid page before, + // me may want to remember our place within the old page. For this we could create a new m_xEnvironmentHistory to store it. + // So the next SetPage with a valid new page would restore that environment within the new page. + // But for the original Bug (#57300#) we don't need that, so I omit it here. Maybe this will be implemented later. + impl_isolateControlModel_nothrow(); + SdrUnoObj::handlePageChange(pOldPage, pNewPage); + return; + } + + Reference< css::form::XForms > xNewPageForms = pNewFormPage->GetForms(); + Reference< XIndexContainer > xNewParent; + Sequence< ScriptEventDescriptor> aNewEvents; + + // calc the new parent for my model (within the new page's forms hierarchy) + // do we have a history ? (from :Clone) + if ( m_xEnvironmentHistory.is() ) + { + // the element in m_xEnvironmentHistory which is equivalent to my new parent (which (perhaps) has to be created within pNewPage->GetForms) + // is the right-most element in the tree. + Reference< XIndexContainer > xRightMostLeaf( m_xEnvironmentHistory, UNO_QUERY_THROW ); + try + { + while ( xRightMostLeaf->getCount() ) + { + xRightMostLeaf.set( + xRightMostLeaf->getByIndex( xRightMostLeaf->getCount() - 1 ), + UNO_QUERY_THROW + ); + } + + xNewParent.set( ensureModelEnv( xRightMostLeaf, xNewPageForms ), UNO_QUERY_THROW ); + + // we successfully cloned the environment in m_xEnvironmentHistory, so we can use m_aEventsHistory + // (which describes the events of our model at the moment m_xEnvironmentHistory was created) + aNewEvents = m_aEventsHistory; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + if ( !xNewParent.is() ) + { + // are we a valid part of our current page forms ? + Reference< XIndexContainer > xOldForms; + if ( pOldFormPage ) + xOldForms.set( pOldFormPage->GetForms(), UNO_QUERY_THROW ); + + if ( xOldForms.is() ) + { + // search (upward from our model) for xOldForms + Reference< XChild > xSearch( GetUnoControlModel(), UNO_QUERY ); + while (xSearch.is()) + { + if ( xSearch == xOldForms ) + break; + xSearch.set( xSearch->getParent(), UNO_QUERY ); + } + if ( xSearch.is() ) // implies xSearch == xOldForms, which means we're a valid part of our current page forms hierarchy + { + Reference< XChild > xMeAsChild( GetUnoControlModel(), UNO_QUERY ); + xNewParent.set( ensureModelEnv( xMeAsChild->getParent(), xNewPageForms ), UNO_QUERY ); + + if ( xNewParent.is() ) + { + try + { + // transfer the events from our (model's) parent to the new (model's) parent, too + Reference< XEventAttacherManager > xEventManager(xMeAsChild->getParent(), UNO_QUERY); + Reference< XIndexAccess > xManagerAsIndex(xEventManager, UNO_QUERY); + if (xManagerAsIndex.is()) + { + sal_Int32 nPos = getElementPos(xManagerAsIndex, xMeAsChild); + if (nPos >= 0) + aNewEvents = xEventManager->getScriptEvents(nPos); + } + else + aNewEvents = aEvts; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + } + } + } + + // now set the page + SdrUnoObj::handlePageChange(pOldPage, pNewPage); + + // place my model within the new parent container + if (xNewParent.is()) + { + Reference< XFormComponent > xMeAsFormComp(GetUnoControlModel(), UNO_QUERY); + if (xMeAsFormComp.is()) + { + // check if I have another parent (and remove me, if necessary) + Reference< XIndexContainer > xOldParent(xMeAsFormComp->getParent(), UNO_QUERY); + if (xOldParent.is()) + { + sal_Int32 nPos = getElementPos(xOldParent, xMeAsFormComp); + if (nPos > -1) + xOldParent->removeByIndex(nPos); + } + + // and insert into the new container + xNewParent->insertByIndex(xNewParent->getCount(), makeAny(xMeAsFormComp)); + + // transfer the events + if (aNewEvents.hasElements()) + { + try + { + Reference< XEventAttacherManager > xEventManager(xNewParent, UNO_QUERY); + Reference< XIndexAccess > xManagerAsIndex(xEventManager, UNO_QUERY); + if (xManagerAsIndex.is()) + { + sal_Int32 nPos = getElementPos(xManagerAsIndex, xMeAsFormComp); + DBG_ASSERT(nPos >= 0, "FmFormObj::SetPage : inserted but not present ?"); + xEventManager->registerScriptEvents(nPos, aNewEvents); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + } + } + } + + // delete my history + if (m_xEnvironmentHistory.is()) + m_xEnvironmentHistory->dispose(); + + m_xEnvironmentHistory = nullptr; + m_aEventsHistory.realloc(0); + + pNewFormPage->GetImpl().formObjectInserted( *this ); +} + + +SdrInventor FmFormObj::GetObjInventor() const +{ + return SdrInventor::FmForm; +} + + +sal_uInt16 FmFormObj::GetObjIdentifier() const +{ + return OBJ_UNO; +} + + +void FmFormObj::clonedFrom(const FmFormObj* _pSource) +{ + DBG_ASSERT(_pSource != nullptr, "FmFormObj::clonedFrom : invalid source !"); + if (m_xEnvironmentHistory.is()) + m_xEnvironmentHistory->dispose(); + + m_xEnvironmentHistory = nullptr; + m_aEventsHistory.realloc(0); + + Reference< XChild > xSourceAsChild(_pSource->GetUnoControlModel(), UNO_QUERY); + if (!xSourceAsChild.is()) + return; + + Reference< XInterface > xSourceContainer = xSourceAsChild->getParent(); + + m_xEnvironmentHistory = css::form::Forms::create( comphelper::getProcessComponentContext() ); + + ensureModelEnv(xSourceContainer, m_xEnvironmentHistory); + m_aEventsHistory = aEvts; + // if we were clone there was a call to operator=, so aEvts are exactly the events we need here... +} + + +FmFormObj* FmFormObj::CloneSdrObject(SdrModel& rTargetModel) const +{ + FmFormObj* pFormObject = CloneHelper< FmFormObj >(rTargetModel); + DBG_ASSERT(pFormObject != nullptr, "FmFormObj::Clone : invalid clone !"); + if (pFormObject) + pFormObject->clonedFrom(this); + + return pFormObject; +} + + +FmFormObj& FmFormObj::operator= (const FmFormObj& rObj) +{ + if( this == &rObj ) + return *this; + SdrUnoObj::operator= (rObj); + + // If UnoControlModel is part of an event environment, + // events may assigned to it. + Reference< XFormComponent > xContent(rObj.xUnoControlModel, UNO_QUERY); + if (xContent.is()) + { + Reference< XEventAttacherManager > xManager(xContent->getParent(), UNO_QUERY); + Reference< XIndexAccess > xManagerAsIndex(xManager, UNO_QUERY); + if (xManagerAsIndex.is()) + { + sal_Int32 nPos = getElementPos( xManagerAsIndex, xContent ); + if ( nPos >= 0 ) + aEvts = xManager->getScriptEvents( nPos ); + } + } + else + aEvts = rObj.aEvts; + return *this; +} + + +void FmFormObj::NbcReformatText() +{ + impl_checkRefDevice_nothrow(); + SdrUnoObj::NbcReformatText(); +} + + +namespace +{ + OUString lcl_getFormComponentAccessPath(const Reference< XInterface >& _xElement, Reference< XInterface >& _rTopLevelElement) + { + Reference< css::form::XFormComponent> xChild(_xElement, UNO_QUERY); + Reference< css::container::XIndexAccess> xParent; + if (xChild.is()) + xParent.set(xChild->getParent(), UNO_QUERY); + + // while the current content is a form + OUString sReturn; + while (xChild.is()) + { + // get the content's relative pos within its parent container + sal_Int32 nPos = getElementPos(xParent, xChild); + + // prepend this current relative pos + OUString sCurrentIndex = OUString::number(nPos); + if (!sReturn.isEmpty()) + { + sCurrentIndex += "\\" + sReturn; + } + + sReturn = sCurrentIndex; + + // travel up + xChild.set(xParent, css::uno::UNO_QUERY); + if (xChild.is()) + xParent.set(xChild->getParent(), UNO_QUERY); + } + + _rTopLevelElement = xParent; + return sReturn; + } +} + + +Reference< XInterface > FmFormObj::ensureModelEnv(const Reference< XInterface > & _rSourceContainer, const Reference<css::form::XForms>& _rTopLevelDestContainer) +{ + Reference< XInterface > xTopLevelSource; + OUString sAccessPath = lcl_getFormComponentAccessPath(_rSourceContainer, xTopLevelSource); + if (!xTopLevelSource.is()) + // something went wrong, maybe _rSourceContainer isn't part of a valid forms hierarchy + return Reference< XInterface > (); + + Reference< XIndexContainer > xDestContainer(_rTopLevelDestContainer, UNO_QUERY_THROW); + Reference< XIndexContainer > xSourceContainer(xTopLevelSource, UNO_QUERY); + DBG_ASSERT(xSourceContainer.is(), "FmFormObj::ensureModelEnv : the top level source is invalid !"); + + sal_Int32 nTokIndex = 0; + do + { + OUString aToken = sAccessPath.getToken( 0, '\\', nTokIndex ); + sal_uInt16 nIndex = static_cast<sal_uInt16>(aToken.toInt32()); + + // get the DSS of the source form (we have to find an equivalent for) + DBG_ASSERT(nIndex<xSourceContainer->getCount(), "FmFormObj::ensureModelEnv : invalid access path !"); + Reference< XPropertySet > xSourceForm; + xSourceContainer->getByIndex(nIndex) >>= xSourceForm; + DBG_ASSERT(xSourceForm.is(), "FmFormObj::ensureModelEnv : invalid source form !"); + + Any aSrcCursorSource, aSrcCursorSourceType, aSrcDataSource; + DBG_ASSERT(::comphelper::hasProperty(FM_PROP_COMMAND, xSourceForm) && ::comphelper::hasProperty(FM_PROP_COMMANDTYPE, xSourceForm) + && ::comphelper::hasProperty(FM_PROP_DATASOURCE, xSourceForm), "FmFormObj::ensureModelEnv : invalid access path or invalid form (missing props) !"); + // the parent access path should refer to a row set + try + { + aSrcCursorSource = xSourceForm->getPropertyValue(FM_PROP_COMMAND); + aSrcCursorSourceType = xSourceForm->getPropertyValue(FM_PROP_COMMANDTYPE); + aSrcDataSource = xSourceForm->getPropertyValue(FM_PROP_DATASOURCE); + } + catch(Exception&) + { + OSL_FAIL("FmFormObj::ensureModelEnv : could not retrieve a source DSS !"); + } + + + // calc the number of (source) form siblings with the same DSS + Reference< XPropertySet > xCurrentSourceForm, xCurrentDestForm; + sal_Int16 nCurrentSourceIndex = 0; + sal_Int32 nCurrentDestIndex = 0; + while (nCurrentSourceIndex <= nIndex) + { + bool bEqualDSS = false; + while (!bEqualDSS) // (we don't have to check nCurrentSourceIndex here : it's bound by nIndex) + { + xSourceContainer->getByIndex(nCurrentSourceIndex) >>= xCurrentSourceForm; + DBG_ASSERT(xCurrentSourceForm.is(), "FmFormObj::ensureModelEnv : invalid form ancestor (2) !"); + bEqualDSS = false; + if (::comphelper::hasProperty(FM_PROP_DATASOURCE, xCurrentSourceForm)) + { // it is a form + try + { + if ( xCurrentSourceForm->getPropertyValue(FM_PROP_COMMAND) == aSrcCursorSource + && xCurrentSourceForm->getPropertyValue(FM_PROP_COMMANDTYPE) == aSrcCursorSourceType + && xCurrentSourceForm->getPropertyValue(FM_PROP_DATASOURCE) == aSrcDataSource + ) + { + bEqualDSS = true; + } + } + catch(Exception&) + { + OSL_FAIL("FmFormObj::ensureModelEnv : exception while getting a sibling's DSS !"); + } + + } + ++nCurrentSourceIndex; + } + + DBG_ASSERT(bEqualDSS, "FmFormObj::ensureModelEnv : found no source form !"); + // ??? at least the nIndex-th one should have been found ??? + + // now search the next one with the given DSS (within the destination container) + bEqualDSS = false; + while (!bEqualDSS && (nCurrentDestIndex < xDestContainer->getCount())) + { + xDestContainer->getByIndex(nCurrentDestIndex) >>= xCurrentDestForm; + DBG_ASSERT(xCurrentDestForm.is(), "FmFormObj::ensureModelEnv : invalid destination form !"); + bEqualDSS = false; + if (::comphelper::hasProperty(FM_PROP_DATASOURCE, xCurrentDestForm)) + { // it is a form + try + { + if ( xCurrentDestForm->getPropertyValue(FM_PROP_COMMAND) == aSrcCursorSource + && xCurrentDestForm->getPropertyValue(FM_PROP_COMMANDTYPE) == aSrcCursorSourceType + && xCurrentDestForm->getPropertyValue(FM_PROP_DATASOURCE) == aSrcDataSource + ) + { + bEqualDSS = true; + } + } + catch(Exception&) + { + OSL_FAIL("FmFormObj::ensureModelEnv : exception while getting a destination DSS !"); + } + + } + ++nCurrentDestIndex; + } + + if (!bEqualDSS) + { // There is at least one more source form with the given DSS than destination forms are. + // correct this ... + try + { + // create and insert (into the destination) a copy of the form + xCurrentDestForm.set( + ::comphelper::getProcessServiceFactory()->createInstance("com.sun.star.form.component.DataForm"), + UNO_QUERY_THROW ); + ::comphelper::copyProperties( xCurrentSourceForm, xCurrentDestForm ); + + DBG_ASSERT(nCurrentDestIndex == xDestContainer->getCount(), "FmFormObj::ensureModelEnv : something went wrong with the numbers !"); + xDestContainer->insertByIndex(nCurrentDestIndex, makeAny(xCurrentDestForm)); + + ++nCurrentDestIndex; + // like nCurrentSourceIndex, nCurrentDestIndex now points 'behind' the form it actually means + } + catch(Exception&) + { + OSL_FAIL("FmFormObj::ensureModelEnv : something went seriously wrong while creating a new form !"); + // no more options anymore ... + return Reference< XInterface > (); + } + + } + } + + // now xCurrentDestForm is a form equivalent to xSourceForm (which means they have the same DSS and the same number + // of left siblings with the same DSS, which counts for all their ancestors, too) + + // go down + xDestContainer.set(xCurrentDestForm, UNO_QUERY); + xSourceContainer.set(xSourceForm, UNO_QUERY); + DBG_ASSERT(xDestContainer.is() && xSourceContainer.is(), "FmFormObj::ensureModelEnv : invalid container !"); + } + while ( nTokIndex >= 0 ); + + return Reference<XInterface>( xDestContainer, UNO_QUERY ); +} + +FmFormObj* FmFormObj::GetFormObject( SdrObject* _pSdrObject ) +{ + FmFormObj* pFormObject = dynamic_cast< FmFormObj* >( _pSdrObject ); + if ( !pFormObject ) + { + SdrVirtObj* pVirtualObject = dynamic_cast< SdrVirtObj* >( _pSdrObject ); + if ( pVirtualObject ) + pFormObject = dynamic_cast< FmFormObj* >( &pVirtualObject->ReferencedObj() ); + } + return pFormObject; +} + + +const FmFormObj* FmFormObj::GetFormObject( const SdrObject* _pSdrObject ) +{ + const FmFormObj* pFormObject = dynamic_cast< const FmFormObj* >( _pSdrObject ); + if ( !pFormObject ) + { + const SdrVirtObj* pVirtualObject = dynamic_cast< const SdrVirtObj* >( _pSdrObject ); + if ( pVirtualObject ) + pFormObject = dynamic_cast< const FmFormObj* >( &pVirtualObject->GetReferencedObj() ); + } + return pFormObject; +} + + +void FmFormObj::SetUnoControlModel( const Reference< css::awt::XControlModel >& _rxModel ) +{ + SdrUnoObj::SetUnoControlModel( _rxModel ); + + FmFormPage* pFormPage(dynamic_cast< FmFormPage* >(getSdrPageFromSdrObject())); + if ( pFormPage ) + pFormPage->GetImpl().formModelAssigned( *this ); + + impl_checkRefDevice_nothrow( true ); +} + + +bool FmFormObj::EndCreate( SdrDragStat& rStat, SdrCreateCmd eCmd ) +{ + bool bResult = SdrUnoObj::EndCreate(rStat, eCmd); + if ( bResult && SdrCreateCmd::ForceEnd == eCmd && rStat.GetView() ) + { + FmFormPage* pFormPage(dynamic_cast< FmFormPage* >(getSdrPageFromSdrObject())); + + if (nullptr != pFormPage) + { + try + { + Reference< XFormComponent > xContent( xUnoControlModel, UNO_QUERY_THROW ); + Reference< XForm > xParentForm( xContent->getParent(), UNO_QUERY ); + + Reference< XIndexContainer > xFormToInsertInto; + + if ( !xParentForm.is() ) + { // model is not yet part of a form component hierarchy + xParentForm.set( pFormPage->GetImpl().findPlaceInFormComponentHierarchy( xContent ), UNO_SET_THROW ); + xFormToInsertInto.set( xParentForm, UNO_QUERY_THROW ); + } + + FmFormPageImpl::setUniqueName( xContent, xParentForm ); + + if ( xFormToInsertInto.is() ) + xFormToInsertInto->insertByIndex( xFormToInsertInto->getCount(), makeAny( xContent ) ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + FmFormView* pView( dynamic_cast< FmFormView* >( rStat.GetView() ) ); + FmXFormView* pViewImpl = pView ? pView->GetImpl() : nullptr; + OSL_ENSURE( pViewImpl, "FmFormObj::EndCreate: no view!?" ); + if ( pViewImpl ) + pViewImpl->onCreatedFormObject( *this ); + } + return bResult; +} + + +void FmFormObj::BrkCreate( SdrDragStat& rStat ) +{ + SdrUnoObj::BrkCreate( rStat ); + impl_isolateControlModel_nothrow(); + + FmFormView* pView( dynamic_cast< FmFormView* >( rStat.GetView() ) ); + FmXFormView* pViewImpl = pView ? pView->GetImpl() : nullptr; + OSL_ENSURE( pViewImpl, "FmFormObj::EndCreate: no view!?" ); + if ( pViewImpl ) + pViewImpl->breakCreateFormObject(); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmobjfac.cxx b/svx/source/form/fmobjfac.cxx new file mode 100644 index 000000000..5694776e0 --- /dev/null +++ b/svx/source/form/fmobjfac.cxx @@ -0,0 +1,231 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_features.h> + +#include <tools/diagnose_ex.h> +#include <svx/svdobj.hxx> +#include <svx/fmtools.hxx> +#include <fmservs.hxx> + +#include <svx/fmobjfac.hxx> + +#include <svx/fmglob.hxx> + +#include <fmobj.hxx> + +#include <svx/fmshell.hxx> + +#include <svx/svxids.hrc> +#include <tbxform.hxx> + +#include <tabwin.hxx> +#include <fmexpl.hxx> +#include <filtnav.hxx> + +#include <fmprop.hxx> +#include <fmPropBrw.hxx> +#include <datanavi.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::svxform; + +static bool bInit = false; + +FmFormObjFactory::FmFormObjFactory() +{ + if ( bInit ) + return; + + SdrObjFactory::InsertMakeObjectHdl(LINK(this, FmFormObjFactory, MakeObject)); + + + // register the configuration css::frame::Controller and the NavigationBar + SvxFmTbxCtlAbsRec::RegisterControl( SID_FM_RECORD_ABSOLUTE ); + SvxFmTbxCtlRecText::RegisterControl( SID_FM_RECORD_TEXT ); + SvxFmTbxCtlRecFromText::RegisterControl( SID_FM_RECORD_FROM_TEXT ); + SvxFmTbxCtlRecTotal::RegisterControl( SID_FM_RECORD_TOTAL ); + SvxFmTbxPrevRec::RegisterControl( SID_FM_RECORD_PREV ); + SvxFmTbxNextRec::RegisterControl( SID_FM_RECORD_NEXT ); + + // registering global windows + FmFieldWinMgr::RegisterChildWindow(); + FmPropBrwMgr::RegisterChildWindow(); + NavigatorFrameManager::RegisterChildWindow(); + DataNavigatorManager::RegisterChildWindow(); +#if HAVE_FEATURE_DBCONNECTIVITY + FmFilterNavigatorWinMgr::RegisterChildWindow(); +#endif + + // register the interface for the Formshell + FmFormShell::RegisterInterface(); + + ImplSmartRegisterUnoServices(); + bInit = true; +} + +FmFormObjFactory::~FmFormObjFactory() +{ +} + +// create css::form::Form objects +namespace +{ + void lcl_initProperty( FmFormObj const * _pObject, const OUString& _rPropName, const Any& _rValue ) + { + try + { + Reference< XPropertySet > xModelSet( _pObject->GetUnoControlModel(), UNO_QUERY ); + if ( xModelSet.is() ) + xModelSet->setPropertyValue( _rPropName, _rValue ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "lcl_initProperty" ); + } + } +} + +IMPL_STATIC_LINK(FmFormObjFactory, MakeObject, SdrObjCreatorParams, aParams, SdrObject*) +{ + SdrObject* pNewObj = nullptr; + + if (aParams.nInventor == SdrInventor::FmForm) + { + OUString sServiceSpecifier; + + typedef ::std::vector< ::std::pair< OUString, Any > > PropertyValueArray; + PropertyValueArray aInitialProperties; + + switch ( aParams.nObjIdentifier ) + { + case OBJ_FM_EDIT: + sServiceSpecifier = FM_COMPONENT_EDIT; + break; + + case OBJ_FM_BUTTON: + sServiceSpecifier = FM_COMPONENT_COMMANDBUTTON; + break; + + case OBJ_FM_FIXEDTEXT: + sServiceSpecifier = FM_COMPONENT_FIXEDTEXT; + break; + + case OBJ_FM_LISTBOX: + sServiceSpecifier = FM_COMPONENT_LISTBOX; + break; + + case OBJ_FM_CHECKBOX: + sServiceSpecifier = FM_COMPONENT_CHECKBOX; + break; + + case OBJ_FM_RADIOBUTTON: + sServiceSpecifier = FM_COMPONENT_RADIOBUTTON; + break; + + case OBJ_FM_GROUPBOX: + sServiceSpecifier = FM_COMPONENT_GROUPBOX; + break; + + case OBJ_FM_COMBOBOX: + sServiceSpecifier = FM_COMPONENT_COMBOBOX; + break; + + case OBJ_FM_GRID: + sServiceSpecifier = FM_COMPONENT_GRID; + break; + + case OBJ_FM_IMAGEBUTTON: + sServiceSpecifier = FM_COMPONENT_IMAGEBUTTON; + break; + + case OBJ_FM_FILECONTROL: + sServiceSpecifier = FM_COMPONENT_FILECONTROL; + break; + + case OBJ_FM_DATEFIELD: + sServiceSpecifier = FM_COMPONENT_DATEFIELD; + break; + + case OBJ_FM_TIMEFIELD: + sServiceSpecifier = FM_COMPONENT_TIMEFIELD; + aInitialProperties.emplace_back( FM_PROP_TIMEMAX, makeAny( tools::Time( 23, 59, 59, 999999999 ).GetUNOTime() ) ); + break; + + case OBJ_FM_NUMERICFIELD: + sServiceSpecifier = FM_COMPONENT_NUMERICFIELD; + break; + + case OBJ_FM_CURRENCYFIELD: + sServiceSpecifier = FM_COMPONENT_CURRENCYFIELD; + break; + + case OBJ_FM_PATTERNFIELD: + sServiceSpecifier = FM_COMPONENT_PATTERNFIELD; + break; + + case OBJ_FM_HIDDEN: + sServiceSpecifier = FM_COMPONENT_HIDDEN; + break; + + case OBJ_FM_IMAGECONTROL: + sServiceSpecifier = FM_COMPONENT_IMAGECONTROL; + break; + + case OBJ_FM_FORMATTEDFIELD: + sServiceSpecifier = FM_COMPONENT_FORMATTEDFIELD; + break; + + case OBJ_FM_NAVIGATIONBAR: + sServiceSpecifier = FM_SUN_COMPONENT_NAVIGATIONBAR; + break; + + case OBJ_FM_SCROLLBAR: + sServiceSpecifier = FM_SUN_COMPONENT_SCROLLBAR; + aInitialProperties.emplace_back( FM_PROP_BORDER, makeAny( sal_Int16(0) ) ); + break; + + case OBJ_FM_SPINBUTTON: + sServiceSpecifier = FM_SUN_COMPONENT_SPINBUTTON; + aInitialProperties.emplace_back( FM_PROP_BORDER, makeAny( sal_Int16(0) ) ); + break; + } + + // create the actual object + if ( !sServiceSpecifier.isEmpty() ) + pNewObj = new FmFormObj(aParams.rSdrModel, sServiceSpecifier); + else + pNewObj = new FmFormObj(aParams.rSdrModel); + + // initialize some properties which we want to differ from the defaults + for (const auto& rInitProp : aInitialProperties) + { + lcl_initProperty( + static_cast< FmFormObj* >( pNewObj ), + rInitProp.first, + rInitProp.second + ); + } + } + return pNewObj; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmpage.cxx b/svx/source/form/fmpage.cxx new file mode 100644 index 000000000..a77d7fef1 --- /dev/null +++ b/svx/source/form/fmpage.cxx @@ -0,0 +1,170 @@ +/* -*- 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 <svx/fmpage.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> + +#include <svx/fmmodel.hxx> + +#include <fmobj.hxx> + +#include <fmpgeimp.hxx> + +#include <svx/svdview.hxx> +#include <tools/urlobj.hxx> +#include <vcl/help.hxx> + + +#include <fmprop.hxx> +#include <fmundo.hxx> +using namespace ::svxform; +#include <comphelper/property.hxx> +#include <comphelper/types.hxx> + +using com::sun::star::uno::Reference; +using com::sun::star::uno::UNO_QUERY; + + +FmFormPage::FmFormPage(FmFormModel& rModel, bool bMasterPage) +: SdrPage(rModel, bMasterPage) + ,m_pImpl( new FmFormPageImpl( *this ) ) +{ +} + +void FmFormPage::lateInit(const FmFormPage& rPage) +{ + // call parent + SdrPage::lateInit( rPage ); + + // copy local variables (former stuff from copy constructor) + m_pImpl->initFrom( rPage.GetImpl() ); + m_sPageName = rPage.m_sPageName; +} + + +FmFormPage::~FmFormPage() +{ +} + +SdrPage* FmFormPage::CloneSdrPage(SdrModel& rTargetModel) const +{ + FmFormModel& rFmFormModel(static_cast< FmFormModel& >(rTargetModel)); + FmFormPage* pClonedFmFormPage( + new FmFormPage( + rFmFormModel, + IsMasterPage())); + pClonedFmFormPage->lateInit(*this); + return pClonedFmFormPage; +} + + +void FmFormPage::InsertObject(SdrObject* pObj, size_t nPos) +{ + SdrPage::InsertObject( pObj, nPos ); + static_cast< FmFormModel& >(getSdrModelFromSdrPage()).GetUndoEnv().Inserted(pObj); +} + + +const Reference< css::form::XForms > & FmFormPage::GetForms( bool _bForceCreate ) const +{ + const SdrPage& rMasterPage( *this ); + const FmFormPage* pFormPage = dynamic_cast< const FmFormPage* >( &rMasterPage ); + OSL_ENSURE( pFormPage, "FmFormPage::GetForms: referenced page is no FmFormPage - is this allowed?!" ); + if ( !pFormPage ) + pFormPage = this; + + return pFormPage->m_pImpl->getForms( _bForceCreate ); +} + + +bool FmFormPage::RequestHelp( vcl::Window* pWindow, SdrView const * pView, + const HelpEvent& rEvt ) +{ + if( pView->IsAction() ) + return false; + + Point aPos = rEvt.GetMousePosPixel(); + aPos = pWindow->ScreenToOutputPixel( aPos ); + aPos = pWindow->PixelToLogic( aPos ); + + SdrPageView* pPV = nullptr; + SdrObject* pObj = pView->PickObj(aPos, 0, pPV, SdrSearchOptions::DEEP); + if (!pObj) + return false; + + FmFormObj* pFormObject = FmFormObj::GetFormObject( pObj ); + if ( !pFormObject ) + return false; + + OUString aHelpText; + css::uno::Reference< css::beans::XPropertySet > xSet( pFormObject->GetUnoControlModel(), css::uno::UNO_QUERY ); + if (xSet.is()) + { + if (::comphelper::hasProperty(FM_PROP_HELPTEXT, xSet)) + aHelpText = ::comphelper::getString(xSet->getPropertyValue(FM_PROP_HELPTEXT)); + + if (aHelpText.isEmpty() && ::comphelper::hasProperty(FM_PROP_TARGET_URL, xSet)) + { + OUString aText = ::comphelper::getString(xSet->getPropertyValue(FM_PROP_TARGET_URL)); + INetURLObject aUrl(aText); + + // test if it is a protocol type that I want to display + INetProtocol aProtocol = aUrl.GetProtocol(); + static const INetProtocol s_aQuickHelpSupported[] = + { INetProtocol::Ftp, INetProtocol::Http, INetProtocol::File, INetProtocol::Mailto, + INetProtocol::Https, INetProtocol::Javascript, + INetProtocol::Ldap + }; + for (const INetProtocol& i : s_aQuickHelpSupported) + if (i == aProtocol) + { + aHelpText = aUrl.GetURLNoPass(INetURLObject::DecodeMechanism::Unambiguous); + break; + } + } + } + if ( !aHelpText.isEmpty() ) + { + // display the help + tools::Rectangle aItemRect = pObj->GetCurrentBoundRect(); + aItemRect = pWindow->LogicToPixel( aItemRect ); + Point aPt = pWindow->OutputToScreenPixel( aItemRect.TopLeft() ); + aItemRect.SetLeft( aPt.X() ); + aItemRect.SetTop( aPt.Y() ); + aPt = pWindow->OutputToScreenPixel( aItemRect.BottomRight() ); + aItemRect.SetRight( aPt.X() ); + aItemRect.SetBottom( aPt.Y() ); + if( rEvt.GetMode() == HelpEventMode::BALLOON ) + Help::ShowBalloon( pWindow, aItemRect.Center(), aItemRect, aHelpText); + else + Help::ShowQuickHelp( pWindow, aItemRect, aHelpText ); + } + return true; +} + + +SdrObject* FmFormPage::RemoveObject(size_t nObjNum) +{ + SdrObject* pObj = SdrPage::RemoveObject(nObjNum); + if (pObj) + static_cast< FmFormModel& >(getSdrModelFromSdrPage()).GetUndoEnv().Removed(pObj); + return pObj; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmpgeimp.cxx b/svx/source/form/fmpgeimp.cxx new file mode 100644 index 000000000..449cc724f --- /dev/null +++ b/svx/source/form/fmpgeimp.cxx @@ -0,0 +1,713 @@ +/* -*- 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 <fmpgeimp.hxx> +#include <fmundo.hxx> +#include <svx/fmtools.hxx> +#include <fmprop.hxx> +#include <fmservs.hxx> +#include <fmobj.hxx> +#include <formcontrolfactory.hxx> +#include <svx/svditer.hxx> +#include <svx/strings.hrc> +#include <treevisitor.hxx> + +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/sdbc/XRowSet.hpp> +#include <com/sun/star/container/EnumerableMap.hpp> +#include <com/sun/star/drawing/XControlShape.hpp> +#include <com/sun/star/form/Forms.hpp> +#include <com/sun/star/form/FormComponentType.hpp> + +#include <sal/log.hxx> +#include <sfx2/objsh.hxx> +#include <svx/fmpage.hxx> +#include <svx/fmmodel.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <svx/dialmgr.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/types.hxx> +#include <connectivity/dbtools.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::form; +using ::com::sun::star::awt::XControlModel; +using ::com::sun::star::container::XMap; +using ::com::sun::star::container::EnumerableMap; +using ::com::sun::star::drawing::XControlShape; +using namespace ::svxform; +using namespace ::dbtools; + + +FmFormPageImpl::FmFormPageImpl( FmFormPage& _rPage ) + :m_rPage( _rPage ) + ,m_bFirstActivation( true ) + ,m_bAttemptedFormCreation( false ) +{ +} + + +namespace +{ + class FormComponentInfo + { + public: + static size_t childCount( const Reference< XInterface >& _component ) + { + Reference< XIndexAccess > xContainer( _component, UNO_QUERY ); + if ( xContainer.is() ) + return xContainer->getCount(); + return 0; + } + + static Reference< XInterface > getChild( const Reference< XInterface >& _component, size_t _index ) + { + Reference< XIndexAccess > xContainer( _component, UNO_QUERY_THROW ); + return Reference< XInterface >( xContainer->getByIndex( _index ), UNO_QUERY ); + } + }; + + typedef ::std::pair< Reference< XInterface >, Reference< XInterface > > FormComponentPair; + + class FormHierarchyComparator + { + public: + FormHierarchyComparator() + { + } + + static size_t childCount( const FormComponentPair& _components ) + { + size_t lhsCount = FormComponentInfo::childCount( _components.first ); + size_t rhsCount = FormComponentInfo::childCount( _components.second ); + if ( lhsCount != rhsCount ) + throw RuntimeException( "Found inconsistent form component hierarchies (1)!" ); + return lhsCount; + } + + static FormComponentPair getChild( const FormComponentPair& _components, size_t _index ) + { + return FormComponentPair( + FormComponentInfo::getChild( _components.first, _index ), + FormComponentInfo::getChild( _components.second, _index ) + ); + } + }; + + typedef ::std::map< Reference< XControlModel >, Reference< XControlModel > > MapControlModels; + + class FormComponentAssignment + { + public: + explicit FormComponentAssignment( MapControlModels& _out_controlModelMap ) + :m_rControlModelMap( _out_controlModelMap ) + { + } + + void process( const FormComponentPair& _component ) + { + Reference< XControlModel > lhsControlModel( _component.first, UNO_QUERY ); + Reference< XControlModel > rhsControlModel( _component.second, UNO_QUERY ); + if ( lhsControlModel.is() != rhsControlModel.is() ) + throw RuntimeException( "Found inconsistent form component hierarchies (2)!" ); + + if ( lhsControlModel.is() ) + m_rControlModelMap[ lhsControlModel ] = rhsControlModel; + } + + private: + MapControlModels& m_rControlModelMap; + }; +} + + +void FmFormPageImpl::initFrom( FmFormPageImpl& i_foreignImpl ) +{ + // clone the Forms collection + const Reference< css::form::XForms > xForeignForms( i_foreignImpl.getForms( false ) ); + + if ( !xForeignForms.is() ) + return; + + try + { + m_xForms.set( xForeignForms->createClone(), UNO_QUERY_THROW ); + + // create a mapping between the original control models and their clones + MapControlModels aModelAssignment; + + typedef TreeVisitor< FormComponentPair, FormHierarchyComparator, FormComponentAssignment > FormComponentVisitor; + FormComponentVisitor aVisitor{ FormHierarchyComparator() }; + + FormComponentAssignment aAssignmentProcessor( aModelAssignment ); + aVisitor.process( FormComponentPair( xForeignForms, m_xForms ), aAssignmentProcessor ); + + // assign the cloned models to their SdrObjects + SdrObjListIter aForeignIter( &i_foreignImpl.m_rPage ); + SdrObjListIter aOwnIter( &m_rPage ); + + OSL_ENSURE( aForeignIter.IsMore() == aOwnIter.IsMore(), "FmFormPageImpl::FmFormPageImpl: inconsistent number of objects (1)!" ); + while ( aForeignIter.IsMore() && aOwnIter.IsMore() ) + { + FmFormObj* pForeignObj = dynamic_cast< FmFormObj* >( aForeignIter.Next() ); + FmFormObj* pOwnObj = dynamic_cast< FmFormObj* >( aOwnIter.Next() ); + + bool bForeignIsForm = pForeignObj && ( pForeignObj->GetObjInventor() == SdrInventor::FmForm ); + bool bOwnIsForm = pOwnObj && ( pOwnObj->GetObjInventor() == SdrInventor::FmForm ); + + if ( bForeignIsForm != bOwnIsForm ) + { + // if this fires, don't attempt to do further assignments, something's completely messed up + SAL_WARN( "svx.form", "FmFormPageImpl::FmFormPageImpl: inconsistent ordering of objects!" ); + break; + } + + if ( !bForeignIsForm ) + // no form control -> next round + continue; + + Reference< XControlModel > xForeignModel( pForeignObj->GetUnoControlModel() ); + if ( !xForeignModel.is() ) + { + // if this fires, the SdrObject does not have a UNO Control Model. This is pathological, but well ... + // So the cloned SdrObject will also not have a UNO Control Model. + SAL_WARN( "svx.form", "FmFormPageImpl::FmFormPageImpl: control shape without control!" ); + continue; + } + + MapControlModels::const_iterator assignment = aModelAssignment.find( xForeignModel ); + if ( assignment == aModelAssignment.end() ) + { + // if this fires, the source SdrObject has a model, but it is not part of the model hierarchy in + // i_foreignImpl.getForms(). + // Pathological, too ... + SAL_WARN( "svx.form", "FmFormPageImpl::FmFormPageImpl: no clone found for this model!" ); + continue; + } + + pOwnObj->SetUnoControlModel( assignment->second ); + } + OSL_ENSURE( aForeignIter.IsMore() == aOwnIter.IsMore(), "FmFormPageImpl::FmFormPageImpl: inconsistent number of objects (2)!" ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +Reference< XMap > FmFormPageImpl::getControlToShapeMap() +{ + Reference< XMap > xControlShapeMap( m_aControlShapeMap.get(), UNO_QUERY ); + if ( xControlShapeMap.is() ) + return xControlShapeMap; + + xControlShapeMap = impl_createControlShapeMap_nothrow(); + m_aControlShapeMap = xControlShapeMap; + return xControlShapeMap; +} + + +namespace +{ + void lcl_insertFormObject_throw( const FmFormObj& _object, const Reference< XMap >& _map ) + { + // the control model + Reference< XControlModel > xControlModel = _object.GetUnoControlModel(); + OSL_ENSURE( xControlModel.is(), "lcl_insertFormObject_throw: suspicious: no control model!" ); + if ( !xControlModel.is() ) + return; + + Reference< XControlShape > xControlShape( const_cast< FmFormObj& >( _object ).getUnoShape(), UNO_QUERY ); + OSL_ENSURE( xControlShape.is(), "lcl_insertFormObject_throw: suspicious: no control shape!" ); + if ( !xControlShape.is() ) + return; + + _map->put( makeAny( xControlModel ), makeAny( xControlShape ) ); + } + + void lcl_removeFormObject_throw( const FmFormObj& _object, const Reference< XMap >& _map ) + { + // the control model + Reference< XControlModel > xControlModel = _object.GetUnoControlModel(); + OSL_ENSURE( xControlModel.is(), "lcl_removeFormObject: suspicious: no control model!" ); + if ( !xControlModel.is() ) + { + return; + } + + Any aOldAssignment = _map->remove( makeAny( xControlModel ) ); + OSL_ENSURE( + aOldAssignment == makeAny( Reference< XControlShape >( const_cast< FmFormObj& >( _object ).getUnoShape(), UNO_QUERY ) ), + "lcl_removeFormObject: map was inconsistent!" ); + } +} + + +Reference< XMap > FmFormPageImpl::impl_createControlShapeMap_nothrow() +{ + Reference< XMap > xMap; + + try + { + xMap = EnumerableMap::create( comphelper::getProcessComponentContext(), + ::cppu::UnoType< XControlModel >::get(), + ::cppu::UnoType< XControlShape >::get() + ); + + SdrObjListIter aPageIter( &m_rPage ); + while ( aPageIter.IsMore() ) + { + // only FmFormObjs are what we're interested in + FmFormObj* pCurrent = FmFormObj::GetFormObject( aPageIter.Next() ); + if ( !pCurrent ) + continue; + + lcl_insertFormObject_throw( *pCurrent, xMap ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return xMap; +} + + +const Reference< css::form::XForms >& FmFormPageImpl::getForms( bool _bForceCreate ) +{ + if ( m_xForms.is() || !_bForceCreate ) + return m_xForms; + + if ( !m_bAttemptedFormCreation ) + { + m_bAttemptedFormCreation = true; + + Reference<XComponentContext> xContext = comphelper::getProcessComponentContext(); + m_xForms = css::form::Forms::create( xContext ); + + if ( m_aFormsCreationHdl.IsSet() ) + { + m_aFormsCreationHdl.Call( *this ); + } + + FmFormModel& rFmFormModel(dynamic_cast< FmFormModel& >(m_rPage.getSdrModelFromSdrPage())); + + // give the newly created collection a place in the universe + SfxObjectShell* pObjShell(rFmFormModel.GetObjectShell()); + if ( pObjShell ) + m_xForms->setParent( pObjShell->GetModel() ); + + // tell the UNDO environment that we have a new forms collection + rFmFormModel.GetUndoEnv().AddForms( Reference<XNameContainer>(m_xForms,UNO_QUERY_THROW) ); + } + return m_xForms; +} + + +FmFormPageImpl::~FmFormPageImpl() +{ + xCurrentForm = nullptr; + + ::comphelper::disposeComponent( m_xForms ); +} + + +bool FmFormPageImpl::validateCurForm() +{ + if ( !xCurrentForm.is() ) + return false; + + if ( !xCurrentForm->getParent().is() ) + xCurrentForm.clear(); + + return xCurrentForm.is(); +} + + +void FmFormPageImpl::setCurForm(const Reference< css::form::XForm >& xForm) +{ + xCurrentForm = xForm; +} + + +Reference< XForm > FmFormPageImpl::getDefaultForm() +{ + Reference< XForm > xForm; + + Reference< XForms > xForms( getForms() ); + + // by default, we use our "current form" + if ( !validateCurForm() ) + { + // check whether there is a "standard" form + if ( Reference<XNameAccess>(xForms,UNO_QUERY_THROW)->hasElements() ) + { + // find the standard form + OUString sStandardFormname = SvxResId(RID_STR_STDFORMNAME); + + try + { + if ( xForms->hasByName( sStandardFormname ) ) + xForm.set( xForms->getByName( sStandardFormname ), UNO_QUERY_THROW ); + else + { + xForm.set( xForms->getByIndex(0), UNO_QUERY_THROW ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + } + else + { + xForm = xCurrentForm; + } + + // did not find an existing suitable form -> create a new one + if ( !xForm.is() ) + { + SdrModel& rModel(m_rPage.getSdrModelFromSdrPage()); + + if( rModel.IsUndoEnabled() ) + { + OUString aStr(SvxResId(RID_STR_FORM)); + OUString aUndoStr(SvxResId(RID_STR_UNDO_CONTAINER_INSERT)); + rModel.BegUndo(aUndoStr.replaceFirst("'#'", aStr)); + } + + try + { + xForm.set( ::comphelper::getProcessServiceFactory()->createInstance( FM_SUN_COMPONENT_FORM ), UNO_QUERY ); + + // a form should always have the command type table as default + Reference< XPropertySet > xFormProps( xForm, UNO_QUERY_THROW ); + xFormProps->setPropertyValue( FM_PROP_COMMANDTYPE, makeAny( sal_Int32( CommandType::TABLE ) ) ); + + // and the "Standard" name + OUString sName = SvxResId(RID_STR_STDFORMNAME); + xFormProps->setPropertyValue( FM_PROP_NAME, makeAny( sName ) ); + + if( rModel.IsUndoEnabled() ) + { + rModel.AddUndo( + std::make_unique<FmUndoContainerAction>( + static_cast< FmFormModel& >(rModel), + FmUndoContainerAction::Inserted, + xForms, + xForm, + xForms->getCount())); + } + xForms->insertByName( sName, makeAny( xForm ) ); + xCurrentForm = xForm; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + xForm.clear(); + } + + if( rModel.IsUndoEnabled() ) + rModel.EndUndo(); + } + + return xForm; +} + + +Reference< css::form::XForm > FmFormPageImpl::findPlaceInFormComponentHierarchy( + const Reference< XFormComponent > & rContent, const Reference< XDataSource > & rDatabase, + const OUString& rDBTitle, const OUString& rCursorSource, sal_Int32 nCommandType ) +{ + // if the control already is child of a form, don't do anything + if (!rContent.is() || rContent->getParent().is()) + return nullptr; + + Reference< XForm > xForm; + + // If database and CursorSource are set, the form is searched for using + // these criteria, otherwise only current and the DefaultForm. + if (rDatabase.is() && !rCursorSource.isEmpty()) + { + validateCurForm(); + + // first search in the current form + xForm = findFormForDataSource( xCurrentForm, rDatabase, rCursorSource, nCommandType ); + + Reference< css::container::XIndexAccess > xFormsByIndex = getForms(); + DBG_ASSERT(xFormsByIndex.is(), "FmFormPageImpl::findPlaceInFormComponentHierarchy : no index access for my forms collection !"); + sal_Int32 nCount = xFormsByIndex->getCount(); + for (sal_Int32 i = 0; !xForm.is() && i < nCount; i++) + { + Reference< css::form::XForm > xToSearch; + xFormsByIndex->getByIndex(i) >>= xToSearch; + xForm = findFormForDataSource( xToSearch, rDatabase, rCursorSource, nCommandType ); + } + + // If no css::form found, then create a new one + if (!xForm.is()) + { + SdrModel& rModel(m_rPage.getSdrModelFromSdrPage()); + const bool bUndo(rModel.IsUndoEnabled()); + + if( bUndo ) + { + OUString aStr(SvxResId(RID_STR_FORM)); + OUString aUndoStr(SvxResId(RID_STR_UNDO_CONTAINER_INSERT)); + aUndoStr = aUndoStr.replaceFirst("#", aStr); + rModel.BegUndo(aUndoStr); + } + + xForm.set(::comphelper::getProcessServiceFactory()->createInstance(FM_SUN_COMPONENT_FORM), UNO_QUERY); + // a form should always have the command type table as default + Reference< css::beans::XPropertySet > xFormProps(xForm, UNO_QUERY); + try { xFormProps->setPropertyValue(FM_PROP_COMMANDTYPE, makeAny(sal_Int32(CommandType::TABLE))); } + catch(Exception&) { } + + if (!rDBTitle.isEmpty()) + xFormProps->setPropertyValue(FM_PROP_DATASOURCE,makeAny(rDBTitle)); + else + { + Reference< css::beans::XPropertySet > xDatabaseProps(rDatabase, UNO_QUERY); + Any aDatabaseUrl = xDatabaseProps->getPropertyValue(FM_PROP_URL); + xFormProps->setPropertyValue(FM_PROP_URL, aDatabaseUrl); + } + + xFormProps->setPropertyValue(FM_PROP_COMMAND,makeAny(rCursorSource)); + xFormProps->setPropertyValue(FM_PROP_COMMANDTYPE, makeAny(nCommandType)); + + Reference< css::container::XNameAccess > xNamedSet = getForms(); + + const bool bTableOrQuery = ( CommandType::TABLE == nCommandType ) || ( CommandType::QUERY == nCommandType ); + OUString sName = FormControlFactory::getUniqueName( xNamedSet, + bTableOrQuery ? rCursorSource : SvxResId(RID_STR_STDFORMNAME) ); + + xFormProps->setPropertyValue( FM_PROP_NAME, makeAny( sName ) ); + + if( bUndo ) + { + Reference< css::container::XIndexContainer > xContainer = getForms(); + rModel.AddUndo( + std::make_unique<FmUndoContainerAction>( + static_cast< FmFormModel& >(rModel), + FmUndoContainerAction::Inserted, + xContainer, + xForm, + xContainer->getCount())); + } + + getForms()->insertByName( sName, makeAny( xForm ) ); + + if( bUndo ) + rModel.EndUndo(); + } + xCurrentForm = xForm; + } + + xForm = getDefaultForm(); + return xForm; +} + + +Reference< XForm > FmFormPageImpl::findFormForDataSource( + const Reference< XForm > & rForm, const Reference< XDataSource > & _rxDatabase, + const OUString& _rCursorSource, sal_Int32 nCommandType) +{ + Reference< XForm > xResultForm; + Reference< XRowSet > xDBForm(rForm, UNO_QUERY); + Reference< XPropertySet > xFormProps(rForm, UNO_QUERY); + if (!xDBForm.is() || !xFormProps.is()) + return xResultForm; + + OSL_ENSURE(_rxDatabase.is(), "FmFormPageImpl::findFormForDataSource: invalid data source!"); + OUString sLookupName; // the name of the data source we're looking for + OUString sFormDataSourceName; // the name of the data source the current connection in the form is based on + try + { + Reference< XPropertySet > xDSProps(_rxDatabase, UNO_QUERY); + if (xDSProps.is()) + xDSProps->getPropertyValue(FM_PROP_NAME) >>= sLookupName; + + xFormProps->getPropertyValue(FM_PROP_DATASOURCE) >>= sFormDataSourceName; + // if there's no DataSourceName set at the form, check whether we can deduce one from its + // ActiveConnection + if (sFormDataSourceName.isEmpty()) + { + Reference< XConnection > xFormConnection; + xFormProps->getPropertyValue( FM_PROP_ACTIVE_CONNECTION ) >>= xFormConnection; + if ( !xFormConnection.is() ) + isEmbeddedInDatabase( xFormProps, xFormConnection ); + if (xFormConnection.is()) + { + Reference< XChild > xConnAsChild(xFormConnection, UNO_QUERY); + if (xConnAsChild.is()) + { + Reference< XDataSource > xFormDS(xConnAsChild->getParent(), UNO_QUERY); + if (xFormDS.is()) + { + xDSProps.set(xFormDS, css::uno::UNO_QUERY); + if (xDSProps.is()) + xDSProps->getPropertyValue(FM_PROP_NAME) >>= sFormDataSourceName; + } + } + } + } + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("svx", "FmFormPageImpl::findFormForDataSource"); + } + + if (sLookupName == sFormDataSourceName) + { + // now check whether CursorSource and type match + OUString aCursorSource = ::comphelper::getString(xFormProps->getPropertyValue(FM_PROP_COMMAND)); + sal_Int32 nType = ::comphelper::getINT32(xFormProps->getPropertyValue(FM_PROP_COMMANDTYPE)); + if (aCursorSource.isEmpty() || ((nType == nCommandType) && (aCursorSource == _rCursorSource))) // found the form + { + xResultForm = rForm; + // if no data source is set yet, it is done here + if (aCursorSource.isEmpty()) + { + xFormProps->setPropertyValue(FM_PROP_COMMAND, makeAny(_rCursorSource)); + xFormProps->setPropertyValue(FM_PROP_COMMANDTYPE, makeAny(nCommandType)); + } + } + } + + // as long as xResultForm is NULL, search the child forms of rForm + Reference< XIndexAccess > xComponents(rForm, UNO_QUERY); + sal_Int32 nCount = xComponents->getCount(); + for (sal_Int32 i = 0; !xResultForm.is() && i < nCount; ++i) + { + Reference< css::form::XForm > xSearchForm; + xComponents->getByIndex(i) >>= xSearchForm; + // continue searching in the sub form + if (xSearchForm.is()) + xResultForm = findFormForDataSource( xSearchForm, _rxDatabase, _rCursorSource, nCommandType ); + } + return xResultForm; +} + + +OUString FmFormPageImpl::setUniqueName(const Reference< XFormComponent > & xFormComponent, const Reference< XForm > & xControls) +{ +#if OSL_DEBUG_LEVEL > 0 + try + { + OSL_ENSURE( !xFormComponent->getParent().is(), "FmFormPageImpl::setUniqueName: to be called before insertion!" ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +#endif + OUString sName; + Reference< css::beans::XPropertySet > xSet(xFormComponent, UNO_QUERY); + if (xSet.is()) + { + sName = ::comphelper::getString( xSet->getPropertyValue( FM_PROP_NAME ) ); + Reference< css::container::XNameAccess > xNameAcc(xControls, UNO_QUERY); + + if (sName.isEmpty() || xNameAcc->hasByName(sName)) + { + // set a default name via the ClassId + sal_Int16 nClassId( FormComponentType::CONTROL ); + xSet->getPropertyValue( FM_PROP_CLASSID ) >>= nClassId; + + OUString sDefaultName = FormControlFactory::getDefaultUniqueName_ByComponentType( + Reference< XNameAccess >( xControls, UNO_QUERY ), xSet ); + + // do not overwrite the name of radio buttons that have it! + if (sName.isEmpty() || nClassId != css::form::FormComponentType::RADIOBUTTON) + { + xSet->setPropertyValue(FM_PROP_NAME, makeAny(sDefaultName)); + } + + sName = sDefaultName; + } + } + return sName; +} + + +void FmFormPageImpl::formModelAssigned( const FmFormObj& _object ) +{ + Reference< XMap > xControlShapeMap( m_aControlShapeMap.get(), UNO_QUERY ); + if ( !xControlShapeMap.is() ) + // our map does not exist -> not interested in this event + return; + + try + { + lcl_removeFormObject_throw( _object, xControlShapeMap ); + lcl_insertFormObject_throw( _object, xControlShapeMap ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +void FmFormPageImpl::formObjectInserted( const FmFormObj& _object ) +{ + Reference< XMap > xControlShapeMap( m_aControlShapeMap.get(), UNO_QUERY ); + if ( !xControlShapeMap.is() ) + // our map does not exist -> not interested in this event + return; + + try + { + lcl_insertFormObject_throw( _object, xControlShapeMap ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +void FmFormPageImpl::formObjectRemoved( const FmFormObj& _object ) +{ + Reference< XMap > xControlShapeMap( m_aControlShapeMap.get(), UNO_QUERY ); + if ( !xControlShapeMap.is() ) + // our map does not exist -> not interested in this event + return; + + try + { + lcl_removeFormObject_throw( _object, xControlShapeMap ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmscriptingenv.cxx b/svx/source/form/fmscriptingenv.cxx new file mode 100644 index 000000000..14374f12e --- /dev/null +++ b/svx/source/form/fmscriptingenv.cxx @@ -0,0 +1,1040 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_features.h> + +#include <fmscriptingenv.hxx> +#include <svx/fmmodel.hxx> + +#include <com/sun/star/awt/XControl.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/TerminationVetoException.hpp> +#include <com/sun/star/frame/XTerminateListener.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/EventObject.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/script/XScriptListener.hpp> + +#include <tools/diagnose_ex.h> +#include <tools/debug.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/processfactory.hxx> +#include <vcl/svapp.hxx> +#include <osl/mutex.hxx> +#include <o3tl/sorted_vector.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/app.hxx> +#include <basic/basmgr.hxx> + +#include <memory> +#include <utility> + +using std::pair; + +namespace svxform +{ + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::script::XEventAttacherManager; + using ::com::sun::star::lang::IllegalArgumentException; + using ::com::sun::star::script::XScriptListener; + using ::com::sun::star::script::ScriptEvent; + using ::com::sun::star::lang::EventObject; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::lang::DisposedException; + using ::com::sun::star::uno::RuntimeException; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::awt::XControl; + using ::com::sun::star::beans::XPropertySet; + + //= FormScriptListener + + typedef ::cppu::WeakImplHelper < XScriptListener + > FormScriptListener_Base; + + /** implements the XScriptListener interface, is used by FormScriptingEnvironment + */ + class FormScriptListener :public FormScriptListener_Base + { + private: + ::osl::Mutex m_aMutex; + FormScriptingEnvironment *m_pScriptExecutor; + + public: + explicit FormScriptListener( FormScriptingEnvironment * pScriptExecutor ); + + // XScriptListener + virtual void SAL_CALL firing( const ScriptEvent& aEvent ) override; + virtual Any SAL_CALL approveFiring( const ScriptEvent& aEvent ) override; + // XEventListener + virtual void SAL_CALL disposing( const EventObject& Source ) override; + + // lifetime control + void dispose(); + + protected: + virtual ~FormScriptListener() override; + + private: + /** determines whether calling a given method at a given listener interface can be done asynchronously + + @param _rListenerType + the name of the UNO type whose method is to be checked + @param _rMethodName + the name of the method at the interface determined by _rListenerType + + @return + <TRUE/> if and only if the method is declared <code>oneway</code>, i.e. can be called asynchronously + */ + static bool impl_allowAsynchronousCall_nothrow( const OUString& _rListenerType, const OUString& _rMethodName ); + + /** determines whether the instance is already disposed + */ + bool impl_isDisposed_nothrow() const { return !m_pScriptExecutor; } + + /** fires the given script event in a thread-safe manner + + This methods calls our script executor's doFireScriptEvent, with previously releasing the given mutex guard, + but ensuring that our script executor is not deleted between this release and the actual call. + + @param _rGuard + a clearable guard to our mutex. Must be the only active guard to our mutex. + @param _rEvent + the event to fire + @param _pSynchronousResult + a place to take a possible result of the script call. + + @precond + m_pScriptExecutor is not <NULL/>. + */ + void impl_doFireScriptEvent_nothrow( ::osl::ClearableMutexGuard& _rGuard, const ScriptEvent& _rEvent, Any* _pSynchronousResult ); + + private: + DECL_LINK( OnAsyncScriptEvent, void*, void ); + }; + + FormScriptListener::FormScriptListener( FormScriptingEnvironment* pScriptExecutor ) + :m_pScriptExecutor( pScriptExecutor ) + { + } + + + FormScriptListener::~FormScriptListener() + { + } + + + bool FormScriptListener::impl_allowAsynchronousCall_nothrow( const OUString& _rListenerType, const OUString& _rMethodName ) + { + // This used to be implemented as: + // is (_rListenerType + "::" + _rMethodName) a oneway function? + // since we got rid of the notion of oneway, this is the list + // of oneway methods, autogenerated by postprocessing of + // commitdiff 90eac3e69749a9227c4b6902b1f3cef1e338c6d1 + static const o3tl::sorted_vector<pair<OUString, OUString>> delayed_event_listeners{ + pair<OUString,OUString>("com.sun.star.accessibility.XAccessibleComponent","grabFocus"), + pair<OUString,OUString>("com.sun.star.accessibility.XAccessibleEventBroadcaster","addAccessibleEventListener"), + pair<OUString,OUString>("com.sun.star.accessibility.XAccessibleEventBroadcaster","removeAccessibleEventListener"), + pair<OUString,OUString>("com.sun.star.accessibility.XAccessibleSelection","clearAccessibleSelection"), + pair<OUString,OUString>("com.sun.star.accessibility.XAccessibleSelection","selectAllAccessibleChildren"), + pair<OUString,OUString>("com.sun.star.awt.XActionListener","actionPerformed"), + pair<OUString,OUString>("com.sun.star.awt.XActivateListener","windowActivated"), + pair<OUString,OUString>("com.sun.star.awt.XActivateListener","windowDeactivated"), + pair<OUString,OUString>("com.sun.star.awt.XAdjustmentListener","adjustmentValueChanged"), + pair<OUString,OUString>("com.sun.star.awt.XButton","addActionListener"), + pair<OUString,OUString>("com.sun.star.awt.XButton","removeActionListener"), + pair<OUString,OUString>("com.sun.star.awt.XButton","setLabel"), + pair<OUString,OUString>("com.sun.star.awt.XButton","setActionCommand"), + pair<OUString,OUString>("com.sun.star.awt.XCheckBox","addItemListener"), + pair<OUString,OUString>("com.sun.star.awt.XCheckBox","removeItemListener"), + pair<OUString,OUString>("com.sun.star.awt.XCheckBox","setState"), + pair<OUString,OUString>("com.sun.star.awt.XCheckBox","setLabel"), + pair<OUString,OUString>("com.sun.star.awt.XCheckBox","enableTriState"), + pair<OUString,OUString>("com.sun.star.awt.XComboBox","addItemListener"), + pair<OUString,OUString>("com.sun.star.awt.XComboBox","removeItemListener"), + pair<OUString,OUString>("com.sun.star.awt.XComboBox","addActionListener"), + pair<OUString,OUString>("com.sun.star.awt.XComboBox","removeActionListener"), + pair<OUString,OUString>("com.sun.star.awt.XComboBox","addItem"), + pair<OUString,OUString>("com.sun.star.awt.XComboBox","addItems"), + pair<OUString,OUString>("com.sun.star.awt.XComboBox","removeItems"), + pair<OUString,OUString>("com.sun.star.awt.XComboBox","setDropDownLineCount"), + pair<OUString,OUString>("com.sun.star.awt.XControl","setContext"), + pair<OUString,OUString>("com.sun.star.awt.XControl","createPeer"), + pair<OUString,OUString>("com.sun.star.awt.XControl","setDesignMode"), + pair<OUString,OUString>("com.sun.star.awt.XControlContainer","setStatusText"), + pair<OUString,OUString>("com.sun.star.awt.XControlContainer","addControl"), + pair<OUString,OUString>("com.sun.star.awt.XControlContainer","removeControl"), + pair<OUString,OUString>("com.sun.star.awt.XCurrencyField","setValue"), + pair<OUString,OUString>("com.sun.star.awt.XCurrencyField","setMin"), + pair<OUString,OUString>("com.sun.star.awt.XCurrencyField","setMax"), + pair<OUString,OUString>("com.sun.star.awt.XCurrencyField","setFirst"), + pair<OUString,OUString>("com.sun.star.awt.XCurrencyField","setLast"), + pair<OUString,OUString>("com.sun.star.awt.XCurrencyField","setSpinSize"), + pair<OUString,OUString>("com.sun.star.awt.XCurrencyField","setDecimalDigits"), + pair<OUString,OUString>("com.sun.star.awt.XCurrencyField","setStrictFormat"), + pair<OUString,OUString>("com.sun.star.awt.XDateField","setDate"), + pair<OUString,OUString>("com.sun.star.awt.XDateField","setMin"), + pair<OUString,OUString>("com.sun.star.awt.XDateField","setMax"), + pair<OUString,OUString>("com.sun.star.awt.XDateField","setFirst"), + pair<OUString,OUString>("com.sun.star.awt.XDateField","setLast"), + pair<OUString,OUString>("com.sun.star.awt.XDateField","setLongFormat"), + pair<OUString,OUString>("com.sun.star.awt.XDateField","setStrictFormat"), + pair<OUString,OUString>("com.sun.star.awt.XDialog","setTitle"), + pair<OUString,OUString>("com.sun.star.awt.XDisplayConnection","addEventHandler"), + pair<OUString,OUString>("com.sun.star.awt.XDisplayConnection","removeEventHandler"), + pair<OUString,OUString>("com.sun.star.awt.XDisplayConnection","addErrorHandler"), + pair<OUString,OUString>("com.sun.star.awt.XDisplayConnection","removeErrorHandler"), + pair<OUString,OUString>("com.sun.star.awt.XExtendedToolkit","addTopWindowListener"), + pair<OUString,OUString>("com.sun.star.awt.XExtendedToolkit","removeTopWindowListener"), + pair<OUString,OUString>("com.sun.star.awt.XExtendedToolkit","addKeyHandler"), + pair<OUString,OUString>("com.sun.star.awt.XExtendedToolkit","removeKeyHandler"), + pair<OUString,OUString>("com.sun.star.awt.XExtendedToolkit","addFocusListener"), + pair<OUString,OUString>("com.sun.star.awt.XExtendedToolkit","removeFocusListener"), + pair<OUString,OUString>("com.sun.star.awt.XExtendedToolkit","fireFocusGained"), + pair<OUString,OUString>("com.sun.star.awt.XExtendedToolkit","fireFocusLost"), + pair<OUString,OUString>("com.sun.star.awt.XFileDialog","setPath"), + pair<OUString,OUString>("com.sun.star.awt.XFileDialog","setFilters"), + pair<OUString,OUString>("com.sun.star.awt.XFileDialog","setCurrentFilter"), + pair<OUString,OUString>("com.sun.star.awt.XFixedHyperlink","setText"), + pair<OUString,OUString>("com.sun.star.awt.XFixedHyperlink","setURL"), + pair<OUString,OUString>("com.sun.star.awt.XFixedHyperlink","setAlignment"), + pair<OUString,OUString>("com.sun.star.awt.XFixedHyperlink","addActionListener"), + pair<OUString,OUString>("com.sun.star.awt.XFixedHyperlink","removeActionListener"), + pair<OUString,OUString>("com.sun.star.awt.XFixedText","setText"), + pair<OUString,OUString>("com.sun.star.awt.XFixedText","setAlignment"), + pair<OUString,OUString>("com.sun.star.awt.XFocusListener","focusGained"), + pair<OUString,OUString>("com.sun.star.awt.XFocusListener","focusLost"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","setFont"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","selectFont"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","setTextColor"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","setTextFillColor"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","setLineColor"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","setFillColor"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","setRasterOp"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","setClipRegion"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","intersectClipRegion"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","push"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","pop"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","copy"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","draw"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","drawPixel"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","drawLine"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","drawRect"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","drawRoundedRect"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","drawPolyLine"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","drawPolygon"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","drawPolyPolygon"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","drawEllipse"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","drawArc"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","drawPie"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","drawChord"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","drawGradient"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","drawText"), + pair<OUString,OUString>("com.sun.star.awt.XGraphics","drawTextArray"), + pair<OUString,OUString>("com.sun.star.awt.XImageButton","addActionListener"), + pair<OUString,OUString>("com.sun.star.awt.XImageButton","removeActionListener"), + pair<OUString,OUString>("com.sun.star.awt.XImageButton","setActionCommand"), + pair<OUString,OUString>("com.sun.star.awt.XImageConsumer","init"), + pair<OUString,OUString>("com.sun.star.awt.XImageConsumer","setColorModel"), + pair<OUString,OUString>("com.sun.star.awt.XImageConsumer","setPixelsByBytes"), + pair<OUString,OUString>("com.sun.star.awt.XImageConsumer","setPixelsByLongs"), + pair<OUString,OUString>("com.sun.star.awt.XImageConsumer","complete"), + pair<OUString,OUString>("com.sun.star.awt.XImageProducer","addConsumer"), + pair<OUString,OUString>("com.sun.star.awt.XImageProducer","removeConsumer"), + pair<OUString,OUString>("com.sun.star.awt.XImageProducer","startProduction"), + pair<OUString,OUString>("com.sun.star.awt.XItemEventBroadcaster","addItemListener"), + pair<OUString,OUString>("com.sun.star.awt.XItemEventBroadcaster","removeItemListener"), + pair<OUString,OUString>("com.sun.star.awt.XItemListener","itemStateChanged"), + pair<OUString,OUString>("com.sun.star.awt.XKeyListener","keyPressed"), + pair<OUString,OUString>("com.sun.star.awt.XKeyListener","keyReleased"), + pair<OUString,OUString>("com.sun.star.awt.XListBox","addItemListener"), + pair<OUString,OUString>("com.sun.star.awt.XListBox","removeItemListener"), + pair<OUString,OUString>("com.sun.star.awt.XListBox","addActionListener"), + pair<OUString,OUString>("com.sun.star.awt.XListBox","removeActionListener"), + pair<OUString,OUString>("com.sun.star.awt.XListBox","addItem"), + pair<OUString,OUString>("com.sun.star.awt.XListBox","addItems"), + pair<OUString,OUString>("com.sun.star.awt.XListBox","removeItems"), + pair<OUString,OUString>("com.sun.star.awt.XListBox","selectItemPos"), + pair<OUString,OUString>("com.sun.star.awt.XListBox","selectItemsPos"), + pair<OUString,OUString>("com.sun.star.awt.XListBox","selectItem"), + pair<OUString,OUString>("com.sun.star.awt.XListBox","setMultipleMode"), + pair<OUString,OUString>("com.sun.star.awt.XListBox","setDropDownLineCount"), + pair<OUString,OUString>("com.sun.star.awt.XListBox","makeVisible"), + pair<OUString,OUString>("com.sun.star.awt.XMenu","addMenuListener"), + pair<OUString,OUString>("com.sun.star.awt.XMenu","removeMenuListener"), + pair<OUString,OUString>("com.sun.star.awt.XMenu","insertItem"), + pair<OUString,OUString>("com.sun.star.awt.XMenu","removeItem"), + pair<OUString,OUString>("com.sun.star.awt.XMenu","enableItem"), + pair<OUString,OUString>("com.sun.star.awt.XMenu","setItemText"), + pair<OUString,OUString>("com.sun.star.awt.XMenu","setPopupMenu"), + pair<OUString,OUString>("com.sun.star.awt.XMenuListener","highlight"), + pair<OUString,OUString>("com.sun.star.awt.XMenuListener","select"), + pair<OUString,OUString>("com.sun.star.awt.XMenuListener","activate"), + pair<OUString,OUString>("com.sun.star.awt.XMenuListener","deactivate"), + pair<OUString,OUString>("com.sun.star.awt.XMessageBox","setCaptionText"), + pair<OUString,OUString>("com.sun.star.awt.XMessageBox","setMessageText"), + pair<OUString,OUString>("com.sun.star.awt.XMouseListener","mousePressed"), + pair<OUString,OUString>("com.sun.star.awt.XMouseListener","mouseReleased"), + pair<OUString,OUString>("com.sun.star.awt.XMouseListener","mouseEntered"), + pair<OUString,OUString>("com.sun.star.awt.XMouseListener","mouseExited"), + pair<OUString,OUString>("com.sun.star.awt.XNumericField","setValue"), + pair<OUString,OUString>("com.sun.star.awt.XNumericField","setMin"), + pair<OUString,OUString>("com.sun.star.awt.XNumericField","setMax"), + pair<OUString,OUString>("com.sun.star.awt.XNumericField","setFirst"), + pair<OUString,OUString>("com.sun.star.awt.XNumericField","setLast"), + pair<OUString,OUString>("com.sun.star.awt.XNumericField","setSpinSize"), + pair<OUString,OUString>("com.sun.star.awt.XNumericField","setDecimalDigits"), + pair<OUString,OUString>("com.sun.star.awt.XNumericField","setStrictFormat"), + pair<OUString,OUString>("com.sun.star.awt.XPaintListener","windowPaint"), + pair<OUString,OUString>("com.sun.star.awt.XPatternField","setMasks"), + pair<OUString,OUString>("com.sun.star.awt.XPatternField","setString"), + pair<OUString,OUString>("com.sun.star.awt.XPatternField","setStrictFormat"), + pair<OUString,OUString>("com.sun.star.awt.XPointer","setType"), + pair<OUString,OUString>("com.sun.star.awt.XPopupMenu","insertSeparator"), + pair<OUString,OUString>("com.sun.star.awt.XPopupMenu","setDefaultItem"), + pair<OUString,OUString>("com.sun.star.awt.XPopupMenu","checkItem"), + pair<OUString,OUString>("com.sun.star.awt.XProgressBar","setForegroundColor"), + pair<OUString,OUString>("com.sun.star.awt.XProgressBar","setBackgroundColor"), + pair<OUString,OUString>("com.sun.star.awt.XProgressBar","setRange"), + pair<OUString,OUString>("com.sun.star.awt.XProgressBar","setValue"), + pair<OUString,OUString>("com.sun.star.awt.XProgressMonitor","addText"), + pair<OUString,OUString>("com.sun.star.awt.XProgressMonitor","removeText"), + pair<OUString,OUString>("com.sun.star.awt.XProgressMonitor","updateText"), + pair<OUString,OUString>("com.sun.star.awt.XRadioButton","addItemListener"), + pair<OUString,OUString>("com.sun.star.awt.XRadioButton","removeItemListener"), + pair<OUString,OUString>("com.sun.star.awt.XRadioButton","setState"), + pair<OUString,OUString>("com.sun.star.awt.XRadioButton","setLabel"), + pair<OUString,OUString>("com.sun.star.awt.XRegion","clear"), + pair<OUString,OUString>("com.sun.star.awt.XRegion","move"), + pair<OUString,OUString>("com.sun.star.awt.XRegion","unionRectangle"), + pair<OUString,OUString>("com.sun.star.awt.XRegion","intersectRectangle"), + pair<OUString,OUString>("com.sun.star.awt.XRegion","excludeRectangle"), + pair<OUString,OUString>("com.sun.star.awt.XRegion","xOrRectangle"), + pair<OUString,OUString>("com.sun.star.awt.XRegion","unionRegion"), + pair<OUString,OUString>("com.sun.star.awt.XRegion","intersectRegion"), + pair<OUString,OUString>("com.sun.star.awt.XRegion","excludeRegion"), + pair<OUString,OUString>("com.sun.star.awt.XRegion","xOrRegion"), + pair<OUString,OUString>("com.sun.star.awt.XScrollBar","addAdjustmentListener"), + pair<OUString,OUString>("com.sun.star.awt.XScrollBar","removeAdjustmentListener"), + pair<OUString,OUString>("com.sun.star.awt.XScrollBar","setValue"), + pair<OUString,OUString>("com.sun.star.awt.XScrollBar","setValues"), + pair<OUString,OUString>("com.sun.star.awt.XScrollBar","setMaximum"), + pair<OUString,OUString>("com.sun.star.awt.XScrollBar","setLineIncrement"), + pair<OUString,OUString>("com.sun.star.awt.XScrollBar","setBlockIncrement"), + pair<OUString,OUString>("com.sun.star.awt.XScrollBar","setVisibleSize"), + pair<OUString,OUString>("com.sun.star.awt.XScrollBar","setOrientation"), + pair<OUString,OUString>("com.sun.star.awt.XSpinField","addSpinListener"), + pair<OUString,OUString>("com.sun.star.awt.XSpinField","removeSpinListener"), + pair<OUString,OUString>("com.sun.star.awt.XSpinField","up"), + pair<OUString,OUString>("com.sun.star.awt.XSpinField","down"), + pair<OUString,OUString>("com.sun.star.awt.XSpinField","first"), + pair<OUString,OUString>("com.sun.star.awt.XSpinField","last"), + pair<OUString,OUString>("com.sun.star.awt.XSpinField","enableRepeat"), + pair<OUString,OUString>("com.sun.star.awt.XSpinListener","up"), + pair<OUString,OUString>("com.sun.star.awt.XSpinListener","down"), + pair<OUString,OUString>("com.sun.star.awt.XSpinListener","first"), + pair<OUString,OUString>("com.sun.star.awt.XSpinListener","last"), + pair<OUString,OUString>("com.sun.star.awt.XSpinValue","addAdjustmentListener"), + pair<OUString,OUString>("com.sun.star.awt.XSpinValue","removeAdjustmentListener"), + pair<OUString,OUString>("com.sun.star.awt.XSpinValue","setValue"), + pair<OUString,OUString>("com.sun.star.awt.XSpinValue","setValues"), + pair<OUString,OUString>("com.sun.star.awt.XSpinValue","setMinimum"), + pair<OUString,OUString>("com.sun.star.awt.XSpinValue","setMaximum"), + pair<OUString,OUString>("com.sun.star.awt.XSpinValue","setSpinIncrement"), + pair<OUString,OUString>("com.sun.star.awt.XTabController","setModel"), + pair<OUString,OUString>("com.sun.star.awt.XTabController","setContainer"), + pair<OUString,OUString>("com.sun.star.awt.XTabController","autoTabOrder"), + pair<OUString,OUString>("com.sun.star.awt.XTabController","activateTabOrder"), + pair<OUString,OUString>("com.sun.star.awt.XTabController","activateFirst"), + pair<OUString,OUString>("com.sun.star.awt.XTabController","activateLast"), + pair<OUString,OUString>("com.sun.star.awt.XTabControllerModel","setGroupControl"), + pair<OUString,OUString>("com.sun.star.awt.XTabControllerModel","setControlModels"), + pair<OUString,OUString>("com.sun.star.awt.XTabControllerModel","setGroup"), + pair<OUString,OUString>("com.sun.star.awt.XTextComponent","addTextListener"), + pair<OUString,OUString>("com.sun.star.awt.XTextComponent","removeTextListener"), + pair<OUString,OUString>("com.sun.star.awt.XTextComponent","setText"), + pair<OUString,OUString>("com.sun.star.awt.XTextComponent","insertText"), + pair<OUString,OUString>("com.sun.star.awt.XTextComponent","setSelection"), + pair<OUString,OUString>("com.sun.star.awt.XTextComponent","setEditable"), + pair<OUString,OUString>("com.sun.star.awt.XTextComponent","setMaxTextLen"), + pair<OUString,OUString>("com.sun.star.awt.XTextEditField","setEchoChar"), + pair<OUString,OUString>("com.sun.star.awt.XTextListener","textChanged"), + pair<OUString,OUString>("com.sun.star.awt.XTimeField","setTime"), + pair<OUString,OUString>("com.sun.star.awt.XTimeField","setMin"), + pair<OUString,OUString>("com.sun.star.awt.XTimeField","setMax"), + pair<OUString,OUString>("com.sun.star.awt.XTimeField","setFirst"), + pair<OUString,OUString>("com.sun.star.awt.XTimeField","setLast"), + pair<OUString,OUString>("com.sun.star.awt.XTimeField","setStrictFormat"), + pair<OUString,OUString>("com.sun.star.awt.XTopWindow","addTopWindowListener"), + pair<OUString,OUString>("com.sun.star.awt.XTopWindow","removeTopWindowListener"), + pair<OUString,OUString>("com.sun.star.awt.XTopWindow","toFront"), + pair<OUString,OUString>("com.sun.star.awt.XTopWindow","toBack"), + pair<OUString,OUString>("com.sun.star.awt.XTopWindow","setMenuBar"), + pair<OUString,OUString>("com.sun.star.awt.XTopWindowListener","windowOpened"), + pair<OUString,OUString>("com.sun.star.awt.XTopWindowListener","windowClosing"), + pair<OUString,OUString>("com.sun.star.awt.XTopWindowListener","windowClosed"), + pair<OUString,OUString>("com.sun.star.awt.XTopWindowListener","windowMinimized"), + pair<OUString,OUString>("com.sun.star.awt.XTopWindowListener","windowNormalized"), + pair<OUString,OUString>("com.sun.star.awt.XTopWindowListener","windowActivated"), + pair<OUString,OUString>("com.sun.star.awt.XTopWindowListener","windowDeactivated"), + pair<OUString,OUString>("com.sun.star.awt.XUnoControlContainer","setTabControllers"), + pair<OUString,OUString>("com.sun.star.awt.XUnoControlContainer","addTabController"), + pair<OUString,OUString>("com.sun.star.awt.XUnoControlContainer","removeTabController"), + pair<OUString,OUString>("com.sun.star.awt.XUserInputInterception","addKeyHandler"), + pair<OUString,OUString>("com.sun.star.awt.XUserInputInterception","removeKeyHandler"), + pair<OUString,OUString>("com.sun.star.awt.XUserInputInterception","addMouseClickHandler"), + pair<OUString,OUString>("com.sun.star.awt.XUserInputInterception","removeMouseClickHandler"), + pair<OUString,OUString>("com.sun.star.awt.XVclContainer","addVclContainerListener"), + pair<OUString,OUString>("com.sun.star.awt.XVclContainer","removeVclContainerListener"), + pair<OUString,OUString>("com.sun.star.awt.XVclContainerListener","windowAdded"), + pair<OUString,OUString>("com.sun.star.awt.XVclContainerListener","windowRemoved"), + pair<OUString,OUString>("com.sun.star.awt.XVclContainerPeer","enableDialogControl"), + pair<OUString,OUString>("com.sun.star.awt.XVclContainerPeer","setTabOrder"), + pair<OUString,OUString>("com.sun.star.awt.XVclContainerPeer","setGroup"), + pair<OUString,OUString>("com.sun.star.awt.XVclWindowPeer","setDesignMode"), + pair<OUString,OUString>("com.sun.star.awt.XVclWindowPeer","enableClipSiblings"), + pair<OUString,OUString>("com.sun.star.awt.XVclWindowPeer","setForeground"), + pair<OUString,OUString>("com.sun.star.awt.XVclWindowPeer","setControlFont"), + pair<OUString,OUString>("com.sun.star.awt.XView","draw"), + pair<OUString,OUString>("com.sun.star.awt.XView","setZoom"), + pair<OUString,OUString>("com.sun.star.awt.XWindow","setPosSize"), + pair<OUString,OUString>("com.sun.star.awt.XWindow","setVisible"), + pair<OUString,OUString>("com.sun.star.awt.XWindow","setEnable"), + pair<OUString,OUString>("com.sun.star.awt.XWindow","setFocus"), + pair<OUString,OUString>("com.sun.star.awt.XWindow","addWindowListener"), + pair<OUString,OUString>("com.sun.star.awt.XWindow","removeWindowListener"), + pair<OUString,OUString>("com.sun.star.awt.XWindow","addFocusListener"), + pair<OUString,OUString>("com.sun.star.awt.XWindow","removeFocusListener"), + pair<OUString,OUString>("com.sun.star.awt.XWindow","addKeyListener"), + pair<OUString,OUString>("com.sun.star.awt.XWindow","removeKeyListener"), + pair<OUString,OUString>("com.sun.star.awt.XWindow","addMouseListener"), + pair<OUString,OUString>("com.sun.star.awt.XWindow","removeMouseListener"), + pair<OUString,OUString>("com.sun.star.awt.XWindow","addMouseMotionListener"), + pair<OUString,OUString>("com.sun.star.awt.XWindow","removeMouseMotionListener"), + pair<OUString,OUString>("com.sun.star.awt.XWindow","addPaintListener"), + pair<OUString,OUString>("com.sun.star.awt.XWindow","removePaintListener"), + pair<OUString,OUString>("com.sun.star.awt.XWindowListener","windowResized"), + pair<OUString,OUString>("com.sun.star.awt.XWindowListener","windowMoved"), + pair<OUString,OUString>("com.sun.star.awt.XWindowListener","windowShown"), + pair<OUString,OUString>("com.sun.star.awt.XWindowListener","windowHidden"), + pair<OUString,OUString>("com.sun.star.awt.XWindowListener2","windowEnabled"), + pair<OUString,OUString>("com.sun.star.awt.XWindowListener2","windowDisabled"), + pair<OUString,OUString>("com.sun.star.awt.XWindowPeer","setPointer"), + pair<OUString,OUString>("com.sun.star.awt.XWindowPeer","setBackground"), + pair<OUString,OUString>("com.sun.star.awt.XWindowPeer","invalidate"), + pair<OUString,OUString>("com.sun.star.awt.XWindowPeer","invalidateRect"), + pair<OUString,OUString>("com.sun.star.awt.grid.XGridSelectionListener","selectionChanged"), + pair<OUString,OUString>("com.sun.star.awt.tab.XTabPageContainer","addTabPageContainerListener"), + pair<OUString,OUString>("com.sun.star.awt.tab.XTabPageContainer","removeTabPageContainerListener"), + pair<OUString,OUString>("com.sun.star.awt.tab.XTabPageContainerListener","tabPageActivated"), + pair<OUString,OUString>("com.sun.star.configuration.backend.XBackendChangesNotifier","addChangesListener"), + pair<OUString,OUString>("com.sun.star.configuration.backend.XBackendChangesNotifier","removeChangesListener"), + pair<OUString,OUString>("com.sun.star.datatransfer.clipboard.XClipboard","setContents"), + pair<OUString,OUString>("com.sun.star.datatransfer.clipboard.XClipboardListener","changedContents"), + pair<OUString,OUString>("com.sun.star.datatransfer.clipboard.XClipboardNotifier","addClipboardListener"), + pair<OUString,OUString>("com.sun.star.datatransfer.clipboard.XClipboardNotifier","removeClipboardListener"), + pair<OUString,OUString>("com.sun.star.datatransfer.clipboard.XClipboardOwner","lostOwnership"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XAutoscroll","autoscroll"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDragGestureListener","dragGestureRecognized"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDragGestureRecognizer","addDragGestureListener"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDragGestureRecognizer","removeDragGestureListener"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDragSource","startDrag"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDragSourceContext","setCursor"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDragSourceContext","setImage"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDragSourceContext","transferablesFlavorsChanged"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDragSourceListener","dragDropEnd"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDragSourceListener","dragEnter"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDragSourceListener","dragExit"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDragSourceListener","dragOver"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDragSourceListener","dropActionChanged"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDropTarget","addDropTargetListener"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDropTarget","removeDropTargetListener"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDropTarget","setDefaultActions"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDropTargetDragContext","acceptDrag"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDropTargetDragContext","rejectDrag"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDropTargetDropContext","acceptDrop"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDropTargetDropContext","rejectDrop"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDropTargetDropContext","dropComplete"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDropTargetListener","dragEnter"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDropTargetListener","dragExit"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDropTargetListener","dragOver"), + pair<OUString,OUString>("com.sun.star.datatransfer.dnd.XDropTargetListener","dropActionChanged"), + pair<OUString,OUString>("com.sun.star.document.XEventBroadcaster","addEventListener"), + pair<OUString,OUString>("com.sun.star.document.XEventBroadcaster","removeEventListener"), + pair<OUString,OUString>("com.sun.star.document.XEventListener","notifyEvent"), + pair<OUString,OUString>("com.sun.star.document.XStorageChangeListener","notifyStorageChange"), + pair<OUString,OUString>("com.sun.star.drawing.XControlShape","setControl"), + pair<OUString,OUString>("com.sun.star.form.XApproveActionBroadcaster","addApproveActionListener"), + pair<OUString,OUString>("com.sun.star.form.XApproveActionBroadcaster","removeApproveActionListener"), + pair<OUString,OUString>("com.sun.star.form.XBoundControl","setLock"), + pair<OUString,OUString>("com.sun.star.form.XChangeBroadcaster","addChangeListener"), + pair<OUString,OUString>("com.sun.star.form.XChangeBroadcaster","removeChangeListener"), + pair<OUString,OUString>("com.sun.star.form.XChangeListener","changed"), + pair<OUString,OUString>("com.sun.star.form.XConfirmDeleteBroadcaster","addConfirmDeleteListener"), + pair<OUString,OUString>("com.sun.star.form.XConfirmDeleteBroadcaster","removeConfirmDeleteListener"), + pair<OUString,OUString>("com.sun.star.form.XDatabaseParameterBroadcaster","addParameterListener"), + pair<OUString,OUString>("com.sun.star.form.XDatabaseParameterBroadcaster","removeParameterListener"), + pair<OUString,OUString>("com.sun.star.form.XDatabaseParameterBroadcaster2","addDatabaseParameterListener"), + pair<OUString,OUString>("com.sun.star.form.XDatabaseParameterBroadcaster2","removeDatabaseParameterListener"), + pair<OUString,OUString>("com.sun.star.form.XErrorBroadcaster","addErrorListener"), + pair<OUString,OUString>("com.sun.star.form.XErrorBroadcaster","removeErrorListener"), + pair<OUString,OUString>("com.sun.star.form.XFormController","addActivateListener"), + pair<OUString,OUString>("com.sun.star.form.XFormController","removeActivateListener"), + pair<OUString,OUString>("com.sun.star.form.XFormControllerListener","formActivated"), + pair<OUString,OUString>("com.sun.star.form.XFormControllerListener","formDeactivated"), + pair<OUString,OUString>("com.sun.star.form.XGrid","setCurrentColumnPosition"), + pair<OUString,OUString>("com.sun.star.form.XGridPeer","setColumns"), + pair<OUString,OUString>("com.sun.star.form.XLoadListener","loaded"), + pair<OUString,OUString>("com.sun.star.form.XLoadListener","unloading"), + pair<OUString,OUString>("com.sun.star.form.XLoadListener","unloaded"), + pair<OUString,OUString>("com.sun.star.form.XLoadListener","reloading"), + pair<OUString,OUString>("com.sun.star.form.XLoadListener","reloaded"), + pair<OUString,OUString>("com.sun.star.form.XLoadable","load"), + pair<OUString,OUString>("com.sun.star.form.XLoadable","unload"), + pair<OUString,OUString>("com.sun.star.form.XLoadable","reload"), + pair<OUString,OUString>("com.sun.star.form.XLoadable","addLoadListener"), + pair<OUString,OUString>("com.sun.star.form.XLoadable","removeLoadListener"), + pair<OUString,OUString>("com.sun.star.form.XPositioningListener","positioned"), + pair<OUString,OUString>("com.sun.star.form.XReset","reset"), + pair<OUString,OUString>("com.sun.star.form.XReset","addResetListener"), + pair<OUString,OUString>("com.sun.star.form.XReset","removeResetListener"), + pair<OUString,OUString>("com.sun.star.form.XResetListener","resetted"), + pair<OUString,OUString>("com.sun.star.form.XSubmit","submit"), + pair<OUString,OUString>("com.sun.star.form.XSubmit","addSubmitListener"), + pair<OUString,OUString>("com.sun.star.form.XSubmit","removeSubmitListener"), + pair<OUString,OUString>("com.sun.star.form.XUpdateBroadcaster","addUpdateListener"), + pair<OUString,OUString>("com.sun.star.form.XUpdateBroadcaster","removeUpdateListener"), + pair<OUString,OUString>("com.sun.star.frame.XBrowseHistoryRegistry","updateViewData"), + pair<OUString,OUString>("com.sun.star.frame.XBrowseHistoryRegistry","createNewEntry"), + pair<OUString,OUString>("com.sun.star.frame.XConfigManager","addPropertyChangeListener"), + pair<OUString,OUString>("com.sun.star.frame.XConfigManager","removePropertyChangeListener"), + pair<OUString,OUString>("com.sun.star.frame.XConfigManager","flush"), + pair<OUString,OUString>("com.sun.star.frame.XDesktop","addTerminateListener"), + pair<OUString,OUString>("com.sun.star.frame.XDesktop","removeTerminateListener"), + pair<OUString,OUString>("com.sun.star.frame.XDispatch","dispatch"), + pair<OUString,OUString>("com.sun.star.frame.XDispatch","addStatusListener"), + pair<OUString,OUString>("com.sun.star.frame.XDispatch","removeStatusListener"), + pair<OUString,OUString>("com.sun.star.frame.XDocumentTemplates","update"), + pair<OUString,OUString>("com.sun.star.frame.XFrame","setCreator"), + pair<OUString,OUString>("com.sun.star.frame.XFrame","setName"), + pair<OUString,OUString>("com.sun.star.frame.XFrame","activate"), + pair<OUString,OUString>("com.sun.star.frame.XFrame","deactivate"), + pair<OUString,OUString>("com.sun.star.frame.XFrame","addFrameActionListener"), + pair<OUString,OUString>("com.sun.star.frame.XFrame","removeFrameActionListener"), + pair<OUString,OUString>("com.sun.star.frame.XFrameActionListener","frameAction"), + pair<OUString,OUString>("com.sun.star.frame.XFrameLoader","load"), + pair<OUString,OUString>("com.sun.star.frame.XFrameLoader","cancel"), + pair<OUString,OUString>("com.sun.star.frame.XLoadEventListener","loadFinished"), + pair<OUString,OUString>("com.sun.star.frame.XLoadEventListener","loadCancelled"), + pair<OUString,OUString>("com.sun.star.frame.XModel","connectController"), + pair<OUString,OUString>("com.sun.star.frame.XModel","disconnectController"), + pair<OUString,OUString>("com.sun.star.frame.XModel","lockControllers"), + pair<OUString,OUString>("com.sun.star.frame.XModel","unlockControllers"), + pair<OUString,OUString>("com.sun.star.frame.XNotifyingDispatch","dispatchWithNotification"), + pair<OUString,OUString>("com.sun.star.frame.XRecordableDispatch","dispatchAndRecord"), + pair<OUString,OUString>("com.sun.star.frame.XSessionManagerClient","addSessionManagerListener"), + pair<OUString,OUString>("com.sun.star.frame.XSessionManagerClient","removeSessionManagerListener"), + pair<OUString,OUString>("com.sun.star.frame.XSessionManagerClient","queryInteraction"), + pair<OUString,OUString>("com.sun.star.frame.XSessionManagerClient","interactionDone"), + pair<OUString,OUString>("com.sun.star.frame.XSessionManagerClient","saveDone"), + pair<OUString,OUString>("com.sun.star.frame.XSessionManagerListener","doSave"), + pair<OUString,OUString>("com.sun.star.frame.XSessionManagerListener","approveInteraction"), + pair<OUString,OUString>("com.sun.star.frame.XSessionManagerListener","shutdownCanceled"), + pair<OUString,OUString>("com.sun.star.frame.XSessionManagerListener2","doQuit"), + pair<OUString,OUString>("com.sun.star.frame.XStatusListener","statusChanged"), + pair<OUString,OUString>("com.sun.star.frame.XTask","tileWindows"), + pair<OUString,OUString>("com.sun.star.frame.XTask","arrangeWindowsVertical"), + pair<OUString,OUString>("com.sun.star.frame.XTask","arrangeWindowsHorizontal"), + pair<OUString,OUString>("com.sun.star.frame.XWindowArranger","arrange"), + pair<OUString,OUString>("com.sun.star.inspection.XPropertyControlContext","activateNextControl"), + pair<OUString,OUString>("com.sun.star.inspection.XPropertyControlObserver","focusGained"), + pair<OUString,OUString>("com.sun.star.inspection.XPropertyControlObserver","valueChanged"), + pair<OUString,OUString>("com.sun.star.mozilla.XCloseSessionListener","sessionClosed"), + pair<OUString,OUString>("com.sun.star.mozilla.XMenuProxy","addMenuProxyListener"), + pair<OUString,OUString>("com.sun.star.mozilla.XMenuProxy","removeMenuProxyListener"), + pair<OUString,OUString>("com.sun.star.mozilla.XPluginInstance","start"), + pair<OUString,OUString>("com.sun.star.mozilla.XPluginInstance","stop"), + pair<OUString,OUString>("com.sun.star.mozilla.XPluginInstance","destroy"), + pair<OUString,OUString>("com.sun.star.mozilla.XPluginInstance","createWindow"), + pair<OUString,OUString>("com.sun.star.mozilla.XPluginInstance","newStream"), + pair<OUString,OUString>("com.sun.star.mozilla.XPluginInstance","newURL"), + pair<OUString,OUString>("com.sun.star.mozilla.XPluginInstanceNotifySink","notifyURL"), + pair<OUString,OUString>("com.sun.star.mozilla.XPluginInstancePeer","showStatusMessage"), + pair<OUString,OUString>("com.sun.star.mozilla.XPluginInstancePeer","enableScripting"), + pair<OUString,OUString>("com.sun.star.mozilla.XPluginInstancePeer","newStream"), + pair<OUString,OUString>("com.sun.star.mozilla.XPluginInstancePeer","getURL"), + pair<OUString,OUString>("com.sun.star.mozilla.XPluginInstancePeer","postURL"), + pair<OUString,OUString>("com.sun.star.mozilla.XPluginWindowPeer","setChildWindow"), + pair<OUString,OUString>("com.sun.star.script.vba.XVBACompatibility","addVBAScriptListener"), + pair<OUString,OUString>("com.sun.star.script.vba.XVBACompatibility","removeVBAScriptListener"), + pair<OUString,OUString>("com.sun.star.sdb.XDatabaseAccess","addDatabaseAccessListener"), + pair<OUString,OUString>("com.sun.star.sdb.XDatabaseAccess","removeDatabaseAccessListener"), + pair<OUString,OUString>("com.sun.star.sdb.XDatabaseAccessListener","connectionChanged"), + pair<OUString,OUString>("com.sun.star.sdb.XDatabaseAccessListener","connectionClosing"), + pair<OUString,OUString>("com.sun.star.sdb.XRowSetApproveBroadcaster","addRowSetApproveListener"), + pair<OUString,OUString>("com.sun.star.sdb.XRowSetApproveBroadcaster","removeRowSetApproveListener"), + pair<OUString,OUString>("com.sun.star.sdb.XRowSetChangeListener","onRowSetChanged"), + pair<OUString,OUString>("com.sun.star.sdb.XRowSetSupplier","setRowSet"), + pair<OUString,OUString>("com.sun.star.sdb.XRowsChangeListener","rowsChanged"), + pair<OUString,OUString>("com.sun.star.sdb.XSQLErrorBroadcaster","addSQLErrorListener"), + pair<OUString,OUString>("com.sun.star.sdb.XSQLErrorBroadcaster","removeSQLErrorListener"), + pair<OUString,OUString>("com.sun.star.sdbc.XRowSet","addRowSetListener"), + pair<OUString,OUString>("com.sun.star.sdbc.XRowSet","removeRowSetListener"), + pair<OUString,OUString>("com.sun.star.sdbc.XRowSetListener","cursorMoved"), + pair<OUString,OUString>("com.sun.star.sdbc.XRowSetListener","rowChanged"), + pair<OUString,OUString>("com.sun.star.sdbc.XRowSetListener","rowSetChanged"), + pair<OUString,OUString>("com.sun.star.sheet.XCalculatable","enableAutomaticCalculation"), + pair<OUString,OUString>("com.sun.star.sheet.XVolatileResult","addResultListener"), + pair<OUString,OUString>("com.sun.star.sheet.XVolatileResult","removeResultListener"), + pair<OUString,OUString>("com.sun.star.task.XJobExecutor","trigger"), + pair<OUString,OUString>("com.sun.star.task.XStatusIndicator","start"), + pair<OUString,OUString>("com.sun.star.task.XStatusIndicator","end"), + pair<OUString,OUString>("com.sun.star.task.XStatusIndicator","setText"), + pair<OUString,OUString>("com.sun.star.task.XStatusIndicator","setValue"), + pair<OUString,OUString>("com.sun.star.task.XStatusIndicator","reset"), + pair<OUString,OUString>("com.sun.star.text.XSimpleText","insertString"), + pair<OUString,OUString>("com.sun.star.text.XTextCursor","collapseToStart"), + pair<OUString,OUString>("com.sun.star.text.XTextCursor","collapseToEnd"), + pair<OUString,OUString>("com.sun.star.text.XTextRange","setString"), + pair<OUString,OUString>("com.sun.star.text.XTextViewCursor","setVisible"), + pair<OUString,OUString>("com.sun.star.ucb.XCommandProcessor","abort"), + pair<OUString,OUString>("com.sun.star.ucb.XCommandProcessor2","releaseCommandIdentifier"), + pair<OUString,OUString>("com.sun.star.ucb.XContent","addContentEventListener"), + pair<OUString,OUString>("com.sun.star.ucb.XContent","removeContentEventListener"), + pair<OUString,OUString>("com.sun.star.ucb.XContentProviderManager","deregisterContentProvider"), + pair<OUString,OUString>("com.sun.star.ucb.XContentTransmitter","transmit"), + pair<OUString,OUString>("com.sun.star.ucb.XPropertySetRegistry","removePropertySet"), + pair<OUString,OUString>("com.sun.star.ui.XUIConfigurationListener","elementInserted"), + pair<OUString,OUString>("com.sun.star.ui.XUIConfigurationListener","elementRemoved"), + pair<OUString,OUString>("com.sun.star.ui.XUIConfigurationListener","elementReplaced"), + pair<OUString,OUString>("com.sun.star.ui.dialogs.XFilePickerNotifier","addFilePickerListener"), + pair<OUString,OUString>("com.sun.star.ui.dialogs.XFilePickerNotifier","removeFilePickerListener"), + pair<OUString,OUString>("com.sun.star.util.XBroadcaster","lockBroadcasts"), + pair<OUString,OUString>("com.sun.star.util.XBroadcaster","unlockBroadcasts"), + pair<OUString,OUString>("com.sun.star.util.XChangesListener","changesOccurred"), + pair<OUString,OUString>("com.sun.star.util.XChangesNotifier","addChangesListener"), + pair<OUString,OUString>("com.sun.star.util.XChangesNotifier","removeChangesListener"), + pair<OUString,OUString>("com.sun.star.util.XCloseBroadcaster","addCloseListener"), + pair<OUString,OUString>("com.sun.star.util.XCloseBroadcaster","removeCloseListener"), + pair<OUString,OUString>("com.sun.star.util.XFlushable","addFlushListener"), + pair<OUString,OUString>("com.sun.star.util.XFlushable","removeFlushListener"), + pair<OUString,OUString>("com.sun.star.util.XModeChangeListener","modeChanged"), + pair<OUString,OUString>("com.sun.star.util.XModifyBroadcaster","addModifyListener"), + pair<OUString,OUString>("com.sun.star.util.XModifyBroadcaster","removeModifyListener"), + pair<OUString,OUString>("com.sun.star.util.XRefreshable","addRefreshListener"), + pair<OUString,OUString>("com.sun.star.util.XRefreshable","removeRefreshListener"), + pair<OUString,OUString>("com.sun.star.util.XSearchDescriptor","setSearchString"), + pair<OUString,OUString>("com.sun.star.view.XPrintJobBroadcaster","addPrintJobListener"), + pair<OUString,OUString>("com.sun.star.view.XPrintJobBroadcaster","removePrintJobListener"), + pair<OUString,OUString>("com.sun.star.view.XPrintJobListener","printJobEvent"), + pair<OUString,OUString>("com.sun.star.view.XPrintableBroadcaster","addPrintableListener"), + pair<OUString,OUString>("com.sun.star.view.XPrintableBroadcaster","removePrintableListener"), + pair<OUString,OUString>("com.sun.star.view.XPrintableListener","stateChanged"), + pair<OUString,OUString>("com.sun.star.view.XSelectionChangeListener","selectionChanged"), + pair<OUString,OUString>("com.sun.star.beans.XMultiPropertySet","addPropertiesChangeListener"), + pair<OUString,OUString>("com.sun.star.beans.XMultiPropertySet","removePropertiesChangeListener"), + pair<OUString,OUString>("com.sun.star.beans.XMultiPropertySet","firePropertiesChangeEvent"), + pair<OUString,OUString>("com.sun.star.beans.XPropertiesChangeNotifier","addPropertiesChangeListener"), + pair<OUString,OUString>("com.sun.star.beans.XPropertiesChangeNotifier","removePropertiesChangeListener"), + pair<OUString,OUString>("com.sun.star.container.XContainer","addContainerListener"), + pair<OUString,OUString>("com.sun.star.container.XContainer","removeContainerListener"), + pair<OUString,OUString>("com.sun.star.container.XContainerListener","elementInserted"), + pair<OUString,OUString>("com.sun.star.container.XContainerListener","elementRemoved"), + pair<OUString,OUString>("com.sun.star.container.XContainerListener","elementReplaced"), + pair<OUString,OUString>("com.sun.star.container.XNamed","setName"), + pair<OUString,OUString>("com.sun.star.io.XDataExporter","exportData"), + pair<OUString,OUString>("com.sun.star.io.XDataExporter","cancel"), + pair<OUString,OUString>("com.sun.star.io.XDataImporter","importData"), + pair<OUString,OUString>("com.sun.star.io.XDataImporter","cancel"), + pair<OUString,OUString>("com.sun.star.io.XDataTransferEventListener","finished"), + pair<OUString,OUString>("com.sun.star.io.XDataTransferEventListener","cancelled"), + pair<OUString,OUString>("com.sun.star.lang.XConnectionPointContainer","advise"), + pair<OUString,OUString>("com.sun.star.lang.XConnectionPointContainer","unadvise"), + pair<OUString,OUString>("com.sun.star.script.XAllListener","firing"), + pair<OUString,OUString>("com.sun.star.uno.XInterface","acquire"), + pair<OUString,OUString>("com.sun.star.uno.XInterface","release"), + pair<OUString,OUString>("com.sun.star.uno.XReference","dispose")}; + + pair<OUString,OUString> k(_rListenerType, _rMethodName); + return delayed_event_listeners.find(k) != delayed_event_listeners.end(); + } + + + void FormScriptListener::impl_doFireScriptEvent_nothrow( ::osl::ClearableMutexGuard& _rGuard, const ScriptEvent& _rEvent, Any* _pSynchronousResult ) + { + OSL_PRECOND( m_pScriptExecutor, "FormScriptListener::impl_doFireScriptEvent_nothrow: this will crash!" ); + + _rGuard.clear(); + m_pScriptExecutor->doFireScriptEvent( _rEvent, _pSynchronousResult ); + } + + + void SAL_CALL FormScriptListener::firing( const ScriptEvent& _rEvent ) + { + if ( _rEvent.ScriptType == "VBAInterop" ) + return; // not handled here + + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + if ( impl_isDisposed_nothrow() ) + return; + + if ( !impl_allowAsynchronousCall_nothrow( _rEvent.ListenerType.getTypeName(), _rEvent.MethodName ) ) + { + impl_doFireScriptEvent_nothrow( aGuard, _rEvent, nullptr ); + return; + } + + acquire(); + Application::PostUserEvent( LINK( this, FormScriptListener, OnAsyncScriptEvent ), new ScriptEvent( _rEvent ) ); + } + + + Any SAL_CALL FormScriptListener::approveFiring( const ScriptEvent& _rEvent ) + { + Any aResult; + + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + if ( !impl_isDisposed_nothrow() ) + impl_doFireScriptEvent_nothrow( aGuard, _rEvent, &aResult ); + + return aResult; + } + + + void SAL_CALL FormScriptListener::disposing( const EventObject& /*Source*/ ) + { + // not interested in + } + + + void FormScriptListener::dispose() + { + ::osl::MutexGuard aGuard( m_aMutex ); + m_pScriptExecutor = nullptr; + } + + namespace { + + // tdf#88985 If LibreOffice tries to exit during the execution of a macro + // then: detect the effort, stop basic execution, block until the macro + // returns due to that stop, then restart the quit. This avoids the app + // exiting and destroying itself until the macro is parked at a safe place + // to do that. + class QuitGuard + { + private: + + class TerminateListener : public cppu::WeakComponentImplHelper<css::frame::XTerminateListener, + css::lang::XServiceInfo> + { + private: + css::uno::Reference<css::frame::XDesktop2> m_xDesktop; + osl::Mutex maMutex; + bool mbQuitBlocked; + public: + // XTerminateListener + virtual void SAL_CALL queryTermination(const css::lang::EventObject& /*rEvent*/) override + { + mbQuitBlocked = true; +#if HAVE_FEATURE_SCRIPTING + StarBASIC::Stop(); +#endif + throw css::frame::TerminationVetoException(); + } + + virtual void SAL_CALL notifyTermination(const css::lang::EventObject& /*rEvent*/) override + { + mbQuitBlocked = false; + } + + using cppu::WeakComponentImplHelperBase::disposing; + + virtual void SAL_CALL disposing(const css::lang::EventObject& rEvent) override + { + const bool bShutDown = (rEvent.Source == m_xDesktop); + if (bShutDown && m_xDesktop.is()) + { + m_xDesktop->removeTerminateListener(this); + m_xDesktop.clear(); + } + } + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.svx.StarBasicQuitGuard"; + } + + virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override + { + return cppu::supportsService(this, ServiceName); + } + + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override + { + return { "com.sun.star.svx.StarBasicQuitGuard" }; + } + + public: + TerminateListener() + : cppu::WeakComponentImplHelper<css::frame::XTerminateListener, + css::lang::XServiceInfo>(maMutex) + , mbQuitBlocked(false) + { + } + + void start() + { + css::uno::Reference<css::uno::XComponentContext> xContext(comphelper::getProcessComponentContext()); + m_xDesktop = css::frame::Desktop::create(xContext); + m_xDesktop->addTerminateListener(this); + } + + void stop() + { + if (!m_xDesktop.is()) + return; + m_xDesktop->removeTerminateListener(this); + if (mbQuitBlocked) + m_xDesktop->terminate(); + } + }; + + rtl::Reference<TerminateListener> mxListener; + public: + QuitGuard() + : mxListener(new TerminateListener) + { + mxListener->start(); + } + + ~QuitGuard() + { + mxListener->stop(); + } + }; + + } + + IMPL_LINK( FormScriptListener, OnAsyncScriptEvent, void*, p, void ) + { + ScriptEvent* _pEvent = static_cast<ScriptEvent*>(p); + OSL_PRECOND( _pEvent != nullptr, "FormScriptListener::OnAsyncScriptEvent: invalid event!" ); + if ( !_pEvent ) + return; + + { + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + if ( !impl_isDisposed_nothrow() ) + { + QuitGuard aQuitGuard; + impl_doFireScriptEvent_nothrow( aGuard, *_pEvent, nullptr ); + } + } + + delete _pEvent; + // we acquired ourself immediately before posting the event + release(); + } + + FormScriptingEnvironment::FormScriptingEnvironment( FmFormModel& _rModel ) + :m_rFormModel( _rModel ) + ,m_bDisposed( false ) + { + m_pScriptListener = new FormScriptListener( this ); + // note that this is a cyclic reference between the FormScriptListener and the FormScriptingEnvironment + // This cycle is broken up when our instance is disposed. + } + + void FormScriptingEnvironment::impl_registerOrRevoke_throw( const Reference< XEventAttacherManager >& _rxManager, bool _bRegister ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( !_rxManager.is() ) + throw IllegalArgumentException(); + if ( m_bDisposed ) + throw DisposedException(); + + try + { + if ( _bRegister ) + _rxManager->addScriptListener( m_pScriptListener.get() ); + else + _rxManager->removeScriptListener( m_pScriptListener.get() ); + } + catch( const RuntimeException& ) { throw; } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + + void FormScriptingEnvironment::registerEventAttacherManager( const Reference< XEventAttacherManager >& _rxManager ) + { + impl_registerOrRevoke_throw( _rxManager, true ); + } + + + void FormScriptingEnvironment::revokeEventAttacherManager( const Reference< XEventAttacherManager >& _rxManager ) + { + impl_registerOrRevoke_throw( _rxManager, false ); + } + + namespace + { + class NewStyleUNOScript + { + SfxObjectShell& m_rObjectShell; + const OUString m_sScriptCode; + + public: + NewStyleUNOScript( SfxObjectShell& _rObjectShell, const OUString& _rScriptCode ) + :m_rObjectShell( _rObjectShell ) + ,m_sScriptCode( _rScriptCode ) + { + } + + void invoke( const Sequence< Any >& _rArguments, Any& _rSynchronousResult ); + }; + + + void NewStyleUNOScript::invoke( const Sequence< Any >& _rArguments, Any& _rSynchronousResult ) + { + Sequence< sal_Int16 > aOutArgsIndex; + Sequence< Any > aOutArgs; + EventObject aEvent; + Any aCaller; + if ( _rArguments.hasElements() && ( _rArguments[ 0 ] >>= aEvent ) ) + { + try + { + Reference< XControl > xControl( aEvent.Source, UNO_QUERY_THROW ); + Reference< XPropertySet > xProps( xControl->getModel(), UNO_QUERY_THROW ); + aCaller = xProps->getPropertyValue("Name"); + } + catch( Exception& ) {} + } + m_rObjectShell.CallXScript( m_sScriptCode, _rArguments, _rSynchronousResult, aOutArgsIndex, aOutArgs, true, aCaller.hasValue() ? &aCaller : nullptr ); + } + } + + + void FormScriptingEnvironment::doFireScriptEvent( const ScriptEvent& _rEvent, Any* _pSynchronousResult ) + { +#if !HAVE_FEATURE_SCRIPTING + (void) _rEvent; + (void) _pSynchronousResult; + (void) m_rFormModel; +#else + SolarMutexClearableGuard aSolarGuard; + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + if ( m_bDisposed ) + return; + + // SfxObjectShellRef is good here since the model controls the lifetime of the object + SfxObjectShellRef xObjectShell = m_rFormModel.GetObjectShell(); + if( !xObjectShell.is() ) + return; + + // the script to execute + std::shared_ptr< NewStyleUNOScript > pScript; + + if ( _rEvent.ScriptType != "StarBasic" ) + { + pScript = std::make_shared<NewStyleUNOScript>( *xObjectShell, _rEvent.ScriptCode ); + } + else + { + OUString sScriptCode = _rEvent.ScriptCode; + OUString sMacroLocation; + + // is there a location in the script name ("application" or "document")? + sal_Int32 nPrefixLen = sScriptCode.indexOf( ':' ); + DBG_ASSERT( 0 <= nPrefixLen, "FormScriptingEnvironment::doFireScriptEvent: Basic script name in old format encountered!" ); + + if ( 0 <= nPrefixLen ) + { + // and it has such a prefix + sMacroLocation = sScriptCode.copy( 0, nPrefixLen ); + DBG_ASSERT( sMacroLocation == "document" + || sMacroLocation == "application", + "FormScriptingEnvironment::doFireScriptEvent: invalid (unknown) prefix!" ); + + // strip the prefix: the SfxObjectShell::CallScript knows nothing about such prefixes + sScriptCode = sScriptCode.copy( nPrefixLen + 1 ); + } + + if ( sMacroLocation.isEmpty() ) + { + // legacy format: use the app-wide Basic, if it has a respective method, otherwise fall back to the doc's Basic + if ( SfxApplication::GetBasicManager()->HasMacro( sScriptCode ) ) + sMacroLocation = "application"; + else + sMacroLocation = "document"; + } + + OUString sScriptURI = "vnd.sun.star.script:" + + sScriptCode + + "?language=Basic&location=" + + sMacroLocation; + + pScript = std::make_shared<NewStyleUNOScript>( *xObjectShell, sScriptURI ); + } + + assert(pScript && "FormScriptingEnvironment::doFireScriptEvent: no script to execute!"); + + aGuard.clear(); + aSolarGuard.clear(); + + Any aIgnoreResult; + pScript->invoke( _rEvent.Arguments, _pSynchronousResult ? *_pSynchronousResult : aIgnoreResult ); + pScript.reset(); + + { + // object shells are not thread safe, so guard the destruction + SolarMutexGuard aSolarGuarsReset; + xObjectShell = nullptr; + } +#endif + } + + + void FormScriptingEnvironment::dispose() + { + ::osl::MutexGuard aGuard( m_aMutex ); + m_bDisposed = true; + m_pScriptListener->dispose(); + m_pScriptListener.clear(); + } + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmservs.cxx b/svx/source/form/fmservs.cxx new file mode 100644 index 000000000..86b6960ad --- /dev/null +++ b/svx/source/form/fmservs.cxx @@ -0,0 +1,74 @@ +/* -*- 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 <com/sun/star/container/XSet.hpp> +#include <cppuhelper/factory.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <fmservs.hxx> + +using namespace com::sun::star; + +#define REGISTER_SERVICE(ImplName, ServiceName) \ + sString = (ServiceName); \ + xSingleFactory = ::cppu::createSingleFactory(xServiceFactory, \ + OUString(), ImplName##_NewInstance_Impl, \ + uno::Sequence< OUString>(&sString, 1)); \ + if (xSingleFactory.is()) \ + xSet->insert(uno::makeAny(xSingleFactory)); + +namespace svxform +{ + void ImplSmartRegisterUnoServices() + { + uno::Reference< lang::XMultiServiceFactory > xServiceFactory = ::comphelper::getProcessServiceFactory(); + uno::Reference< container::XSet > xSet(xServiceFactory, uno::UNO_QUERY); + if (!xSet.is()) + return; + + uno::Reference< lang::XSingleServiceFactory > xSingleFactory; + + OUString sString; + + + // FormController + REGISTER_SERVICE( FormController, "com.sun.star.form.runtime.FormController" ); + REGISTER_SERVICE( LegacyFormController, "com.sun.star.form.FormController" ); + + + // FormController - register selfaware service + xSingleFactory = ::cppu::createSingleFactory( xServiceFactory, + OAddConditionDialog_GetImplementationName(), + OAddConditionDialog_Create, + OAddConditionDialog_GetSupportedServiceNames() + ); + if ( xSingleFactory.is() ) + xSet->insert( uno::makeAny( xSingleFactory ) ); + + + // DBGridControl + REGISTER_SERVICE(FmXGridControl, FM_CONTROL_GRID); // compatibility + REGISTER_SERVICE(FmXGridControl, FM_CONTROL_GRIDCONTROL); + REGISTER_SERVICE(FmXGridControl, FM_SUN_CONTROL_GRIDCONTROL); + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmshell.cxx b/svx/source/form/fmshell.cxx new file mode 100644 index 000000000..31170e1ec --- /dev/null +++ b/svx/source/form/fmshell.cxx @@ -0,0 +1,1417 @@ +/* -*- 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 <fmvwimp.hxx> +#include <svx/fmshell.hxx> +#include <svx/fmtools.hxx> +#include <fmprop.hxx> +#include <fmundo.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/awt/XTabControllerModel.hpp> +#include <sfx2/viewfrm.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <svl/whiter.hxx> +#include <sfx2/app.hxx> +#include <svl/intitem.hxx> +#include <svl/stritem.hxx> +#include <svl/visitem.hxx> +#include <unotools/moduleoptions.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/request.hxx> +#include <sfx2/dispatch.hxx> +#include <svx/svdobj.hxx> +#include <svx/fmpage.hxx> +#include <svx/svditer.hxx> + +#include <svx/svxids.hrc> + +#include <svx/fmglob.hxx> +#include <svl/eitem.hxx> +#include <tools/diagnose_ex.h> +#include <svx/svdpage.hxx> +#include <svx/fmmodel.hxx> +#include <fmshimp.hxx> +#include <svx/svdpagv.hxx> +#include <sfx2/objitem.hxx> +#include <sfx2/viewsh.hxx> +#include <fmexpl.hxx> +#include <formcontrolling.hxx> +#include <comphelper/types.hxx> +#include <fmdocumentclassification.hxx> +#include <formtoolbars.hxx> + +#include <svx/svxdlg.hxx> + +#include <svx/sdrobjectfilter.hxx> + +#define ShellClass_FmFormShell +#include <svxslots.hxx> + +#include <memory> + +// is used for Invalidate -> maintain it as well +// sort ascending !!!!!! +sal_uInt16 const ControllerSlotMap[] = // slots of the controller +{ + SID_FM_CONFIG, + SID_FM_PUSHBUTTON, + SID_FM_RADIOBUTTON, + SID_FM_CHECKBOX, + SID_FM_FIXEDTEXT, + SID_FM_GROUPBOX, + SID_FM_EDIT, + SID_FM_LISTBOX, + SID_FM_COMBOBOX, + SID_FM_DBGRID, + SID_FM_IMAGEBUTTON, + SID_FM_FILECONTROL, + SID_FM_NAVIGATIONBAR, + SID_FM_CTL_PROPERTIES, + SID_FM_PROPERTIES, + SID_FM_TAB_DIALOG, + SID_FM_ADD_FIELD, + SID_FM_DESIGN_MODE, + SID_FM_SHOW_FMEXPLORER, + SID_FM_SHOW_PROPERTIES, + SID_FM_FMEXPLORER_CONTROL, + SID_FM_DATEFIELD, + SID_FM_TIMEFIELD, + SID_FM_NUMERICFIELD, + SID_FM_CURRENCYFIELD, + SID_FM_PATTERNFIELD, + SID_FM_OPEN_READONLY, + SID_FM_IMAGECONTROL, + SID_FM_USE_WIZARDS, + SID_FM_FORMATTEDFIELD, + SID_FM_FILTER_NAVIGATOR, + SID_FM_AUTOCONTROLFOCUS, + SID_FM_SCROLLBAR, + SID_FM_SPINBUTTON, + SID_FM_SHOW_DATANAVIGATOR, + SID_FM_DATANAVIGATOR_CONTROL, + + 0 +}; + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdbcx; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::form; +using namespace ::com::sun::star::form::runtime; +using namespace ::svxform; + +FmDesignModeChangedHint::FmDesignModeChangedHint( bool bDesMode ) + :m_bDesignMode( bDesMode ) +{ +} + + +FmDesignModeChangedHint::~FmDesignModeChangedHint() +{ +} + +SFX_IMPL_INTERFACE(FmFormShell, SfxShell) + +void FmFormShell::InitInterface_Impl() +{ + GetStaticInterface()->RegisterObjectBar(SFX_OBJECTBAR_NAVIGATION, SfxVisibilityFlags::Standard|SfxVisibilityFlags::ReadonlyDoc, + ToolbarId::SvxTbx_Form_Navigation, + SfxShellFeature::FormShowDatabaseBar); + + GetStaticInterface()->RegisterObjectBar(SFX_OBJECTBAR_NAVIGATION, SfxVisibilityFlags::Standard|SfxVisibilityFlags::ReadonlyDoc, + ToolbarId::SvxTbx_Form_Filter, + SfxShellFeature::FormShowFilterBar); + + GetStaticInterface()->RegisterObjectBar(SFX_OBJECTBAR_OBJECT, SfxVisibilityFlags::Standard | SfxVisibilityFlags::ReadonlyDoc, + ToolbarId::SvxTbx_Text_Control_Attributes, + SfxShellFeature::FormShowTextControlBar); + + GetStaticInterface()->RegisterChildWindow(SID_FM_ADD_FIELD, false, SfxShellFeature::FormShowField); + GetStaticInterface()->RegisterChildWindow(SID_FM_SHOW_PROPERTIES, false, SfxShellFeature::FormShowProperies); + GetStaticInterface()->RegisterChildWindow(SID_FM_SHOW_FMEXPLORER, false, SfxShellFeature::FormShowExplorer); + GetStaticInterface()->RegisterChildWindow(SID_FM_FILTER_NAVIGATOR, false, SfxShellFeature::FormShowFilterNavigator); + GetStaticInterface()->RegisterChildWindow(SID_FM_SHOW_DATANAVIGATOR, false, SfxShellFeature::FormShowDataNavigator); + + GetStaticInterface()->RegisterObjectBar(SFX_OBJECTBAR_OBJECT, SfxVisibilityFlags::Standard, + ToolbarId::SvxTbx_Controls, + SfxShellFeature::FormTBControls); + + GetStaticInterface()->RegisterObjectBar(SFX_OBJECTBAR_OBJECT, SfxVisibilityFlags::Standard, + ToolbarId::SvxTbx_FormDesign, + SfxShellFeature::FormTBDesign); +} + + +FmFormShell::FmFormShell( SfxViewShell* _pParent, FmFormView* pView ) + :SfxShell(_pParent) + ,m_pImpl(new FmXFormShell(*this, _pParent->GetViewFrame())) + ,m_pFormView( pView ) + ,m_pFormModel( nullptr ) + ,m_nLastSlot( 0 ) + ,m_bDesignMode( true ) + ,m_bHasForms(false) +{ + SetPool( &SfxGetpApp()->GetPool() ); + SetName( "Form" ); + + SetView(m_pFormView); +} + + +FmFormShell::~FmFormShell() +{ + if ( m_pFormView ) + SetView( nullptr ); + + m_pImpl->dispose(); +} + + +void FmFormShell::NotifyMarkListChanged(FmFormView* pWhichView) +{ + FmNavViewMarksChanged aChangeNotification(pWhichView); + Broadcast(aChangeNotification); +} + + +bool FmFormShell::PrepareClose(bool bUI) +{ + if (GetImpl()->didPrepareClose_Lock()) + // we already made a PrepareClose for the current modifications of the current form + return true; + + bool bResult = true; + // Save the data records, not in DesignMode and FilterMode + if (!m_bDesignMode && !GetImpl()->isInFilterMode_Lock() && + m_pFormView && m_pFormView->GetActualOutDev() && + m_pFormView->GetActualOutDev()->GetOutDevType() == OUTDEV_WINDOW) + { + SdrPageView* pCurPageView = m_pFormView->GetSdrPageView(); + + // sal_uInt16 nPos = pCurPageView ? pCurPageView->GetWinList().Find((OutputDevice*)m_pFormView->GetActualOutDev()) : SDRPAGEVIEWWIN_NOTFOUND; + SdrPageWindow* pWindow = pCurPageView ? pCurPageView->FindPageWindow(*const_cast<OutputDevice*>(m_pFormView->GetActualOutDev())) : nullptr; + + if(pWindow) + { + // First, the current contents of the controls are stored. + // If everything has gone smoothly, the modified records are stored. + if (GetImpl()->getActiveController_Lock().is()) + { + const svx::ControllerFeatures& rController = GetImpl()->getActiveControllerFeatures_Lock(); + if ( rController->commitCurrentControl() ) + { + const bool bModified = rController->isModifiedRow(); + + if ( bModified && bUI ) + { + SfxViewShell* pShell = GetViewShell(); + vcl::Window* pShellWnd = pShell ? pShell->GetWindow() : nullptr; + weld::Widget* pFrameWeld = pShellWnd ? pShellWnd->GetFrameWeld() : nullptr; + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pFrameWeld, "svx/ui/savemodifieddialog.ui")); + std::unique_ptr<weld::MessageDialog> xQry(xBuilder->weld_message_dialog("SaveModifiedDialog")); + switch (xQry->run()) + { + case RET_YES: + bResult = rController->commitCurrentRecord( ); + [[fallthrough]]; + case RET_NO: + GetImpl()->didPrepareClose_Lock(true); + break; + + case RET_CANCEL: + return false; + } + } + } + } + } + } + return bResult; +} + + +void FmFormShell::impl_setDesignMode(bool bDesign) +{ + if (m_pFormView) + { + if (!bDesign) + m_nLastSlot = SID_FM_DESIGN_MODE; + + GetImpl()->SetDesignMode_Lock(bDesign); + // my m_bDesignMode is also set by the Impl ... + } + else + { + m_bHasForms = false; + m_bDesignMode = bDesign; + UIFeatureChanged(); + } + + GetViewShell()->GetViewFrame()->GetBindings().Invalidate(ControllerSlotMap); +} + + +bool FmFormShell::HasUIFeature(SfxShellFeature nFeature) const +{ + assert((nFeature & ~SfxShellFeature::FormMask) == SfxShellFeature::NONE); + bool bResult = false; + if (nFeature & SfxShellFeature::FormShowDatabaseBar) + { + // only if forms are also available + bResult = !m_bDesignMode && GetImpl()->hasDatabaseBar_Lock() && !GetImpl()->isInFilterMode_Lock(); + } + else if (nFeature & SfxShellFeature::FormShowFilterBar) + { + // only if forms are also available + bResult = !m_bDesignMode && GetImpl()->hasDatabaseBar_Lock() && GetImpl()->isInFilterMode_Lock(); + } + else if (nFeature & SfxShellFeature::FormShowFilterNavigator) + { + bResult = !m_bDesignMode && GetImpl()->hasDatabaseBar_Lock() && GetImpl()->isInFilterMode_Lock(); + } + else if (nFeature & SfxShellFeature::FormShowField) + { + bResult = m_bDesignMode && m_pFormView && m_bHasForms; + } + else if (nFeature & SfxShellFeature::FormShowProperies) + { + bResult = m_bDesignMode && m_pFormView && m_bHasForms; + } + else if (nFeature & SfxShellFeature::FormShowExplorer) + { + bResult = m_bDesignMode; // OJ #101593# && m_pFormView && m_bHasForms; + } + else if (nFeature & SfxShellFeature::FormShowTextControlBar) + { + bResult = !GetImpl()->IsReadonlyDoc_Lock() && m_pImpl->IsActiveControl_Lock(true); + } + else if (nFeature & SfxShellFeature::FormShowDataNavigator) + { + bResult = GetImpl()->isEnhancedForm_Lock(); + } + else if ( (nFeature & SfxShellFeature::FormTBControls) + || (nFeature & SfxShellFeature::FormTBDesign) + ) + { + bResult = true; + } + + return bResult; +} + + +void FmFormShell::Execute(SfxRequest &rReq) +{ + sal_uInt16 nSlot = rReq.GetSlot(); + + + // set MasterSlot + switch( nSlot ) + { + case SID_FM_PUSHBUTTON: + case SID_FM_RADIOBUTTON: + case SID_FM_CHECKBOX: + case SID_FM_FIXEDTEXT: + case SID_FM_GROUPBOX: + case SID_FM_LISTBOX: + case SID_FM_COMBOBOX: + case SID_FM_NAVIGATIONBAR: + case SID_FM_EDIT: + case SID_FM_DBGRID: + case SID_FM_IMAGEBUTTON: + case SID_FM_IMAGECONTROL: + case SID_FM_FILECONTROL: + case SID_FM_DATEFIELD: + case SID_FM_TIMEFIELD: + case SID_FM_NUMERICFIELD: + case SID_FM_CURRENCYFIELD: + case SID_FM_PATTERNFIELD: + case SID_FM_FORMATTEDFIELD: + case SID_FM_SCROLLBAR: + case SID_FM_SPINBUTTON: + m_nLastSlot = nSlot; + break; + } + + + // set the Identifier and Inventor of the Uno control + sal_uInt16 nIdentifier = 0; + switch( nSlot ) + { + case SID_FM_CHECKBOX: + nIdentifier = OBJ_FM_CHECKBOX; + break; + case SID_FM_PUSHBUTTON: + nIdentifier = OBJ_FM_BUTTON; + break; + case SID_FM_FIXEDTEXT: + nIdentifier = OBJ_FM_FIXEDTEXT; + break; + case SID_FM_LISTBOX: + nIdentifier = OBJ_FM_LISTBOX; + break; + case SID_FM_EDIT: + nIdentifier = OBJ_FM_EDIT; + break; + case SID_FM_RADIOBUTTON: + nIdentifier = OBJ_FM_RADIOBUTTON; + break; + case SID_FM_GROUPBOX: + nIdentifier = OBJ_FM_GROUPBOX; + break; + case SID_FM_COMBOBOX: + nIdentifier = OBJ_FM_COMBOBOX; + break; + case SID_FM_NAVIGATIONBAR: + nIdentifier = OBJ_FM_NAVIGATIONBAR; + break; + case SID_FM_DBGRID: + nIdentifier = OBJ_FM_GRID; + break; + case SID_FM_IMAGEBUTTON: + nIdentifier = OBJ_FM_IMAGEBUTTON; + break; + case SID_FM_IMAGECONTROL: + nIdentifier = OBJ_FM_IMAGECONTROL; + break; + case SID_FM_FILECONTROL: + nIdentifier = OBJ_FM_FILECONTROL; + break; + case SID_FM_DATEFIELD: + nIdentifier = OBJ_FM_DATEFIELD; + break; + case SID_FM_TIMEFIELD: + nIdentifier = OBJ_FM_TIMEFIELD; + break; + case SID_FM_NUMERICFIELD: + nIdentifier = OBJ_FM_NUMERICFIELD; + break; + case SID_FM_CURRENCYFIELD: + nIdentifier = OBJ_FM_CURRENCYFIELD; + break; + case SID_FM_PATTERNFIELD: + nIdentifier = OBJ_FM_PATTERNFIELD; + break; + case SID_FM_FORMATTEDFIELD: + nIdentifier = OBJ_FM_FORMATTEDFIELD; + break; + case SID_FM_SCROLLBAR: + nIdentifier = OBJ_FM_SCROLLBAR; + break; + case SID_FM_SPINBUTTON: + nIdentifier = OBJ_FM_SPINBUTTON; + break; + } + + switch ( nSlot ) + { + case SID_FM_CHECKBOX: + case SID_FM_PUSHBUTTON: + case SID_FM_FIXEDTEXT: + case SID_FM_LISTBOX: + case SID_FM_EDIT: + case SID_FM_RADIOBUTTON: + case SID_FM_COMBOBOX: + case SID_FM_NAVIGATIONBAR: + case SID_FM_GROUPBOX: + case SID_FM_DBGRID: + case SID_FM_IMAGEBUTTON: + case SID_FM_IMAGECONTROL: + case SID_FM_FILECONTROL: + case SID_FM_DATEFIELD: + case SID_FM_TIMEFIELD: + case SID_FM_NUMERICFIELD: + case SID_FM_CURRENCYFIELD: + case SID_FM_PATTERNFIELD: + case SID_FM_FORMATTEDFIELD: + case SID_FM_SCROLLBAR: + case SID_FM_SPINBUTTON: + { + const SfxBoolItem* pGrabFocusItem = rReq.GetArg<SfxBoolItem>(SID_FM_TOGGLECONTROLFOCUS); + if ( pGrabFocusItem && pGrabFocusItem->GetValue() ) + { // see below + SfxViewShell* pShell = GetViewShell(); + vcl::Window* pShellWnd = pShell ? pShell->GetWindow() : nullptr; + if ( pShellWnd ) + pShellWnd->GrabFocus(); + break; + } + + SfxUInt16Item aIdentifierItem( SID_FM_CONTROL_IDENTIFIER, nIdentifier ); + SfxUInt32Item aInventorItem( SID_FM_CONTROL_INVENTOR, sal_uInt32(SdrInventor::FmForm) ); + const SfxPoolItem* pArgs[] = + { + &aIdentifierItem, &aInventorItem, nullptr + }; + const SfxPoolItem* pInternalArgs[] = + { + nullptr + }; + + GetViewShell()->GetViewFrame()->GetDispatcher()->Execute( SID_FM_CREATE_CONTROL, SfxCallMode::ASYNCHRON, + pArgs, rReq.GetModifier(), pInternalArgs ); + + if ( rReq.GetModifier() & KEY_MOD1 ) + { + // #99013# if selected with control key, return focus to current view + // do this asynchron, so that the creation can be finished first + // reusing the SID_FM_TOGGLECONTROLFOCUS is somewhat hacky... which it wouldn't if it would have another + // name, so I do not really have a big problem with this... + SfxBoolItem aGrabFocusIndicatorItem( SID_FM_TOGGLECONTROLFOCUS, true ); + GetViewShell()->GetViewFrame()->GetDispatcher()->ExecuteList( + nSlot, SfxCallMode::ASYNCHRON, + { &aGrabFocusIndicatorItem }); + } + + rReq.Done(); + } break; + } + + // individual actions + switch( nSlot ) + { + case SID_FM_FORM_DESIGN_TOOLS: + { + FormToolboxes aToolboxAccess(GetImpl()->getHostFrame_Lock()); + aToolboxAccess.toggleToolbox( nSlot ); + rReq.Done(); + } + break; + + case SID_FM_TOGGLECONTROLFOCUS: + { + FmFormView* pFormView = GetFormView(); + if ( !pFormView ) + break; + + // if we execute this ourself, then either the application does not implement an own handling for this, + // of we're on the top of the dispatcher stack, which means a control has the focus. + // In the latter case, we put the focus to the document window, otherwise, we focus the first control + const bool bHasControlFocus = GetImpl()->HasControlFocus_Lock(); + if ( bHasControlFocus ) + { + if (m_pFormView) + { + const OutputDevice* pDevice = m_pFormView->GetActualOutDev(); + vcl::Window* pWindow = dynamic_cast< vcl::Window* >( const_cast< OutputDevice* >( pDevice ) ); + if ( pWindow ) + pWindow->GrabFocus(); + } + } + else + { + pFormView->GrabFirstControlFocus( ); + } + } + break; + + case SID_FM_VIEW_AS_GRID: + GetImpl()->CreateExternalView_Lock(); + break; + case SID_FM_CONVERTTO_EDIT : + case SID_FM_CONVERTTO_BUTTON : + case SID_FM_CONVERTTO_FIXEDTEXT : + case SID_FM_CONVERTTO_LISTBOX : + case SID_FM_CONVERTTO_CHECKBOX : + case SID_FM_CONVERTTO_RADIOBUTTON : + case SID_FM_CONVERTTO_GROUPBOX : + case SID_FM_CONVERTTO_COMBOBOX : + case SID_FM_CONVERTTO_IMAGEBUTTON : + case SID_FM_CONVERTTO_FILECONTROL : + case SID_FM_CONVERTTO_DATE : + case SID_FM_CONVERTTO_TIME : + case SID_FM_CONVERTTO_NUMERIC : + case SID_FM_CONVERTTO_CURRENCY : + case SID_FM_CONVERTTO_PATTERN : + case SID_FM_CONVERTTO_IMAGECONTROL : + case SID_FM_CONVERTTO_FORMATTED : + case SID_FM_CONVERTTO_SCROLLBAR : + case SID_FM_CONVERTTO_SPINBUTTON : + case SID_FM_CONVERTTO_NAVIGATIONBAR : + GetImpl()->executeControlConversionSlot_Lock(FmXFormShell::SlotToIdent(nSlot)); + // after the conversion, re-determine the selection, since the + // selected object has changed + GetImpl()->SetSelection_Lock(GetFormView()->GetMarkedObjectList()); + break; + case SID_FM_LEAVE_CREATE: + m_nLastSlot = 0; + rReq.Done(); + break; + case SID_FM_SHOW_PROPERTY_BROWSER: + { + const SfxBoolItem* pShowItem = rReq.GetArg<SfxBoolItem>(SID_FM_SHOW_PROPERTIES); + bool bShow = true; + if ( pShowItem ) + bShow = pShowItem->GetValue(); + GetImpl()->ShowSelectionProperties_Lock(bShow); + + rReq.Done(); + } break; + + case SID_FM_PROPERTIES: + { + // display the PropertyBrowser + const SfxBoolItem* pShowItem = rReq.GetArg<SfxBoolItem>(nSlot); + bool bShow = pShowItem == nullptr || pShowItem->GetValue(); + + InterfaceBag aOnlyTheForm; + aOnlyTheForm.insert(Reference<XInterface>(GetImpl()->getCurrentForm_Lock(), UNO_QUERY)); + GetImpl()->setCurrentSelection_Lock(aOnlyTheForm); + + GetImpl()->ShowSelectionProperties_Lock(bShow); + + rReq.Done(); + } break; + + case SID_FM_CTL_PROPERTIES: + { + const SfxBoolItem* pShowItem = rReq.GetArg<SfxBoolItem>(nSlot); + bool bShow = pShowItem == nullptr || pShowItem->GetValue(); + + OSL_ENSURE( GetImpl()->onlyControlsAreMarked_Lock(), "FmFormShell::Execute: ControlProperties should be disabled!" ); + if ( bShow ) + GetImpl()->selectLastMarkedControls_Lock(); + GetImpl()->ShowSelectionProperties_Lock(bShow); + + rReq.Done(); + } break; + case SID_FM_SHOW_PROPERTIES: + case SID_FM_ADD_FIELD: + case SID_FM_FILTER_NAVIGATOR: + case SID_FM_SHOW_DATANAVIGATOR : + { + GetViewShell()->GetViewFrame()->ToggleChildWindow(nSlot); + rReq.Done(); + } break; + case SID_FM_SHOW_FMEXPLORER: + { + if (!m_pFormView) // force setting the view + GetViewShell()->GetViewFrame()->GetDispatcher()->Execute(SID_CREATE_SW_DRAWVIEW); + + GetViewShell()->GetViewFrame()->ChildWindowExecute(rReq); + rReq.Done(); + } + break; + + case SID_FM_TAB_DIALOG: + { + GetImpl()->ExecuteTabOrderDialog_Lock( + Reference<XTabControllerModel>(GetImpl()->getCurrentForm_Lock(), UNO_QUERY)); + rReq.Done(); + } + break; + + case SID_FM_DESIGN_MODE: + { + const SfxBoolItem* pDesignItem = rReq.GetArg<SfxBoolItem>(nSlot); + bool bDesignMode = pDesignItem ? pDesignItem->GetValue() : !m_bDesignMode; + SetDesignMode( bDesignMode ); + if ( m_bDesignMode == bDesignMode ) + rReq.Done(); + + m_nLastSlot = SID_FM_DESIGN_MODE; + } + break; + + case SID_FM_AUTOCONTROLFOCUS: + { + FmFormModel* pModel = GetFormModel(); + DBG_ASSERT(pModel, "FmFormShell::Execute : invalid call !"); + // should have been disabled in GetState if we don't have a FormModel + pModel->SetAutoControlFocus( !pModel->GetAutoControlFocus() ); + GetViewShell()->GetViewFrame()->GetBindings().Invalidate(SID_FM_AUTOCONTROLFOCUS); + } + break; + case SID_FM_OPEN_READONLY: + { + FmFormModel* pModel = GetFormModel(); + DBG_ASSERT(pModel, "FmFormShell::Execute : invalid call !"); + // should have been disabled in GetState if we don't have a FormModel + pModel->SetOpenInDesignMode( !pModel->GetOpenInDesignMode() ); + GetViewShell()->GetViewFrame()->GetBindings().Invalidate(SID_FM_OPEN_READONLY); + } + break; + case SID_FM_USE_WIZARDS: + { + GetImpl()->SetWizardUsing_Lock(!GetImpl()->GetWizardUsing_Lock()); + GetViewShell()->GetViewFrame()->GetBindings().Invalidate(SID_FM_USE_WIZARDS); + } + break; + case SID_FM_SEARCH: + { + const svx::ControllerFeatures& rController = GetImpl()->getActiveControllerFeatures_Lock(); + if ( rController->commitCurrentControl() && rController->commitCurrentRecord() ) + GetImpl()->ExecuteSearch_Lock(); + rReq.Done(); + } + break; + + case SID_FM_RECORD_FIRST: + case SID_FM_RECORD_PREV: + case SID_FM_RECORD_NEXT: + case SID_FM_RECORD_LAST: + case SID_FM_RECORD_NEW: + case SID_FM_REFRESH: + case SID_FM_REFRESH_FORM_CONTROL: + case SID_FM_RECORD_DELETE: + case SID_FM_RECORD_UNDO: + case SID_FM_RECORD_SAVE: + case SID_FM_REMOVE_FILTER_SORT: + case SID_FM_SORTDOWN: + case SID_FM_SORTUP: + case SID_FM_AUTOFILTER: + case SID_FM_ORDERCRIT: + case SID_FM_FORM_FILTERED: + { + GetImpl()->ExecuteFormSlot_Lock(nSlot); + rReq.Done(); + } + break; + + case SID_FM_RECORD_ABSOLUTE: + { + const svx::ControllerFeatures& rController = GetImpl()->getNavControllerFeatures_Lock(); + sal_Int32 nRecord = -1; + + const SfxItemSet* pArgs = rReq.GetArgs(); + if ( pArgs ) + { + const SfxPoolItem* pItem; + if ( ( pArgs->GetItemState( FN_PARAM_1, true, &pItem ) ) == SfxItemState::SET ) + { + const SfxInt32Item* pTypedItem = dynamic_cast<const SfxInt32Item* >( pItem ); + if ( pTypedItem ) + nRecord = std::max( pTypedItem->GetValue(), sal_Int32(0) ); + } + } + else + { + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractFmInputRecordNoDialog> dlg(pFact->CreateFmInputRecordNoDialog(rReq.GetFrameWeld())); + dlg->SetValue( rController->getCursor()->getRow() ); + if ( dlg->Execute() == RET_OK ) + nRecord = dlg->GetValue(); + + rReq.AppendItem( SfxInt32Item( FN_PARAM_1, nRecord ) ); + } + + if ( nRecord != -1 ) + rController->execute( nSlot, "Position", makeAny( nRecord ) ); + + rReq.Done(); + } break; + case SID_FM_FILTER_EXECUTE: + case SID_FM_FILTER_EXIT: + { + bool bCancelled = ( SID_FM_FILTER_EXIT == nSlot ); + bool bReopenNavigator = false; + + if ( !bCancelled ) + { + // if the filter navigator is still open, we need to close it, so it can possibly + // commit it's most recent changes + if ( GetViewShell() && GetViewShell()->GetViewFrame() ) + if ( GetViewShell()->GetViewFrame()->HasChildWindow( SID_FM_FILTER_NAVIGATOR ) ) + { + GetViewShell()->GetViewFrame()->ToggleChildWindow( SID_FM_FILTER_NAVIGATOR ); + bReopenNavigator = true; + } + + Reference<runtime::XFormController> const xController(GetImpl()->getActiveController_Lock()); + + if ( GetViewShell()->GetViewFrame()->HasChildWindow( SID_FM_FILTER_NAVIGATOR ) + // closing the window was denied, for instance because of an invalid criterion + + || ( xController.is() + && !GetImpl()->getActiveControllerFeatures_Lock()->commitCurrentControl() + ) + // committing the controller was denied + ) + { + rReq.Done(); + break; + } + } + + GetImpl()->stopFiltering_Lock(!bCancelled); + rReq.Done(); + + if ( bReopenNavigator ) + // we closed the navigator only to implicitly commit it (as we do not have another + // direct wire to it), but to the user, it should look as it was always open + GetViewShell()->GetViewFrame()->ToggleChildWindow( SID_FM_FILTER_NAVIGATOR ); + } + break; + + case SID_FM_FILTER_START: + { + GetImpl()->startFiltering_Lock(); + rReq.Done(); + + // initially open the filter navigator, the whole form based filter is pretty useless without it + SfxBoolItem aIdentifierItem( SID_FM_FILTER_NAVIGATOR, true ); + GetViewShell()->GetViewFrame()->GetDispatcher()->ExecuteList( + SID_FM_FILTER_NAVIGATOR, SfxCallMode::ASYNCHRON, + { &aIdentifierItem }); + } break; + } +} + + +void FmFormShell::GetState(SfxItemSet &rSet) +{ + SfxWhichIter aIter( rSet ); + sal_uInt16 nWhich = aIter.FirstWhich(); + while ( nWhich ) + { + switch( nWhich ) + { + case SID_FM_FORM_DESIGN_TOOLS: + { + FormToolboxes aToolboxAccess(GetImpl()->getHostFrame_Lock()); + rSet.Put( SfxBoolItem( nWhich, aToolboxAccess.isToolboxVisible( nWhich ) ) ); + } + break; + + case SID_FM_FILTER_EXECUTE: + case SID_FM_FILTER_EXIT: + if (!GetImpl()->isInFilterMode_Lock()) + rSet.DisableItem( nWhich ); + break; + + case SID_FM_USE_WIZARDS: + if ( !SvtModuleOptions().IsModuleInstalled( SvtModuleOptions::EModule::DATABASE ) ) + rSet.Put( SfxVisibilityItem( nWhich, false ) ); + else if (!GetFormModel()) + rSet.DisableItem( nWhich ); + else + rSet.Put(SfxBoolItem(nWhich, GetImpl()->GetWizardUsing_Lock())); + break; + case SID_FM_AUTOCONTROLFOCUS: + if (!GetFormModel()) + rSet.DisableItem( nWhich ); + else + rSet.Put( SfxBoolItem(nWhich, GetFormModel()->GetAutoControlFocus() ) ); + break; + case SID_FM_OPEN_READONLY: + if (!GetFormModel()) + rSet.DisableItem( nWhich ); + else + rSet.Put( SfxBoolItem(nWhich, GetFormModel()->GetOpenInDesignMode() ) ); + break; + + case SID_FM_NAVIGATIONBAR: + case SID_FM_DBGRID: + if ( !SvtModuleOptions().IsModuleInstalled( SvtModuleOptions::EModule::DATABASE ) ) + { + rSet.Put( SfxVisibilityItem( nWhich, false ) ); + break; + } + [[fallthrough]]; + + case SID_FM_SCROLLBAR: + case SID_FM_IMAGECONTROL: + case SID_FM_FILECONTROL: + case SID_FM_CURRENCYFIELD: + case SID_FM_PATTERNFIELD: + case SID_FM_IMAGEBUTTON: + case SID_FM_RADIOBUTTON: + case SID_FM_COMBOBOX: + case SID_FM_GROUPBOX: + case SID_FM_CHECKBOX: + case SID_FM_PUSHBUTTON: + case SID_FM_FIXEDTEXT: + case SID_FM_LISTBOX: + case SID_FM_EDIT: + case SID_FM_DATEFIELD: + case SID_FM_TIMEFIELD: + case SID_FM_NUMERICFIELD: + case SID_FM_FORMATTEDFIELD: + case SID_FM_SPINBUTTON: + if (!m_bDesignMode) + rSet.DisableItem( nWhich ); + else + { + bool bLayerLocked = false; + if (m_pFormView) + { + // If the css::drawing::Layer is locked, the slots must be disabled. #36897 + SdrPageView* pPV = m_pFormView->GetSdrPageView(); + if (pPV != nullptr) + bLayerLocked = pPV->IsLayerLocked(m_pFormView->GetActiveLayer()); + } + if (bLayerLocked) + rSet.DisableItem( nWhich ); + else + rSet.Put( SfxBoolItem(nWhich, (nWhich==m_nLastSlot)) ); + } + break; + case SID_FM_FILTER_NAVIGATOR_CONTROL: + { + if (GetImpl()->isInFilterMode_Lock()) + rSet.Put(SfxObjectItem(nWhich, this)); + else + rSet.Put(SfxObjectItem(nWhich)); + } break; + case SID_FM_FIELDS_CONTROL: + case SID_FM_PROPERTY_CONTROL: + { + if (!m_bDesignMode || !m_pFormView || !m_bHasForms) + rSet.Put(SfxObjectItem(nWhich)); + else + rSet.Put(SfxObjectItem(nWhich, this)); + + } break; + case SID_FM_FMEXPLORER_CONTROL: + case SID_FM_DATANAVIGATOR_CONTROL : + { + if (!m_bDesignMode || !m_pFormView) + rSet.Put(SfxObjectItem(nWhich)); + else + rSet.Put(SfxObjectItem(nWhich, this)); + + } break; + case SID_FM_ADD_FIELD: + case SID_FM_SHOW_FMEXPLORER: + case SID_FM_SHOW_PROPERTIES: + case SID_FM_FILTER_NAVIGATOR: + case SID_FM_SHOW_DATANAVIGATOR: + { + if ( GetViewShell()->GetViewFrame()->KnowsChildWindow(nWhich) ) + rSet.Put( SfxBoolItem( nWhich, GetViewShell()->GetViewFrame()->HasChildWindow(nWhich)) ); + else + rSet.DisableItem(nWhich); + } break; + + case SID_FM_SHOW_PROPERTY_BROWSER: + { + rSet.Put(SfxBoolItem(nWhich, GetImpl()->IsPropBrwOpen_Lock())); + } + break; + + case SID_FM_CTL_PROPERTIES: + { + // potentially, give the Impl the opportunity to update its + // current objects which are aligned with the current MarkList + if (GetImpl()->IsSelectionUpdatePending_Lock()) + GetImpl()->ForceUpdateSelection_Lock(); + + if (!m_pFormView || !m_bDesignMode || !GetImpl()->onlyControlsAreMarked_Lock()) + rSet.DisableItem( nWhich ); + else + { + bool const bChecked = GetImpl()->IsPropBrwOpen_Lock() && !GetImpl()->isSolelySelected_Lock(GetImpl()->getCurrentForm_Lock()); + // if the property browser is open, and only controls are marked, and the current selection + // does not consist of only the current form, then the current selection is the (composition of) + // the currently marked controls + rSet.Put( SfxBoolItem( nWhich, bChecked ) ); + } + } break; + + case SID_FM_PROPERTIES: + { + // potentially, give the Impl the opportunity to update its + // current objects which are aligned with the current MarkList + if (GetImpl()->IsSelectionUpdatePending_Lock()) + GetImpl()->ForceUpdateSelection_Lock(); + + if (!m_pFormView || !m_bDesignMode || !GetImpl()->getCurrentForm_Lock().is()) + rSet.DisableItem( nWhich ); + else + { + bool const bChecked = GetImpl()->IsPropBrwOpen_Lock() && GetImpl()->isSolelySelected_Lock(GetImpl()->getCurrentForm_Lock()); + rSet.Put(SfxBoolItem(nWhich, bChecked)); + } + } break; + case SID_FM_TAB_DIALOG: + // potentially, give the Impl the opportunity to update its + // current objects which are aligned with the current MarkList + if (GetImpl()->IsSelectionUpdatePending_Lock()) + GetImpl()->ForceUpdateSelection_Lock(); + + if (!m_pFormView || !m_bDesignMode || !GetImpl()->getCurrentForm_Lock().is() ) + rSet.DisableItem( nWhich ); + break; + case SID_FM_DESIGN_MODE: + if (!m_pFormView || GetImpl()->IsReadonlyDoc_Lock()) + rSet.DisableItem( nWhich ); + else + rSet.Put( SfxBoolItem(nWhich, m_bDesignMode) ); + break; + case SID_FM_SEARCH: + case SID_FM_RECORD_FIRST: + case SID_FM_RECORD_NEXT: + case SID_FM_RECORD_PREV: + case SID_FM_RECORD_LAST: + case SID_FM_RECORD_NEW: + case SID_FM_RECORD_DELETE: + case SID_FM_RECORD_ABSOLUTE: + case SID_FM_RECORD_TOTAL: + case SID_FM_RECORD_SAVE: + case SID_FM_RECORD_UNDO: + case SID_FM_FORM_FILTERED: + case SID_FM_REMOVE_FILTER_SORT: + case SID_FM_SORTUP: + case SID_FM_SORTDOWN: + case SID_FM_ORDERCRIT: + case SID_FM_FILTER_START: + case SID_FM_AUTOFILTER: + case SID_FM_REFRESH: + case SID_FM_REFRESH_FORM_CONTROL: + case SID_FM_VIEW_AS_GRID: + GetFormState(rSet,nWhich); + break; + + case SID_FM_CHANGECONTROLTYPE: + { + if ( !m_pFormView || !m_bDesignMode ) + rSet.DisableItem( nWhich ); + else + { + if (!GetImpl()->canConvertCurrentSelectionToControl_Lock("ConvertToFixed")) + // if it cannot be converted to a fixed text, it is no single control + rSet.DisableItem( nWhich ); + } + } break; + + case SID_FM_CONVERTTO_FILECONTROL : + case SID_FM_CONVERTTO_CURRENCY : + case SID_FM_CONVERTTO_PATTERN : + case SID_FM_CONVERTTO_IMAGECONTROL : + case SID_FM_CONVERTTO_SCROLLBAR : + case SID_FM_CONVERTTO_NAVIGATIONBAR : + case SID_FM_CONVERTTO_IMAGEBUTTON : + case SID_FM_CONVERTTO_EDIT : + case SID_FM_CONVERTTO_BUTTON : + case SID_FM_CONVERTTO_FIXEDTEXT : + case SID_FM_CONVERTTO_LISTBOX : + case SID_FM_CONVERTTO_CHECKBOX : + case SID_FM_CONVERTTO_RADIOBUTTON : + case SID_FM_CONVERTTO_GROUPBOX : + case SID_FM_CONVERTTO_COMBOBOX : + case SID_FM_CONVERTTO_DATE : + case SID_FM_CONVERTTO_TIME : + case SID_FM_CONVERTTO_NUMERIC : + case SID_FM_CONVERTTO_FORMATTED : + case SID_FM_CONVERTTO_SPINBUTTON : + { + if (!m_pFormView || !m_bDesignMode || !GetImpl()->canConvertCurrentSelectionToControl_Lock(FmXFormShell::SlotToIdent(nWhich))) + rSet.DisableItem( nWhich ); + else + { + rSet.Put( SfxBoolItem( nWhich, false ) ); + // just to have a defined state (available and not checked) + } + } + break; + } + nWhich = aIter.NextWhich(); + } +} + + +void FmFormShell::GetFormState(SfxItemSet &rSet, sal_uInt16 nWhich) +{ + if ( !GetImpl()->getNavController_Lock().is() + || !isRowSetAlive(GetImpl()->getNavController_Lock()->getModel()) + || !m_pFormView + || m_bDesignMode + || !GetImpl()->getActiveForm_Lock().is() + || GetImpl()->isInFilterMode_Lock() + ) + rSet.DisableItem(nWhich); + else + { + bool bEnable = false; + try + { + switch (nWhich) + { + case SID_FM_VIEW_AS_GRID: + if (GetImpl()->getHostFrame_Lock().is() && GetImpl()->getNavController_Lock().is()) + { + bEnable = true; + bool bDisplayingCurrent = + GetImpl()->getInternalForm_Lock( + Reference<XForm>(GetImpl()->getNavController_Lock()->getModel(), UNO_QUERY) + ) == GetImpl()->getExternallyDisplayedForm_Lock(); + rSet.Put(SfxBoolItem(nWhich, bDisplayingCurrent)); + } + break; + + case SID_FM_SEARCH: + { + Reference<css::beans::XPropertySet> const xNavSet(GetImpl()->getActiveForm_Lock(), UNO_QUERY); + sal_Int32 nCount = ::comphelper::getINT32(xNavSet->getPropertyValue(FM_PROP_ROWCOUNT)); + bEnable = nCount != 0; + } break; + case SID_FM_RECORD_ABSOLUTE: + case SID_FM_RECORD_TOTAL: + { + FeatureState aState; + GetImpl()->getNavControllerFeatures_Lock()->getState( nWhich, aState ); + if ( SID_FM_RECORD_ABSOLUTE == nWhich ) + { + sal_Int32 nPosition = 0; + aState.State >>= nPosition; + rSet.Put( SfxInt32Item( nWhich, nPosition ) ); + } + else if ( SID_FM_RECORD_TOTAL == nWhich ) + { + OUString sTotalCount; + aState.State >>= sTotalCount; + rSet.Put( SfxStringItem( nWhich, sTotalCount ) ); + } + bEnable = aState.Enabled; + } + break; + + // first, prev, next, last, and absolute affect the nav controller, not the + // active controller + case SID_FM_RECORD_FIRST: + case SID_FM_RECORD_PREV: + case SID_FM_RECORD_NEXT: + case SID_FM_RECORD_LAST: + case SID_FM_RECORD_NEW: + case SID_FM_RECORD_SAVE: + case SID_FM_RECORD_UNDO: + case SID_FM_RECORD_DELETE: + case SID_FM_REFRESH: + case SID_FM_REFRESH_FORM_CONTROL: + case SID_FM_REMOVE_FILTER_SORT: + case SID_FM_SORTUP: + case SID_FM_SORTDOWN: + case SID_FM_AUTOFILTER: + case SID_FM_ORDERCRIT: + bEnable = GetImpl()->IsFormSlotEnabled( nWhich, nullptr ); + break; + + case SID_FM_FORM_FILTERED: + { + FeatureState aState; + bEnable = GetImpl()->IsFormSlotEnabled( nWhich, &aState ); + + rSet.Put( SfxBoolItem( nWhich, ::comphelper::getBOOL( aState.State ) ) ); + } + break; + + case SID_FM_FILTER_START: + bEnable = GetImpl()->getActiveControllerFeatures_Lock()->canDoFormFilter(); + break; + } + } + catch( const Exception& ) + { + OSL_FAIL( "FmFormShell::GetFormState: caught an exception while determining the state!" ); + } + if (!bEnable) + rSet.DisableItem(nWhich); + } +} + + +FmFormPage* FmFormShell::GetCurPage() const +{ + FmFormPage* pP = nullptr; + if (m_pFormView && m_pFormView->GetSdrPageView()) + pP = dynamic_cast<FmFormPage*>( m_pFormView->GetSdrPageView()->GetPage() ); + return pP; +} + + +void FmFormShell::SetView( FmFormView* _pView ) +{ + if ( m_pFormView ) + { + if ( IsActive() ) + GetImpl()->viewDeactivated_Lock(*m_pFormView); + + m_pFormView->SetFormShell( nullptr, FmFormView::FormShellAccess() ); + m_pFormView = nullptr; + m_pFormModel = nullptr; + } + + if ( !_pView ) + return; + + m_pFormView = _pView; + m_pFormView->SetFormShell( this, FmFormView::FormShellAccess() ); + m_pFormModel = static_cast<FmFormModel*>(m_pFormView->GetModel()); + + impl_setDesignMode( m_pFormView->IsDesignMode() ); + + // We activate our view if we are activated ourself, but sometimes the Activate precedes the SetView. + // But here we know both the view and our activation state so we at least are able to pass the latter + // to the former. + // FS - 30.06.99 - 67308 + if ( IsActive() ) + GetImpl()->viewActivated_Lock(*m_pFormView); +} + + +void FmFormShell::DetermineForms(bool bInvalidate) +{ + // are there forms on the current page + bool bForms = GetImpl()->hasForms_Lock(); + if (bForms != m_bHasForms) + { + m_bHasForms = bForms; + if (bInvalidate) + UIFeatureChanged(); + } +} + + +bool FmFormShell::GetY2KState(sal_uInt16& nReturn) +{ + return GetImpl()->GetY2KState_Lock(nReturn); +} + + +void FmFormShell::SetY2KState(sal_uInt16 n) +{ + GetImpl()->SetY2KState_Lock(n); +} + + +void FmFormShell::Activate(bool bMDI) +{ + SfxShell::Activate(bMDI); + + if ( m_pFormView ) + GetImpl()->viewActivated_Lock(*m_pFormView, true); +} + + +void FmFormShell::Deactivate(bool bMDI) +{ + SfxShell::Deactivate(bMDI); + + if ( m_pFormView ) + GetImpl()->viewDeactivated_Lock(*m_pFormView, false); +} + + +void FmFormShell::ExecuteTextAttribute( SfxRequest& _rReq ) +{ + m_pImpl->ExecuteTextAttribute_Lock(_rReq); +} + + +void FmFormShell::GetTextAttributeState( SfxItemSet& _rSet ) +{ + m_pImpl->GetTextAttributeState_Lock(_rSet); +} + + +bool FmFormShell::IsActiveControl() const +{ + return m_pImpl->IsActiveControl_Lock(false); +} + + +void FmFormShell::ForgetActiveControl() +{ + m_pImpl->ForgetActiveControl_Lock(); +} + + +void FmFormShell::SetControlActivationHandler( const Link<LinkParamNone*,void>& _rHdl ) +{ + m_pImpl->SetControlActivationHandler_Lock(_rHdl); +} + + +namespace +{ + SdrUnoObj* lcl_findUnoObject( const SdrObjList& _rObjList, const Reference< XControlModel >& _rxModel ) + { + SdrObjListIter aIter( &_rObjList ); + while ( aIter.IsMore() ) + { + SdrObject* pObject = aIter.Next(); + SdrUnoObj* pUnoObject = dynamic_cast<SdrUnoObj*>( pObject ); + if ( !pUnoObject ) + continue; + + Reference< XControlModel > xControlModel = pUnoObject->GetUnoControlModel(); + if ( !xControlModel.is() ) + continue; + + if ( _rxModel == xControlModel ) + return pUnoObject; + } + return nullptr; + } +} + + +void FmFormShell::ToggleControlFocus( const SdrUnoObj& i_rUnoObject, const SdrView& i_rView, OutputDevice& i_rDevice ) const +{ + try + { + // check if the focus currently is in a control + // Well, okay, do it the other way 'round: Check whether the current control of the active controller + // actually has the focus. This should be equivalent. + const bool bHasControlFocus = GetImpl()->HasControlFocus_Lock(); + + if ( bHasControlFocus ) + { + vcl::Window* pWindow( dynamic_cast< vcl::Window* >( &i_rDevice ) ); + OSL_ENSURE( pWindow, "FmFormShell::ToggleControlFocus: I need a Window, really!" ); + if ( pWindow ) + pWindow->GrabFocus(); + } + else + { + Reference< XControl > xControl; + GetFormControl( i_rUnoObject.GetUnoControlModel(), i_rView, i_rDevice, xControl ); + Reference< XWindow > xControlWindow( xControl, UNO_QUERY ); + if ( xControlWindow.is() ) + xControlWindow->setFocus(); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +namespace +{ + class FocusableControlsFilter : public svx::ISdrObjectFilter + { + public: + FocusableControlsFilter( const SdrView& i_rView, const OutputDevice& i_rDevice ) + :m_rView( i_rView ) + ,m_rDevice( i_rDevice ) + { + } + + public: + virtual bool includeObject( const SdrObject& i_rObject ) const override + { + const SdrUnoObj* pUnoObj = dynamic_cast< const SdrUnoObj* >( &i_rObject ); + if ( !pUnoObj ) + return false; + + Reference< XControl > xControl = pUnoObj->GetUnoControl( m_rView, m_rDevice ); + return FmXFormView::isFocusable( xControl ); + } + + private: + const SdrView& m_rView; + const OutputDevice& m_rDevice; + }; +} + + +::std::unique_ptr< svx::ISdrObjectFilter > FmFormShell::CreateFocusableControlFilter( const SdrView& i_rView, const OutputDevice& i_rDevice ) +{ + ::std::unique_ptr< svx::ISdrObjectFilter > pFilter; + + if ( !i_rView.IsDesignMode() ) + pFilter.reset( new FocusableControlsFilter( i_rView, i_rDevice ) ); + + return pFilter; +} + + +SdrUnoObj* FmFormShell::GetFormControl( const Reference< XControlModel >& _rxModel, const SdrView& _rView, const OutputDevice& _rDevice, Reference< XControl >& _out_rxControl ) const +{ + if ( !_rxModel.is() ) + return nullptr; + + // we can only retrieve controls for SdrObjects which belong to page which is actually displayed in the given view + SdrPageView* pPageView = _rView.GetSdrPageView(); + SdrPage* pPage = pPageView ? pPageView->GetPage() : nullptr; + OSL_ENSURE( pPage, "FmFormShell::GetFormControl: no page displayed in the given view!" ); + if ( !pPage ) + return nullptr; + + SdrUnoObj* pUnoObject = lcl_findUnoObject( *pPage, _rxModel ); + if ( pUnoObject ) + { + _out_rxControl = pUnoObject->GetUnoControl( _rView, _rDevice ); + return pUnoObject; + } + +#if OSL_DEBUG_LEVEL > 0 + // perhaps we are fed with a control model which lives on a page other than the one displayed + // in the given view. This is worth being reported as error, in non-product builds. + FmFormModel* pModel = GetFormModel(); + if ( pModel ) + { + sal_uInt16 pageCount = pModel->GetPageCount(); + for ( sal_uInt16 page = 0; page < pageCount; ++page ) + { + pPage = pModel->GetPage( page ); + OSL_ENSURE( pPage, "FmFormShell::GetFormControl: NULL page encountered!" ); + if ( !pPage ) + continue; + + pUnoObject = lcl_findUnoObject( *pPage, _rxModel ); + OSL_ENSURE( !pUnoObject, "FmFormShell::GetFormControl: the given control model belongs to a wrong page (displayed elsewhere)!" ); + } + } +#else + (void) this; // avoid loplugin:staticmethods +#endif + + return nullptr; +} + + +Reference< runtime::XFormController > FmFormShell::GetFormController( const Reference< XForm >& _rxForm, const SdrView& _rView, const OutputDevice& _rDevice ) +{ + const FmFormView* pFormView = dynamic_cast< const FmFormView* >( &_rView ); + if ( !pFormView ) + return nullptr; + + return pFormView->GetFormController( _rxForm, _rDevice ); +} + + +void FmFormShell::SetDesignMode( bool _bDesignMode ) +{ + if ( _bDesignMode == m_bDesignMode ) + return; + + FmFormModel* pModel = GetFormModel(); + if (pModel) + // Switch off the undo environment for the time of the transition. This ensures that + // one can also change non-transient properties there. (It should be done with + // caution, however, and it should always be reversed when one switches the mode back. + // An example is the setting of the maximum text length by the OEditModel on its control.) + pModel->GetUndoEnv().Lock(); + + // then the actual switch + if ( m_bDesignMode || PrepareClose() ) + impl_setDesignMode(!m_bDesignMode ); + + // and my undo environment back on + if ( pModel ) + pModel->GetUndoEnv().UnLock(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmshimp.cxx b/svx/source/form/fmshimp.cxx new file mode 100644 index 000000000..6407f74e9 --- /dev/null +++ b/svx/source/form/fmshimp.cxx @@ -0,0 +1,3972 @@ +/* -*- 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 <sal/config.h> + +#include <o3tl/safeint.hxx> +#include <sal/macros.h> +#include <sal/log.hxx> +#include <fmobj.hxx> +#include <fmpgeimp.hxx> +#include <svx/fmtools.hxx> +#include <fmprop.hxx> +#include <fmservs.hxx> +#include <fmshimp.hxx> +#include <fmtextcontrolshell.hxx> +#include <fmundo.hxx> +#include <fmurl.hxx> +#include <fmvwimp.hxx> +#include <gridcols.hxx> +#include <svx/svditer.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <svx/fmglob.hxx> +#include <svx/fmmodel.hxx> +#include <svx/fmpage.hxx> +#include <svx/fmshell.hxx> +#include <svx/fmview.hxx> +#include <svx/obj3d.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svxdlg.hxx> +#include <svx/svxids.hrc> +#include <bitmaps.hlst> + +#include <com/sun/star/awt/XWindow2.hpp> +#include <com/sun/star/awt/XCheckBox.hpp> +#include <com/sun/star/awt/XListBox.hpp> +#include <com/sun/star/awt/XTextComponent.hpp> +#include <com/sun/star/beans/theIntrospection.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/container/XContainer.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/form/ListSourceType.hpp> +#include <com/sun/star/form/TabOrderDialog.hpp> +#include <com/sun/star/form/XGrid.hpp> +#include <com/sun/star/form/XGridPeer.hpp> +#include <com/sun/star/form/XLoadable.hpp> +#include <com/sun/star/form/XReset.hpp> +#include <com/sun/star/form/binding/XBindableValue.hpp> +#include <com/sun/star/form/binding/XListEntrySink.hpp> +#include <com/sun/star/frame/FrameSearchFlag.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/script/XEventAttacherManager.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/util/XModeSelector.hpp> +#include <com/sun/star/util/XNumberFormatsSupplier.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> + +#include <comphelper/evtmethodhelper.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/property.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/solarmutex.hxx> +#include <comphelper/string.hxx> +#include <comphelper/types.hxx> +#include <connectivity/dbtools.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/viewsh.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <vcl/image.hxx> +#include <vcl/weld.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> + +#include <algorithm> +#include <map> +#include <memory> +#include <vector> + +// is used for Invalidate -> maintain it as well +static const sal_uInt16 DatabaseSlotMap[] = +{ + SID_FM_RECORD_FIRST, + SID_FM_RECORD_NEXT, + SID_FM_RECORD_PREV, + SID_FM_RECORD_LAST, + SID_FM_RECORD_NEW, + SID_FM_RECORD_DELETE, + SID_FM_RECORD_ABSOLUTE, + SID_FM_RECORD_TOTAL, + SID_FM_RECORD_SAVE, + SID_FM_RECORD_UNDO, + SID_FM_REMOVE_FILTER_SORT, + SID_FM_SORTUP, + SID_FM_SORTDOWN, + SID_FM_ORDERCRIT, + SID_FM_AUTOFILTER, + SID_FM_FORM_FILTERED, + SID_FM_REFRESH, + SID_FM_REFRESH_FORM_CONTROL, + SID_FM_SEARCH, + SID_FM_FILTER_START, + SID_FM_VIEW_AS_GRID, + 0 +}; + +// is used for Invalidate -> maintain it as well +// sort ascending !!!!!! +static const sal_Int16 DlgSlotMap[] = // slots of the controller +{ + SID_FM_CTL_PROPERTIES, + SID_FM_PROPERTIES, + SID_FM_TAB_DIALOG, + SID_FM_ADD_FIELD, + SID_FM_SHOW_FMEXPLORER, + SID_FM_FIELDS_CONTROL, + SID_FM_SHOW_PROPERTIES, + SID_FM_PROPERTY_CONTROL, + SID_FM_FMEXPLORER_CONTROL, + SID_FM_SHOW_DATANAVIGATOR, + SID_FM_DATANAVIGATOR_CONTROL, + 0 +}; + +static const sal_Int16 SelObjectSlotMap[] = // slots depending on the SelObject +{ + SID_FM_CONVERTTO_EDIT, + SID_FM_CONVERTTO_BUTTON, + SID_FM_CONVERTTO_FIXEDTEXT, + SID_FM_CONVERTTO_LISTBOX, + SID_FM_CONVERTTO_CHECKBOX, + SID_FM_CONVERTTO_RADIOBUTTON, + SID_FM_CONVERTTO_GROUPBOX, + SID_FM_CONVERTTO_COMBOBOX, + SID_FM_CONVERTTO_IMAGEBUTTON, + SID_FM_CONVERTTO_FILECONTROL, + SID_FM_CONVERTTO_DATE, + SID_FM_CONVERTTO_TIME, + SID_FM_CONVERTTO_NUMERIC, + SID_FM_CONVERTTO_CURRENCY, + SID_FM_CONVERTTO_PATTERN, + SID_FM_CONVERTTO_IMAGECONTROL, + SID_FM_CONVERTTO_FORMATTED, + SID_FM_CONVERTTO_SCROLLBAR, + SID_FM_CONVERTTO_SPINBUTTON, + SID_FM_CONVERTTO_NAVIGATIONBAR, + + SID_FM_FMEXPLORER_CONTROL, + SID_FM_DATANAVIGATOR_CONTROL, + + 0 +}; + +// the following arrays must be consistent, i.e., corresponding entries should +// be at the same relative position within their respective arrays +static const char* aConvertSlots[] = +{ + "ConvertToEdit", + "ConvertToButton", + "ConvertToFixed", + "ConvertToList", + "ConvertToCheckBox", + "ConvertToRadio", + "ConvertToGroup", + "ConvertToCombo", + "ConvertToImageBtn", + "ConvertToFileControl", + "ConvertToDate", + "ConvertToTime", + "ConvertToNumeric", + "ConvertToCurrency", + "ConvertToPattern", + "ConvertToImageControl", + "ConvertToFormatted", + "ConvertToScrollBar", + "ConvertToSpinButton", + "ConvertToNavigationBar" +}; + +static const OUStringLiteral aImgIds[] = +{ + RID_SVXBMP_EDITBOX, + RID_SVXBMP_BUTTON, + RID_SVXBMP_FIXEDTEXT, + RID_SVXBMP_LISTBOX, + RID_SVXBMP_CHECKBOX, + RID_SVXBMP_RADIOBUTTON, + RID_SVXBMP_GROUPBOX, + RID_SVXBMP_COMBOBOX, + RID_SVXBMP_IMAGEBUTTON, + RID_SVXBMP_FILECONTROL, + RID_SVXBMP_DATEFIELD, + RID_SVXBMP_TIMEFIELD, + RID_SVXBMP_NUMERICFIELD, + RID_SVXBMP_CURRENCYFIELD, + RID_SVXBMP_PATTERNFIELD, + RID_SVXBMP_IMAGECONTROL, + RID_SVXBMP_FORMATTEDFIELD, + RID_SVXBMP_SCROLLBAR, + RID_SVXBMP_SPINBUTTON, + RID_SVXBMP_NAVIGATIONBAR +}; + +static const sal_Int16 nObjectTypes[] = +{ + OBJ_FM_EDIT, + OBJ_FM_BUTTON, + OBJ_FM_FIXEDTEXT, + OBJ_FM_LISTBOX, + OBJ_FM_CHECKBOX, + OBJ_FM_RADIOBUTTON, + OBJ_FM_GROUPBOX, + OBJ_FM_COMBOBOX, + OBJ_FM_IMAGEBUTTON, + OBJ_FM_FILECONTROL, + OBJ_FM_DATEFIELD, + OBJ_FM_TIMEFIELD, + OBJ_FM_NUMERICFIELD, + OBJ_FM_CURRENCYFIELD, + OBJ_FM_PATTERNFIELD, + OBJ_FM_IMAGECONTROL, + OBJ_FM_FORMATTEDFIELD, + OBJ_FM_SCROLLBAR, + OBJ_FM_SPINBUTTON, + OBJ_FM_NAVIGATIONBAR +}; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::ui; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdbcx; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::form; +using namespace ::com::sun::star::form::binding; +using namespace ::com::sun::star::form::runtime; +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::view; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::script; +using namespace ::svxform; +using namespace ::svx; +using namespace ::dbtools; + + +//= helper + +namespace +{ + + void collectInterfacesFromMarkList( const SdrMarkList& _rMarkList, InterfaceBag& /* [out] */ _rInterfaces ) + { + _rInterfaces.clear(); + + const size_t nMarkCount = _rMarkList.GetMarkCount(); + for ( size_t i = 0; i < nMarkCount; ++i) + { + SdrObject* pCurrent = _rMarkList.GetMark( i )->GetMarkedSdrObj(); + + std::unique_ptr<SdrObjListIter> pGroupIterator; + if ( pCurrent->IsGroupObject() ) + { + pGroupIterator.reset(new SdrObjListIter( pCurrent->GetSubList() )); + pCurrent = pGroupIterator->IsMore() ? pGroupIterator->Next() : nullptr; + } + + while ( pCurrent ) + { + FmFormObj* pAsFormObject = FmFormObj::GetFormObject( pCurrent ); + // note this will de-reference virtual objects, if necessary/possible + if ( pAsFormObject ) + { + Reference< XInterface > xControlModel( pAsFormObject->GetUnoControlModel(), UNO_QUERY ); + // the UNO_QUERY is important for normalization + if ( xControlModel.is() ) + _rInterfaces.insert( xControlModel ); + } + + // next element + pCurrent = pGroupIterator && pGroupIterator->IsMore() ? pGroupIterator->Next() : nullptr; + } + } + } + + + sal_Int32 GridView2ModelPos(const Reference< XIndexAccess>& rColumns, sal_Int16 nViewPos) + { + try + { + if (rColumns.is()) + { + // loop through all columns + sal_Int32 i; + Reference< XPropertySet> xCur; + for (i=0; i<rColumns->getCount(); ++i) + { + rColumns->getByIndex(i) >>= xCur; + if (!::comphelper::getBOOL(xCur->getPropertyValue(FM_PROP_HIDDEN))) + { + // for every visible col : if nViewPos is greater zero, decrement it, else we + // have found the model position + if (!nViewPos) + break; + else + --nViewPos; + } + } + if (i<rColumns->getCount()) + return i; + } + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return -1; + } + + + void TransferEventScripts(const Reference< XControlModel>& xModel, const Reference< XControl>& xControl, + const Sequence< ScriptEventDescriptor>& rTransferIfAvailable) + { + // first check if we have a XEventAttacherManager for the model + Reference< XChild> xModelChild(xModel, UNO_QUERY); + if (!xModelChild.is()) + return; // nothing to do + + Reference< XEventAttacherManager> xEventManager(xModelChild->getParent(), UNO_QUERY); + if (!xEventManager.is()) + return; // nothing to do + + if (!rTransferIfAvailable.hasElements()) + return; // nothing to do + + // check for the index of the model within its parent + Reference< XIndexAccess> xParentIndex(xModelChild->getParent(), UNO_QUERY); + if (!xParentIndex.is()) + return; // nothing to do + sal_Int32 nIndex = getElementPos(xParentIndex, xModel); + if (nIndex<0 || nIndex>=xParentIndex->getCount()) + return; // nothing to do + + // then we need information about the listeners supported by the control and the model + Sequence< Type> aModelListeners; + Sequence< Type> aControlListeners; + + Reference< XIntrospection> xIntrospection = theIntrospection::get(::comphelper::getProcessComponentContext()); + + if (xModel.is()) + { + Any aModel(makeAny(xModel)); + aModelListeners = xIntrospection->inspect(aModel)->getSupportedListeners(); + } + + if (xControl.is()) + { + Any aControl(makeAny(xControl)); + aControlListeners = xIntrospection->inspect(aControl)->getSupportedListeners(); + } + + sal_Int32 nMaxNewLen = aModelListeners.getLength() + aControlListeners.getLength(); + if (!nMaxNewLen) + return; // the model and the listener don't support any listeners (or we were unable to retrieve these infos) + + Sequence< ScriptEventDescriptor> aTransferable(nMaxNewLen); + ScriptEventDescriptor* pTransferable = aTransferable.getArray(); + + for (const ScriptEventDescriptor& rCurrent : rTransferIfAvailable) + { + // search the model/control idl classes for the event described by pCurrent + for (const Sequence< Type>* pCurrentArray : { &aModelListeners, &aControlListeners }) + { + for (const Type& rCurrentListener : *pCurrentArray) + { + OUString aListener = rCurrentListener.getTypeName(); + if (!aListener.isEmpty()) + aListener = aListener.copy(aListener.lastIndexOf('.')+1); + + if (aListener == rCurrent.ListenerType) + // the current ScriptEventDescriptor doesn't match the current listeners class + continue; + + // now check the methods + Sequence< OUString> aMethodsNames = ::comphelper::getEventMethodsForType(rCurrentListener); + + if (comphelper::findValue(aMethodsNames, rCurrent.EventMethod) != -1) + { + // we can transfer the script event : the model (control) supports it + *pTransferable = rCurrent; + ++pTransferable; + break; + } + } + } + } + + sal_Int32 nRealNewLen = pTransferable - aTransferable.getArray(); + aTransferable.realloc(nRealNewLen); + + xEventManager->registerScriptEvents(nIndex, aTransferable); + } + + + OUString getServiceNameByControlType(sal_Int16 nType) + { + switch (nType) + { + case OBJ_FM_EDIT : return FM_COMPONENT_TEXTFIELD; + case OBJ_FM_BUTTON : return FM_COMPONENT_COMMANDBUTTON; + case OBJ_FM_FIXEDTEXT : return FM_COMPONENT_FIXEDTEXT; + case OBJ_FM_LISTBOX : return FM_COMPONENT_LISTBOX; + case OBJ_FM_CHECKBOX : return FM_COMPONENT_CHECKBOX; + case OBJ_FM_RADIOBUTTON : return FM_COMPONENT_RADIOBUTTON; + case OBJ_FM_GROUPBOX : return FM_COMPONENT_GROUPBOX; + case OBJ_FM_COMBOBOX : return FM_COMPONENT_COMBOBOX; + case OBJ_FM_GRID : return FM_COMPONENT_GRIDCONTROL; + case OBJ_FM_IMAGEBUTTON : return FM_COMPONENT_IMAGEBUTTON; + case OBJ_FM_FILECONTROL : return FM_COMPONENT_FILECONTROL; + case OBJ_FM_DATEFIELD : return FM_COMPONENT_DATEFIELD; + case OBJ_FM_TIMEFIELD : return FM_COMPONENT_TIMEFIELD; + case OBJ_FM_NUMERICFIELD : return FM_COMPONENT_NUMERICFIELD; + case OBJ_FM_CURRENCYFIELD : return FM_COMPONENT_CURRENCYFIELD; + case OBJ_FM_PATTERNFIELD : return FM_COMPONENT_PATTERNFIELD; + case OBJ_FM_HIDDEN : return FM_COMPONENT_HIDDENCONTROL; + case OBJ_FM_IMAGECONTROL : return FM_COMPONENT_IMAGECONTROL; + case OBJ_FM_FORMATTEDFIELD : return FM_COMPONENT_FORMATTEDFIELD; + case OBJ_FM_SCROLLBAR : return FM_SUN_COMPONENT_SCROLLBAR; + case OBJ_FM_SPINBUTTON : return FM_SUN_COMPONENT_SPINBUTTON; + case OBJ_FM_NAVIGATIONBAR : return FM_SUN_COMPONENT_NAVIGATIONBAR; + } + return OUString(); + } + +} + + +// check if the control has one of the interfaces we can use for searching +// *_pCurrentText will be filled with the current text of the control (as used when searching this control) +bool IsSearchableControl( const css::uno::Reference< css::uno::XInterface>& _rxControl, + OUString* _pCurrentText ) +{ + if ( !_rxControl.is() ) + return false; + + Reference< XTextComponent > xAsText( _rxControl, UNO_QUERY ); + if ( xAsText.is() ) + { + if ( _pCurrentText ) + *_pCurrentText = xAsText->getText(); + return true; + } + + Reference< XListBox > xListBox( _rxControl, UNO_QUERY ); + if ( xListBox.is() ) + { + if ( _pCurrentText ) + *_pCurrentText = xListBox->getSelectedItem(); + return true; + } + + Reference< XCheckBox > xCheckBox( _rxControl, UNO_QUERY ); + if ( xCheckBox.is() ) + { + if ( _pCurrentText ) + { + switch ( static_cast<::TriState>(xCheckBox->getState()) ) + { + case TRISTATE_FALSE: *_pCurrentText = "0"; break; + case TRISTATE_TRUE: *_pCurrentText = "1"; break; + default: _pCurrentText->clear(); break; + } + } + return true; + } + + return false; +} + + +bool FmXBoundFormFieldIterator::ShouldStepInto(const Reference< XInterface>& _rContainer) const +{ + if (_rContainer == m_xStartingPoint) + // would be quite stupid to step over the root... + return true; + + return Reference< XControlModel>(_rContainer, UNO_QUERY).is(); +} + + +bool FmXBoundFormFieldIterator::ShouldHandleElement(const Reference< XInterface>& _rElement) +{ + if (!_rElement.is()) + // NULL element + return false; + + if (Reference< XForm>(_rElement, UNO_QUERY).is() || Reference< XGrid>(_rElement, UNO_QUERY).is()) + // a forms or a grid + return false; + + Reference< XPropertySet> xSet(_rElement, UNO_QUERY); + if (!xSet.is() || !::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet)) + // no "BoundField" property + return false; + + Any aVal( xSet->getPropertyValue(FM_PROP_BOUNDFIELD) ); + if (aVal.getValueTypeClass() != TypeClass_INTERFACE) + // void or invalid property value + return false; + + return aVal.hasValue(); +} + + +static bool isControlList(const SdrMarkList& rMarkList) +{ + // the list contains only controls and at least one control + const size_t nMarkCount = rMarkList.GetMarkCount(); + bool bControlList = nMarkCount != 0; + + bool bHadAnyLeafs = false; + + for (size_t i = 0; i < nMarkCount && bControlList; ++i) + { + SdrObject *pObj = rMarkList.GetMark(i)->GetMarkedSdrObj(); + E3dObject* pAs3DObject = dynamic_cast< E3dObject* >( pObj); + // E3dObject's do not contain any 2D-objects (by definition) + // we need this extra check here : an E3dObject->IsGroupObject says "YES", but an SdrObjListIter working + // with an E3dObject doesn't give me any Nodes (E3dObject has a sub list, but no members in that list, + // cause there implementation differs from the one of "normal" SdrObject's. Unfortunally SdrObject::IsGroupObject + // doesn't check the element count of the sub list, which is simply a bug in IsGroupObject we can't fix at the moment). + // So at the end of this function bControlList would have the same value it was initialized with above : sal_True + // And this would be wrong :) + // 03.02.00 - 72529 - FS + if (!pAs3DObject) + { + if (pObj->IsGroupObject()) + { + SdrObjListIter aIter(pObj->GetSubList()); + while (aIter.IsMore() && bControlList) + { + bControlList = SdrInventor::FmForm == aIter.Next()->GetObjInventor(); + bHadAnyLeafs = true; + } + } + else + { + bHadAnyLeafs = true; + bControlList = SdrInventor::FmForm == pObj->GetObjInventor(); + } + } + } + + return bControlList && bHadAnyLeafs; +} + + +static Reference< XForm > GetForm(const Reference< XInterface>& _rxElement) +{ + Reference< XForm > xForm( _rxElement, UNO_QUERY ); + if ( xForm.is() ) + return xForm; + + Reference< XChild > xChild( _rxElement, UNO_QUERY ); + if ( xChild.is() ) + return GetForm( xChild->getParent() ); + + return Reference< XForm >(); +} + +FmXFormShell_Base_Disambiguation::FmXFormShell_Base_Disambiguation( ::osl::Mutex& _rMutex ) + :FmXFormShell_BD_BASE( _rMutex ) +{ +} + +FmXFormShell::FmXFormShell( FmFormShell& _rShell, SfxViewFrame* _pViewFrame ) + :FmXFormShell_BASE(m_aMutex) + ,FmXFormShell_CFGBASE("Office.Common/Misc", ConfigItemMode::NONE) + ,m_eNavigate( NavigationBarMode_NONE ) + ,m_nInvalidationEvent( nullptr ) + ,m_nActivationEvent( nullptr ) + ,m_pShell( &_rShell ) + ,m_pTextShell( new svx::FmTextControlShell( _pViewFrame ) ) + ,m_aActiveControllerFeatures( this ) + ,m_aNavControllerFeatures( this ) + ,m_eDocumentType( eUnknownDocumentType ) + ,m_nLockSlotInvalidation( 0 ) + ,m_bHadPropertyBrowserInDesignMode( false ) + ,m_bTrackProperties( true ) + ,m_bUseWizards( true ) + ,m_bDatabaseBar( false ) + ,m_bInActivate( false ) + ,m_bSetFocus( false ) + ,m_bFilterMode( false ) + ,m_bChangingDesignMode( false ) + ,m_bPreparedClose( false ) + ,m_bFirstActivation( true ) +{ + m_aMarkTimer.SetTimeout(100); + m_aMarkTimer.SetInvokeHandler(LINK(this, FmXFormShell, OnTimeOut_Lock)); + m_aMarkTimer.SetDebugName("svx::FmXFormShell m_aMarkTimer"); + + m_xAttachedFrame = _pViewFrame->GetFrame().GetFrameInterface(); + + // to prevent deletion of this we acquire our refcounter once + osl_atomic_increment(&m_refCount); + + // correct the refcounter + osl_atomic_decrement(&m_refCount); + + // cache the current configuration settings we're interested in + implAdjustConfigCache_Lock(); + // and register for changes on this settings + Sequence< OUString > aNames { "FormControlPilotsEnabled" }; + EnableNotification(aNames); +} + + +FmXFormShell::~FmXFormShell() +{ +} + + +Reference< css::frame::XModel > FmXFormShell::getContextDocument_Lock() const +{ + Reference< css::frame::XModel > xModel; + + // determine the type of document we live in + try + { + Reference< css::frame::XController > xController; + if ( m_xAttachedFrame.is() ) + xController = m_xAttachedFrame->getController(); + if ( xController.is() ) + xModel = xController->getModel(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return xModel; +} + + +bool FmXFormShell::isEnhancedForm_Lock() const +{ + return getDocumentType_Lock() == eEnhancedForm; +} + + +bool FmXFormShell::impl_checkDisposed_Lock() const +{ + DBG_TESTSOLARMUTEX(); + if ( !m_pShell ) + { + OSL_FAIL( "FmXFormShell::impl_checkDisposed: already disposed!" ); + return true; + } + return false; +} + + +::svxform::DocumentType FmXFormShell::getDocumentType_Lock() const +{ + if ( m_eDocumentType != eUnknownDocumentType ) + return m_eDocumentType; + + // determine the type of document we live in + Reference<css::frame::XModel> xModel = getContextDocument_Lock(); + if ( xModel.is() ) + m_eDocumentType = DocumentClassification::classifyDocument( xModel ); + else + { + OSL_FAIL( "FmXFormShell::getDocumentType: can't determine the document type!" ); + m_eDocumentType = eTextDocument; + // fallback, just to have a defined state + } + + return m_eDocumentType; +} + + +bool FmXFormShell::IsReadonlyDoc_Lock() const +{ + if (impl_checkDisposed_Lock()) + return true; + + FmFormModel* pModel = m_pShell->GetFormModel(); + if ( pModel && pModel->GetObjectShell() ) + return pModel->GetObjectShell()->IsReadOnly() || pModel->GetObjectShell()->IsReadOnlyUI(); + return true; +} + +// EventListener + +void SAL_CALL FmXFormShell::disposing(const lang::EventObject& e) +{ + SolarMutexGuard g; + + if (m_xActiveController == e.Source) + { + // the controller will release, then release everything + stopListening_Lock(); + m_xActiveForm = nullptr; + m_xActiveController = nullptr; + m_xNavigationController = nullptr; + + m_aActiveControllerFeatures.dispose(); + m_aNavControllerFeatures.dispose(); + + if ( m_pShell ) + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().InvalidateShell(*m_pShell); + } + + if (e.Source == m_xExternalViewController) + { + Reference< runtime::XFormController > xFormController( m_xExternalViewController, UNO_QUERY ); + OSL_ENSURE( xFormController.is(), "FmXFormShell::disposing: invalid external view controller!" ); + if (xFormController.is()) + xFormController->removeActivateListener(static_cast<XFormControllerListener*>(this)); + + if (m_xExternalViewController.is()) + m_xExternalViewController->removeEventListener(static_cast<XEventListener*>(static_cast<XPropertyChangeListener*>(this))); + + m_xExternalViewController = nullptr; + m_xExternalDisplayedForm = nullptr; + m_xExtViewTriggerController = nullptr; + + InvalidateSlot_Lock( SID_FM_VIEW_AS_GRID, false ); + } +} + + +void SAL_CALL FmXFormShell::propertyChange(const PropertyChangeEvent& evt) +{ + SolarMutexGuard g; + + if (impl_checkDisposed_Lock()) + return; + + if (evt.PropertyName == FM_PROP_ROWCOUNT) + { + // The update following this forces a re-painting of the corresponding + // slots. But if I am not in the MainThread of the application (because, + // for example, a cursor is counting data sets at the moment and always + // gives me this PropertyChanges), this can clash with normal paints in + // the MainThread of the application. (Such paints happen, for example, + // if one simply places another application over the office and switches + // back again). + // Therefore the use of the SolarMutex, which safeguards that. + comphelper::SolarMutex& rSolarSafety = Application::GetSolarMutex(); + if (rSolarSafety.tryToAcquire()) + { + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().Invalidate(SID_FM_RECORD_TOTAL, true); + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().Update(SID_FM_RECORD_TOTAL); + rSolarSafety.release(); + } + else + { + // with the following the slot is invalidated asynchron + LockSlotInvalidation_Lock(true); + InvalidateSlot_Lock(SID_FM_RECORD_TOTAL, false); + LockSlotInvalidation_Lock(false); + } + } + + // this may be called from a non-main-thread so invalidate the shell asynchronously + LockSlotInvalidation_Lock(true); + InvalidateSlot_Lock(0, false); // special meaning : invalidate m_pShell + LockSlotInvalidation_Lock(false); +} + + +void FmXFormShell::invalidateFeatures( const ::std::vector< sal_Int32 >& _rFeatures ) +{ + SolarMutexGuard g; + + if (impl_checkDisposed_Lock()) + return; + + OSL_ENSURE( !_rFeatures.empty(), "FmXFormShell::invalidateFeatures: invalid arguments!" ); + + if ( m_pShell->GetViewShell() && m_pShell->GetViewShell()->GetViewFrame() ) + { + // unfortunately, SFX requires sal_uInt16 + ::std::vector< sal_uInt16 > aSlotIds; + aSlotIds.reserve( _rFeatures.size() ); + ::std::copy( _rFeatures.begin(), + _rFeatures.end(), + ::std::insert_iterator< ::std::vector< sal_uInt16 > >( aSlotIds, aSlotIds.begin() ) + ); + + // furthermore, SFX wants a terminating 0 + aSlotIds.push_back( 0 ); + + // and, last but not least, SFX wants the ids to be sorted + ::std::sort( aSlotIds.begin(), aSlotIds.end() - 1 ); + + sal_uInt16 *pSlotIds = aSlotIds.data(); + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().Invalidate( pSlotIds ); + } +} + + +void SAL_CALL FmXFormShell::formActivated(const lang::EventObject& rEvent) +{ + SolarMutexGuard g; + + if (impl_checkDisposed_Lock()) + return; + + Reference< runtime::XFormController > xController( rEvent.Source, UNO_QUERY_THROW ); + m_pTextShell->formActivated( xController ); + setActiveController_Lock(xController); +} + + +void SAL_CALL FmXFormShell::formDeactivated(const lang::EventObject& rEvent) +{ + SolarMutexGuard g; + + if (impl_checkDisposed_Lock()) + return; + + Reference< runtime::XFormController > xController( rEvent.Source, UNO_QUERY_THROW ); + m_pTextShell->formDeactivated( xController ); +} + + +void FmXFormShell::disposing() +{ + SolarMutexGuard g; + + FmXFormShell_BASE::disposing(); + + if ( m_pShell && !m_pShell->IsDesignMode() ) + setActiveController_Lock(nullptr, true); + // do NOT save the content of the old form (the second parameter tells this) + // if we're here, then we expect that PrepareClose has been called, and thus the user + // got a chance to commit or reject any changes. So in case we're here and there + // are still uncommitted changes, the user explicitly wanted this. + + m_pTextShell->dispose(); + + m_xAttachedFrame = nullptr; + + CloseExternalFormViewer_Lock(); + + while ( !m_aLoadingPages.empty() ) + { + Application::RemoveUserEvent( m_aLoadingPages.front().nEventId ); + m_aLoadingPages.pop(); + } + + { + if (m_nInvalidationEvent) + { + Application::RemoveUserEvent(m_nInvalidationEvent); + m_nInvalidationEvent = nullptr; + } + if ( m_nActivationEvent ) + { + Application::RemoveUserEvent( m_nActivationEvent ); + m_nActivationEvent = nullptr; + } + } + + { + DBG_ASSERT(!m_nInvalidationEvent, "FmXFormShell::~FmXFormShell : still have an invalidation event !"); + // should have been deleted while being disposed + + m_aMarkTimer.Stop(); + } + + DisableNotification(); + + RemoveElement_Lock(m_xForms); + m_xForms.clear(); + + impl_switchActiveControllerListening_Lock(false); + m_xActiveController = nullptr; + m_xActiveForm = nullptr; + + m_pShell = nullptr; + m_xNavigationController = nullptr; + m_xCurrentForm = nullptr; + m_xLastGridFound = nullptr; + m_xAttachedFrame = nullptr; + m_xExternalViewController = nullptr; + m_xExtViewTriggerController = nullptr; + m_xExternalDisplayedForm = nullptr; + + InterfaceBag aEmpty; + m_aCurrentSelection.swap( aEmpty ); + + m_aActiveControllerFeatures.dispose(); + m_aNavControllerFeatures.dispose(); +} + + +void FmXFormShell::UpdateSlot_Lock(sal_Int16 _nId) +{ + if (impl_checkDisposed_Lock()) + return; + + if ( m_nLockSlotInvalidation ) + { + OSL_FAIL( "FmXFormShell::UpdateSlot: cannot update if invalidation is currently locked!" ); + InvalidateSlot_Lock(_nId, false); + } + else + { + OSL_ENSURE( _nId, "FmXFormShell::UpdateSlot: can't update the complete shell!" ); + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().Invalidate( _nId, true, true ); + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().Update( _nId ); + } +} + + +void FmXFormShell::InvalidateSlot_Lock(sal_Int16 nId, bool bWithId) +{ + if (impl_checkDisposed_Lock()) + return; + + if (m_nLockSlotInvalidation) + { + sal_uInt8 nFlags = ( bWithId ? 0x01 : 0 ); + m_arrInvalidSlots.emplace_back(nId, nFlags ); + } + else + if (nId) + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().Invalidate(nId, true, bWithId); + else + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().InvalidateShell(*m_pShell); +} + + +void FmXFormShell::LockSlotInvalidation_Lock(bool bLock) +{ + if (impl_checkDisposed_Lock()) + return; + + DBG_ASSERT(bLock || m_nLockSlotInvalidation>0, "FmXFormShell::LockSlotInvalidation : invalid call !"); + + if (bLock) + ++m_nLockSlotInvalidation; + else if (!--m_nLockSlotInvalidation) + { + // (asynchronously) invalidate everything accumulated during the locked phase + if (!m_nInvalidationEvent) + m_nInvalidationEvent = Application::PostUserEvent(LINK(this, FmXFormShell, OnInvalidateSlots_Lock)); + } +} + + +IMPL_LINK_NOARG(FmXFormShell, OnInvalidateSlots_Lock, void*,void) +{ + if (impl_checkDisposed_Lock()) + return; + + m_nInvalidationEvent = nullptr; + + for (const auto& rInvalidSlot : m_arrInvalidSlots) + { + if (rInvalidSlot.id) + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().Invalidate(rInvalidSlot.id, true, (rInvalidSlot.flags & 0x01)); + else + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().InvalidateShell(*m_pShell); + } + m_arrInvalidSlots.clear(); +} + + +void FmXFormShell::ForceUpdateSelection_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + if (IsSelectionUpdatePending_Lock()) + { + m_aMarkTimer.Stop(); + + // optionally turn off the invalidation of slots which is implicitly done by SetSelection + LockSlotInvalidation_Lock(true); + + SetSelection_Lock(m_pShell->GetFormView()->GetMarkedObjectList()); + + LockSlotInvalidation_Lock(false); + } +} + +std::unique_ptr<VclBuilder> FmXFormShell::GetConversionMenu_Lock() +{ + std::unique_ptr<VclBuilder> pBuilder(new VclBuilder(nullptr, VclBuilderContainer::getUIRootDir(), "svx/ui/convertmenu.ui", "")); + VclPtr<PopupMenu> pNewMenu(pBuilder->get_menu("menu")); + for (size_t i = 0; i < SAL_N_ELEMENTS(aConvertSlots); ++i) + { + // the corresponding image at it + pNewMenu->SetItemImage(pNewMenu->GetItemId(aConvertSlots[i]), Image(StockImage::Yes, aImgIds[i])); + } + return pBuilder; +} + +OString FmXFormShell::SlotToIdent(sal_uInt16 nSlot) +{ + assert(SAL_N_ELEMENTS(SelObjectSlotMap) >= SAL_N_ELEMENTS(aConvertSlots)); + + for (size_t i = 0; i < SAL_N_ELEMENTS(aConvertSlots); ++i) + { + if (nSlot == SelObjectSlotMap[i]) + return aConvertSlots[i]; + } + + return OString(); +} + +bool FmXFormShell::isControlConversionSlot(const OString& rIdent) +{ + for (const auto& rConvertSlot : aConvertSlots) + if (rIdent == rConvertSlot) + return true; + return false; +} + +void FmXFormShell::executeControlConversionSlot_Lock(const OString &rIdent) +{ + OSL_PRECOND( canConvertCurrentSelectionToControl_Lock(rIdent), "FmXFormShell::executeControlConversionSlot: illegal call!" ); + InterfaceBag::const_iterator aSelectedElement = m_aCurrentSelection.begin(); + if ( aSelectedElement == m_aCurrentSelection.end() ) + return; + + executeControlConversionSlot_Lock(Reference<XFormComponent>(*aSelectedElement, UNO_QUERY), rIdent); +} + +bool FmXFormShell::executeControlConversionSlot_Lock(const Reference<XFormComponent>& _rxObject, const OString& rIdent) +{ + if (impl_checkDisposed_Lock()) + return false; + + OSL_ENSURE( _rxObject.is(), "FmXFormShell::executeControlConversionSlot: invalid object!" ); + if ( !_rxObject.is() ) + return false; + + SdrPage* pPage = m_pShell->GetCurPage(); + FmFormPage* pFormPage = dynamic_cast< FmFormPage* >( pPage ); + OSL_ENSURE( pFormPage, "FmXFormShell::executeControlConversionSlot: no current (form) page!" ); + if ( !pFormPage ) + return false; + + OSL_ENSURE( isSolelySelected_Lock(_rxObject), + "FmXFormShell::executeControlConversionSlot: hmm ... shouldn't this parameter be redundant?" ); + + for (size_t lookupSlot = 0; lookupSlot < SAL_N_ELEMENTS(aConvertSlots); ++lookupSlot) + { + if (rIdent == aConvertSlots[lookupSlot]) + { + Reference< XInterface > xNormalizedObject( _rxObject, UNO_QUERY ); + + FmFormObj* pFormObject = nullptr; + SdrObjListIter aPageIter( pFormPage ); + while ( aPageIter.IsMore() ) + { + SdrObject* pCurrent = aPageIter.Next(); + pFormObject = FmFormObj::GetFormObject( pCurrent ); + if ( !pFormObject ) + continue; + + Reference< XInterface > xCurrentNormalized( pFormObject->GetUnoControlModel(), UNO_QUERY ); + if ( xCurrentNormalized.get() == xNormalizedObject.get() ) + break; + + pFormObject = nullptr; + } + + if ( !pFormObject ) + return false; + + OUString sNewName( getServiceNameByControlType( nObjectTypes[ lookupSlot ] ) ); + Reference<XComponentContext> xContext = comphelper::getProcessComponentContext(); + Reference< XControlModel> xNewModel( xContext->getServiceManager()->createInstanceWithContext(sNewName, xContext), UNO_QUERY ); + if (!xNewModel.is()) + return false; + + Reference< XControlModel> xOldModel( pFormObject->GetUnoControlModel() ); + + // transfer properties + Reference< XPropertySet> xOldSet(xOldModel, UNO_QUERY); + Reference< XPropertySet> xNewSet(xNewModel, UNO_QUERY); + + + lang::Locale aNewLanguage = Application::GetSettings().GetUILanguageTag().getLocale(); + TransferFormComponentProperties(xOldSet, xNewSet, aNewLanguage); + + Sequence< css::script::ScriptEventDescriptor> aOldScripts; + Reference< XChild> xChild(xOldModel, UNO_QUERY); + if (xChild.is()) + { + Reference< XIndexAccess> xParent(xChild->getParent(), UNO_QUERY); + + // remember old script events + Reference< css::script::XEventAttacherManager> xEvManager(xChild->getParent(), UNO_QUERY); + if (xParent.is() && xEvManager.is()) + { + sal_Int32 nIndex = getElementPos(xParent, xOldModel); + if (nIndex>=0 && nIndex<xParent->getCount()) + aOldScripts = xEvManager->getScriptEvents(nIndex); + } + + // replace the model within the parent container + Reference< XIndexContainer> xIndexParent(xChild->getParent(), UNO_QUERY); + if (xIndexParent.is()) + { + // the form container works with FormComponents + Reference< XFormComponent> xComponent(xNewModel, UNO_QUERY); + DBG_ASSERT(xComponent.is(), "FmXFormShell::executeControlConversionSlot: the new model is no form component !"); + Any aNewModel(makeAny(xComponent)); + try + { + + sal_Int32 nIndex = getElementPos(xParent, xOldModel); + if (nIndex>=0 && nIndex<xParent->getCount()) + xIndexParent->replaceByIndex(nIndex, aNewModel); + else + { + OSL_FAIL("FmXFormShell::executeControlConversionSlot: could not replace the model !"); + Reference< css::lang::XComponent> xNewComponent(xNewModel, UNO_QUERY); + if (xNewComponent.is()) + xNewComponent->dispose(); + return false; + } + } + catch(Exception&) + { + OSL_FAIL("FmXFormShell::executeControlConversionSlot: could not replace the model !"); + Reference< css::lang::XComponent> xNewComponent(xNewModel, UNO_QUERY); + if (xNewComponent.is()) + xNewComponent->dispose(); + return false; + } + + } + } + + // special handling for the LabelControl-property : can only be set when the model is placed + // within the forms hierarchy + if (::comphelper::hasProperty(FM_PROP_CONTROLLABEL, xOldSet) && ::comphelper::hasProperty(FM_PROP_CONTROLLABEL, xNewSet)) + { + try + { + xNewSet->setPropertyValue(FM_PROP_CONTROLLABEL, xOldSet->getPropertyValue(FM_PROP_CONTROLLABEL)); + } + catch(Exception&) + { + } + + } + + // set new model + pFormObject->SetChanged(); + pFormObject->SetUnoControlModel(xNewModel); + + // transfer script events + // (do this _after_ SetUnoControlModel as we need the new (implicitly created) control) + if (aOldScripts.hasElements()) + { + // find the control for the model + Reference<XControlContainer> xControlContainer(getControlContainerForView_Lock()); + + Sequence< Reference< XControl> > aControls( xControlContainer->getControls() ); + + Reference< XControl> xControl; + auto pControl = std::find_if(aControls.begin(), aControls.end(), + [&xNewModel](const Reference< XControl>& rControl) { return rControl->getModel() == xNewModel; }); + if (pControl != aControls.end()) + xControl = *pControl; + TransferEventScripts(xNewModel, xControl, aOldScripts); + } + + // transfer value bindings, if possible + { + Reference< XBindableValue > xOldBindable( xOldModel, UNO_QUERY ); + Reference< XBindableValue > xNewBindable( xNewModel, UNO_QUERY ); + if ( xOldBindable.is() ) + { + try + { + if ( xNewBindable.is() ) + xNewBindable->setValueBinding( xOldBindable->getValueBinding() ); + xOldBindable->setValueBinding( nullptr ); + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + } + // same for list entry sources + { + Reference< XListEntrySink > xOldSink( xOldModel, UNO_QUERY ); + Reference< XListEntrySink > xNewSink( xNewModel, UNO_QUERY ); + if ( xOldSink.is() ) + { + try + { + if ( xNewSink.is() ) + xNewSink->setListEntrySource( xOldSink->getListEntrySource() ); + xOldSink->setListEntrySource( nullptr ); + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + } + + // create an undo action + FmFormModel* pModel = m_pShell->GetFormModel(); + DBG_ASSERT(pModel != nullptr, "FmXFormShell::executeControlConversionSlot: my shell has no model !"); + if (pModel && pModel->IsUndoEnabled() ) + { + pModel->AddUndo(std::make_unique<FmUndoModelReplaceAction>(*pModel, pFormObject, xOldModel)); + } + else + { + FmUndoModelReplaceAction::DisposeElement( xOldModel ); + } + + return true; + } + } + return false; +} + +bool FmXFormShell::canConvertCurrentSelectionToControl_Lock(const OString& rIdent) +{ + if ( m_aCurrentSelection.empty() ) + return false; + + InterfaceBag::const_iterator aCheck = m_aCurrentSelection.begin(); + Reference< lang::XServiceInfo > xElementInfo( *aCheck, UNO_QUERY ); + if ( !xElementInfo.is() ) + // no service info -> cannot determine this + return false; + + if ( ++aCheck != m_aCurrentSelection.end() ) + // more than one element + return false; + + if ( Reference< XForm >::query( xElementInfo ).is() ) + // it's a form + return false; + + sal_Int16 nObjectType = getControlTypeByObject( xElementInfo ); + + if ( ( OBJ_FM_HIDDEN == nObjectType ) + || ( OBJ_FM_CONTROL == nObjectType ) + || ( OBJ_FM_GRID == nObjectType ) + ) + return false; // those types cannot be converted + + DBG_ASSERT(SAL_N_ELEMENTS(aConvertSlots) == SAL_N_ELEMENTS(nObjectTypes), + "FmXFormShell::canConvertCurrentSelectionToControl: aConvertSlots & nObjectTypes must have the same size !"); + + for (size_t i = 0; i < SAL_N_ELEMENTS(aConvertSlots); ++i) + if (rIdent == aConvertSlots[i]) + return nObjectTypes[i] != nObjectType; + + return true; // all other slots: assume "yes" +} + +void FmXFormShell::checkControlConversionSlotsForCurrentSelection_Lock(Menu& rMenu) +{ + for (sal_uInt16 i = 0; i < rMenu.GetItemCount(); ++i) + { + // the context is already of a type that corresponds to the entry -> disable + const sal_uInt16 nId = rMenu.GetItemId(i); + rMenu.EnableItem(nId, canConvertCurrentSelectionToControl_Lock(rMenu.GetItemIdent(nId))); + } +} + +void FmXFormShell::LoopGrids_Lock(LoopGridsSync nSync, LoopGridsFlags nFlags) +{ + if (impl_checkDisposed_Lock()) + return; + + Reference< XIndexContainer> xControlModels(m_xActiveForm, UNO_QUERY); + if (xControlModels.is()) + { + for (sal_Int32 i=0; i<xControlModels->getCount(); ++i) + { + Reference< XPropertySet> xModelSet; + xControlModels->getByIndex(i) >>= xModelSet; + if (!xModelSet.is()) + continue; + + if (!::comphelper::hasProperty(FM_PROP_CLASSID, xModelSet)) + continue; + sal_Int16 nClassId = ::comphelper::getINT16(xModelSet->getPropertyValue(FM_PROP_CLASSID)); + if (FormComponentType::GRIDCONTROL != nClassId) + continue; + + if (!::comphelper::hasProperty(FM_PROP_CURSORCOLOR, xModelSet) || !::comphelper::hasProperty(FM_PROP_ALWAYSSHOWCURSOR, xModelSet) || !::comphelper::hasProperty(FM_PROP_DISPLAYSYNCHRON, xModelSet)) + continue; + + switch (nSync) + { + case LoopGridsSync::DISABLE_SYNC: + { + xModelSet->setPropertyValue(FM_PROP_DISPLAYSYNCHRON, Any(false)); + } + break; + case LoopGridsSync::FORCE_SYNC: + { + Any aOldVal( xModelSet->getPropertyValue(FM_PROP_DISPLAYSYNCHRON) ); + xModelSet->setPropertyValue(FM_PROP_DISPLAYSYNCHRON, Any(true)); + xModelSet->setPropertyValue(FM_PROP_DISPLAYSYNCHRON, aOldVal); + } + break; + case LoopGridsSync::ENABLE_SYNC: + { + xModelSet->setPropertyValue(FM_PROP_DISPLAYSYNCHRON, Any(true)); + } + break; + } + + if (nFlags & LoopGridsFlags::DISABLE_ROCTRLR) + { + xModelSet->setPropertyValue(FM_PROP_ALWAYSSHOWCURSOR, Any(false)); + Reference< XPropertyState> xModelPropState(xModelSet, UNO_QUERY); + if (xModelPropState.is()) + xModelPropState->setPropertyToDefault(FM_PROP_CURSORCOLOR); + else + xModelSet->setPropertyValue(FM_PROP_CURSORCOLOR, Any()); // this should be the default + } + } + } +} + + +Reference< XControlContainer > FmXFormShell::getControlContainerForView_Lock() const +{ + if (impl_checkDisposed_Lock()) + return nullptr; + + SdrPageView* pPageView = nullptr; + if ( m_pShell && m_pShell->GetFormView() ) + pPageView = m_pShell->GetFormView()->GetSdrPageView(); + + Reference< XControlContainer> xControlContainer; + if ( pPageView ) + xControlContainer = pPageView->GetPageWindow(0)->GetControlContainer(); + + return xControlContainer; +} + + +void FmXFormShell::ExecuteTabOrderDialog_Lock(const Reference<XTabControllerModel>& _rxForForm) +{ + if (impl_checkDisposed_Lock()) + return; + + OSL_PRECOND( _rxForForm.is(), "FmXFormShell::ExecuteTabOrderDialog: invalid tabbing model!" ); + if ( !_rxForForm.is() ) + return; + + try + { + Reference< XWindow > xParentWindow; + if ( m_pShell->GetViewShell() && m_pShell->GetViewShell()->GetViewFrame() ) + xParentWindow = VCLUnoHelper::GetInterface ( &m_pShell->GetViewShell()->GetViewFrame()->GetWindow() ); + + Reference< dialogs::XExecutableDialog > xDialog = form::TabOrderDialog::createWithModel( + comphelper::getProcessComponentContext(), + _rxForForm, getControlContainerForView_Lock(), xParentWindow + ); + + xDialog->execute(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "FmXFormShell::ExecuteTabOrderDialog" ); + } +} + + +void FmXFormShell::ExecuteSearch_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + // a collection of all (logical) forms + FmFormArray aEmpty; + m_aSearchForms.swap( aEmpty ); + ::std::vector< OUString > aContextNames; + impl_collectFormSearchContexts_nothrow_Lock( + m_pShell->GetCurPage()->GetForms(), OUString(), + m_aSearchForms, aContextNames); + + if ( m_aSearchForms.size() != aContextNames.size() ) + { + SAL_WARN ( "svx.form", "FmXFormShell::ExecuteSearch: nonsense!" ); + return; + } + + // filter out the forms which do not contain valid controls at all + { + FmFormArray aValidForms; + ::std::vector< OUString > aValidContexts; + FmFormArray::const_iterator form = m_aSearchForms.begin(); + ::std::vector< OUString >::const_iterator contextName = aContextNames.begin(); + for ( ; form != m_aSearchForms.end(); ++form, ++contextName ) + { + FmSearchContext aTestContext; + aTestContext.nContext = static_cast< sal_Int16 >( form - m_aSearchForms.begin() ); + sal_uInt32 nValidControls = OnSearchContextRequest_Lock(aTestContext); + if ( nValidControls > 0 ) + { + aValidForms.push_back( *form ); + aValidContexts.push_back( *contextName ); + } + } + + m_aSearchForms.swap( aValidForms ); + aContextNames.swap( aValidContexts ); + } + + if (m_aSearchForms.empty() ) + { + // there are no controls that meet all the conditions for a search + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Warning, VclButtonsType::Ok, + SvxResId(RID_STR_NODATACONTROLS))); + xBox->run(); + return; + } + + // now I need another 'initial context' + sal_Int16 nInitialContext = 0; + Reference<XForm> xActiveForm(getActiveForm_Lock()); + for ( size_t i=0; i<m_aSearchForms.size(); ++i ) + { + if (m_aSearchForms.at(i) == xActiveForm) + { + nInitialContext = static_cast<sal_Int16>(i); + break; + } + } + + // If the dialog should initially offer the text of the active control, + // this must have an XTextComponent interface. An addition, this makes + // sense only if the current field is also bound to a table (or whatever) field. + OUString strActiveField; + OUString strInitialText; + // ... this I get from my FormController + DBG_ASSERT(m_xActiveController.is(), "FmXFormShell::ExecuteSearch : no active controller !"); + Reference< XControl> xActiveControl( m_xActiveController->getCurrentControl()); + if (xActiveControl.is()) + { + // the control can tell me its model ... + Reference< XControlModel> xActiveModel( xActiveControl->getModel()); + DBG_ASSERT(xActiveModel.is(), "FmXFormShell::ExecuteSearch : active control has no model !"); + + // I ask the model for the ControlSource property ... + Reference< XPropertySet> xProperties(xActiveControl->getModel(), UNO_QUERY); + if (::comphelper::hasProperty(FM_PROP_CONTROLSOURCE, xProperties) && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xProperties)) + { + Reference< XPropertySet> xField; + xProperties->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField; + if (xField.is()) // (only when the thing is really bound) + { + // and the control itself for a TextComponent interface (so that I can pick up the text there) + Reference< XTextComponent> xText(xActiveControl, UNO_QUERY); + if (xText.is()) + { + strActiveField = getLabelName(xProperties); + strInitialText = xText->getText(); + } + } + } + else + { + // the control itself has no ControlSource, but maybe it is a GridControl + Reference< XGrid> xGrid(xActiveControl, UNO_QUERY); + if (xGrid.is()) + { + // for strActiveField I need the ControlSource of the column, + // for that the columns container, for that the GridPeer + Reference< XGridPeer> xGridPeer(xActiveControl->getPeer(), UNO_QUERY); + Reference< XIndexAccess> xColumns; + if (xGridPeer.is()) + xColumns = xGridPeer->getColumns(); + + sal_Int16 nViewCol = xGrid->getCurrentColumnPosition(); + sal_Int32 nModelCol = GridView2ModelPos(xColumns, nViewCol); + Reference< XPropertySet> xCurrentCol; + if(xColumns.is()) + xColumns->getByIndex(nModelCol) >>= xCurrentCol; + if (xCurrentCol.is()) + strActiveField = ::comphelper::getString(xCurrentCol->getPropertyValue(FM_PROP_LABEL)); + + // the text of the current column + Reference< XIndexAccess> xColControls(xGridPeer, UNO_QUERY); + Reference< XInterface> xCurControl; + xColControls->getByIndex(nViewCol) >>= xCurControl; + OUString sInitialText; + if (IsSearchableControl(xCurControl, &sInitialText)) + strInitialText = sInitialText; + } + } + } + + // taking care of possible GridControls that I know + LoopGrids_Lock(LoopGridsSync::DISABLE_SYNC); + + // Now I am ready for the dialogue. + // When the potential deadlocks caused by the use of the solar mutex in + // MTs VCLX... classes are eventually cleared, an SM_USETHREAD should be + // placed here, because the search in a separate thread is nevertheless + // somewhat more fluid. Should be, however, somehow made dependent of the + // underlying cursor. DAO for example is not thread-safe. + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractFmSearchDialog> pDialog( + pFact->CreateFmSearchDialog( + m_pShell->GetViewShell()->GetViewFrame()->GetWindow().GetFrameWeld(), + strInitialText, aContextNames, nInitialContext, + LINK(this, FmXFormShell, OnSearchContextRequest_Lock) )); + pDialog->SetActiveField( strActiveField ); + pDialog->SetFoundHandler(LINK(this, FmXFormShell, OnFoundData_Lock)); + pDialog->SetCanceledNotFoundHdl(LINK(this, FmXFormShell, OnCanceledNotFound_Lock)); + pDialog->Execute(); + pDialog.disposeAndClear(); + + // restore GridControls again + LoopGrids_Lock(LoopGridsSync::ENABLE_SYNC, LoopGridsFlags::DISABLE_ROCTRLR); + + m_pShell->GetFormView()->UnMarkAll(m_pShell->GetFormView()->GetSdrPageView()); + // because I marked controls in OnFoundData (if I was there) +} + + +bool FmXFormShell::GetY2KState_Lock(sal_uInt16& n) +{ + if (impl_checkDisposed_Lock()) + return false; + + if (m_pShell->IsDesignMode()) + // in the design mode (without active controls) the main document is to take care of it + return false; + + Reference<XForm> xForm(getActiveForm_Lock()); + if (!xForm.is()) + // no current form (in particular no current control) -> the main document is to take care + return false; + + Reference< XRowSet> xDB(xForm, UNO_QUERY); + DBG_ASSERT(xDB.is(), "FmXFormShell::GetY2KState : current form has no dbform-interface !"); + + Reference< XNumberFormatsSupplier> xSupplier( getNumberFormats(getConnection(xDB))); + if (xSupplier.is()) + { + Reference< XPropertySet> xSet(xSupplier->getNumberFormatSettings()); + if (xSet.is()) + { + try + { + Any aVal( xSet->getPropertyValue("TwoDigitDateStart") ); + aVal >>= n; + return true; + } + catch(Exception&) + { + } + + } + } + return false; +} + + +void FmXFormShell::SetY2KState_Lock(sal_uInt16 n) +{ + if (impl_checkDisposed_Lock()) + return; + + Reference<XForm> xActiveForm(getActiveForm_Lock()); + Reference< XRowSet > xActiveRowSet( xActiveForm, UNO_QUERY ); + if ( xActiveRowSet.is() ) + { + Reference< XNumberFormatsSupplier > xSupplier( getNumberFormats( getConnection( xActiveRowSet ) ) ); + if (xSupplier.is()) + { + Reference< XPropertySet> xSet(xSupplier->getNumberFormatSettings()); + if (xSet.is()) + { + try + { + xSet->setPropertyValue("TwoDigitDateStart", makeAny<sal_uInt16>(n)); + } + catch(Exception&) + { + OSL_FAIL("FmXFormShell::SetY2KState: Exception occurred!"); + } + + } + return; + } + } + + // no active form found -> iterate through all current forms + Reference< XIndexAccess> xCurrentForms( m_xForms); + if (!xCurrentForms.is()) + { // in the alive mode, my forms are not set, but the ones on the page are + if (m_pShell->GetCurPage()) + xCurrentForms = m_pShell->GetCurPage()->GetForms( false ); + } + if (!xCurrentForms.is()) + return; + + ::comphelper::IndexAccessIterator aIter(xCurrentForms); + Reference< XInterface> xCurrentElement( aIter.Next()); + while (xCurrentElement.is()) + { + // is the current element a DatabaseForm? + Reference< XRowSet> xElementAsRowSet( xCurrentElement, UNO_QUERY ); + if ( xElementAsRowSet.is() ) + { + Reference< XNumberFormatsSupplier > xSupplier( getNumberFormats( getConnection( xElementAsRowSet ) ) ); + if (!xSupplier.is()) + continue; + + Reference< XPropertySet> xSet(xSupplier->getNumberFormatSettings()); + if (xSet.is()) + { + try + { + xSet->setPropertyValue("TwoDigitDateStart", makeAny<sal_uInt16>(n)); + } + catch(Exception&) + { + OSL_FAIL("FmXFormShell::SetY2KState: Exception occurred!"); + } + + } + } + xCurrentElement = aIter.Next(); + } +} + + +void FmXFormShell::CloseExternalFormViewer_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + if (!m_xExternalViewController.is()) + return; + + Reference< css::frame::XFrame> xExternalViewFrame( m_xExternalViewController->getFrame()); + Reference< css::frame::XDispatchProvider> xCommLink(xExternalViewFrame, UNO_QUERY); + if (!xCommLink.is()) + return; + + xExternalViewFrame->setComponent(nullptr,nullptr); + ::comphelper::disposeComponent(xExternalViewFrame); + m_xExternalViewController = nullptr; + m_xExtViewTriggerController = nullptr; + m_xExternalDisplayedForm = nullptr; +} + + +Reference<XResultSet> FmXFormShell::getInternalForm_Lock(const Reference<XResultSet>& _xForm) const +{ + if (impl_checkDisposed_Lock()) + return nullptr; + + Reference< runtime::XFormController> xExternalCtrlr(m_xExternalViewController, UNO_QUERY); + if (xExternalCtrlr.is() && (_xForm == xExternalCtrlr->getModel())) + { + DBG_ASSERT(m_xExternalDisplayedForm.is(), "FmXFormShell::getInternalForm : invalid external form !"); + return m_xExternalDisplayedForm; + } + return _xForm; +} + + +Reference<XForm> FmXFormShell::getInternalForm_Lock(const Reference<XForm>& _xForm) const +{ + if (impl_checkDisposed_Lock()) + return nullptr; + + Reference< runtime::XFormController > xExternalCtrlr(m_xExternalViewController, UNO_QUERY); + if (xExternalCtrlr.is() && (_xForm == xExternalCtrlr->getModel())) + { + DBG_ASSERT(m_xExternalDisplayedForm.is(), "FmXFormShell::getInternalForm : invalid external form !"); + return Reference< XForm>(m_xExternalDisplayedForm, UNO_QUERY); + } + return _xForm; +} + + +namespace +{ + bool lcl_isNavigationRelevant( sal_Int32 _nWhich ) + { + return ( _nWhich == SID_FM_RECORD_FIRST ) + || ( _nWhich == SID_FM_RECORD_PREV ) + || ( _nWhich == SID_FM_RECORD_NEXT ) + || ( _nWhich == SID_FM_RECORD_LAST ) + || ( _nWhich == SID_FM_RECORD_NEW ); + } +} + + +bool FmXFormShell::IsFormSlotEnabled( sal_Int32 _nSlot, FeatureState* _pCompleteState ) +{ + const svx::ControllerFeatures& rController = + lcl_isNavigationRelevant( _nSlot ) + ? getNavControllerFeatures_Lock() + : getActiveControllerFeatures_Lock(); + + if ( !_pCompleteState ) + return rController->isEnabled( _nSlot ); + + rController->getState( _nSlot, *_pCompleteState ); + return _pCompleteState->Enabled; +} + + +void FmXFormShell::ExecuteFormSlot_Lock( sal_Int32 _nSlot ) +{ + const svx::ControllerFeatures& rController = + lcl_isNavigationRelevant( _nSlot ) + ? getNavControllerFeatures_Lock() + : getActiveControllerFeatures_Lock(); + + rController->execute( _nSlot ); + + if ( _nSlot == SID_FM_RECORD_UNDO ) + { + // if we're doing an UNDO, *and* if the affected form is the form which we also display + // as external view, then we need to reset the controls of the external form, too + if (getInternalForm_Lock(getActiveForm_Lock()) == m_xExternalDisplayedForm) + { + Reference< XIndexAccess > xContainer( m_xExternalDisplayedForm, UNO_QUERY ); + if ( xContainer.is() ) + { + Reference< XReset > xReset; + for ( sal_Int32 i = 0; i < xContainer->getCount(); ++i ) + { + if ( ( xContainer->getByIndex( i ) >>= xReset ) && xReset.is() ) + { + // no resets on sub forms + Reference< XForm > xAsForm( xReset, UNO_QUERY ); + if ( !xAsForm.is() ) + xReset->reset(); + } + } + } + } + } +} + + +void FmXFormShell::impl_switchActiveControllerListening_Lock(const bool _bListen) +{ + if ( !m_xActiveController.is() ) + return; + + if ( _bListen ) + m_xActiveController->addEventListener( static_cast<XFormControllerListener*>(this) ); + else + m_xActiveController->removeEventListener( static_cast<XFormControllerListener*>(this) ); +} + + +void FmXFormShell::setActiveController_Lock(const Reference<runtime::XFormController>& xController, bool _bNoSaveOldContent) +{ + if (impl_checkDisposed_Lock()) + return; + + if (m_bChangingDesignMode) + return; + DBG_ASSERT(!m_pShell->IsDesignMode(), "only to be used in alive mode"); + + // if the routine has been called a second time, + // the focus should no longer be transferred + if (m_bInActivate) + { + m_bSetFocus = xController != m_xActiveController; + return; + } + + if (xController == m_xActiveController) + return; + + // switch all nav dispatchers belonging to the form of the current nav controller to 'non active' + Reference< XResultSet> xNavigationForm; + if (m_xNavigationController.is()) + xNavigationForm.set(m_xNavigationController->getModel(), UNO_QUERY); + + m_bInActivate = true; + + // check if the 2 controllers serve different forms + Reference< XResultSet> xOldForm; + if (m_xActiveController.is()) + xOldForm.set(m_xActiveController->getModel(), UNO_QUERY); + Reference< XResultSet> xNewForm; + if (xController.is()) + xNewForm = Reference< XResultSet>(xController->getModel(), UNO_QUERY); + xOldForm = getInternalForm_Lock(xOldForm); + xNewForm = getInternalForm_Lock(xNewForm); + + bool bDifferentForm = ( xOldForm.get() != xNewForm.get() ); + bool bNeedSave = bDifferentForm && !_bNoSaveOldContent; + // we save the content of the old form if we move to a new form, and saving old content is allowed + + if ( m_xActiveController.is() && bNeedSave ) + { + // save content on change of the controller; a commit has already been executed + if ( m_aActiveControllerFeatures->commitCurrentControl() ) + { + m_bSetFocus = true; + if ( m_aActiveControllerFeatures->isModifiedRow() ) + { + bool bIsNew = m_aActiveControllerFeatures->isInsertionRow(); + bool bResult = m_aActiveControllerFeatures->commitCurrentRecord(); + if ( !bResult && m_bSetFocus ) + { + // if we couldn't save the current record, set the focus back to the + // current control + Reference< XWindow > xWindow( m_xActiveController->getCurrentControl(), UNO_QUERY ); + if ( xWindow.is() ) + xWindow->setFocus(); + m_bInActivate = false; + return; + } + else if ( bResult && bIsNew ) + { + Reference< XResultSet > xCursor( m_aActiveControllerFeatures->getCursor().get() ); + if ( xCursor.is() ) + { + DO_SAFE( xCursor->last(); ); + } + } + } + } + } + + stopListening_Lock(); + + impl_switchActiveControllerListening_Lock(false); + + m_aActiveControllerFeatures.dispose(); + m_xActiveController = xController; + if ( m_xActiveController.is() ) + m_aActiveControllerFeatures.assign( m_xActiveController ); + + impl_switchActiveControllerListening_Lock(true); + + if ( m_xActiveController.is() ) + m_xActiveForm = getInternalForm_Lock(Reference<XForm>(m_xActiveController->getModel(), UNO_QUERY)); + else + m_xActiveForm = nullptr; + + startListening_Lock(); + + // activate all dispatchers belonging to form of the new navigation controller + xNavigationForm = nullptr; + if (m_xNavigationController.is()) + xNavigationForm.set(m_xNavigationController->getModel(), UNO_QUERY); + + m_bInActivate = false; + + m_pShell->UIFeatureChanged(); + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().InvalidateShell(*m_pShell); + + InvalidateSlot_Lock(SID_FM_FILTER_NAVIGATOR_CONTROL, true); +} + + +void FmXFormShell::getCurrentSelection_Lock(InterfaceBag& /* [out] */ _rSelection) const +{ + _rSelection = m_aCurrentSelection; +} + + +bool FmXFormShell::setCurrentSelectionFromMark_Lock(const SdrMarkList& _rMarkList) +{ + m_aLastKnownMarkedControls.clear(); + + if ( ( _rMarkList.GetMarkCount() > 0 ) && isControlList( _rMarkList ) ) + collectInterfacesFromMarkList( _rMarkList, m_aLastKnownMarkedControls ); + + return setCurrentSelection_Lock(m_aLastKnownMarkedControls); +} + + +bool FmXFormShell::selectLastMarkedControls_Lock() +{ + return setCurrentSelection_Lock(m_aLastKnownMarkedControls); +} + + +bool FmXFormShell::setCurrentSelection_Lock( const InterfaceBag& _rSelection ) +{ + if (impl_checkDisposed_Lock()) + return false; + + DBG_ASSERT( m_pShell->IsDesignMode(), "FmXFormShell::setCurrentSelection: only to be used in design mode!" ); + + if ( _rSelection.empty() && m_aCurrentSelection.empty() ) + // nothing to do + return false; + + if ( _rSelection.size() == m_aCurrentSelection.size() ) + { + InterfaceBag::const_iterator aNew = _rSelection.begin(); + InterfaceBag::const_iterator aOld = m_aCurrentSelection.begin(); + for ( ; aNew != _rSelection.end(); ++aNew, ++aOld ) + { + OSL_ENSURE( Reference< XInterface >( *aNew, UNO_QUERY ).get() == aNew->get(), "FmXFormShell::setCurrentSelection: new interface not normalized!" ); + OSL_ENSURE( Reference< XInterface >( *aOld, UNO_QUERY ).get() == aOld->get(), "FmXFormShell::setCurrentSelection: old interface not normalized!" ); + + if ( aNew->get() != aOld->get() ) + break; + } + + if ( aNew == _rSelection.end() ) + // both bags equal + return false; + } + + // the following is some strange code to ensure that when you have two grid controls in a document, + // only one of them can have a selected column. + // TODO: this should happen elsewhere, but not here - shouldn't it? + if ( !m_aCurrentSelection.empty() ) + { + Reference< XChild > xCur; if ( m_aCurrentSelection.size() == 1 ) xCur.set(*m_aCurrentSelection.begin(), css::uno::UNO_QUERY); + Reference< XChild > xNew; if ( _rSelection.size() == 1 ) xNew.set(*_rSelection.begin(), css::uno::UNO_QUERY); + + // is there nothing to be selected, or the parents differ, and the parent of the current object + // is a selection supplier, then deselect + if ( xCur.is() && ( !xNew.is() || ( xCur->getParent() != xNew->getParent() ) ) ) + { + Reference< XSelectionSupplier > xSel( xCur->getParent(), UNO_QUERY ); + if ( xSel.is() ) + xSel->select( Any() ); + } + } + + m_aCurrentSelection = _rSelection; + + // determine the form which all the selected objects belong to, if any + Reference< XForm > xNewCurrentForm; + for (const auto& rpSelection : m_aCurrentSelection) + { + Reference< XForm > xThisRoundsForm( GetForm( rpSelection ) ); + OSL_ENSURE( xThisRoundsForm.is(), "FmXFormShell::setCurrentSelection: *everything* should belong to a form!" ); + + if ( !xNewCurrentForm.is() ) + { // the first form we encountered + xNewCurrentForm = xThisRoundsForm; + } + else if ( xNewCurrentForm != xThisRoundsForm ) + { // different forms -> no "current form" at all + xNewCurrentForm.clear(); + break; + } + } + + if ( !m_aCurrentSelection.empty() ) + impl_updateCurrentForm_Lock(xNewCurrentForm); + + // ensure some slots are updated + for (sal_Int16 i : SelObjectSlotMap) + InvalidateSlot_Lock(i, false); + + return true; +} + + +bool FmXFormShell::isSolelySelected_Lock(const Reference<XInterface>& _rxObject) +{ + return ( m_aCurrentSelection.size() == 1 ) && ( *m_aCurrentSelection.begin() == _rxObject ); +} + + +void FmXFormShell::forgetCurrentForm_Lock() +{ + if ( !m_xCurrentForm.is() ) + return; + + // reset ... + impl_updateCurrentForm_Lock(nullptr); + + // ... and try finding a new current form + // #i88186# / 2008-04-12 / frank.schoenheit@sun.com + impl_defaultCurrentForm_nothrow_Lock(); +} + + +void FmXFormShell::impl_updateCurrentForm_Lock(const Reference<XForm>& _rxNewCurForm) +{ + if (impl_checkDisposed_Lock()) + return; + + m_xCurrentForm = _rxNewCurForm; + + // propagate to the FormPage(Impl) + FmFormPage* pPage = m_pShell->GetCurPage(); + if ( pPage ) + pPage->GetImpl().setCurForm( m_xCurrentForm ); + + // ensure the UI which depends on the current form is up-to-date + for (sal_Int16 i : DlgSlotMap) + InvalidateSlot_Lock(i, false); +} + + +void FmXFormShell::startListening_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + Reference< XRowSet> xDatabaseForm(m_xActiveForm, UNO_QUERY); + if (xDatabaseForm.is() && getConnection(xDatabaseForm).is()) + { + Reference< XPropertySet> xActiveFormSet(m_xActiveForm, UNO_QUERY); + if (xActiveFormSet.is()) + { + // if there is a data source, then build the listener + // TODO: this is strange - shouldn't this depend on a isLoaded instead of + // a "has command value"? Finally, the command value only means that it was + // intended to be loaded, not that it actually *is* loaded + OUString aSource = ::comphelper::getString(xActiveFormSet->getPropertyValue(FM_PROP_COMMAND)); + if (!aSource.isEmpty()) + { + m_bDatabaseBar = true; + + xActiveFormSet->getPropertyValue(FM_PROP_NAVIGATION) >>= m_eNavigate; + + switch (m_eNavigate) + { + case NavigationBarMode_PARENT: + { + // search for the controller via which navigation is possible + Reference< XChild> xChild = m_xActiveController; + Reference< runtime::XFormController > xParent; + while (xChild.is()) + { + xChild.set(xChild->getParent(), UNO_QUERY); + xParent.set(xChild, UNO_QUERY); + Reference< XPropertySet> xParentSet; + if (xParent.is()) + xParentSet.set(xParent->getModel(), UNO_QUERY); + if (xParentSet.is()) + { + xParentSet->getPropertyValue(FM_PROP_NAVIGATION) >>= m_eNavigate; + if (m_eNavigate == NavigationBarMode_CURRENT) + break; + } + } + m_xNavigationController = xParent; + } + break; + + case NavigationBarMode_CURRENT: + m_xNavigationController = m_xActiveController; + break; + + default: + m_xNavigationController = nullptr; + m_bDatabaseBar = false; + } + + m_aNavControllerFeatures.dispose(); + if ( m_xNavigationController.is() && ( m_xNavigationController != m_xActiveController ) ) + m_aNavControllerFeatures.assign( m_xNavigationController ); + + // because of RecordCount, listen at the controller which controls the navigation + Reference< XPropertySet> xNavigationSet; + if (m_xNavigationController.is()) + { + xNavigationSet.set(m_xNavigationController->getModel(), UNO_QUERY); + if (xNavigationSet.is()) + xNavigationSet->addPropertyChangeListener(FM_PROP_ROWCOUNT,this); + } + return; + } + } + } + + m_eNavigate = NavigationBarMode_NONE; + m_bDatabaseBar = false; + m_xNavigationController = nullptr; +} + + +void FmXFormShell::stopListening_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + Reference< XRowSet> xDatabaseForm(m_xActiveForm, UNO_QUERY); + if ( xDatabaseForm.is() ) + { + if (m_xNavigationController.is()) + { + Reference< XPropertySet> xSet(m_xNavigationController->getModel(), UNO_QUERY); + if (xSet.is()) + xSet->removePropertyChangeListener(FM_PROP_ROWCOUNT, this); + + } + } + + m_bDatabaseBar = false; + m_eNavigate = NavigationBarMode_NONE; + m_xNavigationController = nullptr; +} + + +void FmXFormShell::ShowSelectionProperties_Lock(bool bShow) +{ + if (impl_checkDisposed_Lock()) + return; + + // if the window is already visible, only update the state + bool bHasChild = m_pShell->GetViewShell()->GetViewFrame()->HasChildWindow( SID_FM_SHOW_PROPERTIES ); + if ( bHasChild && bShow ) + UpdateSlot_Lock(SID_FM_PROPERTY_CONTROL); + + // else toggle state + else + m_pShell->GetViewShell()->GetViewFrame()->ToggleChildWindow(SID_FM_SHOW_PROPERTIES); + + InvalidateSlot_Lock(SID_FM_PROPERTIES, false); + InvalidateSlot_Lock(SID_FM_CTL_PROPERTIES, false); +} + + +IMPL_LINK(FmXFormShell, OnFoundData_Lock, FmFoundRecordInformation&, rfriWhere, void) +{ + if (impl_checkDisposed_Lock()) + return; + + DBG_ASSERT((rfriWhere.nContext >= 0) && (rfriWhere.nContext < static_cast<sal_Int16>(m_aSearchForms.size())), + "FmXFormShell::OnFoundData : invalid context!"); + Reference< XForm> xForm( m_aSearchForms.at(rfriWhere.nContext)); + DBG_ASSERT(xForm.is(), "FmXFormShell::OnFoundData : invalid form!"); + + Reference< XRowLocate> xCursor(xForm, UNO_QUERY); + if (!xCursor.is()) + return; // what should I do there? + + // to the record + try + { + xCursor->moveToBookmark(rfriWhere.aPosition); + } + catch(const SQLException&) + { + OSL_FAIL("Can position on bookmark!"); + } + + LoopGrids_Lock(LoopGridsSync::FORCE_SYNC); + + // and to the field (for that, I collected the XVclComponent interfaces before the start of the search) + SAL_WARN_IF(o3tl::make_unsigned(rfriWhere.nFieldPos) >= + m_arrSearchedControls.size(), + "svx.form", "FmXFormShell::OnFoundData : invalid index!"); + SdrObject* pObject = m_arrSearchedControls.at(rfriWhere.nFieldPos); + + m_pShell->GetFormView()->UnMarkAll(m_pShell->GetFormView()->GetSdrPageView()); + m_pShell->GetFormView()->MarkObj(pObject, m_pShell->GetFormView()->GetSdrPageView()); + + FmFormObj* pFormObject = FmFormObj::GetFormObject( pObject ); + Reference< XControlModel > xControlModel( pFormObject ? pFormObject->GetUnoControlModel() : Reference< XControlModel >() ); + DBG_ASSERT( xControlModel.is(), "FmXFormShell::OnFoundData: invalid control!" ); + if ( !xControlModel.is() ) + return; + + // disable the permanent cursor for the last grid we found a record + if (m_xLastGridFound.is() && (m_xLastGridFound != xControlModel)) + { + Reference< XPropertySet> xOldSet(m_xLastGridFound, UNO_QUERY); + xOldSet->setPropertyValue(FM_PROP_ALWAYSSHOWCURSOR, makeAny( false ) ); + Reference< XPropertyState> xOldSetState(xOldSet, UNO_QUERY); + if (xOldSetState.is()) + xOldSetState->setPropertyToDefault(FM_PROP_CURSORCOLOR); + else + xOldSet->setPropertyValue(FM_PROP_CURSORCOLOR, Any()); + } + + // if the field is in a GridControl, I have to additionally go into the corresponding column there + sal_Int32 nGridColumn = m_arrRelativeGridColumn[rfriWhere.nFieldPos]; + if (nGridColumn != -1) + { // unfortunately, I have to first get the control again + Reference<XControl> xControl(pFormObject ? impl_getControl_Lock(xControlModel, *pFormObject) : Reference<XControl>()); + Reference< XGrid> xGrid(xControl, UNO_QUERY); + DBG_ASSERT(xGrid.is(), "FmXFormShell::OnFoundData : invalid control!"); + // if one of the asserts fires, I probably did something wrong on building of m_arrSearchedControls + + // enable a permanent cursor for the grid so we can see the found text + Reference< XPropertySet> xModelSet(xControlModel, UNO_QUERY); + DBG_ASSERT(xModelSet.is(), "FmXFormShell::OnFoundData : invalid control model (no property set) !"); + xModelSet->setPropertyValue( FM_PROP_ALWAYSSHOWCURSOR, makeAny( true ) ); + xModelSet->setPropertyValue( FM_PROP_CURSORCOLOR, makeAny( COL_LIGHTRED ) ); + m_xLastGridFound = xControlModel; + + if ( xGrid.is() ) + xGrid->setCurrentColumnPosition(static_cast<sal_Int16>(nGridColumn)); + } + + // As the cursor has been repositioned, I have (in positioned) invalidated + // my form bar slots. But that does not take effect here unfortunately, as + // generally the (modal) search dialog is of course at the top ... So, force ... + sal_uInt16 nPos = 0; + while (DatabaseSlotMap[nPos]) + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().Update(DatabaseSlotMap[nPos++]); + // unfortunately the update goes against the invalidate with only individual slots +} + + +IMPL_LINK(FmXFormShell, OnCanceledNotFound_Lock, FmFoundRecordInformation&, rfriWhere, void) +{ + if (impl_checkDisposed_Lock()) + return; + + DBG_ASSERT((rfriWhere.nContext >= 0) && (rfriWhere.nContext < static_cast<sal_Int16>(m_aSearchForms.size())), + "FmXFormShell::OnCanceledNotFound : invalid context!"); + Reference< XForm> xForm( m_aSearchForms.at(rfriWhere.nContext)); + DBG_ASSERT(xForm.is(), "FmXFormShell::OnCanceledNotFound : invalid form!"); + + Reference< XRowLocate> xCursor(xForm, UNO_QUERY); + if (!xCursor.is()) + return; // what should I do there? + + // to the record + try + { + xCursor->moveToBookmark(rfriWhere.aPosition); + } + catch(const SQLException&) + { + OSL_FAIL("Can position on bookmark!"); + } + + + m_pShell->GetFormView()->UnMarkAll(m_pShell->GetFormView()->GetSdrPageView()); +} + + +IMPL_LINK(FmXFormShell, OnSearchContextRequest_Lock, FmSearchContext&, rfmscContextInfo, sal_uInt32) +{ + if (impl_checkDisposed_Lock()) + return 0; + + DBG_ASSERT(rfmscContextInfo.nContext < static_cast<sal_Int16>(m_aSearchForms.size()), "FmXFormShell::OnSearchContextRequest : invalid parameter !"); + Reference< XForm> xForm( m_aSearchForms.at(rfmscContextInfo.nContext)); + DBG_ASSERT(xForm.is(), "FmXFormShell::OnSearchContextRequest : unexpected : invalid context !"); + + Reference< XResultSet> xIter(xForm, UNO_QUERY); + DBG_ASSERT(xIter.is(), "FmXFormShell::OnSearchContextRequest : unexpected : context has no iterator !"); + + + // assemble the list of fields to involve (that is, the ControlSources of all fields that have such a property) + OUString strFieldList, sFieldDisplayNames; + m_arrSearchedControls.clear(); + m_arrRelativeGridColumn.clear(); + + // small problem: To mark found fields, I need SdrObjects. To determine which controls + // to include in the search, I need Controls (that is, XControl interfaces). So I have + // to iterate over one of them and get the other in some way. Unfortunately, there is + // no direct connection between the two worlds (except from a GetUnoControl to a + // SdrUnoObject, but this requires an OutputDevice I can not do anything with. + // However I can get to the Model from the Control and also from the SdrObject, and in + // this way the assignment SdrObject<->Control is possible with a double loop. + // The alternative to this (ugly but certainly not entirely fixable) solution would be + // to renounce the caching of the SdrObjects, which would lead to significant extra + // work in OnFoundData (since there I'd have to get the SdrObject first thing every + // time). But since OnFoundData is usually called more often than ExecuteSearch, I'll + // do that here. + + Reference< XNameAccess> xValidFormFields; + Reference< XColumnsSupplier> xSupplyCols(xIter, UNO_QUERY); + DBG_ASSERT(xSupplyCols.is(), "FmXFormShell::OnSearchContextRequest : invalid cursor : no columns supplier !"); + if (xSupplyCols.is()) + xValidFormFields = xSupplyCols->getColumns(); + DBG_ASSERT(xValidFormFields.is(), "FmXFormShell::OnSearchContextRequest : form has no fields !"); + + // current Page/Controller + FmFormPage* pCurrentPage = m_pShell->GetCurPage(); + assert(pCurrentPage && "FmXFormShell::OnSearchContextRequest : no page !"); + // Search all SdrControls of this page... + OUString sControlSource, aName; + + SdrObjListIter aPageIter( pCurrentPage ); + while ( aPageIter.IsMore() ) + { + SdrObject* pCurrent = aPageIter.Next(); + FmFormObj* pFormObject = FmFormObj::GetFormObject( pCurrent ); + // note that in case pCurrent is a virtual object, pFormObject points to the referenced object + + if ( !pFormObject ) + continue; + + // the current object's model, in different tastes + Reference< XControlModel> xControlModel( pFormObject->GetUnoControlModel() ); + Reference< XFormComponent > xCurrentFormComponent( xControlModel, UNO_QUERY ); + DBG_ASSERT( xCurrentFormComponent.is(), "FmXFormShell::OnSearchContextRequest: invalid objects!" ); + if ( !xCurrentFormComponent.is() ) + continue; + + // does the component belong to the form which we're interested in? + if ( xCurrentFormComponent->getParent() != xForm ) + continue; + + // ... ask for the ControlSource property + SearchableControlIterator iter( xCurrentFormComponent ); + Reference< XControl> xControl; + // the control that has model xControlModel + // (the following while can be passed through several times, without the Control + // being modified, so I don't have to search every time from scratch) + + Reference< XInterface > xSearchable( iter.Next() ); + while ( xSearchable.is() ) + { + sControlSource = iter.getCurrentValue(); + if ( sControlSource.isEmpty() ) + { + // the current element has no ControlSource, so it is a GridControl (that + // is the only thing that still permits the SearchableControlIteratore) + xControl = impl_getControl_Lock(xControlModel, *pFormObject); + DBG_ASSERT(xControl.is(), "FmXFormShell::OnSearchContextRequest : didn't ::std::find a control with requested model !"); + + Reference< XGridPeer> xGridPeer; + if ( xControl.is() ) + xGridPeer.set( xControl->getPeer(), UNO_QUERY ); + do + { + if (!xGridPeer.is()) + break; + + Reference< XIndexAccess> xPeerContainer(xGridPeer, UNO_QUERY); + if (!xPeerContainer.is()) + break; + + Reference< XIndexAccess> xModelColumns = xGridPeer->getColumns(); + DBG_ASSERT(xModelColumns.is(), "FmXFormShell::OnSearchContextRequest : there is a grid control without columns !"); + // the case 'no columns' should be indicated with an empty container, I think ... + DBG_ASSERT(xModelColumns->getCount() >= xPeerContainer->getCount(), "FmXFormShell::OnSearchContextRequest : impossible : have more view than model columns !"); + + Reference< XInterface> xCurrentColumn; + for (sal_Int32 nViewPos=0; nViewPos<xPeerContainer->getCount(); ++nViewPos) + { + xPeerContainer->getByIndex(nViewPos) >>= xCurrentColumn; + if (!xCurrentColumn.is()) + continue; + + // can we use this column control for searching ? + if (!IsSearchableControl(xCurrentColumn)) + continue; + + sal_Int32 nModelPos = GridView2ModelPos(xModelColumns, nViewPos); + Reference< XPropertySet> xCurrentColModel; + xModelColumns->getByIndex(nModelPos) >>= xCurrentColModel; + aName = ::comphelper::getString(xCurrentColModel->getPropertyValue(FM_PROP_CONTROLSOURCE)); + // the cursor has a field matching the control source ? + if (xValidFormFields->hasByName(aName)) + { + strFieldList += aName + ";"; + + sFieldDisplayNames += + ::comphelper::getString(xCurrentColModel->getPropertyValue(FM_PROP_LABEL)) + + ";"; + + rfmscContextInfo.arrFields.push_back(xCurrentColumn); + + // and the SdrOject to the Field + m_arrSearchedControls.push_back(pCurrent); + // the number of the column + m_arrRelativeGridColumn.push_back(nViewPos); + } + } + } while (false); + } + else + { + if (!sControlSource.isEmpty() && xValidFormFields->hasByName(sControlSource)) + { + // now I need the Control to SdrObject + if (!xControl.is()) + { + xControl = impl_getControl_Lock(xControlModel, *pFormObject); + DBG_ASSERT(xControl.is(), "FmXFormShell::OnSearchContextRequest : didn't ::std::find a control with requested model !"); + } + + if (IsSearchableControl(xControl)) + { + // all tests passed -> take along in the list + strFieldList += sControlSource + ";"; + + // the label which should appear for the control : + sFieldDisplayNames += + getLabelName(Reference< XPropertySet>(xControlModel, UNO_QUERY)) + + ";"; + + // mark the SdrObject (accelerates the treatment in OnFoundData) + m_arrSearchedControls.push_back(pCurrent); + + // the number of the column (here a dummy, since it is only interesting for GridControls) + m_arrRelativeGridColumn.push_back(-1); + + // and for the formatted search... + rfmscContextInfo.arrFields.emplace_back( xControl, UNO_QUERY ); + } + } + } + + xSearchable = iter.Next(); + } + } + + strFieldList = comphelper::string::stripEnd(strFieldList, ';'); + sFieldDisplayNames = comphelper::string::stripEnd(sFieldDisplayNames, ';'); + + if (rfmscContextInfo.arrFields.empty()) + { + rfmscContextInfo.arrFields.clear(); + rfmscContextInfo.xCursor = nullptr; + rfmscContextInfo.strUsedFields.clear(); + return 0; + } + + rfmscContextInfo.xCursor = xIter; + rfmscContextInfo.strUsedFields = strFieldList; + rfmscContextInfo.sFieldDisplayNames = sFieldDisplayNames; + + // 66463 - 31.05.99 - FS + // when the cursor is a non-STANDARD RecordMode, set it back + Reference< XPropertySet> xCursorSet(rfmscContextInfo.xCursor, UNO_QUERY); + Reference< XResultSetUpdate> xUpdateCursor(rfmscContextInfo.xCursor, UNO_QUERY); + if (xUpdateCursor.is() && xCursorSet.is()) + { + if (::comphelper::getBOOL(xCursorSet->getPropertyValue(FM_PROP_ISNEW))) + xUpdateCursor->moveToCurrentRow(); + else if (::comphelper::getBOOL(xCursorSet->getPropertyValue(FM_PROP_ISMODIFIED))) + xUpdateCursor->cancelRowUpdates(); + } + + return rfmscContextInfo.arrFields.size(); +} + + // XContainerListener + +void SAL_CALL FmXFormShell::elementInserted(const ContainerEvent& evt) +{ + SolarMutexGuard g; + + if (impl_checkDisposed_Lock()) + return; + + // new object to listen to + Reference< XInterface> xTemp; + evt.Element >>= xTemp; + AddElement_Lock(xTemp); + + m_pShell->DetermineForms(true); +} + + +void SAL_CALL FmXFormShell::elementReplaced(const ContainerEvent& evt) +{ + SolarMutexGuard g; + + if (impl_checkDisposed_Lock() ) + return; + + Reference< XInterface> xTemp; + evt.ReplacedElement >>= xTemp; + RemoveElement_Lock(xTemp); + evt.Element >>= xTemp; + AddElement_Lock(xTemp); +} + + +void SAL_CALL FmXFormShell::elementRemoved(const ContainerEvent& evt) +{ + SolarMutexGuard g; + + if (impl_checkDisposed_Lock()) + return; + + Reference< XInterface> xTemp; + evt.Element >>= xTemp; + RemoveElement_Lock(xTemp); + + m_pShell->DetermineForms(true); +} + + +void FmXFormShell::UpdateForms_Lock(bool _bInvalidate) +{ + if (impl_checkDisposed_Lock()) + return; + + Reference< XIndexAccess > xForms; + + FmFormPage* pPage = m_pShell->GetCurPage(); + if ( pPage && m_pShell->m_bDesignMode ) + xForms = pPage->GetForms( false ); + + if ( m_xForms != xForms ) + { + RemoveElement_Lock( m_xForms ); + m_xForms = xForms; + AddElement_Lock(m_xForms); + } + + SolarMutexGuard g; + m_pShell->DetermineForms( _bInvalidate ); +} + + +void FmXFormShell::AddElement_Lock(const Reference<XInterface>& _xElement) +{ + if (impl_checkDisposed_Lock()) + return; + impl_AddElement_nothrow(_xElement); +} + +void FmXFormShell::impl_AddElement_nothrow(const Reference< XInterface>& Element) +{ + // listen at the container + const Reference< XIndexContainer> xContainer(Element, UNO_QUERY); + if (xContainer.is()) + { + const sal_uInt32 nCount = xContainer->getCount(); + Reference< XInterface> xElement; + for (sal_uInt32 i = 0; i < nCount; ++i) + { + xElement.set(xContainer->getByIndex(i),UNO_QUERY); + impl_AddElement_nothrow(xElement); + } + + const Reference< XContainer> xCont(Element, UNO_QUERY); + if (xCont.is()) + xCont->addContainerListener(this); + } + + const Reference< css::view::XSelectionSupplier> xSelSupplier(Element, UNO_QUERY); + if (xSelSupplier.is()) + xSelSupplier->addSelectionChangeListener(this); +} + + +void FmXFormShell::RemoveElement_Lock(const Reference<XInterface>& Element) +{ + if (impl_checkDisposed_Lock()) + return; + impl_RemoveElement_nothrow_Lock(Element); +} + +void FmXFormShell::impl_RemoveElement_nothrow_Lock(const Reference<XInterface>& Element) +{ + const Reference< css::view::XSelectionSupplier> xSelSupplier(Element, UNO_QUERY); + if (xSelSupplier.is()) + xSelSupplier->removeSelectionChangeListener(this); + + // remove connection to children + const Reference< XIndexContainer> xContainer(Element, UNO_QUERY); + if (xContainer.is()) + { + const Reference< XContainer> xCont(Element, UNO_QUERY); + if (xCont.is()) + xCont->removeContainerListener(this); + + const sal_uInt32 nCount = xContainer->getCount(); + Reference< XInterface> xElement; + for (sal_uInt32 i = 0; i < nCount; i++) + { + xElement.set(xContainer->getByIndex(i),UNO_QUERY); + impl_RemoveElement_nothrow_Lock(xElement); + } + } + + InterfaceBag::iterator wasSelectedPos = m_aCurrentSelection.find( Element ); + if ( wasSelectedPos != m_aCurrentSelection.end() ) + m_aCurrentSelection.erase( wasSelectedPos ); +} + + +void SAL_CALL FmXFormShell::selectionChanged(const lang::EventObject& rEvent) +{ + SolarMutexGuard g; + + if (impl_checkDisposed_Lock()) + return; + + Reference< XSelectionSupplier > xSupplier( rEvent.Source, UNO_QUERY ); + Reference< XInterface > xSelObj( xSupplier->getSelection(), UNO_QUERY ); + // a selection was removed, this can only be done by the shell + if ( !xSelObj.is() ) + return; + + EnableTrackProperties_Lock(false); + + bool bMarkChanged = m_pShell->GetFormView()->checkUnMarkAll(rEvent.Source); + + InterfaceBag aNewSelection; + aNewSelection.insert( Reference<XInterface>( xSelObj, UNO_QUERY ) ); + + if (setCurrentSelection_Lock(aNewSelection) && IsPropBrwOpen_Lock()) + ShowSelectionProperties_Lock(true); + + EnableTrackProperties_Lock(true); + + if ( bMarkChanged ) + m_pShell->NotifyMarkListChanged( m_pShell->GetFormView() ); +} + + +IMPL_LINK_NOARG(FmXFormShell, OnTimeOut_Lock, Timer*, void) +{ + if (impl_checkDisposed_Lock()) + return; + + if (m_pShell->IsDesignMode() && m_pShell->GetFormView()) + SetSelection_Lock(m_pShell->GetFormView()->GetMarkedObjectList()); +} + + +void FmXFormShell::SetSelectionDelayed_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + if (m_pShell->IsDesignMode() && IsTrackPropertiesEnabled_Lock() && !m_aMarkTimer.IsActive()) + m_aMarkTimer.Start(); +} + + +void FmXFormShell::SetSelection_Lock(const SdrMarkList& rMarkList) +{ + if (impl_checkDisposed_Lock()) + return; + + DetermineSelection_Lock(rMarkList); + m_pShell->NotifyMarkListChanged(m_pShell->GetFormView()); +} + + +void FmXFormShell::DetermineSelection_Lock(const SdrMarkList& rMarkList) +{ + if (setCurrentSelectionFromMark_Lock(rMarkList) && IsPropBrwOpen_Lock()) + ShowSelectionProperties_Lock(true); +} + + +bool FmXFormShell::IsPropBrwOpen_Lock() const +{ + if (impl_checkDisposed_Lock()) + return false; + + return m_pShell->GetViewShell() && m_pShell->GetViewShell()->GetViewFrame() + && m_pShell->GetViewShell()->GetViewFrame()->HasChildWindow(SID_FM_SHOW_PROPERTIES); +} + + +class FmXFormShell::SuspendPropertyTracking +{ +private: + FmXFormShell& m_rShell; + bool m_bEnabled; + +public: + explicit SuspendPropertyTracking( FmXFormShell& _rShell ) + :m_rShell( _rShell ) + ,m_bEnabled( false ) + { + if (m_rShell.IsTrackPropertiesEnabled_Lock()) + { + m_rShell.EnableTrackProperties_Lock(false); + m_bEnabled = true; + } + } + + ~SuspendPropertyTracking( ) + { + if ( m_bEnabled ) // note that ( false != m_bEnabled ) implies ( NULL != m_pShell ) + m_rShell.EnableTrackProperties_Lock(true); + } +}; + + +void FmXFormShell::SetDesignMode_Lock(bool bDesign) +{ + if (impl_checkDisposed_Lock()) + return; + + DBG_ASSERT(m_pShell->GetFormView(), "FmXFormShell::SetDesignMode : invalid call (have no shell or no view) !"); + m_bChangingDesignMode = true; + + // 67506 - 15.07.99 - FS + // if we're switching off the design mode we have to force the property browser to be closed + // so it can commit it's changes _before_ we load the forms + if (!bDesign) + { + m_bHadPropertyBrowserInDesignMode = m_pShell->GetViewShell()->GetViewFrame()->HasChildWindow(SID_FM_SHOW_PROPERTIES); + if (m_bHadPropertyBrowserInDesignMode) + m_pShell->GetViewShell()->GetViewFrame()->ToggleChildWindow(SID_FM_SHOW_PROPERTIES); + } + + FmFormView* pFormView = m_pShell->GetFormView(); + if (bDesign) + { + // we are currently filtering, so stop filtering + if (m_bFilterMode) + stopFiltering_Lock(false); + + // unsubscribe from the objects of my MarkList + pFormView->GetImpl()->stopMarkListWatching(); + } + else + { + m_aMarkTimer.Stop(); + + SuspendPropertyTracking aSuspend( *this ); + pFormView->GetImpl()->saveMarkList(); + } + + if (bDesign && m_xExternalViewController.is()) + CloseExternalFormViewer_Lock(); + + pFormView->ChangeDesignMode(bDesign); + + // notify listeners + FmDesignModeChangedHint aChangedHint( bDesign ); + m_pShell->Broadcast(aChangedHint); + + m_pShell->m_bDesignMode = bDesign; + UpdateForms_Lock(false); + + m_pTextShell->designModeChanged(); + + if (bDesign) + { + SdrMarkList aList; + { + // during changing the mark list, don't track the selected objects in the property browser + SuspendPropertyTracking aSuspend( *this ); + // restore the marks + pFormView->GetImpl()->restoreMarkList( aList ); + } + + // synchronize with the restored mark list + if ( aList.GetMarkCount() ) + SetSelection_Lock(aList); + } + else + { + // subscribe to the model of the view (so that I'm informed when someone deletes + // during the alive mode controls that I had saved in the saveMarklist (60343) + pFormView->GetImpl()->startMarkListWatching(); + } + + m_pShell->UIFeatureChanged(); + + // 67506 - 15.07.99 - FS + if (bDesign && m_bHadPropertyBrowserInDesignMode) + { + // The UIFeatureChanged performs an update (a check of the available features) asynchronously. + // So we can't call ShowSelectionProperties directly as the according feature isn't enabled yet. + // That's why we use an asynchron execution on the dispatcher. + // (And that's why this has to be done AFTER the UIFeatureChanged.) + m_pShell->GetViewShell()->GetViewFrame()->GetDispatcher()->Execute( SID_FM_SHOW_PROPERTY_BROWSER, SfxCallMode::ASYNCHRON ); + } + m_bChangingDesignMode = false; +} + + +Reference< XControl> FmXFormShell::impl_getControl_Lock(const Reference<XControlModel>& i_rxModel, const FmFormObj& i_rKnownFormObj) +{ + if (impl_checkDisposed_Lock()) + return nullptr; + + Reference< XControl > xControl; + try + { + Reference< XControlContainer> xControlContainer(getControlContainerForView_Lock(), UNO_SET_THROW); + + const Sequence< Reference< XControl > > seqControls( xControlContainer->getControls() ); + // ... that I can then search + for (Reference< XControl > const & control : seqControls) + { + xControl.set( control, UNO_SET_THROW ); + Reference< XControlModel > xCurrentModel( xControl->getModel() ); + if ( xCurrentModel == i_rxModel ) + break; + xControl.clear(); + } + + if ( !xControl.is() ) + { + // fallback (some controls might not have been created, yet, since they were never visible so far) + Reference< XControl > xContainerControl( xControlContainer, UNO_QUERY_THROW ); + const vcl::Window* pContainerWindow = VCLUnoHelper::GetWindow( xContainerControl->getPeer() ); + ENSURE_OR_THROW( pContainerWindow, "unexpected control container implementation" ); + + const SdrView* pSdrView = m_pShell ? m_pShell->GetFormView() : nullptr; + ENSURE_OR_THROW( pSdrView, "no current view" ); + + xControl.set( i_rKnownFormObj.GetUnoControl( *pSdrView, *pContainerWindow ), UNO_SET_THROW ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + OSL_ENSURE( xControl.is(), "FmXFormShell::impl_getControl: no control found!" ); + return xControl; +} + +// note: _out_rForms is a member so needs lock +void FmXFormShell::impl_collectFormSearchContexts_nothrow_Lock( const Reference<XInterface>& _rxStartingPoint, + const OUString& _rCurrentLevelPrefix, FmFormArray& _out_rForms, ::std::vector< OUString >& _out_rNames ) +{ + try + { + Reference< XIndexAccess> xContainer( _rxStartingPoint, UNO_QUERY ); + if ( !xContainer.is() ) + return; + + sal_Int32 nCount( xContainer->getCount() ); + if ( nCount == 0 ) + return; + + OUString sCurrentFormName; + OUStringBuffer aNextLevelPrefix; + for ( sal_Int32 i=0; i<nCount; ++i ) + { + // is the current child a form? + Reference< XForm > xCurrentAsForm( xContainer->getByIndex(i), UNO_QUERY ); + if ( !xCurrentAsForm.is() ) + continue; + + Reference< XNamed > xNamed( xCurrentAsForm, UNO_QUERY_THROW ); + sCurrentFormName = xNamed->getName(); + + // the name of the current form + OUStringBuffer sCompleteCurrentName( sCurrentFormName ); + if ( !_rCurrentLevelPrefix.isEmpty() ) + { + sCompleteCurrentName.append( " (" ); + sCompleteCurrentName.append ( _rCurrentLevelPrefix ); + sCompleteCurrentName.append( ")" ); + } + + // the prefix for the next level + aNextLevelPrefix = _rCurrentLevelPrefix; + if ( !_rCurrentLevelPrefix.isEmpty() ) + aNextLevelPrefix.append( '/' ); + aNextLevelPrefix.append( sCurrentFormName ); + + // remember both the form and its "display name" + _out_rForms.push_back( xCurrentAsForm ); + _out_rNames.push_back( sCompleteCurrentName.makeStringAndClear() ); + + // and descend + impl_collectFormSearchContexts_nothrow_Lock( + xCurrentAsForm, aNextLevelPrefix.makeStringAndClear(), + _out_rForms, _out_rNames); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +void FmXFormShell::startFiltering_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + // setting all forms in filter mode + FmXFormView* pXView = m_pShell->GetFormView()->GetImpl(); + + // if the active controller is our external one we have to use the trigger controller + Reference< XControlContainer> xContainer; + if (getActiveController_Lock() == m_xExternalViewController) + { + DBG_ASSERT(m_xExtViewTriggerController.is(), "FmXFormShell::startFiltering : inconsistent : active external controller, but no one triggered this !"); + xContainer = m_xExtViewTriggerController->getContainer(); + } + else + xContainer = getActiveController_Lock()->getContainer(); + + PFormViewPageWindowAdapter pAdapter = pXView->findWindow( xContainer ); + if ( pAdapter.is() ) + { + const ::std::vector< Reference< runtime::XFormController> >& rControllerList = pAdapter->GetList(); + for (const auto& rpController : rControllerList) + { + Reference< XModeSelector> xModeSelector(rpController, UNO_QUERY); + if (xModeSelector.is()) + xModeSelector->setMode( "FilterMode" ); + } + } + + m_bFilterMode = true; + + m_pShell->UIFeatureChanged(); + SfxViewFrame* pViewFrame = m_pShell->GetViewShell()->GetViewFrame(); + pViewFrame->GetBindings().InvalidateShell( *m_pShell ); + + if ( pViewFrame->KnowsChildWindow( SID_FM_FILTER_NAVIGATOR ) + && !pViewFrame->HasChildWindow( SID_FM_FILTER_NAVIGATOR ) + ) + { + pViewFrame->ToggleChildWindow( SID_FM_FILTER_NAVIGATOR ); + } +} + + +static void saveFilter(const Reference< runtime::XFormController >& _rxController) +{ + Reference< XPropertySet> xFormAsSet(_rxController->getModel(), UNO_QUERY); + Reference< XPropertySet> xControllerAsSet(_rxController, UNO_QUERY); + + // call the subcontroller + Reference< runtime::XFormController > xController; + for (sal_Int32 i = 0, nCount = _rxController->getCount(); i < nCount; ++i) + { + _rxController->getByIndex(i) >>= xController; + saveFilter(xController); + } + + try + { + + xFormAsSet->setPropertyValue(FM_PROP_FILTER, xControllerAsSet->getPropertyValue(FM_PROP_FILTER)); + xFormAsSet->setPropertyValue(FM_PROP_APPLYFILTER, makeAny( true ) ); + } + catch (const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + +} + + +void FmXFormShell::stopFiltering_Lock(bool bSave) +{ + if (impl_checkDisposed_Lock()) + return; + + m_bFilterMode = false; + + FmXFormView* pXView = m_pShell->GetFormView()->GetImpl(); + + // if the active controller is our external one we have to use the trigger controller + Reference< XControlContainer> xContainer; + if (getActiveController_Lock() == m_xExternalViewController) + { + DBG_ASSERT(m_xExtViewTriggerController.is(), "FmXFormShell::stopFiltering : inconsistent : active external controller, but no one triggered this !"); + xContainer = m_xExtViewTriggerController->getContainer(); + } + else + xContainer = getActiveController_Lock()->getContainer(); + + PFormViewPageWindowAdapter pAdapter = pXView->findWindow(xContainer); + if ( pAdapter.is() ) + { + const ::std::vector< Reference< runtime::XFormController > >& rControllerList = pAdapter->GetList(); + ::std::vector < OUString > aOriginalFilters; + ::std::vector < bool > aOriginalApplyFlags; + + if (bSave) + { + for (const auto& rpController : rControllerList) + { + // remember the current filter settings in case we're going to reload the forms below (which may fail) + try + { + Reference< XPropertySet > xFormAsSet(rpController->getModel(), UNO_QUERY); + aOriginalFilters.push_back(::comphelper::getString(xFormAsSet->getPropertyValue(FM_PROP_FILTER))); + aOriginalApplyFlags.push_back(::comphelper::getBOOL(xFormAsSet->getPropertyValue(FM_PROP_APPLYFILTER))); + } + catch(Exception&) + { + OSL_FAIL("FmXFormShell::stopFiltering : could not get the original filter !"); + // put dummies into the arrays so the they have the right size + + if (aOriginalFilters.size() == aOriginalApplyFlags.size()) + // the first getPropertyValue failed -> use two dummies + aOriginalFilters.emplace_back( ); + aOriginalApplyFlags.push_back( false ); + } + saveFilter(rpController); + } + } + for (const auto& rController : rControllerList) + { + + Reference< XModeSelector> xModeSelector(rController, UNO_QUERY); + if (xModeSelector.is()) + xModeSelector->setMode( "DataMode" ); + } + if (bSave) // execute the filter + { + const ::std::vector< Reference< runtime::XFormController > > & rControllers = pAdapter->GetList(); + for (::std::vector< Reference< runtime::XFormController > > ::const_iterator j = rControllers.begin(); + j != rControllers.end(); ++j) + { + Reference< XLoadable> xReload((*j)->getModel(), UNO_QUERY); + if (!xReload.is()) + continue; + Reference< XPropertySet > xFormSet(xReload, UNO_QUERY); + + try + { + xReload->reload(); + } + catch(Exception&) + { + OSL_FAIL("FmXFormShell::stopFiltering: Exception occurred!"); + } + + if (!isRowSetAlive(xFormSet)) + { // something went wrong -> restore the original state + OUString sOriginalFilter = aOriginalFilters[ j - rControllers.begin() ]; + bool bOriginalApplyFlag = aOriginalApplyFlags[ j - rControllers.begin() ]; + try + { + xFormSet->setPropertyValue(FM_PROP_FILTER, makeAny(sOriginalFilter)); + xFormSet->setPropertyValue(FM_PROP_APPLYFILTER, makeAny(bOriginalApplyFlag)); + xReload->reload(); + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + } + } + } + + m_pShell->UIFeatureChanged(); + m_pShell->GetViewShell()->GetViewFrame()->GetBindings().InvalidateShell(*m_pShell); +} + + +void FmXFormShell::CreateExternalView_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + DBG_ASSERT(m_xAttachedFrame.is(), "FmXFormShell::CreateExternalView : no frame !"); + + // the frame the external view is displayed in + bool bAlreadyExistent = m_xExternalViewController.is(); + Reference< css::frame::XFrame> xExternalViewFrame; + OUString sFrameName("_beamer"); + + Reference<runtime::XFormController> xCurrentNavController(getNavController_Lock()); + // the creation of the "partwindow" may cause a deactivate of the document which will result in our nav controller to be set to NULL + + // _first_ check if we have any valid fields we can use for the grid view + // FS - 21.10.99 - 69219 + { + FmXBoundFormFieldIterator aModelIterator(xCurrentNavController->getModel()); + bool bHaveUsableControls = false; + for (;;) + { + Reference< XPropertySet> xCurrentModelSet(aModelIterator.Next(), UNO_QUERY); + if (!xCurrentModelSet.is()) + break; + // the FmXBoundFormFieldIterator only supplies controls with a valid control source + // so we just have to check the field type + sal_Int16 nClassId = ::comphelper::getINT16(xCurrentModelSet->getPropertyValue(FM_PROP_CLASSID)); + switch (nClassId) + { + case FormComponentType::IMAGECONTROL: + case FormComponentType::CONTROL: + continue; + } + bHaveUsableControls = true; + break; + } + + if (!bHaveUsableControls) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Warning, VclButtonsType::Ok, + SvxResId(RID_STR_NOCONTROLS_FOR_EXTERNALDISPLAY))); + xBox->run(); + return; + } + } + + // load the component for external form views + if (!bAlreadyExistent) + { + URL aWantToDispatch; + aWantToDispatch.Complete = FMURL_COMPONENT_FORMGRIDVIEW; + + Reference< css::frame::XDispatchProvider> xProv(m_xAttachedFrame, UNO_QUERY); + Reference< css::frame::XDispatch> xDisp; + if (xProv.is()) + xDisp = xProv->queryDispatch(aWantToDispatch, sFrameName, + css::frame::FrameSearchFlag::CHILDREN | css::frame::FrameSearchFlag::CREATE); + if (xDisp.is()) + { + xDisp->dispatch(aWantToDispatch, Sequence< PropertyValue>()); + } + + // with this the component should be loaded, now search the frame where it resides in + xExternalViewFrame = m_xAttachedFrame->findFrame(sFrameName, css::frame::FrameSearchFlag::CHILDREN); + if (xExternalViewFrame.is()) + { + m_xExternalViewController = xExternalViewFrame->getController(); + if (m_xExternalViewController.is()) + m_xExternalViewController->addEventListener(static_cast<XEventListener*>(static_cast<XPropertyChangeListener*>(this))); + } + } + else + { + xExternalViewFrame = m_xExternalViewController->getFrame(); + Reference< css::frame::XDispatchProvider> xCommLink(xExternalViewFrame, UNO_QUERY); + + // if we display the active form we interpret the slot as "remove it" + Reference< XForm> xCurrentModel(xCurrentNavController->getModel(), UNO_QUERY); + if ((xCurrentModel == m_xExternalDisplayedForm) || (getInternalForm_Lock(xCurrentModel) == m_xExternalDisplayedForm)) + { + if (m_xExternalViewController == getActiveController_Lock()) + { + Reference< runtime::XFormController > xAsFormController( m_xExternalViewController, UNO_QUERY ); + ControllerFeatures aHelper( xAsFormController ); + (void)aHelper->commitCurrentControl(); + } + + Reference< runtime::XFormController > xNewController(m_xExtViewTriggerController); + CloseExternalFormViewer_Lock(); + setActiveController_Lock(xNewController); + return; + } + + URL aClearURL; + aClearURL.Complete = FMURL_GRIDVIEW_CLEARVIEW; + + Reference< css::frame::XDispatch> xClear( xCommLink->queryDispatch(aClearURL, OUString(), 0)); + if (xClear.is()) + xClear->dispatch(aClearURL, Sequence< PropertyValue>()); + } + + // TODO: We need an interceptor at the xSupplier, which forwards all queryDispatch requests to the FormController + // instance for which this "external view" was triggered + + // get the dispatch interface of the frame so we can communicate (interceptable) with the controller + Reference< css::frame::XDispatchProvider> xCommLink(xExternalViewFrame, UNO_QUERY); + + if (m_xExternalViewController.is()) + { + DBG_ASSERT(xCommLink.is(), "FmXFormShell::CreateExternalView : the component doesn't have the necessary interfaces !"); + // collect the dispatchers we will need + URL aAddColumnURL; + aAddColumnURL.Complete = FMURL_GRIDVIEW_ADDCOLUMN; + Reference< css::frame::XDispatch> xAddColumnDispatch( xCommLink->queryDispatch(aAddColumnURL, OUString(), 0)); + URL aAttachURL; + aAttachURL.Complete = FMURL_GRIDVIEW_ATTACHTOFORM; + Reference< css::frame::XDispatch> xAttachDispatch( xCommLink->queryDispatch(aAttachURL, OUString(), 0)); + + if (xAddColumnDispatch.is() && xAttachDispatch.is()) + { + DBG_ASSERT(xCurrentNavController.is(), "FmXFormShell::CreateExternalView : invalid call : have no nav controller !"); + // first : dispatch the descriptions for the columns to add + sal_Int16 nAddedColumns = 0; + + // for radio buttons we need some special structures + typedef std::map< OUString, Sequence< OUString> > MapUString2UstringSeq; + typedef std::map< OUString, OUString > FmMapUString2UString; + typedef std::map< OUString, sal_Int16 > FmMapUString2Int16; + + MapUString2UstringSeq aRadioValueLists; + MapUString2UstringSeq aRadioListSources; + FmMapUString2UString aRadioControlSources; + FmMapUString2Int16 aRadioPositions; + + FmXBoundFormFieldIterator aModelIterator(xCurrentNavController->getModel()); + OUString sColumnType,aGroupName,sControlSource; + Sequence< Property> aProps; + for (;;) + { + Reference< XPropertySet> xCurrentModelSet(aModelIterator.Next(), UNO_QUERY); + if (!xCurrentModelSet.is()) + break; + OSL_ENSURE(xCurrentModelSet.is(),"xCurrentModelSet is null!"); + // create a description of the column to be created + // first : determine it's type + + sal_Int16 nClassId = ::comphelper::getINT16(xCurrentModelSet->getPropertyValue(FM_PROP_CLASSID)); + switch (nClassId) + { + case FormComponentType::RADIOBUTTON: + { + // get the label of the button (this is the access key for our structures) + aGroupName = getLabelName(xCurrentModelSet); + + // add the reference value of the radio button to the list source sequence + Sequence< OUString>& aThisGroupLabels = aRadioListSources[aGroupName]; + sal_Int32 nNewSizeL = aThisGroupLabels.getLength() + 1; + aThisGroupLabels.realloc(nNewSizeL); + aThisGroupLabels.getArray()[nNewSizeL - 1] = ::comphelper::getString(xCurrentModelSet->getPropertyValue(FM_PROP_REFVALUE)); + + // add the label to the value list sequence + Sequence< OUString>& aThisGroupControlSources = aRadioValueLists[aGroupName]; + sal_Int32 nNewSizeC = aThisGroupControlSources.getLength() + 1; + aThisGroupControlSources.realloc(nNewSizeC); + aThisGroupControlSources.getArray()[nNewSizeC - 1] = ::comphelper::getString(xCurrentModelSet->getPropertyValue(FM_PROP_LABEL)); + + // remember the controls source of the radio group + sControlSource = ::comphelper::getString(xCurrentModelSet->getPropertyValue(FM_PROP_CONTROLSOURCE)); + if (aRadioControlSources.find(aGroupName) == aRadioControlSources.end()) + aRadioControlSources[aGroupName] = sControlSource; +#ifdef DBG_UTIL + else + DBG_ASSERT(aRadioControlSources[aGroupName] == sControlSource, + "FmXFormShell::CreateExternalView : inconsistent radio buttons detected !"); + // (radio buttons with the same name should have the same control source) +#endif + // remember the position within the columns + if (aRadioPositions.find(aGroupName) == aRadioPositions.end()) + aRadioPositions[aGroupName] = nAddedColumns; + + // any further handling is done below + } + continue; + + case FormComponentType::IMAGECONTROL: + case FormComponentType::CONTROL: + // no grid columns for these types (though they have a control source) + continue; + case FormComponentType::CHECKBOX: + sColumnType = FM_COL_CHECKBOX; break; + case FormComponentType::LISTBOX: + sColumnType = FM_COL_LISTBOX; break; + case FormComponentType::COMBOBOX: + sColumnType = FM_COL_COMBOBOX; break; + case FormComponentType::DATEFIELD: + sColumnType = FM_COL_DATEFIELD; break; + case FormComponentType::TIMEFIELD: + sColumnType = FM_COL_TIMEFIELD; break; + case FormComponentType::NUMERICFIELD: + sColumnType = FM_COL_NUMERICFIELD; break; + case FormComponentType::CURRENCYFIELD: + sColumnType = FM_COL_CURRENCYFIELD; break; + case FormComponentType::PATTERNFIELD: + sColumnType = FM_COL_PATTERNFIELD; break; + + case FormComponentType::TEXTFIELD: + { + sColumnType = FM_COL_TEXTFIELD; + // we know at least two different controls which are TextFields : the basic edit field and the formatted + // field. we distinguish them by their service name + Reference< lang::XServiceInfo> xInfo(xCurrentModelSet, UNO_QUERY); + if (xInfo.is()) + { + sal_Int16 nObjectType = getControlTypeByObject(xInfo); + if (OBJ_FM_FORMATTEDFIELD == nObjectType) + sColumnType = FM_COL_FORMATTEDFIELD; + } + } + break; + default: + sColumnType = FM_COL_TEXTFIELD; break; + } + + const sal_Int16 nDispatchArgs = 3; + Sequence< PropertyValue> aDispatchArgs(nDispatchArgs); + PropertyValue* pDispatchArgs = aDispatchArgs.getArray(); + + // properties describing "meta data" about the column + // the type + pDispatchArgs->Name = FMARG_ADDCOL_COLUMNTYPE; + pDispatchArgs->Value <<= sColumnType; + ++pDispatchArgs; + + // the pos : append the col + pDispatchArgs->Name = FMARG_ADDCOL_COLUMNPOS; + pDispatchArgs->Value <<= nAddedColumns; + ++pDispatchArgs; + + // the properties to forward to the new column + Sequence< PropertyValue> aColumnProps(1); + PropertyValue* pColumnProps = aColumnProps.getArray(); + + // the label + pColumnProps->Name = FM_PROP_LABEL; + pColumnProps->Value <<= getLabelName(xCurrentModelSet); + ++pColumnProps; + + // for all other props : transfer them + Reference< XPropertySetInfo> xControlModelInfo( xCurrentModelSet->getPropertySetInfo()); + DBG_ASSERT(xControlModelInfo.is(), "FmXFormShell::CreateExternalView : the control model has no property info ! This will crash !"); + aProps = xControlModelInfo->getProperties(); + + // realloc the control description sequence + sal_Int32 nExistentDescs = pColumnProps - aColumnProps.getArray(); + aColumnProps.realloc(nExistentDescs + aProps.getLength()); + pColumnProps = aColumnProps.getArray() + nExistentDescs; + + for (const Property& rProp : std::as_const(aProps)) + { + if (rProp.Name == FM_PROP_LABEL) + // already set + continue; + if (rProp.Name == FM_PROP_DEFAULTCONTROL) + // allow the column's own "default control" + continue; + if (rProp.Attributes & PropertyAttribute::READONLY) + // assume that properties which are readonly for the control are ro for the column to be created, too + continue; + + pColumnProps->Name = rProp.Name; + pColumnProps->Value = xCurrentModelSet->getPropertyValue(rProp.Name); + ++pColumnProps; + } + aColumnProps.realloc(pColumnProps - aColumnProps.getArray()); + + // columns props are a dispatch argument + pDispatchArgs->Name = "ColumnProperties"; // TODO : fmurl.* + pDispatchArgs->Value <<= aColumnProps; + ++pDispatchArgs; + DBG_ASSERT(nDispatchArgs == (pDispatchArgs - aDispatchArgs.getConstArray()), + "FmXFormShell::CreateExternalView : forgot to adjust nDispatchArgs ?"); + + // dispatch the "add column" + xAddColumnDispatch->dispatch(aAddColumnURL, aDispatchArgs); + ++nAddedColumns; + } + + // now for the radio button handling + sal_Int16 nOffset(0); + // properties describing the "direct" column properties + const sal_Int16 nListBoxDescription = 6; + Sequence< PropertyValue> aListBoxDescription(nListBoxDescription); + for (const auto& rCtrlSource : aRadioControlSources) + { + PropertyValue* pListBoxDescription = aListBoxDescription.getArray(); + // label + pListBoxDescription->Name = FM_PROP_LABEL; + pListBoxDescription->Value <<= rCtrlSource.first; + ++pListBoxDescription; + + // control source + pListBoxDescription->Name = FM_PROP_CONTROLSOURCE; + pListBoxDescription->Value <<= rCtrlSource.second; + ++pListBoxDescription; + + // bound column + pListBoxDescription->Name = FM_PROP_BOUNDCOLUMN; + pListBoxDescription->Value <<= sal_Int16(1); + ++pListBoxDescription; + + // content type + pListBoxDescription->Name = FM_PROP_LISTSOURCETYPE; + pListBoxDescription->Value <<= ListSourceType_VALUELIST; + ++pListBoxDescription; + + // list source + MapUString2UstringSeq::const_iterator aCurrentListSource = aRadioListSources.find(rCtrlSource.first); + DBG_ASSERT(aCurrentListSource != aRadioListSources.end(), + "FmXFormShell::CreateExternalView : inconsistent radio descriptions !"); + pListBoxDescription->Name = FM_PROP_LISTSOURCE; + pListBoxDescription->Value <<= (*aCurrentListSource).second; + ++pListBoxDescription; + + // value list + MapUString2UstringSeq::const_iterator aCurrentValueList = aRadioValueLists.find(rCtrlSource.first); + DBG_ASSERT(aCurrentValueList != aRadioValueLists.end(), + "FmXFormShell::CreateExternalView : inconsistent radio descriptions !"); + pListBoxDescription->Name = FM_PROP_STRINGITEMLIST; + pListBoxDescription->Value <<= (*aCurrentValueList).second; + ++pListBoxDescription; + + DBG_ASSERT(nListBoxDescription == (pListBoxDescription - aListBoxDescription.getConstArray()), + "FmXFormShell::CreateExternalView : forgot to adjust nListBoxDescription ?"); + + // properties describing the column "meta data" + const sal_Int16 nDispatchArgs = 3; + Sequence< PropertyValue> aDispatchArgs(nDispatchArgs); + PropertyValue* pDispatchArgs = aDispatchArgs.getArray(); + + // column type : listbox + pDispatchArgs->Name = FMARG_ADDCOL_COLUMNTYPE; + OUString fColName = FM_COL_LISTBOX; + pDispatchArgs->Value <<= fColName; +// pDispatchArgs->Value <<= (OUString)FM_COL_LISTBOX; + ++pDispatchArgs; + + // column position + pDispatchArgs->Name = FMARG_ADDCOL_COLUMNPOS; + FmMapUString2Int16::const_iterator aOffset = aRadioPositions.find(rCtrlSource.first); + DBG_ASSERT(aOffset != aRadioPositions.end(), + "FmXFormShell::CreateExternalView : inconsistent radio descriptions !"); + sal_Int16 nPosition = (*aOffset).second; + nPosition = nPosition + nOffset; + // we already inserted nOffset additional columns... + pDispatchArgs->Value <<= nPosition; + ++pDispatchArgs; + + // the + pDispatchArgs->Name = "ColumnProperties"; // TODO : fmurl.* + pDispatchArgs->Value <<= aListBoxDescription; + ++pDispatchArgs; + DBG_ASSERT(nDispatchArgs == (pDispatchArgs - aDispatchArgs.getConstArray()), + "FmXFormShell::CreateExternalView : forgot to adjust nDispatchArgs ?"); + + // dispatch the "add column" + xAddColumnDispatch->dispatch(aAddColumnURL, aDispatchArgs); + ++nAddedColumns; + ++nOffset; + } + + + DBG_ASSERT(nAddedColumns > 0, "FmXFormShell::CreateExternalView : no controls (inconsistent) !"); + // we should have checked if we have any usable controls (see above). + + // "load" the "form" of the external view + PropertyValue aArg; + aArg.Name = FMARG_ATTACHTO_MASTERFORM; + Reference< XResultSet> xForm(xCurrentNavController->getModel(), UNO_QUERY); + aArg.Value <<= xForm; + + m_xExternalDisplayedForm = xForm; + // do this before dispatching the "attach" command, as the attach may result in a call to our queryDispatch (for the FormSlots) + // which needs the m_xExternalDisplayedForm + + xAttachDispatch->dispatch(aAttachURL, Sequence< PropertyValue>(&aArg, 1)); + + m_xExtViewTriggerController = xCurrentNavController; + + // we want to know modifications done in the external view + // if the external controller is a XFormController we can use all our default handlings for it + Reference< runtime::XFormController > xFormController( m_xExternalViewController, UNO_QUERY ); + OSL_ENSURE( xFormController.is(), "FmXFormShell::CreateExternalView:: invalid external view controller!" ); + if (xFormController.is()) + xFormController->addActivateListener(static_cast<XFormControllerListener*>(this)); + } + } +#ifdef DBG_UTIL + else + { + OSL_FAIL("FmXFormShell::CreateExternalView : could not create the external form view !"); + } +#endif + InvalidateSlot_Lock(SID_FM_VIEW_AS_GRID, false); +} + + +void FmXFormShell::implAdjustConfigCache_Lock() +{ + // get (cache) the wizard usage flag + Sequence< OUString > aNames { "FormControlPilotsEnabled" }; + Sequence< Any > aFlags = GetProperties(aNames); + if (1 == aFlags.getLength()) + m_bUseWizards = ::cppu::any2bool(aFlags[0]); +} + + +void FmXFormShell::Notify( const css::uno::Sequence< OUString >& _rPropertyNames) +{ + DBG_TESTSOLARMUTEX(); + if (impl_checkDisposed_Lock()) + return; + + for (const OUString& rName : _rPropertyNames) + if (rName == "FormControlPilotsEnabled") + { + implAdjustConfigCache_Lock(); + InvalidateSlot_Lock(SID_FM_USE_WIZARDS, true); + } +} + +void FmXFormShell::ImplCommit() +{ +} + + +void FmXFormShell::SetWizardUsing_Lock(bool _bUseThem) +{ + m_bUseWizards = _bUseThem; + + Sequence< OUString > aNames { "FormControlPilotsEnabled" }; + Sequence< Any > aValues(1); + aValues[0] <<= m_bUseWizards; + PutProperties(aNames, aValues); +} + + +void FmXFormShell::viewDeactivated_Lock(FmFormView& _rCurrentView, bool _bDeactivateController) +{ + + if ( _rCurrentView.GetImpl() && !_rCurrentView.IsDesignMode() ) + { + _rCurrentView.GetImpl()->Deactivate( _bDeactivateController ); + } + + // if we have an async load operation pending for the 0-th page for this view, + // we need to cancel this + if (FmFormPage* pPage = _rCurrentView.GetCurPage()) + { + // move all events from our queue to a new one, omit the events for the deactivated + // page + ::std::queue< FmLoadAction > aNewEvents; + while ( !m_aLoadingPages.empty() ) + { + FmLoadAction aAction = m_aLoadingPages.front(); + m_aLoadingPages.pop(); + if ( pPage != aAction.pPage ) + { + aNewEvents.push( aAction ); + } + else + { + Application::RemoveUserEvent( aAction.nEventId ); + } + } + m_aLoadingPages = aNewEvents; + + // remove callbacks at the page + pPage->GetImpl().SetFormsCreationHdl( Link<FmFormPageImpl&,void>() ); + } + UpdateForms_Lock(true); +} + + +IMPL_LINK_NOARG( FmXFormShell, OnFirstTimeActivation_Lock, void*, void ) +{ + if (impl_checkDisposed_Lock()) + return; + + m_nActivationEvent = nullptr; + SfxObjectShell* pDocument = m_pShell->GetObjectShell(); + + if ( pDocument && !pDocument->HasName() ) + { + if (isEnhancedForm_Lock()) + { + // show the data navigator + if ( !m_pShell->GetViewShell()->GetViewFrame()->HasChildWindow( SID_FM_SHOW_DATANAVIGATOR ) ) + m_pShell->GetViewShell()->GetViewFrame()->ToggleChildWindow( SID_FM_SHOW_DATANAVIGATOR ); + } + } +} + + +IMPL_LINK_NOARG( FmXFormShell, OnFormsCreated_Lock, FmFormPageImpl&, void ) +{ + UpdateForms_Lock(true); +} + + +void FmXFormShell::viewActivated_Lock(FmFormView& _rCurrentView, bool _bSyncAction) +{ + FmFormPage* pPage = _rCurrentView.GetCurPage(); + + // activate our view if we are activated ourself + // FS - 30.06.99 - 67308 + if ( _rCurrentView.GetImpl() && !_rCurrentView.IsDesignMode() ) + { + // load forms for the page the current view belongs to + if ( pPage ) + { + if ( !pPage->GetImpl().hasEverBeenActivated() ) + loadForms_Lock(pPage, LoadFormsFlags::Load + | (_bSyncAction ? LoadFormsFlags::Sync + : LoadFormsFlags::Async)); + pPage->GetImpl().setHasBeenActivated( ); + } + + // first-time initializations for the views + if ( !_rCurrentView.GetImpl()->hasEverBeenActivated( ) ) + { + _rCurrentView.GetImpl()->onFirstViewActivation( dynamic_cast<FmFormModel*>( _rCurrentView.GetModel() ) ); + _rCurrentView.GetImpl()->setHasBeenActivated( ); + } + + // activate the current view + _rCurrentView.GetImpl()->Activate( _bSyncAction ); + } + + // set callbacks at the page + if ( pPage ) + { + pPage->GetImpl().SetFormsCreationHdl(LINK(this, FmXFormShell, OnFormsCreated_Lock)); + } + + UpdateForms_Lock(true); + + if ( m_bFirstActivation ) + { + m_nActivationEvent = Application::PostUserEvent(LINK(this, FmXFormShell, OnFirstTimeActivation_Lock)); + m_bFirstActivation = false; + } + + // find a default "current form", if there is none, yet + // #i88186# / 2008-04-12 / frank.schoenheit@sun.com + impl_defaultCurrentForm_nothrow_Lock(); +} + + +void FmXFormShell::impl_defaultCurrentForm_nothrow_Lock() +{ + if (impl_checkDisposed_Lock()) + return; + + if ( m_xCurrentForm.is() ) + // no action required + return; + + FmFormView* pFormView = m_pShell->GetFormView(); + FmFormPage* pPage = pFormView ? pFormView->GetCurPage() : nullptr; + if ( !pPage ) + return; + + try + { + Reference< XIndexAccess > xForms = pPage->GetForms( false ); + if ( !xForms.is() || !xForms->hasElements() ) + return; + + Reference< XForm > xNewCurrentForm( xForms->getByIndex(0), UNO_QUERY_THROW ); + impl_updateCurrentForm_Lock(xNewCurrentForm); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +void FmXFormShell::smartControlReset( const Reference< XIndexAccess >& _rxModels ) +{ + if (!_rxModels.is()) + { + OSL_FAIL("FmXFormShell::smartControlReset: invalid container!"); + return; + } + + static const OUString sClassIdPropertyName = FM_PROP_CLASSID; + static const OUString sBoundFieldPropertyName = FM_PROP_BOUNDFIELD; + sal_Int32 nCount = _rxModels->getCount(); + Reference< XPropertySet > xCurrent; + Reference< XPropertySetInfo > xCurrentInfo; + Reference< XPropertySet > xBoundField; + + for (sal_Int32 i=0; i<nCount; ++i) + { + _rxModels->getByIndex(i) >>= xCurrent; + if (xCurrent.is()) + xCurrentInfo = xCurrent->getPropertySetInfo(); + else + xCurrentInfo.clear(); + if (!xCurrentInfo.is()) + continue; + + if (xCurrentInfo->hasPropertyByName(sClassIdPropertyName)) + { // it's a control model + + // check if this control is bound to a living database field + if (xCurrentInfo->hasPropertyByName(sBoundFieldPropertyName)) + xCurrent->getPropertyValue(sBoundFieldPropertyName) >>= xBoundField; + else + xBoundField.clear(); + + // reset only if it's *not* bound + bool bReset = !xBoundField.is(); + + // and additionally, check if it has an external value binding + Reference< XBindableValue > xBindable( xCurrent, UNO_QUERY ); + if ( xBindable.is() && xBindable->getValueBinding().is() ) + bReset = false; + + if ( bReset ) + { + Reference< XReset > xControlReset( xCurrent, UNO_QUERY ); + if ( xControlReset.is() ) + xControlReset->reset(); + } + } + else + { + Reference< XIndexAccess > xContainer(xCurrent, UNO_QUERY); + if (xContainer.is()) + smartControlReset(xContainer); + } + } +} + + +IMPL_LINK_NOARG( FmXFormShell, OnLoadForms_Lock, void*, void ) +{ + FmLoadAction aAction = m_aLoadingPages.front(); + m_aLoadingPages.pop(); + + loadForms_Lock(aAction.pPage, aAction.nFlags & ~LoadFormsFlags::Async); +} + + +namespace +{ + bool lcl_isLoadable( const Reference< XInterface >& _rxLoadable ) + { + // determines whether a form should be loaded or not + // if there is no datasource or connection there is no reason to load a form + Reference< XPropertySet > xSet( _rxLoadable, UNO_QUERY ); + if ( !xSet.is() ) + return false; + try + { + Reference< XConnection > xConn; + if ( isEmbeddedInDatabase( _rxLoadable.get(), xConn ) ) + return true; + + // is there already an active connection + xSet->getPropertyValue(FM_PROP_ACTIVE_CONNECTION) >>= xConn; + if ( xConn.is() ) + return true; + + OUString sPropertyValue; + OSL_VERIFY( xSet->getPropertyValue( FM_PROP_DATASOURCE ) >>= sPropertyValue ); + if ( !sPropertyValue.isEmpty() ) + return true; + + OSL_VERIFY( xSet->getPropertyValue( FM_PROP_URL ) >>= sPropertyValue ); + if ( !sPropertyValue.isEmpty() ) + return true; + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return false; + } +} + + +void FmXFormShell::loadForms_Lock(FmFormPage* _pPage, const LoadFormsFlags _nBehaviour /* LoadFormsFlags::Load | LoadFormsFlags::Sync */) +{ + DBG_ASSERT( ( _nBehaviour & ( LoadFormsFlags::Async | LoadFormsFlags::Unload ) ) != ( LoadFormsFlags::Async | LoadFormsFlags::Unload ), + "FmXFormShell::loadForms: async loading not supported - this will heavily fail!" ); + + if ( _nBehaviour & LoadFormsFlags::Async ) + { + m_aLoadingPages.push( FmLoadAction( + _pPage, + _nBehaviour, + Application::PostUserEvent(LINK(this, FmXFormShell, OnLoadForms_Lock), _pPage) + ) ); + return; + } + + DBG_ASSERT( _pPage, "FmXFormShell::loadForms: invalid page!" ); + if ( _pPage ) + { + // lock the undo env so the forms can change non-transient properties while loading + // (without this my doc's modified flag would be set) + FmFormModel& rFmFormModel(dynamic_cast< FmFormModel& >(_pPage->getSdrModelFromSdrPage())); + rFmFormModel.GetUndoEnv().Lock(); + + // load all forms + Reference< XIndexAccess > xForms = _pPage->GetForms( false ); + + if ( xForms.is() ) + { + Reference< XLoadable > xForm; + for ( sal_Int32 j = 0, nCount = xForms->getCount(); j < nCount; ++j ) + { + xForms->getByIndex( j ) >>= xForm; + bool bFormWasLoaded = false; + // a database form must be loaded for + try + { + if ( !( _nBehaviour & LoadFormsFlags::Unload ) ) + { + if ( lcl_isLoadable( xForm ) && !xForm->isLoaded() ) + xForm->load(); + } + else + { + if ( xForm->isLoaded() ) + { + bFormWasLoaded = true; + xForm->unload(); + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + // reset the form if it was loaded + if ( bFormWasLoaded ) + { + Reference< XIndexAccess > xContainer( xForm, UNO_QUERY ); + DBG_ASSERT( xContainer.is(), "FmXFormShell::loadForms: the form is no container!" ); + if ( xContainer.is() ) + smartControlReset( xContainer ); + } + } + } + + // unlock the environment + rFmFormModel.GetUndoEnv().UnLock(); + } +} + + +void FmXFormShell::ExecuteTextAttribute_Lock(SfxRequest& _rReq) +{ + DBG_TESTSOLARMUTEX(); + m_pTextShell->ExecuteTextAttribute( _rReq ); +} + + +void FmXFormShell::GetTextAttributeState_Lock(SfxItemSet& _rSet) +{ + DBG_TESTSOLARMUTEX(); + m_pTextShell->GetTextAttributeState( _rSet ); +} + + +bool FmXFormShell::IsActiveControl_Lock(bool _bCountRichTextOnly ) const +{ + DBG_TESTSOLARMUTEX(); + return m_pTextShell->IsActiveControl( _bCountRichTextOnly ); +} + + +void FmXFormShell::ForgetActiveControl_Lock() +{ + DBG_TESTSOLARMUTEX(); + m_pTextShell->ForgetActiveControl(); +} + + +void FmXFormShell::SetControlActivationHandler_Lock(const Link<LinkParamNone*,void>& _rHdl) +{ + DBG_TESTSOLARMUTEX(); + m_pTextShell->SetControlActivationHandler( _rHdl ); +} + +void FmXFormShell::handleShowPropertiesRequest_Lock() +{ + if (onlyControlsAreMarked_Lock()) + ShowSelectionProperties_Lock( true ); +} + + +void FmXFormShell::handleMouseButtonDown_Lock(const SdrViewEvent& _rViewEvent) +{ + // catch simple double clicks + if ( ( _rViewEvent.nMouseClicks == 2 ) && ( _rViewEvent.nMouseCode == MOUSE_LEFT ) ) + { + if ( _rViewEvent.eHit == SdrHitKind::MarkedObject ) + { + if (onlyControlsAreMarked_Lock()) + ShowSelectionProperties_Lock( true ); + } + } +} + + +bool FmXFormShell::HasControlFocus_Lock() const +{ + bool bHasControlFocus = false; + + try + { + Reference<runtime::XFormController> xController(getActiveController_Lock()); + Reference< XControl > xCurrentControl; + if ( xController.is() ) + xCurrentControl.set( xController->getCurrentControl() ); + if ( xCurrentControl.is() ) + { + Reference< XWindow2 > xPeerWindow( xCurrentControl->getPeer(), UNO_QUERY_THROW ); + bHasControlFocus = xPeerWindow->hasFocus(); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + return bHasControlFocus; +} + + +SearchableControlIterator::SearchableControlIterator(Reference< XInterface> const & xStartingPoint) + :IndexAccessIterator(xStartingPoint) +{ +} + + +bool SearchableControlIterator::ShouldHandleElement(const Reference< XInterface>& xElement) +{ + // if the thing has a ControlSource and a BoundField property + Reference< XPropertySet> xProperties(xElement, UNO_QUERY); + if (::comphelper::hasProperty(FM_PROP_CONTROLSOURCE, xProperties) && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xProperties)) + { + // and the BoundField is valid + Reference< XPropertySet> xField; + xProperties->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField; + if (xField.is()) + { + // we take it + m_sCurrentValue = ::comphelper::getString(xProperties->getPropertyValue(FM_PROP_CONTROLSOURCE)); + return true; + } + } + + // if it is a grid control + if (::comphelper::hasProperty(FM_PROP_CLASSID, xProperties)) + { + Any aClassId( xProperties->getPropertyValue(FM_PROP_CLASSID) ); + if (::comphelper::getINT16(aClassId) == FormComponentType::GRIDCONTROL) + { + m_sCurrentValue.clear(); + return true; + } + } + + return false; +} + + +bool SearchableControlIterator::ShouldStepInto(const Reference< XInterface>& /*xContainer*/) const +{ + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmsrccfg.cxx b/svx/source/form/fmsrccfg.cxx new file mode 100644 index 000000000..499d2cb16 --- /dev/null +++ b/svx/source/form/fmsrccfg.cxx @@ -0,0 +1,286 @@ +/* -*- 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 <svx/fmsrccfg.hxx> + +#include <sal/log.hxx> +#include <comphelper/processfactory.hxx> +#include <i18nutil/transliteration.hxx> + +using namespace ::com::sun::star::uno; + +namespace svxform +{ + // search parameters + + FmSearchParams::FmSearchParams() + :nTransliterationFlags( TransliterationFlags::NONE ) + ,nSearchForType ( 0 ) + ,nPosition ( MATCHING_ANYWHERE ) + ,nLevOther ( 2 ) + ,nLevShorter ( 2 ) + ,nLevLonger ( 2 ) + ,bLevRelaxed ( true ) + ,bAllFields ( false ) + ,bUseFormatter ( true ) + ,bBackwards ( false ) + ,bWildcard ( false ) + ,bRegular ( false ) + ,bApproxSearch ( false ) + ,bSoundsLikeCJK ( false ) + { + nTransliterationFlags = + TransliterationFlags::ignoreSpace_ja_JP + | TransliterationFlags::ignoreMiddleDot_ja_JP + | TransliterationFlags::ignoreProlongedSoundMark_ja_JP + | TransliterationFlags::ignoreSeparator_ja_JP + | TransliterationFlags::IGNORE_CASE; + } + + bool FmSearchParams::isIgnoreWidthCJK( ) const + { + return bool(nTransliterationFlags & TransliterationFlags::IGNORE_WIDTH); + } + + bool FmSearchParams::isCaseSensitive( ) const + { + return !(nTransliterationFlags & TransliterationFlags::IGNORE_CASE); + } + + void FmSearchParams::setCaseSensitive( bool _bCase ) + { + if ( _bCase ) + nTransliterationFlags &= ~TransliterationFlags::IGNORE_CASE; + else + nTransliterationFlags |= TransliterationFlags::IGNORE_CASE; + } + + // maps from ascii values to int values + + namespace { + + struct Ascii2Int16 + { + const char* pAscii; + sal_Int16 nValue; + }; + + } + + static const Ascii2Int16* lcl_getSearchForTypeValueMap() + { + static const Ascii2Int16 s_aSearchForTypeMap[] = + { + { "text", 0 }, + { "null", 1 }, + { "non-null", 2 }, + { nullptr, -1 } + }; + return s_aSearchForTypeMap; + } + + static const Ascii2Int16* lcl_getSearchPositionValueMap() + { + static const Ascii2Int16 s_aSearchPositionMap[] = + { + { "anywhere-in-field", MATCHING_ANYWHERE }, + { "beginning-of-field", MATCHING_BEGINNING }, + { "end-of-field", MATCHING_END }, + { "complete-field", MATCHING_WHOLETEXT }, + { nullptr, -1 } + }; + return s_aSearchPositionMap; + } + + static sal_Int16 lcl_implMapAsciiValue( const OUString& _rAsciiValue, const Ascii2Int16* _pMap ) + { + // search the map for the given ascii value + const Ascii2Int16* pSearch = _pMap; + while ( pSearch && pSearch->pAscii ) + { + if ( _rAsciiValue.equalsAscii( pSearch->pAscii ) ) + // found + return pSearch->nValue; + ++pSearch; + } + + SAL_WARN( + "svx.form", "could not convert the ascii value " << _rAsciiValue); + return -1; + } + + static const char* lcl_implMapIntValue( const sal_Int16 _nValue, const Ascii2Int16* _pMap ) + { + // search the map for the given integer value + const Ascii2Int16* pSearch = _pMap; + while ( pSearch && pSearch->pAscii ) + { + if ( _nValue == pSearch->nValue ) + // found + return pSearch->pAscii; + ++pSearch; + } + + SAL_WARN( "svx", "lcl_implMapIntValue: could not convert the integer value " + << _nValue << " !"); + static const char* const s_pDummy = ""; + // just as a fallback... + return s_pDummy; + } + + // class FmSearchConfigItem - a config item that stores search parameters + +#define TA( c ) &c, cppu::UnoType<decltype(c)>::get() + + FmSearchConfigItem::FmSearchConfigItem() + :OConfigurationValueContainer( ::comphelper::getProcessComponentContext(), m_aMutex, "/org.openoffice.Office.DataAccess/FormSearchOptions", 2 ) + { + // register our members so the data exchange with the node values is done automatically + + registerExchangeLocation( "SearchHistory", TA( aHistory ) ); + registerExchangeLocation( "LevenshteinOther", TA( nLevOther ) ); + registerExchangeLocation( "LevenshteinShorter", TA( nLevShorter ) ); + registerExchangeLocation( "LevenshteinLonger", TA( nLevLonger ) ); + registerExchangeLocation( "IsLevenshteinRelaxed", TA( bLevRelaxed ) ); + registerExchangeLocation( "IsSearchAllFields", TA( bAllFields ) ); + registerExchangeLocation( "IsUseFormatter", TA( bUseFormatter ) ); + registerExchangeLocation( "IsBackwards", TA( bBackwards ) ); + registerExchangeLocation( "IsWildcardSearch", TA( bWildcard ) ); + registerExchangeLocation( "IsUseRegularExpression", TA( bRegular ) ); + registerExchangeLocation( "IsSimilaritySearch", TA( bApproxSearch ) ); + registerExchangeLocation( "IsUseAsianOptions", TA( bSoundsLikeCJK ) ); + + // the properties which need to be translated + registerExchangeLocation( "SearchType", TA( m_sSearchForType ) ); + registerExchangeLocation( "SearchPosition", TA( m_sSearchPosition ) ); + + registerExchangeLocation( "IsMatchCase", TA( m_bIsMatchCase ) ); + registerExchangeLocation( "Japanese/IsMatchFullHalfWidthForms", TA( m_bIsMatchFullHalfWidthForms ) ); + registerExchangeLocation( "Japanese/IsMatchHiraganaKatakana", TA( m_bIsMatchHiraganaKatakana ) ); + registerExchangeLocation( "Japanese/IsMatchContractions", TA( m_bIsMatchContractions ) ); + registerExchangeLocation( "Japanese/IsMatchMinusDashCho-on", TA( m_bIsMatchMinusDashCho_on ) ); + registerExchangeLocation( "Japanese/IsMatchRepeatCharMarks", TA( m_bIsMatchRepeatCharMarks ) ); + registerExchangeLocation( "Japanese/IsMatchVariantFormKanji", TA( m_bIsMatchVariantFormKanji ) ); + registerExchangeLocation( "Japanese/IsMatchOldKanaForms", TA( m_bIsMatchOldKanaForms ) ); + registerExchangeLocation( "Japanese/IsMatch_DiZi_DuZu", TA( m_bIsMatch_DiZi_DuZu ) ); + registerExchangeLocation( "Japanese/IsMatch_BaVa_HaFa", TA( m_bIsMatch_BaVa_HaFa ) ); + registerExchangeLocation( "Japanese/IsMatch_TsiThiChi_DhiZi", TA( m_bIsMatch_TsiThiChi_DhiZi ) ); + registerExchangeLocation( "Japanese/IsMatch_HyuIyu_ByuVyu", TA( m_bIsMatch_HyuIyu_ByuVyu ) ); + registerExchangeLocation( "Japanese/IsMatch_SeShe_ZeJe", TA( m_bIsMatch_SeShe_ZeJe ) ); + registerExchangeLocation( "Japanese/IsMatch_IaIya", TA( m_bIsMatch_IaIya ) ); + registerExchangeLocation( "Japanese/IsMatch_KiKu", TA( m_bIsMatch_KiKu ) ); + registerExchangeLocation( "Japanese/IsIgnorePunctuation", TA( m_bIsIgnorePunctuation ) ); + registerExchangeLocation( "Japanese/IsIgnoreWhitespace", TA( m_bIsIgnoreWhitespace ) ); + registerExchangeLocation( "Japanese/IsIgnoreProlongedSoundMark",TA( m_bIsIgnoreProlongedSoundMark ) ); + registerExchangeLocation( "Japanese/IsIgnoreMiddleDot", TA( m_bIsIgnoreMiddleDot ) ); + + read( ); + } + + FmSearchConfigItem::~FmSearchConfigItem() + { + commit( ); + } + + void FmSearchConfigItem::implTranslateFromConfig( ) + { + // the search-for string + nSearchForType = lcl_implMapAsciiValue( m_sSearchForType, lcl_getSearchForTypeValueMap() ); + + // the search position + nPosition = lcl_implMapAsciiValue( m_sSearchPosition, lcl_getSearchPositionValueMap() ); + + // the transliteration flags + nTransliterationFlags = TransliterationFlags::NONE; + + if ( !m_bIsMatchCase ) nTransliterationFlags |= TransliterationFlags::IGNORE_CASE; + if ( m_bIsMatchFullHalfWidthForms ) nTransliterationFlags |= TransliterationFlags::IGNORE_WIDTH; + if ( m_bIsMatchHiraganaKatakana ) nTransliterationFlags |= TransliterationFlags::IGNORE_KANA; + if ( m_bIsMatchContractions ) nTransliterationFlags |= TransliterationFlags::ignoreSize_ja_JP; + if ( m_bIsMatchMinusDashCho_on ) nTransliterationFlags |= TransliterationFlags::ignoreMinusSign_ja_JP; + if ( m_bIsMatchRepeatCharMarks ) nTransliterationFlags |= TransliterationFlags::ignoreIterationMark_ja_JP; + if ( m_bIsMatchVariantFormKanji ) nTransliterationFlags |= TransliterationFlags::ignoreTraditionalKanji_ja_JP; + if ( m_bIsMatchOldKanaForms ) nTransliterationFlags |= TransliterationFlags::ignoreTraditionalKana_ja_JP; + if ( m_bIsMatch_DiZi_DuZu ) nTransliterationFlags |= TransliterationFlags::ignoreZiZu_ja_JP; + if ( m_bIsMatch_BaVa_HaFa ) nTransliterationFlags |= TransliterationFlags::ignoreBaFa_ja_JP; + if ( m_bIsMatch_TsiThiChi_DhiZi ) nTransliterationFlags |= TransliterationFlags::ignoreTiJi_ja_JP; + if ( m_bIsMatch_HyuIyu_ByuVyu ) nTransliterationFlags |= TransliterationFlags::ignoreHyuByu_ja_JP; + if ( m_bIsMatch_SeShe_ZeJe ) nTransliterationFlags |= TransliterationFlags::ignoreSeZe_ja_JP; + if ( m_bIsMatch_IaIya ) nTransliterationFlags |= TransliterationFlags::ignoreIandEfollowedByYa_ja_JP; + if ( m_bIsMatch_KiKu ) nTransliterationFlags |= TransliterationFlags::ignoreKiKuFollowedBySa_ja_JP; + + if ( m_bIsIgnorePunctuation ) nTransliterationFlags |= TransliterationFlags::ignoreSeparator_ja_JP; + if ( m_bIsIgnoreWhitespace ) nTransliterationFlags |= TransliterationFlags::ignoreSpace_ja_JP; + if ( m_bIsIgnoreProlongedSoundMark ) nTransliterationFlags |= TransliterationFlags::ignoreProlongedSoundMark_ja_JP; + if ( m_bIsIgnoreMiddleDot ) nTransliterationFlags |= TransliterationFlags::ignoreMiddleDot_ja_JP; + } + + void FmSearchConfigItem::implTranslateToConfig( ) + { + // the search-for string + m_sSearchForType = OUString::createFromAscii( lcl_implMapIntValue( nSearchForType, lcl_getSearchForTypeValueMap() ) ); + + // the search position + m_sSearchPosition = OUString::createFromAscii( lcl_implMapIntValue( nPosition, lcl_getSearchPositionValueMap() ) ); + + // the transliteration flags + + m_bIsMatchCase = !( nTransliterationFlags & TransliterationFlags::IGNORE_CASE ); + m_bIsMatchFullHalfWidthForms = bool( nTransliterationFlags & TransliterationFlags::IGNORE_WIDTH ); + m_bIsMatchHiraganaKatakana = bool( nTransliterationFlags & TransliterationFlags::IGNORE_KANA ); + m_bIsMatchContractions = bool( nTransliterationFlags & TransliterationFlags::ignoreSize_ja_JP ); + m_bIsMatchMinusDashCho_on = bool( nTransliterationFlags & TransliterationFlags::ignoreMinusSign_ja_JP ); + m_bIsMatchRepeatCharMarks = bool( nTransliterationFlags & TransliterationFlags::ignoreIterationMark_ja_JP ); + m_bIsMatchVariantFormKanji = bool( nTransliterationFlags & TransliterationFlags::ignoreTraditionalKanji_ja_JP ); + m_bIsMatchOldKanaForms = bool( nTransliterationFlags & TransliterationFlags::ignoreTraditionalKana_ja_JP ); + m_bIsMatch_DiZi_DuZu = bool( nTransliterationFlags & TransliterationFlags::ignoreZiZu_ja_JP ); + m_bIsMatch_BaVa_HaFa = bool( nTransliterationFlags & TransliterationFlags::ignoreBaFa_ja_JP ); + m_bIsMatch_TsiThiChi_DhiZi = bool( nTransliterationFlags & TransliterationFlags::ignoreTiJi_ja_JP ); + m_bIsMatch_HyuIyu_ByuVyu = bool( nTransliterationFlags & TransliterationFlags::ignoreHyuByu_ja_JP ); + m_bIsMatch_SeShe_ZeJe = bool( nTransliterationFlags & TransliterationFlags::ignoreSeZe_ja_JP ); + m_bIsMatch_IaIya = bool( nTransliterationFlags & TransliterationFlags::ignoreIandEfollowedByYa_ja_JP ); + m_bIsMatch_KiKu = bool( nTransliterationFlags & TransliterationFlags::ignoreKiKuFollowedBySa_ja_JP ); + + m_bIsIgnorePunctuation = bool( nTransliterationFlags & TransliterationFlags::ignoreSeparator_ja_JP ); + m_bIsIgnoreWhitespace = bool( nTransliterationFlags & TransliterationFlags::ignoreSpace_ja_JP ); + m_bIsIgnoreProlongedSoundMark = bool( nTransliterationFlags & TransliterationFlags::ignoreProlongedSoundMark_ja_JP ); + m_bIsIgnoreMiddleDot = bool( nTransliterationFlags & TransliterationFlags::ignoreMiddleDot_ja_JP ); + } + + const FmSearchParams& FmSearchConfigItem::getParams() const + { + // ensure that the properties which are not stored directly are up-to-date + const_cast< FmSearchConfigItem* >( this )->implTranslateFromConfig( ); + + // and return our FmSearchParams part + return *this; + } + + void FmSearchConfigItem::setParams( const FmSearchParams& _rParams ) + { + // copy the FmSearchParams part + *static_cast< FmSearchParams* >( this ) = _rParams; + + // translate the settings not represented by a direct config value + implTranslateToConfig(); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmsrcimp.cxx b/svx/source/form/fmsrcimp.cxx new file mode 100644 index 000000000..82a6313a9 --- /dev/null +++ b/svx/source/form/fmsrcimp.cxx @@ -0,0 +1,1063 @@ +/* -*- 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 <sal/config.h> + +#include <o3tl/safeint.hxx> +#include <svx/fmtools.hxx> +#include <svx/fmsrccfg.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <tools/wldcrd.hxx> +#include <vcl/svapp.hxx> +#include <unotools/textsearch.hxx> +#include <com/sun/star/awt/XTextComponent.hpp> +#include <com/sun/star/awt/XListBox.hpp> +#include <com/sun/star/awt/XCheckBox.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/util/SearchAlgorithms2.hpp> +#include <com/sun/star/util/SearchFlags.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/i18n/CollatorOptions.hpp> + +#include <com/sun/star/sdb/XColumn.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdbc/XDatabaseMetaData.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> + +#include <fmprop.hxx> +#include <svx/fmsrcimp.hxx> + +#include <comphelper/types.hxx> +#include <unotools/syslocale.hxx> +#include <i18nutil/searchopt.hxx> + +#define EQUAL_BOOKMARKS(a, b) a == b + +#define IFACECAST(c) static_cast<const Reference< XInterface >&>(c) + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::beans; +using namespace ::svxform; + + +// = FmRecordCountListener + +// SMART_UNO_IMPLEMENTATION(FmRecordCountListener, UsrObject); + + +FmRecordCountListener::FmRecordCountListener(const Reference< css::sdbc::XResultSet > & dbcCursor) +{ + + m_xListening.set(dbcCursor, UNO_QUERY); + if (!m_xListening.is()) + return; + + if (::comphelper::getBOOL(m_xListening->getPropertyValue(FM_PROP_ROWCOUNTFINAL))) + { + m_xListening = nullptr; + // there's nothing to do as the record count is already known + return; + } + + m_xListening->addPropertyChangeListener(FM_PROP_ROWCOUNT, static_cast<css::beans::XPropertyChangeListener*>(this)); +} + + +void FmRecordCountListener::SetPropChangeHandler(const Link<sal_Int32,void>& lnk) +{ + m_lnkWhoWantsToKnow = lnk; + + if (m_xListening.is()) + NotifyCurrentCount(); +} + + +FmRecordCountListener::~FmRecordCountListener() +{ + +} + + +void FmRecordCountListener::DisConnect() +{ + if(m_xListening.is()) + m_xListening->removePropertyChangeListener(FM_PROP_ROWCOUNT, static_cast<css::beans::XPropertyChangeListener*>(this)); + m_xListening = nullptr; +} + + +void SAL_CALL FmRecordCountListener::disposing(const css::lang::EventObject& /*Source*/) +{ + DBG_ASSERT(m_xListening.is(), "FmRecordCountListener::disposing should never have been called without a propset !"); + DisConnect(); +} + + +void FmRecordCountListener::NotifyCurrentCount() +{ + if (m_lnkWhoWantsToKnow.IsSet()) + { + DBG_ASSERT(m_xListening.is(), "FmRecordCountListener::NotifyCurrentCount : I have no propset ... !?"); + sal_Int32 theCount = ::comphelper::getINT32(m_xListening->getPropertyValue(FM_PROP_ROWCOUNT)); + m_lnkWhoWantsToKnow.Call(theCount); + } +} + + +void FmRecordCountListener::propertyChange(const css::beans::PropertyChangeEvent& /*evt*/) +{ + NotifyCurrentCount(); +} + + +// FmSearchEngine - local classes + +SimpleTextWrapper::SimpleTextWrapper(const Reference< css::awt::XTextComponent > & _xText) + :ControlTextWrapper(_xText.get()) + ,m_xText(_xText) +{ + DBG_ASSERT(m_xText.is(), "FmSearchEngine::SimpleTextWrapper::SimpleTextWrapper : invalid argument !"); +} + + +OUString SimpleTextWrapper::getCurrentText() const +{ + return m_xText->getText(); +} + + +ListBoxWrapper::ListBoxWrapper(const Reference< css::awt::XListBox > & _xBox) + :ControlTextWrapper(_xBox.get()) + ,m_xBox(_xBox) +{ + DBG_ASSERT(m_xBox.is(), "FmSearchEngine::ListBoxWrapper::ListBoxWrapper : invalid argument !"); +} + + +OUString ListBoxWrapper::getCurrentText() const +{ + return m_xBox->getSelectedItem(); +} + + +CheckBoxWrapper::CheckBoxWrapper(const Reference< css::awt::XCheckBox > & _xBox) + :ControlTextWrapper(_xBox.get()) + ,m_xBox(_xBox) +{ + DBG_ASSERT(m_xBox.is(), "FmSearchEngine::CheckBoxWrapper::CheckBoxWrapper : invalid argument !"); +} + + +OUString CheckBoxWrapper::getCurrentText() const +{ + switch (static_cast<TriState>(m_xBox->getState())) + { + case TRISTATE_FALSE: return "0"; + case TRISTATE_TRUE: return "1"; + default: break; + } + return OUString(); +} + + +// = FmSearchEngine + +bool FmSearchEngine::MoveCursor() +{ + bool bSuccess = true; + try + { + if (m_bForward) + if (m_xSearchCursor.isLast()) + m_xSearchCursor.first(); + else + m_xSearchCursor.next(); + else + if (m_xSearchCursor.isFirst()) + { + rtl::Reference<FmRecordCountListener> prclListener = new FmRecordCountListener(m_xSearchCursor); + prclListener->SetPropChangeHandler(LINK(this, FmSearchEngine, OnNewRecordCount)); + + m_xSearchCursor.last(); + + prclListener->DisConnect(); + } + else + m_xSearchCursor.previous(); + } + catch(Exception const&) + { + TOOLS_WARN_EXCEPTION( "svx", "FmSearchEngine::MoveCursor"); + bSuccess = false; + } + catch(...) + { + OSL_FAIL("FmSearchEngine::MoveCursor : caught an unknown Exception !"); + bSuccess = false; + } + + return bSuccess; +} + + +bool FmSearchEngine::MoveField(sal_Int32& nPos, FieldCollection::iterator& iter, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd) +{ + bool bSuccess(true); + if (m_bForward) + { + ++iter; + ++nPos; + if (iter == iterEnd) + { + bSuccess = MoveCursor(); + iter = iterBegin; + nPos = 0; + } + } else + { + if (iter == iterBegin) + { + bSuccess = MoveCursor(); + iter = iterEnd; + nPos = iter-iterBegin; + } + --iter; + --nPos; + } + return bSuccess; +} + + +void FmSearchEngine::BuildAndInsertFieldInfo(const Reference< css::container::XIndexAccess > & xAllFields, sal_Int32 nField) +{ + DBG_ASSERT( xAllFields.is() && ( nField >= 0 ) && ( nField < xAllFields->getCount() ), + "FmSearchEngine::BuildAndInsertFieldInfo: invalid field descriptor!" ); + + // the field itself + Reference< XInterface > xCurrentField; + xAllFields->getByIndex(nField) >>= xCurrentField; + + // From this I now know that it supports the DatabaseRecord service (I hope). + // For the FormatKey and the type I need the PropertySet. + Reference< css::beans::XPropertySet > xProperties(xCurrentField, UNO_QUERY_THROW); + + // build the FieldInfo for that + FieldInfo fiCurrent; + fiCurrent.xContents.set(xCurrentField, UNO_QUERY); + + // and memorize + m_arrUsedFields.insert(m_arrUsedFields.end(), fiCurrent); + +} + +OUString FmSearchEngine::FormatField(sal_Int32 nWhich) +{ + DBG_ASSERT(o3tl::make_unsigned(nWhich) < m_aControlTexts.size(), "FmSearchEngine::FormatField(sal_Int32) : invalid position !"); + DBG_ASSERT(m_aControlTexts[nWhich], "FmSearchEngine::FormatField(sal_Int32) : invalid object in array !"); + DBG_ASSERT(m_aControlTexts[nWhich]->getControl().is(), "FmSearchEngine::FormatField : invalid control !"); + + if (m_nCurrentFieldIndex != -1) + { + DBG_ASSERT((nWhich == 0) || (nWhich == m_nCurrentFieldIndex), "FmSearchEngine::FormatField : parameter nWhich is invalid"); + // analogous situation as below + nWhich = m_nCurrentFieldIndex; + } + + DBG_ASSERT((nWhich >= 0) && (o3tl::make_unsigned(nWhich) < m_aControlTexts.size()), + "FmSearchEngine::FormatField : invalid argument nWhich !"); + return m_aControlTexts[m_nCurrentFieldIndex == -1 ? nWhich : m_nCurrentFieldIndex]->getCurrentText(); +} + + +FmSearchEngine::SearchResult FmSearchEngine::SearchSpecial(bool _bSearchForNull, sal_Int32& nFieldPos, + FieldCollection::iterator& iterFieldLoop, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd) +{ + // memorize the start position + Any aStartMark; + try { aStartMark = m_xSearchCursor.getBookmark(); } + catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; } + FieldCollection::const_iterator iterInitialField = iterFieldLoop; + + + bool bFound(false); + bool bMovedAround(false); + do + { + Application::Reschedule( true ); + + // the content to be compared currently + iterFieldLoop->xContents->getString(); // needed for wasNull + bFound = _bSearchForNull == bool(iterFieldLoop->xContents->wasNull()); + if (bFound) + break; + + // next field (implicitly next record, if necessary) + if (!MoveField(nFieldPos, iterFieldLoop, iterBegin, iterEnd)) + { // When moving to the next field, something went wrong... + // Continuing is not possible, since the next time exactly the same + // will definitely go wrong again, thus abort. + // Before, however, so that the search continues at the current position: + try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); } + catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); } + m_iterPreviousLocField = iterFieldLoop; + // and leave + return SearchResult::Error; + } + + Any aCurrentBookmark; + try { aCurrentBookmark = m_xSearchCursor.getBookmark(); } + catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; } + + bMovedAround = EQUAL_BOOKMARKS(aStartMark, aCurrentBookmark) && (iterFieldLoop == iterInitialField); + + if (nFieldPos == 0) + // that is, I've moved to a new record + PropagateProgress(bMovedAround); + // if we moved to the starting position we don't have to propagate an 'overflow' message + // FS - 07.12.99 - 68530 + + // cancel requested? + if (CancelRequested()) + return SearchResult::Cancelled; + + } while (!bMovedAround); + + return bFound ? SearchResult::Found : SearchResult::NotFound; +} + + +FmSearchEngine::SearchResult FmSearchEngine::SearchWildcard(const OUString& strExpression, sal_Int32& nFieldPos, + FieldCollection::iterator& iterFieldLoop, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd) +{ + // memorize the start position + Any aStartMark; + try { aStartMark = m_xSearchCursor.getBookmark(); } + catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; } + FieldCollection::const_iterator iterInitialField = iterFieldLoop; + + WildCard aSearchExpression(strExpression); + + + bool bFound(false); + bool bMovedAround(false); + do + { + Application::Reschedule( true ); + + // the content to be compared currently + OUString sCurrentCheck; + if (m_bFormatter) + sCurrentCheck = FormatField(nFieldPos); + else + sCurrentCheck = iterFieldLoop->xContents->getString(); + + if (!GetCaseSensitive()) + // norm the string + sCurrentCheck = m_aCharacterClassficator.lowercase(sCurrentCheck); + + // now the test is easy... + bFound = aSearchExpression.Matches(sCurrentCheck); + + if (bFound) + break; + + // next field (implicitly next record, if necessary) + if (!MoveField(nFieldPos, iterFieldLoop, iterBegin, iterEnd)) + { // When moving to the next field, something went wrong... + // Continuing is not possible, since the next time exactly the same + // will definitely go wrong again, thus abort. + // Before, however, so that the search continues at the current position: + try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); } + catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); } + m_iterPreviousLocField = iterFieldLoop; + // and leave + return SearchResult::Error; + } + + Any aCurrentBookmark; + try { aCurrentBookmark = m_xSearchCursor.getBookmark(); } + catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; } + + bMovedAround = EQUAL_BOOKMARKS(aStartMark, aCurrentBookmark) && (iterFieldLoop == iterInitialField); + + if (nFieldPos == 0) + // that is, I've moved to a new record + PropagateProgress(bMovedAround); + // if we moved to the starting position we don't have to propagate an 'overflow' message + // FS - 07.12.99 - 68530 + + // cancel requested? + if (CancelRequested()) + return SearchResult::Cancelled; + + } while (!bMovedAround); + + return bFound ? SearchResult::Found : SearchResult::NotFound; +} + + +FmSearchEngine::SearchResult FmSearchEngine::SearchRegularApprox(const OUString& strExpression, sal_Int32& nFieldPos, + FieldCollection::iterator& iterFieldLoop, const FieldCollection::iterator& iterBegin, const FieldCollection::iterator& iterEnd) +{ + DBG_ASSERT(m_bLevenshtein || m_bRegular, + "FmSearchEngine::SearchRegularApprox : invalid search mode!"); + DBG_ASSERT(!m_bLevenshtein || !m_bRegular, + "FmSearchEngine::SearchRegularApprox : cannot search for regular expressions and similarities at the same time!"); + + // memorize start position + Any aStartMark; + try { aStartMark = m_xSearchCursor.getBookmark(); } + catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; } + FieldCollection::const_iterator iterInitialField = iterFieldLoop; + + // collect parameters + i18nutil::SearchOptions2 aParam; + aParam.AlgorithmType2 = m_bRegular ? SearchAlgorithms2::REGEXP : SearchAlgorithms2::APPROXIMATE; + aParam.searchFlag = 0; + aParam.transliterateFlags = GetTransliterationFlags(); + if ( !GetTransliteration() ) + { // if transliteration is not enabled, the only flags which matter are IGNORE_CASE and IGNORE_WIDTH + aParam.transliterateFlags &= TransliterationFlags::IGNORE_CASE | TransliterationFlags::IGNORE_WIDTH; + } + if (m_bLevenshtein) + { + if (m_bLevRelaxed) + aParam.searchFlag |= SearchFlags::LEV_RELAXED; + aParam.changedChars = m_nLevOther; + aParam.deletedChars = m_nLevShorter; + aParam.insertedChars = m_nLevLonger; + } + aParam.searchString = strExpression; + aParam.Locale = SvtSysLocale().GetLanguageTag().getLocale(); + ::utl::TextSearch aLocalEngine( aParam); + + + bool bFound = false; + bool bMovedAround(false); + do + { + Application::Reschedule( true ); + + // the content to be compared currently + OUString sCurrentCheck; + if (m_bFormatter) + sCurrentCheck = FormatField(nFieldPos); + else + sCurrentCheck = iterFieldLoop->xContents->getString(); + + // (don't care about case here, this is done by the TextSearch object, 'cause we passed our case parameter to it) + + sal_Int32 nStart = 0, nEnd = sCurrentCheck.getLength(); + bFound = aLocalEngine.SearchForward(sCurrentCheck, &nStart, &nEnd); + // it says 'forward' here, but that only refers to the search within + // sCurrentCheck, so it has nothing to do with the direction of my + // record migration (MoveField takes care of that) + + // check if the position is correct + if (bFound) + { + switch (m_nPosition) + { + case MATCHING_WHOLETEXT : + if (nEnd != sCurrentCheck.getLength()) + { + bFound = false; + break; + } + [[fallthrough]]; + case MATCHING_BEGINNING : + if (nStart != 0) + bFound = false; + break; + case MATCHING_END : + if (nEnd != sCurrentCheck.getLength()) + bFound = false; + break; + } + } + + if (bFound) // still? + break; + + // next field (implicitly next record, if necessary) + if (!MoveField(nFieldPos, iterFieldLoop, iterBegin, iterEnd)) + { // When moving to the next field, something went wrong... + // Continuing is not possible, since the next time exactly the same + // will definitely go wrong again, thus abort (without error + // notification, I expect it to be displayed in the Move). + // Before, however, so that the search continues at the current position: + try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); } + catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); } + m_iterPreviousLocField = iterFieldLoop; + // and leave + return SearchResult::Error; + } + + Any aCurrentBookmark; + try { aCurrentBookmark = m_xSearchCursor.getBookmark(); } + catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); return SearchResult::Error; } + bMovedAround = EQUAL_BOOKMARKS(aStartMark, aCurrentBookmark) && (iterFieldLoop == iterInitialField); + + if (nFieldPos == 0) + // that is, I've moved to a new record + PropagateProgress(bMovedAround); + // if we moved to the starting position we don't have to propagate an 'overflow' message + // FS - 07.12.99 - 68530 + + // cancel requested? + if (CancelRequested()) + return SearchResult::Cancelled; + + } while (!bMovedAround); + + return bFound ? SearchResult::Found : SearchResult::NotFound; +} + + +FmSearchEngine::FmSearchEngine(const Reference< XComponentContext >& _rxContext, + const Reference< XResultSet > & xCursor, const OUString& sVisibleFields, + const InterfaceArray& arrFields) + :m_xSearchCursor(xCursor) + ,m_aCharacterClassficator( _rxContext, SvtSysLocale().GetLanguageTag() ) + ,m_aStringCompare( _rxContext ) + ,m_nCurrentFieldIndex(-2) // -1 already has a meaning, so I take -2 for 'invalid' + ,m_xOriginalIterator(xCursor) + ,m_xClonedIterator(m_xOriginalIterator, true) + ,m_eSearchForType(SearchFor::String) + ,m_srResult(SearchResult::Found) + ,m_bSearchingCurrently(false) + ,m_bCancelAsynchRequest(false) + ,m_bFormatter(true) // this must be consistent with m_xSearchCursor, which is generally == m_xOriginalIterator + ,m_bForward(false) + ,m_bWildcard(false) + ,m_bRegular(false) + ,m_bLevenshtein(false) + ,m_bTransliteration(false) + ,m_bLevRelaxed(false) + ,m_nLevOther(0) + ,m_nLevShorter(0) + ,m_nLevLonger(0) + ,m_nPosition(MATCHING_ANYWHERE) + ,m_nTransliterationFlags(TransliterationFlags::NONE) +{ + + fillControlTexts(arrFields); + Init(sVisibleFields); +} + + +void FmSearchEngine::SetIgnoreWidthCJK(bool bSet) +{ + if (bSet) + m_nTransliterationFlags |= TransliterationFlags::IGNORE_WIDTH; + else + m_nTransliterationFlags &= ~TransliterationFlags::IGNORE_WIDTH; +} + + +bool FmSearchEngine::GetIgnoreWidthCJK() const +{ + return bool(m_nTransliterationFlags & TransliterationFlags::IGNORE_WIDTH); +} + + +void FmSearchEngine::SetCaseSensitive(bool bSet) +{ + if (bSet) + m_nTransliterationFlags &= ~TransliterationFlags::IGNORE_CASE; + else + m_nTransliterationFlags |= TransliterationFlags::IGNORE_CASE; +} + + +bool FmSearchEngine::GetCaseSensitive() const +{ + return !(m_nTransliterationFlags & TransliterationFlags::IGNORE_CASE); +} + + +void FmSearchEngine::fillControlTexts(const InterfaceArray& arrFields) +{ + m_aControlTexts.clear(); + Reference< XInterface > xCurrent; + for (const auto & rField : arrFields) + { + xCurrent = rField; + DBG_ASSERT(xCurrent.is(), "FmSearchEngine::fillControlTexts : invalid field interface !"); + // check which type of control this is + Reference< css::awt::XTextComponent > xAsText(xCurrent, UNO_QUERY); + if (xAsText.is()) + { + m_aControlTexts.emplace_back(new SimpleTextWrapper(xAsText)); + continue; + } + + Reference< css::awt::XListBox > xAsListBox(xCurrent, UNO_QUERY); + if (xAsListBox.is()) + { + m_aControlTexts.emplace_back(new ListBoxWrapper(xAsListBox)); + continue; + } + + Reference< css::awt::XCheckBox > xAsCheckBox(xCurrent, UNO_QUERY); + DBG_ASSERT(xAsCheckBox.is(), "FmSearchEngine::fillControlTexts : invalid field interface (no supported type) !"); + // we don't have any more options ... + m_aControlTexts.emplace_back(new CheckBoxWrapper(xAsCheckBox)); + } +} + + +void FmSearchEngine::Init(const OUString& sVisibleFields) +{ + // analyze the fields + // additionally, create the mapping: because the list of used columns can be shorter than the list + // of columns of the cursor, we need a mapping: "used column number n" -> "cursor column m" + m_arrFieldMapping.clear(); + + // important: The case of the columns does not need to be exact - for instance: + // - a user created a form which works on a table, for which the driver returns a column name "COLUMN" + // - the driver itself works case-insensitive with column names + // - a control in the form is bound to "column" - not the different case + // In such a scenario, the form and the field would work okay, but we here need to case for the different case + // explicitly + // #i8755# + + // so first of all, check if the database handles identifiers case sensitive + Reference< XConnection > xConn; + Reference< XDatabaseMetaData > xMeta; + Reference< XPropertySet > xCursorProps( IFACECAST( m_xSearchCursor ), UNO_QUERY ); + if ( xCursorProps.is() ) + { + try + { + xCursorProps->getPropertyValue( FM_PROP_ACTIVE_CONNECTION ) >>= xConn; + } + catch( const Exception& ) { /* silent this - will be asserted below */ } + } + if ( xConn.is() ) + xMeta = xConn->getMetaData(); + OSL_ENSURE( xMeta.is(), "FmSearchEngine::Init: very strange cursor (could not derive connection meta data from it)!" ); + + bool bCaseSensitiveIdentifiers = true; // assume case sensitivity + if ( xMeta.is() ) + bCaseSensitiveIdentifiers = xMeta->supportsMixedCaseQuotedIdentifiers(); + + // now that we have this information, we need a collator which is able to case (in)sensitivity compare strings + m_aStringCompare.loadDefaultCollator( SvtSysLocale().GetLanguageTag().getLocale(), + bCaseSensitiveIdentifiers ? 0 : css::i18n::CollatorOptions::CollatorOptions_IGNORE_CASE ); + + try + { + // the cursor can give me a record (as PropertySet), which supports the DatabaseRecord service + Reference< css::sdbcx::XColumnsSupplier > xSupplyCols(IFACECAST(m_xSearchCursor), UNO_QUERY); + DBG_ASSERT(xSupplyCols.is(), "FmSearchEngine::Init : invalid cursor (no columns supplier) !"); + Reference< css::container::XNameAccess > xAllFieldNames = xSupplyCols->getColumns(); + Sequence< OUString > seqFieldNames = xAllFieldNames->getElementNames(); + + OUString sCurrentField; + sal_Int32 nIndex = 0; + do + { + sCurrentField = sVisibleFields.getToken(0, ';' , nIndex); + + // search in the field collection + sal_Int32 nFoundIndex = -1; + auto pFieldName = std::find_if(seqFieldNames.begin(), seqFieldNames.end(), + [this, &sCurrentField](const OUString& rFieldName) { + return 0 == m_aStringCompare.compareString( rFieldName, sCurrentField ); }); + if (pFieldName != seqFieldNames.end()) + nFoundIndex = static_cast<sal_Int32>(std::distance(seqFieldNames.begin(), pFieldName)); + DBG_ASSERT(nFoundIndex != -1, "FmSearchEngine::Init : Invalid field name were given !"); + m_arrFieldMapping.push_back(nFoundIndex); + } + while ( nIndex >= 0 ); + } + catch (const Exception&) + { + OSL_FAIL("Exception occurred!"); + } + +} + + +void FmSearchEngine::SetFormatterUsing(bool bSet) +{ + if (m_bFormatter == bSet) + return; + m_bFormatter = bSet; + + // I did not use a formatter, but TextComponents -> the SearchIterator needs to be adjusted + try + { + if (m_bFormatter) + { + DBG_ASSERT(m_xSearchCursor == m_xClonedIterator, "FmSearchEngine::SetFormatterUsing : inconsistent state !"); + m_xSearchCursor = m_xOriginalIterator; + m_xSearchCursor.moveToBookmark(m_xClonedIterator.getBookmark()); + // so that I continue with the new iterator at the actual place where I previously stopped + } + else + { + DBG_ASSERT(m_xSearchCursor == m_xOriginalIterator, "FmSearchEngine::SetFormatterUsing : inconsistent state !"); + m_xSearchCursor = m_xClonedIterator; + m_xSearchCursor.moveToBookmark(m_xOriginalIterator.getBookmark()); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + // I have to re-bind the fields, because the text exchange might take + // place over these fields and the underlying cursor has changed + RebuildUsedFields(m_nCurrentFieldIndex, true); +} + + +void FmSearchEngine::PropagateProgress(bool _bDontPropagateOverflow) +{ + if (m_aProgressHandler.IsSet()) + { + FmSearchProgress aProgress; + try + { + aProgress.aSearchState = FmSearchProgress::State::Progress; + aProgress.nCurrentRecord = m_xSearchCursor.getRow() - 1; + if (m_bForward) + aProgress.bOverflow = !_bDontPropagateOverflow && m_xSearchCursor.isFirst(); + else + aProgress.bOverflow = !_bDontPropagateOverflow && m_xSearchCursor.isLast(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + m_aProgressHandler.Call(&aProgress); + } +} + + +void FmSearchEngine::SearchNextImpl() +{ + DBG_ASSERT(!(m_bWildcard && m_bRegular) && !(m_bRegular && m_bLevenshtein) && !(m_bLevenshtein && m_bWildcard), + "FmSearchEngine::SearchNextImpl : search parameters are mutually exclusive!"); + + DBG_ASSERT(m_xSearchCursor.is(), "FmSearchEngine::SearchNextImpl : have invalid iterator!"); + + // the parameters of the search + OUString strSearchExpression(m_strSearchExpression); // I need non-const + if (!GetCaseSensitive()) + // norm the string + strSearchExpression = m_aCharacterClassficator.lowercase(strSearchExpression); + + if (!m_bRegular && !m_bLevenshtein) + { // 'normal' search I run through WildCards in any case, but must before adjust the OUString depending on the mode + + if (!m_bWildcard) + { // since in all other cases * and ? in the search string are of course + // also allowed, but should not count as WildCards, I need to normalize + OUString aTmp(strSearchExpression); + const OUString s_sStar("\\*"); + const OUString s_sQuotation("\\?"); + aTmp = aTmp.replaceAll("*", s_sStar); + aTmp = aTmp.replaceAll("?", s_sQuotation); + strSearchExpression = aTmp; + + switch (m_nPosition) + { + case MATCHING_ANYWHERE : + strSearchExpression = "*" + strSearchExpression + "*"; + break; + case MATCHING_BEGINNING : + strSearchExpression += "*"; + break; + case MATCHING_END : + strSearchExpression = "*" + strSearchExpression; + break; + case MATCHING_WHOLETEXT : + break; + default : + OSL_FAIL("FmSearchEngine::SearchNextImpl() : the methods listbox may contain only 4 entries ..."); + } + } + } + + // for work on field list + FieldCollection::iterator iterBegin = m_arrUsedFields.begin(); + FieldCollection::iterator iterEnd = m_arrUsedFields.end(); + FieldCollection::iterator iterFieldCheck; + + sal_Int32 nFieldPos; + + if (m_aPreviousLocBookmark.hasValue()) + { + DBG_ASSERT(EQUAL_BOOKMARKS(m_aPreviousLocBookmark, m_xSearchCursor.getBookmark()), + "FmSearchEngine::SearchNextImpl : invalid position!"); + iterFieldCheck = m_iterPreviousLocField; + // continue in the field after (or before) the last discovery + nFieldPos = iterFieldCheck - iterBegin; + MoveField(nFieldPos, iterFieldCheck, iterBegin, iterEnd); + } + else + { + if (m_bForward) + iterFieldCheck = iterBegin; + else + { + iterFieldCheck = iterEnd; + --iterFieldCheck; + } + nFieldPos = iterFieldCheck - iterBegin; + } + + PropagateProgress(true); + SearchResult srResult; + if (m_eSearchForType != SearchFor::String) + srResult = SearchSpecial(m_eSearchForType == SearchFor::Null, nFieldPos, iterFieldCheck, iterBegin, iterEnd); + else if (!m_bRegular && !m_bLevenshtein) + srResult = SearchWildcard(strSearchExpression, nFieldPos, iterFieldCheck, iterBegin, iterEnd); + else + srResult = SearchRegularApprox(strSearchExpression, nFieldPos, iterFieldCheck, iterBegin, iterEnd); + + m_srResult = srResult; + + if (SearchResult::Error == m_srResult) + return; + + // found? + if (SearchResult::Found == m_srResult) + { + // memorize the position + try { m_aPreviousLocBookmark = m_xSearchCursor.getBookmark(); } + catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("svx"); } + m_iterPreviousLocField = iterFieldCheck; + } + else + // invalidate the "last discovery" + InvalidatePreviousLoc(); +} + + +void FmSearchEngine::OnSearchTerminated() +{ + if (!m_aProgressHandler.IsSet()) + return; + + FmSearchProgress aProgress; + try + { + switch (m_srResult) + { + case SearchResult::Error : + aProgress.aSearchState = FmSearchProgress::State::Error; + break; + case SearchResult::Found : + aProgress.aSearchState = FmSearchProgress::State::Successful; + aProgress.aBookmark = m_aPreviousLocBookmark; + aProgress.nFieldIndex = m_iterPreviousLocField - m_arrUsedFields.begin(); + break; + case SearchResult::NotFound : + aProgress.aSearchState = FmSearchProgress::State::NothingFound; + aProgress.aBookmark = m_xSearchCursor.getBookmark(); + break; + case SearchResult::Cancelled : + aProgress.aSearchState = FmSearchProgress::State::Canceled; + aProgress.aBookmark = m_xSearchCursor.getBookmark(); + break; + } + aProgress.nCurrentRecord = m_xSearchCursor.getRow() - 1; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + // by definition, the link must be thread-safe (I just require that), + // so that I do not have to worry about such things here + m_aProgressHandler.Call(&aProgress); + + m_bSearchingCurrently = false; +} + + +IMPL_LINK(FmSearchEngine, OnNewRecordCount, sal_Int32, theCounter, void) +{ + if (!m_aProgressHandler.IsSet()) + return; + + FmSearchProgress aProgress; + aProgress.nCurrentRecord = theCounter; + aProgress.aSearchState = FmSearchProgress::State::ProgressCounting; + m_aProgressHandler.Call(&aProgress); +} + + +bool FmSearchEngine::CancelRequested() +{ + m_aCancelAsynchAccess.acquire(); + bool bReturn = m_bCancelAsynchRequest; + m_aCancelAsynchAccess.release(); + return bReturn; +} + + +void FmSearchEngine::CancelSearch() +{ + m_aCancelAsynchAccess.acquire(); + m_bCancelAsynchRequest = true; + m_aCancelAsynchAccess.release(); +} + + +void FmSearchEngine::SwitchToContext(const Reference< css::sdbc::XResultSet > & xCursor, const OUString& sVisibleFields, const InterfaceArray& arrFields, + sal_Int32 nFieldIndex) +{ + DBG_ASSERT(!m_bSearchingCurrently, "FmSearchEngine::SwitchToContext : please do not call while I'm searching !"); + if (m_bSearchingCurrently) + return; + + m_xSearchCursor = xCursor; + m_xOriginalIterator = xCursor; + m_xClonedIterator = CursorWrapper(m_xOriginalIterator, true); + + fillControlTexts(arrFields); + + Init(sVisibleFields); + RebuildUsedFields(nFieldIndex, true); +} + + +void FmSearchEngine::ImplStartNextSearch() +{ + m_bCancelAsynchRequest = false; + m_bSearchingCurrently = true; + + SearchNextImpl(); + OnSearchTerminated(); +} + + +void FmSearchEngine::SearchNext(const OUString& strExpression) +{ + m_strSearchExpression = strExpression; + m_eSearchForType = SearchFor::String; + ImplStartNextSearch(); +} + + +void FmSearchEngine::SearchNextSpecial(bool _bSearchForNull) +{ + m_eSearchForType = _bSearchForNull ? SearchFor::Null : SearchFor::NotNull; + ImplStartNextSearch(); +} + + +void FmSearchEngine::StartOver(const OUString& strExpression) +{ + try + { + if (m_bForward) + m_xSearchCursor.first(); + else + m_xSearchCursor.last(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + return; + } + + InvalidatePreviousLoc(); + SearchNext(strExpression); +} + + +void FmSearchEngine::StartOverSpecial(bool _bSearchForNull) +{ + try + { + if (m_bForward) + m_xSearchCursor.first(); + else + m_xSearchCursor.last(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + return; + } + + InvalidatePreviousLoc(); + SearchNextSpecial(_bSearchForNull); +} + + +void FmSearchEngine::InvalidatePreviousLoc() +{ + m_aPreviousLocBookmark.clear(); + m_iterPreviousLocField = m_arrUsedFields.end(); +} + + +void FmSearchEngine::RebuildUsedFields(sal_Int32 nFieldIndex, bool bForce) +{ + if (!bForce && (nFieldIndex == m_nCurrentFieldIndex)) + return; + // (since I allow no change of the iterator from the outside, the same css::sdbcx::Index + // also always means the same column, so I have nothing to do) + + DBG_ASSERT((nFieldIndex == -1) || + ((nFieldIndex >= 0) && + (o3tl::make_unsigned(nFieldIndex) < m_arrFieldMapping.size())), + "FmSearchEngine::RebuildUsedFields : nFieldIndex is invalid!"); + // collect all fields I need to search through + m_arrUsedFields.clear(); + if (nFieldIndex == -1) + { + Reference< css::container::XIndexAccess > xFields; + for (sal_Int32 i : m_arrFieldMapping) + { + Reference< css::sdbcx::XColumnsSupplier > xSupplyCols(IFACECAST(m_xSearchCursor), UNO_QUERY); + DBG_ASSERT(xSupplyCols.is(), "FmSearchEngine::RebuildUsedFields : invalid cursor (no columns supplier) !"); + xFields.set(xSupplyCols->getColumns(), UNO_QUERY); + BuildAndInsertFieldInfo(xFields, i); + } + } + else + { + Reference< css::container::XIndexAccess > xFields; + Reference< css::sdbcx::XColumnsSupplier > xSupplyCols(IFACECAST(m_xSearchCursor), UNO_QUERY); + DBG_ASSERT(xSupplyCols.is(), "FmSearchEngine::RebuildUsedFields : invalid cursor (no columns supplier) !"); + xFields.set (xSupplyCols->getColumns(), UNO_QUERY); + BuildAndInsertFieldInfo(xFields, m_arrFieldMapping[static_cast< size_t >(nFieldIndex)]); + } + + m_nCurrentFieldIndex = nFieldIndex; + // and of course I start the next search in a virgin state again + InvalidatePreviousLoc(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmtextcontroldialogs.cxx b/svx/source/form/fmtextcontroldialogs.cxx new file mode 100644 index 000000000..0c7c2a7de --- /dev/null +++ b/svx/source/form/fmtextcontroldialogs.cxx @@ -0,0 +1,79 @@ +/* -*- 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 <fmtextcontroldialogs.hxx> +#include <svx/dialogs.hrc> +#include <svx/svxids.hrc> + +#include <svx/flagsdef.hxx> +#include <svl/intitem.hxx> + +#include <svl/cjkoptions.hxx> + + +namespace svx +{ + + TextControlCharAttribDialog::TextControlCharAttribDialog(weld::Window* pParent, const SfxItemSet& rCoreSet, const SvxFontListItem& rFontList) + : SfxTabDialogController(pParent, "svx/ui/textcontrolchardialog.ui", "TextControlCharacterPropertiesDialog", &rCoreSet) + , m_aFontList(rFontList) + { + AddTabPage("font", RID_SVXPAGE_CHAR_NAME); + AddTabPage("fonteffects", RID_SVXPAGE_CHAR_EFFECTS); + AddTabPage("position", RID_SVXPAGE_CHAR_POSITION); + } + + void TextControlCharAttribDialog::PageCreated(const OString& rId, SfxTabPage& rPage) + { + SfxAllItemSet aSet(*(GetInputSetImpl()->GetPool())); + + if (rId == "font") + { + aSet.Put (m_aFontList); + rPage.PageCreated(aSet); + } + else if (rId == "fonteffects") + { + aSet.Put (SfxUInt16Item(SID_DISABLE_CTL,DISABLE_CASEMAP)); + rPage.PageCreated(aSet); + } + else if (rId == "position") + { + aSet.Put( SfxUInt32Item(SID_FLAG_TYPE, SVX_PREVIEW_CHARACTER) ); + rPage.PageCreated(aSet); + } + } + + TextControlParaAttribDialog::TextControlParaAttribDialog(weld::Window* pParent, const SfxItemSet& rCoreSet) + : SfxTabDialogController(pParent, "svx/ui/textcontrolparadialog.ui", "TextControlParagraphPropertiesDialog", &rCoreSet) + { + AddTabPage("labelTP_PARA_STD", RID_SVXPAGE_STD_PARAGRAPH); + AddTabPage("labelTP_PARA_ALIGN", RID_SVXPAGE_ALIGN_PARAGRAPH); + + SvtCJKOptions aCJKOptions; + if( aCJKOptions.IsAsianTypographyEnabled() ) + AddTabPage("labelTP_PARA_ASIAN", RID_SVXPAGE_PARA_ASIAN); + else + RemoveTabPage("labelTP_PARA_ASIAN"); + + AddTabPage("labelTP_TABULATOR", RID_SVXPAGE_TABULATOR); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmtextcontrolfeature.cxx b/svx/source/form/fmtextcontrolfeature.cxx new file mode 100644 index 000000000..68bb8606a --- /dev/null +++ b/svx/source/form/fmtextcontrolfeature.cxx @@ -0,0 +1,118 @@ +/* -*- 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 <fmtextcontrolfeature.hxx> +#include <fmtextcontrolshell.hxx> + +#include <osl/diagnose.h> +#include <tools/diagnose_ex.h> + +namespace svx +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::frame; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::util; + + FmTextControlFeature::FmTextControlFeature( const Reference< XDispatch >& _rxDispatcher, const URL& _rFeatureURL, SfxSlotId _nSlotId, FmTextControlShell* _pInvalidator ) + :m_xDispatcher ( _rxDispatcher ) + ,m_aFeatureURL ( _rFeatureURL ) + ,m_nSlotId ( _nSlotId ) + ,m_pInvalidator ( _pInvalidator ) + ,m_bFeatureEnabled( false ) + { + OSL_ENSURE( _rxDispatcher.is(), "FmTextControlFeature::FmTextControlFeature: invalid dispatcher!" ); + OSL_ENSURE( m_nSlotId, "FmTextControlFeature::FmTextControlFeature: invalid slot id!" ); + OSL_ENSURE( m_pInvalidator, "FmTextControlFeature::FmTextControlFeature: invalid invalidator!" ); + + osl_atomic_increment( &m_refCount ); + try + { + m_xDispatcher->addStatusListener( this, m_aFeatureURL ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "FmTextControlFeature::FmTextControlFeature" ); + } + osl_atomic_decrement( &m_refCount ); + } + + + FmTextControlFeature::~FmTextControlFeature( ) + { + } + + + void FmTextControlFeature::dispatch() const + { + dispatch( Sequence< PropertyValue >( ) ); + } + + + void FmTextControlFeature::dispatch( const Sequence< PropertyValue >& _rArgs ) const + { + try + { + if ( m_xDispatcher.is() ) + m_xDispatcher->dispatch( m_aFeatureURL, _rArgs ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "FmTextControlFeature::dispatch" ); + } + } + + + void SAL_CALL FmTextControlFeature::statusChanged( const FeatureStateEvent& _rState ) + { + m_aFeatureState = _rState.State; + m_bFeatureEnabled = _rState.IsEnabled; + + if ( m_pInvalidator ) + m_pInvalidator->Invalidate( m_nSlotId ); + } + + + void SAL_CALL FmTextControlFeature::disposing( const EventObject& /*Source*/ ) + { + // nothing to do + } + + + void FmTextControlFeature::dispose() + { + try + { + m_xDispatcher->removeStatusListener( this, m_aFeatureURL ); + m_xDispatcher.clear(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "FmTextControlFeature::dispose" ); + } + } + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmtextcontrolshell.cxx b/svx/source/form/fmtextcontrolshell.cxx new file mode 100644 index 000000000..1177e6eb3 --- /dev/null +++ b/svx/source/form/fmtextcontrolshell.cxx @@ -0,0 +1,1316 @@ +/* -*- 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 <fmprop.hxx> +#include <fmtextcontroldialogs.hxx> +#include <fmtextcontrolfeature.hxx> +#include <fmtextcontrolshell.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/editeng.hxx> +#include <editeng/scriptspaceitem.hxx> +#include <svx/svxids.hrc> +#include <editeng/udlnitem.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/awt/XFocusListener.hpp> +#include <com/sun/star/awt/XMouseListener.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/util/URLTransformer.hpp> + +#include <comphelper/processfactory.hxx> +#include <cppuhelper/implbase.hxx> +#include <sfx2/app.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/msgpool.hxx> +#include <sfx2/msg.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/request.hxx> +#include <sfx2/sfxuno.hxx> +#include <sfx2/viewfrm.hxx> +#include <svl/eitem.hxx> +#include <svl/itempool.hxx> +#include <svl/languageoptions.hxx> +#include <svtools/stringtransfer.hxx> +#include <svl/whiter.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <sal/log.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> + +#include <memory> + + +namespace svx +{ + + + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::form; + using namespace ::com::sun::star::form::runtime; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::frame; + using namespace ::com::sun::star::util; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::container; + + + typedef sal_uInt16 WhichId; + + + static SfxSlotId pTextControlSlots[] = + { + SID_CLIPBOARD_FORMAT_ITEMS, + SID_CUT, + SID_COPY, + SID_PASTE, + SID_SELECTALL, +// SID_ATTR_TABSTOP, /* 2 */ + SID_ATTR_CHAR_FONT, + SID_ATTR_CHAR_POSTURE, + SID_ATTR_CHAR_WEIGHT, + SID_ATTR_CHAR_SHADOWED, + SID_ATTR_CHAR_WORDLINEMODE, + SID_ATTR_CHAR_CONTOUR, + SID_ATTR_CHAR_STRIKEOUT, + SID_ATTR_CHAR_UNDERLINE, + SID_ATTR_CHAR_FONTHEIGHT, + SID_ATTR_CHAR_COLOR, + SID_ATTR_CHAR_KERNING, + SID_ATTR_CHAR_LANGUAGE, /* 20 */ + SID_ATTR_CHAR_ESCAPEMENT, + SID_ATTR_PARA_ADJUST, /* 28 */ + SID_ATTR_PARA_ADJUST_LEFT, + SID_ATTR_PARA_ADJUST_RIGHT, + SID_ATTR_PARA_ADJUST_CENTER, + SID_ATTR_PARA_ADJUST_BLOCK, + SID_ATTR_PARA_LINESPACE, /* 33 */ + SID_ATTR_PARA_LINESPACE_10, + SID_ATTR_PARA_LINESPACE_15, + SID_ATTR_PARA_LINESPACE_20, + SID_ATTR_LRSPACE, /* 48 */ + SID_ATTR_ULSPACE, /* 49 */ + SID_ATTR_CHAR_AUTOKERN, + SID_SET_SUPER_SCRIPT, + SID_SET_SUB_SCRIPT, + SID_CHAR_DLG, + SID_PARA_DLG, +// SID_TEXTDIRECTION_LEFT_TO_RIGHT, /* 907 */ +// SID_TEXTDIRECTION_TOP_TO_BOTTOM, + SID_ATTR_CHAR_SCALEWIDTH, /* 911 */ + SID_ATTR_CHAR_RELIEF, + SID_ATTR_PARA_LEFT_TO_RIGHT, /* 950 */ + SID_ATTR_PARA_RIGHT_TO_LEFT, + SID_ATTR_CHAR_OVERLINE, + 0 + }; + + // slots which we are not responsible for on the SfxShell level, but + // need to handle during the "paragraph attributes" and/or "character + // attributes" dialogs + static SfxSlotId pDialogSlots[] = + { + SID_ATTR_TABSTOP, + SID_ATTR_PARA_HANGPUNCTUATION, + SID_ATTR_PARA_FORBIDDEN_RULES, + SID_ATTR_PARA_SCRIPTSPACE, + SID_ATTR_CHAR_LATIN_LANGUAGE, + SID_ATTR_CHAR_CJK_LANGUAGE, + SID_ATTR_CHAR_CTL_LANGUAGE, + SID_ATTR_CHAR_LATIN_FONT, + SID_ATTR_CHAR_CJK_FONT, + SID_ATTR_CHAR_CTL_FONT, + SID_ATTR_CHAR_LATIN_FONTHEIGHT, + SID_ATTR_CHAR_CJK_FONTHEIGHT, + SID_ATTR_CHAR_CTL_FONTHEIGHT, + SID_ATTR_CHAR_LATIN_WEIGHT, + SID_ATTR_CHAR_CJK_WEIGHT, + SID_ATTR_CHAR_CTL_WEIGHT, + SID_ATTR_CHAR_LATIN_POSTURE, + SID_ATTR_CHAR_CJK_POSTURE, + SID_ATTR_CHAR_CTL_POSTURE, + SID_ATTR_CHAR_EMPHASISMARK, + 0 + }; + + typedef ::cppu::WeakImplHelper < css::awt::XFocusListener + > FmFocusListenerAdapter_Base; + class FmFocusListenerAdapter : public FmFocusListenerAdapter_Base + { + private: + IFocusObserver* m_pObserver; + Reference< css::awt::XWindow > m_xWindow; + + public: + FmFocusListenerAdapter( const Reference< css::awt::XControl >& _rxControl, IFocusObserver* _pObserver ); + + // clean up the instance + void dispose(); + + protected: + virtual ~FmFocusListenerAdapter() override; + + protected: + virtual void SAL_CALL focusGained( const css::awt::FocusEvent& e ) override; + virtual void SAL_CALL focusLost( const css::awt::FocusEvent& e ) override; + virtual void SAL_CALL disposing( const EventObject& Source ) override; + }; + + + FmFocusListenerAdapter::FmFocusListenerAdapter( const Reference< css::awt::XControl >& _rxControl, IFocusObserver* _pObserver ) + :m_pObserver( _pObserver ) + ,m_xWindow( _rxControl, UNO_QUERY ) + { + + DBG_ASSERT( m_xWindow.is(), "FmFocusListenerAdapter::FmFocusListenerAdapter: invalid control!" ); + osl_atomic_increment( &m_refCount ); + { + try + { + if ( m_xWindow.is() ) + m_xWindow->addFocusListener( this ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + osl_atomic_decrement( &m_refCount ); + } + + + FmFocusListenerAdapter::~FmFocusListenerAdapter() + { + acquire(); + dispose(); + + } + + + void FmFocusListenerAdapter::dispose() + { + if ( m_xWindow.is() ) + { + m_xWindow->removeFocusListener( this ); + m_xWindow.clear(); + } + } + + + void SAL_CALL FmFocusListenerAdapter::focusGained( const css::awt::FocusEvent& e ) + { + if ( m_pObserver ) + m_pObserver->focusGained( e ); + } + + + void SAL_CALL FmFocusListenerAdapter::focusLost( const css::awt::FocusEvent& e ) + { + if ( m_pObserver ) + m_pObserver->focusLost( e ); + } + + + void SAL_CALL FmFocusListenerAdapter::disposing( const EventObject& Source ) + { + DBG_ASSERT( Source.Source == m_xWindow, "FmFocusListenerAdapter::disposing: where did this come from?" ); + m_xWindow.clear(); + } + + typedef ::cppu::WeakImplHelper < css::awt::XMouseListener + > FmMouseListenerAdapter_Base; + class FmMouseListenerAdapter : public FmMouseListenerAdapter_Base + { + private: + IContextRequestObserver* m_pObserver; + Reference< css::awt::XWindow > m_xWindow; + + public: + FmMouseListenerAdapter( const Reference< css::awt::XControl >& _rxControl, IContextRequestObserver* _pObserver ); + + // clean up the instance + void dispose(); + + protected: + virtual ~FmMouseListenerAdapter() override; + + protected: + virtual void SAL_CALL mousePressed( const css::awt::MouseEvent& e ) override; + virtual void SAL_CALL mouseReleased( const css::awt::MouseEvent& e ) override; + virtual void SAL_CALL mouseEntered( const css::awt::MouseEvent& e ) override; + virtual void SAL_CALL mouseExited( const css::awt::MouseEvent& e ) override; + virtual void SAL_CALL disposing( const EventObject& Source ) override; + }; + + FmMouseListenerAdapter::FmMouseListenerAdapter( const Reference< css::awt::XControl >& _rxControl, IContextRequestObserver* _pObserver ) + :m_pObserver( _pObserver ) + ,m_xWindow( _rxControl, UNO_QUERY ) + { + + DBG_ASSERT( m_xWindow.is(), "FmMouseListenerAdapter::FmMouseListenerAdapter: invalid control!" ); + osl_atomic_increment( &m_refCount ); + { + try + { + if ( m_xWindow.is() ) + m_xWindow->addMouseListener( this ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + osl_atomic_decrement( &m_refCount ); + } + + + FmMouseListenerAdapter::~FmMouseListenerAdapter() + { + acquire(); + dispose(); + + } + + + void FmMouseListenerAdapter::dispose() + { + if ( m_xWindow.is() ) + { + m_xWindow->removeMouseListener( this ); + m_xWindow.clear(); + } + } + + + void SAL_CALL FmMouseListenerAdapter::mousePressed( const css::awt::MouseEvent& _rEvent ) + { + SolarMutexGuard aGuard; + // is this a request for a context menu? + if ( _rEvent.PopupTrigger ) + { + if ( m_pObserver ) + m_pObserver->contextMenuRequested(); + } + } + + + void SAL_CALL FmMouseListenerAdapter::mouseReleased( const css::awt::MouseEvent& /*e*/ ) + { + // not interested in + } + + + void SAL_CALL FmMouseListenerAdapter::mouseEntered( const css::awt::MouseEvent& /*e*/ ) + { + // not interested in + } + + + void SAL_CALL FmMouseListenerAdapter::mouseExited( const css::awt::MouseEvent& /*e*/ ) + { + // not interested in + } + + + void SAL_CALL FmMouseListenerAdapter::disposing( const EventObject& Source ) + { + DBG_ASSERT( Source.Source == m_xWindow, "FmMouseListenerAdapter::disposing: where did this come from?" ); + m_xWindow.clear(); + } + + + //= FmTextControlShell + + + namespace + { + + void lcl_translateUnoStateToItem( SfxSlotId _nSlot, const Any& _rUnoState, SfxItemSet& _rSet ) + { + WhichId nWhich = _rSet.GetPool()->GetWhich( _nSlot ); + if ( !_rUnoState.hasValue() ) + { + if ( ( _nSlot != SID_CUT ) + && ( _nSlot != SID_COPY ) + && ( _nSlot != SID_PASTE ) + ) + { + _rSet.InvalidateItem( nWhich ); + } + } + else + { + switch ( _rUnoState.getValueType().getTypeClass() ) + { + case TypeClass_BOOLEAN: + { + bool bState = false; + _rUnoState >>= bState; + if ( _nSlot == SID_ATTR_PARA_SCRIPTSPACE ) + _rSet.Put( SvxScriptSpaceItem( bState, nWhich ) ); + else + _rSet.Put( SfxBoolItem( nWhich, bState ) ); + } + break; + + default: + { + Sequence< PropertyValue > aComplexState; + if ( _rUnoState >>= aComplexState ) + { + if ( !aComplexState.hasElements() ) + _rSet.InvalidateItem( nWhich ); + else + { + SfxAllItemSet aAllItems( _rSet ); + TransformParameters( _nSlot, aComplexState, aAllItems ); + const SfxPoolItem* pTransformed = aAllItems.GetItem( nWhich ); + OSL_ENSURE( pTransformed, "lcl_translateUnoStateToItem: non-empty parameter sequence leading to empty item?" ); + if ( pTransformed ) + _rSet.Put( *pTransformed ); + else + _rSet.InvalidateItem( nWhich ); + } + } + else + { + OSL_FAIL( "lcl_translateUnoStateToItem: invalid state!" ); + } + } + } + } + } + + + OUString lcl_getUnoSlotName( SfxSlotId _nSlotId ) + { + OUString sSlotUnoName; + + SfxSlotPool& rSlotPool = SfxSlotPool::GetSlotPool(); + const SfxSlot* pSlot = rSlotPool.GetSlot( _nSlotId ); + + const char* pAsciiUnoName = nullptr; + if ( pSlot ) + { + pAsciiUnoName = pSlot->GetUnoName(); + } + else + { + // some hard-coded slots, which do not have a UNO name at SFX level, but which + // we nevertheless need to transport via UNO mechanisms, so we need a name + switch ( _nSlotId ) + { + case SID_ATTR_PARA_HANGPUNCTUATION: pAsciiUnoName = "AllowHangingPunctuation"; break; + case SID_ATTR_PARA_FORBIDDEN_RULES: pAsciiUnoName = "ApplyForbiddenCharacterRules"; break; + case SID_ATTR_PARA_SCRIPTSPACE: pAsciiUnoName = "UseScriptSpacing"; break; + } + } + + if ( pAsciiUnoName ) + { + sSlotUnoName = ".uno:" + OUString::createFromAscii( pAsciiUnoName ); + } + else + { + SAL_WARN( "svx", "lcl_getUnoSlotName: invalid slot id, or invalid slot, or no UNO name! " + "(slot id: " << _nSlotId << ")"); + } + return sSlotUnoName; + } + + + bool lcl_determineReadOnly( const Reference< css::awt::XControl >& _rxControl ) + { + bool bIsReadOnlyModel = true; + try + { + Reference< XPropertySet > xModelProps; + if ( _rxControl.is() ) + xModelProps.set(_rxControl->getModel(), css::uno::UNO_QUERY); + Reference< XPropertySetInfo > xModelPropInfo; + if ( xModelProps.is() ) + xModelPropInfo = xModelProps->getPropertySetInfo(); + + if ( !xModelPropInfo.is() || !xModelPropInfo->hasPropertyByName( FM_PROP_READONLY ) ) + bIsReadOnlyModel = true; + else + { + bool bReadOnly = true; + xModelProps->getPropertyValue( FM_PROP_READONLY ) >>= bReadOnly; + bIsReadOnlyModel = bReadOnly; + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return bIsReadOnlyModel; + } + + + vcl::Window* lcl_getWindow( const Reference< css::awt::XControl >& _rxControl ) + { + vcl::Window* pWindow = nullptr; + try + { + Reference< css::awt::XWindowPeer > xControlPeer; + if ( _rxControl.is() ) + xControlPeer = _rxControl->getPeer(); + if ( xControlPeer.is() ) + pWindow = VCLUnoHelper::GetWindow( xControlPeer ).get(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + return pWindow; + } + + + bool lcl_isRichText( const Reference< css::awt::XControl >& _rxControl ) + { + if ( !_rxControl.is() ) + return false; + + bool bIsRichText = false; + try + { + Reference< XPropertySet > xModelProps( _rxControl->getModel(), UNO_QUERY ); + Reference< XPropertySetInfo > xPSI; + if ( xModelProps.is() ) + xPSI = xModelProps->getPropertySetInfo(); + OUString sRichTextPropertyName = "RichText"; + if ( xPSI.is() && xPSI->hasPropertyByName( sRichTextPropertyName ) ) + { + OSL_VERIFY( xModelProps->getPropertyValue( sRichTextPropertyName ) >>= bIsRichText ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return bIsRichText; + } + } + + + FmTextControlShell::FmTextControlShell( SfxViewFrame* _pFrame ) + :m_bActiveControl( false ) + ,m_bActiveControlIsReadOnly( true ) + ,m_bActiveControlIsRichText( false ) + ,m_pViewFrame( _pFrame ) + ,m_rBindings( _pFrame->GetBindings() ) + ,m_bNeedClipboardInvalidation( true ) + { + m_aClipboardInvalidation.SetInvokeHandler( LINK( this, FmTextControlShell, OnInvalidateClipboard ) ); + m_aClipboardInvalidation.SetTimeout( 200 ); + } + + + FmTextControlShell::~FmTextControlShell() + { + dispose(); + } + + + IMPL_LINK_NOARG( FmTextControlShell, OnInvalidateClipboard, Timer*, void ) + { + if ( m_bNeedClipboardInvalidation ) + { + SAL_INFO("svx.form", "invalidating clipboard slots" ); + m_rBindings.Invalidate( SID_CUT ); + m_rBindings.Invalidate( SID_COPY ); + m_rBindings.Invalidate( SID_PASTE ); + m_bNeedClipboardInvalidation = false; + } + } + + + void FmTextControlShell::transferFeatureStatesToItemSet( ControlFeatures& _rDispatchers, SfxAllItemSet& _rSet, bool _bTranslateLatin ) + { + SfxItemPool& rPool = *_rSet.GetPool(); + + for (const auto& rFeature : _rDispatchers) + { + SfxSlotId nSlotId( rFeature.first ); +#if OSL_DEBUG_LEVEL > 0 + OUString sUnoSlotName; + if ( SfxGetpApp() ) + sUnoSlotName = lcl_getUnoSlotName( nSlotId ); + else + sUnoSlotName = "<unknown>"; + OString sUnoSlotNameAscii = "\"" + + OString( sUnoSlotName.getStr(), sUnoSlotName.getLength(), RTL_TEXTENCODING_ASCII_US ) + + "\""; +#endif + + if ( _bTranslateLatin ) + { + // A rich text control offers a dispatcher for the "Font" slot/feature. + // Sadly, the semantics of the dispatches is that the feature "Font" depends + // on the current cursor position: If it's on latin text, it's the "latin font" + // which is set up at the control. If it's on CJK text, it's the "CJK font", and + // equivalent for "CTL font". + // The same holds for some other font related features/slots. + // Thus, we have separate dispatches for "Latin Font", "Latin Font Size", etc, + // which are only "virtual", in a sense that there exist no item with this id. + // So when we encounter such a dispatcher for, say, "Latin Font", we need to + // put an item into the set which has the "Font" id. + + switch ( nSlotId ) + { + case SID_ATTR_CHAR_LATIN_FONT: nSlotId = SID_ATTR_CHAR_FONT; break; + case SID_ATTR_CHAR_LATIN_FONTHEIGHT:nSlotId = SID_ATTR_CHAR_FONTHEIGHT; break; + case SID_ATTR_CHAR_LATIN_LANGUAGE: nSlotId = SID_ATTR_CHAR_LANGUAGE; break; + case SID_ATTR_CHAR_LATIN_POSTURE: nSlotId = SID_ATTR_CHAR_POSTURE; break; + case SID_ATTR_CHAR_LATIN_WEIGHT: nSlotId = SID_ATTR_CHAR_WEIGHT; break; + } + } + + WhichId nWhich = rPool.GetWhich( nSlotId ); + bool bIsInPool = rPool.IsInRange( nWhich ); + if ( bIsInPool ) + { +#if OSL_DEBUG_LEVEL > 0 + bool bFeatureIsEnabled = rFeature.second->isFeatureEnabled(); + OString sMessage = "found a feature state for " + sUnoSlotNameAscii; + if ( !bFeatureIsEnabled ) + sMessage += " (disabled)"; + SAL_INFO("svx.form", sMessage ); +#endif + + lcl_translateUnoStateToItem( nSlotId, rFeature.second->getFeatureState(), _rSet ); + } +#if OSL_DEBUG_LEVEL > 0 + else + { + SAL_WARN("svx.form", "found a feature state for " << sUnoSlotNameAscii << ", but could not translate it into an item!" ); + } +#endif + } + } + + + void FmTextControlShell::executeAttributeDialog( AttributeSet _eSet, SfxRequest& rReq ) + { + const SvxFontListItem* pFontList = dynamic_cast<const SvxFontListItem*>( m_pViewFrame->GetObjectShell()->GetItem( SID_ATTR_CHAR_FONTLIST ) ); + DBG_ASSERT( pFontList, "FmTextControlShell::executeAttributeDialog: no font list item!" ); + if ( !pFontList ) + return; + + SfxItemPool* pPool = EditEngine::CreatePool(); + pPool->FreezeIdRanges(); + std::unique_ptr< SfxItemSet > xPureItems( new SfxItemSet( *pPool ) ); + + // put the current states of the items into the set + std::unique_ptr<SfxAllItemSet> xCurrentItems( new SfxAllItemSet( *xPureItems ) ); + transferFeatureStatesToItemSet( m_aControlFeatures, *xCurrentItems, false ); + + // additional items, which we are not responsible for at the SfxShell level, + // but which need to be forwarded to the dialog, anyway + ControlFeatures aAdditionalFestures; + fillFeatureDispatchers( m_xActiveControl, pDialogSlots, aAdditionalFestures ); + transferFeatureStatesToItemSet( aAdditionalFestures, *xCurrentItems, true ); + + std::unique_ptr<SfxTabDialogController> xDialog; + if (_eSet == eCharAttribs) + xDialog = std::make_unique<TextControlCharAttribDialog>(rReq.GetFrameWeld(), *xCurrentItems, *pFontList); + else + xDialog = std::make_unique<TextControlParaAttribDialog>(rReq.GetFrameWeld(), *xCurrentItems); + if ( RET_OK == xDialog->run() ) + { + const SfxItemSet& rModifiedItems = *xDialog->GetOutputItemSet(); + for ( WhichId nWhich = pPool->GetFirstWhich(); nWhich <= pPool->GetLastWhich(); ++nWhich ) + { + if ( rModifiedItems.GetItemState( nWhich ) == SfxItemState::SET ) + { + SfxSlotId nSlotForItemSet = pPool->GetSlotId( nWhich ); + const SfxPoolItem* pModifiedItem = rModifiedItems.GetItem( nWhich ); + + + SfxSlotId nSlotForDispatcher = nSlotForItemSet; + switch ( nSlotForDispatcher ) + { + case SID_ATTR_CHAR_FONT: nSlotForDispatcher = SID_ATTR_CHAR_LATIN_FONT; break; + case SID_ATTR_CHAR_FONTHEIGHT:nSlotForDispatcher = SID_ATTR_CHAR_LATIN_FONTHEIGHT; break; + case SID_ATTR_CHAR_LANGUAGE: nSlotForDispatcher = SID_ATTR_CHAR_LATIN_LANGUAGE; break; + case SID_ATTR_CHAR_POSTURE: nSlotForDispatcher = SID_ATTR_CHAR_LATIN_POSTURE; break; + case SID_ATTR_CHAR_WEIGHT: nSlotForDispatcher = SID_ATTR_CHAR_LATIN_WEIGHT; break; + } + + // do we already have a dispatcher for this slot/feature? + ControlFeatures::const_iterator aFeaturePos = m_aControlFeatures.find( nSlotForDispatcher ); + bool bFound = aFeaturePos != m_aControlFeatures.end( ); + + if ( !bFound ) + { + aFeaturePos = aAdditionalFestures.find( nSlotForDispatcher ); + bFound = aFeaturePos != aAdditionalFestures.end( ); + } + + if ( bFound ) + { + Sequence< PropertyValue > aArgs; + // temporarily put the modified item into a "clean" set, + // and let TransformItems calc the respective UNO parameters + xPureItems->Put( *pModifiedItem ); + TransformItems( nSlotForItemSet, *xPureItems, aArgs ); + xPureItems->ClearItem( nWhich ); + + if ( ( nSlotForItemSet == SID_ATTR_PARA_HANGPUNCTUATION ) + || ( nSlotForItemSet == SID_ATTR_PARA_FORBIDDEN_RULES ) + || ( nSlotForItemSet == SID_ATTR_PARA_SCRIPTSPACE ) + ) + { + // these are no UNO slots, they need special handling since TransformItems cannot + // handle them + DBG_ASSERT( !aArgs.hasElements(), "FmTextControlShell::executeAttributeDialog: these are no UNO slots - are they?" ); + + const SfxBoolItem* pBoolItem = dynamic_cast<const SfxBoolItem*>( pModifiedItem ); + DBG_ASSERT( pBoolItem, "FmTextControlShell::executeAttributeDialog: no bool item?!" ); + if ( pBoolItem ) + { + aArgs.realloc( 1 ); + aArgs[ 0 ].Name = "Enable"; + aArgs[ 0 ].Value <<= pBoolItem->GetValue(); + } + } + + // dispatch this + aFeaturePos->second->dispatch( aArgs ); + } + #if OSL_DEBUG_LEVEL > 0 + else + { + OUString sUnoSlotName = lcl_getUnoSlotName( nSlotForItemSet ); + if ( sUnoSlotName.isEmpty() ) + sUnoSlotName = "unknown (no SfxSlot)"; + SAL_WARN( "svx", "FmTextControShell::executeAttributeDialog: Could not handle the following item:" + "\n SlotID: " << nSlotForItemSet + << "\n WhichID: " << nWhich + << "\n UNO name: " << sUnoSlotName ); + } + #endif + } + } + rReq.Done( rModifiedItems ); + } + + xDialog.reset(); + xCurrentItems.reset(); + xPureItems.reset(); + SfxItemPool::Free(pPool); + } + + + void FmTextControlShell::executeSelectAll( ) + { + try + { + if ( m_xActiveTextComponent.is() ) + { + sal_Int32 nTextLen = m_xActiveTextComponent->getText().getLength(); + m_xActiveTextComponent->setSelection( css::awt::Selection( 0, nTextLen ) ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + + void FmTextControlShell::executeClipboardSlot( SfxSlotId _nSlot ) + { + try + { + if ( m_xActiveTextComponent.is() ) + { + switch ( _nSlot ) + { + case SID_COPY: + case SID_CUT: + { + OUString sSelectedText( m_xActiveTextComponent->getSelectedText() ); + ::svt::OStringTransfer::CopyString( sSelectedText, lcl_getWindow( m_xActiveControl ) ); + if ( SID_CUT == _nSlot ) + { + css::awt::Selection aSelection( m_xActiveTextComponent->getSelection() ); + m_xActiveTextComponent->insertText( aSelection, OUString() ); + } + } + break; + case SID_PASTE: + { + OUString sClipboardContent; + OSL_VERIFY( ::svt::OStringTransfer::PasteString( sClipboardContent, lcl_getWindow( m_xActiveControl ) ) ); + css::awt::Selection aSelection( m_xActiveTextComponent->getSelection() ); + m_xActiveTextComponent->insertText( aSelection, sClipboardContent ); + } + break; + default: + OSL_FAIL( "FmTextControlShell::executeClipboardSlot: invalid slot!" ); + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + + void FmTextControlShell::ExecuteTextAttribute( SfxRequest& _rReq ) + { + SfxSlotId nSlot = _rReq.GetSlot(); + + ControlFeatures::const_iterator aFeaturePos = m_aControlFeatures.find( nSlot ); + if ( aFeaturePos == m_aControlFeatures.end() ) + { + // special slots + switch ( nSlot ) + { + case SID_CHAR_DLG: + executeAttributeDialog( eCharAttribs, _rReq ); + break; + + case SID_PARA_DLG: + executeAttributeDialog( eParaAttribs, _rReq ); + break; + + case SID_SELECTALL: + executeSelectAll(); + break; + + case SID_CUT: + case SID_COPY: + case SID_PASTE: + executeClipboardSlot( nSlot ); + break; + + default: + DBG_ASSERT( aFeaturePos != m_aControlFeatures.end(), "FmTextControShell::ExecuteTextAttribute: I have no such dispatcher, and cannot handle it at all!" ); + return; + } + } + else + { + // slots which are dispatched to the control + + switch ( nSlot ) + { + case SID_ATTR_CHAR_STRIKEOUT: + case SID_ATTR_CHAR_UNDERLINE: + case SID_ATTR_CHAR_OVERLINE: + { + SfxItemSet aToggled( *_rReq.GetArgs() ); + + lcl_translateUnoStateToItem( nSlot, aFeaturePos->second->getFeatureState(), aToggled ); + WhichId nWhich = aToggled.GetPool()->GetWhich( nSlot ); + const SfxPoolItem* pItem = aToggled.GetItem( nWhich ); + if ( ( SID_ATTR_CHAR_UNDERLINE == nSlot ) || ( SID_ATTR_CHAR_OVERLINE == nSlot ) ) + { + const SvxTextLineItem* pTextLine = dynamic_cast<const SvxTextLineItem*>( pItem ); + DBG_ASSERT( pTextLine, "FmTextControlShell::ExecuteTextAttribute: ooops - no underline/overline item!" ); + if ( pTextLine ) + { + FontLineStyle eTL = pTextLine->GetLineStyle(); + if ( SID_ATTR_CHAR_UNDERLINE == nSlot ) { + aToggled.Put( SvxUnderlineItem( eTL == LINESTYLE_SINGLE ? LINESTYLE_NONE : LINESTYLE_SINGLE, nWhich ) ); + } else { + aToggled.Put( SvxOverlineItem( eTL == LINESTYLE_SINGLE ? LINESTYLE_NONE : LINESTYLE_SINGLE, nWhich ) ); + } + } + } + else + { + const SvxCrossedOutItem* pCrossedOut = dynamic_cast<const SvxCrossedOutItem*>( pItem ); + DBG_ASSERT( pCrossedOut, "FmTextControlShell::ExecuteTextAttribute: ooops - no CrossedOut item!" ); + if ( pCrossedOut ) + { + FontStrikeout eFS = pCrossedOut->GetStrikeout(); + aToggled.Put( SvxCrossedOutItem( eFS == STRIKEOUT_SINGLE ? STRIKEOUT_NONE : STRIKEOUT_SINGLE, nWhich ) ); + } + } + + Sequence< PropertyValue > aArguments; + TransformItems( nSlot, aToggled, aArguments ); + aFeaturePos->second->dispatch( aArguments ); + } + break; + + case SID_ATTR_CHAR_FONTHEIGHT: + case SID_ATTR_CHAR_FONT: + case SID_ATTR_CHAR_POSTURE: + case SID_ATTR_CHAR_WEIGHT: + case SID_ATTR_CHAR_SHADOWED: + case SID_ATTR_CHAR_CONTOUR: + case SID_SET_SUPER_SCRIPT: + case SID_SET_SUB_SCRIPT: + { + const SfxItemSet* pArgs = _rReq.GetArgs(); + Sequence< PropertyValue > aArgs; + if ( pArgs ) + TransformItems( nSlot, *pArgs, aArgs ); + aFeaturePos->second->dispatch( aArgs ); + } + break; + + default: + if ( aFeaturePos->second->isFeatureEnabled() ) + aFeaturePos->second->dispatch(); + break; + } + } + _rReq.Done(); + } + + + void FmTextControlShell::GetTextAttributeState( SfxItemSet& _rSet ) + { + SfxWhichIter aIter( _rSet ); + sal_uInt16 nSlot = aIter.FirstWhich(); + while ( nSlot ) + { + if ( ( nSlot == SID_ATTR_PARA_LEFT_TO_RIGHT ) + || ( nSlot == SID_ATTR_PARA_RIGHT_TO_LEFT ) + ) + { + if ( !SvtLanguageOptions().IsCTLFontEnabled() ) + { + _rSet.DisableItem( nSlot ); + nSlot = aIter.NextWhich(); + continue; + } + } + + ControlFeatures::const_iterator aFeaturePos = m_aControlFeatures.find( nSlot ); + if ( aFeaturePos != m_aControlFeatures.end() ) + { + if ( aFeaturePos->second->isFeatureEnabled() ) + lcl_translateUnoStateToItem( nSlot, aFeaturePos->second->getFeatureState(), _rSet ); + else + _rSet.DisableItem( nSlot ); + } + else + { + bool bDisable = false; + + bool bNeedWriteableControl = false; + bool bNeedTextComponent = false; + bool bNeedSelection = false; + + switch ( nSlot ) + { + case SID_CHAR_DLG: + case SID_PARA_DLG: + bDisable |= m_aControlFeatures.empty(); + bNeedWriteableControl = true; + break; + + case SID_CUT: + bNeedSelection = true; + bNeedTextComponent = true; + bNeedWriteableControl = true; + SAL_INFO("svx.form", "need to invalidate again" ); + m_bNeedClipboardInvalidation = true; + break; + + case SID_PASTE: + { + vcl::Window* pActiveControlVCLWindow = lcl_getWindow( m_xActiveControl ); + if ( pActiveControlVCLWindow ) + { + TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromSystemClipboard( pActiveControlVCLWindow) ); + bDisable |= !aDataHelper.HasFormat( SotClipboardFormatId::STRING ); + } + else + bDisable = true; + + bNeedTextComponent = true; + bNeedWriteableControl = true; + } + break; + + case SID_COPY: + bNeedTextComponent = true; + bNeedSelection = true; + break; + + case SID_SELECTALL: + bNeedTextComponent = true; + break; + + default: + // slot is unknown at all + bDisable = true; + break; + } + SAL_WARN_IF( bNeedSelection && !bNeedTextComponent, "svx.form", "FmTextControlShell::GetTextAttributeState: bNeedSelection should imply bNeedTextComponent!" ); + + if ( !bDisable && bNeedWriteableControl ) + bDisable |= !IsActiveControl( ) || m_bActiveControlIsReadOnly; + + if ( !bDisable && bNeedTextComponent ) + bDisable |= !m_xActiveTextComponent.is(); + + if ( !bDisable && bNeedSelection ) + { + css::awt::Selection aSelection = m_xActiveTextComponent->getSelection(); + bDisable |= aSelection.Min == aSelection.Max; + } + + if ( bDisable ) + _rSet.DisableItem( nSlot ); + } + + nSlot = aIter.NextWhich(); + } + } + + + bool FmTextControlShell::IsActiveControl( bool _bCountRichTextOnly ) const + { + if ( _bCountRichTextOnly && !m_bActiveControlIsRichText ) + return false; + + return m_bActiveControl; + } + + + void FmTextControlShell::dispose() + { + if ( IsActiveControl() ) + controlDeactivated(); + if ( isControllerListening() ) + stopControllerListening(); + } + + + void FmTextControlShell::designModeChanged() + { + m_rBindings.Invalidate( pTextControlSlots ); + } + + + void FmTextControlShell::formActivated( const Reference< runtime::XFormController >& _rxController ) + { +#if OSL_DEBUG_LEVEL > 0 + SAL_INFO("svx.form", "0x" << OUString::number( reinterpret_cast<sal_IntPtr>(_rxController.get()), 16 )); +#endif + + DBG_ASSERT( _rxController.is(), "FmTextControlShell::formActivated: invalid controller!" ); + if ( !_rxController.is() ) + return; + + // sometimes, a form controller notifies activations, even if it's already activated + if ( m_xActiveController == _rxController ) + return; + + try + { + startControllerListening( _rxController ); + controlActivated( _rxController->getCurrentControl() ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + + void FmTextControlShell::formDeactivated( const Reference< runtime::XFormController >& _rxController ) + { + SAL_INFO("svx.form", "0x" << OUString::number( reinterpret_cast<sal_IntPtr>(_rxController.get()), 16 )); + + if ( IsActiveControl() ) + controlDeactivated(); + if ( isControllerListening() ) + stopControllerListening(); + } + + + void FmTextControlShell::startControllerListening( const Reference< runtime::XFormController >& _rxController ) + { + OSL_PRECOND( _rxController.is(), "FmTextControlShell::startControllerListening: invalid controller!" ); + if ( !_rxController.is() ) + return; + + OSL_PRECOND( !isControllerListening(), "FmTextControlShell::startControllerListening: already listening!" ); + if ( isControllerListening() ) + stopControllerListening( ); + DBG_ASSERT( !isControllerListening(), "FmTextControlShell::startControllerListening: inconsistence!" ); + + try + { + Sequence< Reference< css::awt::XControl > > aControls( _rxController->getControls() ); + m_aControlObservers.resize( 0 ); + m_aControlObservers.reserve( aControls.getLength() ); + + std::transform(aControls.begin(), aControls.end(), std::back_inserter(m_aControlObservers), + [this](const Reference< css::awt::XControl >& rControl) -> FocusListenerAdapter { + return FocusListenerAdapter( new FmFocusListenerAdapter( rControl, this ) ); }); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + m_xActiveController = _rxController; + } + + + void FmTextControlShell::stopControllerListening( ) + { + OSL_PRECOND( isControllerListening(), "FmTextControlShell::stopControllerListening: inconsistence!" ); + + // dispose all listeners associated with the controls of the active controller + for (auto& rpObserver : m_aControlObservers) + { + rpObserver->dispose(); + } + + FocusListenerAdapters aEmpty; + m_aControlObservers.swap( aEmpty ); + + m_xActiveController.clear(); + } + + + void FmTextControlShell::implClearActiveControlRef() + { + // no more features for this control + for (auto& rFeature : m_aControlFeatures) + { + rFeature.second->dispose(); + } + + ControlFeatures aEmpty; + m_aControlFeatures.swap( aEmpty ); + + if ( m_aContextMenuObserver ) + { + m_aContextMenuObserver->dispose(); + m_aContextMenuObserver = MouseListenerAdapter(); + } + + if ( m_xActiveTextComponent.is() ) + { + SAL_INFO("svx.form", "stopping timer for clipboard invalidation" ); + m_aClipboardInvalidation.Stop(); + } + // no more active control + m_xActiveControl.clear(); + m_xActiveTextComponent.clear(); + m_bActiveControlIsReadOnly = true; + m_bActiveControlIsRichText = false; + m_bActiveControl = false; + } + + + void FmTextControlShell::controlDeactivated( ) + { + DBG_ASSERT( IsActiveControl(), "FmTextControlShell::controlDeactivated: no active control!" ); + + m_bActiveControl = false; + + m_rBindings.Invalidate( pTextControlSlots ); + } + + + void FmTextControlShell::controlActivated( const Reference< css::awt::XControl >& _rxControl ) + { + // ensure that all knittings with the previously active control are lost + if ( m_xActiveControl.is() ) + implClearActiveControlRef(); + DBG_ASSERT( m_aControlFeatures.empty(), "FmTextControlShell::controlActivated: should have no dispatchers when I'm here!" ); + +#if OSL_DEBUG_LEVEL > 0 + { + Sequence< Reference< css::awt::XControl > > aActiveControls; + if ( m_xActiveController.is() ) + aActiveControls = m_xActiveController->getControls(); + + bool bFoundThisControl = false; + + const Reference< css::awt::XControl >* pControls = aActiveControls.getConstArray(); + const Reference< css::awt::XControl >* pControlsEnd = pControls + aActiveControls.getLength(); + for ( ; ( pControls != pControlsEnd ) && !bFoundThisControl; ++pControls ) + { + if ( *pControls == _rxControl ) + bFoundThisControl = true; + } + DBG_ASSERT( bFoundThisControl, "FmTextControlShell::controlActivated: only controls which belong to the active controller can be activated!" ); + } +#endif + // ask the control for dispatchers for our text-related slots + fillFeatureDispatchers( _rxControl, pTextControlSlots, m_aControlFeatures ); + + // remember this control + m_xActiveControl = _rxControl; + m_xActiveTextComponent.set(_rxControl, css::uno::UNO_QUERY); + m_bActiveControlIsReadOnly = lcl_determineReadOnly( m_xActiveControl ); + m_bActiveControlIsRichText = lcl_isRichText( m_xActiveControl ); + + // if we found a rich text control, we need context menu support + if ( m_bActiveControlIsRichText ) + { + DBG_ASSERT( nullptr == m_aContextMenuObserver.get(), "FmTextControlShell::controlActivated: already have an observer!" ); + m_aContextMenuObserver = MouseListenerAdapter( new FmMouseListenerAdapter( _rxControl, this ) ); + } + + if ( m_xActiveTextComponent.is() ) + { + SAL_INFO("svx.form", "starting timer for clipboard invalidation" ); + m_aClipboardInvalidation.Start(); + } + + m_bActiveControl = true; + + m_rBindings.Invalidate( pTextControlSlots ); + + if ( m_pViewFrame ) + m_pViewFrame->UIFeatureChanged(); + + // don't call the activation handler if we don't have any slots we can serve + // The activation handler is used to put the shell on the top of the dispatcher stack, + // so it's preferred when slots are distributed. + // Note that this is a slight hack, to prevent that we grab slots from the SfxDispatcher + // which should be served by other shells (e.g. Cut/Copy/Paste). + // A real solution would be a forwarding-mechanism for slots: We should be on the top + // if we're active, but if we cannot handle the slot, then we need to tell the dispatcher + // to skip our shell, and pass the slot to the next one. However, this mechanism is not + // not in place in SFX. + // Another possibility would be to have dedicated shells for the slots which we might + // or might not be able to serve. However, this could probably increase the number of + // shells too much (In theory, nearly every slot could have an own shell then). + + // #i51621# / 2005-08-19 / frank.schoenheit@sun.com + // bool bHaveAnyServeableSlots = m_xActiveTextComponent.is() || !m_aControlFeatures.empty(); + // LEM: not calling m_aControlActivatonHandler causes fdo#63695, so disable this hack for now. + m_aControlActivationHandler.Call( nullptr ); + + m_bNeedClipboardInvalidation = true; + } + + + void FmTextControlShell::fillFeatureDispatchers(const Reference< css::awt::XControl >& _rxControl, SfxSlotId* _pZeroTerminatedSlots, + ControlFeatures& _rDispatchers) + { + Reference< XDispatchProvider > xProvider( _rxControl, UNO_QUERY ); + SfxApplication* pApplication = SfxGetpApp(); + DBG_ASSERT( pApplication, "FmTextControlShell::fillFeatureDispatchers: no SfxApplication!" ); + if ( xProvider.is() && pApplication ) + { + SfxSlotId* pSlots = _pZeroTerminatedSlots; + while ( *pSlots ) + { + FmTextControlFeature* pDispatcher = implGetFeatureDispatcher( xProvider, pApplication, *pSlots ); + if ( pDispatcher ) + _rDispatchers.emplace( *pSlots, ControlFeature( pDispatcher ) ); + + ++pSlots; + } + } + } + + + FmTextControlFeature* FmTextControlShell::implGetFeatureDispatcher( const Reference< XDispatchProvider >& _rxProvider, SfxApplication const * _pApplication, SfxSlotId _nSlot ) + { + OSL_PRECOND( _rxProvider.is() && _pApplication, "FmTextControlShell::implGetFeatureDispatcher: invalid arg(s)!" ); + URL aFeatureURL; + aFeatureURL.Complete = lcl_getUnoSlotName( _nSlot ); + try + { + if ( !m_xURLTransformer.is() ) + { + m_xURLTransformer = util::URLTransformer::create( ::comphelper::getProcessComponentContext() ); + } + if ( m_xURLTransformer.is() ) + m_xURLTransformer->parseStrict( aFeatureURL ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + Reference< XDispatch > xDispatcher = _rxProvider->queryDispatch( aFeatureURL, OUString(), 0xFF ); + if ( xDispatcher.is() ) + return new FmTextControlFeature( xDispatcher, aFeatureURL, _nSlot, this ); + return nullptr; + } + + + void FmTextControlShell::Invalidate( SfxSlotId _nSlot ) + { + m_rBindings.Invalidate( _nSlot ); + // despite this method being called "Invalidate", we also update here - this gives more immediate + // feedback in the UI + m_rBindings.Update( _nSlot ); + } + + + void FmTextControlShell::focusGained( const css::awt::FocusEvent& _rEvent ) + { + Reference< css::awt::XControl > xControl( _rEvent.Source, UNO_QUERY ); + +#if OSL_DEBUG_LEVEL > 0 + SAL_INFO("svx.form", "0x" << OUString::number( reinterpret_cast<sal_IntPtr>(xControl.get()), 16 )); +#endif + + DBG_ASSERT( xControl.is(), "FmTextControlShell::focusGained: suspicious focus event!" ); + if ( xControl.is() ) + controlActivated( xControl ); + } + + + void FmTextControlShell::focusLost( const css::awt::FocusEvent& _rEvent ) + { + Reference< css::awt::XControl > xControl( _rEvent.Source, UNO_QUERY ); + +#if OSL_DEBUG_LEVEL > 0 + SAL_INFO("svx.form", "0x" << OUString::number( reinterpret_cast<sal_IntPtr>(xControl.get()), 16 )); +#endif + + m_bActiveControl = false; + } + + + void FmTextControlShell::ForgetActiveControl() + { + implClearActiveControlRef(); + } + + + void FmTextControlShell::contextMenuRequested() + { + m_rBindings.GetDispatcher()->ExecutePopup( "formrichtext" ); + } + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmtools.cxx b/svx/source/form/fmtools.cxx new file mode 100644 index 000000000..21df3d4b4 --- /dev/null +++ b/svx/source/form/fmtools.cxx @@ -0,0 +1,384 @@ +/* -*- 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 <fmprop.hxx> +#include <fmservs.hxx> +#include <svx/fmtools.hxx> +#include <svx/fmglob.hxx> + +#include <com/sun/star/awt/LineEndFormat.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/io/XPersistObject.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/sdb/ErrorCondition.hpp> +#include <com/sun/star/sdb/ErrorMessageDialog.hpp> +#include <com/sun/star/sdb/SQLContext.hpp> +#include <com/sun/star/sdb/SQLErrorEvent.hpp> +#include <com/sun/star/sdb/XCompletedConnection.hpp> +#include <com/sun/star/sdb/XResultSetAccess.hpp> +#include <com/sun/star/sdbc/XRowSet.hpp> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/util/Language.hpp> + +#include <comphelper/processfactory.hxx> +#include <comphelper/property.hxx> +#include <comphelper/types.hxx> +#include <osl/diagnose.h> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <vcl/svapp.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::ui::dialogs; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdbcx; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::form; +using namespace ::svxform; + + +namespace +{ + bool lcl_shouldDisplayError( const Any& _rError ) + { + SQLException aError; + if ( !( _rError >>= aError ) ) + return true; + + if ( ! aError.Message.startsWith( "[OOoBase]" ) ) + // it is an exception *not* thrown by an OOo Base core component + return true; + + // the only exception we do not display ATM is a RowSetVetoException, which + // has been raised because an XRowSetApprovalListener vetoed a change + if ( aError.ErrorCode + ErrorCondition::ROW_SET_OPERATION_VETOED == 0 ) + return false; + + // everything else is to be displayed + return true; + } +} + + +void displayException(const Any& _rExcept, vcl::Window* _pParent) +{ + // check whether we need to display it + if ( !lcl_shouldDisplayError( _rExcept ) ) + return; + + try + { + // the parent window + vcl::Window* pParentWindow = _pParent ? _pParent : Application::GetDefDialogParent(); + Reference< XWindow > xParentWindow = VCLUnoHelper::GetInterface(pParentWindow); + + Reference< XExecutableDialog > xErrorDialog = ErrorMessageDialog::create(::comphelper::getProcessComponentContext(), "", xParentWindow, _rExcept); + xErrorDialog->execute(); + } + catch(const Exception&) + { + OSL_FAIL("displayException: could not display the error message!"); + } +} + + +void displayException(const css::sdbc::SQLException& _rExcept, vcl::Window* _pParent) +{ + displayException(makeAny(_rExcept), _pParent); +} + + +void displayException(const css::sdb::SQLContext& _rExcept, vcl::Window* _pParent) +{ + displayException(makeAny(_rExcept), _pParent); +} + + +void displayException(const css::sdb::SQLErrorEvent& _rEvent, vcl::Window* _pParent) +{ + displayException(_rEvent.Reason, _pParent); +} + + +sal_Int32 getElementPos(const Reference< css::container::XIndexAccess>& xCont, const Reference< XInterface >& xElement) +{ + sal_Int32 nIndex = -1; + if (!xCont.is()) + return nIndex; + + + Reference< XInterface > xNormalized( xElement, UNO_QUERY ); + DBG_ASSERT( xNormalized.is(), "getElementPos: invalid element!" ); + if ( xNormalized.is() ) + { + // find child position + nIndex = xCont->getCount(); + while (nIndex--) + { + try + { + Reference< XInterface > xCurrent(xCont->getByIndex( nIndex ),UNO_QUERY); + DBG_ASSERT( xCurrent.get() == Reference< XInterface >( xCurrent, UNO_QUERY ).get(), + "getElementPos: container element not normalized!" ); + if ( xNormalized.get() == xCurrent.get() ) + break; + } + catch(Exception&) + { + TOOLS_WARN_EXCEPTION( "svx", "getElementPos" ); + } + + } + } + return nIndex; +} + + +OUString getLabelName(const Reference< css::beans::XPropertySet>& xControlModel) +{ + if (!xControlModel.is()) + return OUString(); + + if (::comphelper::hasProperty(FM_PROP_CONTROLLABEL, xControlModel)) + { + Reference< css::beans::XPropertySet> xLabelSet; + xControlModel->getPropertyValue(FM_PROP_CONTROLLABEL) >>= xLabelSet; + if (xLabelSet.is() && ::comphelper::hasProperty(FM_PROP_LABEL, xLabelSet)) + { + Any aLabel( xLabelSet->getPropertyValue(FM_PROP_LABEL) ); + if ((aLabel.getValueTypeClass() == TypeClass_STRING) && !::comphelper::getString(aLabel).isEmpty()) + return ::comphelper::getString(aLabel); + } + } + + return ::comphelper::getString(xControlModel->getPropertyValue(FM_PROP_CONTROLSOURCE)); +} + + +// = CursorWrapper + +CursorWrapper::CursorWrapper(const Reference< css::sdbc::XRowSet>& _rxCursor, bool bUseCloned) +{ + ImplConstruct(Reference< css::sdbc::XResultSet>(_rxCursor), bUseCloned); +} + + +CursorWrapper::CursorWrapper(const Reference< css::sdbc::XResultSet>& _rxCursor, bool bUseCloned) +{ + ImplConstruct(_rxCursor, bUseCloned); +} + + +void CursorWrapper::ImplConstruct(const Reference< css::sdbc::XResultSet>& _rxCursor, bool bUseCloned) +{ + if (bUseCloned) + { + Reference< css::sdb::XResultSetAccess> xAccess(_rxCursor, UNO_QUERY); + try + { + m_xMoveOperations = xAccess.is() ? xAccess->createResultSet() : Reference< css::sdbc::XResultSet>(); + } + catch(Exception&) + { + } + } + else + m_xMoveOperations = _rxCursor; + + m_xBookmarkOperations.set(m_xMoveOperations, css::uno::UNO_QUERY); + m_xColumnsSupplier.set(m_xMoveOperations, css::uno::UNO_QUERY); + m_xPropertyAccess.set(m_xMoveOperations, css::uno::UNO_QUERY); + + if ( !m_xMoveOperations.is() || !m_xBookmarkOperations.is() || !m_xColumnsSupplier.is() || !m_xPropertyAccess.is() ) + { // all or nothing !! + m_xMoveOperations = nullptr; + m_xBookmarkOperations = nullptr; + m_xColumnsSupplier = nullptr; + } + else + m_xGeneric = m_xMoveOperations.get(); +} + +CursorWrapper& CursorWrapper::operator=(const Reference< css::sdbc::XRowSet>& _rxCursor) +{ + m_xMoveOperations.set(_rxCursor); + m_xBookmarkOperations.set(_rxCursor, UNO_QUERY); + m_xColumnsSupplier.set(_rxCursor, UNO_QUERY); + if (!m_xMoveOperations.is() || !m_xBookmarkOperations.is() || !m_xColumnsSupplier.is()) + { // all or nothing !! + m_xMoveOperations = nullptr; + m_xBookmarkOperations = nullptr; + m_xColumnsSupplier = nullptr; + } + return *this; +} + +FmXDisposeListener::~FmXDisposeListener() +{ + setAdapter(nullptr); +} + +void FmXDisposeListener::setAdapter(FmXDisposeMultiplexer* pAdapter) +{ + ::osl::MutexGuard aGuard(m_aMutex); + m_pAdapter = pAdapter; +} + +FmXDisposeMultiplexer::FmXDisposeMultiplexer(FmXDisposeListener* _pListener, const Reference< css::lang::XComponent>& _rxObject) + :m_xObject(_rxObject) + ,m_pListener(_pListener) +{ + m_pListener->setAdapter(this); + + if (m_xObject.is()) + m_xObject->addEventListener(this); +} + +FmXDisposeMultiplexer::~FmXDisposeMultiplexer() +{ +} + +// css::lang::XEventListener + +void FmXDisposeMultiplexer::disposing(const css::lang::EventObject& /*Source*/) +{ + Reference< css::lang::XEventListener> xPreventDelete(this); + + if (m_pListener) + { + m_pListener->disposing(0); + m_pListener->setAdapter(nullptr); + m_pListener = nullptr; + } + m_xObject = nullptr; +} + + +void FmXDisposeMultiplexer::dispose() +{ + if (m_xObject.is()) + { + Reference< css::lang::XEventListener> xPreventDelete(this); + + m_xObject->removeEventListener(this); + m_xObject = nullptr; + + m_pListener->setAdapter(nullptr); + m_pListener = nullptr; + } +} + + +sal_Int16 getControlTypeByObject(const Reference< css::lang::XServiceInfo>& _rxObject) +{ + // ask for the persistent service name + Reference< css::io::XPersistObject> xPersistence(_rxObject, UNO_QUERY); + DBG_ASSERT(xPersistence.is(), "::getControlTypeByObject : argument should be a css::io::XPersistObject !"); + if (!xPersistence.is()) + return OBJ_FM_CONTROL; + + OUString sPersistentServiceName = xPersistence->getServiceName(); + if (sPersistentServiceName == FM_COMPONENT_EDIT) // 5.0-Name + { + // may be a simple edit field or a formatted field, dependent of the supported services + if (_rxObject->supportsService(FM_SUN_COMPONENT_FORMATTEDFIELD)) + return OBJ_FM_FORMATTEDFIELD; + return OBJ_FM_EDIT; + } + if (sPersistentServiceName == FM_COMPONENT_TEXTFIELD) + return OBJ_FM_EDIT; + if (sPersistentServiceName == FM_COMPONENT_COMMANDBUTTON) + return OBJ_FM_BUTTON; + if (sPersistentServiceName == FM_COMPONENT_FIXEDTEXT) + return OBJ_FM_FIXEDTEXT; + if (sPersistentServiceName == FM_COMPONENT_LISTBOX) + return OBJ_FM_LISTBOX; + if (sPersistentServiceName == FM_COMPONENT_CHECKBOX) + return OBJ_FM_CHECKBOX; + if (sPersistentServiceName == FM_COMPONENT_RADIOBUTTON) + return OBJ_FM_RADIOBUTTON; + if (sPersistentServiceName == FM_COMPONENT_GROUPBOX) + return OBJ_FM_GROUPBOX; + if (sPersistentServiceName == FM_COMPONENT_COMBOBOX) + return OBJ_FM_COMBOBOX; + if (sPersistentServiceName == FM_COMPONENT_GRID) // 5.0-Name + return OBJ_FM_GRID; + if (sPersistentServiceName == FM_COMPONENT_GRIDCONTROL) + return OBJ_FM_GRID; + if (sPersistentServiceName == FM_COMPONENT_IMAGEBUTTON) + return OBJ_FM_IMAGEBUTTON; + if (sPersistentServiceName == FM_COMPONENT_FILECONTROL) + return OBJ_FM_FILECONTROL; + if (sPersistentServiceName == FM_COMPONENT_DATEFIELD) + return OBJ_FM_DATEFIELD; + if (sPersistentServiceName == FM_COMPONENT_TIMEFIELD) + return OBJ_FM_TIMEFIELD; + if (sPersistentServiceName == FM_COMPONENT_NUMERICFIELD) + return OBJ_FM_NUMERICFIELD; + if (sPersistentServiceName == FM_COMPONENT_CURRENCYFIELD) + return OBJ_FM_CURRENCYFIELD; + if (sPersistentServiceName == FM_COMPONENT_PATTERNFIELD) + return OBJ_FM_PATTERNFIELD; + if (sPersistentServiceName == FM_COMPONENT_HIDDEN) // 5.0-Name + return OBJ_FM_HIDDEN; + if (sPersistentServiceName == FM_COMPONENT_HIDDENCONTROL) + return OBJ_FM_HIDDEN; + if (sPersistentServiceName == FM_COMPONENT_IMAGECONTROL) + return OBJ_FM_IMAGECONTROL; + if (sPersistentServiceName == FM_COMPONENT_FORMATTEDFIELD) + { + OSL_FAIL("::getControlTypeByObject : suspicious persistent service name (formatted field) !"); + // objects with that service name should exist as they aren't compatible with older versions + return OBJ_FM_FORMATTEDFIELD; + } + if ( sPersistentServiceName == FM_SUN_COMPONENT_SCROLLBAR ) + return OBJ_FM_SCROLLBAR; + if ( sPersistentServiceName == FM_SUN_COMPONENT_SPINBUTTON ) + return OBJ_FM_SPINBUTTON; + if ( sPersistentServiceName == FM_SUN_COMPONENT_NAVIGATIONBAR ) + return OBJ_FM_NAVIGATIONBAR; + + OSL_FAIL("::getControlTypeByObject : unknown object type !"); + return OBJ_FM_CONTROL; +} + + +bool isRowSetAlive(const Reference< XInterface >& _rxRowSet) +{ + bool bIsAlive = false; + Reference< css::sdbcx::XColumnsSupplier> xSupplyCols(_rxRowSet, UNO_QUERY); + Reference< css::container::XIndexAccess> xCols; + if (xSupplyCols.is()) + xCols.set(xSupplyCols->getColumns(), UNO_QUERY); + if (xCols.is() && (xCols->getCount() > 0)) + bIsAlive = true; + + return bIsAlive; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmundo.cxx b/svx/source/form/fmundo.cxx new file mode 100644 index 000000000..7b8fa0279 --- /dev/null +++ b/svx/source/form/fmundo.cxx @@ -0,0 +1,1265 @@ +/* -*- 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 <sal/config.h> + +#include <map> + +#include <sal/macros.h> +#include <fmundo.hxx> +#include <fmpgeimp.hxx> +#include <svx/svditer.hxx> +#include <fmobj.hxx> +#include <fmprop.hxx> +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> +#include <svx/fmmodel.hxx> +#include <svx/fmpage.hxx> + +#include <com/sun/star/util/XModifyBroadcaster.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/container/XContainer.hpp> +#include <com/sun/star/container/XContainerListener.hpp> +#include <com/sun/star/script/XEventAttacherManager.hpp> +#include <com/sun/star/form/binding/XBindableValue.hpp> +#include <com/sun/star/form/binding/XListEntrySink.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <svx/fmtools.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <sfx2/objsh.hxx> +#include <sfx2/event.hxx> +#include <osl/mutex.hxx> +#include <comphelper/property.hxx> +#include <comphelper/types.hxx> +#include <connectivity/dbtools.hxx> +#include <vcl/svapp.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::script; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::form; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::form::binding; +using namespace ::com::sun::star::sdbc; +using namespace ::svxform; +using namespace ::dbtools; + + +#include <com/sun/star/script/XScriptListener.hpp> +#include <comphelper/processfactory.hxx> +#include <cppuhelper/implbase.hxx> + +namespace { + +class ScriptEventListenerWrapper : public cppu::WeakImplHelper< XScriptListener > +{ +public: + /// @throws css::uno::RuntimeException + explicit ScriptEventListenerWrapper( FmFormModel& _rModel) + :m_rModel( _rModel ) + ,m_attemptedListenerCreation( false ) + { + + } + // XEventListener + virtual void SAL_CALL disposing(const EventObject& ) override {} + + // XScriptListener + virtual void SAL_CALL firing(const ScriptEvent& evt) override + { + attemptListenerCreation(); + if ( m_vbaListener.is() ) + { + m_vbaListener->firing( evt ); + } + } + + virtual Any SAL_CALL approveFiring(const ScriptEvent& evt) override + { + attemptListenerCreation(); + if ( m_vbaListener.is() ) + { + return m_vbaListener->approveFiring( evt ); + } + return Any(); + } + +private: + void attemptListenerCreation() + { + if ( m_attemptedListenerCreation ) + return; + m_attemptedListenerCreation = true; + + try + { + css::uno::Reference<css::uno::XComponentContext> context( + comphelper::getProcessComponentContext()); + Reference< XScriptListener > const xScriptListener( + context->getServiceManager()->createInstanceWithContext( + "ooo.vba.EventListener", context), + UNO_QUERY_THROW); + Reference< XPropertySet > const xListenerProps( xScriptListener, UNO_QUERY_THROW ); + // SfxObjectShellRef is good here since the model controls the lifetime of the shell + SfxObjectShellRef const xObjectShell = m_rModel.GetObjectShell(); + ENSURE_OR_THROW( xObjectShell.is(), "no object shell!" ); + xListenerProps->setPropertyValue("Model", makeAny( xObjectShell->GetModel() ) ); + + m_vbaListener = xScriptListener; + } + catch( Exception const & ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + FmFormModel& m_rModel; + Reference< XScriptListener > m_vbaListener; + bool m_attemptedListenerCreation; + + +}; + + +// some helper structs for caching property infos + +struct PropertyInfo +{ + bool bIsTransientOrReadOnly : 1; // the property is transient or read-only, thus we need no undo action for it + bool bIsValueProperty : 1; // the property is the special value property, thus it may be handled + // as if it's transient or persistent +}; + +struct PropertySetInfo +{ + typedef std::map<OUString, PropertyInfo> AllProperties; + + AllProperties aProps; // all properties of this set which we know so far + bool bHasEmptyControlSource; // sal_True -> the set has a DataField property, and the current value is an empty string + // sal_False -> the set has _no_ such property or its value isn't empty +}; + +} + +typedef std::map<Reference< XPropertySet >, PropertySetInfo> PropertySetInfoCache; + + +static OUString static_STR_UNDO_PROPERTY; + + +FmXUndoEnvironment::FmXUndoEnvironment(FmFormModel& _rModel) + :rModel( _rModel ) + ,m_pPropertySetCache( nullptr ) + ,m_pScriptingEnv( new svxform::FormScriptingEnvironment( _rModel ) ) + ,m_Locks( 0 ) + ,bReadOnly( false ) + ,m_bDisposed( false ) +{ + try + { + m_vbaListener = new ScriptEventListenerWrapper( _rModel ); + } + catch( Exception& ) + { + } +} + +FmXUndoEnvironment::~FmXUndoEnvironment() +{ + if ( !m_bDisposed ) // i120746, call FormScriptingEnvironment::dispose to avoid memory leak + m_pScriptingEnv->dispose(); + + if (m_pPropertySetCache) + delete static_cast<PropertySetInfoCache*>(m_pPropertySetCache); +} + +void FmXUndoEnvironment::dispose() +{ + OSL_ENSURE( !m_bDisposed, "FmXUndoEnvironment::dispose: disposed twice?" ); + if ( !m_bDisposed ) + return; + + Lock(); + + sal_uInt16 nCount = rModel.GetPageCount(); + sal_uInt16 i; + for (i = 0; i < nCount; i++) + { + FmFormPage* pPage = dynamic_cast<FmFormPage*>( rModel.GetPage(i) ); + if ( pPage ) + { + Reference< css::form::XForms > xForms = pPage->GetForms( false ).get(); + if ( xForms.is() ) + RemoveElement( xForms ); + } + } + + nCount = rModel.GetMasterPageCount(); + for (i = 0; i < nCount; i++) + { + FmFormPage* pPage = dynamic_cast<FmFormPage*>( rModel.GetMasterPage(i) ); + if ( pPage ) + { + Reference< css::form::XForms > xForms = pPage->GetForms( false ).get(); + if ( xForms.is() ) + RemoveElement( xForms ); + } + } + + UnLock(); + + OSL_PRECOND( rModel.GetObjectShell(), "FmXUndoEnvironment::dispose: no object shell anymore!" ); + if ( rModel.GetObjectShell() ) + EndListening( *rModel.GetObjectShell() ); + + if ( IsListening( rModel ) ) + EndListening( rModel ); + + m_pScriptingEnv->dispose(); + + m_bDisposed = true; +} + + +void FmXUndoEnvironment::ModeChanged() +{ + OSL_PRECOND( rModel.GetObjectShell(), "FmXUndoEnvironment::ModeChanged: no object shell anymore!" ); + if ( !rModel.GetObjectShell() ) + return; + + if (bReadOnly != (rModel.GetObjectShell()->IsReadOnly() || rModel.GetObjectShell()->IsReadOnlyUI())) + { + bReadOnly = !bReadOnly; + + sal_uInt16 nCount = rModel.GetPageCount(); + sal_uInt16 i; + for (i = 0; i < nCount; i++) + { + FmFormPage* pPage = dynamic_cast<FmFormPage*>( rModel.GetPage(i) ); + if ( pPage ) + { + Reference< css::form::XForms > xForms = pPage->GetForms( false ).get(); + if ( xForms.is() ) + TogglePropertyListening( xForms ); + } + } + + nCount = rModel.GetMasterPageCount(); + for (i = 0; i < nCount; i++) + { + FmFormPage* pPage = dynamic_cast<FmFormPage*>( rModel.GetMasterPage(i) ); + if ( pPage ) + { + Reference< css::form::XForms > xForms = pPage->GetForms( false ).get(); + if ( xForms.is() ) + TogglePropertyListening( xForms ); + } + } + + if (!bReadOnly) + StartListening(rModel); + else + EndListening(rModel); + } +} + + +void FmXUndoEnvironment::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint ) +{ + if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint) + { + const SdrHint* pSdrHint = static_cast<const SdrHint*>(&rHint); + switch (pSdrHint->GetKind()) + { + case SdrHintKind::ObjectInserted: + { + SdrObject* pSdrObj = const_cast<SdrObject*>(pSdrHint->GetObject()); + Inserted( pSdrObj ); + } break; + case SdrHintKind::ObjectRemoved: + { + SdrObject* pSdrObj = const_cast<SdrObject*>(pSdrHint->GetObject()); + Removed( pSdrObj ); + } + break; + default: + break; + } + } + else if (rHint.GetId() != SfxHintId::NONE) + { + switch (rHint.GetId()) + { + case SfxHintId::Dying: + dispose(); + rModel.SetObjectShell( nullptr ); + break; + case SfxHintId::ModeChanged: + ModeChanged(); + break; + default: break; + } + } + else if (const SfxEventHint* pEventHint = dynamic_cast<const SfxEventHint*>(&rHint)) + { + switch (pEventHint->GetEventId()) + { + case SfxEventHintId::CreateDoc: + case SfxEventHintId::OpenDoc: + ModeChanged(); + break; + default: break; + } + } +} + +void FmXUndoEnvironment::Inserted(SdrObject* pObj) +{ + if (pObj->GetObjInventor() == SdrInventor::FmForm) + { + FmFormObj* pFormObj = dynamic_cast<FmFormObj*>( pObj ); + Inserted( pFormObj ); + } + else if (pObj->IsGroupObject()) + { + SdrObjListIter aIter(pObj->GetSubList()); + while ( aIter.IsMore() ) + Inserted( aIter.Next() ); + } +} + + +namespace +{ + bool lcl_searchElement(const Reference< XIndexAccess>& xCont, const Reference< XInterface >& xElement) + { + if (!xCont.is() || !xElement.is()) + return false; + + sal_Int32 nCount = xCont->getCount(); + Reference< XInterface > xComp; + for (sal_Int32 i = 0; i < nCount; i++) + { + try + { + xCont->getByIndex(i) >>= xComp; + if (xComp.is()) + { + if ( xElement == xComp ) + return true; + else + { + Reference< XIndexAccess> xCont2(xComp, UNO_QUERY); + if (xCont2.is() && lcl_searchElement(xCont2, xElement)) + return true; + } + } + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + return false; + } +} + + +void FmXUndoEnvironment::Inserted(FmFormObj* pObj) +{ + DBG_ASSERT( pObj, "FmXUndoEnvironment::Inserted: invalid object!" ); + if ( !pObj ) + return; + + // is the control still assigned to a form + Reference< XInterface > xModel(pObj->GetUnoControlModel(), UNO_QUERY); + Reference< XFormComponent > xContent(xModel, UNO_QUERY); + if (xContent.is() && pObj->getSdrPageFromSdrObject()) + { + // if the component doesn't belong to a form, yet, find one to insert into + if (!xContent->getParent().is()) + { + try + { + const Reference< XIndexContainer >& xObjectParent = pObj->GetOriginalParent(); + + FmFormPage& rPage(dynamic_cast< FmFormPage& >( *pObj->getSdrPageFromSdrObject())); + Reference< XIndexAccess > xForms( rPage.GetForms(), UNO_QUERY_THROW ); + + Reference< XIndexContainer > xNewParent; + Reference< XForm > xForm; + sal_Int32 nPos = -1; + if ( lcl_searchElement( xForms, xObjectParent ) ) + { + // the form which was the parent of the object when it was removed is still + // part of the form component hierarchy of the current page + xNewParent = xObjectParent; + xForm.set( xNewParent, UNO_QUERY_THROW ); + nPos = ::std::min( pObj->GetOriginalIndex(), xNewParent->getCount() ); + } + else + { + xForm.set( rPage.GetImpl().findPlaceInFormComponentHierarchy( xContent ), UNO_SET_THROW ); + xNewParent.set( xForm, UNO_QUERY_THROW ); + nPos = xNewParent->getCount(); + } + + FmFormPageImpl::setUniqueName( xContent, xForm ); + xNewParent->insertByIndex( nPos, makeAny( xContent ) ); + + Reference< XEventAttacherManager > xManager( xNewParent, UNO_QUERY_THROW ); + xManager->registerScriptEvents( nPos, pObj->GetOriginalEvents() ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + // reset FormObject + pObj->ClearObjEnv(); + } +} + + +void FmXUndoEnvironment::Removed(SdrObject* pObj) +{ + if ( pObj->IsVirtualObj() ) + // for virtual objects, we've already been notified of the removal of the master + // object, which is sufficient here + return; + + if (pObj->GetObjInventor() == SdrInventor::FmForm) + { + FmFormObj* pFormObj = dynamic_cast<FmFormObj*>( pObj ); + Removed(pFormObj); + } + else if (pObj->IsGroupObject()) + { + SdrObjListIter aIter(pObj->GetSubList()); + while ( aIter.IsMore() ) + Removed( aIter.Next() ); + } +} + + +void FmXUndoEnvironment::Removed(FmFormObj* pObj) +{ + DBG_ASSERT( pObj, "FmXUndoEnvironment::Removed: invalid object!" ); + if ( !pObj ) + return; + + // is the control still assigned to a form + Reference< XFormComponent > xContent(pObj->GetUnoControlModel(), UNO_QUERY); + if (xContent.is()) + { + // The object is taken out of a list. + // If a father exists, the object is removed at the father and + // noted at the FormObject! + + // If the object is reinserted and a parent exists, this parent is set though. + Reference< XIndexContainer > xForm(xContent->getParent(), UNO_QUERY); + if (xForm.is()) + { + Reference< XIndexAccess > xIndexAccess(xForm.get()); + // determine which position the child was at + const sal_Int32 nPos = getElementPos(xIndexAccess, xContent); + if (nPos >= 0) + { + Sequence< ScriptEventDescriptor > aEvts; + Reference< XEventAttacherManager > xManager(xForm, UNO_QUERY); + if (xManager.is()) + aEvts = xManager->getScriptEvents(nPos); + + try + { + pObj->SetObjEnv(xForm, nPos, aEvts); + xForm->removeByIndex(nPos); + } + catch(Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + } + } + } +} + +// XEventListener + +void SAL_CALL FmXUndoEnvironment::disposing(const EventObject& e) +{ + // check if it's an object we have cached information about + if (m_pPropertySetCache) + { + Reference< XPropertySet > xSourceSet(e.Source, UNO_QUERY); + if (xSourceSet.is()) + { + PropertySetInfoCache* pCache = static_cast<PropertySetInfoCache*>(m_pPropertySetCache); + PropertySetInfoCache::iterator aSetPos = pCache->find(xSourceSet); + if (aSetPos != pCache->end()) + pCache->erase(aSetPos); + } + } +} + +// XPropertyChangeListener + +void SAL_CALL FmXUndoEnvironment::propertyChange(const PropertyChangeEvent& evt) +{ + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + if (!IsLocked()) + { + Reference< XPropertySet > xSet(evt.Source, UNO_QUERY); + if (!xSet.is()) + return; + + // if it's a "default value" property of a control model, set the according "value" property + static const OUStringLiteral pDefaultValueProperties[] = { + FM_PROP_DEFAULT_TEXT, FM_PROP_DEFAULTCHECKED, FM_PROP_DEFAULT_DATE, FM_PROP_DEFAULT_TIME, + FM_PROP_DEFAULT_VALUE, FM_PROP_DEFAULT_SELECT_SEQ, FM_PROP_EFFECTIVE_DEFAULT + }; + static const OUStringLiteral aValueProperties[] = { + FM_PROP_TEXT, FM_PROP_STATE, FM_PROP_DATE, FM_PROP_TIME, + FM_PROP_VALUE, FM_PROP_SELECT_SEQ, FM_PROP_EFFECTIVE_VALUE + }; + sal_Int32 nDefaultValueProps = SAL_N_ELEMENTS(pDefaultValueProperties); + OSL_ENSURE(SAL_N_ELEMENTS(aValueProperties) == nDefaultValueProps, + "FmXUndoEnvironment::propertyChange: inconsistence!"); + for (sal_Int32 i=0; i<nDefaultValueProps; ++i) + { + if (evt.PropertyName == pDefaultValueProperties[i]) + { + try + { + xSet->setPropertyValue(aValueProperties[i], evt.NewValue); + } + catch(const Exception&) + { + OSL_FAIL("FmXUndoEnvironment::propertyChange: could not adjust the value property!"); + } + } + } + + // no Undo for transient and readonly props. But unfortunately "transient" is not only that the + // "transient" flag is set for the property in question, instead it is somewhat more complex + // Transience criterions are: + // - the "transient" flag is set for the property + // - OR the control has a non-empty COntrolSource property, i.e. is intended to be bound + // to a database column. Note that it doesn't matter here whether the control actually + // *is* bound to a column + // - OR the control is bound to an external value via XBindableValue/XValueBinding + // which does not have a "ExternalData" property being <TRUE/> + + if (!m_pPropertySetCache) + m_pPropertySetCache = new PropertySetInfoCache; + PropertySetInfoCache* pCache = static_cast<PropertySetInfoCache*>(m_pPropertySetCache); + + // let's see if we know something about the set + PropertySetInfoCache::iterator aSetPos = pCache->find(xSet); + if (aSetPos == pCache->end()) + { + PropertySetInfo aNewEntry; + if (!::comphelper::hasProperty(FM_PROP_CONTROLSOURCE, xSet)) + { + aNewEntry.bHasEmptyControlSource = false; + } + else + { + try + { + Any aCurrentControlSource = xSet->getPropertyValue(FM_PROP_CONTROLSOURCE); + aNewEntry.bHasEmptyControlSource = !aCurrentControlSource.hasValue() || ::comphelper::getString(aCurrentControlSource).isEmpty(); + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + aSetPos = pCache->emplace(xSet,aNewEntry).first; + DBG_ASSERT(aSetPos != pCache->end(), "FmXUndoEnvironment::propertyChange : just inserted it ... why it's not there ?"); + } + else + { // is it the DataField property ? + if (evt.PropertyName == FM_PROP_CONTROLSOURCE) + { + aSetPos->second.bHasEmptyControlSource = !evt.NewValue.hasValue() || ::comphelper::getString(evt.NewValue).isEmpty(); + } + } + + // now we have access to the cached info about the set + // let's see what we know about the property + PropertySetInfo::AllProperties& rPropInfos = aSetPos->second.aProps; + PropertySetInfo::AllProperties::iterator aPropertyPos = rPropInfos.find(evt.PropertyName); + if (aPropertyPos == rPropInfos.end()) + { // nothing 'til now ... have to change this... + PropertyInfo aNewEntry; + + // the attributes + sal_Int32 nAttributes = xSet->getPropertySetInfo()->getPropertyByName(evt.PropertyName).Attributes; + aNewEntry.bIsTransientOrReadOnly = ((nAttributes & PropertyAttribute::READONLY) != 0) || ((nAttributes & PropertyAttribute::TRANSIENT) != 0); + + // check if it is the special "DataFieldProperty" + aNewEntry.bIsValueProperty = false; + try + { + if (::comphelper::hasProperty(FM_PROP_CONTROLSOURCEPROPERTY, xSet)) + { + Any aControlSourceProperty = xSet->getPropertyValue(FM_PROP_CONTROLSOURCEPROPERTY); + OUString sControlSourceProperty; + aControlSourceProperty >>= sControlSourceProperty; + + aNewEntry.bIsValueProperty = (sControlSourceProperty == evt.PropertyName); + } + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + // insert the new entry + aPropertyPos = rPropInfos.emplace(evt.PropertyName,aNewEntry).first; + DBG_ASSERT(aPropertyPos != rPropInfos.end(), "FmXUndoEnvironment::propertyChange : just inserted it ... why it's not there ?"); + } + + // now we have access to the cached info about the property affected + // and are able to decide whether or not we need an undo action + + bool bAddUndoAction = rModel.IsUndoEnabled(); + // no UNDO for transient/readonly properties + if ( bAddUndoAction && aPropertyPos->second.bIsTransientOrReadOnly ) + bAddUndoAction = false; + + if ( bAddUndoAction && aPropertyPos->second.bIsValueProperty ) + { + // no UNDO when the "value" property changes, but the ControlSource is non-empty + // (in this case the control is intended to be bound to a database column) + if ( !aSetPos->second.bHasEmptyControlSource ) + bAddUndoAction = false; + + // no UNDO if the control is currently bound to an external value + if ( bAddUndoAction ) + { + Reference< XBindableValue > xBindable( evt.Source, UNO_QUERY ); + Reference< XValueBinding > xBinding; + if ( xBindable.is() ) + xBinding = xBindable->getValueBinding(); + + Reference< XPropertySet > xBindingProps; + Reference< XPropertySetInfo > xBindingPropsPSI; + if ( xBindable.is() ) + xBindingProps.set( xBinding, UNO_QUERY ); + if ( xBindingProps.is() ) + xBindingPropsPSI = xBindingProps->getPropertySetInfo(); + // TODO: we should cache all those things, else this might be too expensive. + // However, this requires we're notified of changes in the value binding + + static const char s_sExternalData[] = "ExternalData"; + if ( xBindingPropsPSI.is() && xBindingPropsPSI->hasPropertyByName( s_sExternalData ) ) + { + bool bExternalData = true; + OSL_VERIFY( xBindingProps->getPropertyValue( s_sExternalData ) >>= bExternalData ); + bAddUndoAction = !bExternalData; + } + else + bAddUndoAction = !xBinding.is(); + } + } + + if ( bAddUndoAction && ( evt.PropertyName == FM_PROP_STRINGITEMLIST ) ) + { + Reference< XListEntrySink > xSink( evt.Source, UNO_QUERY ); + if ( xSink.is() && xSink->getListEntrySource().is() ) + // #i41029# / 2005-01-31 / frank.schoenheit@sun.com + bAddUndoAction = false; + } + + if ( bAddUndoAction ) + { + aGuard.clear(); + // TODO: this is a potential race condition: two threads here could in theory + // add their undo actions out-of-order + + SolarMutexGuard aSolarGuard; + rModel.AddUndo(std::make_unique<FmUndoPropertyAction>(rModel, evt)); + } + } + else + { + // if it's the DataField property we may have to adjust our cache + if (m_pPropertySetCache && evt.PropertyName == FM_PROP_CONTROLSOURCE) + { + Reference< XPropertySet > xSet(evt.Source, UNO_QUERY); + PropertySetInfoCache* pCache = static_cast<PropertySetInfoCache*>(m_pPropertySetCache); + PropertySetInfo& rSetInfo = (*pCache)[xSet]; + rSetInfo.bHasEmptyControlSource = !evt.NewValue.hasValue() || ::comphelper::getString(evt.NewValue).isEmpty(); + } + } +} + +// XContainerListener + +void SAL_CALL FmXUndoEnvironment::elementInserted(const ContainerEvent& evt) +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + // new object for listening + Reference< XInterface > xIface; + evt.Element >>= xIface; + OSL_ENSURE(xIface.is(), "FmXUndoEnvironment::elementInserted: invalid container notification!"); + AddElement(xIface); + + implSetModified(); +} + + +void FmXUndoEnvironment::implSetModified() +{ + if ( !IsLocked() && rModel.GetObjectShell() ) + { + rModel.GetObjectShell()->SetModified(); + } +} + + +void SAL_CALL FmXUndoEnvironment::elementReplaced(const ContainerEvent& evt) +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + Reference< XInterface > xIface; + evt.ReplacedElement >>= xIface; + OSL_ENSURE(xIface.is(), "FmXUndoEnvironment::elementReplaced: invalid container notification!"); + RemoveElement(xIface); + + evt.Element >>= xIface; + AddElement(xIface); + + implSetModified(); +} + + +void SAL_CALL FmXUndoEnvironment::elementRemoved(const ContainerEvent& evt) +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + Reference< XInterface > xIface( evt.Element, UNO_QUERY ); + OSL_ENSURE(xIface.is(), "FmXUndoEnvironment::elementRemoved: invalid container notification!"); + RemoveElement(xIface); + + implSetModified(); +} + + +void SAL_CALL FmXUndoEnvironment::modified( const EventObject& /*aEvent*/ ) +{ + implSetModified(); +} + + +void FmXUndoEnvironment::AddForms(const Reference< XNameContainer > & rForms) +{ + Lock(); + AddElement(Reference<XInterface>( rForms, UNO_QUERY )); + UnLock(); +} + + +void FmXUndoEnvironment::RemoveForms(const Reference< XNameContainer > & rForms) +{ + Lock(); + RemoveElement(Reference<XInterface>( rForms, UNO_QUERY )); + UnLock(); +} + + +void FmXUndoEnvironment::TogglePropertyListening(const Reference< XInterface > & Element) +{ + // listen at the container + Reference< XIndexContainer > xContainer(Element, UNO_QUERY); + if (xContainer.is()) + { + sal_uInt32 nCount = xContainer->getCount(); + Reference< XInterface > xIface; + for (sal_uInt32 i = 0; i < nCount; i++) + { + xContainer->getByIndex(i) >>= xIface; + TogglePropertyListening(xIface); + } + } + + Reference< XPropertySet > xSet(Element, UNO_QUERY); + if (xSet.is()) + { + if (!bReadOnly) + xSet->addPropertyChangeListener( OUString(), this ); + else + xSet->removePropertyChangeListener( OUString(), this ); + } +} + + +void FmXUndoEnvironment::switchListening( const Reference< XIndexContainer >& _rxContainer, bool _bStartListening ) +{ + OSL_PRECOND( _rxContainer.is(), "FmXUndoEnvironment::switchListening: invalid container!" ); + if ( !_rxContainer.is() ) + return; + + try + { + // if it's an EventAttacherManager, then we need to listen for + // script events + Reference< XEventAttacherManager > xManager( _rxContainer, UNO_QUERY ); + if ( xManager.is() ) + { + if ( _bStartListening ) + { + m_pScriptingEnv->registerEventAttacherManager( xManager ); + if ( m_vbaListener.is() ) + xManager->addScriptListener( m_vbaListener ); + } + else + { + m_pScriptingEnv->revokeEventAttacherManager( xManager ); + if ( m_vbaListener.is() ) + xManager->removeScriptListener( m_vbaListener ); + } + } + + // also handle all children of this element + sal_uInt32 nCount = _rxContainer->getCount(); + Reference< XInterface > xInterface; + for ( sal_uInt32 i = 0; i < nCount; ++i ) + { + _rxContainer->getByIndex( i ) >>= xInterface; + if ( _bStartListening ) + AddElement( xInterface ); + else + RemoveElement( xInterface ); + } + + // be notified of any changes in the container elements + Reference< XContainer > xSimpleContainer( _rxContainer, UNO_QUERY ); + OSL_ENSURE( xSimpleContainer.is(), "FmXUndoEnvironment::switchListening: how are we expected to be notified of changes in the container?" ); + if ( xSimpleContainer.is() ) + { + if ( _bStartListening ) + xSimpleContainer->addContainerListener( this ); + else + xSimpleContainer->removeContainerListener( this ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "FmXUndoEnvironment::switchListening" ); + } +} + + +void FmXUndoEnvironment::switchListening( const Reference< XInterface >& _rxObject, bool _bStartListening ) +{ + OSL_PRECOND( _rxObject.is(), "FmXUndoEnvironment::switchListening: how should I listen at a NULL object?" ); + + try + { + if ( !bReadOnly ) + { + Reference< XPropertySet > xProps( _rxObject, UNO_QUERY ); + if ( xProps.is() ) + { + if ( _bStartListening ) + xProps->addPropertyChangeListener( OUString(), this ); + else + xProps->removePropertyChangeListener( OUString(), this ); + } + } + + Reference< XModifyBroadcaster > xBroadcaster( _rxObject, UNO_QUERY ); + if ( xBroadcaster.is() ) + { + if ( _bStartListening ) + xBroadcaster->addModifyListener( this ); + else + xBroadcaster->removeModifyListener( this ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "FmXUndoEnvironment::switchListening" ); + } +} + + +void FmXUndoEnvironment::AddElement(const Reference< XInterface >& _rxElement ) +{ + OSL_ENSURE( !m_bDisposed, "FmXUndoEnvironment::AddElement: not when I'm already disposed!" ); + + // listen at the container + Reference< XIndexContainer > xContainer( _rxElement, UNO_QUERY ); + if ( xContainer.is() ) + switchListening( xContainer, true ); + + switchListening( _rxElement, true ); +} + + +void FmXUndoEnvironment::RemoveElement(const Reference< XInterface >& _rxElement) +{ + if ( m_bDisposed ) + return; + + switchListening( _rxElement, false ); + + if (!bReadOnly) + { + // reset the ActiveConnection if the form is to be removed. This will (should) free the resources + // associated with this connection + // 86299 - 05/02/2001 - frank.schoenheit@germany.sun.com + Reference< XForm > xForm( _rxElement, UNO_QUERY ); + Reference< XPropertySet > xFormProperties( xForm, UNO_QUERY ); + if ( xFormProperties.is() ) + { + Reference< XConnection > xDummy; + if ( !isEmbeddedInDatabase( _rxElement, xDummy ) ) + // (if there is a connection in the context of the component, setting + // a new connection would be vetoed, anyway) + // #i34196# + xFormProperties->setPropertyValue( FM_PROP_ACTIVE_CONNECTION, Any() ); + } + } + + Reference< XIndexContainer > xContainer( _rxElement, UNO_QUERY ); + if ( xContainer.is() ) + switchListening( xContainer, false ); +} + + +FmUndoPropertyAction::FmUndoPropertyAction(FmFormModel& rNewMod, const PropertyChangeEvent& evt) + :SdrUndoAction(rNewMod) + ,xObj(evt.Source, UNO_QUERY) + ,aPropertyName(evt.PropertyName) + ,aNewValue(evt.NewValue) + ,aOldValue(evt.OldValue) +{ + if (rNewMod.GetObjectShell()) + rNewMod.GetObjectShell()->SetModified(); + if(static_STR_UNDO_PROPERTY.isEmpty()) + static_STR_UNDO_PROPERTY = SvxResId(RID_STR_UNDO_PROPERTY); +} + + +void FmUndoPropertyAction::Undo() +{ + FmXUndoEnvironment& rEnv = static_cast<FmFormModel&>(rMod).GetUndoEnv(); + + if (xObj.is() && !rEnv.IsLocked()) + { + rEnv.Lock(); + try + { + xObj->setPropertyValue( aPropertyName, aOldValue ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "FmUndoPropertyAction::Undo" ); + } + rEnv.UnLock(); + } +} + + +void FmUndoPropertyAction::Redo() +{ + FmXUndoEnvironment& rEnv = static_cast<FmFormModel&>(rMod).GetUndoEnv(); + + if (xObj.is() && !rEnv.IsLocked()) + { + rEnv.Lock(); + try + { + xObj->setPropertyValue( aPropertyName, aNewValue ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "FmUndoPropertyAction::Redo" ); + } + rEnv.UnLock(); + } +} + + +OUString FmUndoPropertyAction::GetComment() const +{ + OUString aStr = static_STR_UNDO_PROPERTY.replaceFirst( "#", aPropertyName ); + return aStr; +} + + +FmUndoContainerAction::FmUndoContainerAction(FmFormModel& _rMod, + Action _eAction, + const Reference< XIndexContainer > & xCont, + const Reference< XInterface > & xElem, + sal_Int32 nIdx) + :SdrUndoAction( _rMod ) + ,m_xContainer( xCont ) + ,m_nIndex( nIdx ) + ,m_eAction( _eAction ) +{ + OSL_ENSURE( nIdx >= 0, "FmUndoContainerAction::FmUndoContainerAction: invalid index!" ); + // some old code suggested this could be a valid argument. However, this code was + // buggy, and it *seemed* that nobody used it - so it was removed. + + if ( xCont.is() && xElem.is() ) + { + // normalize + m_xElement = xElem; + if ( m_eAction == Removed ) + { + if (m_nIndex >= 0) + { + Reference< XEventAttacherManager > xManager( xCont, UNO_QUERY ); + if ( xManager.is() ) + m_aEvents = xManager->getScriptEvents(m_nIndex); + } + else + m_xElement = nullptr; + + // we now own the element + m_xOwnElement = m_xElement; + } + } +} + + +FmUndoContainerAction::~FmUndoContainerAction() +{ + // if we own the object... + DisposeElement( m_xOwnElement ); +} + + +void FmUndoContainerAction::DisposeElement( const Reference< XInterface > & xElem ) +{ + Reference< XComponent > xComp( xElem, UNO_QUERY ); + if ( xComp.is() ) + { + // and the object does not have a parent + Reference< XChild > xChild( xElem, UNO_QUERY ); + if ( xChild.is() && !xChild->getParent().is() ) + // -> dispose it + xComp->dispose(); + } +} + + +void FmUndoContainerAction::implReInsert( ) +{ + if ( m_xContainer->getCount() >= m_nIndex ) + { + // insert the element + Any aVal; + if ( m_xContainer->getElementType() == cppu::UnoType<XFormComponent>::get() ) + { + aVal <<= Reference< XFormComponent >( m_xElement, UNO_QUERY ); + } + else + { + aVal <<= Reference< XForm >( m_xElement, UNO_QUERY ); + } + m_xContainer->insertByIndex( m_nIndex, aVal ); + + OSL_ENSURE( getElementPos( m_xContainer.get(), m_xElement ) == m_nIndex, "FmUndoContainerAction::implReInsert: insertion did not work!" ); + + // register the events + Reference< XEventAttacherManager > xManager( m_xContainer, UNO_QUERY ); + if ( xManager.is() ) + xManager->registerScriptEvents( m_nIndex, m_aEvents ); + + // we don't own the object anymore + m_xOwnElement = nullptr; + } +} + + +void FmUndoContainerAction::implReRemove( ) +{ + Reference< XInterface > xElement; + if ( ( m_nIndex >= 0 ) && ( m_nIndex < m_xContainer->getCount() ) ) + m_xContainer->getByIndex( m_nIndex ) >>= xElement; + + if ( xElement != m_xElement ) + { + // the indexes in the container changed. Okay, so go the long way and + // manually determine the index + m_nIndex = getElementPos( m_xContainer.get(), m_xElement ); + if ( m_nIndex != -1 ) + xElement = m_xElement; + } + + OSL_ENSURE( xElement == m_xElement, "FmUndoContainerAction::implReRemove: cannot find the element which I'm responsible for!" ); + if ( xElement == m_xElement ) + { + Reference< XEventAttacherManager > xManager( m_xContainer, UNO_QUERY ); + if ( xManager.is() ) + m_aEvents = xManager->getScriptEvents( m_nIndex ); + m_xContainer->removeByIndex( m_nIndex ); + // from now on, we own this object + m_xOwnElement = m_xElement; + } +} + + +void FmUndoContainerAction::Undo() +{ + FmXUndoEnvironment& rEnv = static_cast< FmFormModel& >( rMod ).GetUndoEnv(); + + if ( m_xContainer.is() && !rEnv.IsLocked() && m_xElement.is() ) + { + rEnv.Lock(); + try + { + switch ( m_eAction ) + { + case Inserted: + implReRemove(); + break; + + case Removed: + implReInsert(); + break; + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "FmUndoContainerAction::Undo" ); + } + rEnv.UnLock(); + } +} + + +void FmUndoContainerAction::Redo() +{ + FmXUndoEnvironment& rEnv = static_cast< FmFormModel& >( rMod ).GetUndoEnv(); + if ( m_xContainer.is() && !rEnv.IsLocked() && m_xElement.is() ) + { + rEnv.Lock(); + try + { + switch ( m_eAction ) + { + case Inserted: + implReInsert(); + break; + + case Removed: + implReRemove(); + break; + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "FmUndoContainerAction::Redo" ); + } + rEnv.UnLock(); + } +} + + +FmUndoModelReplaceAction::FmUndoModelReplaceAction(FmFormModel& _rMod, SdrUnoObj* _pObject, const Reference< XControlModel > & _xReplaced) + :SdrUndoAction(_rMod) + ,m_xReplaced(_xReplaced) + ,m_pObject(_pObject) +{ +} + + +FmUndoModelReplaceAction::~FmUndoModelReplaceAction() +{ + // dispose our element if nobody else is responsible for + DisposeElement(m_xReplaced); +} + + +void FmUndoModelReplaceAction::DisposeElement( const css::uno::Reference< css::awt::XControlModel>& xReplaced ) +{ + Reference< XComponent > xComp(xReplaced, UNO_QUERY); + if (xComp.is()) + { + Reference< XChild > xChild(xReplaced, UNO_QUERY); + if (!xChild.is() || !xChild->getParent().is()) + xComp->dispose(); + } +} + + +void FmUndoModelReplaceAction::Undo() +{ + try + { + Reference< XControlModel > xCurrentModel( m_pObject->GetUnoControlModel() ); + + // replace the model within the parent + Reference< XChild > xCurrentAsChild( xCurrentModel, UNO_QUERY ); + Reference< XNameContainer > xCurrentsParent; + if ( xCurrentAsChild.is() ) + xCurrentsParent.set(xCurrentAsChild->getParent(), css::uno::UNO_QUERY); + DBG_ASSERT( xCurrentsParent.is(), "FmUndoModelReplaceAction::Undo: invalid current model!" ); + + if ( xCurrentsParent.is() ) + { + // the form container works with FormComponents + Reference< XFormComponent > xComponent( m_xReplaced, UNO_QUERY ); + DBG_ASSERT( xComponent.is(), "FmUndoModelReplaceAction::Undo: the new model is no form component !" ); + + Reference< XPropertySet > xCurrentAsSet( xCurrentModel, UNO_QUERY ); + DBG_ASSERT( ::comphelper::hasProperty(FM_PROP_NAME, xCurrentAsSet ), "FmUndoModelReplaceAction::Undo : one of the models is invalid !"); + + OUString sName; + xCurrentAsSet->getPropertyValue( FM_PROP_NAME ) >>= sName; + xCurrentsParent->replaceByName( sName, makeAny( xComponent ) ); + + m_pObject->SetUnoControlModel(m_xReplaced); + m_pObject->SetChanged(); + + m_xReplaced = xCurrentModel; + } + } + catch(Exception&) + { + OSL_FAIL("FmUndoModelReplaceAction::Undo : could not replace the model !"); + } +} + + +OUString FmUndoModelReplaceAction::GetComment() const +{ + return SvxResId(RID_STR_UNDO_MODEL_REPLACE); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmview.cxx b/svx/source/form/fmview.cxx new file mode 100644 index 000000000..1ed008e59 --- /dev/null +++ b/svx/source/form/fmview.cxx @@ -0,0 +1,570 @@ +/* -*- 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 <sfx2/docfile.hxx> +#ifdef REFERENCE +#undef REFERENCE +#endif +#include <com/sun/star/sdb/SQLContext.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/form/XLoadable.hpp> +#include <fmvwimp.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/bindings.hxx> +#include <fmobj.hxx> +#include <svx/svditer.hxx> +#include <svx/svdpagv.hxx> +#include <svx/fmview.hxx> +#include <svx/fmmodel.hxx> +#include <svx/fmpage.hxx> +#include <svx/fmshell.hxx> +#include <fmshimp.hxx> +#include <fmservs.hxx> +#include <fmundo.hxx> +#include <svx/dataaccessdescriptor.hxx> +#include <comphelper/namedvaluecollection.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <tools/debug.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <svx/svxids.hrc> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::form; +using namespace ::com::sun::star::util; +using namespace ::svxform; +using namespace ::svx; + +FmFormView::FmFormView( + SdrModel& rSdrModel, + OutputDevice* pOut) +: E3dView(rSdrModel, pOut) +{ + Init(); +} + +void FmFormView::Init() +{ + pFormShell = nullptr; + pImpl = new FmXFormView(this); + + // set model + SdrModel* pModel = GetModel(); + + DBG_ASSERT( dynamic_cast<const FmFormModel*>( pModel) != nullptr, "Wrong model" ); + if( dynamic_cast<const FmFormModel*>( pModel) == nullptr ) return; + FmFormModel* pFormModel = static_cast<FmFormModel*>(pModel); + + + // get DesignMode from model + bool bInitDesignMode = pFormModel->GetOpenInDesignMode(); + if ( pFormModel->OpenInDesignModeIsDefaulted( ) ) + { // this means that nobody ever explicitly set this on the model, and the model has never + // been loaded from a stream. + // This means this is a newly created document. This means, we want to have it in design + // mode by default (though a newly created model returns true for GetOpenInDesignMode). + // We _want_ to have this because it makes a lot of hacks following the original fix + DBG_ASSERT( !bInitDesignMode, "FmFormView::Init: doesn't the model default to FALSE anymore?" ); + // if this asserts, either the on-construction default in the model has changed (then this here + // may not be necessary anymore), or we're not dealing with a new document... + bInitDesignMode = true; + } + + SfxObjectShell* pObjShell = pFormModel->GetObjectShell(); + if ( pObjShell && pObjShell->GetMedium() ) + { + const SfxPoolItem *pItem=nullptr; + if ( pObjShell->GetMedium()->GetItemSet()->GetItemState( SID_COMPONENTDATA, false, &pItem ) == SfxItemState::SET ) + { + ::comphelper::NamedValueCollection aComponentData( static_cast<const SfxUnoAnyItem*>(pItem)->GetValue() ); + bInitDesignMode = aComponentData.getOrDefault( "ApplyFormDesignMode", bInitDesignMode ); + } + } + + // this will be done in the shell + // bDesignMode = !bInitDesignMode; // forces execution of SetDesignMode + SetDesignMode( bInitDesignMode ); +} + + +FmFormView::~FmFormView() +{ + if( pFormShell ) + pFormShell->SetView( nullptr ); + + pImpl->notifyViewDying(); +} + + +FmFormPage* FmFormView::GetCurPage() +{ + SdrPageView* pPageView = GetSdrPageView(); + FmFormPage* pCurPage = pPageView ? dynamic_cast<FmFormPage*>( pPageView->GetPage() ) : nullptr; + return pCurPage; +} + + +void FmFormView::MarkListHasChanged() +{ + E3dView::MarkListHasChanged(); + + if ( pFormShell && IsDesignMode() ) + { + FmFormObj* pObj = getMarkedGrid(); + if ( pImpl->m_pMarkedGrid && pImpl->m_pMarkedGrid != pObj ) + { + pImpl->m_pMarkedGrid = nullptr; + if ( pImpl->m_xWindow.is() ) + { + pImpl->m_xWindow->removeFocusListener(pImpl.get()); + pImpl->m_xWindow = nullptr; + } + SetMoveOutside(false); + //OLMRefreshAllIAOManagers(); + } + + pFormShell->GetImpl()->SetSelectionDelayed_Lock(); + } +} + +namespace +{ + const SdrPageWindow* findPageWindow( const SdrPaintView* _pView, OutputDevice const * _pWindow ) + { + SdrPageView* pPageView = _pView->GetSdrPageView(); + if(pPageView) + { + for ( sal_uInt32 window = 0; window < pPageView->PageWindowCount(); ++window ) + { + const SdrPageWindow* pPageWindow = pPageView->GetPageWindow( window ); + if ( !pPageWindow || &pPageWindow->GetPaintWindow().GetOutputDevice() != _pWindow ) + continue; + + return pPageWindow; + } + } + return nullptr; + } +} + + +void FmFormView::AddWindowToPaintView(OutputDevice* pNewWin, vcl::Window* pWindow) +{ + E3dView::AddWindowToPaintView(pNewWin, pWindow); + + if ( !pNewWin ) + return; + + // look up the PageViewWindow for the newly inserted window, and care for it + // #i39269# / 2004-12-20 / frank.schoenheit@sun.com + const SdrPageWindow* pPageWindow = findPageWindow( this, pNewWin ); + if ( pPageWindow ) + pImpl->addWindow( *pPageWindow ); +} + + +void FmFormView::DeleteWindowFromPaintView(OutputDevice* pNewWin) +{ + const SdrPageWindow* pPageWindow = findPageWindow( this, pNewWin ); + if ( pPageWindow ) + pImpl->removeWindow( pPageWindow->GetControlContainer() ); + + E3dView::DeleteWindowFromPaintView(pNewWin); +} + + +void FmFormView::ChangeDesignMode(bool bDesign) +{ + if (bDesign == IsDesignMode()) + return; + + FmFormModel* pModel = dynamic_cast<FmFormModel*>( GetModel() ); + if (pModel) + { // For the duration of the transition the Undo-Environment is disabled. This ensures that non-transient Properties can + // also be changed (this should be done with care and also reversed before switching the mode back. An example is the + // setting of the maximal length of the text by FmXEditModel on its control.) + pModel->GetUndoEnv().Lock(); + } + + // --- 1. deactivate all controls if we are switching to design mode + if ( bDesign ) + DeactivateControls( GetSdrPageView() ); + + // --- 2. simulate a deactivation (the shell will handle some things there ...?) + if ( pFormShell && pFormShell->GetImpl() ) + pFormShell->GetImpl()->viewDeactivated_Lock(*this); + else + pImpl->Deactivate(); + + // --- 3. activate all controls, if we're switching to alive mode + if ( !bDesign ) + ActivateControls( GetSdrPageView() ); + + // --- 4. load resp. unload the forms + FmFormPage* pCurPage = GetCurPage(); + if ( pCurPage ) + { + if ( pFormShell && pFormShell->GetImpl() ) + pFormShell->GetImpl()->loadForms_Lock(pCurPage, (bDesign ? LoadFormsFlags::Unload : LoadFormsFlags::Load)); + } + + // --- 5. base class functionality + SetDesignMode( bDesign ); + + // --- 6. simulate an activation (the shell will handle some things there ...?) + OSL_PRECOND( pFormShell && pFormShell->GetImpl(), "FmFormView::ChangeDesignMode: is this really allowed? No shell?" ); + if ( pFormShell && pFormShell->GetImpl() ) + pFormShell->GetImpl()->viewActivated_Lock(*this); + else + pImpl->Activate(); + + if ( pCurPage ) + { + if ( bDesign ) + { + if ( GetActualOutDev() && GetActualOutDev()->GetOutDevType() == OUTDEV_WINDOW ) + { + const vcl::Window* pWindow = static_cast< const vcl::Window* >( GetActualOutDev() ); + const_cast< vcl::Window* >( pWindow )->GrabFocus(); + } + + // redraw UNO objects + if ( GetSdrPageView() ) + { + SdrObjListIter aIter(pCurPage); + while( aIter.IsMore() ) + { + SdrObject* pObj = aIter.Next(); + if (pObj && pObj->IsUnoObj()) + { + // For redraw just use ActionChanged() + // pObj->BroadcastObjectChange(); + pObj->ActionChanged(); + } + } + } + } + else + { + // set the auto focus to the first control (if indicated by the model to do so) + bool bForceControlFocus = pModel && pModel->GetAutoControlFocus(); + if (bForceControlFocus) + pImpl->AutoFocus(); + } + } + + // Unlock Undo-Environment + if (pModel) + pModel->GetUndoEnv().UnLock(); +} + + +void FmFormView::GrabFirstControlFocus() +{ + if ( !IsDesignMode() ) + pImpl->AutoFocus(); +} + + +SdrPageView* FmFormView::ShowSdrPage(SdrPage* pPage) +{ + SdrPageView* pPV = E3dView::ShowSdrPage(pPage); + + if (pPage) + { + if (!IsDesignMode()) + { + // creating the controllers + ActivateControls(pPV); + + // Deselect all + UnmarkAll(); + } + else if ( pFormShell && pFormShell->IsDesignMode() ) + { + FmXFormShell* pFormShellImpl = pFormShell->GetImpl(); + pFormShellImpl->UpdateForms_Lock(true); + + // so that the form navigator can react to the pagechange + pFormShell->GetViewShell()->GetViewFrame()->GetBindings().Invalidate(SID_FM_FMEXPLORER_CONTROL, true); + + pFormShellImpl->SetSelection_Lock(GetMarkedObjectList()); + } + } + + // notify our shell that we have been activated + if ( pFormShell && pFormShell->GetImpl() ) + pFormShell->GetImpl()->viewActivated_Lock(*this); + else + pImpl->Activate(); + + return pPV; +} + + +void FmFormView::HideSdrPage() +{ + // --- 1. deactivate controls + if ( !IsDesignMode() ) + DeactivateControls(GetSdrPageView()); + + // --- 2. tell the shell the view is (going to be) deactivated + if ( pFormShell && pFormShell->GetImpl() ) + pFormShell->GetImpl()->viewDeactivated_Lock(*this); + else + pImpl->Deactivate(); + + // --- 3. base class behavior + E3dView::HideSdrPage(); +} + + +void FmFormView::ActivateControls(SdrPageView const * pPageView) +{ + if (!pPageView) + return; + + for (sal_uInt32 i = 0; i < pPageView->PageWindowCount(); ++i) + { + const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(i); + pImpl->addWindow(rPageWindow); + } +} + + +void FmFormView::DeactivateControls(SdrPageView const * pPageView) +{ + if( !pPageView ) + return; + + for (sal_uInt32 i = 0; i < pPageView->PageWindowCount(); ++i) + { + const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(i); + pImpl->removeWindow(rPageWindow.GetControlContainer() ); + } +} + + +SdrObjectUniquePtr FmFormView::CreateFieldControl( const ODataAccessDescriptor& _rColumnDescriptor ) +{ + return pImpl->implCreateFieldControl( _rColumnDescriptor ); +} + + +SdrObjectUniquePtr FmFormView::CreateXFormsControl( const OXFormsDescriptor &_rDesc ) +{ + return pImpl->implCreateXFormsControl(_rDesc); +} + + +SdrObjectUniquePtr FmFormView::CreateFieldControl(const OUString& rFieldDesc) const +{ + sal_Int32 nIdx{ 0 }; + OUString sDataSource = rFieldDesc.getToken(0, u'\x000B', nIdx); + OUString sObjectName = rFieldDesc.getToken(0, u'\x000B', nIdx); + sal_uInt16 nObjectType = static_cast<sal_uInt16>(rFieldDesc.getToken(0, u'\x000B', nIdx).toInt32()); + OUString sFieldName = rFieldDesc.getToken(0, u'\x000B', nIdx); + + if (sFieldName.isEmpty() || sObjectName.isEmpty() || sDataSource.isEmpty()) + return nullptr; + + ODataAccessDescriptor aColumnDescriptor; + aColumnDescriptor.setDataSource(sDataSource); + aColumnDescriptor[ DataAccessDescriptorProperty::Command ] <<= sObjectName; + aColumnDescriptor[ DataAccessDescriptorProperty::CommandType ] <<= nObjectType; + aColumnDescriptor[ DataAccessDescriptorProperty::ColumnName ] <<= sFieldName; + + return pImpl->implCreateFieldControl( aColumnDescriptor ); +} + + +void FmFormView::InsertControlContainer(const Reference< css::awt::XControlContainer > & xCC) +{ + if( !IsDesignMode() ) + { + SdrPageView* pPageView = GetSdrPageView(); + if( pPageView ) + { + for( sal_uInt32 i = 0; i < pPageView->PageWindowCount(); i++ ) + { + const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(i); + + if( rPageWindow.GetControlContainer( false ) == xCC ) + { + pImpl->addWindow(rPageWindow); + break; + } + } + } + } +} + + +void FmFormView::RemoveControlContainer(const Reference< css::awt::XControlContainer > & xCC) +{ + if( !IsDesignMode() ) + { + pImpl->removeWindow( xCC ); + } +} + + +SdrPaintWindow* FmFormView::BeginCompleteRedraw(OutputDevice* pOut) +{ + SdrPaintWindow* pPaintWindow = E3dView::BeginCompleteRedraw( pOut ); + pImpl->suspendTabOrderUpdate(); + return pPaintWindow; +} + + +void FmFormView::EndCompleteRedraw( SdrPaintWindow& rPaintWindow, bool bPaintFormLayer ) +{ + E3dView::EndCompleteRedraw( rPaintWindow, bPaintFormLayer ); + pImpl->resumeTabOrderUpdate(); +} + + +bool FmFormView::KeyInput(const KeyEvent& rKEvt, vcl::Window* pWin) +{ + bool bDone = false; + const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); + if ( IsDesignMode() + && rKeyCode.GetCode() == KEY_RETURN + ) + { + // RETURN alone enters grid controls, for keyboard accessibility + if ( pWin + && !rKeyCode.IsShift() + && !rKeyCode.IsMod1() + && !rKeyCode.IsMod2() + ) + { + FmFormObj* pObj = getMarkedGrid(); + if ( pObj ) + { + Reference< awt::XWindow > xWindow( pObj->GetUnoControl( *this, *pWin ), UNO_QUERY ); + if ( xWindow.is() ) + { + pImpl->m_pMarkedGrid = pObj; + pImpl->m_xWindow = xWindow; + // add as listener to get notified when ESC will be pressed inside the grid + pImpl->m_xWindow->addFocusListener(pImpl.get()); + SetMoveOutside(true); + //OLMRefreshAllIAOManagers(); + xWindow->setFocus(); + bDone = true; + } + } + } + // Alt-RETURN alone shows the properties of the selection + if ( pFormShell + && pFormShell->GetImpl() + && !rKeyCode.IsShift() + && !rKeyCode.IsMod1() + && rKeyCode.IsMod2() + ) + { + pFormShell->GetImpl()->handleShowPropertiesRequest_Lock(); + } + + } + + if ( !bDone ) + bDone = E3dView::KeyInput(rKEvt,pWin); + return bDone; +} + +bool FmFormView::checkUnMarkAll(const Reference< XInterface >& _xSource) +{ + Reference< css::awt::XControl> xControl(pImpl->m_xWindow,UNO_QUERY); + bool bRet = !xControl.is() || !_xSource.is() || _xSource != xControl->getModel(); + if ( bRet ) + UnmarkAll(); + + return bRet; +} + + +bool FmFormView::MouseButtonDown( const MouseEvent& _rMEvt, OutputDevice* _pWin ) +{ + bool bReturn = E3dView::MouseButtonDown( _rMEvt, _pWin ); + + if ( pFormShell && pFormShell->GetImpl() ) + { + SdrViewEvent aViewEvent; + PickAnything( _rMEvt, SdrMouseEventKind::BUTTONDOWN, aViewEvent ); + pFormShell->GetImpl()->handleMouseButtonDown_Lock(aViewEvent); + } + + return bReturn; +} + + +FmFormObj* FmFormView::getMarkedGrid() const +{ + FmFormObj* pFormObject = nullptr; + const SdrMarkList& rMarkList = GetMarkedObjectList(); + if ( 1 == rMarkList.GetMarkCount() ) + { + SdrMark* pMark = rMarkList.GetMark(0); + if ( pMark ) + { + pFormObject = FmFormObj::GetFormObject( pMark->GetMarkedSdrObj() ); + if ( pFormObject ) + { + Reference< XServiceInfo > xServInfo( pFormObject->GetUnoControlModel(), UNO_QUERY ); + if ( !xServInfo.is() || !xServInfo->supportsService( FM_SUN_COMPONENT_GRIDCONTROL ) ) + pFormObject = nullptr; + } + } + } + return pFormObject; +} + + +void FmFormView::createControlLabelPair( OutputDevice const * _pOutDev, sal_Int32 _nXOffsetMM, sal_Int32 _nYOffsetMM, + const Reference< XPropertySet >& _rxField, const Reference< XNumberFormats >& _rxNumberFormats, + sal_uInt16 _nControlObjectID, SdrInventor _nInventor, sal_uInt16 _nLabelObjectID, + SdrModel& _rModel, + std::unique_ptr<SdrUnoObj, SdrObjectFreeOp>& _rpLabel, + std::unique_ptr<SdrUnoObj, SdrObjectFreeOp>& _rpControl ) +{ + FmXFormView::createControlLabelPair( + *_pOutDev, _nXOffsetMM, _nYOffsetMM, + _rxField, _rxNumberFormats, + _nControlObjectID, "", _nInventor, _nLabelObjectID, + _rModel, + _rpLabel, _rpControl + ); +} + +Reference< runtime::XFormController > FmFormView::GetFormController( const Reference< XForm >& _rxForm, const OutputDevice& _rDevice ) const +{ + return pImpl->getFormController( _rxForm, _rDevice ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/fmvwimp.cxx b/svx/source/form/fmvwimp.cxx new file mode 100644 index 000000000..1ede28ae3 --- /dev/null +++ b/svx/source/form/fmvwimp.cxx @@ -0,0 +1,1917 @@ +/* -*- 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 <memory> +#include <fmdocumentclassification.hxx> +#include <fmobj.hxx> +#include <fmpgeimp.hxx> +#include <fmprop.hxx> +#include <svx/strings.hrc> +#include <fmservs.hxx> +#include <fmshimp.hxx> +#include <svx/fmtools.hxx> +#include <fmvwimp.hxx> +#include <formcontrolfactory.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <svx/svditer.hxx> +#include <svx/dataaccessdescriptor.hxx> +#include <svx/dialmgr.hxx> +#include <svx/fmglob.hxx> +#include <svx/fmmodel.hxx> +#include <svx/fmpage.hxx> +#include <svx/fmshell.hxx> +#include <svx/fmview.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdpagv.hxx> +#include <svx/xmlexchg.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/sdbc/XRowSet.hpp> +#include <com/sun/star/util/XNumberFormatsSupplier.hpp> +#include <com/sun/star/util/XNumberFormats.hpp> +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/sdbc/DataType.hpp> +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/form/FormButtonType.hpp> +#include <com/sun/star/form/binding/XBindableValue.hpp> +#include <com/sun/star/form/binding/XValueBinding.hpp> +#include <com/sun/star/form/runtime/FormController.hpp> +#include <com/sun/star/form/submission/XSubmissionSupplier.hpp> +#include <com/sun/star/awt/XTabControllerModel.hpp> +#include <com/sun/star/awt/XControlContainer.hpp> +#include <com/sun/star/awt/XTabController.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/awt/XControl.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/sdb/XQueriesSupplier.hpp> +#include <com/sun/star/container/XContainer.hpp> + +#include <comphelper/namedvaluecollection.hxx> +#include <comphelper/property.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/types.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <unotools/moduleoptions.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <sal/log.hxx> +#include <vcl/svapp.hxx> +#include <vcl/stdtext.hxx> +#include <connectivity/dbtools.hxx> + +#include <algorithm> + +using namespace ::comphelper; +using namespace ::svx; +using namespace ::svxform; +using namespace ::dbtools; + + using namespace ::com::sun::star; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::uno::UNO_SET_THROW; + using ::com::sun::star::uno::Type; + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::makeAny; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::form::FormButtonType_SUBMIT; + using ::com::sun::star::form::binding::XValueBinding; + using ::com::sun::star::form::binding::XBindableValue; + using ::com::sun::star::lang::XComponent; + using ::com::sun::star::container::XIndexAccess; + using ::com::sun::star::form::runtime::FormController; + using ::com::sun::star::form::runtime::XFormController; + using ::com::sun::star::script::XEventAttacherManager; + using ::com::sun::star::awt::XTabControllerModel; + using ::com::sun::star::container::XChild; + using ::com::sun::star::task::XInteractionHandler; + using ::com::sun::star::awt::XTabController; + using ::com::sun::star::awt::XControlContainer; + using ::com::sun::star::awt::XControl; + using ::com::sun::star::form::XFormComponent; + using ::com::sun::star::form::XForm; + using ::com::sun::star::lang::IndexOutOfBoundsException; + using ::com::sun::star::container::XContainer; + using ::com::sun::star::container::ContainerEvent; + using ::com::sun::star::lang::EventObject; + using ::com::sun::star::sdb::SQLErrorEvent; + using ::com::sun::star::sdbc::XRowSet; + using ::com::sun::star::beans::XPropertySet; + using ::com::sun::star::container::XElementAccess; + using ::com::sun::star::awt::XWindow; + using ::com::sun::star::awt::FocusEvent; + using ::com::sun::star::ui::dialogs::XExecutableDialog; + using ::com::sun::star::sdbc::XDataSource; + using ::com::sun::star::container::XIndexContainer; + using ::com::sun::star::sdbc::XConnection; + using ::com::sun::star::container::XNameAccess; + using ::com::sun::star::sdbc::SQLException; + using ::com::sun::star::util::XNumberFormatsSupplier; + using ::com::sun::star::util::XNumberFormats; + using ::com::sun::star::beans::XPropertySetInfo; + + namespace FormComponentType = ::com::sun::star::form::FormComponentType; + namespace CommandType = ::com::sun::star::sdb::CommandType; + namespace DataType = ::com::sun::star::sdbc::DataType; + + +class FmXFormView::ObjectRemoveListener : public SfxListener +{ + FmXFormView* m_pParent; +public: + explicit ObjectRemoveListener( FmXFormView* pParent ); + virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override; +}; + +FormViewPageWindowAdapter::FormViewPageWindowAdapter( const css::uno::Reference<css::uno::XComponentContext>& _rContext, const SdrPageWindow& _rWindow, FmXFormView* _pViewImpl ) +: m_xControlContainer( _rWindow.GetControlContainer() ), + m_xContext( _rContext ), + m_pViewImpl( _pViewImpl ), + m_pWindow( dynamic_cast< vcl::Window* >( &_rWindow.GetPaintWindow().GetOutputDevice() ) ) +{ + + // create an XFormController for every form + FmFormPage* pFormPage = dynamic_cast< FmFormPage* >( _rWindow.GetPageView().GetPage() ); + DBG_ASSERT( pFormPage, "FormViewPageWindowAdapter::FormViewPageWindowAdapter: no FmFormPage found!" ); + if ( pFormPage ) + { + try + { + Reference< XIndexAccess > xForms( pFormPage->GetForms(), UNO_QUERY_THROW ); + sal_uInt32 nLength = xForms->getCount(); + for (sal_uInt32 i = 0; i < nLength; i++) + { + Reference< XForm > xForm( xForms->getByIndex(i), UNO_QUERY ); + if ( xForm.is() ) + setController( xForm, nullptr ); + } + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } +} + +FormViewPageWindowAdapter::~FormViewPageWindowAdapter() +{ +} + +void FormViewPageWindowAdapter::dispose() +{ + for ( ::std::vector< Reference< XFormController > >::const_iterator i = m_aControllerList.begin(); + i != m_aControllerList.end(); + ++i + ) + { + try + { + Reference< XFormController > xController( *i, UNO_SET_THROW ); + + // detaching the events + Reference< XChild > xControllerModel( xController->getModel(), UNO_QUERY ); + if ( xControllerModel.is() ) + { + Reference< XEventAttacherManager > xEventManager( xControllerModel->getParent(), UNO_QUERY_THROW ); + Reference< XInterface > xControllerNormalized( xController, UNO_QUERY_THROW ); + xEventManager->detach( i - m_aControllerList.begin(), xControllerNormalized ); + } + + // dispose the formcontroller + xController->dispose(); + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + m_aControllerList.clear(); +} + +sal_Bool SAL_CALL FormViewPageWindowAdapter::hasElements() +{ + return getCount() != 0; +} + +Type SAL_CALL FormViewPageWindowAdapter::getElementType() +{ + return cppu::UnoType<XFormController>::get(); +} + +// XIndexAccess +sal_Int32 SAL_CALL FormViewPageWindowAdapter::getCount() +{ + return m_aControllerList.size(); +} + +Any SAL_CALL FormViewPageWindowAdapter::getByIndex(sal_Int32 nIndex) +{ + if (nIndex < 0 || + nIndex >= getCount()) + throw IndexOutOfBoundsException(); + + Any aElement; + aElement <<= m_aControllerList[nIndex]; + return aElement; +} + +void SAL_CALL FormViewPageWindowAdapter::makeVisible( const Reference< XControl >& Control ) +{ + SolarMutexGuard aSolarGuard; + + Reference< XWindow > xWindow( Control, UNO_QUERY ); + if ( xWindow.is() && m_pViewImpl->getView() && m_pWindow ) + { + awt::Rectangle aRect = xWindow->getPosSize(); + ::tools::Rectangle aNewRect( aRect.X, aRect.Y, aRect.X + aRect.Width, aRect.Y + aRect.Height ); + aNewRect = m_pWindow->PixelToLogic( aNewRect ); + m_pViewImpl->getView()->MakeVisible( aNewRect, *m_pWindow ); + } +} + +static Reference< XFormController > getControllerSearchChildren( const Reference< XIndexAccess > & xIndex, const Reference< XTabControllerModel > & xModel) +{ + if (xIndex.is() && xIndex->getCount()) + { + Reference< XFormController > xController; + + for (sal_Int32 n = xIndex->getCount(); n-- && !xController.is(); ) + { + xIndex->getByIndex(n) >>= xController; + if (xModel.get() == xController->getModel().get()) + return xController; + else + { + xController = getControllerSearchChildren(xController, xModel); + if ( xController.is() ) + return xController; + } + } + } + return Reference< XFormController > (); +} + +// Search the according controller +Reference< XFormController > FormViewPageWindowAdapter::getController( const Reference< XForm > & xForm ) const +{ + Reference< XTabControllerModel > xModel(xForm, UNO_QUERY); + for (const auto& rpController : m_aControllerList) + { + if (rpController->getModel().get() == xModel.get()) + return rpController; + + // the current-round controller isn't the right one. perhaps one of its children ? + Reference< XFormController > xChildSearch = getControllerSearchChildren(Reference< XIndexAccess > (rpController, UNO_QUERY), xModel); + if (xChildSearch.is()) + return xChildSearch; + } + return Reference< XFormController > (); +} + + +void FormViewPageWindowAdapter::setController(const Reference< XForm > & xForm, const Reference< XFormController >& _rxParentController ) +{ + DBG_ASSERT( xForm.is(), "FormViewPageWindowAdapter::setController: there should be a form!" ); + Reference< XIndexAccess > xFormCps(xForm, UNO_QUERY); + if (!xFormCps.is()) + return; + + Reference< XTabControllerModel > xTabOrder(xForm, UNO_QUERY); + + // create a form controller + Reference< XFormController > xController( FormController::create(m_xContext) ); + + Reference< XInteractionHandler > xHandler; + if ( _rxParentController.is() ) + xHandler = _rxParentController->getInteractionHandler(); + else + { + // TODO: should we create a default handler? Not really necessary, since the + // FormController itself has a default fallback + } + if ( xHandler.is() ) + xController->setInteractionHandler( xHandler ); + + xController->setContext( this ); + + xController->setModel( xTabOrder ); + xController->setContainer( m_xControlContainer ); + xController->activateTabOrder(); + xController->addActivateListener( m_pViewImpl ); + + if ( _rxParentController.is() ) + _rxParentController->addChildController( xController ); + else + { + m_aControllerList.push_back(xController); + + xController->setParent( *this ); + + // attaching the events + Reference< XEventAttacherManager > xEventManager( xForm->getParent(), UNO_QUERY ); + xEventManager->attach(m_aControllerList.size() - 1, Reference<XInterface>( xController, UNO_QUERY ), makeAny(xController) ); + } + + // now go through the subforms + sal_uInt32 nLength = xFormCps->getCount(); + Reference< XForm > xSubForm; + for (sal_uInt32 i = 0; i < nLength; i++) + { + if ( xFormCps->getByIndex(i) >>= xSubForm ) + setController( xSubForm, xController ); + } +} + + +void FormViewPageWindowAdapter::updateTabOrder( const Reference< XForm >& _rxForm ) +{ + OSL_PRECOND( _rxForm.is(), "FormViewPageWindowAdapter::updateTabOrder: illegal argument!" ); + if ( !_rxForm.is() ) + return; + + try + { + Reference< XTabController > xTabCtrl( getController( _rxForm ).get() ); + if ( xTabCtrl.is() ) + { // if there already is a TabController for this form, then delegate the "updateTabOrder" request + xTabCtrl->activateTabOrder(); + } + else + { // otherwise, create a TabController + + // if it's a sub form, then we must ensure there exist TabControllers + // for all its ancestors, too + Reference< XForm > xParentForm( _rxForm->getParent(), UNO_QUERY ); + // there is a parent form -> look for the respective controller + Reference< XFormController > xParentController; + if ( xParentForm.is() ) + xParentController = getController( xParentForm ); + + setController( _rxForm, xParentController ); + } + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +FmXFormView::FmXFormView(FmFormView* _pView ) + :m_pMarkedGrid(nullptr) + ,m_pView(_pView) + ,m_nActivationEvent(nullptr) + ,m_nErrorMessageEvent( nullptr ) + ,m_nAutoFocusEvent( nullptr ) + ,m_nControlWizardEvent( nullptr ) + ,m_bFirstActivation( true ) + ,m_isTabOrderUpdateSuspended( false ) +{ +} + + +void FmXFormView::cancelEvents() +{ + if ( m_nActivationEvent ) + { + Application::RemoveUserEvent( m_nActivationEvent ); + m_nActivationEvent = nullptr; + } + + if ( m_nErrorMessageEvent ) + { + Application::RemoveUserEvent( m_nErrorMessageEvent ); + m_nErrorMessageEvent = nullptr; + } + + if ( m_nAutoFocusEvent ) + { + Application::RemoveUserEvent( m_nAutoFocusEvent ); + m_nAutoFocusEvent = nullptr; + } + + if ( m_nControlWizardEvent ) + { + Application::RemoveUserEvent( m_nControlWizardEvent ); + m_nControlWizardEvent = nullptr; + } +} + + +void FmXFormView::notifyViewDying( ) +{ + DBG_ASSERT( m_pView, "FmXFormView::notifyViewDying: my view already died!" ); + m_pView = nullptr; + cancelEvents(); +} + + +FmXFormView::~FmXFormView() +{ + DBG_ASSERT( m_aPageWindowAdapters.empty(), "FmXFormView::~FmXFormView: Window list not empty!" ); + for (const auto& rpAdapter : m_aPageWindowAdapters) + { + rpAdapter->dispose(); + } + + cancelEvents(); +} + +// EventListener + +void SAL_CALL FmXFormView::disposing(const EventObject& Source) +{ + if ( m_xWindow.is() && Source.Source == m_xWindow ) + { + m_xWindow->removeFocusListener(this); + if ( m_pView ) + { + m_pView->SetMoveOutside( false, FmFormView::ImplAccess() ); + } + m_xWindow = nullptr; + } +} + +// XFormControllerListener + +void SAL_CALL FmXFormView::formActivated(const EventObject& rEvent) +{ + if ( m_pView && m_pView->GetFormShell() && m_pView->GetFormShell()->GetImpl() ) + m_pView->GetFormShell()->GetImpl()->formActivated( rEvent ); +} + + +void SAL_CALL FmXFormView::formDeactivated(const EventObject& rEvent) +{ + if ( m_pView && m_pView->GetFormShell() && m_pView->GetFormShell()->GetImpl() ) + m_pView->GetFormShell()->GetImpl()->formDeactivated( rEvent ); +} + +// XContainerListener + +void SAL_CALL FmXFormView::elementInserted(const ContainerEvent& evt) +{ + try + { + Reference< XControlContainer > xControlContainer( evt.Source, UNO_QUERY_THROW ); + Reference< XControl > xControl( evt.Element, UNO_QUERY_THROW ); + Reference< XFormComponent > xControlModel( xControl->getModel(), UNO_QUERY_THROW ); + Reference< XForm > xForm( xControlModel->getParent(), UNO_QUERY_THROW ); + + if ( m_isTabOrderUpdateSuspended ) + { + // remember the container and the control, so we can update the tab order on resumeTabOrderUpdate + m_aNeedTabOrderUpdate[ xControlContainer ].insert( xForm ); + } + else + { + PFormViewPageWindowAdapter pAdapter = findWindow( xControlContainer ); + if ( pAdapter.is() ) + pAdapter->updateTabOrder( xForm ); + } + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +void SAL_CALL FmXFormView::elementReplaced(const ContainerEvent& evt) +{ + elementInserted(evt); +} + + +void SAL_CALL FmXFormView::elementRemoved(const ContainerEvent& /*evt*/) +{ +} + + +PFormViewPageWindowAdapter FmXFormView::findWindow( const Reference< XControlContainer >& _rxCC ) const +{ + auto i = std::find_if(m_aPageWindowAdapters.begin(), m_aPageWindowAdapters.end(), + [&_rxCC](const PFormViewPageWindowAdapter& rpAdapter) { return _rxCC == rpAdapter->getControlContainer(); }); + if (i != m_aPageWindowAdapters.end()) + return *i; + return nullptr; +} + + +void FmXFormView::addWindow(const SdrPageWindow& rWindow) +{ + FmFormPage* pFormPage = dynamic_cast<FmFormPage*>( rWindow.GetPageView().GetPage() ); + if ( !pFormPage ) + return; + + const Reference< XControlContainer >& xCC = rWindow.GetControlContainer(); + if ( xCC.is() + && ( !findWindow( xCC ).is() ) + ) + { + PFormViewPageWindowAdapter pAdapter = new FormViewPageWindowAdapter( comphelper::getProcessComponentContext(), rWindow, this ); + m_aPageWindowAdapters.push_back( pAdapter ); + + // listen at the ControlContainer to notice changes + Reference< XContainer > xContainer( xCC, UNO_QUERY ); + if ( xContainer.is() ) + xContainer->addContainerListener( this ); + } +} + + +void FmXFormView::removeWindow( const Reference< XControlContainer >& _rxCC ) +{ + // Is called if + // - the design mode is being switched to + // - a window is deleted while in the design mode + // - the control container for a window is removed while the active mode is on + + auto i = std::find_if(m_aPageWindowAdapters.begin(), m_aPageWindowAdapters.end(), + [&_rxCC](const PFormViewPageWindowAdapter& rpAdapter) { return _rxCC == rpAdapter->getControlContainer(); }); + if (i != m_aPageWindowAdapters.end()) + { + Reference< XContainer > xContainer( _rxCC, UNO_QUERY ); + if ( xContainer.is() ) + xContainer->removeContainerListener( this ); + + (*i)->dispose(); + m_aPageWindowAdapters.erase( i ); + } +} + + +void FmXFormView::displayAsyncErrorMessage( const SQLErrorEvent& _rEvent ) +{ + DBG_ASSERT( nullptr == m_nErrorMessageEvent, "FmXFormView::displayAsyncErrorMessage: not too fast, please!" ); + // This should not happen - usually, the PostUserEvent is faster than any possible user + // interaction which could trigger a new error. If it happens, we need a queue for the events. + m_aAsyncError = _rEvent; + m_nErrorMessageEvent = Application::PostUserEvent( LINK( this, FmXFormView, OnDelayedErrorMessage ) ); +} + + +IMPL_LINK_NOARG(FmXFormView, OnDelayedErrorMessage, void*, void) +{ + m_nErrorMessageEvent = nullptr; + displayException( m_aAsyncError ); +} + + +void FmXFormView::onFirstViewActivation( const FmFormModel* _pDocModel ) +{ + if ( _pDocModel && _pDocModel->GetAutoControlFocus() ) + m_nAutoFocusEvent = Application::PostUserEvent( LINK( this, FmXFormView, OnAutoFocus ) ); +} + + +void FmXFormView::suspendTabOrderUpdate() +{ + OSL_ENSURE( !m_isTabOrderUpdateSuspended, "FmXFormView::suspendTabOrderUpdate: nesting not allowed!" ); + m_isTabOrderUpdateSuspended = true; +} + + +void FmXFormView::resumeTabOrderUpdate() +{ + OSL_ENSURE( m_isTabOrderUpdateSuspended, "FmXFormView::resumeTabOrderUpdate: not suspended!" ); + m_isTabOrderUpdateSuspended = false; + + // update the tab orders for all components which were collected since the suspendTabOrderUpdate call. + for (const auto& rContainer : m_aNeedTabOrderUpdate) + { + PFormViewPageWindowAdapter pAdapter = findWindow( rContainer.first ); + if ( !pAdapter.is() ) + continue; + + for (const auto& rForm : rContainer.second) + { + pAdapter->updateTabOrder( rForm ); + } + } + m_aNeedTabOrderUpdate.clear(); +} + +namespace +{ + bool isActivableDatabaseForm(const Reference< XFormController > &xController) + { + // only database forms are to be activated + Reference< XRowSet > xForm(xController->getModel(), UNO_QUERY); + if ( !xForm.is() || !getConnection( xForm ).is() ) + return false; + + Reference< XPropertySet > xFormSet( xForm, UNO_QUERY ); + if ( !xFormSet.is() ) + { + SAL_WARN( "svx.form", "FmXFormView::OnActivate: a form which does not have properties?" ); + return false; + } + + const OUString aSource = ::comphelper::getString( xFormSet->getPropertyValue( FM_PROP_COMMAND ) ); + + return !aSource.isEmpty(); + } + + class find_active_databaseform + { + const Reference< XFormController > xActiveController; + + public: + + explicit find_active_databaseform( const Reference< XFormController >& _xActiveController ) + : xActiveController(_xActiveController ) + {} + + Reference < XFormController > operator() (const Reference< XFormController > &xController) + { + if(xController == xActiveController && isActivableDatabaseForm(xController)) + return xController; + + if ( !xController.is() ) + { + SAL_WARN( "svx.form", "FmXFormView::OnActivate: a form controller which does not have children?" ); + return nullptr; + } + + for(sal_Int32 i = 0; i < xController->getCount(); ++i) + { + const Any a(xController->getByIndex(i)); + Reference < XFormController > xI; + if ((a >>= xI) && xI.is()) + { + Reference < XFormController > xRes(operator()(xI)); + if (xRes.is()) + return xRes; + } + } + + return nullptr; + } + }; +} + + +IMPL_LINK_NOARG(FmXFormView, OnActivate, void*, void) +{ + m_nActivationEvent = nullptr; + + if ( !m_pView ) + { + OSL_FAIL( "FmXFormView::OnActivate: well... seems we have a timing problem (the view already died)!" ); + return; + } + + // setting the controller to activate + if (m_pView->GetFormShell() && m_pView->GetActualOutDev() && m_pView->GetActualOutDev()->GetOutDevType() == OUTDEV_WINDOW) + { + FmXFormShell* const pShImpl = m_pView->GetFormShell()->GetImpl(); + + if(!pShImpl) + return; + + find_active_databaseform fad(pShImpl->getActiveController_Lock()); + + vcl::Window* pWindow = const_cast<vcl::Window*>(static_cast<const vcl::Window*>(m_pView->GetActualOutDev())); + PFormViewPageWindowAdapter pAdapter = m_aPageWindowAdapters.empty() ? nullptr : m_aPageWindowAdapters[0]; + for (const auto& rpPageWindowAdapter : m_aPageWindowAdapters) + { + if ( pWindow == rpPageWindowAdapter->getWindow() ) + pAdapter = rpPageWindowAdapter; + } + + if ( pAdapter.is() ) + { + Reference< XFormController > xControllerToActivate; + for (const Reference< XFormController > & xController : pAdapter->GetList()) + { + if ( !xController.is() ) + continue; + + { + Reference< XFormController > xActiveController(fad(xController)); + if (xActiveController.is()) + { + xControllerToActivate = xActiveController; + break; + } + } + + if(xControllerToActivate.is() || !isActivableDatabaseForm(xController)) + continue; + + xControllerToActivate = xController; + } + pShImpl->setActiveController_Lock(xControllerToActivate); + } + } +} + + +void FmXFormView::Activate(bool bSync) +{ + if (m_nActivationEvent) + { + Application::RemoveUserEvent(m_nActivationEvent); + m_nActivationEvent = nullptr; + } + + if (bSync) + { + LINK(this,FmXFormView,OnActivate).Call(nullptr); + } + else + m_nActivationEvent = Application::PostUserEvent(LINK(this,FmXFormView,OnActivate)); +} + + +void FmXFormView::Deactivate(bool bDeactivateController) +{ + if (m_nActivationEvent) + { + Application::RemoveUserEvent(m_nActivationEvent); + m_nActivationEvent = nullptr; + } + + FmXFormShell* pShImpl = m_pView->GetFormShell() ? m_pView->GetFormShell()->GetImpl() : nullptr; + if (pShImpl && bDeactivateController) + pShImpl->setActiveController_Lock(nullptr); +} + + +FmFormShell* FmXFormView::GetFormShell() const +{ + return m_pView ? m_pView->GetFormShell() : nullptr; +} + +void FmXFormView::AutoFocus() +{ + if (m_nAutoFocusEvent) + Application::RemoveUserEvent(m_nAutoFocusEvent); + + m_nAutoFocusEvent = Application::PostUserEvent(LINK(this, FmXFormView, OnAutoFocus)); +} + + +bool FmXFormView::isFocusable( const Reference< XControl >& i_rControl ) +{ + if ( !i_rControl.is() ) + return false; + + try + { + Reference< XPropertySet > xModelProps( i_rControl->getModel(), UNO_QUERY_THROW ); + + // only enabled controls are allowed to participate + bool bEnabled = false; + OSL_VERIFY( xModelProps->getPropertyValue( FM_PROP_ENABLED ) >>= bEnabled ); + if ( !bEnabled ) + return false; + + // check the class id of the control model + sal_Int16 nClassId = FormComponentType::CONTROL; + OSL_VERIFY( xModelProps->getPropertyValue( FM_PROP_CLASSID ) >>= nClassId ); + + // controls which are not focussable + if ( ( FormComponentType::CONTROL != nClassId ) + && ( FormComponentType::IMAGEBUTTON != nClassId ) + && ( FormComponentType::GROUPBOX != nClassId ) + && ( FormComponentType::FIXEDTEXT != nClassId ) + && ( FormComponentType::HIDDENCONTROL != nClassId ) + && ( FormComponentType::IMAGECONTROL != nClassId ) + && ( FormComponentType::SCROLLBAR != nClassId ) + && ( FormComponentType::SPINBUTTON!= nClassId ) + ) + { + return true; + } + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return false; +} + + +static Reference< XControl > lcl_firstFocussableControl( const Sequence< Reference< XControl > >& _rControls ) +{ + Reference< XControl > xReturn; + + // loop through all the controls + for ( auto const & control : _rControls ) + { + if ( !control.is() ) + continue; + + if ( FmXFormView::isFocusable( control ) ) + { + xReturn = control; + break; + } + } + + if ( !xReturn.is() && _rControls.hasElements() ) + xReturn = _rControls[0]; + + return xReturn; +} + + +namespace +{ + + void lcl_ensureControlsOfFormExist_nothrow( const SdrPage& _rPage, const SdrView& _rView, const vcl::Window& _rWindow, const Reference< XForm >& _rxForm ) + { + try + { + Reference< XInterface > xNormalizedForm( _rxForm, UNO_QUERY_THROW ); + + SdrObjListIter aSdrObjectLoop( &_rPage, SdrIterMode::DeepNoGroups ); + while ( aSdrObjectLoop.IsMore() ) + { + FmFormObj* pFormObject = FmFormObj::GetFormObject( aSdrObjectLoop.Next() ); + if ( !pFormObject ) + continue; + + Reference< XChild > xModel( pFormObject->GetUnoControlModel(), UNO_QUERY_THROW ); + Reference< XInterface > xModelParent( xModel->getParent(), UNO_QUERY ); + + if ( xNormalizedForm.get() != xModelParent.get() ) + continue; + + pFormObject->GetUnoControl( _rView, _rWindow ); + } + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } +} + + +Reference< XFormController > FmXFormView::getFormController( const Reference< XForm >& _rxForm, const OutputDevice& _rDevice ) const +{ + Reference< XFormController > xController; + + for (const PFormViewPageWindowAdapter& pAdapter : m_aPageWindowAdapters) + { + if ( !pAdapter ) + { + SAL_WARN( "svx.form", "FmXFormView::getFormController: invalid page window adapter!" ); + continue; + } + + if ( pAdapter->getWindow() != &_rDevice ) + // wrong device + continue; + + xController = pAdapter->getController( _rxForm ); + if ( xController.is() ) + break; + } + return xController; +} + + +IMPL_LINK_NOARG(FmXFormView, OnAutoFocus, void*, void) +{ + m_nAutoFocusEvent = nullptr; + + // go to the first form of our page, examine it's TabController, go to its first (in terms of the tab order) + // control, give it the focus + + SdrPageView *pPageView = m_pView ? m_pView->GetSdrPageView() : nullptr; + SdrPage *pSdrPage = pPageView ? pPageView->GetPage() : nullptr; + // get the forms collection of the page we belong to + FmFormPage* pPage = dynamic_cast<FmFormPage*>( pSdrPage ); + Reference< XIndexAccess > xForms( pPage ? Reference< XIndexAccess >( pPage->GetForms() ) : Reference< XIndexAccess >() ); + + const PFormViewPageWindowAdapter pAdapter = m_aPageWindowAdapters.empty() ? nullptr : m_aPageWindowAdapters[0]; + const vcl::Window* pWindow = pAdapter ? pAdapter->getWindow() : nullptr; + + ENSURE_OR_RETURN_VOID( xForms.is() && pWindow, "FmXFormView::OnAutoFocus: could not collect all essentials!" ); + + try + { + // go for the tab controller of the first form + if ( !xForms->getCount() ) + return; + Reference< XForm > xForm( xForms->getByIndex( 0 ), UNO_QUERY_THROW ); + Reference< XTabController > xTabController( pAdapter->getController( xForm ), UNO_QUERY_THROW ); + + // go for the first control of the controller + Sequence< Reference< XControl > > aControls( xTabController->getControls() ); + if ( !aControls.hasElements() ) + { + Reference< XElementAccess > xFormElementAccess( xForm, UNO_QUERY_THROW ); + if (xFormElementAccess->hasElements() && pPage && m_pView) + { + // there are control models in the form, but no controls, yet. + // Well, since some time controls are created on demand only. In particular, + // they're normally created when they're first painted. + // Unfortunately, the FormController does not have any way to + // trigger the creation itself, so we must hack this ... + lcl_ensureControlsOfFormExist_nothrow( *pPage, *m_pView, *pWindow, xForm ); + aControls = xTabController->getControls(); + OSL_ENSURE( aControls.hasElements(), "FmXFormView::OnAutoFocus: no controls at all!" ); + } + } + + // set the focus to this first control + Reference< XWindow > xControlWindow( lcl_firstFocussableControl( aControls ), UNO_QUERY ); + if ( !xControlWindow.is() ) + return; + + xControlWindow->setFocus(); + + // ensure that the control is visible + // 80210 - 12/07/00 - FS + const vcl::Window* pCurrentWindow = m_pView ? dynamic_cast<const vcl::Window*>(m_pView->GetActualOutDev()) : nullptr; + if ( pCurrentWindow ) + { + awt::Rectangle aRect = xControlWindow->getPosSize(); + ::tools::Rectangle aNonUnoRect( aRect.X, aRect.Y, aRect.X + aRect.Width, aRect.Y + aRect.Height ); + m_pView->MakeVisible( pCurrentWindow->PixelToLogic( aNonUnoRect ), *const_cast< vcl::Window* >( pCurrentWindow ) ); + } + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +void FmXFormView::onCreatedFormObject( FmFormObj const & _rFormObject ) +{ + FmFormShell* pShell = m_pView ? m_pView->GetFormShell() : nullptr; + FmXFormShell* pShellImpl = pShell ? pShell->GetImpl() : nullptr; + OSL_ENSURE( pShellImpl, "FmXFormView::onCreatedFormObject: no form shell!" ); + if ( !pShellImpl ) + return; + + // it is valid that the form shell's forms collection is not initialized, yet + pShellImpl->UpdateForms_Lock(true); + + m_xLastCreatedControlModel.set( _rFormObject.GetUnoControlModel(), UNO_QUERY ); + if ( !m_xLastCreatedControlModel.is() ) + return; + + // some initial property defaults + FormControlFactory aControlFactory; + aControlFactory.initializeControlModel(pShellImpl->getDocumentType_Lock(), _rFormObject); + + if (!pShellImpl->GetWizardUsing_Lock()) + return; + + // #i31958# don't call wizards in XForms mode + if (pShellImpl->isEnhancedForm_Lock()) + return; + + // #i46898# no wizards if there is no Base installed - currently, all wizards are + // database related + if ( !SvtModuleOptions().IsModuleInstalled( SvtModuleOptions::EModule::DATABASE ) ) + return; + + if ( m_nControlWizardEvent ) + Application::RemoveUserEvent( m_nControlWizardEvent ); + m_nControlWizardEvent = Application::PostUserEvent( LINK( this, FmXFormView, OnStartControlWizard ) ); +} + +void FmXFormView::breakCreateFormObject() +{ + if (m_nControlWizardEvent != nullptr) + { + Application::RemoveUserEvent(m_nControlWizardEvent); + m_nControlWizardEvent = nullptr; + } + m_xLastCreatedControlModel.clear(); +} + +IMPL_LINK_NOARG( FmXFormView, OnStartControlWizard, void*, void ) +{ + m_nControlWizardEvent = nullptr; + OSL_PRECOND( m_xLastCreatedControlModel.is(), "FmXFormView::OnStartControlWizard: illegal call!" ); + if ( !m_xLastCreatedControlModel.is() ) + return; + + sal_Int16 nClassId = FormComponentType::CONTROL; + try + { + OSL_VERIFY( m_xLastCreatedControlModel->getPropertyValue( FM_PROP_CLASSID ) >>= nClassId ); + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + const char* pWizardAsciiName = nullptr; + switch ( nClassId ) + { + case FormComponentType::GRIDCONTROL: + pWizardAsciiName = "com.sun.star.sdb.GridControlAutoPilot"; + break; + case FormComponentType::LISTBOX: + case FormComponentType::COMBOBOX: + pWizardAsciiName = "com.sun.star.sdb.ListComboBoxAutoPilot"; + break; + case FormComponentType::GROUPBOX: + pWizardAsciiName = "com.sun.star.sdb.GroupBoxAutoPilot"; + break; + } + + if ( pWizardAsciiName ) + { + // build the argument list + ::comphelper::NamedValueCollection aWizardArgs; + aWizardArgs.put("ObjectModel", m_xLastCreatedControlModel); + const vcl::Window* pCurrentWindow = m_pView ? dynamic_cast<const vcl::Window*>(m_pView->GetActualOutDev()) : nullptr; + aWizardArgs.put("ParentWindow", VCLUnoHelper::GetInterface(const_cast<vcl::Window*>(pCurrentWindow))); + + // create the wizard object + Reference< XExecutableDialog > xWizard; + try + { + Reference<XComponentContext> xContext = comphelper::getProcessComponentContext(); + xWizard.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext( OUString::createFromAscii(pWizardAsciiName), aWizardArgs.getWrappedPropertyValues(), xContext ), UNO_QUERY); + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + if ( !xWizard.is() ) + { + ShowServiceNotAvailableError( nullptr, OUString::createFromAscii(pWizardAsciiName), true ); + } + else + { + // execute the wizard + try + { + xWizard->execute(); + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + } + + m_xLastCreatedControlModel.clear(); +} + + +namespace +{ + void lcl_insertIntoFormComponentHierarchy_throw( const FmFormView& _rView, const SdrUnoObj& _rSdrObj, + const Reference< XDataSource >& _rxDataSource, const OUString& _rDataSourceName, + const OUString& _rCommand, const sal_Int32 _nCommandType ) + { + FmFormPage& rPage = static_cast< FmFormPage& >( *_rView.GetSdrPageView()->GetPage() ); + + Reference< XFormComponent > xFormComponent( _rSdrObj.GetUnoControlModel(), UNO_QUERY_THROW ); + Reference< XForm > xTargetForm( + rPage.GetImpl().findPlaceInFormComponentHierarchy( xFormComponent, _rxDataSource, _rDataSourceName, _rCommand, _nCommandType ), + UNO_SET_THROW ); + + FmFormPageImpl::setUniqueName( xFormComponent, xTargetForm ); + + Reference< XIndexContainer > xFormAsContainer( xTargetForm, UNO_QUERY_THROW ); + xFormAsContainer->insertByIndex( xFormAsContainer->getCount(), makeAny( xFormComponent ) ); + } +} + + +SdrObjectUniquePtr FmXFormView::implCreateFieldControl( const svx::ODataAccessDescriptor& _rColumnDescriptor ) +{ + // not if we're in design mode + if ( !m_pView->IsDesignMode() ) + return nullptr; + + OUString sCommand, sFieldName; + sal_Int32 nCommandType = CommandType::COMMAND; + SharedConnection xConnection; + + OUString sDataSource = _rColumnDescriptor.getDataSource(); + _rColumnDescriptor[ DataAccessDescriptorProperty::Command ] >>= sCommand; + _rColumnDescriptor[ DataAccessDescriptorProperty::ColumnName ] >>= sFieldName; + _rColumnDescriptor[ DataAccessDescriptorProperty::CommandType ] >>= nCommandType; + { + Reference< XConnection > xExternalConnection; + _rColumnDescriptor[ DataAccessDescriptorProperty::Connection ] >>= xExternalConnection; + xConnection.reset( xExternalConnection, SharedConnection::NoTakeOwnership ); + } + + if ( sCommand.isEmpty() + || sFieldName.isEmpty() + || ( sDataSource.isEmpty() + && !xConnection.is() + ) + ) + { + OSL_FAIL( "FmXFormView::implCreateFieldControl: nonsense!" ); + } + + Reference< XDataSource > xDataSource; + SQLErrorEvent aError; + try + { + if ( xConnection.is() && !xDataSource.is() && sDataSource.isEmpty() ) + { + Reference< XChild > xChild( xConnection, UNO_QUERY ); + if ( xChild.is() ) + xDataSource.set(xChild->getParent(), css::uno::UNO_QUERY); + } + + // obtain the data source + if ( !xDataSource.is() ) + xDataSource = getDataSource( sDataSource, comphelper::getProcessComponentContext() ); + + // and the connection, if necessary + if ( !xConnection.is() ) + xConnection.reset( getConnection_withFeedback( + sDataSource, + OUString(), + OUString(), + comphelper::getProcessComponentContext(), + nullptr + ) ); + } + catch (const SQLException&) + { + aError.Reason = ::cppu::getCaughtException(); + } + catch (const Exception& ) + { + /* will be asserted below */ + } + if (aError.Reason.hasValue()) + { + displayAsyncErrorMessage( aError ); + return nullptr; + } + + // need a data source and a connection here + if (!xDataSource.is() || !xConnection.is()) + { + OSL_FAIL("FmXFormView::implCreateFieldControl : could not retrieve the data source or the connection!"); + return nullptr; + } + + Reference< XComponent > xKeepFieldsAlive; + // go + try + { + // determine the table/query field which we should create a control for + Reference< XPropertySet > xField; + + Reference< XNameAccess > xFields = getFieldsByCommandDescriptor( + xConnection, nCommandType, sCommand, xKeepFieldsAlive ); + + if (xFields.is() && xFields->hasByName(sFieldName)) + xFields->getByName(sFieldName) >>= xField; + if ( !xField.is() ) + return nullptr; + + Reference< XNumberFormatsSupplier > xSupplier( getNumberFormats( xConnection ), UNO_SET_THROW ); + Reference< XNumberFormats > xNumberFormats( xSupplier->getNumberFormats(), UNO_SET_THROW ); + + OUString sLabelPostfix; + + + // only for text size + OutputDevice* pOutDev = nullptr; + if (m_pView->GetActualOutDev() && m_pView->GetActualOutDev()->GetOutDevType() == OUTDEV_WINDOW) + pOutDev = const_cast<OutputDevice*>(m_pView->GetActualOutDev()); + else + {// find OutDev + if (SdrPageView* pPageView = m_pView->GetSdrPageView()) + { + // const SdrPageViewWinList& rWinList = pPageView->GetWinList(); + // const SdrPageViewWindows& rPageViewWindows = pPageView->GetPageViewWindows(); + + for( sal_uInt32 i = 0; i < pPageView->PageWindowCount(); i++ ) + { + const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(i); + + if( rPageWindow.GetPaintWindow().OutputToWindow()) + { + pOutDev = &rPageWindow.GetPaintWindow().GetOutputDevice(); + break; + } + } + } + } + + if ( !pOutDev ) + return nullptr; + + sal_Int32 nDataType = ::comphelper::getINT32(xField->getPropertyValue(FM_PROP_FIELDTYPE)); + if ((DataType::BINARY == nDataType) || (DataType::VARBINARY == nDataType)) + return nullptr; + + + // determine the control type by examining the data type of the bound column + sal_uInt16 nOBJID = 0; + bool bDateNTimeField = false; + + bool bIsCurrency = false; + if (::comphelper::hasProperty(FM_PROP_ISCURRENCY, xField)) + bIsCurrency = ::comphelper::getBOOL(xField->getPropertyValue(FM_PROP_ISCURRENCY)); + + if (bIsCurrency) + nOBJID = OBJ_FM_CURRENCYFIELD; + else + switch (nDataType) + { + case DataType::BLOB: + case DataType::LONGVARBINARY: + nOBJID = OBJ_FM_IMAGECONTROL; + break; + case DataType::LONGVARCHAR: + case DataType::CLOB: + nOBJID = OBJ_FM_EDIT; + break; + case DataType::BINARY: + case DataType::VARBINARY: + return nullptr; + case DataType::BIT: + case DataType::BOOLEAN: + nOBJID = OBJ_FM_CHECKBOX; + break; + case DataType::TINYINT: + case DataType::SMALLINT: + case DataType::INTEGER: + nOBJID = OBJ_FM_NUMERICFIELD; + break; + case DataType::REAL: + case DataType::DOUBLE: + case DataType::NUMERIC: + case DataType::DECIMAL: + nOBJID = OBJ_FM_FORMATTEDFIELD; + break; + case DataType::TIMESTAMP: + bDateNTimeField = true; + sLabelPostfix = SvxResId(RID_STR_POSTFIX_DATE); + [[fallthrough]]; + case DataType::DATE: + nOBJID = OBJ_FM_DATEFIELD; + break; + case DataType::TIME: + nOBJID = OBJ_FM_TIMEFIELD; + break; + case DataType::CHAR: + case DataType::VARCHAR: + default: + nOBJID = OBJ_FM_EDIT; + break; + } + if (!nOBJID) + return nullptr; + + std::unique_ptr<SdrUnoObj, SdrObjectFreeOp> pLabel; + std::unique_ptr<SdrUnoObj, SdrObjectFreeOp> pControl; + if ( !createControlLabelPair( *pOutDev, 0, 0, xField, xNumberFormats, nOBJID, sLabelPostfix, + pLabel, pControl, xDataSource, sDataSource, sCommand, nCommandType ) + ) + { + return nullptr; + } + + + // group objects + bool bCheckbox = ( OBJ_FM_CHECKBOX == nOBJID ); + OSL_ENSURE( !bCheckbox || !pLabel, "FmXFormView::implCreateFieldControl: why was there a label created for a check box?" ); + if ( bCheckbox ) + return SdrObjectUniquePtr(pControl.release()); + + SdrObjGroup* pGroup = new SdrObjGroup(getView()->getSdrModelFromSdrView()); + SdrObjList* pObjList = pGroup->GetSubList(); + pObjList->InsertObject( pLabel.release() ); + pObjList->InsertObject( pControl.release() ); + + if ( bDateNTimeField ) + { // so far we created a date field only, but we also need a time field + if ( createControlLabelPair( *pOutDev, 0, 1000, xField, xNumberFormats, OBJ_FM_TIMEFIELD, + SvxResId(RID_STR_POSTFIX_TIME), pLabel, pControl, + xDataSource, sDataSource, sCommand, nCommandType ) + ) + { + pObjList->InsertObject( pLabel.release() ); + pObjList->InsertObject( pControl.release() ); + } + } + + return SdrObjectUniquePtr(pGroup); // and done + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + + return nullptr; +} + + +SdrObjectUniquePtr FmXFormView::implCreateXFormsControl( const svx::OXFormsDescriptor &_rDesc ) +{ + // not if we're in design mode + if ( !m_pView->IsDesignMode() ) + return nullptr; + + // go + try + { + // determine the table/query field which we should create a control for + Reference< XNumberFormats > xNumberFormats; + OUString sLabelPostfix = _rDesc.szName; + + + // only for text size + OutputDevice* pOutDev = nullptr; + if (m_pView->GetActualOutDev() && m_pView->GetActualOutDev()->GetOutDevType() == OUTDEV_WINDOW) + pOutDev = const_cast<OutputDevice*>(m_pView->GetActualOutDev()); + else + {// find OutDev + if (SdrPageView* pPageView = m_pView->GetSdrPageView()) + { + // const SdrPageViewWinList& rWinList = pPageView->GetWinList(); + // const SdrPageViewWindows& rPageViewWindows = pPageView->GetPageViewWindows(); + + for( sal_uInt32 i = 0; i < pPageView->PageWindowCount(); i++ ) + { + const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(i); + + if( rPageWindow.GetPaintWindow().GetOutputDevice().GetOutDevType() == OUTDEV_WINDOW) + { + pOutDev = &rPageWindow.GetPaintWindow().GetOutputDevice(); + break; + } + } + } + } + + if ( !pOutDev ) + return nullptr; + + + // The service name decides which control should be created + sal_uInt16 nOBJID = OBJ_FM_EDIT; + if(_rDesc.szServiceName == FM_SUN_COMPONENT_NUMERICFIELD) + nOBJID = OBJ_FM_NUMERICFIELD; + if(_rDesc.szServiceName == FM_SUN_COMPONENT_CHECKBOX) + nOBJID = OBJ_FM_CHECKBOX; + if(_rDesc.szServiceName == FM_COMPONENT_COMMANDBUTTON) + nOBJID = OBJ_FM_BUTTON; + + Reference< css::form::submission::XSubmission > xSubmission(_rDesc.xPropSet, UNO_QUERY); + + // xform control or submission button? + if ( !xSubmission.is() ) + { + std::unique_ptr<SdrUnoObj, SdrObjectFreeOp> pLabel; + std::unique_ptr<SdrUnoObj, SdrObjectFreeOp> pControl; + if ( !createControlLabelPair( *pOutDev, 0, 0, nullptr, xNumberFormats, nOBJID, sLabelPostfix, + pLabel, pControl, nullptr, "", "", -1 ) + ) + { + return nullptr; + } + + + // Now build the connection between the control and the data item. + Reference< XValueBinding > xValueBinding(_rDesc.xPropSet,UNO_QUERY); + Reference< XBindableValue > xBindableValue(pControl->GetUnoControlModel(),UNO_QUERY); + + DBG_ASSERT( xBindableValue.is(), "FmXFormView::implCreateXFormsControl: control's not bindable!" ); + if ( xBindableValue.is() ) + xBindableValue->setValueBinding(xValueBinding); + + bool bCheckbox = ( OBJ_FM_CHECKBOX == nOBJID ); + OSL_ENSURE( !bCheckbox || !pLabel, "FmXFormView::implCreateXFormsControl: why was there a label created for a check box?" ); + if ( bCheckbox ) + return SdrObjectUniquePtr(pControl.release()); + + + // group objects + SdrObjGroup* pGroup = new SdrObjGroup(getView()->getSdrModelFromSdrView()); + SdrObjList* pObjList = pGroup->GetSubList(); + pObjList->InsertObject(pLabel.release()); + pObjList->InsertObject(pControl.release()); + + return SdrObjectUniquePtr(pGroup); + } + else { + + // create a button control + const MapMode& eTargetMode( pOutDev->GetMapMode() ); + const MapMode eSourceMode(MapUnit::Map100thMM); + const sal_uInt16 nObjID = OBJ_FM_BUTTON; + ::Size controlSize(4000, 500); + FmFormObj *pControl = static_cast<FmFormObj*>( + SdrObjFactory::MakeNewObject( + getView()->getSdrModelFromSdrView(), + SdrInventor::FmForm, + nObjID)); + controlSize.setWidth( long(controlSize.Width() * eTargetMode.GetScaleX()) ); + controlSize.setHeight( long(controlSize.Height() * eTargetMode.GetScaleY()) ); + ::Point controlPos( OutputDevice::LogicToLogic( ::Point( controlSize.Width(), 0 ), eSourceMode, eTargetMode ) ); + ::tools::Rectangle controlRect( controlPos, OutputDevice::LogicToLogic( controlSize, eSourceMode, eTargetMode ) ); + pControl->SetLogicRect(controlRect); + + // set the button label + Reference< XPropertySet > xControlSet(pControl->GetUnoControlModel(), UNO_QUERY); + xControlSet->setPropertyValue(FM_PROP_LABEL, makeAny(_rDesc.szName)); + + // connect the submission with the submission supplier (aka the button) + xControlSet->setPropertyValue( FM_PROP_BUTTON_TYPE, + makeAny( FormButtonType_SUBMIT ) ); + Reference< css::form::submission::XSubmissionSupplier > xSubmissionSupplier(pControl->GetUnoControlModel(), UNO_QUERY); + xSubmissionSupplier->setSubmission(xSubmission); + + return SdrObjectUniquePtr(pControl); + } + } + catch (const Exception&) + { + OSL_FAIL("FmXFormView::implCreateXFormsControl: caught an exception while creating the control !"); + } + + + return nullptr; +} + + +bool FmXFormView::createControlLabelPair( OutputDevice const & _rOutDev, sal_Int32 _nXOffsetMM, sal_Int32 _nYOffsetMM, + const Reference< XPropertySet >& _rxField, const Reference< XNumberFormats >& _rxNumberFormats, + sal_uInt16 _nControlObjectID, const OUString& _rFieldPostfix, + std::unique_ptr<SdrUnoObj, SdrObjectFreeOp>& _rpLabel, + std::unique_ptr<SdrUnoObj, SdrObjectFreeOp>& _rpControl, + const Reference< XDataSource >& _rxDataSource, const OUString& _rDataSourceName, + const OUString& _rCommand, const sal_Int32 _nCommandType ) +{ + if(!createControlLabelPair( + _rOutDev, + _nXOffsetMM, + _nYOffsetMM, + _rxField, + _rxNumberFormats, + _nControlObjectID, + _rFieldPostfix, + SdrInventor::FmForm, + OBJ_FM_FIXEDTEXT, + + // tdf#118963 Hand over a SdrModel to SdrObject-creation. It uses the local m_pView + // and already returning false when nullptr == getView() could be done, but m_pView + // is already dereferenced here in many places (see below), so just use it for now. + getView()->getSdrModelFromSdrView(), + + _rpLabel, + _rpControl)) + { + return false; + } + + // insert the control model(s) into the form component hierarchy + if ( _rpLabel ) + lcl_insertIntoFormComponentHierarchy_throw( *m_pView, *_rpLabel, _rxDataSource, _rDataSourceName, _rCommand, _nCommandType ); + lcl_insertIntoFormComponentHierarchy_throw( *m_pView, *_rpControl, _rxDataSource, _rDataSourceName, _rCommand, _nCommandType ); + + // some context-dependent initializations + FormControlFactory aControlFactory; + if ( _rpLabel ) + aControlFactory.initializeControlModel( impl_getDocumentType(), *_rpLabel ); + aControlFactory.initializeControlModel( impl_getDocumentType(), *_rpControl ); + + return true; +} + + +bool FmXFormView::createControlLabelPair( OutputDevice const & _rOutDev, sal_Int32 _nXOffsetMM, sal_Int32 _nYOffsetMM, + const Reference< XPropertySet >& _rxField, + const Reference< XNumberFormats >& _rxNumberFormats, sal_uInt16 _nControlObjectID, + const OUString& _rFieldPostfix, SdrInventor _nInventor, sal_uInt16 _nLabelObjectID, + SdrModel& _rModel, + std::unique_ptr<SdrUnoObj, SdrObjectFreeOp>& _rpLabel, std::unique_ptr<SdrUnoObj, SdrObjectFreeOp>& _rpControl) +{ + sal_Int32 nDataType = 0; + OUString sFieldName; + Any aFieldName; + if ( _rxField.is() ) + { + nDataType = ::comphelper::getINT32(_rxField->getPropertyValue(FM_PROP_FIELDTYPE)); + aFieldName = _rxField->getPropertyValue(FM_PROP_NAME); + aFieldName >>= sFieldName; + } + + // calculate the positions, respecting the settings of the target device + ::Size aTextSize( _rOutDev.GetTextWidth(sFieldName + _rFieldPostfix), _rOutDev.GetTextHeight() ); + + MapMode eTargetMode( _rOutDev.GetMapMode() ), + eSourceMode( MapUnit::Map100thMM ); + + // text width is at least 4 centimeters + // text height is always half a centimeter + ::Size aDefTxtSize(4000, 500); + ::Size aDefSize(4000, 500); + ::Size aDefImageSize(4000, 4000); + + ::Size aRealSize = OutputDevice::LogicToLogic(aTextSize, eTargetMode, eSourceMode); + aRealSize.setWidth( std::max(aRealSize.Width(), aDefTxtSize.Width()) ); + aRealSize.setHeight( aDefSize.Height() ); + + // adjust to scaling of the target device (#53523#) + aRealSize.setWidth( long(Fraction(aRealSize.Width(), 1) * eTargetMode.GetScaleX()) ); + aRealSize.setHeight( long(Fraction(aRealSize.Height(), 1) * eTargetMode.GetScaleY()) ); + + // for boolean fields, we do not create a label, but just a checkbox + bool bNeedLabel = ( _nControlObjectID != OBJ_FM_CHECKBOX ); + + // the label + ::std::unique_ptr< SdrUnoObj, SdrObjectFreeOp > pLabel; + Reference< XPropertySet > xLabelModel; + + if ( bNeedLabel ) + { + pLabel.reset( dynamic_cast< SdrUnoObj* >( + SdrObjFactory::MakeNewObject( + _rModel, + _nInventor, + _nLabelObjectID))); + + OSL_ENSURE(pLabel, "FmXFormView::createControlLabelPair: could not create the label!"); + + if (!pLabel) + return false; + + xLabelModel.set( pLabel->GetUnoControlModel(), UNO_QUERY ); + if ( xLabelModel.is() ) + { + OUString sLabel; + if ( _rxField.is() && _rxField->getPropertySetInfo()->hasPropertyByName(FM_PROP_LABEL) ) + _rxField->getPropertyValue(FM_PROP_LABEL) >>= sLabel; + if ( sLabel.isEmpty() ) + sLabel = sFieldName; + + xLabelModel->setPropertyValue( FM_PROP_LABEL, makeAny( sLabel + _rFieldPostfix ) ); + OUString sObjectLabel(SvxResId(RID_STR_OBJECT_LABEL).replaceAll("#object#", sFieldName)); + xLabelModel->setPropertyValue(FM_PROP_NAME, makeAny(sObjectLabel)); + } + + pLabel->SetLogicRect( ::tools::Rectangle( + OutputDevice::LogicToLogic( ::Point( _nXOffsetMM, _nYOffsetMM ), eSourceMode, eTargetMode ), + OutputDevice::LogicToLogic( aRealSize, eSourceMode, eTargetMode ) + ) ); + } + + // the control + ::std::unique_ptr< SdrUnoObj, SdrObjectFreeOp > pControl( dynamic_cast< SdrUnoObj* >( + SdrObjFactory::MakeNewObject( + _rModel, + _nInventor, + _nControlObjectID))); + + OSL_ENSURE(pControl, "FmXFormView::createControlLabelPair: could not create the control!"); + + if (!pControl) + return false; + + Reference< XPropertySet > xControlSet( pControl->GetUnoControlModel(), UNO_QUERY ); + if ( !xControlSet.is() ) + return false; + + // size of the control + ::Size aControlSize( aDefSize ); + switch ( nDataType ) + { + case DataType::BIT: + case DataType::BOOLEAN: + aControlSize = aDefSize; + break; + case DataType::LONGVARCHAR: + case DataType::CLOB: + case DataType::LONGVARBINARY: + case DataType::BLOB: + aControlSize = aDefImageSize; + break; + } + + if ( OBJ_FM_IMAGECONTROL == _nControlObjectID ) + aControlSize = aDefImageSize; + + aControlSize.setWidth( long(Fraction(aControlSize.Width(), 1) * eTargetMode.GetScaleX()) ); + aControlSize.setHeight( long(Fraction(aControlSize.Height(), 1) * eTargetMode.GetScaleY()) ); + + pControl->SetLogicRect( ::tools::Rectangle( + OutputDevice::LogicToLogic( ::Point( aRealSize.Width() + _nXOffsetMM, _nYOffsetMM ), eSourceMode, eTargetMode ), + OutputDevice::LogicToLogic( aControlSize, eSourceMode, eTargetMode ) + ) ); + + // some initializations + Reference< XPropertySetInfo > xControlPropInfo = xControlSet->getPropertySetInfo(); + + if ( aFieldName.hasValue() ) + { + xControlSet->setPropertyValue( FM_PROP_CONTROLSOURCE, aFieldName ); + xControlSet->setPropertyValue( FM_PROP_NAME, aFieldName ); + if ( !bNeedLabel ) + { + // no dedicated label control => use the label property + if ( xControlPropInfo->hasPropertyByName( FM_PROP_LABEL ) ) + xControlSet->setPropertyValue( FM_PROP_LABEL, makeAny( sFieldName + _rFieldPostfix ) ); + else + OSL_FAIL( "FmXFormView::createControlLabelPair: can't set a label for the control!" ); + } + } + + if ( (nDataType == DataType::LONGVARCHAR || nDataType == DataType::CLOB) && xControlPropInfo->hasPropertyByName( FM_PROP_MULTILINE ) ) + { + xControlSet->setPropertyValue( FM_PROP_MULTILINE, makeAny( true ) ); + } + + // announce the label to the control + if ( xControlPropInfo->hasPropertyByName( FM_PROP_CONTROLLABEL ) && xLabelModel.is() ) + { + try + { + xControlSet->setPropertyValue( FM_PROP_CONTROLLABEL, makeAny( xLabelModel ) ); + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + if ( _rxField.is() ) + { + FormControlFactory::initializeFieldDependentProperties( _rxField, xControlSet, _rxNumberFormats ); + } + + _rpLabel = std::move(pLabel); + _rpControl = std::move(pControl); + return true; +} + + +FmXFormView::ObjectRemoveListener::ObjectRemoveListener( FmXFormView* pParent ) + :m_pParent( pParent ) +{ +} + + +void FmXFormView::ObjectRemoveListener::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint ) +{ + if (rHint.GetId() != SfxHintId::ThisIsAnSdrHint) + return; + const SdrHint* pSdrHint = static_cast<const SdrHint*>(&rHint); + if (pSdrHint->GetKind() == SdrHintKind::ObjectRemoved) + m_pParent->ObjectRemovedInAliveMode(pSdrHint->GetObject()); +} + + +void FmXFormView::ObjectRemovedInAliveMode( const SdrObject* pObject ) +{ + // if the remote object in my MarkList, which I have memorized when switching to the + // Alive mode, I have to take it out now, because I otherwise try to set the mark + // again when switching back (interestingly, this fails only with grouped objects + // (when accessing their ObjList GPF), not with individual ones) + + const size_t nCount = m_aMark.GetMarkCount(); + for (size_t i = 0; i < nCount; ++i) + { + SdrMark* pMark = m_aMark.GetMark(i); + SdrObject* pCurrent = pMark->GetMarkedSdrObj(); + if (pObject == pCurrent) + { + m_aMark.DeleteMark(i); + return; + } + // I do not need to descend into GroupObjects: if an object is deleted there, + // then the pointer, which I have, to the GroupObject still remains valid ... + } +} + + +void FmXFormView::stopMarkListWatching() +{ + if ( m_pWatchStoredList ) + { + m_pWatchStoredList->EndListeningAll(); + m_pWatchStoredList.reset(); + } +} + + +void FmXFormView::startMarkListWatching() +{ + if ( !m_pWatchStoredList ) + { + FmFormModel* pModel = GetFormShell() ? GetFormShell()->GetFormModel() : nullptr; + DBG_ASSERT( pModel != nullptr, "FmXFormView::startMarkListWatching: shell has no model!" ); + if (pModel) + { + m_pWatchStoredList.reset(new ObjectRemoveListener( this )); + m_pWatchStoredList->StartListening( *static_cast< SfxBroadcaster* >( pModel ) ); + } + } + else + { + OSL_FAIL( "FmXFormView::startMarkListWatching: already listening!" ); + } +} + +void FmXFormView::saveMarkList() +{ + if ( m_pView ) + { + m_aMark = m_pView->GetMarkedObjectList(); + const size_t nCount = m_aMark.GetMarkCount( ); + for ( size_t i = 0; i < nCount; ++i ) + { + SdrMark* pMark = m_aMark.GetMark(i); + SdrObject* pObj = pMark->GetMarkedSdrObj(); + + if ( m_pView->IsObjMarked( pObj ) ) + { + if ( pObj->IsGroupObject() ) + { + SdrObjListIter aIter( pObj->GetSubList() ); + bool bMixed = false; + while ( aIter.IsMore() && !bMixed ) + bMixed = ( aIter.Next()->GetObjInventor() != SdrInventor::FmForm ); + + if ( !bMixed ) + { + // all objects in the group are form objects + m_pView->MarkObj( pMark->GetMarkedSdrObj(), pMark->GetPageView(), true /* unmark! */ ); + } + } + else + { + if ( pObj->GetObjInventor() == SdrInventor::FmForm ) + { // this is a form layer object + m_pView->MarkObj( pMark->GetMarkedSdrObj(), pMark->GetPageView(), true /* unmark! */ ); + } + } + } + } + } + else + { + OSL_FAIL( "FmXFormView::saveMarkList: invalid view!" ); + m_aMark.Clear(); + } +} + +static bool lcl_hasObject( SdrObjListIter& rIter, SdrObject const * pObj ) +{ + bool bFound = false; + while (rIter.IsMore() && !bFound) + bFound = pObj == rIter.Next(); + + rIter.Reset(); + return bFound; +} + + +void FmXFormView::restoreMarkList( SdrMarkList& _rRestoredMarkList ) +{ + if ( !m_pView ) + return; + + _rRestoredMarkList.Clear(); + + const SdrMarkList& rCurrentList = m_pView->GetMarkedObjectList(); + FmFormPage* pPage = GetFormShell() ? GetFormShell()->GetCurPage() : nullptr; + if (pPage) + { + if (rCurrentList.GetMarkCount()) + { // there is a current mark ... hmm. Is it a subset of the mark we remembered in saveMarkList? + bool bMisMatch = false; + + // loop through all current marks + const size_t nCurrentCount = rCurrentList.GetMarkCount(); + for ( size_t i=0; i<nCurrentCount && !bMisMatch; ++i ) + { + const SdrObject* pCurrentMarked = rCurrentList.GetMark( i )->GetMarkedSdrObj(); + + // loop through all saved marks, check for equality + bool bFound = false; + const size_t nSavedCount = m_aMark.GetMarkCount(); + for ( size_t j=0; j<nSavedCount && !bFound; ++j ) + { + if ( m_aMark.GetMark( j )->GetMarkedSdrObj() == pCurrentMarked ) + bFound = true; + } + + // did not find a current mark in the saved marks + if ( !bFound ) + bMisMatch = true; + } + + if ( bMisMatch ) + { + m_aMark.Clear(); + _rRestoredMarkList = rCurrentList; + return; + } + } + // it is important that the objects of the mark list are not accessed, + // because they can be already destroyed + SdrPageView* pCurPageView = m_pView->GetSdrPageView(); + SdrObjListIter aPageIter( pPage ); + bool bFound = true; + + // do all objects still exist + const size_t nCount = m_aMark.GetMarkCount(); + for (size_t i = 0; i < nCount && bFound; ++i) + { + SdrMark* pMark = m_aMark.GetMark(i); + SdrObject* pObj = pMark->GetMarkedSdrObj(); + if (pObj->IsGroupObject()) + { + SdrObjListIter aIter(pObj->GetSubList()); + while (aIter.IsMore() && bFound) + bFound = lcl_hasObject(aPageIter, aIter.Next()); + } + else + bFound = lcl_hasObject(aPageIter, pObj); + + bFound = bFound && pCurPageView == pMark->GetPageView(); + } + + if (bFound) + { + // evaluate the LastObject + if (nCount) // now mark the objects + { + for (size_t i = 0; i < nCount; ++i) + { + SdrMark* pMark = m_aMark.GetMark(i); + SdrObject* pObj = pMark->GetMarkedSdrObj(); + if ( pObj->GetObjInventor() == SdrInventor::FmForm ) + if ( !m_pView->IsObjMarked( pObj ) ) + m_pView->MarkObj( pObj, pMark->GetPageView() ); + } + + _rRestoredMarkList = m_aMark; + } + } + m_aMark.Clear(); + } +} + +void SAL_CALL FmXFormView::focusGained( const FocusEvent& /*e*/ ) +{ + if ( m_xWindow.is() && m_pView ) + { + m_pView->SetMoveOutside( true, FmFormView::ImplAccess() ); + } +} + +void SAL_CALL FmXFormView::focusLost( const FocusEvent& /*e*/ ) +{ + // when switch the focus outside the office the mark didn't change + // so we can not remove us as focus listener + if ( m_xWindow.is() && m_pView ) + { + m_pView->SetMoveOutside( false, FmFormView::ImplAccess() ); + } +} + +DocumentType FmXFormView::impl_getDocumentType() const +{ + if ( GetFormShell() && GetFormShell()->GetImpl() ) + return GetFormShell()->GetImpl()->getDocumentType_Lock(); + return eUnknownDocumentType; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/formcontrolfactory.cxx b/svx/source/form/formcontrolfactory.cxx new file mode 100644 index 000000000..5e4f17a8f --- /dev/null +++ b/svx/source/form/formcontrolfactory.cxx @@ -0,0 +1,713 @@ +/* -*- 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 <formcontrolfactory.hxx> +#include <fmcontrollayout.hxx> +#include <fmprop.hxx> +#include <svx/strings.hrc> +#include <fmservs.hxx> +#include <svx/dialmgr.hxx> +#include <svx/svdouno.hxx> + +#include <com/sun/star/form/XFormComponent.hpp> +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/awt/ScrollBarOrientation.hpp> +#include <com/sun/star/awt/MouseWheelBehavior.hpp> +#include <com/sun/star/form/XGridColumnFactory.hpp> +#include <com/sun/star/style/VerticalAlignment.hpp> +#include <com/sun/star/awt/LineEndFormat.hpp> +#include <com/sun/star/awt/ImageScaleMode.hpp> +#include <com/sun/star/sdbc/DataType.hpp> +#include <com/sun/star/sdbc/XDataSource.hpp> +#include <com/sun/star/util/XNumberFormatTypes.hpp> +#include <com/sun/star/sdbc/ColumnValue.hpp> +#include <com/sun/star/text/WritingMode2.hpp> + +#include <comphelper/numbers.hxx> +#include <comphelper/processfactory.hxx> +#include <unotools/syslocale.hxx> +#include <tools/gen.hxx> +#include <tools/diagnose_ex.h> +#include <connectivity/dbtools.hxx> +#include <i18nlangtag/languagetag.hxx> + +#include <set> + +using namespace ::dbtools; + +namespace svxform +{ + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::uno::UNO_SET_THROW; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::makeAny; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::beans::XPropertySet; + using ::com::sun::star::form::XFormComponent; + using ::com::sun::star::container::XIndexAccess; + using ::com::sun::star::beans::XPropertySetInfo; + using ::com::sun::star::beans::PropertyValue; + using ::com::sun::star::container::XChild; + using ::com::sun::star::form::XGridColumnFactory; + using ::com::sun::star::style::VerticalAlignment_MIDDLE; + using ::com::sun::star::beans::Property; + using ::com::sun::star::uno::TypeClass_DOUBLE; + using ::com::sun::star::uno::TypeClass_LONG; + using ::com::sun::star::util::XNumberFormats; + using ::com::sun::star::util::XNumberFormatTypes; + using ::com::sun::star::lang::XServiceInfo; + using ::com::sun::star::container::XNameAccess; + + namespace FormComponentType = ::com::sun::star::form::FormComponentType; + namespace ScrollBarOrientation = ::com::sun::star::awt::ScrollBarOrientation; + namespace MouseWheelBehavior = ::com::sun::star::awt::MouseWheelBehavior; + namespace LineEndFormat = ::com::sun::star::awt::LineEndFormat; + namespace ImageScaleMode = ::com::sun::star::awt::ImageScaleMode; + namespace DataType = ::com::sun::star::sdbc::DataType; + namespace ColumnValue = ::com::sun::star::sdbc::ColumnValue; + namespace WritingMode2 = ::com::sun::star::text::WritingMode2; + + struct FormControlFactory_Data + { + Reference<XComponentContext> m_xContext; + + explicit FormControlFactory_Data( const Reference<XComponentContext>& _rContext ) + :m_xContext( _rContext ) + { + } + }; + + FormControlFactory::FormControlFactory( const Reference<XComponentContext>& _rContext ) + :m_pData( new FormControlFactory_Data( _rContext ) ) + { + } + + FormControlFactory::FormControlFactory( ) + :m_pData( new FormControlFactory_Data( comphelper::getProcessComponentContext() ) ) + { + } + + + FormControlFactory::~FormControlFactory() + { + } + + + sal_Int16 FormControlFactory::initializeControlModel( const DocumentType _eDocType, const SdrUnoObj& _rObject ) + { + return initializeControlModel( + _eDocType, + Reference< XPropertySet >( _rObject.GetUnoControlModel(), UNO_QUERY ), + _rObject.GetCurrentBoundRect() + ); + } + + + void FormControlFactory::initializeControlModel( const DocumentType _eDocType, const Reference< XPropertySet >& _rxControlModel ) + { + initializeControlModel( + _eDocType, _rxControlModel, tools::Rectangle() + ); + } + + + namespace + { + + OUString lcl_getUniqueLabel_nothrow( const Reference< XPropertySet >& _rxControlModel, const OUString& _rBaseLabel ) + { + OUString sLabel( _rBaseLabel ); + try + { + typedef ::std::set< OUString > StringBag; + StringBag aUsedLabels; + + Reference< XFormComponent > xFormComponent( _rxControlModel, UNO_QUERY_THROW ); + Reference< XIndexAccess > xContainer( xFormComponent->getParent(), UNO_QUERY_THROW ); + // loop through all siblings of the control model, and collect their labels + for ( sal_Int32 index=xContainer->getCount(); index>0; ) + { + Reference< XPropertySet > xElement( xContainer->getByIndex( --index ), UNO_QUERY_THROW ); + if ( xElement == _rxControlModel ) + continue; + + Reference< XPropertySetInfo > xPSI( xElement->getPropertySetInfo(), UNO_SET_THROW ); + if ( !xPSI->hasPropertyByName( FM_PROP_LABEL ) ) + continue; + + OUString sElementLabel; + OSL_VERIFY( xElement->getPropertyValue( FM_PROP_LABEL ) >>= sElementLabel ); + aUsedLabels.insert( sElementLabel ); + } + + // now find a free label + sal_Int32 i=2; + while ( aUsedLabels.find( sLabel ) != aUsedLabels.end() ) + { + OUStringBuffer aBuffer( _rBaseLabel ); + aBuffer.append( " " ); + aBuffer.append( i++ ); + sLabel = aBuffer.makeStringAndClear(); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return sLabel; + } + + + Sequence< PropertyValue > lcl_getDataSourceIndirectProperties( const Reference< XPropertySet >& _rxControlModel, + const Reference<XComponentContext>& _rContext ) + { + OSL_PRECOND( _rxControlModel.is(), "lcl_getDataSourceIndirectProperties: invalid model!" ); + + Sequence< PropertyValue > aInfo; + try + { + Reference< XChild > xChild( _rxControlModel, UNO_QUERY ); + Reference< XPropertySet > xForm; + if ( xChild.is() ) + xForm.set(xChild->getParent(), css::uno::UNO_QUERY); + + if ( Reference< XGridColumnFactory >( xForm, UNO_QUERY ).is() ) + { // hmm. the model is a grid column, in real + xChild.set(xForm, css::uno::UNO_QUERY); + xForm.set(xChild->getParent(), css::uno::UNO_QUERY); + } + + OSL_ENSURE( xForm.is(), "lcl_getDataSourceIndirectProperties: could not determine the form!" ); + if ( !xForm.is() ) + return aInfo; + OUString sDataSourceName; + xForm->getPropertyValue( FM_PROP_DATASOURCE ) >>= sDataSourceName; + + Reference< XPropertySet > xDsProperties; + if ( !sDataSourceName.isEmpty() ) + xDsProperties.set(getDataSource( sDataSourceName, _rContext ), css::uno::UNO_QUERY); + if ( xDsProperties.is() ) + xDsProperties->getPropertyValue("Info") >>= aInfo; + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "lcl_getDataSourceIndirectProperties" ); + } + return aInfo; + } + + + static const char* aCharacterAndParagraphProperties[] = + { + "CharFontName", + "CharFontStyleName", + "CharFontFamily", + "CharFontCharSet", + "CharFontPitch", + "CharColor", + "CharEscapement", + "CharHeight", + "CharUnderline", + "CharWeight", + "CharPosture", + "CharAutoKerning", + "CharBackColor", + "CharBackTransparent", + "CharCaseMap", + "CharCrossedOut", + "CharFlash", + "CharStrikeout", + "CharWordMode", + "CharKerning", + "CharLocale", + "CharKeepTogether", + "CharNoLineBreak", + "CharShadowed", + "CharFontType", + "CharStyleName", + "CharContoured", + "CharCombineIsOn", + "CharCombinePrefix", + "CharCombineSuffix", + "CharEmphasize", + "CharRelief", + "RubyText", + "RubyAdjust", + "RubyCharStyleName", + "RubyIsAbove", + "CharRotation", + "CharRotationIsFitToLine", + "CharScaleWidth", + "HyperLinkURL", + "HyperLinkTarget", + "HyperLinkName", + "VisitedCharStyleName", + "UnvisitedCharStyleName", + "CharEscapementHeight", + "CharNoHyphenation", + "CharUnderlineColor", + "CharUnderlineHasColor", + "CharStyleNames", + "CharHeightAsian", + "CharWeightAsian", + "CharFontNameAsian", + "CharFontStyleNameAsian", + "CharFontFamilyAsian", + "CharFontCharSetAsian", + "CharFontPitchAsian", + "CharPostureAsian", + "CharLocaleAsian", + "ParaIsCharacterDistance", + "ParaIsForbiddenRules", + "ParaIsHangingPunctuation", + "CharHeightComplex", + "CharWeightComplex", + "CharFontNameComplex", + "CharFontStyleNameComplex", + "CharFontFamilyComplex", + "CharFontCharSetComplex", + "CharFontPitchComplex", + "CharPostureComplex", + "CharLocaleComplex", + "ParaAdjust", + "ParaLineSpacing", + "ParaBackColor", + "ParaBackTransparent", + "ParaBackGraphic", + "ParaBackGraphicURL", + "ParaBackGraphicFilter", + "ParaBackGraphicLocation", + "ParaLastLineAdjust", + "ParaExpandSingleWord", + "ParaLeftMargin", + "ParaRightMargin", + "ParaTopMargin", + "ParaBottomMargin", + "ParaLineNumberCount", + "ParaLineNumberStartValue", + "PageDescName", + "PageNumberOffset", + "ParaRegisterModeActive", + "ParaTabStops", + "ParaStyleName", + "DropCapFormat", + "DropCapWholeWord", + "ParaKeepTogether", + "Setting", + "ParaSplit", + "Setting", + "NumberingLevel", + "NumberingRules", + "NumberingStartValue", + "ParaIsNumberingRestart", + "NumberingStyleName", + "ParaOrphans", + "ParaWidows", + "ParaShadowFormat", + "LeftBorder", + "RightBorder", + "TopBorder", + "BottomBorder", + "BorderDistance", + "LeftBorderDistance", + "RightBorderDistance", + "TopBorderDistance", + "BottomBorderDistance", + "BreakType", + "DropCapCharStyleName", + "ParaFirstLineIndent", + "ParaIsAutoFirstLineIndent", + "ParaIsHyphenation", + "ParaHyphenationMaxHyphens", + "ParaHyphenationMaxLeadingChars", + "ParaHyphenationMaxTrailingChars", + "ParaVertAlignment", + "ParaUserDefinedAttributes", + "NumberingIsNumber", + "ParaIsConnectBorder", + nullptr + }; + + + void lcl_initializeCharacterAttributes( const Reference< XPropertySet >& _rxModel ) + { + try + { + Reference< XPropertySet > xStyle( ControlLayouter::getDefaultDocumentTextStyle( _rxModel ), UNO_SET_THROW ); + + // transfer all properties which are described by the style + Reference< XPropertySetInfo > xSourcePropInfo( xStyle->getPropertySetInfo(), UNO_SET_THROW ); + Reference< XPropertySetInfo > xDestPropInfo( _rxModel->getPropertySetInfo(), UNO_SET_THROW ); + + OUString sPropertyName; + const char** pCharacterProperty = aCharacterAndParagraphProperties; + while ( *pCharacterProperty ) + { + sPropertyName = OUString::createFromAscii( *pCharacterProperty ); + + if ( xSourcePropInfo->hasPropertyByName( sPropertyName ) && xDestPropInfo->hasPropertyByName( sPropertyName ) ) + _rxModel->setPropertyValue( sPropertyName, xStyle->getPropertyValue( sPropertyName ) ); + + ++pCharacterProperty; + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + } + + + sal_Int16 FormControlFactory::initializeControlModel( const DocumentType _eDocType, const Reference< XPropertySet >& _rxControlModel, + const tools::Rectangle& _rControlBoundRect ) + { + sal_Int16 nClassId = FormComponentType::CONTROL; + + OSL_ENSURE( _rxControlModel.is(), "FormControlFactory::initializeControlModel: invalid model!" ); + if ( !_rxControlModel.is() ) + return nClassId; + + try + { + ControlLayouter::initializeControlLayout( _rxControlModel, _eDocType ); + + _rxControlModel->getPropertyValue( FM_PROP_CLASSID ) >>= nClassId; + Reference< XPropertySetInfo > xPSI( _rxControlModel->getPropertySetInfo(), UNO_SET_THROW ); + switch ( nClassId ) + { + case FormComponentType::SCROLLBAR: + _rxControlModel->setPropertyValue("LiveScroll", makeAny( true ) ); + [[fallthrough]]; + case FormComponentType::SPINBUTTON: + { + sal_Int32 eOrientation = ScrollBarOrientation::HORIZONTAL; + if ( !_rControlBoundRect.IsEmpty() && ( _rControlBoundRect.GetWidth() < _rControlBoundRect.GetHeight() ) ) + eOrientation = ScrollBarOrientation::VERTICAL; + _rxControlModel->setPropertyValue( FM_PROP_ORIENTATION, makeAny( eOrientation ) ); + } + break; + + case FormComponentType::LISTBOX: + case FormComponentType::COMBOBOX: + { + bool bDropDown = !_rControlBoundRect.IsEmpty() && ( _rControlBoundRect.GetWidth() >= 3 * _rControlBoundRect.GetHeight() ); + if ( xPSI->hasPropertyByName( FM_PROP_DROPDOWN ) ) + _rxControlModel->setPropertyValue( FM_PROP_DROPDOWN, makeAny( bDropDown ) ); + _rxControlModel->setPropertyValue( FM_PROP_LINECOUNT, makeAny( sal_Int16( 20 ) ) ); + } + break; + + case FormComponentType::TEXTFIELD: + { + initializeTextFieldLineEnds( _rxControlModel ); + lcl_initializeCharacterAttributes( _rxControlModel ); + + if ( !_rControlBoundRect.IsEmpty() + && ( _rControlBoundRect.GetWidth() <= 4 * _rControlBoundRect.GetHeight() ) + ) + { + if ( xPSI->hasPropertyByName( FM_PROP_MULTILINE ) ) + _rxControlModel->setPropertyValue( FM_PROP_MULTILINE, makeAny( true ) ); + } + } + break; + + case FormComponentType::RADIOBUTTON: + case FormComponentType::CHECKBOX: + case FormComponentType::FIXEDTEXT: + { + OUString sVertAlignPropertyName( "VerticalAlign" ); + if ( xPSI->hasPropertyByName( sVertAlignPropertyName ) ) + _rxControlModel->setPropertyValue( sVertAlignPropertyName, makeAny( VerticalAlignment_MIDDLE ) ); + } + break; + + case FormComponentType::IMAGEBUTTON: + case FormComponentType::IMAGECONTROL: + { + const OUString sScaleModeProperty( "ScaleMode" ); + if ( xPSI->hasPropertyByName( sScaleModeProperty ) ) + _rxControlModel->setPropertyValue( sScaleModeProperty, makeAny( ImageScaleMode::ISOTROPIC ) ); + } + break; + } + + // initial default label for the control + if ( xPSI->hasPropertyByName( FM_PROP_LABEL ) ) + { + OUString sExistingLabel; + OSL_VERIFY( _rxControlModel->getPropertyValue( FM_PROP_LABEL ) >>= sExistingLabel ); + if ( sExistingLabel.isEmpty() ) + { + OUString sInitialLabel; + OSL_VERIFY( _rxControlModel->getPropertyValue( FM_PROP_NAME ) >>= sInitialLabel ); + + const char* pTitleResId = nullptr; + switch ( nClassId ) + { + case FormComponentType::COMMANDBUTTON: pTitleResId = RID_STR_PROPTITLE_PUSHBUTTON; break; + case FormComponentType::RADIOBUTTON: pTitleResId = RID_STR_PROPTITLE_RADIOBUTTON; break; + case FormComponentType::CHECKBOX: pTitleResId = RID_STR_PROPTITLE_CHECKBOX; break; + case FormComponentType::GROUPBOX: pTitleResId = RID_STR_PROPTITLE_GROUPBOX; break; + case FormComponentType::FIXEDTEXT: pTitleResId = RID_STR_PROPTITLE_FIXEDTEXT; break; + } + + if (pTitleResId) + sInitialLabel = SvxResId(pTitleResId); + + _rxControlModel->setPropertyValue( + FM_PROP_LABEL, + makeAny( lcl_getUniqueLabel_nothrow( _rxControlModel, sInitialLabel ) ) + ); + } + } + + // strict format = yes is the default (i93467) + if ( xPSI->hasPropertyByName( FM_PROP_STRICTFORMAT ) ) + { + _rxControlModel->setPropertyValue( FM_PROP_STRICTFORMAT, makeAny( true ) ); + } + + // mouse wheel: don't use it for scrolling by default (i110036) + if ( xPSI->hasPropertyByName( FM_PROP_MOUSE_WHEEL_BEHAVIOR ) ) + { + _rxControlModel->setPropertyValue( FM_PROP_MOUSE_WHEEL_BEHAVIOR, makeAny( MouseWheelBehavior::SCROLL_DISABLED ) ); + } + + if ( xPSI->hasPropertyByName( FM_PROP_WRITING_MODE ) ) + _rxControlModel->setPropertyValue( FM_PROP_WRITING_MODE, makeAny( WritingMode2::CONTEXT ) ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return nClassId; + } + + + void FormControlFactory::initializeTextFieldLineEnds( const Reference< XPropertySet >& _rxModel ) + { + OSL_PRECOND( _rxModel.is(), "initializeTextFieldLineEnds: invalid model!" ); + if ( !_rxModel.is() ) + return; + + try + { + Reference< XPropertySetInfo > xInfo = _rxModel->getPropertySetInfo(); + if ( !xInfo.is() || !xInfo->hasPropertyByName( FM_PROP_LINEENDFORMAT ) ) + return; + + // let's see if the data source which the form belongs to (if any) + // has a setting for the preferred line end format + bool bDosLineEnds = false; + Sequence< PropertyValue > aInfo = lcl_getDataSourceIndirectProperties( _rxModel, m_pData->m_xContext ); + const PropertyValue* pInfo = std::find_if(aInfo.begin(), aInfo.end(), + [](const PropertyValue& rInfo) { return rInfo.Name == "PreferDosLikeLineEnds"; }); + if (pInfo != aInfo.end()) + pInfo->Value >>= bDosLineEnds; + + sal_Int16 nLineEndFormat = bDosLineEnds ? LineEndFormat::CARRIAGE_RETURN_LINE_FEED : LineEndFormat::LINE_FEED; + _rxModel->setPropertyValue( FM_PROP_LINEENDFORMAT, makeAny( nLineEndFormat ) ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + + void FormControlFactory::initializeFieldDependentProperties( const Reference< XPropertySet >& _rxDatabaseField, + const Reference< XPropertySet >& _rxControlModel, const Reference< XNumberFormats >& _rxNumberFormats ) + { + OSL_PRECOND( _rxDatabaseField.is() && _rxControlModel.is(), + "FormControlFactory::initializeFieldDependentProperties: illegal params!" ); + if ( !_rxDatabaseField.is() || !_rxControlModel.is() ) + return; + + try + { + + // if the field has a numeric format, and the model has a "Scale" property, sync it + Reference< XPropertySetInfo > xFieldPSI( _rxDatabaseField->getPropertySetInfo(), UNO_SET_THROW ); + Reference< XPropertySetInfo > xModelPSI( _rxControlModel->getPropertySetInfo(), UNO_SET_THROW ); + + if ( xModelPSI->hasPropertyByName( FM_PROP_DECIMAL_ACCURACY ) ) + { + sal_Int32 nFormatKey = 0; + if ( xFieldPSI->hasPropertyByName( FM_PROP_FORMATKEY ) ) + { + _rxDatabaseField->getPropertyValue( FM_PROP_FORMATKEY ) >>= nFormatKey; + } + else + { + nFormatKey = getDefaultNumberFormat( + _rxDatabaseField, + Reference< XNumberFormatTypes >( _rxNumberFormats, UNO_QUERY ), + SvtSysLocale().GetLanguageTag().getLocale() + ); + } + + Any aScaleVal( ::comphelper::getNumberFormatDecimals( _rxNumberFormats, nFormatKey ) ); + _rxControlModel->setPropertyValue( FM_PROP_DECIMAL_ACCURACY, aScaleVal ); + } + + + // minimum and maximum of the control according to the type of the database field + sal_Int32 nDataType = DataType::OTHER; + OSL_VERIFY( _rxDatabaseField->getPropertyValue( FM_PROP_FIELDTYPE ) >>= nDataType ); + + if ( xModelPSI->hasPropertyByName( FM_PROP_VALUEMIN ) + && xModelPSI->hasPropertyByName( FM_PROP_VALUEMAX ) + ) + { + sal_Int32 nMinValue = -1000000000, nMaxValue = 1000000000; + switch ( nDataType ) + { + case DataType::TINYINT : nMinValue = 0; nMaxValue = 255; break; + case DataType::SMALLINT : nMinValue = -32768; nMaxValue = 32767; break; + case DataType::INTEGER : nMinValue = 0x80000000; nMaxValue = 0x7FFFFFFF; break; + // double and singles are ignored + } + + Any aValue; + + // both the minimum and the maximum value properties can be either Long or Double + Property aProperty = xModelPSI->getPropertyByName( FM_PROP_VALUEMIN ); + if ( aProperty.Type.getTypeClass() == TypeClass_DOUBLE ) + aValue <<= static_cast<double>(nMinValue); + else if ( aProperty.Type.getTypeClass() == TypeClass_LONG ) + aValue <<= nMinValue; + else + { + OSL_FAIL( "FormControlFactory::initializeFieldDependentProperties: unexpected property type (MinValue)!" ); + } + _rxControlModel->setPropertyValue( FM_PROP_VALUEMIN, aValue ); + + // both the minimum and the maximum value properties can be either Long or Double + aProperty = xModelPSI->getPropertyByName( FM_PROP_VALUEMAX ); + if ( aProperty.Type.getTypeClass() == TypeClass_DOUBLE ) + aValue <<= static_cast<double>(nMaxValue); + else if ( aProperty.Type.getTypeClass() == TypeClass_LONG ) + aValue <<= nMaxValue; + else + { + OSL_FAIL( "FormControlFactory::initializeFieldDependentProperties: unexpected property type (MaxValue)!" ); + } + _rxControlModel->setPropertyValue( FM_PROP_VALUEMAX, aValue ); + } + + + // a check box can be tristate if and only if the column it is bound to is nullable + sal_Int16 nClassId = FormComponentType::CONTROL; + OSL_VERIFY( _rxControlModel->getPropertyValue( FM_PROP_CLASSID ) >>= nClassId ); + if ( nClassId == FormComponentType::CHECKBOX ) + { + sal_Int32 nNullable = ColumnValue::NULLABLE_UNKNOWN; + OSL_VERIFY( _rxDatabaseField->getPropertyValue( FM_PROP_ISNULLABLE ) >>= nNullable ); + _rxControlModel->setPropertyValue( FM_PROP_TRISTATE, makeAny( ColumnValue::NO_NULLS != nNullable ) ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + + OUString FormControlFactory::getDefaultName( sal_Int16 _nClassId, const Reference< XServiceInfo >& _rxObject ) + { + const char* pResId(nullptr); + + switch ( _nClassId ) + { + case FormComponentType::COMMANDBUTTON: pResId = RID_STR_PROPTITLE_PUSHBUTTON; break; + case FormComponentType::RADIOBUTTON: pResId = RID_STR_PROPTITLE_RADIOBUTTON; break; + case FormComponentType::CHECKBOX: pResId = RID_STR_PROPTITLE_CHECKBOX; break; + case FormComponentType::LISTBOX: pResId = RID_STR_PROPTITLE_LISTBOX; break; + case FormComponentType::COMBOBOX: pResId = RID_STR_PROPTITLE_COMBOBOX; break; + case FormComponentType::GROUPBOX: pResId = RID_STR_PROPTITLE_GROUPBOX; break; + case FormComponentType::IMAGEBUTTON: pResId = RID_STR_PROPTITLE_IMAGEBUTTON; break; + case FormComponentType::FIXEDTEXT: pResId = RID_STR_PROPTITLE_FIXEDTEXT; break; + case FormComponentType::GRIDCONTROL: pResId = RID_STR_PROPTITLE_DBGRID; break; + case FormComponentType::FILECONTROL: pResId = RID_STR_PROPTITLE_FILECONTROL; break; + case FormComponentType::DATEFIELD: pResId = RID_STR_PROPTITLE_DATEFIELD; break; + case FormComponentType::TIMEFIELD: pResId = RID_STR_PROPTITLE_TIMEFIELD; break; + case FormComponentType::NUMERICFIELD: pResId = RID_STR_PROPTITLE_NUMERICFIELD; break; + case FormComponentType::CURRENCYFIELD: pResId = RID_STR_PROPTITLE_CURRENCYFIELD; break; + case FormComponentType::PATTERNFIELD: pResId = RID_STR_PROPTITLE_PATTERNFIELD; break; + case FormComponentType::IMAGECONTROL: pResId = RID_STR_PROPTITLE_IMAGECONTROL; break; + case FormComponentType::HIDDENCONTROL: pResId = RID_STR_PROPTITLE_HIDDEN; break; + case FormComponentType::SCROLLBAR: pResId = RID_STR_PROPTITLE_SCROLLBAR; break; + case FormComponentType::SPINBUTTON: pResId = RID_STR_PROPTITLE_SPINBUTTON; break; + case FormComponentType::NAVIGATIONBAR: pResId = RID_STR_PROPTITLE_NAVBAR; break; + + case FormComponentType::TEXTFIELD: + pResId = RID_STR_PROPTITLE_EDIT; + if ( _rxObject.is() && _rxObject->supportsService( FM_SUN_COMPONENT_FORMATTEDFIELD ) ) + pResId = RID_STR_PROPTITLE_FORMATTED; + break; + + default: + pResId = RID_STR_CONTROL; break; + } + + return SvxResId(pResId); + } + + + OUString FormControlFactory::getDefaultUniqueName_ByComponentType( const Reference< XNameAccess >& _rxContainer, + const Reference< XPropertySet >& _rxObject ) + { + sal_Int16 nClassId = FormComponentType::CONTROL; + OSL_VERIFY( _rxObject->getPropertyValue( FM_PROP_CLASSID ) >>= nClassId ); + OUString sBaseName = getDefaultName( nClassId, Reference< XServiceInfo >( _rxObject, UNO_QUERY ) ); + + return getUniqueName( _rxContainer, sBaseName ); + } + + + OUString FormControlFactory::getUniqueName( const Reference< XNameAccess >& _rxContainer, const OUString& _rBaseName ) + { + sal_Int32 n = 0; + OUString sName; + do + { + OUStringBuffer aBuf( _rBaseName ); + aBuf.append( " " ); + aBuf.append( ++n ); + sName = aBuf.makeStringAndClear(); + } + while ( _rxContainer->hasByName( sName ) ); + + return sName; + } + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/formcontroller.cxx b/svx/source/form/formcontroller.cxx new file mode 100644 index 000000000..be6e956cc --- /dev/null +++ b/svx/source/form/formcontroller.cxx @@ -0,0 +1,4194 @@ +/* -*- 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 <fmcontrolbordermanager.hxx> +#include <fmcontrollayout.hxx> +#include <formcontroller.hxx> +#include <formfeaturedispatcher.hxx> +#include <fmdocumentclassification.hxx> +#include <formcontrolling.hxx> +#include <fmprop.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> +#include <fmservs.hxx> +#include <svx/fmtools.hxx> +#include <fmurl.hxx> + +#include <com/sun/star/awt/FocusChangeReason.hpp> +#include <com/sun/star/awt/XCheckBox.hpp> +#include <com/sun/star/awt/XComboBox.hpp> +#include <com/sun/star/awt/XListBox.hpp> +#include <com/sun/star/awt/XVclWindowPeer.hpp> +#include <com/sun/star/awt/TabController.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/container/XIdentifierReplace.hpp> +#include <com/sun/star/form/TabulatorCycle.hpp> +#include <com/sun/star/form/validation/XValidatableFormComponent.hpp> +#include <com/sun/star/form/XBoundComponent.hpp> +#include <com/sun/star/form/XBoundControl.hpp> +#include <com/sun/star/form/XGridControl.hpp> +#include <com/sun/star/form/XLoadable.hpp> +#include <com/sun/star/form/XReset.hpp> +#include <com/sun/star/form/control/FilterControl.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/sdb/ParametersRequest.hpp> +#include <com/sun/star/sdb/RowChangeAction.hpp> +#include <com/sun/star/sdb/SQLFilterOperator.hpp> +#include <com/sun/star/sdb/XInteractionSupplyParameters.hpp> +#include <com/sun/star/sdbc/ColumnValue.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/form/runtime/FormOperations.hpp> +#include <com/sun/star/form/runtime/FormFeature.hpp> +#include <com/sun/star/container/XContainer.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <com/sun/star/util/NumberFormatter.hpp> +#include <com/sun/star/sdb/SQLContext.hpp> +#include <com/sun/star/sdb/XColumn.hpp> + +#include <comphelper/enumhelper.hxx> +#include <comphelper/interaction.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/property.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/flagguard.hxx> +#include <comphelper/types.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <connectivity/IParseContext.hxx> +#include <connectivity/dbtools.hxx> +#include <connectivity/sqlparse.hxx> +#include <toolkit/controls/unocontrol.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <unotools/localedatawrapper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/window.hxx> +#include <o3tl/safeint.hxx> +#include <osl/mutex.hxx> +#include <sal/log.hxx> + +#include <algorithm> +#include <iterator> + +using namespace ::com::sun::star; +using namespace ::comphelper; +using namespace ::connectivity; +using namespace ::dbtools; + + +css::uno::Reference< css::uno::XInterface > + FormController_NewInstance_Impl( const css::uno::Reference< css::lang::XMultiServiceFactory > & _rxORB ) +{ + return *( new ::svxform::FormController( comphelper::getComponentContext(_rxORB) ) ); +} + +namespace svxform +{ + + using ::com::sun::star::sdb::XColumn; + using ::com::sun::star::awt::XControl; + using ::com::sun::star::awt::TabController; + using ::com::sun::star::awt::XToolkit; + using ::com::sun::star::awt::XWindowPeer; + using ::com::sun::star::form::XGrid; + using ::com::sun::star::beans::XPropertySet; + using ::com::sun::star::uno::UNO_SET_THROW; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::container::XIndexAccess; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::uno::Reference; + using ::com::sun::star::beans::XPropertySetInfo; + using ::com::sun::star::beans::PropertyValue; + using ::com::sun::star::lang::IndexOutOfBoundsException; + using ::com::sun::star::sdb::XInteractionSupplyParameters; + using ::com::sun::star::awt::XTextComponent; + using ::com::sun::star::awt::XTextListener; + using ::com::sun::star::uno::Any; + using ::com::sun::star::frame::XDispatch; + using ::com::sun::star::lang::XMultiServiceFactory; + using ::com::sun::star::uno::Type; + using ::com::sun::star::lang::IllegalArgumentException; + using ::com::sun::star::sdbc::XConnection; + using ::com::sun::star::sdbc::XRowSet; + using ::com::sun::star::sdbc::XDatabaseMetaData; + using ::com::sun::star::util::XNumberFormatsSupplier; + using ::com::sun::star::util::NumberFormatter; + using ::com::sun::star::util::XNumberFormatter; + using ::com::sun::star::sdbcx::XColumnsSupplier; + using ::com::sun::star::container::XNameAccess; + using ::com::sun::star::lang::EventObject; + using ::com::sun::star::beans::Property; + using ::com::sun::star::container::XEnumeration; + using ::com::sun::star::form::XFormComponent; + using ::com::sun::star::form::runtime::XFormOperations; + using ::com::sun::star::form::runtime::FilterEvent; + using ::com::sun::star::form::runtime::XFilterControllerListener; + using ::com::sun::star::awt::XControlContainer; + using ::com::sun::star::container::XIdentifierReplace; + using ::com::sun::star::form::XFormControllerListener; + using ::com::sun::star::awt::XWindow; + using ::com::sun::star::sdbc::XResultSet; + using ::com::sun::star::awt::XControlModel; + using ::com::sun::star::awt::XTabControllerModel; + using ::com::sun::star::beans::PropertyChangeEvent; + using ::com::sun::star::form::validation::XValidatableFormComponent; + using ::com::sun::star::form::XLoadable; + using ::com::sun::star::form::XBoundControl; + using ::com::sun::star::beans::XPropertyChangeListener; + using ::com::sun::star::awt::TextEvent; + using ::com::sun::star::form::XBoundComponent; + using ::com::sun::star::awt::XCheckBox; + using ::com::sun::star::awt::XComboBox; + using ::com::sun::star::awt::XListBox; + using ::com::sun::star::awt::ItemEvent; + using ::com::sun::star::util::XModifyListener; + using ::com::sun::star::form::XReset; + using ::com::sun::star::frame::XDispatchProviderInterception; + using ::com::sun::star::form::XGridControl; + using ::com::sun::star::awt::XVclWindowPeer; + using ::com::sun::star::form::validation::XValidator; + using ::com::sun::star::awt::FocusEvent; + using ::com::sun::star::sdb::SQLContext; + using ::com::sun::star::container::XChild; + using ::com::sun::star::form::TabulatorCycle_RECORDS; + using ::com::sun::star::container::ContainerEvent; + using ::com::sun::star::lang::DisposedException; + using ::com::sun::star::lang::Locale; + using ::com::sun::star::lang::NoSupportException; + using ::com::sun::star::sdb::RowChangeEvent; + using ::com::sun::star::frame::XStatusListener; + using ::com::sun::star::frame::XDispatchProviderInterceptor; + using ::com::sun::star::sdb::SQLErrorEvent; + using ::com::sun::star::form::DatabaseParameterEvent; + using ::com::sun::star::sdb::ParametersRequest; + using ::com::sun::star::task::XInteractionRequest; + using ::com::sun::star::util::URL; + using ::com::sun::star::frame::FeatureStateEvent; + using ::com::sun::star::form::runtime::XFormControllerContext; + using ::com::sun::star::task::InteractionHandler; + using ::com::sun::star::task::XInteractionHandler; + using ::com::sun::star::form::runtime::FormOperations; + using ::com::sun::star::container::XContainer; + using ::com::sun::star::sdbc::SQLWarning; + + namespace ColumnValue = ::com::sun::star::sdbc::ColumnValue; + namespace PropertyAttribute = ::com::sun::star::beans::PropertyAttribute; + namespace FocusChangeReason = ::com::sun::star::awt::FocusChangeReason; + namespace RowChangeAction = ::com::sun::star::sdb::RowChangeAction; + namespace FormFeature = ::com::sun::star::form::runtime::FormFeature; + +namespace { + +struct ColumnInfo +{ + // information about the column itself + Reference< XColumn > xColumn; + sal_Int32 nNullable; + bool bAutoIncrement; + bool bReadOnly; + OUString sName; + + // information about the control(s) bound to this column + + /// the first control which is bound to the given column, and which requires input + Reference< XControl > xFirstControlWithInputRequired; + /** the first grid control which contains a column which is bound to the given database column, and requires + input + */ + Reference< XGrid > xFirstGridWithInputRequiredColumn; + /** if xFirstControlWithInputRequired is a grid control, then nRequiredGridColumn specifies the position + of the grid column which is actually bound + */ + sal_Int32 nRequiredGridColumn; + + ColumnInfo() + :xColumn() + ,nNullable( ColumnValue::NULLABLE_UNKNOWN ) + ,bAutoIncrement( false ) + ,bReadOnly( false ) + ,sName() + ,xFirstControlWithInputRequired() + ,xFirstGridWithInputRequiredColumn() + ,nRequiredGridColumn( -1 ) + { + } +}; + +} + +class ColumnInfoCache +{ +public: + explicit ColumnInfoCache( const Reference< XColumnsSupplier >& _rxColSupplier ); + + size_t getColumnCount() const { return m_aColumns.size(); } + const ColumnInfo& getColumnInfo( size_t _pos ); + + bool controlsInitialized() const { return m_bControlsInitialized; } + void initializeControls( const Sequence< Reference< XControl > >& _rControls ); + void deinitializeControls(); + +private: + typedef ::std::vector< ColumnInfo > ColumnInfos; + ColumnInfos m_aColumns; + bool m_bControlsInitialized; +}; + + +ColumnInfoCache::ColumnInfoCache( const Reference< XColumnsSupplier >& _rxColSupplier ) + :m_aColumns() + ,m_bControlsInitialized( false ) +{ + try + { + m_aColumns.clear(); + + Reference< XIndexAccess > xColumns( _rxColSupplier->getColumns(), UNO_QUERY_THROW ); + sal_Int32 nColumnCount = xColumns->getCount(); + m_aColumns.reserve( nColumnCount ); + + Reference< XPropertySet > xColumnProps; + for ( sal_Int32 i = 0; i < nColumnCount; ++i ) + { + ColumnInfo aColInfo; + aColInfo.xColumn.set( xColumns->getByIndex(i), UNO_QUERY_THROW ); + + xColumnProps.set( aColInfo.xColumn, UNO_QUERY_THROW ); + OSL_VERIFY( xColumnProps->getPropertyValue( FM_PROP_ISNULLABLE ) >>= aColInfo.nNullable ); + OSL_VERIFY( xColumnProps->getPropertyValue( FM_PROP_AUTOINCREMENT ) >>= aColInfo.bAutoIncrement ); + OSL_VERIFY( xColumnProps->getPropertyValue( FM_PROP_NAME ) >>= aColInfo.sName ); + OSL_VERIFY( xColumnProps->getPropertyValue( FM_PROP_ISREADONLY ) >>= aColInfo.bReadOnly ); + + m_aColumns.push_back( aColInfo ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +namespace +{ + bool lcl_isBoundTo( const Reference< XPropertySet >& _rxControlModel, const Reference< XInterface >& _rxNormDBField ) + { + Reference< XInterface > xNormBoundField( _rxControlModel->getPropertyValue( FM_PROP_BOUNDFIELD ), UNO_QUERY ); + return ( xNormBoundField == _rxNormDBField ); + } + + bool lcl_isInputRequired( const Reference< XPropertySet >& _rxControlModel ) + { + bool bInputRequired = false; + OSL_VERIFY( _rxControlModel->getPropertyValue( FM_PROP_INPUT_REQUIRED ) >>= bInputRequired ); + return bInputRequired; + } + + void lcl_resetColumnControlInfo( ColumnInfo& _rColInfo ) + { + _rColInfo.xFirstControlWithInputRequired.clear(); + _rColInfo.xFirstGridWithInputRequiredColumn.clear(); + _rColInfo.nRequiredGridColumn = -1; + } +} + + +void ColumnInfoCache::deinitializeControls() +{ + for (auto& rCol : m_aColumns) + { + lcl_resetColumnControlInfo( rCol ); + } + m_bControlsInitialized = false; +} + + +void ColumnInfoCache::initializeControls( const Sequence< Reference< XControl > >& _rControls ) +{ + try + { + // for every of our known columns, find the controls which are bound to this column + for (auto& rCol : m_aColumns) + { + OSL_ENSURE( !rCol.xFirstControlWithInputRequired.is() && !rCol.xFirstGridWithInputRequiredColumn.is() + && ( rCol.nRequiredGridColumn == -1 ), "ColumnInfoCache::initializeControls: called me twice?" ); + + lcl_resetColumnControlInfo( rCol ); + + Reference< XInterface > xNormColumn( rCol.xColumn, UNO_QUERY_THROW ); + + const Reference< XControl >* pControl( _rControls.getConstArray() ); + const Reference< XControl >* pControlEnd( pControl + _rControls.getLength() ); + for ( ; pControl != pControlEnd; ++pControl ) + { + if ( !pControl->is() ) + continue; + + Reference< XPropertySet > xModel( (*pControl)->getModel(), UNO_QUERY_THROW ); + Reference< XPropertySetInfo > xModelPSI( xModel->getPropertySetInfo(), UNO_SET_THROW ); + + // special handling for grid controls + Reference< XGrid > xGrid( *pControl, UNO_QUERY ); + if ( xGrid.is() ) + { + Reference< XIndexAccess > xGridColAccess( xModel, UNO_QUERY_THROW ); + sal_Int32 gridColCount = xGridColAccess->getCount(); + sal_Int32 gridCol = 0; + for ( gridCol = 0; gridCol < gridColCount; ++gridCol ) + { + Reference< XPropertySet > xGridColumnModel( xGridColAccess->getByIndex( gridCol ), UNO_QUERY_THROW ); + + if ( !lcl_isBoundTo( xGridColumnModel, xNormColumn ) + || !lcl_isInputRequired( xGridColumnModel ) + ) + continue; // with next grid column + + break; + } + + if ( gridCol < gridColCount ) + { + // found a grid column which is bound to the given + rCol.xFirstGridWithInputRequiredColumn = xGrid; + rCol.nRequiredGridColumn = gridCol; + break; + } + + continue; // with next control + } + + if ( !xModelPSI->hasPropertyByName( FM_PROP_BOUNDFIELD ) + || !lcl_isBoundTo( xModel, xNormColumn ) + || !lcl_isInputRequired( xModel ) + ) + continue; // with next control + + break; + } + + if ( pControl == pControlEnd ) + // did not find a control which is bound to this particular column, and for which the input is required + continue; // with next DB column + + rCol.xFirstControlWithInputRequired = *pControl; + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + m_bControlsInitialized = true; +} + + +const ColumnInfo& ColumnInfoCache::getColumnInfo( size_t _pos ) +{ + if ( _pos >= m_aColumns.size() ) + throw IndexOutOfBoundsException(); + + return m_aColumns[ _pos ]; +} + +namespace { + +class OParameterContinuation : public OInteraction< XInteractionSupplyParameters > +{ + Sequence< PropertyValue > m_aValues; + +public: + OParameterContinuation() { } + + const Sequence< PropertyValue >& getValues() const { return m_aValues; } + +// XInteractionSupplyParameters + virtual void SAL_CALL setParameters( const Sequence< PropertyValue >& _rValues ) override; +}; + +} + +void SAL_CALL OParameterContinuation::setParameters( const Sequence< PropertyValue >& _rValues ) +{ + m_aValues = _rValues; +} + + +// FmXAutoControl + +struct FmFieldInfo +{ + OUString aFieldName; + Reference< XPropertySet > xField; + Reference< XTextComponent > xText; + + FmFieldInfo(const Reference< XPropertySet >& _xField, const Reference< XTextComponent >& _xText) + :xField(_xField) + ,xText(_xText) + {xField->getPropertyValue(FM_PROP_NAME) >>= aFieldName;} +}; + +namespace { + +class FmXAutoControl: public UnoControl + +{ +public: + FmXAutoControl() :UnoControl() + { + } + + virtual OUString GetComponentServiceName() override {return "Edit";} + virtual void SAL_CALL createPeer( const Reference< XToolkit > & rxToolkit, const Reference< XWindowPeer > & rParentPeer ) override; + +protected: + virtual void ImplSetPeerProperty( const OUString& rPropName, const Any& rVal ) override; +}; + +} + +void FmXAutoControl::createPeer( const Reference< XToolkit > & rxToolkit, const Reference< XWindowPeer > & rParentPeer ) +{ + UnoControl::createPeer( rxToolkit, rParentPeer ); + + Reference< XTextComponent > xText(getPeer() , UNO_QUERY); + if (xText.is()) + { + xText->setText(SvxResId(RID_STR_AUTOFIELD)); + xText->setEditable(false); + } +} + + +void FmXAutoControl::ImplSetPeerProperty( const OUString& rPropName, const Any& rVal ) +{ + // these properties are ignored + if (rPropName == FM_PROP_TEXT) + return; + + UnoControl::ImplSetPeerProperty( rPropName, rVal ); +} + + +IMPL_LINK_NOARG( FormController, OnActivateTabOrder, Timer*, void ) +{ + activateTabOrder(); +} + +namespace { + +struct UpdateAllListeners +{ + bool operator()( const Reference< XDispatch >& _rxDispatcher ) const + { + static_cast< svx::OSingleFeatureDispatcher* >( _rxDispatcher.get() )->updateAllListeners(); + // the return is a dummy only so we can use this struct in a lambda expression + return true; + } +}; + +} + +IMPL_LINK_NOARG( FormController, OnInvalidateFeatures, Timer*, void ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + for (const auto& rFeature : m_aInvalidFeatures) + { + DispatcherContainer::const_iterator aDispatcherPos = m_aFeatureDispatchers.find( rFeature ); + if ( aDispatcherPos != m_aFeatureDispatchers.end() ) + { + // TODO: for the real and actual listener notifications, we should release + // our mutex + UpdateAllListeners( )( aDispatcherPos->second ); + } + } +} + +FormController::FormController(const Reference< css::uno::XComponentContext > & _rxORB ) + :FormController_BASE( m_aMutex ) + ,OPropertySetHelper( FormController_BASE::rBHelper ) + ,OSQLParserClient( _rxORB ) + ,m_xComponentContext( _rxORB ) + ,m_aActivateListeners(m_aMutex) + ,m_aModifyListeners(m_aMutex) + ,m_aErrorListeners(m_aMutex) + ,m_aDeleteListeners(m_aMutex) + ,m_aRowSetApproveListeners(m_aMutex) + ,m_aParameterListeners(m_aMutex) + ,m_aFilterListeners(m_aMutex) + ,m_xFormOperations() + ,m_aMode( OUString( "DataMode" ) ) + ,m_aLoadEvent( LINK( this, FormController, OnLoad ) ) + ,m_aToggleEvent( LINK( this, FormController, OnToggleAutoFields ) ) + ,m_aActivationEvent( LINK( this, FormController, OnActivated ) ) + ,m_aDeactivationEvent( LINK( this, FormController, OnDeactivated ) ) + ,m_nCurrentFilterPosition(-1) + ,m_bCurrentRecordModified(false) + ,m_bCurrentRecordNew(false) + ,m_bLocked(false) + ,m_bDBConnection(false) + ,m_bCycle(false) + ,m_bCanInsert(false) + ,m_bCanUpdate(false) + ,m_bCommitLock(false) + ,m_bModified(false) + ,m_bControlsSorted(false) + ,m_bFiltering(false) + ,m_bAttachEvents(true) + ,m_bDetachEvents(true) + ,m_bAttemptedHandlerCreation( false ) + ,m_bSuspendFilterTextListening( false ) +{ + + osl_atomic_increment(&m_refCount); + { + m_xTabController = TabController::create( m_xComponentContext ); + m_xAggregate.set( m_xTabController, UNO_QUERY_THROW ); + m_xAggregate->setDelegator( *this ); + } + osl_atomic_decrement(&m_refCount); + + m_aTabActivationIdle.SetPriority( TaskPriority::LOWEST ); + m_aTabActivationIdle.SetInvokeHandler( LINK( this, FormController, OnActivateTabOrder ) ); + + m_aFeatureInvalidationTimer.SetTimeout( 200 ); + m_aFeatureInvalidationTimer.SetInvokeHandler( LINK( this, FormController, OnInvalidateFeatures ) ); +} + + +FormController::~FormController() +{ + { + ::osl::MutexGuard aGuard( m_aMutex ); + + m_aLoadEvent.CancelPendingCall(); + m_aToggleEvent.CancelPendingCall(); + m_aActivationEvent.CancelPendingCall(); + m_aDeactivationEvent.CancelPendingCall(); + + if ( m_aTabActivationIdle.IsActive() ) + m_aTabActivationIdle.Stop(); + } + + if ( m_aFeatureInvalidationTimer.IsActive() ) + m_aFeatureInvalidationTimer.Stop(); + + disposeAllFeaturesAndDispatchers(); + + if ( m_xFormOperations.is() ) + m_xFormOperations->dispose(); + m_xFormOperations.clear(); + + // release of aggregation + if ( m_xAggregate.is() ) + { + m_xAggregate->setDelegator( nullptr ); + m_xAggregate.clear(); + } +} + + +void SAL_CALL FormController::acquire() throw () +{ + FormController_BASE::acquire(); +} + + +void SAL_CALL FormController::release() throw () +{ + FormController_BASE::release(); +} + + +Any SAL_CALL FormController::queryInterface( const Type& _rType ) +{ + Any aRet = FormController_BASE::queryInterface( _rType ); + if ( !aRet.hasValue() ) + aRet = OPropertySetHelper::queryInterface( _rType ); + if ( !aRet.hasValue() ) + aRet = m_xAggregate->queryAggregation( _rType ); + return aRet; +} + + +Sequence< sal_Int8 > SAL_CALL FormController::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +Sequence< Type > SAL_CALL FormController::getTypes( ) +{ + return comphelper::concatSequences( + FormController_BASE::getTypes(), + ::cppu::OPropertySetHelper::getTypes() + ); +} + +// XServiceInfo +sal_Bool SAL_CALL FormController::supportsService(const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +OUString SAL_CALL FormController::getImplementationName() +{ + return "org.openoffice.comp.svx.FormController"; +} + +Sequence< OUString> SAL_CALL FormController::getSupportedServiceNames() +{ + // service names which are supported only, but cannot be used to created an + // instance at a service factory + Sequence<OUString> aNonCreatableServiceNames { "com.sun.star.form.FormControllerDispatcher" }; + + // services which can be used to created an instance at a service factory + Sequence< OUString > aCreatableServiceNames( getSupportedServiceNames_Static() ); + return ::comphelper::concatSequences( aCreatableServiceNames, aNonCreatableServiceNames ); +} + + +sal_Bool SAL_CALL FormController::approveReset(const EventObject& /*rEvent*/) +{ + return true; +} + + +void SAL_CALL FormController::resetted(const EventObject& rEvent) +{ + ::osl::MutexGuard aGuard(m_aMutex); + if (getCurrentControl().is() && (getCurrentControl()->getModel() == rEvent.Source)) + m_bModified = false; +} + + +Sequence< OUString> const & FormController::getSupportedServiceNames_Static() +{ + static Sequence< OUString> const aServices + { + "com.sun.star.form.runtime.FormController", + "com.sun.star.awt.control.TabController" + }; + return aServices; +} + + +namespace +{ + struct ResetComponentText + { + void operator()( const Reference< XTextComponent >& _rxText ) + { + _rxText->setText( OUString() ); + } + }; + + struct RemoveComponentTextListener + { + explicit RemoveComponentTextListener( const Reference< XTextListener >& _rxListener ) + :m_xListener( _rxListener ) + { + } + + void operator()( const Reference< XTextComponent >& _rxText ) + { + _rxText->removeTextListener( m_xListener ); + } + + private: + Reference< XTextListener > m_xListener; + }; +} + + +void FormController::impl_setTextOnAllFilter_throw() +{ + m_bSuspendFilterTextListening = true; + ::comphelper::FlagGuard aResetFlag( m_bSuspendFilterTextListening ); + + // reset the text for all controls + ::std::for_each( m_aFilterComponents.begin(), m_aFilterComponents.end(), ResetComponentText() ); + + if ( m_aFilterRows.empty() ) + // nothing to do anymore + return; + + if ( m_nCurrentFilterPosition < 0 ) + return; + + // set the text for all filters + OSL_ENSURE( m_aFilterRows.size() > o3tl::make_unsigned(m_nCurrentFilterPosition), + "FormController::impl_setTextOnAllFilter_throw: m_nCurrentFilterPosition too big" ); + + if ( o3tl::make_unsigned(m_nCurrentFilterPosition) < m_aFilterRows.size() ) + { + FmFilterRow& rRow = m_aFilterRows[ m_nCurrentFilterPosition ]; + for (const auto& rEntry : rRow) + { + rEntry.first->setText( rEntry.second ); + } + } +} +// OPropertySetHelper + +sal_Bool FormController::convertFastPropertyValue( Any & /*rConvertedValue*/, Any & /*rOldValue*/, + sal_Int32 /*nHandle*/, const Any& /*rValue*/ ) +{ + return false; +} + + +void FormController::setFastPropertyValue_NoBroadcast( sal_Int32 /*nHandle*/, const Any& /*rValue*/ ) +{ +} + + +void FormController::getFastPropertyValue( Any& rValue, sal_Int32 nHandle ) const +{ + switch (nHandle) + { + case FM_ATTR_FILTER: + { + OUStringBuffer aFilter; + Reference<XConnection> xConnection(getConnection(Reference< XRowSet>(m_xModelAsIndex, UNO_QUERY))); + if (xConnection.is()) + { + Reference< XNumberFormatsSupplier> xFormatSupplier( getNumberFormats( xConnection, true ) ); + Reference< XNumberFormatter> xFormatter = NumberFormatter::create(m_xComponentContext); + xFormatter->attachNumberFormatsSupplier(xFormatSupplier); + + // now add the filter rows + try + { + for (const FmFilterRow& rRow : m_aFilterRows) + { + if ( rRow.empty() ) + continue; + + OUStringBuffer aRowFilter; + for ( FmFilterRow::const_iterator condition = rRow.begin(); condition != rRow.end(); ++condition ) + { + // get the field of the controls map + Reference< XControl > xControl( condition->first, UNO_QUERY_THROW ); + Reference< XPropertySet > xModelProps( xControl->getModel(), UNO_QUERY_THROW ); + Reference< XPropertySet > xField( xModelProps->getPropertyValue( FM_PROP_BOUNDFIELD ), UNO_QUERY_THROW ); + + OUString sFilterValue( condition->second ); + + OUString sErrorMsg, sCriteria; + const std::unique_ptr< OSQLParseNode > pParseNode = + predicateTree( sErrorMsg, sFilterValue, xFormatter, xField ); + OSL_ENSURE( pParseNode != nullptr, "FormController::getFastPropertyValue: could not parse the field value predicate!" ); + if ( pParseNode != nullptr ) + { + // don't use a parse context here, we need it unlocalized + pParseNode->parseNodeToStr( sCriteria, xConnection ); + if ( condition != rRow.begin() ) + aRowFilter.append( " AND " ); + aRowFilter.append( sCriteria ); + } + } + if ( !aRowFilter.isEmpty() ) + { + if ( !aFilter.isEmpty() ) + aFilter.append( " OR " ); + + aFilter.append( "( " ); + aFilter.append( aRowFilter.makeStringAndClear() ); + aFilter.append( " )" ); + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + aFilter.setLength(0); + } + } + rValue <<= aFilter.makeStringAndClear(); + } + break; + + case FM_ATTR_FORM_OPERATIONS: + rValue <<= m_xFormOperations; + break; + } +} + + +Reference< XPropertySetInfo > FormController::getPropertySetInfo() +{ + static Reference< XPropertySetInfo > xInfo( createPropertySetInfo( getInfoHelper() ) ); + return xInfo; +} + + +void FormController::fillProperties( + Sequence< Property >& /* [out] */ _rProps, + Sequence< Property >& /* [out] */ /*_rAggregateProps*/ + ) const +{ + _rProps.realloc(2); + sal_Int32 nPos = 0; + Property* pDesc = _rProps.getArray(); + + pDesc[nPos++] = Property(FM_PROP_FILTER, FM_ATTR_FILTER, + cppu::UnoType<OUString>::get(), + PropertyAttribute::READONLY); + pDesc[nPos++] = Property(FM_PROP_FORM_OPERATIONS, FM_ATTR_FORM_OPERATIONS, + cppu::UnoType<XFormOperations>::get(), + PropertyAttribute::READONLY); +} + + +::cppu::IPropertyArrayHelper& FormController::getInfoHelper() +{ + return *getArrayHelper(); +} + +// XFilterController + +void SAL_CALL FormController::addFilterControllerListener( const Reference< XFilterControllerListener >& Listener ) +{ + m_aFilterListeners.addInterface( Listener ); +} + + +void SAL_CALL FormController::removeFilterControllerListener( const Reference< XFilterControllerListener >& Listener ) +{ + m_aFilterListeners.removeInterface( Listener ); +} + + +::sal_Int32 SAL_CALL FormController::getFilterComponents() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + return m_aFilterComponents.size(); +} + + +::sal_Int32 SAL_CALL FormController::getDisjunctiveTerms() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + return m_aFilterRows.size(); +} + + +void SAL_CALL FormController::setPredicateExpression( ::sal_Int32 Component, ::sal_Int32 Term, const OUString& PredicateExpression ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + if ( ( Component < 0 ) || ( Component >= getFilterComponents() ) || ( Term < 0 ) || ( Term >= getDisjunctiveTerms() ) ) + throw IndexOutOfBoundsException( OUString(), *this ); + + Reference< XTextComponent > xText( m_aFilterComponents[ Component ] ); + xText->setText( PredicateExpression ); + + FmFilterRow& rFilterRow = m_aFilterRows[ Term ]; + if ( !PredicateExpression.isEmpty() ) + rFilterRow[ xText ] = PredicateExpression; + else + rFilterRow.erase( xText ); +} + + +Reference< XControl > FormController::getFilterComponent( ::sal_Int32 Component ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + if ( ( Component < 0 ) || ( Component >= getFilterComponents() ) ) + throw IndexOutOfBoundsException( OUString(), *this ); + + return Reference< XControl >( m_aFilterComponents[ Component ], UNO_QUERY ); +} + + +Sequence< Sequence< OUString > > FormController::getPredicateExpressions() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + Sequence< Sequence< OUString > > aExpressions( m_aFilterRows.size() ); + sal_Int32 termIndex = 0; + for (const FmFilterRow& rRow : m_aFilterRows) + { + Sequence< OUString > aConjunction( m_aFilterComponents.size() ); + sal_Int32 componentIndex = 0; + for (const auto& rComp : m_aFilterComponents) + { + FmFilterRow::const_iterator predicate = rRow.find( rComp ); + if ( predicate != rRow.end() ) + aConjunction[ componentIndex ] = predicate->second; + ++componentIndex; + } + + aExpressions[ termIndex ] = aConjunction; + ++termIndex; + } + + return aExpressions; +} + + +void SAL_CALL FormController::removeDisjunctiveTerm( ::sal_Int32 Term ) +{ + // SYNCHRONIZED --> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + if ( ( Term < 0 ) || ( Term >= getDisjunctiveTerms() ) ) + throw IndexOutOfBoundsException( OUString(), *this ); + + // if the to-be-deleted row is our current row, we need to shift + if ( Term == m_nCurrentFilterPosition ) + { + if ( m_nCurrentFilterPosition < sal_Int32( m_aFilterRows.size() - 1 ) ) + ++m_nCurrentFilterPosition; + else + --m_nCurrentFilterPosition; + } + + FmFilterRows::iterator pos = m_aFilterRows.begin() + Term; + m_aFilterRows.erase( pos ); + + // adjust m_nCurrentFilterPosition if the removed row preceded it + if ( Term < m_nCurrentFilterPosition ) + --m_nCurrentFilterPosition; + + SAL_WARN_IF( !( ( m_nCurrentFilterPosition < 0 ) != ( m_aFilterRows.empty() ) ), + "svx.form", "FormController::removeDisjunctiveTerm: inconsistency!" ); + + // update the texts in the filter controls + impl_setTextOnAllFilter_throw(); + + FilterEvent aEvent; + aEvent.Source = *this; + aEvent.DisjunctiveTerm = Term; + aGuard.clear(); + // <-- SYNCHRONIZED + + m_aFilterListeners.notifyEach( &XFilterControllerListener::disjunctiveTermRemoved, aEvent ); +} + + +void SAL_CALL FormController::appendEmptyDisjunctiveTerm() +{ + // SYNCHRONIZED --> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + impl_appendEmptyFilterRow( aGuard ); + // <-- SYNCHRONIZED +} + + +::sal_Int32 SAL_CALL FormController::getActiveTerm() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + return m_nCurrentFilterPosition; +} + + +void SAL_CALL FormController::setActiveTerm( ::sal_Int32 ActiveTerm ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + if ( ( ActiveTerm < 0 ) || ( ActiveTerm >= getDisjunctiveTerms() ) ) + throw IndexOutOfBoundsException( OUString(), *this ); + + if ( ActiveTerm == getActiveTerm() ) + return; + + m_nCurrentFilterPosition = ActiveTerm; + impl_setTextOnAllFilter_throw(); +} + +// XElementAccess + +sal_Bool SAL_CALL FormController::hasElements() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + return !m_aChildren.empty(); +} + + +Type SAL_CALL FormController::getElementType() +{ + return cppu::UnoType<XFormController>::get(); + +} + +// XEnumerationAccess + +Reference< XEnumeration > SAL_CALL FormController::createEnumeration() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + return new ::comphelper::OEnumerationByIndex(this); +} + +// XIndexAccess + +sal_Int32 SAL_CALL FormController::getCount() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + return m_aChildren.size(); +} + + +Any SAL_CALL FormController::getByIndex(sal_Int32 Index) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if (Index < 0 || + Index >= static_cast<sal_Int32>(m_aChildren.size())) + throw IndexOutOfBoundsException(); + + return makeAny( m_aChildren[ Index ] ); +} + +// EventListener + +void SAL_CALL FormController::disposing(const EventObject& e) +{ + // has the container been disposed + ::osl::MutexGuard aGuard( m_aMutex ); + Reference< XControlContainer > xContainer(e.Source, UNO_QUERY); + if (xContainer.is()) + { + setContainer(Reference< XControlContainer > ()); + } + else + { + // has a control been disposed + Reference< XControl > xControl(e.Source, UNO_QUERY); + if (xControl.is()) + { + if (getContainer().is()) + removeControl(xControl); + } + } +} + +// OComponentHelper + +void FormController::disposeAllFeaturesAndDispatchers() +{ + for (auto& rDispatcher : m_aFeatureDispatchers) + { + try + { + ::comphelper::disposeComponent( rDispatcher.second ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + m_aFeatureDispatchers.clear(); +} + + +void FormController::disposing() +{ + EventObject aEvt( *this ); + + // if we're still active, simulate a "deactivated" event + if ( m_xActiveControl.is() ) + m_aActivateListeners.notifyEach( &XFormControllerListener::formDeactivated, aEvt ); + + // notify all our listeners + m_aActivateListeners.disposeAndClear(aEvt); + m_aModifyListeners.disposeAndClear(aEvt); + m_aErrorListeners.disposeAndClear(aEvt); + m_aDeleteListeners.disposeAndClear(aEvt); + m_aRowSetApproveListeners.disposeAndClear(aEvt); + m_aParameterListeners.disposeAndClear(aEvt); + m_aFilterListeners.disposeAndClear(aEvt); + + removeBoundFieldListener(); + stopFiltering(); + + m_aControlBorderManager.restoreAll(); + + m_aFilterRows.clear(); + + ::osl::MutexGuard aGuard( m_aMutex ); + m_xActiveControl = nullptr; + implSetCurrentControl( nullptr ); + + // clean up our children + for (const auto& rpChild : m_aChildren) + { + // search the position of the model within the form + Reference< XFormComponent > xForm(rpChild->getModel(), UNO_QUERY); + sal_uInt32 nPos = m_xModelAsIndex->getCount(); + Reference< XFormComponent > xTemp; + for( ; nPos; ) + { + + m_xModelAsIndex->getByIndex( --nPos ) >>= xTemp; + if ( xForm.get() == xTemp.get() ) + { + Reference< XInterface > xIfc( rpChild, UNO_QUERY ); + m_xModelAsManager->detach( nPos, xIfc ); + break; + } + } + + Reference< XComponent > (rpChild, UNO_QUERY_THROW)->dispose(); + } + m_aChildren.clear(); + + disposeAllFeaturesAndDispatchers(); + + if ( m_xFormOperations.is() ) + m_xFormOperations->dispose(); + m_xFormOperations.clear(); + + if (m_bDBConnection) + unload(); + + setContainer( nullptr ); + setModel( nullptr ); + setParent( nullptr ); + + ::comphelper::disposeComponent( m_xComposer ); + + m_bDBConnection = false; +} + + +namespace +{ + bool lcl_shouldUseDynamicControlBorder( const Reference< XInterface >& _rxForm, const Any& _rDynamicColorProp ) + { + bool bDoUse = false; + if ( !( _rDynamicColorProp >>= bDoUse ) ) + { + DocumentType eDocType = DocumentClassification::classifyHostDocument( _rxForm ); + return ControlLayouter::useDynamicBorderColor( eDocType ); + } + return bDoUse; + } +} + + +void SAL_CALL FormController::propertyChange(const PropertyChangeEvent& evt) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + if ( evt.PropertyName == FM_PROP_BOUNDFIELD ) + { + Reference<XPropertySet> xOldBound; + evt.OldValue >>= xOldBound; + if ( !xOldBound.is() && evt.NewValue.hasValue() ) + { + Reference< XControlModel > xControlModel(evt.Source,UNO_QUERY); + Reference< XControl > xControl = findControl(m_aControls,xControlModel,false,false); + if ( xControl.is() ) + { + startControlModifyListening( xControl ); + Reference<XPropertySet> xProp(xControlModel,UNO_QUERY); + if ( xProp.is() ) + xProp->removePropertyChangeListener(FM_PROP_BOUNDFIELD, this); + } + } + } + else + { + bool bModifiedChanged = (evt.PropertyName == FM_PROP_ISMODIFIED); + bool bNewChanged = (evt.PropertyName == FM_PROP_ISNEW); + if (bModifiedChanged || bNewChanged) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if (bModifiedChanged) + m_bCurrentRecordModified = ::comphelper::getBOOL(evt.NewValue); + else + m_bCurrentRecordNew = ::comphelper::getBOOL(evt.NewValue); + + // toggle the locking + if (m_bLocked != determineLockState()) + { + m_bLocked = !m_bLocked; + setLocks(); + if (isListeningForChanges()) + startListening(); + else + stopListening(); + } + + if ( bNewChanged ) + m_aToggleEvent.Call(); + + if (!m_bCurrentRecordModified) + m_bModified = false; + } + else if ( evt.PropertyName == FM_PROP_DYNAMIC_CONTROL_BORDER ) + { + bool bEnable = lcl_shouldUseDynamicControlBorder( evt.Source, evt.NewValue ); + if ( bEnable ) + { + m_aControlBorderManager.enableDynamicBorderColor(); + if ( m_xActiveControl.is() ) + m_aControlBorderManager.focusGained( m_xActiveControl.get() ); + } + else + { + m_aControlBorderManager.disableDynamicBorderColor(); + } + } + } +} + + +bool FormController::replaceControl( const Reference< XControl >& _rxExistentControl, const Reference< XControl >& _rxNewControl ) +{ + bool bSuccess = false; + try + { + Reference< XIdentifierReplace > xContainer( getContainer(), UNO_QUERY ); + DBG_ASSERT( xContainer.is(), "FormController::replaceControl: yes, it's not required by the service description, but XIdentifierReplace would be nice!" ); + if ( xContainer.is() ) + { + // look up the ID of _rxExistentControl + Sequence< sal_Int32 > aIdentifiers( xContainer->getIdentifiers() ); + const sal_Int32* pIdentifiers = std::find_if(aIdentifiers.begin(), aIdentifiers.end(), + [&xContainer, &_rxExistentControl](const sal_Int32 nId) { + Reference< XControl > xCheck( xContainer->getByIdentifier( nId ), UNO_QUERY ); + return xCheck == _rxExistentControl; + }); + DBG_ASSERT( pIdentifiers != aIdentifiers.end(), "FormController::replaceControl: did not find the control in the container!" ); + if ( pIdentifiers != aIdentifiers.end() ) + { + bool bReplacedWasActive = ( m_xActiveControl.get() == _rxExistentControl.get() ); + bool bReplacedWasCurrent = ( m_xCurrentControl.get() == _rxExistentControl.get() ); + + if ( bReplacedWasActive ) + { + m_xActiveControl = nullptr; + implSetCurrentControl( nullptr ); + } + else if ( bReplacedWasCurrent ) + { + implSetCurrentControl( _rxNewControl ); + } + + // carry over the model + _rxNewControl->setModel( _rxExistentControl->getModel() ); + + xContainer->replaceByIdentifer( *pIdentifiers, makeAny( _rxNewControl ) ); + bSuccess = true; + + if ( bReplacedWasActive ) + { + Reference< XWindow > xControlWindow( _rxNewControl, UNO_QUERY ); + if ( xControlWindow.is() ) + xControlWindow->setFocus(); + } + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + Reference< XControl > xDisposeIt( bSuccess ? _rxExistentControl : _rxNewControl ); + ::comphelper::disposeComponent( xDisposeIt ); + return bSuccess; +} + + +void FormController::toggleAutoFields(bool bAutoFields) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + + + Sequence< Reference< XControl > > aControlsCopy( m_aControls ); + const Reference< XControl >* pControls = aControlsCopy.getConstArray(); + sal_Int32 nControls = aControlsCopy.getLength(); + + if (bAutoFields) + { + // as we don't want new controls to be attached to the scripting environment + // we change attach flags + m_bAttachEvents = false; + for (sal_Int32 i = nControls; i > 0;) + { + Reference< XControl > xControl = pControls[--i]; + if (xControl.is()) + { + Reference< XPropertySet > xSet(xControl->getModel(), UNO_QUERY); + if (xSet.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet)) + { + // does the model use a bound field ? + Reference< XPropertySet > xField; + xSet->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField; + + // is it an autofield? + if ( xField.is() + && ::comphelper::hasProperty( FM_PROP_AUTOINCREMENT, xField ) + && ::comphelper::getBOOL( xField->getPropertyValue( FM_PROP_AUTOINCREMENT ) ) + ) + { + replaceControl( xControl, new FmXAutoControl() ); + } + } + } + } + m_bAttachEvents = true; + } + else + { + m_bDetachEvents = false; + for (sal_Int32 i = nControls; i > 0;) + { + Reference< XControl > xControl = pControls[--i]; + if (xControl.is()) + { + Reference< XPropertySet > xSet(xControl->getModel(), UNO_QUERY); + if (xSet.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet)) + { + // does the model use a bound field ? + Reference< XPropertySet > xField; + xSet->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField; + + // is it an autofield? + if ( xField.is() + && ::comphelper::hasProperty( FM_PROP_AUTOINCREMENT, xField ) + && ::comphelper::getBOOL( xField->getPropertyValue(FM_PROP_AUTOINCREMENT ) ) + ) + { + OUString sServiceName; + OSL_VERIFY( xSet->getPropertyValue( FM_PROP_DEFAULTCONTROL ) >>= sServiceName ); + Reference< XControl > xNewControl( m_xComponentContext->getServiceManager()->createInstanceWithContext( sServiceName, m_xComponentContext ), UNO_QUERY ); + replaceControl( xControl, xNewControl ); + } + } + } + } + m_bDetachEvents = true; + } +} + + +IMPL_LINK_NOARG(FormController, OnToggleAutoFields, void*, void) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + + toggleAutoFields(m_bCurrentRecordNew); +} + +// XTextListener + +void SAL_CALL FormController::textChanged(const TextEvent& e) +{ + // SYNCHRONIZED --> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + if ( !m_bFiltering ) + { + impl_onModify(); + return; + } + + if ( m_bSuspendFilterTextListening ) + return; + + Reference< XTextComponent > xText(e.Source,UNO_QUERY); + OUString aText = xText->getText(); + + if ( m_aFilterRows.empty() ) + appendEmptyDisjunctiveTerm(); + + // find the current row + if ( ( m_nCurrentFilterPosition < 0 ) || ( o3tl::make_unsigned(m_nCurrentFilterPosition) >= m_aFilterRows.size() ) ) + { + OSL_ENSURE( false, "FormController::textChanged: m_nCurrentFilterPosition is wrong!" ); + return; + } + + FmFilterRow& rRow = m_aFilterRows[ m_nCurrentFilterPosition ]; + + // do we have a new filter + if (!aText.isEmpty()) + rRow[xText] = aText; + else + { + // do we have the control in the row + FmFilterRow::iterator iter = rRow.find(xText); + // erase the entry out of the row + if (iter != rRow.end()) + rRow.erase(iter); + } + + // multiplex the event to our FilterControllerListeners + FilterEvent aEvent; + aEvent.Source = *this; + aEvent.FilterComponent = ::std::find( m_aFilterComponents.begin(), m_aFilterComponents.end(), xText ) - m_aFilterComponents.begin(); + aEvent.DisjunctiveTerm = getActiveTerm(); + aEvent.PredicateExpression = aText; + + aGuard.clear(); + // <-- SYNCHRONIZED + + // notify the changed filter expression + m_aFilterListeners.notifyEach( &XFilterControllerListener::predicateExpressionChanged, aEvent ); +} + +// XItemListener + +void SAL_CALL FormController::itemStateChanged(const ItemEvent& /*rEvent*/) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + impl_onModify(); +} + +// XModificationBroadcaster + +void SAL_CALL FormController::addModifyListener(const Reference< XModifyListener > & l) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + m_aModifyListeners.addInterface( l ); +} + + +void FormController::removeModifyListener(const Reference< XModifyListener > & l) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + m_aModifyListeners.removeInterface( l ); +} + +// XModificationListener + +void FormController::modified( const EventObject& _rEvent ) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + + try + { + if ( _rEvent.Source != m_xActiveControl ) + { // let this control grab the focus + // (this case may happen if somebody moves the scroll wheel of the mouse over a control + // which does not have the focus) + // 85511 - 29.05.2001 - frank.schoenheit@germany.sun.com + + // also, it happens when an image control gets a new image by double-clicking it + // #i88458# / 2009-01-12 / frank.schoenheit@sun.com + Reference< XWindow > xControlWindow( _rEvent.Source, UNO_QUERY_THROW ); + xControlWindow->setFocus(); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + impl_onModify(); +} + + +void FormController::impl_checkDisposed_throw() const +{ + if ( impl_isDisposed_nofail() ) + throw DisposedException( OUString(), *const_cast< FormController* >( this ) ); +} + + +void FormController::impl_onModify() +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !m_bModified ) + m_bModified = true; + } + + EventObject aEvt(static_cast<cppu::OWeakObject*>(this)); + m_aModifyListeners.notifyEach( &XModifyListener::modified, aEvt ); +} + + +void FormController::impl_addFilterRow( const FmFilterRow& _row ) +{ + m_aFilterRows.push_back( _row ); + + if ( m_aFilterRows.size() == 1 ) + { // that's the first row ever + OSL_ENSURE( m_nCurrentFilterPosition == -1, "FormController::impl_addFilterRow: inconsistency!" ); + m_nCurrentFilterPosition = 0; + } +} + + +void FormController::impl_appendEmptyFilterRow( ::osl::ClearableMutexGuard& _rClearBeforeNotify ) +{ + // SYNCHRONIZED --> + impl_addFilterRow( FmFilterRow() ); + + // notify the listeners + FilterEvent aEvent; + aEvent.Source = *this; + aEvent.DisjunctiveTerm = static_cast<sal_Int32>(m_aFilterRows.size()) - 1; + _rClearBeforeNotify.clear(); + // <-- SYNCHRONIZED + m_aFilterListeners.notifyEach( &XFilterControllerListener::disjunctiveTermAdded, aEvent ); +} + + +bool FormController::determineLockState() const +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + // a.) in filter mode we are always locked + // b.) if we have no valid model or our model (a result set) is not alive -> we're locked + // c.) if we are inserting everything is OK and we are not locked + // d.) if are not updatable or on invalid position + Reference< XResultSet > xResultSet(m_xModelAsIndex, UNO_QUERY); + if (m_bFiltering || !xResultSet.is() || !isRowSetAlive(xResultSet)) + return true; + else + return !(m_bCanInsert && m_bCurrentRecordNew) + && (xResultSet->isBeforeFirst() || xResultSet->isAfterLast() || xResultSet->rowDeleted() || !m_bCanUpdate); +} + +// FocusListener + +void FormController::focusGained(const FocusEvent& e) +{ + // SYNCHRONIZED --> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + m_aControlBorderManager.focusGained( e.Source ); + + Reference< XControl > xControl(e.Source, UNO_QUERY); + if (m_bDBConnection) + { + // do we need to keep the locking of the commit + // we hold the lock as long as the control differs from the current + // otherwise we disabled the lock + m_bCommitLock = m_bCommitLock && xControl.get() != m_xCurrentControl.get(); + if (m_bCommitLock) + return; + + // when do we have to commit a value to form or a filter + // a.) if the current value is modified + // b.) there must be a current control + // c.) and it must be different from the new focus owning control or + // d.) the focus is moving around (so we have only one control) + + if ( ( m_bModified || m_bFiltering ) + && m_xCurrentControl.is() + && ( ( xControl.get() != m_xCurrentControl.get() ) + || ( ( e.FocusFlags & FocusChangeReason::AROUND ) + && ( m_bCycle || m_bFiltering ) + ) + ) + ) + { + // check the old control if the content is ok +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + Reference< XBoundControl > xLockingTest(m_xCurrentControl, UNO_QUERY); + bool bControlIsLocked = xLockingTest.is() && xLockingTest->getLock(); + assert(!bControlIsLocked && "FormController::Gained: I'm modified and the current control is locked ? How this ?"); + // normally, a locked control should not be modified, so probably my bModified must + // have been set from a different context, which I would not understand ... +#endif + DBG_ASSERT(m_xCurrentControl.is(), "no CurrentControl set"); + // first the control ask if it supports the IFace + Reference< XBoundComponent > xBound(m_xCurrentControl, UNO_QUERY); + if (!xBound.is() && m_xCurrentControl.is()) + xBound.set(m_xCurrentControl->getModel(), UNO_QUERY); + + // lock if we lose the focus during commit + m_bCommitLock = true; + + // commit unsuccessful, reset focus + if (xBound.is() && !xBound->commit()) + { + // the commit failed and we don't commit again until the current control + // which couldn't be commit gains the focus again + Reference< XWindow > xWindow(m_xCurrentControl, UNO_QUERY); + if (xWindow.is()) + xWindow->setFocus(); + return; + } + else + { + m_bModified = false; + m_bCommitLock = false; + } + } + + if (!m_bFiltering && m_bCycle && (e.FocusFlags & FocusChangeReason::AROUND) && m_xCurrentControl.is()) + { + OSL_ENSURE( m_xFormOperations.is(), "FormController::focusGained: hmm?" ); + // should have been created in setModel + try + { + if ( e.FocusFlags & FocusChangeReason::FORWARD ) + { + if ( m_xFormOperations.is() && m_xFormOperations->isEnabled( FormFeature::MoveToNext ) ) + m_xFormOperations->execute( FormFeature::MoveToNext ); + } + else // backward + { + if ( m_xFormOperations.is() && m_xFormOperations->isEnabled( FormFeature::MoveToPrevious ) ) + m_xFormOperations->execute( FormFeature::MoveToPrevious ); + } + } + catch ( const Exception& ) + { + // don't handle this any further. That's an ... admissible error. + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + } + + // still one and the same control + if ( ( m_xActiveControl == xControl ) + && ( xControl == m_xCurrentControl ) + ) + { + DBG_ASSERT(m_xCurrentControl.is(), "No CurrentControl selected"); + return; + } + + bool bActivated = !m_xActiveControl.is() && xControl.is(); + + m_xActiveControl = xControl; + + implSetCurrentControl( xControl ); + SAL_WARN_IF( !m_xCurrentControl.is(), "svx.form", "implSetCurrentControl did nonsense!" ); + + if ( bActivated ) + { + // (asynchronously) call activation handlers + m_aActivationEvent.Call(); + + // call modify listeners + if ( m_bModified ) + m_aModifyListeners.notifyEach( &XModifyListener::modified, EventObject( *this ) ); + } + + // invalidate all features which depend on the currently focused control + if ( m_bDBConnection && !m_bFiltering ) + implInvalidateCurrentControlDependentFeatures(); + + if ( !m_xCurrentControl.is() ) + return; + + // control gets focus, then possibly in the visible range + Reference< XFormControllerContext > xContext( m_xFormControllerContext ); + Reference< XControl > xCurrentControl( m_xCurrentControl ); + aGuard.clear(); + // <-- SYNCHRONIZED + + if ( xContext.is() ) + xContext->makeVisible( xCurrentControl ); +} + + +IMPL_LINK_NOARG( FormController, OnActivated, void*, void ) +{ + EventObject aEvent; + aEvent.Source = *this; + m_aActivateListeners.notifyEach( &XFormControllerListener::formActivated, aEvent ); +} + + +IMPL_LINK_NOARG( FormController, OnDeactivated, void*, void ) +{ + EventObject aEvent; + aEvent.Source = *this; + m_aActivateListeners.notifyEach( &XFormControllerListener::formDeactivated, aEvent ); +} + + +void FormController::focusLost(const FocusEvent& e) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + + m_aControlBorderManager.focusLost( e.Source ); + + Reference< XWindowPeer > xNext(e.NextFocus, UNO_QUERY); + Reference< XControl > xNextControl = isInList(xNext); + if (!xNextControl.is()) + { + m_xActiveControl = nullptr; + m_aDeactivationEvent.Call(); + } +} + + +void SAL_CALL FormController::mousePressed( const awt::MouseEvent& /*_rEvent*/ ) +{ + // not interested in +} + + +void SAL_CALL FormController::mouseReleased( const awt::MouseEvent& /*_rEvent*/ ) +{ + // not interested in +} + + +void SAL_CALL FormController::mouseEntered( const awt::MouseEvent& _rEvent ) +{ + m_aControlBorderManager.mouseEntered( _rEvent.Source ); +} + + +void SAL_CALL FormController::mouseExited( const awt::MouseEvent& _rEvent ) +{ + m_aControlBorderManager.mouseExited( _rEvent.Source ); +} + + +void SAL_CALL FormController::componentValidityChanged( const EventObject& _rSource ) +{ + Reference< XControl > xControl( findControl( m_aControls, Reference< XControlModel >( _rSource.Source, UNO_QUERY ), false, false ) ); + Reference< XValidatableFormComponent > xValidatable( _rSource.Source, UNO_QUERY ); + + OSL_ENSURE( xControl.is() && xValidatable.is(), "FormController::componentValidityChanged: huh?" ); + + if ( xControl.is() && xValidatable.is() ) + m_aControlBorderManager.validityChanged( xControl, xValidatable ); +} + + +void FormController::setModel(const Reference< XTabControllerModel > & Model) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + DBG_ASSERT(m_xTabController.is(), "FormController::setModel : invalid aggregate !"); + + try + { + // disconnect from the old model + if (m_xModelAsIndex.is()) + { + if (m_bDBConnection) + { + // we are currently working on the model + EventObject aEvt(m_xModelAsIndex); + unloaded(aEvt); + } + + Reference< XLoadable > xForm(m_xModelAsIndex, UNO_QUERY); + if (xForm.is()) + xForm->removeLoadListener(this); + + Reference< XSQLErrorBroadcaster > xBroadcaster(m_xModelAsIndex, UNO_QUERY); + if (xBroadcaster.is()) + xBroadcaster->removeSQLErrorListener(this); + + Reference< XDatabaseParameterBroadcaster > xParamBroadcaster(m_xModelAsIndex, UNO_QUERY); + if (xParamBroadcaster.is()) + xParamBroadcaster->removeParameterListener(this); + + } + + disposeAllFeaturesAndDispatchers(); + + if ( m_xFormOperations.is() ) + m_xFormOperations->dispose(); + m_xFormOperations.clear(); + + // set the new model wait for the load event + if (m_xTabController.is()) + m_xTabController->setModel(Model); + m_xModelAsIndex.set(Model, UNO_QUERY); + m_xModelAsManager.set(Model, UNO_QUERY); + + // only if both ifaces exit, the controller will work successful + if (!m_xModelAsIndex.is() || !m_xModelAsManager.is()) + { + m_xModelAsManager = nullptr; + m_xModelAsIndex = nullptr; + } + + if (m_xModelAsIndex.is()) + { + // re-create m_xFormOperations + m_xFormOperations = FormOperations::createWithFormController( m_xComponentContext, this ); + m_xFormOperations->setFeatureInvalidation( this ); + + // adding load and ui interaction listeners + Reference< XLoadable > xForm(Model, UNO_QUERY); + if (xForm.is()) + xForm->addLoadListener(this); + + Reference< XSQLErrorBroadcaster > xBroadcaster(Model, UNO_QUERY); + if (xBroadcaster.is()) + xBroadcaster->addSQLErrorListener(this); + + Reference< XDatabaseParameterBroadcaster > xParamBroadcaster(Model, UNO_QUERY); + if (xParamBroadcaster.is()) + xParamBroadcaster->addParameterListener(this); + + // well, is the database already loaded? + // then we have to simulate a load event + Reference< XLoadable > xCursor(m_xModelAsIndex, UNO_QUERY); + if (xCursor.is() && xCursor->isLoaded()) + { + EventObject aEvt(xCursor); + loaded(aEvt); + } + + Reference< XPropertySet > xModelProps( m_xModelAsIndex, UNO_QUERY ); + Reference< XPropertySetInfo > xPropInfo( xModelProps->getPropertySetInfo() ); + if ( xPropInfo.is() + && xPropInfo->hasPropertyByName( FM_PROP_DYNAMIC_CONTROL_BORDER ) + && xPropInfo->hasPropertyByName( FM_PROP_CONTROL_BORDER_COLOR_FOCUS ) + && xPropInfo->hasPropertyByName( FM_PROP_CONTROL_BORDER_COLOR_MOUSE ) + && xPropInfo->hasPropertyByName( FM_PROP_CONTROL_BORDER_COLOR_INVALID ) + ) + { + bool bEnableDynamicControlBorder = lcl_shouldUseDynamicControlBorder( + xModelProps.get(), xModelProps->getPropertyValue( FM_PROP_DYNAMIC_CONTROL_BORDER ) ); + if ( bEnableDynamicControlBorder ) + m_aControlBorderManager.enableDynamicBorderColor(); + else + m_aControlBorderManager.disableDynamicBorderColor(); + + Color nColor; + if ( xModelProps->getPropertyValue( FM_PROP_CONTROL_BORDER_COLOR_FOCUS ) >>= nColor ) + m_aControlBorderManager.setStatusColor( ControlStatus::Focused, nColor ); + if ( xModelProps->getPropertyValue( FM_PROP_CONTROL_BORDER_COLOR_MOUSE ) >>= nColor ) + m_aControlBorderManager.setStatusColor( ControlStatus::MouseHover, nColor ); + if ( xModelProps->getPropertyValue( FM_PROP_CONTROL_BORDER_COLOR_INVALID ) >>= nColor ) + m_aControlBorderManager.setStatusColor( ControlStatus::Invalid, nColor ); + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +Reference< XTabControllerModel > FormController::getModel() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + DBG_ASSERT(m_xTabController.is(), "FormController::getModel : invalid aggregate !"); + if (!m_xTabController.is()) + return Reference< XTabControllerModel > (); + return m_xTabController->getModel(); +} + + +void FormController::addToEventAttacher(const Reference< XControl > & xControl) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + OSL_ENSURE( xControl.is(), "FormController::addToEventAttacher: invalid control - how did you reach this?" ); + if ( !xControl.is() ) + return; /* throw IllegalArgumentException(); */ + + // register at the event attacher + Reference< XFormComponent > xComp(xControl->getModel(), UNO_QUERY); + if (xComp.is() && m_xModelAsIndex.is()) + { + // and look for the position of the ControlModel in it + sal_uInt32 nPos = m_xModelAsIndex->getCount(); + Reference< XFormComponent > xTemp; + for( ; nPos; ) + { + m_xModelAsIndex->getByIndex(--nPos) >>= xTemp; + if (xComp.get() == xTemp.get()) + { + m_xModelAsManager->attach( nPos, Reference<XInterface>( xControl, UNO_QUERY ), makeAny(xControl) ); + break; + } + } + } +} + + +void FormController::removeFromEventAttacher(const Reference< XControl > & xControl) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + OSL_ENSURE( xControl.is(), "FormController::removeFromEventAttacher: invalid control - how did you reach this?" ); + if ( !xControl.is() ) + return; /* throw IllegalArgumentException(); */ + + // register at the event attacher + Reference< XFormComponent > xComp(xControl->getModel(), UNO_QUERY); + if ( xComp.is() && m_xModelAsIndex.is() ) + { + // and look for the position of the ControlModel in it + sal_uInt32 nPos = m_xModelAsIndex->getCount(); + Reference< XFormComponent > xTemp; + for( ; nPos; ) + { + m_xModelAsIndex->getByIndex(--nPos) >>= xTemp; + if (xComp.get() == xTemp.get()) + { + m_xModelAsManager->detach( nPos, Reference<XInterface>( xControl, UNO_QUERY ) ); + break; + } + } + } +} + + +void FormController::setContainer(const Reference< XControlContainer > & xContainer) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + Reference< XTabControllerModel > xTabModel(getModel()); + DBG_ASSERT(xTabModel.is() || !xContainer.is(), "No Model defined"); + // if we have a new container we need a model + DBG_ASSERT(m_xTabController.is(), "FormController::setContainer : invalid aggregate !"); + + ::osl::MutexGuard aGuard( m_aMutex ); + Reference< XContainer > xCurrentContainer; + if (m_xTabController.is()) + xCurrentContainer.set(m_xTabController->getContainer(), UNO_QUERY); + if (xCurrentContainer.is()) + { + xCurrentContainer->removeContainerListener(this); + + if ( m_aTabActivationIdle.IsActive() ) + m_aTabActivationIdle.Stop(); + + // clear the filter map + ::std::for_each( m_aFilterComponents.begin(), m_aFilterComponents.end(), RemoveComponentTextListener( this ) ); + m_aFilterComponents.clear(); + + // collecting the controls + for ( const Reference< XControl >& rControl : std::as_const(m_aControls) ) + implControlRemoved( rControl, true ); + + // make database-specific things + if (m_bDBConnection && isListeningForChanges()) + stopListening(); + + m_aControls.realloc( 0 ); + } + + if (m_xTabController.is()) + m_xTabController->setContainer(xContainer); + + // What controls belong to the container? + if (xContainer.is() && xTabModel.is()) + { + const Sequence< Reference< XControlModel > > aModels = xTabModel->getControlModels(); + Sequence< Reference< XControl > > aAllControls = xContainer->getControls(); + + sal_Int32 nCount = aModels.getLength(); + m_aControls = Sequence< Reference< XControl > >( nCount ); + Reference< XControl > * pControls = m_aControls.getArray(); + + // collecting the controls + sal_Int32 j = 0; + for (const Reference< XControlModel >& rModel : aModels ) + { + Reference< XControl > xControl = findControl( aAllControls, rModel, false, true ); + if ( xControl.is() ) + { + pControls[j++] = xControl; + implControlInserted( xControl, true ); + } + } + + // not every model had an associated control + if (j != nCount) + m_aControls.realloc(j); + + // listen at the container + Reference< XContainer > xNewContainer(xContainer, UNO_QUERY); + if (xNewContainer.is()) + xNewContainer->addContainerListener(this); + + // make database-specific things + if (m_bDBConnection) + { + m_bLocked = determineLockState(); + setLocks(); + if (!isLocked()) + startListening(); + } + } + // the controls are in the right order + m_bControlsSorted = true; +} + + +Reference< XControlContainer > FormController::getContainer() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + DBG_ASSERT(m_xTabController.is(), "FormController::getContainer : invalid aggregate !"); + if (!m_xTabController.is()) + return Reference< XControlContainer > (); + return m_xTabController->getContainer(); +} + + +Sequence< Reference< XControl > > FormController::getControls() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + if (!m_bControlsSorted) + { + Reference< XTabControllerModel > xModel = getModel(); + if (!xModel.is()) + return m_aControls; + + const Sequence< Reference< XControlModel > > aControlModels = xModel->getControlModels(); + sal_Int32 nModels = aControlModels.getLength(); + + Sequence< Reference< XControl > > aNewControls(nModels); + + Reference< XControl > * pControls = aNewControls.getArray(); + Reference< XControl > xControl; + + // rearrange the controls according to the tab order sequence + sal_Int32 j = 0; + for ( const Reference< XControlModel >& rModel : aControlModels ) + { + xControl = findControl( m_aControls, rModel, true, true ); + if ( xControl.is() ) + pControls[j++] = xControl; + } + + // not every model had an associated control + if ( j != nModels ) + aNewControls.realloc( j ); + + m_aControls = aNewControls; + m_bControlsSorted = true; + } + return m_aControls; +} + + +void FormController::autoTabOrder() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + DBG_ASSERT(m_xTabController.is(), "FormController::autoTabOrder : invalid aggregate !"); + if (m_xTabController.is()) + m_xTabController->autoTabOrder(); +} + + +void FormController::activateTabOrder() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + DBG_ASSERT(m_xTabController.is(), "FormController::activateTabOrder : invalid aggregate !"); + if (m_xTabController.is()) + m_xTabController->activateTabOrder(); +} + + +void FormController::setControlLock(const Reference< XControl > & xControl) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + bool bLocked = isLocked(); + + // It is locked + // a. if the entire record is locked + // b. if the associated field is locked + Reference< XBoundControl > xBound(xControl, UNO_QUERY); + if (xBound.is() && + ( (bLocked && bLocked != bool(xBound->getLock())) || + !bLocked)) // always uncheck individual fields when unlocking + { + // there is a data source + Reference< XPropertySet > xSet(xControl->getModel(), UNO_QUERY); + if (xSet.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet)) + { + // what about the ReadOnly and Enable properties + bool bTouch = true; + if (::comphelper::hasProperty(FM_PROP_ENABLED, xSet)) + bTouch = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ENABLED)); + if (::comphelper::hasProperty(FM_PROP_READONLY, xSet)) + bTouch = !::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_READONLY)); + + if (bTouch) + { + Reference< XPropertySet > xField; + xSet->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField; + if (xField.is()) + { + if (bLocked) + xBound->setLock(bLocked); + else + { + try + { + Any aVal = xField->getPropertyValue(FM_PROP_ISREADONLY); + if (aVal.hasValue() && ::comphelper::getBOOL(aVal)) + xBound->setLock(true); + else + xBound->setLock(bLocked); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + } + } + } + } + } +} + + +void FormController::setLocks() +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + // lock/unlock all controls connected to a data source + for ( const Reference< XControl >& rControl : std::as_const(m_aControls) ) + setControlLock( rControl ); +} + + +namespace +{ + bool lcl_shouldListenForModifications( const Reference< XControl >& _rxControl, const Reference< XPropertyChangeListener >& _rxBoundFieldListener ) + { + bool bShould = false; + + Reference< XBoundComponent > xBound( _rxControl, UNO_QUERY ); + if ( xBound.is() ) + { + bShould = true; + } + else if ( _rxControl.is() ) + { + Reference< XPropertySet > xModelProps( _rxControl->getModel(), UNO_QUERY ); + if ( xModelProps.is() && ::comphelper::hasProperty( FM_PROP_BOUNDFIELD, xModelProps ) ) + { + Reference< XPropertySet > xField; + xModelProps->getPropertyValue( FM_PROP_BOUNDFIELD ) >>= xField; + bShould = xField.is(); + + if ( !bShould && _rxBoundFieldListener.is() ) + xModelProps->addPropertyChangeListener( FM_PROP_BOUNDFIELD, _rxBoundFieldListener ); + } + } + + return bShould; + } +} + + +void FormController::startControlModifyListening(const Reference< XControl > & xControl) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + + bool bModifyListening = lcl_shouldListenForModifications( xControl, this ); + + // artificial while + while ( bModifyListening ) + { + Reference< XModifyBroadcaster > xMod(xControl, UNO_QUERY); + if (xMod.is()) + { + xMod->addModifyListener(this); + break; + } + + // all the text to prematurely recognize a modified + Reference< XTextComponent > xText(xControl, UNO_QUERY); + if (xText.is()) + { + xText->addTextListener(this); + break; + } + + Reference< XCheckBox > xBox(xControl, UNO_QUERY); + if (xBox.is()) + { + xBox->addItemListener(this); + break; + } + + Reference< XComboBox > xCbBox(xControl, UNO_QUERY); + if (xCbBox.is()) + { + xCbBox->addItemListener(this); + break; + } + + Reference< XListBox > xListBox(xControl, UNO_QUERY); + if (xListBox.is()) + { + xListBox->addItemListener(this); + break; + } + break; + } +} + + +void FormController::stopControlModifyListening(const Reference< XControl > & xControl) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + + bool bModifyListening = lcl_shouldListenForModifications( xControl, nullptr ); + + // artificial while + while (bModifyListening) + { + Reference< XModifyBroadcaster > xMod(xControl, UNO_QUERY); + if (xMod.is()) + { + xMod->removeModifyListener(this); + break; + } + // all the text to prematurely recognize a modified + Reference< XTextComponent > xText(xControl, UNO_QUERY); + if (xText.is()) + { + xText->removeTextListener(this); + break; + } + + Reference< XCheckBox > xBox(xControl, UNO_QUERY); + if (xBox.is()) + { + xBox->removeItemListener(this); + break; + } + + Reference< XComboBox > xCbBox(xControl, UNO_QUERY); + if (xCbBox.is()) + { + xCbBox->removeItemListener(this); + break; + } + + Reference< XListBox > xListBox(xControl, UNO_QUERY); + if (xListBox.is()) + { + xListBox->removeItemListener(this); + break; + } + break; + } +} + + +void FormController::startListening() +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + m_bModified = false; + + // now register at bound fields + for ( const Reference< XControl >& rControl : std::as_const(m_aControls) ) + startControlModifyListening( rControl ); +} + + +void FormController::stopListening() +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + m_bModified = false; + + // now register at bound fields + for ( const Reference< XControl >& rControl : std::as_const(m_aControls) ) + stopControlModifyListening( rControl ); +} + + +Reference< XControl > FormController::findControl(Sequence< Reference< XControl > >& _rControls, const Reference< XControlModel > & xCtrlModel ,bool _bRemove,bool _bOverWrite) const +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + DBG_ASSERT( xCtrlModel.is(), "findControl - which ?!" ); + + Reference< XControl >* pControls = std::find_if(_rControls.begin(), _rControls.end(), + [&xCtrlModel](const Reference< XControl >& rControl) { + return rControl.is() && rControl->getModel().get() == xCtrlModel.get(); }); + if (pControls != _rControls.end()) + { + Reference< XControl > xControl( *pControls ); + if ( _bRemove ) + { + auto i = static_cast<sal_Int32>(std::distance(_rControls.begin(), pControls)); + ::comphelper::removeElementAt( _rControls, i ); + } + else if ( _bOverWrite ) + pControls->clear(); + return xControl; + } + return Reference< XControl > (); +} + + +void FormController::implControlInserted( const Reference< XControl>& _rxControl, bool _bAddToEventAttacher ) +{ + Reference< XWindow > xWindow( _rxControl, UNO_QUERY ); + if ( xWindow.is() ) + { + xWindow->addFocusListener( this ); + xWindow->addMouseListener( this ); + + if ( _bAddToEventAttacher ) + addToEventAttacher( _rxControl ); + } + + // add a dispatch interceptor to the control (if supported) + Reference< XDispatchProviderInterception > xInterception( _rxControl, UNO_QUERY ); + if ( xInterception.is() ) + createInterceptor( xInterception ); + + if ( _rxControl.is() ) + { + Reference< XControlModel > xModel( _rxControl->getModel() ); + + // we want to know about the reset of the model of our controls + // (for correctly resetting m_bModified) + Reference< XReset > xReset( xModel, UNO_QUERY ); + if ( xReset.is() ) + xReset->addResetListener( this ); + + // and we want to know about the validity, to visually indicate it + Reference< XValidatableFormComponent > xValidatable( xModel, UNO_QUERY ); + if ( xValidatable.is() ) + { + xValidatable->addFormComponentValidityListener( this ); + m_aControlBorderManager.validityChanged( _rxControl, xValidatable ); + } + } + +} + + +void FormController::implControlRemoved( const Reference< XControl>& _rxControl, bool _bRemoveFromEventAttacher ) +{ + Reference< XWindow > xWindow( _rxControl, UNO_QUERY ); + if ( xWindow.is() ) + { + xWindow->removeFocusListener( this ); + xWindow->removeMouseListener( this ); + + if ( _bRemoveFromEventAttacher ) + removeFromEventAttacher( _rxControl ); + } + + Reference< XDispatchProviderInterception > xInterception( _rxControl, UNO_QUERY); + if ( xInterception.is() ) + deleteInterceptor( xInterception ); + + if ( _rxControl.is() ) + { + Reference< XControlModel > xModel( _rxControl->getModel() ); + + Reference< XReset > xReset( xModel, UNO_QUERY ); + if ( xReset.is() ) + xReset->removeResetListener( this ); + + Reference< XValidatableFormComponent > xValidatable( xModel, UNO_QUERY ); + if ( xValidatable.is() ) + xValidatable->removeFormComponentValidityListener( this ); + } +} + + +void FormController::implSetCurrentControl( const Reference< XControl >& _rxControl ) +{ + if ( m_xCurrentControl.get() == _rxControl.get() ) + return; + + Reference< XGridControl > xGridControl( m_xCurrentControl, UNO_QUERY ); + if ( xGridControl.is() ) + xGridControl->removeGridControlListener( this ); + + m_xCurrentControl = _rxControl; + + xGridControl.set( m_xCurrentControl, UNO_QUERY ); + if ( xGridControl.is() ) + xGridControl->addGridControlListener( this ); +} + + +void FormController::insertControl(const Reference< XControl > & xControl) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + m_bControlsSorted = false; + m_aControls.realloc(m_aControls.getLength() + 1); + m_aControls.getArray()[m_aControls.getLength() - 1] = xControl; + + if (m_pColumnInfoCache) + m_pColumnInfoCache->deinitializeControls(); + + implControlInserted( xControl, m_bAttachEvents ); + + if (m_bDBConnection && !m_bFiltering) + setControlLock(xControl); + + if (isListeningForChanges() && m_bAttachEvents) + startControlModifyListening( xControl ); +} + + +void FormController::removeControl(const Reference< XControl > & xControl) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + auto pControl = std::find_if(m_aControls.begin(), m_aControls.end(), + [&xControl](const Reference< XControl >& rControl) { return xControl.get() == rControl.get(); }); + if (pControl != m_aControls.end()) + { + auto nIndex = static_cast<sal_Int32>(std::distance(m_aControls.begin(), pControl)); + ::comphelper::removeElementAt( m_aControls, nIndex ); + } + + FilterComponents::iterator componentPos = ::std::find( m_aFilterComponents.begin(), m_aFilterComponents.end(), xControl ); + if ( componentPos != m_aFilterComponents.end() ) + m_aFilterComponents.erase( componentPos ); + + implControlRemoved( xControl, m_bDetachEvents ); + + if ( isListeningForChanges() && m_bDetachEvents ) + stopControlModifyListening( xControl ); +} + +// XLoadListener + +void FormController::loaded(const EventObject& rEvent) +{ + OSL_ENSURE( rEvent.Source == m_xModelAsIndex, "FormController::loaded: where did this come from?" ); + + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + ::osl::MutexGuard aGuard( m_aMutex ); + Reference< XRowSet > xForm(rEvent.Source, UNO_QUERY); + // do we have a connected data source + if (xForm.is() && getConnection(xForm).is()) + { + Reference< XPropertySet > xSet(xForm, UNO_QUERY); + if (xSet.is()) + { + Any aVal = xSet->getPropertyValue(FM_PROP_CYCLE); + sal_Int32 aVal2 = 0; + ::cppu::enum2int(aVal2,aVal); + m_bCycle = !aVal.hasValue() || static_cast<form::TabulatorCycle>(aVal2) == TabulatorCycle_RECORDS; + m_bCanUpdate = canUpdate(xSet); + m_bCanInsert = canInsert(xSet); + m_bCurrentRecordModified = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISMODIFIED)); + m_bCurrentRecordNew = ::comphelper::getBOOL(xSet->getPropertyValue(FM_PROP_ISNEW)); + + startFormListening( xSet, false ); + + // set the locks for the current controls + if (getContainer().is()) + { + m_aLoadEvent.Call(); + } + } + else + { + m_bCanInsert = m_bCanUpdate = m_bCycle = false; + m_bCurrentRecordModified = false; + m_bCurrentRecordNew = false; + m_bLocked = false; + } + m_bDBConnection = true; + } + else + { + m_bDBConnection = false; + m_bCanInsert = m_bCanUpdate = m_bCycle = false; + m_bCurrentRecordModified = false; + m_bCurrentRecordNew = false; + m_bLocked = false; + } + + Reference< XColumnsSupplier > xFormColumns( xForm, UNO_QUERY ); + m_pColumnInfoCache.reset( xFormColumns.is() ? new ColumnInfoCache( xFormColumns ) : nullptr ); + + updateAllDispatchers(); +} + + +void FormController::updateAllDispatchers() const +{ + ::std::for_each( + m_aFeatureDispatchers.begin(), + m_aFeatureDispatchers.end(), + [] (const DispatcherContainer::value_type& dispatcher) { + UpdateAllListeners()(dispatcher.second); + }); +} + + +IMPL_LINK_NOARG(FormController, OnLoad, void*, void) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + m_bLocked = determineLockState(); + + setLocks(); + + if (!m_bLocked) + startListening(); + + // just one exception toggle the auto values + if (m_bCurrentRecordNew) + toggleAutoFields(true); +} + + +void FormController::unloaded(const EventObject& /*rEvent*/) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + updateAllDispatchers(); +} + + +void FormController::reloading(const EventObject& /*aEvent*/) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + // do the same like in unloading + // just one exception toggle the auto values + m_aToggleEvent.CancelPendingCall(); + unload(); +} + + +void FormController::reloaded(const EventObject& aEvent) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + loaded(aEvent); +} + + +void FormController::unloading(const EventObject& /*aEvent*/) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + unload(); +} + + +void FormController::unload() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + m_aLoadEvent.CancelPendingCall(); + + // be sure not to have autofields + if (m_bCurrentRecordNew) + toggleAutoFields(false); + + // remove bound field listing again + removeBoundFieldListener(); + + if (m_bDBConnection && isListeningForChanges()) + stopListening(); + + Reference< XPropertySet > xSet( m_xModelAsIndex, UNO_QUERY ); + if ( m_bDBConnection && xSet.is() ) + stopFormListening( xSet, false ); + + m_bDBConnection = false; + m_bCanInsert = m_bCanUpdate = m_bCycle = false; + m_bCurrentRecordModified = m_bCurrentRecordNew = m_bLocked = false; + + m_pColumnInfoCache.reset(); +} + + +void FormController::removeBoundFieldListener() +{ + for ( const Reference< XControl >& rControl : std::as_const(m_aControls) ) + { + Reference< XPropertySet > xProp( rControl, UNO_QUERY ); + if ( xProp.is() ) + xProp->removePropertyChangeListener( FM_PROP_BOUNDFIELD, this ); + } +} + + +void FormController::startFormListening( const Reference< XPropertySet >& _rxForm, bool _bPropertiesOnly ) +{ + try + { + if ( m_bCanInsert || m_bCanUpdate ) // form can be modified + { + _rxForm->addPropertyChangeListener( FM_PROP_ISNEW, this ); + _rxForm->addPropertyChangeListener( FM_PROP_ISMODIFIED, this ); + + if ( !_bPropertiesOnly ) + { + // set the Listener for UI interaction + Reference< XRowSetApproveBroadcaster > xApprove( _rxForm, UNO_QUERY ); + if ( xApprove.is() ) + xApprove->addRowSetApproveListener( this ); + + // listener for row set changes + Reference< XRowSet > xRowSet( _rxForm, UNO_QUERY ); + if ( xRowSet.is() ) + xRowSet->addRowSetListener( this ); + } + } + + Reference< XPropertySetInfo > xInfo = _rxForm->getPropertySetInfo(); + if ( xInfo.is() && xInfo->hasPropertyByName( FM_PROP_DYNAMIC_CONTROL_BORDER ) ) + _rxForm->addPropertyChangeListener( FM_PROP_DYNAMIC_CONTROL_BORDER, this ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + + +void FormController::stopFormListening( const Reference< XPropertySet >& _rxForm, bool _bPropertiesOnly ) +{ + try + { + if ( m_bCanInsert || m_bCanUpdate ) + { + _rxForm->removePropertyChangeListener( FM_PROP_ISNEW, this ); + _rxForm->removePropertyChangeListener( FM_PROP_ISMODIFIED, this ); + + if ( !_bPropertiesOnly ) + { + Reference< XRowSetApproveBroadcaster > xApprove( _rxForm, UNO_QUERY ); + if (xApprove.is()) + xApprove->removeRowSetApproveListener(this); + + Reference< XRowSet > xRowSet( _rxForm, UNO_QUERY ); + if ( xRowSet.is() ) + xRowSet->removeRowSetListener( this ); + } + } + + Reference< XPropertySetInfo > xInfo = _rxForm->getPropertySetInfo(); + if ( xInfo.is() && xInfo->hasPropertyByName( FM_PROP_DYNAMIC_CONTROL_BORDER ) ) + _rxForm->removePropertyChangeListener( FM_PROP_DYNAMIC_CONTROL_BORDER, this ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } +} + +// css::sdbc::XRowSetListener + +void FormController::cursorMoved(const EventObject& /*event*/) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + // toggle the locking ? + if (m_bLocked != determineLockState()) + { + m_bLocked = !m_bLocked; + setLocks(); + if (isListeningForChanges()) + startListening(); + else + stopListening(); + } + + // neither the current control nor the current record are modified anymore + m_bCurrentRecordModified = m_bModified = false; +} + + +void FormController::rowChanged(const EventObject& /*event*/) +{ + // not interested in ... +} + +void FormController::rowSetChanged(const EventObject& /*event*/) +{ + // not interested in ... +} + + +// XContainerListener + +void SAL_CALL FormController::elementInserted(const ContainerEvent& evt) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + Reference< XControl > xControl( evt.Element, UNO_QUERY ); + if ( !xControl.is() ) + return; + + Reference< XFormComponent > xModel(xControl->getModel(), UNO_QUERY); + if (xModel.is() && m_xModelAsIndex == xModel->getParent()) + { + insertControl(xControl); + + if ( m_aTabActivationIdle.IsActive() ) + m_aTabActivationIdle.Stop(); + + m_aTabActivationIdle.Start(); + } + // are we in filtermode and a XModeSelector has inserted an element + else if (m_bFiltering && Reference< XModeSelector > (evt.Source, UNO_QUERY).is()) + { + xModel.set(evt.Source, UNO_QUERY); + if (xModel.is() && m_xModelAsIndex == xModel->getParent()) + { + Reference< XPropertySet > xSet(xControl->getModel(), UNO_QUERY); + if (xSet.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet)) + { + // does the model use a bound field ? + Reference< XPropertySet > xField; + xSet->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField; + + Reference< XTextComponent > xText(xControl, UNO_QUERY); + // may we filter the field? + if (xText.is() && xField.is() && ::comphelper::hasProperty(FM_PROP_SEARCHABLE, xField) && + ::comphelper::getBOOL(xField->getPropertyValue(FM_PROP_SEARCHABLE))) + { + m_aFilterComponents.push_back( xText ); + xText->addTextListener( this ); + } + } + } + } +} + + +void SAL_CALL FormController::elementReplaced(const ContainerEvent& evt) +{ + // simulate an elementRemoved + ContainerEvent aRemoveEvent( evt ); + aRemoveEvent.Element = evt.ReplacedElement; + aRemoveEvent.ReplacedElement = Any(); + elementRemoved( aRemoveEvent ); + + // simulate an elementInserted + ContainerEvent aInsertEvent( evt ); + aInsertEvent.ReplacedElement = Any(); + elementInserted( aInsertEvent ); +} + + +void SAL_CALL FormController::elementRemoved(const ContainerEvent& evt) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + Reference< XControl > xControl; + evt.Element >>= xControl; + if (!xControl.is()) + return; + + Reference< XFormComponent > xModel(xControl->getModel(), UNO_QUERY); + if (xModel.is() && m_xModelAsIndex == xModel->getParent()) + { + removeControl(xControl); + // Do not recalculate TabOrder, because it must already work internally! + } + // are we in filtermode and a XModeSelector has inserted an element + else if (m_bFiltering && Reference< XModeSelector > (evt.Source, UNO_QUERY).is()) + { + FilterComponents::iterator componentPos = ::std::find( + m_aFilterComponents.begin(), m_aFilterComponents.end(), xControl ); + if ( componentPos != m_aFilterComponents.end() ) + m_aFilterComponents.erase( componentPos ); + } +} + + +Reference< XControl > FormController::isInList(const Reference< XWindowPeer > & xPeer) const +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + const Reference< XControl >* pControls = m_aControls.getConstArray(); + + sal_uInt32 nCtrls = m_aControls.getLength(); + for ( sal_uInt32 n = 0; n < nCtrls && xPeer.is(); ++n, ++pControls ) + { + if ( pControls->is() ) + { + Reference< XVclWindowPeer > xCtrlPeer( (*pControls)->getPeer(), UNO_QUERY); + if ( ( xCtrlPeer.get() == xPeer.get() ) || xCtrlPeer->isChild( xPeer ) ) + return *pControls; + } + } + return Reference< XControl > (); +} + + +void FormController::activateFirst() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + DBG_ASSERT(m_xTabController.is(), "FormController::activateFirst : invalid aggregate !"); + if (m_xTabController.is()) + m_xTabController->activateFirst(); +} + + +void FormController::activateLast() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + DBG_ASSERT(m_xTabController.is(), "FormController::activateLast : invalid aggregate !"); + if (m_xTabController.is()) + m_xTabController->activateLast(); +} + +// XFormController + +Reference< XFormOperations > SAL_CALL FormController::getFormOperations() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + return m_xFormOperations; +} + + +Reference< XControl> SAL_CALL FormController::getCurrentControl() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + return m_xCurrentControl; +} + + +void SAL_CALL FormController::addActivateListener(const Reference< XFormControllerListener > & l) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + m_aActivateListeners.addInterface(l); +} + +void SAL_CALL FormController::removeActivateListener(const Reference< XFormControllerListener > & l) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + m_aActivateListeners.removeInterface(l); +} + + +void SAL_CALL FormController::addChildController( const Reference< XFormController >& ChildController ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + if ( !ChildController.is() ) + throw IllegalArgumentException( OUString(), *this, 1 ); + // TODO: (localized) error message + + // the parent of our (to-be-)child must be our own model + Reference< XFormComponent > xFormOfChild( ChildController->getModel(), UNO_QUERY ); + if ( !xFormOfChild.is() ) + throw IllegalArgumentException( OUString(), *this, 1 ); + // TODO: (localized) error message + + if ( xFormOfChild->getParent() != m_xModelAsIndex ) + throw IllegalArgumentException( OUString(), *this, 1 ); + // TODO: (localized) error message + + m_aChildren.push_back( ChildController ); + ChildController->setParent( *this ); + + // search the position of the model within the form + sal_uInt32 nPos = m_xModelAsIndex->getCount(); + Reference< XFormComponent > xTemp; + for( ; nPos; ) + { + m_xModelAsIndex->getByIndex(--nPos) >>= xTemp; + if ( xFormOfChild == xTemp ) + { + m_xModelAsManager->attach( nPos, Reference<XInterface>( ChildController, UNO_QUERY ), makeAny( ChildController) ); + break; + } + } +} + + +Reference< XFormControllerContext > SAL_CALL FormController::getContext() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + return m_xFormControllerContext; +} + + +void SAL_CALL FormController::setContext( const Reference< XFormControllerContext >& _context ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + m_xFormControllerContext = _context; +} + + +Reference< XInteractionHandler > SAL_CALL FormController::getInteractionHandler() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + return m_xInteractionHandler; +} + + +void SAL_CALL FormController::setInteractionHandler( const Reference< XInteractionHandler >& _interactionHandler ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + m_xInteractionHandler = _interactionHandler; +} + + +void FormController::setFilter(::std::vector<FmFieldInfo>& rFieldInfos) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + // create the composer + Reference< XRowSet > xForm(m_xModelAsIndex, UNO_QUERY); + Reference< XConnection > xConnection(getConnection(xForm)); + if (xForm.is()) + { + try + { + Reference< XMultiServiceFactory > xFactory( xConnection, UNO_QUERY_THROW ); + m_xComposer.set( + xFactory->createInstance("com.sun.star.sdb.SingleSelectQueryComposer"), + UNO_QUERY_THROW ); + + Reference< XPropertySet > xSet( xForm, UNO_QUERY ); + OUString sStatement = ::comphelper::getString( xSet->getPropertyValue( FM_PROP_ACTIVECOMMAND ) ); + OUString sFilter = ::comphelper::getString( xSet->getPropertyValue( FM_PROP_FILTER ) ); + m_xComposer->setElementaryQuery( sStatement ); + m_xComposer->setFilter( sFilter ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + if (m_xComposer.is()) + { + const Sequence< Sequence < PropertyValue > > aFilterRows = m_xComposer->getStructuredFilter(); + + // ok, we receive the list of filters as sequence of fieldnames, value + // now we have to transform the fieldname into UI names, that could be a label of the field or + // an aliasname or the fieldname itself + + // first adjust the field names if necessary + Reference< XNameAccess > xQueryColumns = + Reference< XColumnsSupplier >( m_xComposer, UNO_QUERY_THROW )->getColumns(); + + for (auto& rFieldInfo : rFieldInfos) + { + if ( xQueryColumns->hasByName(rFieldInfo.aFieldName) ) + { + if ( (xQueryColumns->getByName(rFieldInfo.aFieldName) >>= rFieldInfo.xField) && rFieldInfo.xField.is() ) + rFieldInfo.xField->getPropertyValue(FM_PROP_REALNAME) >>= rFieldInfo.aFieldName; + } + } + + Reference< XDatabaseMetaData> xMetaData(xConnection->getMetaData()); + // now transfer the filters into Value/TextComponent pairs + ::comphelper::UStringMixEqual aCompare(xMetaData->storesMixedCaseQuotedIdentifiers()); + + // need to parse criteria localized + Reference< XNumberFormatsSupplier> xFormatSupplier( getNumberFormats(xConnection, true)); + Reference< XNumberFormatter> xFormatter = NumberFormatter::create(m_xComponentContext); + xFormatter->attachNumberFormatsSupplier(xFormatSupplier); + Locale aAppLocale = Application::GetSettings().GetUILanguageTag().getLocale(); + const LocaleDataWrapper& rLocaleWrapper( Application::GetSettings().GetUILocaleDataWrapper() ); + OUString strDecimalSeparator = rLocaleWrapper.getNumDecimalSep(); + + // retrieving the filter + for (const Sequence < PropertyValue >& rRow : aFilterRows) + { + FmFilterRow aRow; + + // search a field for the given name + for (const PropertyValue& rRefValue : rRow) + { + // look for the text component + Reference< XPropertySet > xField; + try + { + Reference< XPropertySet > xSet; + OUString aRealName; + + // first look with the given name + if (xQueryColumns->hasByName(rRefValue.Name)) + { + xQueryColumns->getByName(rRefValue.Name) >>= xSet; + + // get the RealName + xSet->getPropertyValue("RealName") >>= aRealName; + + // compare the condition field name and the RealName + if (aCompare(aRealName, rRefValue.Name)) + xField = xSet; + } + if (!xField.is()) + { + // no we have to check every column to find the realname + Reference< XIndexAccess > xColumnsByIndex(xQueryColumns, UNO_QUERY); + for (sal_Int32 n = 0, nCount = xColumnsByIndex->getCount(); n < nCount; n++) + { + xColumnsByIndex->getByIndex(n) >>= xSet; + xSet->getPropertyValue("RealName") >>= aRealName; + if (aCompare(aRealName, rRefValue.Name)) + { + // get the column by its alias + xField = xSet; + break; + } + } + } + if (!xField.is()) + continue; + } + catch (const Exception&) + { + continue; + } + + // find the text component + for (const auto& rFieldInfo : rFieldInfos) + { + // we found the field so insert a new entry to the filter row + if (rFieldInfo.xField == xField) + { + // do we already have the control ? + if (aRow.find(rFieldInfo.xText) != aRow.end()) + { + OString aVal = m_pParser->getContext().getIntlKeywordAscii(IParseContext::InternationalKeyCode::And); + OUString aCompText = aRow[rFieldInfo.xText] + " " + + OUString(aVal.getStr(),aVal.getLength(),RTL_TEXTENCODING_ASCII_US) + " " + + ::comphelper::getString(rRefValue.Value); + aRow[rFieldInfo.xText] = aCompText; + } + else + { + OUString sPredicate,sErrorMsg; + rRefValue.Value >>= sPredicate; + std::unique_ptr< OSQLParseNode > pParseNode = predicateTree(sErrorMsg, sPredicate, xFormatter, xField); + if ( pParseNode != nullptr ) + { + OUString sCriteria; + switch (rRefValue.Handle) + { + case css::sdb::SQLFilterOperator::EQUAL: + sCriteria += "="; + break; + case css::sdb::SQLFilterOperator::NOT_EQUAL: + sCriteria += "!="; + break; + case css::sdb::SQLFilterOperator::LESS: + sCriteria += "<"; + break; + case css::sdb::SQLFilterOperator::GREATER: + sCriteria += ">"; + break; + case css::sdb::SQLFilterOperator::LESS_EQUAL: + sCriteria += "<="; + break; + case css::sdb::SQLFilterOperator::GREATER_EQUAL: + sCriteria += ">="; + break; + case css::sdb::SQLFilterOperator::LIKE: + sCriteria += "LIKE "; + break; + case css::sdb::SQLFilterOperator::NOT_LIKE: + sCriteria += "NOT LIKE "; + break; + case css::sdb::SQLFilterOperator::SQLNULL: + sCriteria += "IS NULL"; + break; + case css::sdb::SQLFilterOperator::NOT_SQLNULL: + sCriteria += "IS NOT NULL"; + break; + } + pParseNode->parseNodeToPredicateStr( sCriteria + ,xConnection + ,xFormatter + ,xField + ,OUString() + ,aAppLocale + ,strDecimalSeparator + ,getParseContext()); + aRow[rFieldInfo.xText] = sCriteria; + } + } + } + } + } + + if (aRow.empty()) + continue; + + impl_addFilterRow( aRow ); + } + } + + // now set the filter controls + for (const auto& rFieldInfo : rFieldInfos) + { + m_aFilterComponents.push_back( rFieldInfo.xText ); + } +} + + +void FormController::startFiltering() +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + + Reference< XConnection > xConnection( getConnection( Reference< XRowSet >( m_xModelAsIndex, UNO_QUERY ) ) ); + if ( !xConnection.is() ) + // nothing to do - can't filter a form which is not connected + return; + + // stop listening for controls + if (isListeningForChanges()) + stopListening(); + + m_bFiltering = true; + + // as we don't want new controls to be attached to the scripting environment + // we change attach flags + m_bAttachEvents = false; + + // exchanging the controls for the current form + Sequence< Reference< XControl > > aControlsCopy( m_aControls ); + const Reference< XControl >* pControls = aControlsCopy.getConstArray(); + sal_Int32 nControlCount = aControlsCopy.getLength(); + + // the control we have to activate after replacement + Reference< XNumberFormatsSupplier > xFormatSupplier = getNumberFormats(xConnection, true); + Reference< XNumberFormatter > xFormatter = NumberFormatter::create(m_xComponentContext); + xFormatter->attachNumberFormatsSupplier(xFormatSupplier); + + // structure for storing the field info + ::std::vector<FmFieldInfo> aFieldInfos; + + for (sal_Int32 i = nControlCount; i > 0;) + { + Reference< XControl > xControl = pControls[--i]; + if (xControl.is()) + { + // no events for the control anymore + removeFromEventAttacher(xControl); + + // do we have a mode selector + Reference< XModeSelector > xSelector(xControl, UNO_QUERY); + if (xSelector.is()) + { + xSelector->setMode( "FilterMode" ); + + // listening for new controls of the selector + Reference< XContainer > xContainer(xSelector, UNO_QUERY); + if (xContainer.is()) + xContainer->addContainerListener(this); + + Reference< XEnumerationAccess > xElementAccess(xSelector, UNO_QUERY); + if (xElementAccess.is()) + { + Reference< XEnumeration > xEnumeration(xElementAccess->createEnumeration()); + Reference< XControl > xSubControl; + while (xEnumeration->hasMoreElements()) + { + xEnumeration->nextElement() >>= xSubControl; + if (xSubControl.is()) + { + Reference< XPropertySet > xSet(xSubControl->getModel(), UNO_QUERY); + if (xSet.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet)) + { + // does the model use a bound field ? + Reference< XPropertySet > xField; + xSet->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField; + + Reference< XTextComponent > xText(xSubControl, UNO_QUERY); + // may we filter the field? + if (xText.is() && xField.is() && ::comphelper::hasProperty(FM_PROP_SEARCHABLE, xField) && + ::comphelper::getBOOL(xField->getPropertyValue(FM_PROP_SEARCHABLE))) + { + aFieldInfos.emplace_back(xField, xText); + xText->addTextListener(this); + } + } + } + } + } + continue; + } + + Reference< XPropertySet > xModel( xControl->getModel(), UNO_QUERY ); + if (xModel.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xModel)) + { + // does the model use a bound field ? + Any aVal = xModel->getPropertyValue(FM_PROP_BOUNDFIELD); + Reference< XPropertySet > xField; + aVal >>= xField; + + // may we filter the field? + + if ( xField.is() + && ::comphelper::hasProperty( FM_PROP_SEARCHABLE, xField ) + && ::comphelper::getBOOL( xField->getPropertyValue( FM_PROP_SEARCHABLE ) ) + ) + { + // create a filter control + Reference< XControl > xFilterControl = form::control::FilterControl::createWithFormat( + m_xComponentContext, + VCLUnoHelper::GetInterface( getDialogParentWindow() ), + xFormatter, + xModel); + + if ( replaceControl( xControl, xFilterControl ) ) + { + Reference< XTextComponent > xFilterText( xFilterControl, UNO_QUERY ); + aFieldInfos.emplace_back( xField, xFilterText ); + xFilterText->addTextListener(this); + } + } + } + else + { + // unsubscribe from EventManager + } + } + } + + // we have all filter controls now, so the next step is to read the filters from the form + // resolve all aliases and set the current filter to the according structure + setFilter(aFieldInfos); + + Reference< XPropertySet > xSet( m_xModelAsIndex, UNO_QUERY ); + if ( xSet.is() ) + stopFormListening( xSet, true ); + + impl_setTextOnAllFilter_throw(); + + // lock all controls which are not used for filtering + m_bLocked = determineLockState(); + setLocks(); + m_bAttachEvents = true; +} + + +void FormController::stopFiltering() +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + if ( !m_bFiltering ) // #104693# OJ + { // nothing to do + return; + } + + m_bFiltering = false; + m_bDetachEvents = false; + + ::comphelper::disposeComponent(m_xComposer); + + // exchanging the controls for the current form + Sequence< Reference< XControl > > aControlsCopy( m_aControls ); + const Reference< XControl > * pControls = aControlsCopy.getConstArray(); + sal_Int32 nControlCount = aControlsCopy.getLength(); + + // clear the filter control map + ::std::for_each( m_aFilterComponents.begin(), m_aFilterComponents.end(), RemoveComponentTextListener( this ) ); + m_aFilterComponents.clear(); + + for ( sal_Int32 i = nControlCount; i > 0; ) + { + Reference< XControl > xControl = pControls[--i]; + if (xControl.is()) + { + // now enable event handling again + addToEventAttacher(xControl); + + Reference< XModeSelector > xSelector(xControl, UNO_QUERY); + if (xSelector.is()) + { + xSelector->setMode( "DataMode" ); + + // listening for new controls of the selector + Reference< XContainer > xContainer(xSelector, UNO_QUERY); + if (xContainer.is()) + xContainer->removeContainerListener(this); + continue; + } + + Reference< XPropertySet > xSet(xControl->getModel(), UNO_QUERY); + if (xSet.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet)) + { + // does the model use a bound field ? + Reference< XPropertySet > xField; + xSet->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField; + + // may we filter the field? + if ( xField.is() + && ::comphelper::hasProperty( FM_PROP_SEARCHABLE, xField ) + && ::comphelper::getBOOL( xField->getPropertyValue( FM_PROP_SEARCHABLE ) ) + ) + { + OUString sServiceName; + OSL_VERIFY( xSet->getPropertyValue( FM_PROP_DEFAULTCONTROL ) >>= sServiceName ); + Reference< XControl > xNewControl( m_xComponentContext->getServiceManager()->createInstanceWithContext( sServiceName, m_xComponentContext ), UNO_QUERY ); + replaceControl( xControl, xNewControl ); + } + } + } + } + + Reference< XPropertySet > xSet( m_xModelAsIndex, UNO_QUERY ); + if ( xSet.is() ) + startFormListening( xSet, true ); + + m_bDetachEvents = true; + + m_aFilterRows.clear(); + m_nCurrentFilterPosition = -1; + + // release the locks if possible + // lock all controls which are not used for filtering + m_bLocked = determineLockState(); + setLocks(); + + // restart listening for control modifications + if (isListeningForChanges()) + startListening(); +} + +// XModeSelector + +void FormController::setMode(const OUString& Mode) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + if (!supportsMode(Mode)) + throw NoSupportException(); + + if (Mode == m_aMode) + return; + + m_aMode = Mode; + + if ( Mode == "FilterMode" ) + startFiltering(); + else + stopFiltering(); + + for (const auto& rChild : m_aChildren) + { + Reference< XModeSelector > xMode(rChild, UNO_QUERY); + if ( xMode.is() ) + xMode->setMode(Mode); + } +} + + +OUString SAL_CALL FormController::getMode() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + return m_aMode; +} + + +Sequence< OUString > SAL_CALL FormController::getSupportedModes() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + static Sequence< OUString > const aModes + { + "DataMode", + "FilterMode" + }; + return aModes; +} + + +sal_Bool SAL_CALL FormController::supportsMode(const OUString& Mode) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + Sequence< OUString > aModes(getSupportedModes()); + return comphelper::findValue(aModes, Mode) != -1; +} + + +vcl::Window* FormController::getDialogParentWindow() +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + vcl::Window* pParentWindow = nullptr; + try + { + Reference< XControl > xContainerControl( getContainer(), UNO_QUERY_THROW ); + Reference< XWindowPeer > xContainerPeer( xContainerControl->getPeer(), UNO_SET_THROW ); + pParentWindow = VCLUnoHelper::GetWindow( xContainerPeer ).get(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return pParentWindow; +} + +bool FormController::checkFormComponentValidity( OUString& /* [out] */ _rFirstInvalidityExplanation, Reference< XControlModel >& /* [out] */ _rxFirstInvalidModel ) +{ + try + { + Reference< XEnumerationAccess > xControlEnumAcc( getModel(), UNO_QUERY ); + Reference< XEnumeration > xControlEnumeration; + if ( xControlEnumAcc.is() ) + xControlEnumeration = xControlEnumAcc->createEnumeration(); + OSL_ENSURE( xControlEnumeration.is(), "FormController::checkFormComponentValidity: cannot enumerate the controls!" ); + if ( !xControlEnumeration.is() ) + // assume all valid + return true; + + Reference< XValidatableFormComponent > xValidatable; + while ( xControlEnumeration->hasMoreElements() ) + { + if ( !( xControlEnumeration->nextElement() >>= xValidatable ) ) + // control does not support validation + continue; + + if ( xValidatable->isValid() ) + continue; + + Reference< XValidator > xValidator( xValidatable->getValidator() ); + OSL_ENSURE( xValidator.is(), "FormController::checkFormComponentValidity: invalid, but no validator?" ); + if ( !xValidator.is() ) + // this violates the interface definition of css.form.validation.XValidatableFormComponent ... + continue; + + _rFirstInvalidityExplanation = xValidator->explainInvalid( xValidatable->getCurrentValue() ); + _rxFirstInvalidModel.set(xValidatable, css::uno::UNO_QUERY); + return false; + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return true; +} + + +Reference< XControl > FormController::locateControl( const Reference< XControlModel >& _rxModel ) +{ + try + { + const Sequence< Reference< XControl > > aControls( getControls() ); + + for ( auto const & control : aControls ) + { + OSL_ENSURE( control.is(), "FormController::locateControl: NULL-control?" ); + if ( control.is() ) + { + if ( control->getModel() == _rxModel ) + return control; + } + } + OSL_FAIL( "FormController::locateControl: did not find a control for this model!" ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return nullptr; +} + + +namespace +{ + void displayErrorSetFocus( const OUString& _rMessage, const Reference< XControl >& _rxFocusControl, vcl::Window* _pDialogParent ) + { + SQLContext aError; + aError.Message = SvxResId(RID_STR_WRITEERROR); + aError.Details = _rMessage; + displayException( aError, _pDialogParent ); + + if ( _rxFocusControl.is() ) + { + Reference< XWindow > xControlWindow( _rxFocusControl, UNO_QUERY ); + OSL_ENSURE( xControlWindow.is(), "displayErrorSetFocus: invalid control!" ); + if ( xControlWindow.is() ) + xControlWindow->setFocus(); + } + } + + bool lcl_shouldValidateRequiredFields_nothrow( const Reference< XInterface >& _rxForm ) + { + try + { + static const char s_sFormsCheckRequiredFields[] = "FormsCheckRequiredFields"; + + // first, check whether the form has a property telling us the answer + // this allows people to use the XPropertyContainer interface of a form to control + // the behaviour on a per-form basis. + Reference< XPropertySet > xFormProps( _rxForm, UNO_QUERY_THROW ); + Reference< XPropertySetInfo > xPSI( xFormProps->getPropertySetInfo() ); + if ( xPSI->hasPropertyByName( s_sFormsCheckRequiredFields ) ) + { + bool bShouldValidate = true; + OSL_VERIFY( xFormProps->getPropertyValue( s_sFormsCheckRequiredFields ) >>= bShouldValidate ); + return bShouldValidate; + } + + // next, check the data source which created the connection + Reference< XChild > xConnectionAsChild( xFormProps->getPropertyValue( FM_PROP_ACTIVE_CONNECTION ), UNO_QUERY_THROW ); + Reference< XPropertySet > xDataSource( xConnectionAsChild->getParent(), UNO_QUERY ); + if ( !xDataSource.is() ) + // seldom (but possible): this is not a connection created by a data source + return true; + + Reference< XPropertySet > xDataSourceSettings( + xDataSource->getPropertyValue("Settings"), + UNO_QUERY_THROW ); + + bool bShouldValidate = true; + OSL_VERIFY( xDataSourceSettings->getPropertyValue( s_sFormsCheckRequiredFields ) >>= bShouldValidate ); + return bShouldValidate; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + return true; + } +} + +// XRowSetApproveListener + +sal_Bool SAL_CALL FormController::approveRowChange(const RowChangeEvent& _rEvent) +{ + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + ::comphelper::OInterfaceIteratorHelper2 aIter(m_aRowSetApproveListeners); + bool bValid = true; + if (aIter.hasMoreElements()) + { + RowChangeEvent aEvt( _rEvent ); + aEvt.Source = *this; + bValid = static_cast<XRowSetApproveListener*>(aIter.next())->approveRowChange(aEvt); + } + + if ( !bValid ) + return bValid; + + if ( ( _rEvent.Action != RowChangeAction::INSERT ) + && ( _rEvent.Action != RowChangeAction::UPDATE ) + ) + return bValid; + + // if some of the control models are bound to validators, check them + OUString sInvalidityExplanation; + Reference< XControlModel > xInvalidModel; + if ( !checkFormComponentValidity( sInvalidityExplanation, xInvalidModel ) ) + { + Reference< XControl > xControl( locateControl( xInvalidModel ) ); + aGuard.clear(); + displayErrorSetFocus( sInvalidityExplanation, xControl, getDialogParentWindow() ); + return false; + } + + // check values on NULL and required flag + if ( !lcl_shouldValidateRequiredFields_nothrow( _rEvent.Source ) ) + return true; + + OSL_ENSURE(m_pColumnInfoCache, "FormController::approveRowChange: no column infos!"); + if (!m_pColumnInfoCache) + return true; + + try + { + if ( !m_pColumnInfoCache->controlsInitialized() ) + m_pColumnInfoCache->initializeControls( getControls() ); + + size_t colCount = m_pColumnInfoCache->getColumnCount(); + for ( size_t col = 0; col < colCount; ++col ) + { + const ColumnInfo& rColInfo = m_pColumnInfoCache->getColumnInfo( col ); + + if ( rColInfo.bAutoIncrement ) + continue; + + if ( rColInfo.bReadOnly ) + continue; + + if ( !rColInfo.xFirstControlWithInputRequired.is() && !rColInfo.xFirstGridWithInputRequiredColumn.is() ) + { + continue; + } + + // TODO: in case of binary fields, this "getString" below is extremely expensive + if ( !rColInfo.xColumn->wasNull() || !rColInfo.xColumn->getString().isEmpty() ) + continue; + + OUString sMessage( SvxResId( RID_ERR_FIELDREQUIRED ) ); + sMessage = sMessage.replaceFirst( "#", rColInfo.sName ); + + // the control to focus + Reference< XControl > xControl( rColInfo.xFirstControlWithInputRequired ); + if ( !xControl.is() ) + xControl.set( rColInfo.xFirstGridWithInputRequiredColumn, UNO_QUERY ); + + aGuard.clear(); + displayErrorSetFocus( sMessage, rColInfo.xFirstControlWithInputRequired, getDialogParentWindow() ); + return false; + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + return true; +} + + +sal_Bool SAL_CALL FormController::approveCursorMove(const EventObject& event) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + ::comphelper::OInterfaceIteratorHelper2 aIter(m_aRowSetApproveListeners); + if (aIter.hasMoreElements()) + { + EventObject aEvt(event); + aEvt.Source = *this; + return static_cast<XRowSetApproveListener*>(aIter.next())->approveCursorMove(aEvt); + } + + return true; +} + + +sal_Bool SAL_CALL FormController::approveRowSetChange(const EventObject& event) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + ::comphelper::OInterfaceIteratorHelper2 aIter(m_aRowSetApproveListeners); + if (aIter.hasMoreElements()) + { + EventObject aEvt(event); + aEvt.Source = *this; + return static_cast<XRowSetApproveListener*>(aIter.next())->approveRowSetChange(aEvt); + } + + return true; +} + +// XRowSetApproveBroadcaster + +void SAL_CALL FormController::addRowSetApproveListener(const Reference< XRowSetApproveListener > & _rxListener) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + m_aRowSetApproveListeners.addInterface(_rxListener); +} + + +void SAL_CALL FormController::removeRowSetApproveListener(const Reference< XRowSetApproveListener > & _rxListener) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + m_aRowSetApproveListeners.removeInterface(_rxListener); +} + +// XErrorListener + +void SAL_CALL FormController::errorOccured(const SQLErrorEvent& aEvent) +{ + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + ::comphelper::OInterfaceIteratorHelper2 aIter(m_aErrorListeners); + if (aIter.hasMoreElements()) + { + SQLErrorEvent aEvt(aEvent); + aEvt.Source = *this; + static_cast<XSQLErrorListener*>(aIter.next())->errorOccured(aEvt); + } + else + { + aGuard.clear(); + displayException(aEvent, getDialogParentWindow()); + } +} + +// XErrorBroadcaster + +void SAL_CALL FormController::addSQLErrorListener(const Reference< XSQLErrorListener > & aListener) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + m_aErrorListeners.addInterface(aListener); +} + + +void SAL_CALL FormController::removeSQLErrorListener(const Reference< XSQLErrorListener > & aListener) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + m_aErrorListeners.removeInterface(aListener); +} + +// XDatabaseParameterBroadcaster2 + +void SAL_CALL FormController::addDatabaseParameterListener(const Reference< XDatabaseParameterListener > & aListener) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + m_aParameterListeners.addInterface(aListener); +} + + +void SAL_CALL FormController::removeDatabaseParameterListener(const Reference< XDatabaseParameterListener > & aListener) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + m_aParameterListeners.removeInterface(aListener); +} + +// XDatabaseParameterBroadcaster + +void SAL_CALL FormController::addParameterListener(const Reference< XDatabaseParameterListener > & aListener) +{ + FormController::addDatabaseParameterListener( aListener ); +} + + +void SAL_CALL FormController::removeParameterListener(const Reference< XDatabaseParameterListener > & aListener) +{ + FormController::removeDatabaseParameterListener( aListener ); +} + +// XDatabaseParameterListener + +sal_Bool SAL_CALL FormController::approveParameter(const DatabaseParameterEvent& aEvent) +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + ::comphelper::OInterfaceIteratorHelper2 aIter(m_aParameterListeners); + if (aIter.hasMoreElements()) + { + DatabaseParameterEvent aEvt(aEvent); + aEvt.Source = *this; + return static_cast<XDatabaseParameterListener*>(aIter.next())->approveParameter(aEvt); + } + else + { + // default handling: instantiate an interaction handler and let it handle the parameter request + try + { + if ( !ensureInteractionHandler() ) + return false; + + // two continuations allowed: OK and Cancel + OParameterContinuation* pParamValues = new OParameterContinuation; + OInteractionAbort* pAbort = new OInteractionAbort; + // the request + ParametersRequest aRequest; + aRequest.Parameters = aEvent.Parameters; + aRequest.Connection = getConnection(Reference< XRowSet >(aEvent.Source, UNO_QUERY)); + OInteractionRequest* pParamRequest = new OInteractionRequest(makeAny(aRequest)); + Reference< XInteractionRequest > xParamRequest(pParamRequest); + // some knittings + pParamRequest->addContinuation(pParamValues); + pParamRequest->addContinuation(pAbort); + + // handle the request + m_xInteractionHandler->handle(xParamRequest); + + if (!pParamValues->wasSelected()) + // canceled + return false; + + // transfer the values into the parameter supplier + Sequence< PropertyValue > aFinalValues = pParamValues->getValues(); + if (aFinalValues.getLength() != aRequest.Parameters->getCount()) + { + OSL_FAIL("FormController::approveParameter: the InteractionHandler returned nonsense!"); + return false; + } + const PropertyValue* pFinalValues = aFinalValues.getConstArray(); + for (sal_Int32 i=0; i<aFinalValues.getLength(); ++i, ++pFinalValues) + { + Reference< XPropertySet > xParam( + aRequest.Parameters->getByIndex(i), css::uno::UNO_QUERY); + if (xParam.is()) + { +#ifdef DBG_UTIL + OUString sName; + xParam->getPropertyValue(FM_PROP_NAME) >>= sName; + DBG_ASSERT(sName == pFinalValues->Name, "FormController::approveParameter: suspicious value names!"); +#endif + try { xParam->setPropertyValue(FM_PROP_VALUE, pFinalValues->Value); } + catch(Exception&) + { + OSL_FAIL("FormController::approveParameter: setting one of the properties failed!"); + } + } + } + } + catch(Exception&) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + return true; +} + +// XConfirmDeleteBroadcaster + +void SAL_CALL FormController::addConfirmDeleteListener(const Reference< XConfirmDeleteListener > & aListener) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + m_aDeleteListeners.addInterface(aListener); +} + + +void SAL_CALL FormController::removeConfirmDeleteListener(const Reference< XConfirmDeleteListener > & aListener) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + m_aDeleteListeners.removeInterface(aListener); +} + +// XConfirmDeleteListener + +sal_Bool SAL_CALL FormController::confirmDelete(const RowChangeEvent& aEvent) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + impl_checkDisposed_throw(); + + ::comphelper::OInterfaceIteratorHelper2 aIter(m_aDeleteListeners); + if (aIter.hasMoreElements()) + { + RowChangeEvent aEvt(aEvent); + aEvt.Source = *this; + return static_cast<XConfirmDeleteListener*>(aIter.next())->confirmDelete(aEvt); + } + // default handling: instantiate an interaction handler and let it handle the request + + OUString sTitle; + sal_Int32 nLength = aEvent.Rows; + if ( nLength > 1 ) + { + sTitle = SvxResId( RID_STR_DELETECONFIRM_RECORDS ); + sTitle = sTitle.replaceFirst( "#", OUString::number(nLength) ); + } + else + sTitle = SvxResId( RID_STR_DELETECONFIRM_RECORD ); + + try + { + if ( !ensureInteractionHandler() ) + return false; + + // two continuations allowed: Yes and No + OInteractionApprove* pApprove = new OInteractionApprove; + OInteractionDisapprove* pDisapprove = new OInteractionDisapprove; + + // the request + SQLWarning aWarning; + aWarning.Message = sTitle; + SQLWarning aDetails; + aDetails.Message = SvxResId(RID_STR_DELETECONFIRM); + aWarning.NextException <<= aDetails; + + OInteractionRequest* pRequest = new OInteractionRequest( makeAny( aWarning ) ); + Reference< XInteractionRequest > xRequest( pRequest ); + + // some knittings + pRequest->addContinuation( pApprove ); + pRequest->addContinuation( pDisapprove ); + + // handle the request + m_xInteractionHandler->handle( xRequest ); + + if ( pApprove->wasSelected() ) + return true; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + + return false; +} + + +void SAL_CALL FormController::invalidateFeatures( const Sequence< ::sal_Int16 >& Features ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + // for now, just copy the ids of the features, because... + ::std::copy( Features.begin(), Features.end(), + ::std::insert_iterator< ::std::set< sal_Int16 > >( m_aInvalidFeatures, m_aInvalidFeatures.begin() ) + ); + + // ... we will do the real invalidation asynchronously + if ( !m_aFeatureInvalidationTimer.IsActive() ) + m_aFeatureInvalidationTimer.Start(); +} + + +void SAL_CALL FormController::invalidateAllFeatures( ) +{ + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + Sequence< sal_Int16 > aInterceptedFeatures( comphelper::mapKeysToSequence(m_aFeatureDispatchers) ); + + aGuard.clear(); + if ( aInterceptedFeatures.hasElements() ) + invalidateFeatures( aInterceptedFeatures ); +} + + +Reference< XDispatch > +FormController::interceptedQueryDispatch( const URL& aURL, + const OUString& /*aTargetFrameName*/, sal_Int32 /*nSearchFlags*/) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + Reference< XDispatch > xReturn; + // dispatches handled by ourself + if ( ( aURL.Complete == FMURL_CONFIRM_DELETION ) + || ( ( aURL.Complete == "private:/InteractionHandler" ) + && ensureInteractionHandler() + ) + ) + xReturn = static_cast< XDispatch* >( this ); + + // dispatches of FormSlot-URLs we have to translate + if ( !xReturn.is() && m_xFormOperations.is() ) + { + // find the slot id which corresponds to the URL + sal_Int32 nFeatureSlotId = svx::FeatureSlotTranslation::getControllerFeatureSlotIdForURL( aURL.Main ); + sal_Int16 nFormFeature = ( nFeatureSlotId != -1 ) ? svx::FeatureSlotTranslation::getFormFeatureForSlotId( nFeatureSlotId ) : -1; + if ( nFormFeature > 0 ) + { + // get the dispatcher for this feature, create if necessary + DispatcherContainer::const_iterator aDispatcherPos = m_aFeatureDispatchers.find( nFormFeature ); + if ( aDispatcherPos == m_aFeatureDispatchers.end() ) + { + aDispatcherPos = m_aFeatureDispatchers.emplace( + nFormFeature, new svx::OSingleFeatureDispatcher( aURL, nFormFeature, m_xFormOperations, m_aMutex ) + ).first; + } + + OSL_ENSURE( aDispatcherPos->second.is(), "FormController::interceptedQueryDispatch: should have a dispatcher by now!" ); + return aDispatcherPos->second; + } + } + + // no more to offer + return xReturn; +} + + +void SAL_CALL FormController::dispatch( const URL& _rURL, const Sequence< PropertyValue >& _rArgs ) +{ + if ( _rArgs.getLength() != 1 ) + { + OSL_FAIL( "FormController::dispatch: no arguments -> no dispatch!" ); + return; + } + + if ( _rURL.Complete == "private:/InteractionHandler" ) + { + Reference< XInteractionRequest > xRequest; + OSL_VERIFY( _rArgs[0].Value >>= xRequest ); + if ( xRequest.is() ) + handle( xRequest ); + return; + } + + if ( _rURL.Complete == FMURL_CONFIRM_DELETION ) + { + OSL_FAIL( "FormController::dispatch: How do you expect me to return something via this call?" ); + // confirmDelete has a return value - dispatch hasn't + return; + } + + OSL_FAIL( "FormController::dispatch: unknown URL!" ); +} + + +void SAL_CALL FormController::addStatusListener( const Reference< XStatusListener >& _rxListener, const URL& _rURL ) +{ + if (_rURL.Complete == FMURL_CONFIRM_DELETION) + { + if (_rxListener.is()) + { // send an initial statusChanged event + FeatureStateEvent aEvent; + aEvent.FeatureURL = _rURL; + aEvent.IsEnabled = true; + _rxListener->statusChanged(aEvent); + // and don't add the listener at all (the status will never change) + } + } + else + OSL_FAIL("FormController::addStatusListener: invalid (unsupported) URL!"); +} + + +Reference< XInterface > SAL_CALL FormController::getParent() +{ + return m_xParent; +} + + +void SAL_CALL FormController::setParent( const Reference< XInterface >& Parent) +{ + m_xParent = Parent; +} + + +void SAL_CALL FormController::removeStatusListener( const Reference< XStatusListener >& /*_rxListener*/, const URL& _rURL ) +{ + OSL_ENSURE(_rURL.Complete == FMURL_CONFIRM_DELETION, "FormController::removeStatusListener: invalid (unsupported) URL!"); + // we never really added the listener, so we don't need to remove it +} + + +Reference< XDispatchProviderInterceptor > FormController::createInterceptor(const Reference< XDispatchProviderInterception > & _xInterception) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); +#ifdef DBG_UTIL + // check if we already have an interceptor for the given object + for ( const auto & it : m_aControlDispatchInterceptors ) + { + if (it->getIntercepted() == _xInterception) + OSL_FAIL("FormController::createInterceptor : we already do intercept this objects dispatches !"); + } +#endif + + rtl::Reference<DispatchInterceptionMultiplexer> pInterceptor(new DispatchInterceptionMultiplexer( _xInterception, this )); + m_aControlDispatchInterceptors.push_back( pInterceptor ); + + return pInterceptor.get(); +} + + +bool FormController::ensureInteractionHandler() +{ + if ( m_xInteractionHandler.is() ) + return true; + if ( m_bAttemptedHandlerCreation ) + return false; + m_bAttemptedHandlerCreation = true; + + m_xInteractionHandler = InteractionHandler::createWithParent(m_xComponentContext, + VCLUnoHelper::GetInterface(getDialogParentWindow())); + return m_xInteractionHandler.is(); +} + + +void SAL_CALL FormController::handle( const Reference< XInteractionRequest >& _rRequest ) +{ + if ( !ensureInteractionHandler() ) + return; + m_xInteractionHandler->handle( _rRequest ); +} + + +void FormController::deleteInterceptor(const Reference< XDispatchProviderInterception > & _xInterception) +{ + OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); + // search the interceptor responsible for the given object + auto aIter = std::find_if(m_aControlDispatchInterceptors.begin(), m_aControlDispatchInterceptors.end(), + [&_xInterception](const rtl::Reference<DispatchInterceptionMultiplexer>& rpInterceptor) { + return rpInterceptor->getIntercepted() == _xInterception; + }); + if (aIter != m_aControlDispatchInterceptors.end()) + { + // log off the interception from its interception object + (*aIter)->dispose(); + // remove the interceptor from our array + m_aControlDispatchInterceptors.erase(aIter); + } +} + + +void FormController::implInvalidateCurrentControlDependentFeatures() +{ + Sequence< sal_Int16 > aCurrentControlDependentFeatures(4); + + aCurrentControlDependentFeatures[0] = FormFeature::SortAscending; + aCurrentControlDependentFeatures[1] = FormFeature::SortDescending; + aCurrentControlDependentFeatures[2] = FormFeature::AutoFilter; + aCurrentControlDependentFeatures[3] = FormFeature::RefreshCurrentControl; + + invalidateFeatures( aCurrentControlDependentFeatures ); +} + + +void SAL_CALL FormController::columnChanged( const EventObject& /*_event*/ ) +{ + implInvalidateCurrentControlDependentFeatures(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/formcontrolling.cxx b/svx/source/form/formcontrolling.cxx new file mode 100644 index 000000000..7fffd8461 --- /dev/null +++ b/svx/source/form/formcontrolling.cxx @@ -0,0 +1,496 @@ +/* -*- 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 <sal/macros.h> +#include <formcontrolling.hxx> +#include <fmurl.hxx> +#include <svx/svxids.hrc> +#include <fmprop.hxx> +#include <svx/fmtools.hxx> + +#include <com/sun/star/form/runtime/FormOperations.hpp> +#include <com/sun/star/form/runtime/FormFeature.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> + +#include <tools/diagnose_ex.h> +#include <comphelper/anytostring.hxx> +#include <comphelper/processfactory.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <osl/diagnose.h> + +#include <algorithm> + + +namespace svx +{ + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::form::runtime::XFormController; + using ::com::sun::star::form::runtime::FormOperations; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::sdbc::XRowSet; + using ::com::sun::star::form::runtime::FeatureState; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::beans::NamedValue; + using ::com::sun::star::beans::XPropertySet; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::sdbc::SQLException; + using ::com::sun::star::sdb::SQLErrorEvent; + using ::com::sun::star::lang::EventObject; + + namespace FormFeature = ::com::sun::star::form::runtime::FormFeature; + + + //= FeatureSlotTranslation + + namespace + { + struct FeatureDescription + { + OUString sURL; // the URL + sal_Int32 nSlotId; // the SFX-compatible slot ID + sal_Int16 nFormFeature; // the css.form.runtime.FormFeature ID + }; + typedef ::std::vector< FeatureDescription > FeatureDescriptions; + + + const FeatureDescriptions& getFeatureDescriptions() + { + static const FeatureDescriptions s_aFeatureDescriptions({ + { OUString(FMURL_FORM_POSITION), SID_FM_RECORD_ABSOLUTE, FormFeature::MoveAbsolute }, + { OUString(FMURL_FORM_RECORDCOUNT), SID_FM_RECORD_TOTAL, FormFeature::TotalRecords }, + { OUString(FMURL_RECORD_MOVEFIRST), SID_FM_RECORD_FIRST, FormFeature::MoveToFirst }, + { OUString(FMURL_RECORD_MOVEPREV), SID_FM_RECORD_PREV, FormFeature::MoveToPrevious }, + { OUString(FMURL_RECORD_MOVENEXT), SID_FM_RECORD_NEXT, FormFeature::MoveToNext }, + { OUString(FMURL_RECORD_MOVELAST), SID_FM_RECORD_LAST, FormFeature::MoveToLast }, + { OUString(FMURL_RECORD_MOVETONEW), SID_FM_RECORD_NEW, FormFeature::MoveToInsertRow }, + { OUString(FMURL_RECORD_SAVE), SID_FM_RECORD_SAVE, FormFeature::SaveRecordChanges }, + { OUString(FMURL_RECORD_DELETE), SID_FM_RECORD_DELETE, FormFeature::DeleteRecord }, + { OUString(FMURL_FORM_REFRESH), SID_FM_REFRESH, FormFeature::ReloadForm }, + { OUString(FMURL_FORM_REFRESH_CURRENT_CONTROL), + SID_FM_REFRESH_FORM_CONTROL,FormFeature::RefreshCurrentControl }, + { OUString(FMURL_RECORD_UNDO), SID_FM_RECORD_UNDO, FormFeature::UndoRecordChanges }, + { OUString(FMURL_FORM_SORT_UP), SID_FM_SORTUP, FormFeature::SortAscending }, + { OUString(FMURL_FORM_SORT_DOWN), SID_FM_SORTDOWN, FormFeature::SortDescending }, + { OUString(FMURL_FORM_SORT), SID_FM_ORDERCRIT, FormFeature::InteractiveSort }, + { OUString(FMURL_FORM_AUTO_FILTER), SID_FM_AUTOFILTER, FormFeature::AutoFilter }, + { OUString(FMURL_FORM_FILTER), SID_FM_FILTERCRIT, FormFeature::InteractiveFilter }, + { OUString(FMURL_FORM_APPLY_FILTER), SID_FM_FORM_FILTERED, FormFeature::ToggleApplyFilter }, + { OUString(FMURL_FORM_REMOVE_FILTER), SID_FM_REMOVE_FILTER_SORT, FormFeature::RemoveFilterAndSort } + }); + return s_aFeatureDescriptions; + } + } + + + namespace + { + + struct MatchFeatureDescriptionByURL + { + const OUString& m_rURL; + explicit MatchFeatureDescriptionByURL( const OUString& _rURL ) :m_rURL( _rURL ) { } + + bool operator()( const FeatureDescription& _compare ) + { + return m_rURL == _compare.sURL; + } + }; + + + struct MatchFeatureDescriptionBySlotId + { + sal_Int32 m_nSlotId; + explicit MatchFeatureDescriptionBySlotId( sal_Int32 _nSlotId ) :m_nSlotId( _nSlotId ) { } + + bool operator()( const FeatureDescription& _compare ) + { + return m_nSlotId == _compare.nSlotId; + } + }; + + + struct MatchFeatureDescriptionByFormFeature + { + sal_Int32 m_nFormFeature; + explicit MatchFeatureDescriptionByFormFeature( sal_Int32 _nFormFeature ) :m_nFormFeature( _nFormFeature ) { } + + bool operator()( const FeatureDescription& _compare ) + { + return m_nFormFeature == _compare.nFormFeature; + } + }; + + + struct FormFeatureToSlotId + { + sal_Int32 operator()( sal_Int16 FormFeature ) + { + return FeatureSlotTranslation::getSlotIdForFormFeature( FormFeature ); + } + }; + } + + + sal_Int32 FeatureSlotTranslation::getControllerFeatureSlotIdForURL( const OUString& _rMainURL ) + { + const FeatureDescriptions& rDescriptions( getFeatureDescriptions() ); + FeatureDescriptions::const_iterator pos = ::std::find_if( rDescriptions.begin(), rDescriptions.end(), MatchFeatureDescriptionByURL( _rMainURL ) ); + return ( pos != rDescriptions.end() ) ? pos->nSlotId : -1; + } + + + sal_Int16 FeatureSlotTranslation::getFormFeatureForSlotId( sal_Int32 _nSlotId ) + { + const FeatureDescriptions& rDescriptions( getFeatureDescriptions() ); + FeatureDescriptions::const_iterator pos = ::std::find_if( rDescriptions.begin(), rDescriptions.end(), MatchFeatureDescriptionBySlotId( _nSlotId ) ); + OSL_ENSURE( pos != rDescriptions.end(), "FeatureSlotTranslation::getFormFeatureForSlotId: not found!" ); + return ( pos != rDescriptions.end() ) ? pos->nFormFeature : -1; + } + + + sal_Int32 FeatureSlotTranslation::getSlotIdForFormFeature( sal_Int16 _nFormFeature ) + { + const FeatureDescriptions& rDescriptions( getFeatureDescriptions() ); + FeatureDescriptions::const_iterator pos = ::std::find_if( rDescriptions.begin(), rDescriptions.end(), MatchFeatureDescriptionByFormFeature( _nFormFeature ) ); + OSL_ENSURE( pos != rDescriptions.end(), "FeatureSlotTranslation::getSlotIdForFormFeature: not found!" ); + return ( pos != rDescriptions.end() ) ? pos->nSlotId : -1; + } + + ControllerFeatures::ControllerFeatures( IControllerFeatureInvalidation* _pInvalidationCallback ) + :m_pInvalidationCallback( _pInvalidationCallback ) + { + } + + + ControllerFeatures::ControllerFeatures( const Reference< XFormController >& _rxController ) + :m_pInvalidationCallback( nullptr ) + { + assign( _rxController ); + } + + + void ControllerFeatures::assign( const Reference< XFormController >& _rxController ) + { + dispose(); + m_pImpl = new FormControllerHelper( _rxController, m_pInvalidationCallback ); + } + + + ControllerFeatures::~ControllerFeatures() + { + dispose(); + } + + + void ControllerFeatures::dispose() + { + if ( m_pImpl.is() ) + { + m_pImpl->dispose(); + m_pImpl.clear(); + } + } + + FormControllerHelper::FormControllerHelper( const Reference< XFormController >& _rxController, IControllerFeatureInvalidation* _pInvalidationCallback ) + :m_pInvalidationCallback( _pInvalidationCallback ) + { + osl_atomic_increment( &m_refCount ); + try + { + m_xFormOperations = FormOperations::createWithFormController( comphelper::getProcessComponentContext(), _rxController ); + if ( m_xFormOperations.is() ) + m_xFormOperations->setFeatureInvalidation( this ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + osl_atomic_decrement( &m_refCount ); + } + + + FormControllerHelper::~FormControllerHelper( ) + { + try + { + acquire(); + dispose(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + + void FormControllerHelper::dispose() + { + if ( m_xFormOperations.is() ) + m_xFormOperations->dispose(); + m_xFormOperations.clear(); + } + + + bool FormControllerHelper::isEnabled( sal_Int32 _nSlotId ) const + { + if ( !m_xFormOperations.is() ) + return false; + return m_xFormOperations->isEnabled( FeatureSlotTranslation::getFormFeatureForSlotId( _nSlotId ) ); + } + + + Reference< XRowSet > FormControllerHelper::getCursor() const + { + Reference< XRowSet > xCursor; + if ( m_xFormOperations.is() ) + xCursor = m_xFormOperations->getCursor(); + return xCursor; + } + + + void FormControllerHelper::getState( sal_Int32 _nSlotId, FeatureState& _rState ) const + { + if ( m_xFormOperations.is() ) + _rState = m_xFormOperations->getState( FeatureSlotTranslation::getFormFeatureForSlotId( _nSlotId ) ); + } + + + bool FormControllerHelper::commitCurrentControl( ) const + { + return impl_operateForm_nothrow( COMMIT_CONTROL ); + } + + + bool FormControllerHelper::commitCurrentRecord() const + { + return impl_operateForm_nothrow( COMMIT_RECORD ); + } + + + void FormControllerHelper::execute( sal_Int32 _nSlotId, const OUString& _rParamName, const Any& _rParamValue ) const + { + Sequence< NamedValue > aArguments { { _rParamName, _rParamValue } }; + impl_operateForm_nothrow( EXECUTE_ARGS, FeatureSlotTranslation::getFormFeatureForSlotId( _nSlotId ), aArguments ); + } + + + bool FormControllerHelper::impl_operateForm_nothrow( const FormOperation _eWhat, const sal_Int16 _nFeature, + const Sequence< NamedValue >& _rArguments ) const + { + if ( !m_xFormOperations.is() ) + return false; + + Any aError; + bool bSuccess = false; + const_cast< FormControllerHelper* >( this )->m_aOperationError.clear(); + try + { + // to prevent the controller from displaying any error messages which happen while we operate on it, + // we add ourself as XSQLErrorListener. By contract, a FormController displays errors if and only if + // no SQLErrorListeners are registered. + m_xFormOperations->getController()->addSQLErrorListener( const_cast< FormControllerHelper* >(this) ); + + switch ( _eWhat ) + { + case COMMIT_CONTROL: + bSuccess = m_xFormOperations->commitCurrentControl(); + break; + + case COMMIT_RECORD: + { + sal_Bool bDummy( false ); + bSuccess = m_xFormOperations->commitCurrentRecord( bDummy ); + } + break; + + case EXECUTE: + m_xFormOperations->execute( _nFeature ); + bSuccess = true; + break; + + case EXECUTE_ARGS: + m_xFormOperations->executeWithArguments( _nFeature, _rArguments ); + bSuccess = true; + break; + } + } + catch ( const SQLException& ) + { + m_xFormOperations->getController()->removeSQLErrorListener( const_cast< FormControllerHelper* >(this) ); + aError = ::cppu::getCaughtException(); + } + catch( const Exception& ) + { + m_xFormOperations->getController()->removeSQLErrorListener( const_cast< FormControllerHelper* >(this) ); + SQLException aFallbackError; + aFallbackError.Message = ::comphelper::anyToString( ::cppu::getCaughtException() ); + aError <<= aFallbackError; + } + + if ( bSuccess ) + return true; + + // display the error. Prefer the one reported in errorOccurred over the one caught. + if ( m_aOperationError.hasValue() ) + displayException( m_aOperationError ); + else if ( aError.hasValue() ) + displayException( aError ); + else + OSL_FAIL( "FormControllerHelper::impl_operateForm_nothrow: no success, but no error?" ); + + return false; + } + + + void FormControllerHelper::execute( sal_Int32 _nSlotId ) const + { + impl_operateForm_nothrow( EXECUTE, FeatureSlotTranslation::getFormFeatureForSlotId( _nSlotId ), + Sequence< NamedValue >() ); + } + + + void SAL_CALL FormControllerHelper::invalidateFeatures( const Sequence< ::sal_Int16 >& Features ) + { + if ( !m_pInvalidationCallback ) + // nobody's interested in ... + return; + + ::std::vector< sal_Int32 > aFeatures( Features.getLength() ); + ::std::transform( + Features.begin(), + Features.end(), + aFeatures.begin(), + FormFeatureToSlotId() + ); + + m_pInvalidationCallback->invalidateFeatures( aFeatures ); + } + + + void SAL_CALL FormControllerHelper::invalidateAllFeatures() + { + if ( !m_pInvalidationCallback ) + // nobody's interested in ... + return; + + // actually, it's a little bit more than the supported features, + // but on the medium term, we are to support everything listed + // here + ::std::vector< sal_Int32 > aSupportedFeatures; + const sal_Int32 pSupportedFeatures[] = + { + SID_FM_RECORD_FIRST, + SID_FM_RECORD_NEXT, + SID_FM_RECORD_PREV, + SID_FM_RECORD_LAST, + SID_FM_RECORD_NEW, + SID_FM_RECORD_DELETE, + SID_FM_RECORD_ABSOLUTE, + SID_FM_RECORD_TOTAL, + SID_FM_RECORD_SAVE, + SID_FM_RECORD_UNDO, + SID_FM_REMOVE_FILTER_SORT, + SID_FM_SORTUP, + SID_FM_SORTDOWN, + SID_FM_ORDERCRIT, + SID_FM_AUTOFILTER, + SID_FM_FILTERCRIT, + SID_FM_FORM_FILTERED, + SID_FM_REFRESH, + SID_FM_REFRESH_FORM_CONTROL, + SID_FM_SEARCH, + SID_FM_FILTER_START, + SID_FM_VIEW_AS_GRID + }; + sal_Int32 nFeatureCount = SAL_N_ELEMENTS( pSupportedFeatures ); + aSupportedFeatures.resize( nFeatureCount ); + ::std::copy( pSupportedFeatures, pSupportedFeatures + nFeatureCount, aSupportedFeatures.begin() ); + + m_pInvalidationCallback->invalidateFeatures( aSupportedFeatures ); + } + + + void SAL_CALL FormControllerHelper::errorOccured( const SQLErrorEvent& Event ) + { + OSL_ENSURE( !m_aOperationError.hasValue(), "FormControllerHelper::errorOccurred: two errors during one operation?" ); + m_aOperationError = Event.Reason; + } + + + void SAL_CALL FormControllerHelper::disposing( const EventObject& /*_Source*/ ) + { + // not interested in + } + + + bool FormControllerHelper::isInsertionRow() const + { + bool bIs = false; + if ( m_xFormOperations.is() ) + bIs = m_xFormOperations->isInsertionRow(); + return bIs; + } + + + bool FormControllerHelper::isModifiedRow() const + { + bool bIs = false; + if ( m_xFormOperations.is() ) + bIs = m_xFormOperations->isModifiedRow(); + return bIs; + } + + bool FormControllerHelper::canDoFormFilter() const + { + if ( !m_xFormOperations.is() ) + return false; + + bool bCanDo = false; + try + { + Reference< XPropertySet > xCursorProperties( m_xFormOperations->getCursor(), UNO_QUERY_THROW ); + + bool bEscapeProcessing( false ); + OSL_VERIFY( xCursorProperties->getPropertyValue( FM_PROP_ESCAPE_PROCESSING ) >>= bEscapeProcessing ); + + OUString sActiveCommand; + OSL_VERIFY( xCursorProperties->getPropertyValue( FM_PROP_ACTIVECOMMAND ) >>= sActiveCommand ); + + bool bInsertOnlyForm( false ); + OSL_VERIFY( xCursorProperties->getPropertyValue( FM_PROP_INSERTONLY ) >>= bInsertOnlyForm ); + + bCanDo = bEscapeProcessing && !sActiveCommand.isEmpty() && !bInsertOnlyForm; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + return bCanDo; + } + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/formdispatchinterceptor.cxx b/svx/source/form/formdispatchinterceptor.cxx new file mode 100644 index 000000000..5bc0834cf --- /dev/null +++ b/svx/source/form/formdispatchinterceptor.cxx @@ -0,0 +1,177 @@ +/* -*- 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 <formdispatchinterceptor.hxx> + +namespace svxform +{ + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::frame::XDispatchProviderInterception; + using ::com::sun::star::frame::XDispatchProviderInterceptor; + using ::com::sun::star::lang::XComponent; + using ::com::sun::star::util::URL; + using ::com::sun::star::frame::XDispatch; + using ::com::sun::star::frame::DispatchDescriptor; + using ::com::sun::star::frame::XDispatchProvider; + using ::com::sun::star::lang::EventObject; + + DispatchInterceptionMultiplexer::DispatchInterceptionMultiplexer( + const Reference< XDispatchProviderInterception >& _rxToIntercept, DispatchInterceptor* _pMaster ) + :DispatchInterceptionMultiplexer_BASE(_pMaster && _pMaster->getInterceptorMutex() ? *_pMaster->getInterceptorMutex() : m_aFallback) + ,m_aFallback() + ,m_pMutex( _pMaster && _pMaster->getInterceptorMutex() ? _pMaster->getInterceptorMutex() : &m_aFallback ) + ,m_xIntercepted(_rxToIntercept) + ,m_bListening(false) + ,m_pMaster(_pMaster) + { + + ::osl::MutexGuard aGuard( *m_pMutex ); + osl_atomic_increment(&m_refCount); + if (_rxToIntercept.is()) + { + _rxToIntercept->registerDispatchProviderInterceptor(static_cast<XDispatchProviderInterceptor*>(this)); + // this should make us the top-level dispatch-provider for the component, via a call to our + // setDispatchProvider we should have got a fallback for requests we (i.e. our master) cannot fulfill + Reference< XComponent> xInterceptedComponent(_rxToIntercept, UNO_QUERY); + if (xInterceptedComponent.is()) + { + xInterceptedComponent->addEventListener(this); + m_bListening = true; + } + } + osl_atomic_decrement(&m_refCount); + } + + + DispatchInterceptionMultiplexer::~DispatchInterceptionMultiplexer() + { + if (!rBHelper.bDisposed) + dispose(); + + } + + + Reference< XDispatch > SAL_CALL DispatchInterceptionMultiplexer::queryDispatch( const URL& aURL, const OUString& aTargetFrameName, sal_Int32 nSearchFlags ) + { + ::osl::MutexGuard aGuard( *m_pMutex ); + Reference< XDispatch> xResult; + // ask our 'real' interceptor + if (m_pMaster) + xResult = m_pMaster->interceptedQueryDispatch( aURL, aTargetFrameName, nSearchFlags); + + // ask our slave provider + if (!xResult.is() && m_xSlaveDispatcher.is()) + xResult = m_xSlaveDispatcher->queryDispatch(aURL, aTargetFrameName, nSearchFlags); + + return xResult; + } + + + Sequence< Reference< XDispatch > > SAL_CALL + DispatchInterceptionMultiplexer::queryDispatches( const Sequence< DispatchDescriptor >& aDescripts ) + { + ::osl::MutexGuard aGuard( *m_pMutex ); + Sequence< Reference< XDispatch> > aReturn(aDescripts.getLength()); + std::transform(aDescripts.begin(), aDescripts.end(), aReturn.begin(), + [this](const DispatchDescriptor& rDescript) -> Reference< XDispatch> { + return queryDispatch(rDescript.FeatureURL, rDescript.FrameName, rDescript.SearchFlags); }); + return aReturn; + } + + + Reference< XDispatchProvider > SAL_CALL DispatchInterceptionMultiplexer::getSlaveDispatchProvider( ) + { + ::osl::MutexGuard aGuard( *m_pMutex ); + return m_xSlaveDispatcher; + } + + + void SAL_CALL DispatchInterceptionMultiplexer::setSlaveDispatchProvider(const Reference< XDispatchProvider>& xNewDispatchProvider) + { + ::osl::MutexGuard aGuard( *m_pMutex ); + m_xSlaveDispatcher = xNewDispatchProvider; + } + + + Reference< XDispatchProvider> SAL_CALL DispatchInterceptionMultiplexer::getMasterDispatchProvider() + { + ::osl::MutexGuard aGuard( *m_pMutex ); + return m_xMasterDispatcher; + } + + + void SAL_CALL DispatchInterceptionMultiplexer::setMasterDispatchProvider(const Reference< XDispatchProvider>& xNewSupplier) + { + ::osl::MutexGuard aGuard( *m_pMutex ); + m_xMasterDispatcher = xNewSupplier; + } + + + void SAL_CALL DispatchInterceptionMultiplexer::disposing(const EventObject& Source) + { + if (m_bListening) + { + Reference< XDispatchProviderInterception > xIntercepted(m_xIntercepted.get(), UNO_QUERY); + if (Source.Source == xIntercepted) + ImplDetach(); + } + } + + + void DispatchInterceptionMultiplexer::ImplDetach() + { + ::osl::MutexGuard aGuard( *m_pMutex ); + OSL_ENSURE(m_bListening, "DispatchInterceptionMultiplexer::ImplDetach: invalid call!"); + + // deregister ourself from the interception component + Reference< XDispatchProviderInterception > xIntercepted(m_xIntercepted.get(), UNO_QUERY); + if (xIntercepted.is()) + xIntercepted->releaseDispatchProviderInterceptor(static_cast<XDispatchProviderInterceptor*>(this)); + + // m_xIntercepted.clear(); + // Don't reset m_xIntercepted: It may be needed by our owner to check for which object we were + // responsible. As we hold the object with a weak reference only, this should be no problem. + // 88936 - 23.07.2001 - frank.schoenheit@sun.com + m_pMaster = nullptr; + m_pMutex = &m_aFallback; + m_bListening = false; + } + + + void DispatchInterceptionMultiplexer::disposing() + { + // remove ourself as event listener from the interception component + if (m_bListening) + { + Reference< XComponent> xInterceptedComponent(m_xIntercepted.get(), UNO_QUERY); + if (xInterceptedComponent.is()) + xInterceptedComponent->removeEventListener(static_cast<XEventListener*>(this)); + + // detach from the interception component + ImplDetach(); + } + } + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/formfeaturedispatcher.cxx b/svx/source/form/formfeaturedispatcher.cxx new file mode 100644 index 000000000..330713783 --- /dev/null +++ b/svx/source/form/formfeaturedispatcher.cxx @@ -0,0 +1,191 @@ +/* -*- 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 <formfeaturedispatcher.hxx> + +#include <comphelper/namedvaluecollection.hxx> +#include <tools/diagnose_ex.h> + + +namespace svx +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::frame; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::util; + using namespace ::com::sun::star::form::runtime; + + OSingleFeatureDispatcher::OSingleFeatureDispatcher( const URL& _rFeatureURL, const sal_Int16 _nFormFeature, + const Reference< XFormOperations >& _rxFormOperations, ::osl::Mutex& _rMutex ) + :m_rMutex( _rMutex ) + ,m_aStatusListeners( _rMutex ) + ,m_xFormOperations( _rxFormOperations ) + ,m_aFeatureURL( _rFeatureURL ) + ,m_nFormFeature( _nFormFeature ) + ,m_bLastKnownEnabled( false ) + { + } + + + void OSingleFeatureDispatcher::getUnoState( FeatureStateEvent& /* [out] */ _rState ) const + { + _rState.Source = *const_cast< OSingleFeatureDispatcher* >( this ); + + FeatureState aState( m_xFormOperations->getState( m_nFormFeature ) ); + + _rState.FeatureURL = m_aFeatureURL; + _rState.IsEnabled = aState.Enabled; + _rState.Requery = false; + _rState.State = aState.State; + } + + + void OSingleFeatureDispatcher::updateAllListeners() + { + ::osl::ClearableMutexGuard aGuard( m_rMutex ); + + FeatureStateEvent aUnoState; + getUnoState( aUnoState ); + + if ( ( m_aLastKnownState == aUnoState.State ) && ( m_bLastKnownEnabled == bool(aUnoState.IsEnabled) ) ) + return; + + m_aLastKnownState = aUnoState.State; + m_bLastKnownEnabled = aUnoState.IsEnabled; + + notifyStatus( nullptr, aGuard ); + } + + + void OSingleFeatureDispatcher::notifyStatus( const Reference< XStatusListener >& _rxListener, ::osl::ClearableMutexGuard& _rFreeForNotification ) + { + FeatureStateEvent aUnoState; + getUnoState( aUnoState ); + + if ( _rxListener.is() ) + { + try + { + _rFreeForNotification.clear(); + _rxListener->statusChanged( aUnoState ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "OSingleFeatureDispatcher::notifyStatus" ); + } + } + else + { + ::comphelper::OInterfaceIteratorHelper2 aIter( m_aStatusListeners ); + _rFreeForNotification.clear(); + + while ( aIter.hasMoreElements() ) + { + try + { + static_cast< XStatusListener* >( aIter.next() )->statusChanged( aUnoState ); + } + catch( const DisposedException& ) + { + OSL_FAIL( "OSingleFeatureDispatcher::notifyStatus: caught a DisposedException - removing the listener!" ); + aIter.remove( ); + } + catch( const Exception& ) + { + OSL_FAIL( "OSingleFeatureDispatcher::notifyStatus: caught a generic exception while notifying a single listener!" ); + } + } + } + } + + + void SAL_CALL OSingleFeatureDispatcher::dispatch( const URL& _rURL, const Sequence< PropertyValue >& _rArguments ) + { + ::osl::ClearableMutexGuard aGuard( m_rMutex ); + + OSL_ENSURE( _rURL.Complete == m_aFeatureURL.Complete, "OSingleFeatureDispatcher::dispatch: not responsible for this URL!" ); + + if ( !m_xFormOperations->isEnabled( m_nFormFeature ) ) + return; + + // release our mutex before executing the command + sal_Int16 nFormFeature( m_nFormFeature ); + Reference< XFormOperations > xFormOperations( m_xFormOperations ); + aGuard.clear(); + + try + { + if ( !_rArguments.hasElements() ) + { + xFormOperations->execute( nFormFeature ); + } + else + { // at the moment we only support one parameter + ::comphelper::NamedValueCollection aArgs( _rArguments ); + xFormOperations->executeWithArguments( nFormFeature, aArgs.getNamedValues() ); + } + } + catch( const RuntimeException& ) + { + throw; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + + + void SAL_CALL OSingleFeatureDispatcher::addStatusListener( const Reference< XStatusListener >& _rxControl, const URL& _rURL ) + { + OSL_ENSURE( _rURL.Complete == m_aFeatureURL.Complete, "OSingleFeatureDispatcher::addStatusListener: unexpected URL!" ); + OSL_ENSURE( _rxControl.is(), "OSingleFeatureDispatcher::addStatusListener: senseless call!" ); + if ( !_rxControl.is() ) + return; + + ::osl::ClearableMutexGuard aGuard( m_rMutex ); + + m_aStatusListeners.addInterface( _rxControl ); + + // initially update the status + notifyStatus( _rxControl, aGuard ); + } + + + void SAL_CALL OSingleFeatureDispatcher::removeStatusListener( const Reference< XStatusListener >& _rxControl, const URL& _rURL ) + { + OSL_ENSURE( _rURL.Complete == m_aFeatureURL.Complete, "OSingleFeatureDispatcher::removeStatusListener: unexpected URL!" ); + OSL_ENSURE( _rxControl.is(), "OSingleFeatureDispatcher::removeStatusListener: senseless call!" ); + if ( !_rxControl.is() ) + return; + + ::osl::MutexGuard aGuard( m_rMutex ); + + m_aStatusListeners.removeInterface( _rxControl ); + } + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/formtoolbars.cxx b/svx/source/form/formtoolbars.cxx new file mode 100644 index 000000000..d46e7cc1b --- /dev/null +++ b/svx/source/form/formtoolbars.cxx @@ -0,0 +1,93 @@ +/* -*- 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 <formtoolbars.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <osl/diagnose.h> +#include <tools/diagnose_ex.h> + +#include <svx/svxids.hrc> + + +namespace svxform +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::frame; + using namespace ::com::sun::star::beans; + + FormToolboxes::FormToolboxes( const Reference< XFrame >& _rxFrame ) + { + // the layout manager + Reference< XPropertySet > xFrameProps( _rxFrame, UNO_QUERY ); + if ( xFrameProps.is() ) + xFrameProps->getPropertyValue("LayoutManager") >>= m_xLayouter; + } + + + void FormToolboxes::toggleToolbox( sal_uInt16 _nSlotId ) const + { + try + { + Reference< XLayoutManager > xManager( m_xLayouter ); + OSL_ENSURE( xManager. is(), "FormToolboxes::toggleToolbox: couldn't obtain the layout manager!" ); + if ( xManager. is() ) + { + OUString sToolboxResource( getToolboxResourceName( _nSlotId ) ); + if ( xManager->isElementVisible( sToolboxResource ) ) + { + xManager->hideElement( sToolboxResource ); + xManager->destroyElement( sToolboxResource ); + } + else + { + xManager->createElement( sToolboxResource ); + xManager->showElement( sToolboxResource ); + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "FormToolboxes::toggleToolbox" ); + } + } + + + bool FormToolboxes::isToolboxVisible( sal_uInt16 _nSlotId ) const + { + return m_xLayouter.is() && m_xLayouter->isElementVisible( + getToolboxResourceName( _nSlotId ) ); + } + + + OUString FormToolboxes::getToolboxResourceName( sal_uInt16 _nSlotId ) + { + OSL_ENSURE( _nSlotId == SID_FM_FORM_DESIGN_TOOLS , + "FormToolboxes::getToolboxResourceName: unsupported slot!" ); + + return "private:resource/toolbar/formdesign"; + } + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/labelitemwindow.cxx b/svx/source/form/labelitemwindow.cxx new file mode 100644 index 000000000..45c5160f4 --- /dev/null +++ b/svx/source/form/labelitemwindow.cxx @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <svx/labelitemwindow.hxx> + +LabelItemWindow::LabelItemWindow(vcl::Window* pParent, const OUString& rLabel) + : InterimItemWindow(pParent, "svx/ui/labelbox.ui", "LabelBox") + , m_xLabel(m_xBuilder->weld_label("label")) +{ + m_xLabel->set_label(rLabel); + + SetOptimalSize(); + + m_xLabel->set_toolbar_background(); +} + +void LabelItemWindow::SetOptimalSize() +{ + Size aSize(m_xLabel->get_preferred_size()); + aSize.AdjustWidth(12); + + SetSizePixel(aSize); +} + +void LabelItemWindow::set_label(const OUString& rLabel) { m_xLabel->set_label(rLabel); } + +OUString LabelItemWindow::get_label() const { return m_xLabel->get_label(); } + +void LabelItemWindow::dispose() +{ + m_xLabel.reset(); + InterimItemWindow::dispose(); +} + +LabelItemWindow::~LabelItemWindow() { disposeOnce(); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/svx/source/form/legacyformcontroller.cxx b/svx/source/form/legacyformcontroller.cxx new file mode 100644 index 000000000..94b2ae983 --- /dev/null +++ b/svx/source/form/legacyformcontroller.cxx @@ -0,0 +1,201 @@ +/* -*- 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 <fmservs.hxx> + +#include <com/sun/star/form/XFormController.hpp> +#include <com/sun/star/form/runtime/FormController.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/processfactory.hxx> + + +namespace svxform +{ + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::lang::XMultiServiceFactory; + using ::com::sun::star::awt::XControl; + using ::com::sun::star::awt::XTabControllerModel; + using ::com::sun::star::awt::XControlContainer; + using ::com::sun::star::lang::XServiceInfo; + using ::com::sun::star::form::runtime::FormController; + + using namespace ::com::sun::star; + + + //= LegacyFormController + + typedef ::cppu::WeakImplHelper < form::XFormController + , XServiceInfo + > LegacyFormController_Base; + + namespace { + + /** is an implementation of the legacy form controller service, namely css.form.FormController, supporting the + css.form.XFormController interface. + + This legacy API is superseded by css.form.runtime.(X)FormController, and though we migrated all OOo-internal + usage of this old API, their might be clients external to OOo still using it (though this is rather unlikely). + */ + class LegacyFormController : public LegacyFormController_Base + { + public: + static Reference< XInterface > Create( const Reference< XMultiServiceFactory >& _rxFactory ) + { + return *( new LegacyFormController( comphelper::getComponentContext(_rxFactory) ) ); + } + + protected: + explicit LegacyFormController( const Reference< XComponentContext >& _rxContext ) + :m_xDelegator( FormController::create(_rxContext) ) + { + } + + // form::XFormController + virtual Reference< XControl > SAL_CALL getCurrentControl( ) override; + virtual void SAL_CALL addActivateListener( const Reference< form::XFormControllerListener >& l ) override; + virtual void SAL_CALL removeActivateListener( const Reference< form::XFormControllerListener >& l ) override; + + // awt::XTabController + virtual void SAL_CALL setModel( const Reference< XTabControllerModel >& Model ) override; + virtual Reference< XTabControllerModel > SAL_CALL getModel( ) override; + virtual void SAL_CALL setContainer( const Reference< XControlContainer >& Container ) override; + virtual Reference< XControlContainer > SAL_CALL getContainer( ) override; + virtual Sequence< Reference< XControl > > SAL_CALL getControls( ) override; + virtual void SAL_CALL autoTabOrder( ) override; + virtual void SAL_CALL activateTabOrder( ) override; + virtual void SAL_CALL activateFirst( ) override; + virtual void SAL_CALL activateLast( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + private: + const Reference< form::runtime::XFormController > m_xDelegator; + }; + + } + + Reference< XControl > SAL_CALL LegacyFormController::getCurrentControl( ) + { + return m_xDelegator->getCurrentControl(); + } + + + void SAL_CALL LegacyFormController::addActivateListener( const Reference< form::XFormControllerListener >& _listener ) + { + m_xDelegator->addActivateListener( _listener ); + } + + + void SAL_CALL LegacyFormController::removeActivateListener( const Reference< form::XFormControllerListener >& _listener ) + { + m_xDelegator->removeActivateListener( _listener ); + } + + + void SAL_CALL LegacyFormController::setModel( const Reference< XTabControllerModel >& _model ) + { + m_xDelegator->setModel( _model ); + } + + + Reference< XTabControllerModel > SAL_CALL LegacyFormController::getModel( ) + { + return m_xDelegator->getModel(); + } + + + void SAL_CALL LegacyFormController::setContainer( const Reference< XControlContainer >& _container ) + { + m_xDelegator->setContainer( _container ); + } + + + Reference< XControlContainer > SAL_CALL LegacyFormController::getContainer( ) + { + return m_xDelegator->getContainer(); + } + + + Sequence< Reference< XControl > > SAL_CALL LegacyFormController::getControls( ) + { + return m_xDelegator->getControls(); + } + + + void SAL_CALL LegacyFormController::autoTabOrder( ) + { + m_xDelegator->autoTabOrder(); + } + + + void SAL_CALL LegacyFormController::activateTabOrder( ) + { + m_xDelegator->activateTabOrder(); + } + + + void SAL_CALL LegacyFormController::activateFirst( ) + { + m_xDelegator->activateFirst(); + } + + + void SAL_CALL LegacyFormController::activateLast( ) + { + m_xDelegator->activateLast(); + } + + + OUString SAL_CALL LegacyFormController::getImplementationName( ) + { + return "org.openoffice.comp.svx.LegacyFormController"; + } + + sal_Bool SAL_CALL LegacyFormController::supportsService( const OUString& _serviceName ) + { + return cppu::supportsService(this, _serviceName); + } + + Sequence< OUString > SAL_CALL LegacyFormController::getSupportedServiceNames( ) + { + return { "com.sun.star.form.FormController", "com.sun.star.awt.control.TabController" }; + } + +} + +css::uno::Reference< css::uno::XInterface > + LegacyFormController_NewInstance_Impl( const css::uno::Reference< css::lang::XMultiServiceFactory > & _rxORB ) +{ + return ::svxform::LegacyFormController::Create( _rxORB ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/navigatortree.cxx b/svx/source/form/navigatortree.cxx new file mode 100644 index 000000000..2f62c697e --- /dev/null +++ b/svx/source/form/navigatortree.cxx @@ -0,0 +1,2135 @@ +/* -*- 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 <memory> +#include <svx/dialmgr.hxx> +#include <svx/fmshell.hxx> +#include <svx/fmmodel.hxx> +#include <svx/fmpage.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svditer.hxx> + +#include <helpids.h> +#include <fmexpl.hxx> +#include <fmshimp.hxx> +#include <fmservs.hxx> +#include <fmundo.hxx> +#include <fmpgeimp.hxx> +#include <fmobj.hxx> +#include <fmprop.hxx> +#include <sal/log.hxx> +#include <vcl/svapp.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/viewfrm.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/property.hxx> +#include <comphelper/types.hxx> +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/script/XEventAttacherManager.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp> +#include <com/sun/star/datatransfer/XTransferable.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <svx/sdrpaintwindow.hxx> + +#include <svx/strings.hrc> +#include <tools/diagnose_ex.h> +#include <svx/svxids.hrc> +#include <bitmaps.hlst> +#include <vcl/treelistentry.hxx> +#include <vcl/commandevent.hxx> + +namespace svxform +{ + + + #define DROP_ACTION_TIMER_INITIAL_TICKS 10 + // Time until scroll starts + #define DROP_ACTION_TIMER_SCROLL_TICKS 3 + // Time to scroll one line + #define DROP_ACTION_TIMER_TICK_BASE 10 + // factor for both declarations (in ms) + + #define EXPLORER_SYNC_DELAY 200 + // Time (in ms) until explorer synchronizes the view after select or deselect + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::form; + using namespace ::com::sun::star::awt; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::script; + using namespace ::com::sun::star::datatransfer; + using namespace ::com::sun::star::datatransfer::clipboard; + using namespace ::com::sun::star::sdb; + + + // helper + + + typedef ::std::map< Reference< XInterface >, SdrObject* > MapModelToShape; + + + static void collectShapeModelMapping( SdrPage const * _pPage, MapModelToShape& _rMapping ) + { + OSL_ENSURE( _pPage, "collectShapeModelMapping: invalid arg!" ); + + _rMapping.clear(); + + SdrObjListIter aIter( _pPage ); + while ( aIter.IsMore() ) + { + SdrObject* pSdrObject = aIter.Next(); + FmFormObj* pFormObject = FmFormObj::GetFormObject( pSdrObject ); + if ( !pFormObject ) + continue; + + Reference< XInterface > xNormalizedModel( pFormObject->GetUnoControlModel(), UNO_QUERY ); + // note that this is normalized (i.e. queried for XInterface explicitly) + + ::std::pair< MapModelToShape::iterator, bool > aPos = + _rMapping.emplace( xNormalizedModel, pSdrObject ); + DBG_ASSERT( aPos.second, "collectShapeModelMapping: model was already existent!" ); + // if this asserts, this would mean we have 2 shapes pointing to the same model + } + } + + NavigatorTree::NavigatorTree( vcl::Window* pParent ) + :SvTreeListBox( pParent, WB_HASBUTTONS|WB_HASLINES|WB_BORDER|WB_HSCROLL ) // #100258# OJ WB_HSCROLL added + ,m_aControlExchange(this) + ,m_pRootEntry(nullptr) + ,m_pEditEntry(nullptr) + ,nEditEvent(nullptr) + ,m_sdiState(SDI_DIRTY) + ,m_aTimerTriggered(-1,-1) + ,m_aDropActionType( DA_SCROLLUP ) + ,m_nSelectLock(0) + ,m_nFormsSelected(0) + ,m_nControlsSelected(0) + ,m_nHiddenControls(0) + ,m_aTimerCounter( DROP_ACTION_TIMER_INITIAL_TICKS ) + ,m_bDragDataDirty(false) + ,m_bPrevSelectionMixed(false) + ,m_bRootSelected(false) + ,m_bInitialUpdate(true) + ,m_bKeyboardCut( false ) + { + SetHelpId( HID_FORM_NAVIGATOR ); + + SetNodeBitmaps( + Image(StockImage::Yes, RID_SVXBMP_COLLAPSEDNODE), + Image(StockImage::Yes, RID_SVXBMP_EXPANDEDNODE) + ); + + SetDragDropMode(DragDropMode::ALL); + EnableInplaceEditing( true ); + SetSelectionMode(SelectionMode::Multiple); + + m_pNavModel.reset(new NavigatorTreeModel()); + Clear(); + + StartListening( *m_pNavModel ); + + m_aDropActionTimer.SetInvokeHandler(LINK(this, NavigatorTree, OnDropActionTimer)); + + m_aSynchronizeTimer.SetInvokeHandler(LINK(this, NavigatorTree, OnSynchronizeTimer)); + SetSelectHdl(LINK(this, NavigatorTree, OnEntrySelDesel)); + SetDeselectHdl(LINK(this, NavigatorTree, OnEntrySelDesel)); + } + + + NavigatorTree::~NavigatorTree() + { + disposeOnce(); + } + + void NavigatorTree::dispose() + { + if( nEditEvent ) + Application::RemoveUserEvent( nEditEvent ); + + if (m_aSynchronizeTimer.IsActive()) + m_aSynchronizeTimer.Stop(); + + DBG_ASSERT(GetNavModel() != nullptr, "NavigatorTree::~NavigatorTree : unexpected : no ExplorerModel"); + EndListening( *m_pNavModel ); + Clear(); + m_pNavModel.reset(); + SvTreeListBox::dispose(); + } + + + void NavigatorTree::Clear() + { + m_pNavModel->Clear(); + } + + + void NavigatorTree::UpdateContent( FmFormShell* pFormShell ) + { + if (m_bInitialUpdate) + { + GrabFocus(); + m_bInitialUpdate = false; + } + + FmFormShell* pOldShell = GetNavModel()->GetFormShell(); + FmFormPage* pOldPage = GetNavModel()->GetFormPage(); + FmFormPage* pNewPage = pFormShell ? pFormShell->GetCurPage() : nullptr; + + if ((pOldShell != pFormShell) || (pOldPage != pNewPage)) + { + // new shell during editing + if (IsEditingActive()) + CancelTextEditing(); + + m_bDragDataDirty = true; // as a precaution, although I don't drag + } + GetNavModel()->UpdateContent( pFormShell ); + + // if there is a form, expand root + if (m_pRootEntry && !IsExpanded(m_pRootEntry)) + Expand(m_pRootEntry); + // if there is EXACTLY ONE form, expand it too + if (m_pRootEntry) + { + SvTreeListEntry* pFirst = FirstChild(m_pRootEntry); + if (pFirst && !pFirst->NextSibling()) + Expand(pFirst); + } + } + + + bool NavigatorTree::implAllowExchange( sal_Int8 _nAction, bool* _pHasNonHidden ) + { + SvTreeListEntry* pCurEntry = GetCurEntry(); + if (!pCurEntry) + return false; + + // Information for AcceptDrop and Execute Drop + CollectSelectionData(SDI_ALL); + if (m_arrCurrentSelection.empty()) + // nothing to do + return false; + + // check whether there are only hidden controls + // I may add a format to pCtrlExch + bool bHasNonHidden = std::any_of(m_arrCurrentSelection.begin(), m_arrCurrentSelection.end(), + [](const SvTreeListEntry* pEntry) { + FmEntryData* pCurrent = static_cast< FmEntryData* >( pEntry->GetUserData() ); + return !IsHiddenControl( pCurrent ); + }); + + if ( bHasNonHidden && ( 0 == ( _nAction & DND_ACTION_MOVE ) ) ) + // non-hidden controls need to be moved + return false; + + if ( _pHasNonHidden ) + *_pHasNonHidden = bHasNonHidden; + + return true; + } + + + bool NavigatorTree::implPrepareExchange( sal_Int8 _nAction ) + { + EndSelection(); + + bool bHasNonHidden = false; + if ( !implAllowExchange( _nAction, &bHasNonHidden ) ) + return false; + + m_aControlExchange.prepareDrag(); + m_aControlExchange->setFocusEntry( GetCurEntry() ); + + for (const auto& rpEntry : m_arrCurrentSelection) + m_aControlExchange->addSelectedEntry(rpEntry); + + m_aControlExchange->setFormsRoot( GetNavModel()->GetFormPage()->GetForms() ); + m_aControlExchange->buildPathFormat( this, m_pRootEntry ); + + if (!bHasNonHidden) + { + // create a sequence + Sequence< Reference< XInterface > > seqIFaces(m_arrCurrentSelection.size()); + Reference< XInterface >* pArray = seqIFaces.getArray(); + for (const auto& rpEntry : m_arrCurrentSelection) + { + *pArray = static_cast< FmEntryData* >( rpEntry->GetUserData() )->GetElement(); + ++pArray; + } + // and the new format + m_aControlExchange->addHiddenControlsFormat(seqIFaces); + } + + m_bDragDataDirty = false; + return true; + } + + + void NavigatorTree::StartDrag( sal_Int8 /*nAction*/, const ::Point& /*rPosPixel*/ ) + { + EndSelection(); + + if ( !implPrepareExchange( DND_ACTION_COPYMOVE ) ) + // nothing to do or something went wrong + return; + + // collected all possible formats for current situation, we can start now + m_aControlExchange.startDrag( DND_ACTION_COPYMOVE ); + } + + + void NavigatorTree::Command( const CommandEvent& rEvt ) + { + bool bHandled = false; + switch( rEvt.GetCommand() ) + { + case CommandEventId::ContextMenu: + { + // Position of click + ::Point ptWhere; + if (rEvt.IsMouseEvent()) + { + ptWhere = rEvt.GetMousePosPixel(); + SvTreeListEntry* ptClickedOn = GetEntry(ptWhere); + if (ptClickedOn == nullptr) + break; + if ( !IsSelected(ptClickedOn) ) + { + SelectAll(false); + Select(ptClickedOn); + SetCurEntry(ptClickedOn); + } + } + else + { + if (m_arrCurrentSelection.empty()) // only happens with context menu via keyboard + break; + + SvTreeListEntry* pCurrent = GetCurEntry(); + if (!pCurrent) + break; + ptWhere = GetEntryPosition(pCurrent); + } + + // update my selection data + CollectSelectionData(SDI_ALL); + + // if there is at least one no-root-entry and the root selected, I deselect root + if ( (m_arrCurrentSelection.size() > 1) && m_bRootSelected ) + { + Select( m_pRootEntry, false ); + SetCursor( *m_arrCurrentSelection.begin(), true); + } + bool bSingleSelection = (m_arrCurrentSelection.size() == 1); + + + DBG_ASSERT( (!m_arrCurrentSelection.empty()) || m_bRootSelected, "no entries selected" ); + // shouldn't happen, because I would have selected one during call to IsSelected, + // if there was none before + + + // create menu + FmFormShell* pFormShell = GetNavModel()->GetFormShell(); + FmFormModel* pFormModel = pFormShell ? pFormShell->GetFormModel() : nullptr; + if( pFormShell && pFormModel ) + { + VclBuilder aBuilder(nullptr, VclBuilderContainer::getUIRootDir(), "svx/ui/formnavimenu.ui", ""); + VclPtr<PopupMenu> aContextMenu(aBuilder.get_menu("menu")); + const sal_uInt16 nNewId = aContextMenu->GetItemId("new"); + PopupMenu* pSubMenuNew = aContextMenu->GetPopupMenu(nNewId); + + // menu 'New' only exists, if only the root or only one form is selected + aContextMenu->EnableItem(nNewId, bSingleSelection && (m_nFormsSelected || m_bRootSelected)); + + // 'New'\'Form' under the same terms + const sal_uInt16 nFormId = pSubMenuNew->GetItemId("form"); + pSubMenuNew->EnableItem(nFormId, bSingleSelection && (m_nFormsSelected || m_bRootSelected)); + pSubMenuNew->SetItemImage(nFormId, Image(StockImage::Yes, RID_SVXBMP_FORM)); + + // 'New'\'hidden...', if exactly one form is selected + const sal_uInt16 nHiddenId = pSubMenuNew->GetItemId("hidden"); + pSubMenuNew->EnableItem(nHiddenId, bSingleSelection && m_nFormsSelected); + pSubMenuNew->SetItemImage(nHiddenId, Image(StockImage::Yes, RID_SVXBMP_HIDDEN)); + + // 'Delete': everything which is not root can be removed + aContextMenu->EnableItem(aContextMenu->GetItemId("delete"), !m_bRootSelected); + + // 'Cut', 'Copy' and 'Paste' + aContextMenu->EnableItem(aContextMenu->GetItemId("cut"), !m_bRootSelected && implAllowExchange(DND_ACTION_MOVE)); + aContextMenu->EnableItem(aContextMenu->GetItemId("copy"), !m_bRootSelected && implAllowExchange(DND_ACTION_COPY)); + aContextMenu->EnableItem(aContextMenu->GetItemId("paste"), implAcceptPaste()); + + // TabDialog, if exactly one form + aContextMenu->EnableItem(aContextMenu->GetItemId("taborder"), bSingleSelection && m_nFormsSelected); + + const sal_uInt16 nBrowserId = aContextMenu->GetItemId("props"); + // in XML forms, we don't allow for the properties of a form + // #i36484# + if (pFormShell->GetImpl()->isEnhancedForm_Lock() && !m_nControlsSelected) + aContextMenu->RemoveItem(aContextMenu->GetItemPos(nBrowserId)); + + // if the property browser is already open, we don't allow for the properties, too + if (pFormShell->GetImpl()->IsPropBrwOpen_Lock()) + aContextMenu->RemoveItem(aContextMenu->GetItemPos(nBrowserId)); + // and finally, if there's a mixed selection of forms and controls, disable the entry, too + else + aContextMenu->EnableItem(nBrowserId, + (m_nControlsSelected && !m_nFormsSelected) || (!m_nControlsSelected && m_nFormsSelected) ); + + // rename, if one element and no root + aContextMenu->EnableItem(aContextMenu->GetItemId("rename"), bSingleSelection && !m_bRootSelected); + + // Readonly-entry is only for root + aContextMenu->EnableItem(aContextMenu->GetItemId("designmode"), m_bRootSelected); + // the same for automatic control focus + aContextMenu->EnableItem(aContextMenu->GetItemId("controlfocus"), m_bRootSelected); + + std::unique_ptr<VclBuilder> xBuilder; + VclPtr<PopupMenu> xConversionMenu; + // ConvertTo-Slots are enabled, if one control is selected + // the corresponding slot is disabled + const sal_Int16 nChangeId = aContextMenu->GetItemId("change"); + if (!m_bRootSelected && !m_nFormsSelected && (m_nControlsSelected == 1)) + { + xBuilder = FmXFormShell::GetConversionMenu_Lock(); + xConversionMenu = xBuilder->get_menu("menu"); + aContextMenu->SetPopupMenu(nChangeId, xConversionMenu); +#if OSL_DEBUG_LEVEL > 0 + FmControlData* pCurrent = static_cast<FmControlData*>((*m_arrCurrentSelection.begin())->GetUserData()); + OSL_ENSURE( pFormShell->GetImpl()->isSolelySelected_Lock( pCurrent->GetFormComponent() ), + "NavigatorTree::Command: inconsistency between the navigator selection, and the selection as the shell knows it!" ); +#endif + + pFormShell->GetImpl()->checkControlConversionSlotsForCurrentSelection_Lock(*aContextMenu->GetPopupMenu(nChangeId)); + } + else + aContextMenu->EnableItem(nChangeId, false ); + + // remove all disabled entries + aContextMenu->RemoveDisabledEntries(true, true); + + // set OpenReadOnly + + aContextMenu->CheckItem("designmode", pFormModel->GetOpenInDesignMode()); + aContextMenu->CheckItem("controlfocus", pFormModel->GetAutoControlFocus()); + + aContextMenu->Execute(this, ptWhere); + OString sIdent; + if (xConversionMenu) + sIdent = xConversionMenu->GetCurItemIdent(); + if (sIdent.isEmpty()) + sIdent = pSubMenuNew->GetCurItemIdent(); + if (sIdent.isEmpty()) + sIdent = aContextMenu->GetCurItemIdent(); + if (sIdent == "form") + { + OUString aStr(SvxResId(RID_STR_FORM)); + OUString aUndoStr = SvxResId(RID_STR_UNDO_CONTAINER_INSERT).replaceAll("#", aStr); + + pFormModel->BegUndo(aUndoStr); + // slot was only available, if there is only one selected entry, + // which is a root or a form + NewForm( *m_arrCurrentSelection.begin() ); + pFormModel->EndUndo(); + } + else if (sIdent == "hidden") + { + OUString aStr(SvxResId(RID_STR_CONTROL)); + OUString aUndoStr = SvxResId(RID_STR_UNDO_CONTAINER_INSERT).replaceAll("#", aStr); + + pFormModel->BegUndo(aUndoStr); + // slot was valid for (exactly) one selected form + OUString fControlName = FM_COMPONENT_HIDDEN; + NewControl( fControlName, *m_arrCurrentSelection.begin(), true ); + pFormModel->EndUndo(); + } + else if (sIdent == "cut") + doCut(); + else if (sIdent == "copy") + doCopy(); + else if (sIdent == "paste") + doPaste(); + else if (sIdent == "delete") + DeleteSelection(); + else if (sIdent == "taborder") + { + // this slot was effective for exactly one selected form + SvTreeListEntry* pSelectedForm = *m_arrCurrentSelection.begin(); + DBG_ASSERT( IsFormEntry(pSelectedForm), "NavigatorTree::Command: This entry must be a FormEntry." ); + + FmFormData* pFormData = static_cast<FmFormData*>(pSelectedForm->GetUserData()); + const Reference< XForm >& xForm( pFormData->GetFormIface()); + + Reference< XTabControllerModel > xTabController(xForm, UNO_QUERY); + if( !xTabController.is() ) + break; + GetNavModel()->GetFormShell()->GetImpl()->ExecuteTabOrderDialog_Lock(xTabController); + } + else if (sIdent == "props") + ShowSelectionProperties(true); + else if (sIdent == "rename") + { + // only allowed for one no-root-entry + EditEntry( *m_arrCurrentSelection.begin() ); + } + else if (sIdent == "designmode") + { + pFormModel->SetOpenInDesignMode( !pFormModel->GetOpenInDesignMode() ); + pFormShell->GetViewShell()->GetViewFrame()->GetBindings().Invalidate(SID_FM_OPEN_READONLY); + } + else if (sIdent == "controlfocus") + { + pFormModel->SetAutoControlFocus( !pFormModel->GetAutoControlFocus() ); + pFormShell->GetViewShell()->GetViewFrame()->GetBindings().Invalidate(SID_FM_AUTOCONTROLFOCUS); + } + else if (FmXFormShell::isControlConversionSlot(sIdent)) + { + FmControlData* pCurrent = static_cast<FmControlData*>((*m_arrCurrentSelection.begin())->GetUserData()); + if (pFormShell->GetImpl()->executeControlConversionSlot_Lock(pCurrent->GetFormComponent(), sIdent)) + ShowSelectionProperties(); + } + } + bHandled = true; + } + break; + default: break; + } + + if (!bHandled) + SvTreeListBox::Command( rEvt ); + } + + + SvTreeListEntry* NavigatorTree::FindEntry( FmEntryData* pEntryData ) + { + if( !pEntryData ) return nullptr; + SvTreeListEntry* pCurEntry = First(); + while( pCurEntry ) + { + FmEntryData* pCurEntryData = static_cast<FmEntryData*>(pCurEntry->GetUserData()); + if( pCurEntryData && pCurEntryData->IsEqualWithoutChildren(pEntryData) ) + return pCurEntry; + + pCurEntry = Next( pCurEntry ); + } + + return nullptr; + } + + + void NavigatorTree::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint ) + { + if( dynamic_cast<const FmNavRemovedHint*>(&rHint) ) + { + const FmNavRemovedHint* pRemovedHint = static_cast<const FmNavRemovedHint*>(&rHint); + FmEntryData* pEntryData = pRemovedHint->GetEntryData(); + Remove( pEntryData ); + } + + else if( dynamic_cast<const FmNavInsertedHint*>(&rHint) ) + { + const FmNavInsertedHint* pInsertedHint = static_cast<const FmNavInsertedHint*>(&rHint); + FmEntryData* pEntryData = pInsertedHint->GetEntryData(); + sal_uInt32 nRelPos = pInsertedHint->GetRelPos(); + Insert( pEntryData, nRelPos ); + } + + else if( dynamic_cast<const FmNavModelReplacedHint*>(&rHint) ) + { + FmEntryData* pData = static_cast<const FmNavModelReplacedHint*>(&rHint)->GetEntryData(); + SvTreeListEntry* pEntry = FindEntry( pData ); + if (pEntry) + { // reset image + SetCollapsedEntryBmp( pEntry, pData->GetNormalImage() ); + SetExpandedEntryBmp( pEntry, pData->GetNormalImage() ); + } + } + + else if( dynamic_cast<const FmNavNameChangedHint*>(&rHint) ) + { + const FmNavNameChangedHint* pNameChangedHint = static_cast<const FmNavNameChangedHint*>(&rHint); + SvTreeListEntry* pEntry = FindEntry( pNameChangedHint->GetEntryData() ); + SetEntryText( pEntry, pNameChangedHint->GetNewName() ); + } + + else if( dynamic_cast<const FmNavClearedHint*>(&rHint) ) + { + SvTreeListBox::Clear(); + + // default-entry "Forms" + Image aRootImage(StockImage::Yes, RID_SVXBMP_FORMS); + m_pRootEntry = InsertEntry( SvxResId(RID_STR_FORMS), aRootImage, aRootImage, + nullptr, false, 0 ); + } + else if (dynamic_cast<const FmNavRequestSelectHint*>(&rHint)) + { + FmNavRequestSelectHint* pershHint = const_cast<FmNavRequestSelectHint*>(static_cast<const FmNavRequestSelectHint*>(&rHint)); + FmEntryDataArray& arredToSelect = pershHint->GetItems(); + SynchronizeSelection(arredToSelect); + + if (pershHint->IsMixedSelection()) + // in this case I deselect all, although the view had a mixed selection + // during next selection, I must adapt the navigator to the view + m_bPrevSelectionMixed = true; + } + } + + + SvTreeListEntry* NavigatorTree::Insert( FmEntryData* pEntryData, sal_uLong nRelPos ) + { + + // insert current entry + SvTreeListEntry* pParentEntry = FindEntry( pEntryData->GetParent() ); + SvTreeListEntry* pNewEntry; + + if( !pParentEntry ) + pNewEntry = InsertEntry( pEntryData->GetText(), + pEntryData->GetNormalImage(), pEntryData->GetNormalImage(), + m_pRootEntry, false, nRelPos, pEntryData ); + + else + pNewEntry = InsertEntry( pEntryData->GetText(), + pEntryData->GetNormalImage(), pEntryData->GetNormalImage(), + pParentEntry, false, nRelPos, pEntryData ); + + + // If root-entry, expand root + if( !pParentEntry ) + Expand( m_pRootEntry ); + + + // insert children + FmEntryDataList* pChildList = pEntryData->GetChildList(); + size_t nChildCount = pChildList->size(); + for( size_t i = 0; i < nChildCount; i++ ) + { + FmEntryData* pChildData = pChildList->at( i ); + Insert( pChildData, TREELIST_APPEND ); + } + + return pNewEntry; + } + + + void NavigatorTree::Remove( FmEntryData* pEntryData ) + { + if( !pEntryData ) + return; + + // entry for the data + SvTreeListEntry* pEntry = FindEntry( pEntryData ); + if (!pEntry) + return; + + // delete entry from TreeListBox + // I'm not allowed, to treat the selection, which I trigger: + // select changes the MarkList of the view, if somebody else does this at the same time + // and removes a selection, we get a problem + // e.g. Group controls with open navigator + LockSelectionHandling(); + + // little problem: I remember the selected data, but if somebody deletes one of these entries, + // I get inconsistent... this would be bad + Select(pEntry, false); + + // selection can be modified during deletion, + // but because I disabled SelectionHandling, I have to do it later + sal_uLong nExpectedSelectionCount = GetSelectionCount(); + + GetModel()->Remove(pEntry); + + if (nExpectedSelectionCount != GetSelectionCount()) + SynchronizeSelection(); + + // by default I treat the selection of course + UnlockSelectionHandling(); + } + + + bool NavigatorTree::IsFormEntry( SvTreeListEntry const * pEntry ) + { + FmEntryData* pEntryData = static_cast<FmEntryData*>(pEntry->GetUserData()); + return !pEntryData || dynamic_cast<const FmFormData*>( pEntryData) != nullptr; + } + + + bool NavigatorTree::IsFormComponentEntry( SvTreeListEntry const * pEntry ) + { + FmEntryData* pEntryData = static_cast<FmEntryData*>(pEntry->GetUserData()); + return dynamic_cast<const FmControlData*>( pEntryData) != nullptr; + } + + + bool NavigatorTree::implAcceptPaste( ) + { + SvTreeListEntry* pFirstSelected = FirstSelected(); + if ( !pFirstSelected || NextSelected( pFirstSelected ) ) + // no selected entry, or at least two selected entries + return false; + + // get the clipboard + TransferableDataHelper aClipboardContent( TransferableDataHelper::CreateFromSystemClipboard( this ) ); + + sal_Int8 nAction = m_aControlExchange.isClipboardOwner() && doingKeyboardCut( ) ? DND_ACTION_MOVE : DND_ACTION_COPY; + return ( nAction == implAcceptDataTransfer( aClipboardContent.GetDataFlavorExVector(), nAction, pFirstSelected, false ) ); + } + + + sal_Int8 NavigatorTree::implAcceptDataTransfer( const DataFlavorExVector& _rFlavors, sal_Int8 _nAction, SvTreeListEntry* _pTargetEntry, bool _bDnD ) + { + // no target -> no drop + if (!_pTargetEntry) + return DND_ACTION_NONE; + + // format check + bool bHasDefControlFormat = OControlExchange::hasFieldExchangeFormat( _rFlavors ); + bool bHasControlPathFormat = OControlExchange::hasControlPathFormat( _rFlavors ); + bool bHasHiddenControlsFormat = OControlExchange::hasHiddenControlModelsFormat( _rFlavors ); + if (!bHasDefControlFormat && !bHasControlPathFormat && !bHasHiddenControlsFormat) + return DND_ACTION_NONE; + + bool bSelfSource = _bDnD ? m_aControlExchange.isDragSource() : m_aControlExchange.isClipboardOwner(); + + if ( bHasHiddenControlsFormat ) + { // bHasHiddenControlsFormat means that only hidden controls are part of the data + + // hidden controls can be copied to a form only + if ((_pTargetEntry == m_pRootEntry) || !IsFormEntry(_pTargetEntry)) + return DND_ACTION_NONE; + + return bSelfSource ? ( DND_ACTION_COPYMOVE & _nAction ) : DND_ACTION_COPY; + } + + if ( !bSelfSource ) + { + // DnD or CnP crossing navigator boundaries + // The main problem here is that the current API does not allow us to sneak into the content which + // is to be inserted. So we have to allow it for the moment, but maybe reject later on (in the real drop). + + // TODO: this smart behaviour later on ... at the moment, we disallow data transfer crossing navigator + // boundaries. + + return DND_ACTION_NONE; + } + + DBG_ASSERT( _bDnD ? m_aControlExchange.isDragSource() : m_aControlExchange.isClipboardOwner(), + "NavigatorTree::implAcceptDataTransfer: here only with source=dest!" ); + // somebody changed the logic of this method ... + + // from here on, I can work with m_aControlExchange instead of _rData! + + bool bForeignCollection = m_aControlExchange->getFormsRoot().get() != GetNavModel()->GetFormPage()->GetForms().get(); + if ( bForeignCollection ) + { + // crossing shell/page boundaries, we can exchange hidden controls only + // But if we survived the checks above, we do not have hidden controls. + // -> no data transfer + DBG_ASSERT( !bHasHiddenControlsFormat, "NavigatorTree::implAcceptDataTransfer: still hidden controls format!" ); + // somebody changed the logic of this method ... + + return DND_ACTION_COPY; + } + + if (DND_ACTION_MOVE != _nAction) // 'normal' controls within a shell are moved only (never copied) + return DND_ACTION_NONE; + + if ( m_bDragDataDirty || !bHasDefControlFormat ) + { + if (!bHasControlPathFormat) + // I am in the shell/page, which has the controls, but I have no format, + // which survived the shell change (SVX_FM_CONTROLS_AS_PATH) + return DND_ACTION_NONE; + + // I must recreate the list of the ExchangeObjects, because the shell was changed during dragging + // (there are SvLBoxEntries in it, and we lost them during change) + m_aControlExchange->buildListFromPath(this, m_pRootEntry); + m_bDragDataDirty = false; + } + + // List of dropped entries from DragServer + const ListBoxEntrySet& aDropped = m_aControlExchange->selected(); + DBG_ASSERT(!aDropped.empty(), "NavigatorTree::implAcceptDataTransfer: no entries !"); + + bool bDropTargetIsComponent = IsFormComponentEntry( _pTargetEntry ); + //SvTreeListEntry* pDropTargetParent = GetParent( _pTargetEntry ); + + // conditions to disallow the drop + // 0) the root entry is part of the list (can't DnD the root!) + // 1) one of the dragged entries is to be dropped onto its own parent + // 2) - " - is to be dropped onto itself + // 3) - " - is a Form and to be dropped onto one of its descendants + // 4) one of the entries is a control and to be dropped onto the root + // 5) a control or form will be dropped onto a control which is _not_ a sibling (dropping onto a sibling + // means moving the control) + + // collect the ancestors of the drop target (speeds up 3) + SvLBoxEntrySortedArray arrDropAnchestors; + SvTreeListEntry* pLoop = _pTargetEntry; + while (pLoop) + { + arrDropAnchestors.insert(pLoop); + pLoop = GetParent(pLoop); + } + + for (SvTreeListEntry* pCurrent : aDropped) + { + SvTreeListEntry* pCurrentParent = GetParent(pCurrent); + + // test for 0) + if (pCurrent == m_pRootEntry) + return DND_ACTION_NONE; + + // test for 1) + if ( _pTargetEntry == pCurrentParent ) + return DND_ACTION_NONE; + + // test for 2) + if (pCurrent == _pTargetEntry) + return DND_ACTION_NONE; + + // test for 5) + // if ( bDropTargetIsComponent && (pDropTargetParent != pCurrentParent) ) + if ( bDropTargetIsComponent ) // TODO : the line above can be inserted, if ExecuteDrop can handle inversion + return DND_ACTION_NONE; + + // test for 3) + if ( IsFormEntry(pCurrent) ) + { + if ( arrDropAnchestors.find(pCurrent) != arrDropAnchestors.end() ) + return DND_ACTION_NONE; + } else if ( IsFormComponentEntry(pCurrent) ) + { + // test for 4) + if (_pTargetEntry == m_pRootEntry) + return DND_ACTION_NONE; + } + } + + return DND_ACTION_MOVE; + } + + + sal_Int8 NavigatorTree::AcceptDrop( const AcceptDropEvent& rEvt ) + { + ::Point aDropPos = rEvt.maPosPixel; + + // first handle possible DropActions (Scroll and swing open) + if (rEvt.mbLeaving) + { + if (m_aDropActionTimer.IsActive()) + m_aDropActionTimer.Stop(); + } else + { + bool bNeedTrigger = false; + // on the first entry ? + if ((aDropPos.Y() >= 0) && (aDropPos.Y() < GetEntryHeight())) + { + m_aDropActionType = DA_SCROLLUP; + bNeedTrigger = true; + } else + // on the last one (respectively the area, an entry would tale, if it flush with the bottom ? + if ((aDropPos.Y() < GetSizePixel().Height()) && (aDropPos.Y() >= GetSizePixel().Height() - GetEntryHeight())) + { + m_aDropActionType = DA_SCROLLDOWN; + bNeedTrigger = true; + } else + { // on an entry with children, not swang open + SvTreeListEntry* pDroppedOn = GetEntry(aDropPos); + if (pDroppedOn && (GetChildCount(pDroppedOn) > 0) && !IsExpanded(pDroppedOn)) + { + // -> swing open + m_aDropActionType = DA_EXPANDNODE; + bNeedTrigger = true; + } + } + + if (bNeedTrigger && (m_aTimerTriggered != aDropPos)) + { + // restart counting + m_aTimerCounter = DROP_ACTION_TIMER_INITIAL_TICKS; + // remember pos, because I get AcceptDrops, although mouse hasn't moved + m_aTimerTriggered = aDropPos; + // start Timer + if (!m_aDropActionTimer.IsActive()) // exist Timer? + { + m_aDropActionTimer.SetTimeout(DROP_ACTION_TIMER_TICK_BASE); + m_aDropActionTimer.Start(); + } + } else if (!bNeedTrigger) + m_aDropActionTimer.Stop(); + } + + return implAcceptDataTransfer( GetDataFlavorExVector(), rEvt.mnAction, GetEntry( aDropPos ), true ); + } + + + sal_Int8 NavigatorTree::implExecuteDataTransfer( const OControlTransferData& _rData, sal_Int8 _nAction, const ::Point& _rDropPos, bool _bDnD ) + { + return implExecuteDataTransfer( _rData, _nAction, GetEntry( _rDropPos ), _bDnD ); + } + + + sal_Int8 NavigatorTree::implExecuteDataTransfer( const OControlTransferData& _rData, sal_Int8 _nAction, SvTreeListEntry* _pTargetEntry, bool _bDnD ) + { + const DataFlavorExVector& rDataFlavors = _rData.GetDataFlavorExVector(); + + if ( DND_ACTION_NONE == implAcceptDataTransfer( rDataFlavors, _nAction, _pTargetEntry, _bDnD ) ) + // under some platforms, it may happen that ExecuteDrop is called though AcceptDrop returned DND_ACTION_NONE + return DND_ACTION_NONE; + + // would be bad, if we scroll after drop + if (m_aDropActionTimer.IsActive()) + m_aDropActionTimer.Stop(); + + if (!_pTargetEntry) + // no target -> no drop + return DND_ACTION_NONE; + + // format checks +#ifdef DBG_UTIL + bool bHasHiddenControlsFormat = OControlExchange::hasHiddenControlModelsFormat( rDataFlavors ); + bool bForeignCollection = _rData.getFormsRoot().get() != GetNavModel()->GetFormPage()->GetForms().get(); + DBG_ASSERT(!bForeignCollection || bHasHiddenControlsFormat, "NavigatorTree::implExecuteDataTransfer: invalid format (AcceptDrop shouldn't have let this pass) !"); + DBG_ASSERT(bForeignCollection || !m_bDragDataDirty, "NavigatorTree::implExecuteDataTransfer: invalid state (shell changed since last exchange resync) !"); + // this should be done in AcceptDrop: the list of controls is created in _rData + // and m_bDragDataDirty is reset +#endif + + if ( DND_ACTION_COPY == _nAction ) + { // bHasHiddenControlsFormat means that only hidden controls are part of the data +#ifdef DBG_UTIL + DBG_ASSERT( bHasHiddenControlsFormat, "NavigatorTree::implExecuteDataTransfer: copy allowed for hidden controls only!" ); +#endif + DBG_ASSERT( _pTargetEntry && ( _pTargetEntry != m_pRootEntry ) && IsFormEntry( _pTargetEntry ), + "NavigatorTree::implExecuteDataTransfer: should not be here!" ); + // implAcceptDataTransfer should have caught both cases + +#ifdef DBG_UTIL + DBG_ASSERT(bHasHiddenControlsFormat, "NavigatorTree::implExecuteDataTransfer: only copying of hidden controls is supported !"); + // should be caught by AcceptDrop +#endif + + // because i want to select all targets (and only them) + SelectAll(false); + + const Sequence< Reference< XInterface > >& aControls = _rData.hiddenControls(); + sal_Int32 nCount = aControls.getLength(); + const Reference< XInterface >* pControls = aControls.getConstArray(); + + FmFormShell* pFormShell = GetNavModel()->GetFormShell(); + FmFormModel* pFormModel = pFormShell ? pFormShell->GetFormModel() : nullptr; + + // within undo + if (pFormModel) + { + OUString aStr(SvxResId(RID_STR_CONTROL)); + OUString aUndoStr = SvxResId(RID_STR_UNDO_CONTAINER_INSERT).replaceAll("#", aStr); + pFormModel->BegUndo(aUndoStr); + } + + // copy controls + for (sal_Int32 i=0; i<nCount; ++i) + { + // create new control + OUString fControlName = FM_COMPONENT_HIDDEN; + FmControlData* pNewControlData = NewControl( fControlName, _pTargetEntry, false); + Reference< XPropertySet > xNewPropSet( pNewControlData->GetPropertySet() ); + + // copy properties form old control to new one + Reference< XPropertySet > xCurrent(pControls[i], UNO_QUERY); +#if (OSL_DEBUG_LEVEL > 0) + // check whether it is a hidden control + sal_Int16 nClassId = ::comphelper::getINT16(xCurrent->getPropertyValue(FM_PROP_CLASSID)); + OSL_ENSURE(nClassId == FormComponentType::HIDDENCONTROL, "NavigatorTree::implExecuteDataTransfer: invalid control in drop list !"); + // if SVX_FM_HIDDEN_CONTROLS-format exists, the sequence + // should only contain hidden controls +#endif // (OSL_DEBUG_LEVEL > 0) + Reference< XPropertySetInfo > xPropInfo( xCurrent->getPropertySetInfo()); + const Sequence< Property> seqAllCurrentProps = xPropInfo->getProperties(); + for (Property const & currentProp : seqAllCurrentProps) + { + if (((currentProp.Attributes & PropertyAttribute::READONLY) == 0) && (currentProp.Name != FM_PROP_NAME)) + { // (read-only attribs aren't set, ditto name, + // NewControl defined it uniquely + xNewPropSet->setPropertyValue(currentProp.Name, xCurrent->getPropertyValue(currentProp.Name)); + } + } + + SvTreeListEntry* pToSelect = FindEntry(pNewControlData); + Select(pToSelect); + if (i == 0) + SetCurEntry(pToSelect); + } + + if (pFormModel) + pFormModel->EndUndo(); + + return _nAction; + } + + if ( !OControlExchange::hasFieldExchangeFormat( _rData.GetDataFlavorExVector() ) ) + { + // can't do anything without the internal format here ... usually happens when doing DnD or CnP + // over navigator boundaries + return DND_ACTION_NONE; + } + + // some data for the target + bool bDropTargetIsForm = IsFormEntry(_pTargetEntry); + FmFormData* pTargetData = bDropTargetIsForm ? static_cast<FmFormData*>(_pTargetEntry->GetUserData()) : nullptr; + + DBG_ASSERT( DND_ACTION_COPY != _nAction, "NavigatorTree::implExecuteDataTransfer: somebody changed the logics!" ); + + // list of dragged entries + const ListBoxEntrySet aDropped = _rData.selected(); + DBG_ASSERT(!aDropped.empty(), "NavigatorTree::implExecuteDataTransfer: no entries!"); + + // shell and model + FmFormShell* pFormShell = GetNavModel()->GetFormShell(); + FmFormModel* pFormModel = pFormShell ? pFormShell->GetFormModel() : nullptr; + if (!pFormModel) + return DND_ACTION_NONE; + + // for Undo + const bool bUndo = pFormModel->IsUndoEnabled(); + + if( bUndo ) + { + OUString strUndoDescription(SvxResId(RID_STR_UNDO_CONTAINER_REPLACE)); + pFormModel->BegUndo(strUndoDescription); + } + + // remove selection before adding an entry, so the mark doesn't flicker + // -> lock action of selection + LockSelectionHandling(); + + // go through all dropped entries + for ( ListBoxEntrySet::const_iterator dropped = aDropped.begin(); + dropped != aDropped.end(); + ++dropped + ) + { + // some data of the current element + SvTreeListEntry* pCurrent = *dropped; + DBG_ASSERT(pCurrent != nullptr, "NavigatorTree::implExecuteDataTransfer: invalid entry"); + DBG_ASSERT(GetParent(pCurrent) != nullptr, "NavigatorTree::implExecuteDataTransfer: invalid entry"); + // don't drag root + + FmEntryData* pCurrentUserData = static_cast<FmEntryData*>(pCurrent->GetUserData()); + + Reference< XChild > xCurrentChild = pCurrentUserData->GetChildIFace(); + Reference< XIndexContainer > xContainer(xCurrentChild->getParent(), UNO_QUERY); + + FmFormData* pCurrentParentUserData = static_cast<FmFormData*>(pCurrentUserData->GetParent()); + DBG_ASSERT(pCurrentParentUserData == nullptr || dynamic_cast<const FmFormData*>(pCurrentUserData->GetParent()) != nullptr, "NavigatorTree::implExecuteDataTransfer: invalid parent"); + + // remove from parent + if (pCurrentParentUserData) + pCurrentParentUserData->GetChildList()->removeNoDelete( pCurrentUserData ); + else + GetNavModel()->GetRootList()->removeNoDelete( pCurrentUserData ); + + // remove from container + sal_Int32 nIndex = getElementPos(xContainer, xCurrentChild); + GetNavModel()->m_pPropChangeList->Lock(); + // UndoAction for removal + if ( bUndo && GetNavModel()->m_pPropChangeList->CanUndo()) + { + pFormModel->AddUndo(std::make_unique<FmUndoContainerAction>(*pFormModel, FmUndoContainerAction::Removed, + xContainer, xCurrentChild, nIndex)); + } + else if( !GetNavModel()->m_pPropChangeList->CanUndo() ) + { + FmUndoContainerAction::DisposeElement( xCurrentChild ); + } + + // copy events + Reference< XEventAttacherManager > xManager(xContainer, UNO_QUERY); + Sequence< ScriptEventDescriptor > aEvts; + + if (xManager.is() && nIndex >= 0) + aEvts = xManager->getScriptEvents(nIndex); + xContainer->removeByIndex(nIndex); + + // remove selection + Select(pCurrent, false); + // and delete it + Remove(pCurrentUserData); + + // position in DropParents, where to insert dropped entries + if (pTargetData) + xContainer.set(pTargetData->GetElement(), UNO_QUERY); + else + xContainer = GetNavModel()->GetForms(); + + // always insert at the end + nIndex = xContainer->getCount(); + + // UndoAction for insertion + if ( bUndo && GetNavModel()->m_pPropChangeList->CanUndo()) + pFormModel->AddUndo(std::make_unique<FmUndoContainerAction>(*pFormModel, FmUndoContainerAction::Inserted, + xContainer, xCurrentChild, nIndex)); + + // insert in new container + if (pTargetData) + { + // insert in a form needs a FormComponent + xContainer->insertByIndex( nIndex, + makeAny( Reference< XFormComponent >( xCurrentChild, UNO_QUERY ) ) ); + } + else + { + xContainer->insertByIndex( nIndex, + makeAny( Reference< XForm >( xCurrentChild, UNO_QUERY ) ) ); + } + + if (aEvts.hasElements()) + { + xManager.set(xContainer, UNO_QUERY); + if (xManager.is()) + xManager->registerScriptEvents(nIndex, aEvts); + } + + GetNavModel()->m_pPropChangeList->UnLock(); + + // give an entry the new parent + pCurrentUserData->SetParent(pTargetData); + + // give parent the new child + if (pTargetData) + pTargetData->GetChildList()->insert( std::unique_ptr<FmEntryData>(pCurrentUserData), nIndex ); + else + GetNavModel()->GetRootList()->insert( std::unique_ptr<FmEntryData>(pCurrentUserData), nIndex ); + + // announce to myself and reselect + SvTreeListEntry* pNew = Insert( pCurrentUserData, nIndex ); + if ( ( aDropped.begin() == dropped ) && pNew ) + { + SvTreeListEntry* pParent = GetParent( pNew ); + if ( pParent ) + Expand( pParent ); + } + } + + UnlockSelectionHandling(); + + if( bUndo ) + pFormModel->EndUndo(); + + // During the move, the markings of the underlying view did not change (because the view is not affected by the logical + // hierarchy of the form/control models. But my selection changed - which means I have to adjust it according to the + // view marks, again. + SynchronizeSelection(); + + // in addition, with the move of controls such things as "the current form" may have changed - force the shell + // to update itself accordingly + if( pFormShell && pFormShell->GetImpl() && pFormShell->GetFormView() ) + pFormShell->GetImpl()->DetermineSelection_Lock( pFormShell->GetFormView()->GetMarkedObjectList() ); + + if ( m_aControlExchange.isClipboardOwner() && ( DND_ACTION_MOVE == _nAction ) ) + m_aControlExchange->clear(); + + return _nAction; + } + + + sal_Int8 NavigatorTree::ExecuteDrop( const ExecuteDropEvent& rEvt ) + { + sal_Int8 nResult( DND_ACTION_NONE ); + + if ( m_aControlExchange.isDragSource() ) + nResult = implExecuteDataTransfer( *m_aControlExchange, rEvt.mnAction, rEvt.maPosPixel, true ); + else + { + OControlTransferData aDroppedData( rEvt.maDropEvent.Transferable ); + nResult = implExecuteDataTransfer( aDroppedData, rEvt.mnAction, rEvt.maPosPixel, true ); + } + + return nResult; + } + + + void NavigatorTree::doPaste() + { + try + { + if ( m_aControlExchange.isClipboardOwner() ) + { + implExecuteDataTransfer( *m_aControlExchange, doingKeyboardCut( ) ? DND_ACTION_MOVE : DND_ACTION_COPY, FirstSelected(), false ); + } + else + { + // the clipboard content + Reference< XClipboard > xClipboard( GetClipboard() ); + Reference< XTransferable > xTransferable; + if ( xClipboard.is() ) + xTransferable = xClipboard->getContents(); + + OControlTransferData aClipboardContent( xTransferable ); + implExecuteDataTransfer( aClipboardContent, DND_ACTION_COPY, FirstSelected(), false ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "NavigatorTree::doPaste" ); + } + } + + + void NavigatorTree::doCopy() + { + if ( implPrepareExchange( DND_ACTION_COPY ) ) + { + m_aControlExchange.setClipboardListener( LINK( this, NavigatorTree, OnClipboardAction ) ); + m_aControlExchange.copyToClipboard( ); + } + } + + + void NavigatorTree::ModelHasRemoved( SvTreeListEntry* _pEntry ) + { + SvTreeListEntry* pTypedEntry = _pEntry; + if ( doingKeyboardCut() ) + m_aCutEntries.erase( pTypedEntry ); + + if ( m_aControlExchange.isDataExchangeActive() ) + { + if ( 0 == m_aControlExchange->onEntryRemoved( pTypedEntry ) ) + { + // last of the entries which we put into the clipboard has been deleted from the tree. + // Give up the clipboard ownership. + m_aControlExchange.clear(); + } + } + } + + + void NavigatorTree::doCut() + { + if ( implPrepareExchange( DND_ACTION_MOVE ) ) + { + m_aControlExchange.setClipboardListener( LINK( this, NavigatorTree, OnClipboardAction ) ); + m_aControlExchange.copyToClipboard( ); + m_bKeyboardCut = true; + + // mark all the entries we just "cut" into the clipboard as "nearly moved" + for ( SvTreeListEntry* pEntry : m_arrCurrentSelection ) + { + if ( pEntry ) + { + m_aCutEntries.insert( pEntry ); + pEntry->SetFlags( pEntry->GetFlags() | SvTLEntryFlags::SEMITRANSPARENT ); + InvalidateEntry( pEntry ); + } + } + } + } + + + void NavigatorTree::KeyInput(const ::KeyEvent& rKEvt) + { + const vcl::KeyCode& rCode = rKEvt.GetKeyCode(); + + // delete? + if (rKEvt.GetKeyCode().GetCode() == KEY_DELETE && !rKEvt.GetKeyCode().GetModifier()) + { + DeleteSelection(); + return; + } + + // copy'n'paste? + switch ( rCode.GetFunction() ) + { + case KeyFuncType::CUT: + doCut(); + break; + + case KeyFuncType::PASTE: + if ( implAcceptPaste() ) + doPaste(); + break; + + case KeyFuncType::COPY: + doCopy(); + break; + + default: + break; + } + + SvTreeListBox::KeyInput(rKEvt); + } + + + bool NavigatorTree::EditingEntry( SvTreeListEntry* pEntry, ::Selection& rSelection ) + { + if (!SvTreeListBox::EditingEntry( pEntry, rSelection )) + return false; + + return (pEntry && (pEntry->GetUserData() != nullptr)); + // root, which isn't allowed to be renamed, has UserData=NULL + } + + + void NavigatorTree::NewForm( SvTreeListEntry const * pParentEntry ) + { + + // get ParentFormData + if( !IsFormEntry(pParentEntry) ) + return; + + FmFormData* pParentFormData = static_cast<FmFormData*>(pParentEntry->GetUserData()); + + + // create new form + Reference<XComponentContext> xContext = comphelper::getProcessComponentContext(); + Reference< XForm > xNewForm(xContext->getServiceManager()->createInstanceWithContext(FM_SUN_COMPONENT_FORM, xContext), UNO_QUERY); + if (!xNewForm.is()) + return; + + Reference< XPropertySet > xPropertySet(xNewForm, UNO_QUERY); + if (!xPropertySet.is()) + return; + + FmFormData* pNewFormData = new FmFormData(xNewForm, pParentFormData); + + + // set name + OUString aName = GenerateName(pNewFormData); + pNewFormData->SetText(aName); + + try + { + xPropertySet->setPropertyValue( FM_PROP_NAME, makeAny(aName) ); + // a form should always have the command type table as default + xPropertySet->setPropertyValue( FM_PROP_COMMANDTYPE, makeAny(sal_Int32(CommandType::TABLE))); + } + catch ( const Exception& ) + { + OSL_FAIL("NavigatorTree::NewForm : could not set essential properties!"); + } + + + // insert form + GetNavModel()->Insert(pNewFormData, SAL_MAX_UINT32, true); + + + // set new form as active + FmFormShell* pFormShell = GetNavModel()->GetFormShell(); + if( pFormShell ) + { + InterfaceBag aSelection; + aSelection.insert( Reference<XInterface>( xNewForm, UNO_QUERY ) ); + pFormShell->GetImpl()->setCurrentSelection_Lock(aSelection); + + pFormShell->GetViewShell()->GetViewFrame()->GetBindings().Invalidate(SID_FM_PROPERTIES, true, true); + } + GetNavModel()->SetModified(); + + + // switch to EditMode + SvTreeListEntry* pNewEntry = FindEntry( pNewFormData ); + EditEntry( pNewEntry ); + } + + + FmControlData* NavigatorTree::NewControl( const OUString& rServiceName, SvTreeListEntry const * pParentEntry, bool bEditName ) + { + + // get ParentForm + if (!GetNavModel()->GetFormShell()) + return nullptr; + if (!IsFormEntry(pParentEntry)) + return nullptr; + + FmFormData* pParentFormData = static_cast<FmFormData*>(pParentEntry->GetUserData()); + Reference< XForm > xParentForm( pParentFormData->GetFormIface()); + + + // create new component + Reference<XComponentContext> xContext = comphelper::getProcessComponentContext(); + Reference<XFormComponent> xNewComponent( xContext->getServiceManager()->createInstanceWithContext(rServiceName, xContext), UNO_QUERY); + if (!xNewComponent.is()) + return nullptr; + + FmControlData* pNewFormControlData = new FmControlData(xNewComponent, pParentFormData); + + + // set name + OUString sName = FmFormPageImpl::setUniqueName( xNewComponent, xParentForm ); + + pNewFormControlData->SetText( sName ); + + + // insert FormComponent + GetNavModel()->Insert(pNewFormControlData, SAL_MAX_UINT32, true); + GetNavModel()->SetModified(); + + if (bEditName) + { + + // switch to EditMode + SvTreeListEntry* pNewEntry = FindEntry( pNewFormControlData ); + Select( pNewEntry ); + EditEntry( pNewEntry ); + } + + return pNewFormControlData; + } + + + OUString NavigatorTree::GenerateName( FmEntryData const * pEntryData ) + { + const sal_uInt16 nMaxCount = 99; + OUString aNewName; + + + // create base name + OUString aBaseName; + if( dynamic_cast<const FmFormData*>( pEntryData) != nullptr ) + aBaseName = SvxResId( RID_STR_STDFORMNAME ); + else if( dynamic_cast<const FmControlData*>( pEntryData) != nullptr ) + aBaseName = SvxResId( RID_STR_CONTROL ); + + + // create new name + FmFormData* pFormParentData = static_cast<FmFormData*>(pEntryData->GetParent()); + + for( sal_Int32 i=0; i<nMaxCount; i++ ) + { + aNewName = aBaseName; + if( i>0 ) + { + aNewName += " " + OUString::number(i); + } + + if( GetNavModel()->FindData(aNewName, pFormParentData,false) == nullptr ) + break; + } + + return aNewName; + } + + + bool NavigatorTree::EditedEntry( SvTreeListEntry* pEntry, const OUString& rNewText ) + { + if (EditingCanceled()) + return true; + + GrabFocus(); + FmEntryData* pEntryData = static_cast<FmEntryData*>(pEntry->GetUserData()); + bool bRes = NavigatorTreeModel::Rename( pEntryData, rNewText); + if( !bRes ) + { + m_pEditEntry = pEntry; + nEditEvent = Application::PostUserEvent( LINK(this, NavigatorTree, OnEdit), nullptr, true ); + } else + SetCursor(pEntry, true); + + return bRes; + } + + + IMPL_LINK_NOARG(NavigatorTree, OnEdit, void*, void) + { + nEditEvent = nullptr; + EditEntry( m_pEditEntry ); + m_pEditEntry = nullptr; + } + + + IMPL_LINK_NOARG(NavigatorTree, OnDropActionTimer, Timer *, void) + { + if (--m_aTimerCounter > 0) + return; + + switch ( m_aDropActionType ) + { + case DA_EXPANDNODE: + { + SvTreeListEntry* pToExpand = GetEntry(m_aTimerTriggered); + if (pToExpand && (GetChildCount(pToExpand) > 0) && !IsExpanded(pToExpand)) + // normally, we have to test, if the node is expanded, + // but there is no method for this either in base class nor the model + // the base class should tolerate it anyway + Expand(pToExpand); + + // After expansion there is nothing to do like after scrolling + m_aDropActionTimer.Stop(); + } + break; + + case DA_SCROLLUP : + ScrollOutputArea( 1 ); + m_aTimerCounter = DROP_ACTION_TIMER_SCROLL_TICKS; + break; + + case DA_SCROLLDOWN : + ScrollOutputArea( -1 ); + m_aTimerCounter = DROP_ACTION_TIMER_SCROLL_TICKS; + break; + + } + } + + + IMPL_LINK_NOARG(NavigatorTree, OnEntrySelDesel, SvTreeListBox*, void) + { + m_sdiState = SDI_DIRTY; + + if (IsSelectionHandlingLocked()) + return; + + if (m_aSynchronizeTimer.IsActive()) + m_aSynchronizeTimer.Stop(); + + m_aSynchronizeTimer.SetTimeout(EXPLORER_SYNC_DELAY); + m_aSynchronizeTimer.Start(); + } + + + IMPL_LINK_NOARG(NavigatorTree, OnSynchronizeTimer, Timer *, void) + { + SynchronizeMarkList(); + } + + + IMPL_LINK_NOARG(NavigatorTree, OnClipboardAction, OLocalExchange&, void) + { + if ( !m_aControlExchange.isClipboardOwner() ) + { + if ( doingKeyboardCut() ) + { + for (SvTreeListEntry* pEntry : m_aCutEntries) + { + if ( !pEntry ) + continue; + + pEntry->SetFlags( pEntry->GetFlags() & ~SvTLEntryFlags::SEMITRANSPARENT ); + InvalidateEntry( pEntry ); + } + ListBoxEntrySet aEmpty; + m_aCutEntries.swap( aEmpty ); + + m_bKeyboardCut = false; + } + } + } + + + void NavigatorTree::ShowSelectionProperties(bool bForce) + { + // at first i need the FormShell + FmFormShell* pFormShell = GetNavModel()->GetFormShell(); + if (!pFormShell) + // no shell -> impossible to set curObject -> leave + return; + + CollectSelectionData(SDI_ALL); + SAL_WARN_IF(static_cast<size_t>(m_nFormsSelected + m_nControlsSelected + + (m_bRootSelected ? 1 : 0)) != m_arrCurrentSelection.size(), + "svx.form", + "NavigatorTree::ShowSelectionProperties : selection meta data invalid !"); + + + InterfaceBag aSelection; + bool bSetSelectionAsMarkList = false; + + if (m_bRootSelected) + ; // no properties for the root, neither for single nor for multi selection + else if ( m_nFormsSelected + m_nControlsSelected == 0 ) // none of the two should be less 0 + ; // no selection -> no properties + else if ( m_nFormsSelected * m_nControlsSelected != 0 ) + ; // mixed selection -> no properties + else + { // either only forms, or only controls are selected + if (m_arrCurrentSelection.size() == 1) + { + if (m_nFormsSelected > 0) + { // exactly one form is selected + FmFormData* pFormData = static_cast<FmFormData*>((*m_arrCurrentSelection.begin())->GetUserData()); + aSelection.insert( Reference< XInterface >( pFormData->GetFormIface(), UNO_QUERY ) ); + } + else + { // exactly one control is selected (whatever hidden or normal) + FmEntryData* pEntryData = static_cast<FmEntryData*>((*m_arrCurrentSelection.begin())->GetUserData()); + + aSelection.insert( Reference< XInterface >( pEntryData->GetElement(), UNO_QUERY ) ); + } + } + else + { // it's a MultiSelection, so we must build a MultiSet + if (m_nFormsSelected > 0) + { // ... only forms + // first of all collect PropertySet-Interfaces of the forms + SvLBoxEntrySortedArray::const_iterator it = m_arrCurrentSelection.begin(); + for ( sal_Int32 i = 0; i < m_nFormsSelected; ++i ) + { + FmFormData* pFormData = static_cast<FmFormData*>((*it)->GetUserData()); + aSelection.insert( pFormData->GetPropertySet().get() ); + ++it; + } + } + else + { // ... only controls + if (m_nHiddenControls == m_nControlsSelected) + { // a MultiSet for properties of hidden controls + SvLBoxEntrySortedArray::const_iterator it = m_arrCurrentSelection.begin(); + for ( sal_Int32 i = 0; i < m_nHiddenControls; ++i ) + { + FmEntryData* pEntryData = static_cast<FmEntryData*>((*it)->GetUserData()); + aSelection.insert( pEntryData->GetPropertySet().get() ); + ++it; + } + } + else if (m_nHiddenControls == 0) + { // only normal controls + bSetSelectionAsMarkList = true; + } + } + } + + } + + // and now my form and my SelObject + if ( bSetSelectionAsMarkList ) + pFormShell->GetImpl()->setCurrentSelectionFromMark_Lock(pFormShell->GetFormView()->GetMarkedObjectList()); + else + pFormShell->GetImpl()->setCurrentSelection_Lock(aSelection); + + if (pFormShell->GetImpl()->IsPropBrwOpen_Lock() || bForce) + { + // and now deliver all to the PropertyBrowser + pFormShell->GetViewShell()->GetViewFrame()->GetDispatcher()->Execute( SID_FM_SHOW_PROPERTY_BROWSER, SfxCallMode::ASYNCHRON ); + } + } + + + void NavigatorTree::DeleteSelection() + { + // of course, i can't delete root + bool bRootSelected = IsSelected(m_pRootEntry); + sal_uIntPtr nSelectedEntries = GetSelectionCount(); + if (bRootSelected && (nSelectedEntries > 1)) // root and other elements ? + Select(m_pRootEntry, false); // yes -> remove root from selection + + if ((nSelectedEntries == 0) || bRootSelected) // still root ? + return; // -> only selected element -> leave + + DBG_ASSERT(!m_bPrevSelectionMixed, "NavigatorTree::DeleteSelection() : delete permitted if mark and selection are inconsistent"); + + // i need the FormModel later + FmFormShell* pFormShell = GetNavModel()->GetFormShell(); + if (!pFormShell) + return; + FmFormModel* pFormModel = pFormShell->GetFormModel(); + if (!pFormModel) + return; + + // now I have to safeguard the DeleteList: if you delete a form and a dependent element + // - in this order - than the SvLBoxEntryPtr of the dependent element is already invalid, + // when it should be deleted... you have to prohibit this GPF, that of course would happen, + // so I take the 'normalized' list + CollectSelectionData( SDI_NORMALIZED ); + + // see below for why we need this mapping from models to shapes + FmFormView* pFormView = pFormShell->GetFormView(); + SdrPageView* pPageView = pFormView ? pFormView->GetSdrPageView() : nullptr; + SdrPage* pPage = pPageView ? pPageView->GetPage() : nullptr; + DBG_ASSERT( pPage, "NavigatorTree::DeleteSelection: invalid form page!" ); + + MapModelToShape aModelShapes; + if ( pPage ) + collectShapeModelMapping( pPage, aModelShapes ); + + // problem: we have to use ExplorerModel::Remove, since only this one properly deletes Form objects. + // But, the controls themself must be deleted via DeleteMarked (else, the Writer has some problems + // somewhere). In case I'd first delete the structure, then the controls, the UNDO would not work + // (since UNDO then would mean to first restore the controls, then the structure, means their parent + // form). The other way round, the EntryDatas would be invalid, if I'd first delete the controls and + // then go on to the structure. This means I have to delete the forms *after* the normal controls, so + // that during UNDO, they're restored in the proper order. + pFormShell->GetImpl()->EnableTrackProperties_Lock(false); + for (SvLBoxEntrySortedArray::reverse_iterator it = m_arrCurrentSelection.rbegin(); + it != m_arrCurrentSelection.rend(); ) + { + FmEntryData* pCurrent = static_cast<FmEntryData*>((*it)->GetUserData()); + + // a form ? + bool bIsForm = dynamic_cast<const FmFormData*>( pCurrent) != nullptr; + + // because deletion is done by the view, and i build on its MarkList, + // but normally only direct controls, no indirect ones, are marked in a marked form, + // I have to do it later + if (bIsForm) + MarkViewObj(static_cast<FmFormData*>(pCurrent), true/*deep*/); + + // a hidden control ? + bool bIsHidden = IsHiddenControl(pCurrent); + + // keep forms and hidden controls, the rest not + if (!bIsForm && !bIsHidden) + { + // well, no form and no hidden control -> we can remove it from m_arrCurrentSelection, as it will + // be deleted automatically. This is because for every model (except forms and hidden control models) + // there exist a shape, which is marked _if_and_only_if_ the model is selected in our tree. + if ( aModelShapes.find( pCurrent->GetElement() ) != aModelShapes.end() ) + { + // if there's a shape for the current entry, then either it is marked or it is in a + // hidden layer (#i28502#), or something like this. + // In the first case, it will be deleted below, in the second case, we currently don't + // delete it, as there's no real (working!) API for this, neither in UNO nor in non-UNO. + m_arrCurrentSelection.erase( --(it.base()) ); + } + else + ++it; + // In case there is no shape for the current entry, we keep the entry in m_arrCurrentSelection, + // since then we can definitely remove it. + } + else + ++it; + } + pFormShell->GetImpl()->EnableTrackProperties_Lock(true); + + // let the view delete the marked controls + pFormShell->GetFormView()->DeleteMarked(); + + // start UNDO at this point. Unfortunately, this results in 2 UNDO actions, since DeleteMarked is + // creating an own one. However, if we'd move it before DeleteMarked, Writer does not really like + // this ... :( + // #i31038# + { + + // initialize UNDO + OUString aUndoStr; + if ( m_arrCurrentSelection.size() == 1 ) + { + aUndoStr = SvxResId(RID_STR_UNDO_CONTAINER_REMOVE); + if (m_nFormsSelected) + aUndoStr = aUndoStr.replaceFirst( "#", SvxResId( RID_STR_FORM ) ); + else + // it must be a control (else the root would be selected, but it cannot be deleted) + aUndoStr = aUndoStr.replaceFirst( "#", SvxResId( RID_STR_CONTROL ) ); + } + else + { + aUndoStr = SvxResId(RID_STR_UNDO_CONTAINER_REMOVE_MULTIPLE); + aUndoStr = aUndoStr.replaceFirst( "#", OUString::number( m_arrCurrentSelection.size() ) ); + } + pFormModel->BegUndo(aUndoStr); + } + + // remove remaining structure + for (const auto& rpSelection : m_arrCurrentSelection) + { + FmEntryData* pCurrent = static_cast<FmEntryData*>(rpSelection->GetUserData()); + + // if the entry still has children, we skipped deletion of one of those children. + // This may for instance be because the shape is in a hidden layer, where we're unable + // to remove it + if ( pCurrent->GetChildList()->size() ) + continue; + + // one remaining subtile problem, before deleting it : if it's a form and the shell + // knows it as CurrentObject, I have to tell it something else + if (dynamic_cast<const FmFormData*>( pCurrent) != nullptr) + { + Reference< XForm > xCurrentForm( static_cast< FmFormData* >( pCurrent )->GetFormIface() ); + if (pFormShell->GetImpl()->getCurrentForm_Lock() == xCurrentForm) // shell knows form to be deleted ? + pFormShell->GetImpl()->forgetCurrentForm_Lock(); // -> take away ... + } + GetNavModel()->Remove(pCurrent, true); + } + pFormModel->EndUndo(); + } + + + void NavigatorTree::CollectSelectionData(SELDATA_ITEMS sdiHow) + { + DBG_ASSERT(sdiHow != SDI_DIRTY, "NavigatorTree::CollectSelectionData : ever thought about your parameter ? DIRTY ?"); + if (sdiHow == m_sdiState) + return; + + m_arrCurrentSelection.clear(); + m_nFormsSelected = m_nControlsSelected = m_nHiddenControls = 0; + m_bRootSelected = false; + + SvTreeListEntry* pSelectionLoop = FirstSelected(); + while (pSelectionLoop) + { + // count different elements + if (pSelectionLoop == m_pRootEntry) + m_bRootSelected = true; + else + { + if (IsFormEntry(pSelectionLoop)) + ++m_nFormsSelected; + else + { + ++m_nControlsSelected; + if (IsHiddenControl(static_cast<FmEntryData*>(pSelectionLoop->GetUserData()))) + ++m_nHiddenControls; + } + } + + if (sdiHow == SDI_NORMALIZED) + { + // don't take something with a selected ancestor + if (pSelectionLoop == m_pRootEntry) + m_arrCurrentSelection.insert(pSelectionLoop); + else + { + SvTreeListEntry* pParentLoop = GetParent(pSelectionLoop); + while (pParentLoop) + { + // actually i would have to test, if parent is part of m_arr_CurrentSelection ... + // but if it's selected, then it's in m_arrCurrentSelection + // or one of its ancestors, which was selected earlier. + // In both cases IsSelected is enough + if (IsSelected(pParentLoop)) + break; + else + { + if (m_pRootEntry == pParentLoop) + { + // until root (exclusive), there was no selected parent -> entry belongs to normalized list + m_arrCurrentSelection.insert(pSelectionLoop); + break; + } + else + pParentLoop = GetParent(pParentLoop); + } + } + } + } + else if (sdiHow == SDI_NORMALIZED_FORMARK) + { + SvTreeListEntry* pParent = GetParent(pSelectionLoop); + if (!pParent || !IsSelected(pParent) || IsFormEntry(pSelectionLoop)) + m_arrCurrentSelection.insert(pSelectionLoop); + } + else + m_arrCurrentSelection.insert(pSelectionLoop); + + + pSelectionLoop = NextSelected(pSelectionLoop); + } + + m_sdiState = sdiHow; + } + + + void NavigatorTree::SynchronizeSelection(FmEntryDataArray& arredToSelect) + { + LockSelectionHandling(); + if (arredToSelect.empty()) + { + SelectAll(false); + } + else + { + // compare current selection with requested SelectList + SvTreeListEntry* pSelection = FirstSelected(); + while (pSelection) + { + FmEntryData* pCurrent = static_cast<FmEntryData*>(pSelection->GetUserData()); + if (pCurrent != nullptr) + { + FmEntryDataArray::iterator it = arredToSelect.find(pCurrent); + if ( it != arredToSelect.end() ) + { // entry already selected, but also in SelectList + // remove it from there + arredToSelect.erase(it); + } else + { // entry selected, but not in SelectList -> remove selection + Select(pSelection, false); + // make it visible (maybe it's the only modification i do in this handler + // so you should see it + MakeVisible(pSelection); + } + } + else + Select(pSelection, false); + + pSelection = NextSelected(pSelection); + } + + // now SelectList contains only entries, which have to be selected + // two possibilities : 1) run through SelectList, get SvTreeListEntry for every entry and select it (is more intuitive) + // 2) run through my SvLBoxEntries and select those, i can find in the SelectList + // 1) needs =(k*n) (k=length of SelectList, n=number of entries), + // plus the fact, that FindEntry uses extensive IsEqualWithoutChilden instead of comparing pointer to UserData + // 2) needs =(n*log k), duplicates some code from FindEntry + // This may be a frequently used code ( at every change in mark of the view!), + // so i use latter one + SvTreeListEntry* pLoop = First(); + FmEntryDataArray::const_iterator aEnd = arredToSelect.end(); + while(pLoop) + { + FmEntryData* pCurEntryData = static_cast<FmEntryData*>(pLoop->GetUserData()); + FmEntryDataArray::iterator it = arredToSelect.find(pCurEntryData); + if (it != aEnd) + { + Select(pLoop); + MakeVisible(pLoop); + SetCursor(pLoop, true); + } + + pLoop = Next(pLoop); + } + } + UnlockSelectionHandling(); + } + + + void NavigatorTree::SynchronizeSelection() + { + // shell and view + FmFormShell* pFormShell = GetNavModel()->GetFormShell(); + if(!pFormShell) return; + + FmFormView* pFormView = pFormShell->GetFormView(); + if (!pFormView) return; + + GetNavModel()->BroadcastMarkedObjects(pFormView->GetMarkedObjectList()); + } + + + void NavigatorTree::SynchronizeMarkList() + { + // i'll need this shell + FmFormShell* pFormShell = GetNavModel()->GetFormShell(); + if (!pFormShell) return; + + CollectSelectionData(SDI_NORMALIZED_FORMARK); + + // the view shouldn't notify now if MarkList changed + pFormShell->GetImpl()->EnableTrackProperties_Lock(false); + + UnmarkAllViewObj(); + + for (SvTreeListEntry* pSelectionLoop : m_arrCurrentSelection) + { + // When form selection, mark all controls of form + if (IsFormEntry(pSelectionLoop) && (pSelectionLoop != m_pRootEntry)) + MarkViewObj(static_cast<FmFormData*>(pSelectionLoop->GetUserData()), false/*deep*/); + + // When control selection, mark Control-SdrObjects + else if (IsFormComponentEntry(pSelectionLoop)) + { + FmControlData* pControlData = static_cast<FmControlData*>(pSelectionLoop->GetUserData()); + if (pControlData) + { + + // When HiddenControl no object can be selected + Reference< XFormComponent > xFormComponent( pControlData->GetFormComponent()); + if (!xFormComponent.is()) + continue; + Reference< XPropertySet > xSet(xFormComponent, UNO_QUERY); + if (!xSet.is()) + continue; + + sal_uInt16 nClassId = ::comphelper::getINT16(xSet->getPropertyValue(FM_PROP_CLASSID)); + if (nClassId != FormComponentType::HIDDENCONTROL) + MarkViewObj(pControlData); + } + } + } + + // if PropertyBrowser is open, I have to adopt it according to my selection + // (Not as MarkList of view : if a form is selected, all belonging controls are selected in the view + // but of course i want to see the form-properties + ShowSelectionProperties(); + + // reset flag at view + pFormShell->GetImpl()->EnableTrackProperties_Lock(true); + + // if exactly one form is selected now, shell should notice it as CurrentForm + // (if selection handling isn't locked, view cares about it in MarkListHasChanged + // but mechanism doesn't work, if form is empty for example + if ((m_arrCurrentSelection.size() == 1) && (m_nFormsSelected == 1)) + { + FmFormData* pSingleSelectionData = dynamic_cast<FmFormData*>( static_cast< FmEntryData* >( FirstSelected()->GetUserData() ) ); + DBG_ASSERT( pSingleSelectionData, "NavigatorTree::SynchronizeMarkList: invalid selected form!" ); + if ( pSingleSelectionData ) + { + InterfaceBag aSelection; + aSelection.insert( Reference< XInterface >( pSingleSelectionData->GetFormIface(), UNO_QUERY ) ); + pFormShell->GetImpl()->setCurrentSelection_Lock(aSelection); + } + } + } + + + bool NavigatorTree::IsHiddenControl(FmEntryData const * pEntryData) + { + if (pEntryData == nullptr) return false; + + Reference< XPropertySet > xProperties( pEntryData->GetPropertySet() ); + if (::comphelper::hasProperty(FM_PROP_CLASSID, xProperties)) + { + Any aClassID = xProperties->getPropertyValue( FM_PROP_CLASSID ); + return (::comphelper::getINT16(aClassID) == FormComponentType::HIDDENCONTROL); + } + return false; + } + + + bool NavigatorTree::Select( SvTreeListEntry* pEntry, bool bSelect ) + { + if (bSelect == IsSelected(pEntry)) // this happens sometimes, maybe base class is to exact ;) + return true; + + return SvTreeListBox::Select(pEntry, bSelect ); + } + + + void NavigatorTree::UnmarkAllViewObj() + { + FmFormShell* pFormShell = GetNavModel()->GetFormShell(); + if( !pFormShell ) + return; + FmFormView* pFormView = pFormShell->GetFormView(); + pFormView->UnMarkAll(); + } + + void NavigatorTree::MarkViewObj(FmFormData const * pFormData, bool bDeep ) + { + FmFormShell* pFormShell = GetNavModel()->GetFormShell(); + if( !pFormShell ) + return; + + // first collect all sdrobjects + ::std::set< Reference< XFormComponent > > aObjects; + CollectObjects(pFormData,bDeep,aObjects); + + + // find and select appropriate SdrObj in page + FmFormView* pFormView = pFormShell->GetFormView(); + SdrPageView* pPageView = pFormView->GetSdrPageView(); + SdrPage* pPage = pPageView->GetPage(); + //FmFormPage* pFormPage = dynamic_cast< FmFormPage* >( pPage ); + + SdrObjListIter aIter( pPage ); + while ( aIter.IsMore() ) + { + SdrObject* pSdrObject = aIter.Next(); + FmFormObj* pFormObject = FmFormObj::GetFormObject( pSdrObject ); + if ( !pFormObject ) + continue; + + Reference< XFormComponent > xControlModel( pFormObject->GetUnoControlModel(),UNO_QUERY ); + if ( xControlModel.is() && aObjects.find(xControlModel) != aObjects.end() && !pFormView->IsObjMarked( pSdrObject ) ) + { + // unfortunately, the writer doesn't like marking an already-marked object, again, so reset the mark first + pFormView->MarkObj( pSdrObject, pPageView ); + } + } // while ( aIter.IsMore() ) + // make the mark visible + ::tools::Rectangle aMarkRect( pFormView->GetAllMarkedRect()); + for ( sal_uInt32 i = 0; i < pFormView->PaintWindowCount(); ++i ) + { + SdrPaintWindow* pPaintWindow = pFormView->GetPaintWindow( i ); + OutputDevice& rOutDev = pPaintWindow->GetOutputDevice(); + if ( ( OUTDEV_WINDOW == rOutDev.GetOutDevType() ) && !aMarkRect.IsEmpty() ) + { + pFormView->MakeVisible( aMarkRect, static_cast<vcl::Window&>(rOutDev) ); + } + } // for ( sal_uInt32 i = 0; i < pFormView->PaintWindowCount(); ++i ) + } + + void NavigatorTree::CollectObjects(FmFormData const * pFormData, bool bDeep, ::std::set< Reference< XFormComponent > >& _rObjects) + { + FmEntryDataList* pChildList = pFormData->GetChildList(); + FmControlData* pControlData; + for( size_t i = 0; i < pChildList->size(); ++i ) + { + FmEntryData* pEntryData = pChildList->at( i ); + if( dynamic_cast<const FmControlData*>( pEntryData) != nullptr ) + { + pControlData = static_cast<FmControlData*>(pEntryData); + _rObjects.insert(pControlData->GetFormComponent()); + } // if( dynamic_cast<const FmControlData*>( pEntryData) != nullptr ) + else if (bDeep && (dynamic_cast<const FmFormData*>( pEntryData) != nullptr)) + CollectObjects(static_cast<FmFormData*>(pEntryData), bDeep, _rObjects); + } // for( sal_uInt32 i=0; i<pChildList->Count(); i++ ) + } + + void NavigatorTree::MarkViewObj( FmControlData const * pControlData) + { + if( !pControlData ) + return; + FmFormShell* pFormShell = GetNavModel()->GetFormShell(); + if( !pFormShell ) + return; + + + // find and select appropriate SdrObj + FmFormView* pFormView = pFormShell->GetFormView(); + Reference< XFormComponent > xFormComponent( pControlData->GetFormComponent()); + SdrPageView* pPageView = pFormView->GetSdrPageView(); + SdrPage* pPage = pPageView->GetPage(); + + bool bPaint = false; + SdrObjListIter aIter( pPage ); + while ( aIter.IsMore() ) + { + SdrObject* pSdrObject = aIter.Next(); + FmFormObj* pFormObject = FmFormObj::GetFormObject( pSdrObject ); + if ( !pFormObject ) + continue; + + Reference< XInterface > xControlModel( pFormObject->GetUnoControlModel() ); + if ( xControlModel != xFormComponent ) + continue; + + // mark the object + if ( !pFormView->IsObjMarked( pSdrObject ) ) + // unfortunately, the writer doesn't like marking an already-marked object, again, so reset the mark first + pFormView->MarkObj( pSdrObject, pPageView ); + + bPaint = true; + + } // while ( aIter.IsMore() ) + if ( bPaint ) + { + // make the mark visible + ::tools::Rectangle aMarkRect( pFormView->GetAllMarkedRect()); + for ( sal_uInt32 i = 0; i < pFormView->PaintWindowCount(); ++i ) + { + SdrPaintWindow* pPaintWindow = pFormView->GetPaintWindow( i ); + OutputDevice& rOutDev = pPaintWindow->GetOutputDevice(); + if ( OUTDEV_WINDOW == rOutDev.GetOutDevType() ) + { + pFormView->MakeVisible( aMarkRect, static_cast<vcl::Window&>(rOutDev) ); + } + } // for ( sal_uInt32 i = 0; i < pFormView->PaintWindowCount(); ++i ) + } + } + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/navigatortreemodel.cxx b/svx/source/form/navigatortreemodel.cxx new file mode 100644 index 000000000..a0f6baa66 --- /dev/null +++ b/svx/source/form/navigatortreemodel.cxx @@ -0,0 +1,908 @@ +/* -*- 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 <svx/dialmgr.hxx> +#include <svx/fmshell.hxx> +#include <svx/fmmodel.hxx> +#include <svx/fmpage.hxx> +#include <svx/svditer.hxx> +#include <svx/svdogrp.hxx> + +#include <fmprop.hxx> + +#include <fmundo.hxx> +#include <fmexpl.hxx> +#include <svx/strings.hrc> +#include <fmshimp.hxx> +#include <fmobj.hxx> +#include <o3tl/safeint.hxx> +#include <sfx2/objsh.hxx> +#include <tools/diagnose_ex.h> +#include <com/sun/star/container/XContainer.hpp> +#include <comphelper/types.hxx> + + +namespace svxform +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::form; + using namespace ::com::sun::star::awt; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::script; + using namespace ::com::sun::star::sdb; + + OFormComponentObserver::OFormComponentObserver(NavigatorTreeModel* _pModel) + :m_pNavModel(_pModel) + ,m_nLocks(0) + ,m_bCanUndo(true) + { + } + + // XPropertyChangeListener + + void SAL_CALL OFormComponentObserver::disposing(const EventObject& Source) + { + Remove( Source.Source ); + } + + + void SAL_CALL OFormComponentObserver::propertyChange(const PropertyChangeEvent& evt) + { + if( !m_pNavModel ) return; + if( evt.PropertyName != FM_PROP_NAME ) return; + + Reference< XFormComponent > xFormComponent(evt.Source, UNO_QUERY); + Reference< XForm > xForm(evt.Source, UNO_QUERY); + + FmEntryData* pEntryData( nullptr ); + if( xForm.is() ) + pEntryData = m_pNavModel->FindData( xForm, m_pNavModel->GetRootList() ); + else if( xFormComponent.is() ) + pEntryData = m_pNavModel->FindData( xFormComponent, m_pNavModel->GetRootList() ); + + if( pEntryData ) + { + OUString aNewName = ::comphelper::getString(evt.NewValue); + pEntryData->SetText( aNewName ); + FmNavNameChangedHint aNameChangedHint( pEntryData, aNewName ); + m_pNavModel->Broadcast( aNameChangedHint ); + } + } + + // XContainerListener + + void SAL_CALL OFormComponentObserver::elementInserted(const ContainerEvent& evt) + { + if (IsLocked() || !m_pNavModel) + return; + + // insert no Undoaction + m_bCanUndo = false; + + Reference< XInterface > xTemp; + evt.Element >>= xTemp; + Insert(xTemp, ::comphelper::getINT32(evt.Accessor)); + + m_bCanUndo = true; + } + + + void OFormComponentObserver::Insert(const Reference< XInterface > & xIface, sal_Int32 nIndex) + { + Reference< XForm > xForm(xIface, UNO_QUERY); + if (xForm.is()) + { + m_pNavModel->InsertForm(xForm, sal_uInt32(nIndex)); + Reference< XIndexContainer > xContainer(xForm, UNO_QUERY); + Reference< XInterface > xTemp; + for (sal_Int32 i = 0; i < xContainer->getCount(); i++) + { + xContainer->getByIndex(i) >>= xTemp; + Insert(xTemp, i); + } + } + else + { + Reference< XFormComponent > xFormComp(xIface, UNO_QUERY); + if (xFormComp.is()) + m_pNavModel->InsertFormComponent(xFormComp, sal_uInt32(nIndex)); + } + } + + + void SAL_CALL OFormComponentObserver::elementReplaced(const ContainerEvent& evt) + { + if (IsLocked() || !m_pNavModel) + return; + + m_bCanUndo = false; + + // delete EntryData + Reference< XFormComponent > xReplaced; + evt.ReplacedElement >>= xReplaced; + FmEntryData* pEntryData = m_pNavModel->FindData(xReplaced, m_pNavModel->GetRootList()); + if (pEntryData) + { + if (dynamic_cast<const FmControlData*>( pEntryData) != nullptr) + { + Reference< XFormComponent > xComp; + evt.Element >>= xComp; + DBG_ASSERT(xComp.is(), "OFormComponentObserver::elementReplaced : invalid argument !"); + // FmControlData should be coupled with XFormComponent + m_pNavModel->ReplaceFormComponent(xReplaced, xComp); + } + else if (dynamic_cast<const FmFormData*>( pEntryData) != nullptr) + { + OSL_FAIL("replacing forms not implemented yet !"); + } + } + + m_bCanUndo = true; + } + + + void OFormComponentObserver::Remove( const css::uno::Reference< css::uno::XInterface >& _rxElement ) + { + if (IsLocked() || !m_pNavModel) + return; + + m_bCanUndo = false; + + + // delete EntryData + FmEntryData* pEntryData = m_pNavModel->FindData( _rxElement, m_pNavModel->GetRootList() ); + if (pEntryData) + m_pNavModel->Remove(pEntryData); + + m_bCanUndo = true; + } + + + void SAL_CALL OFormComponentObserver::elementRemoved(const ContainerEvent& evt) + { + Reference< XInterface > xElement; + evt.Element >>= xElement; + Remove( xElement ); + } + + NavigatorTreeModel::NavigatorTreeModel() + :m_pFormShell(nullptr) + ,m_pFormPage(nullptr) + ,m_pFormModel(nullptr) + { + m_pPropChangeList = new OFormComponentObserver(this); + m_pRootList.reset( new FmEntryDataList() ); + } + + NavigatorTreeModel::~NavigatorTreeModel() + { + + // unregister Listener + if( m_pFormShell) + { + FmFormModel* pFormModel = m_pFormShell->GetFormModel(); + if( pFormModel && IsListening(*pFormModel)) + EndListening( *pFormModel ); + + if (IsListening(*m_pFormShell)) + EndListening(*m_pFormShell); + } + + Clear(); + m_pRootList.reset(); + m_pPropChangeList->ReleaseModel(); + } + + + void NavigatorTreeModel::SetModified() + { + if( !m_pFormShell ) return; + SfxObjectShell* pObjShell = m_pFormShell->GetFormModel()->GetObjectShell(); + if( !pObjShell ) return; + pObjShell->SetModified(); + } + + + void NavigatorTreeModel::Clear() + { + Reference< css::form::XForms > xForms( GetForms()); + if(xForms.is()) + xForms->removeContainerListener(m_pPropChangeList.get()); + + + // delete RootList + GetRootList()->clear(); + + + // notify UI + FmNavClearedHint aClearedHint; + Broadcast( aClearedHint ); + } + + + Reference< css::form::XForms > NavigatorTreeModel::GetForms() const + { + if( !m_pFormShell || !m_pFormShell->GetCurPage()) + return nullptr; + else + return m_pFormShell->GetCurPage()->GetForms(); + } + + + void NavigatorTreeModel::Insert(FmEntryData* pEntry, sal_uInt32 nRelPos, bool bAlterModel) + { + if (IsListening(*m_pFormModel)) + EndListening(*m_pFormModel); + + m_pPropChangeList->Lock(); + FmFormData* pFolder = static_cast<FmFormData*>( pEntry->GetParent() ); + Reference< XChild > xElement( pEntry->GetChildIFace() ); + if (bAlterModel) + { + OUString aStr; + if (dynamic_cast<const FmFormData*>( pEntry) != nullptr) + aStr = SvxResId(RID_STR_FORM); + else + aStr = SvxResId(RID_STR_CONTROL); + + Reference< XIndexContainer > xContainer; + if (pFolder) + xContainer.set(pFolder->GetFormIface(), UNO_QUERY); + else + xContainer = GetForms(); + + bool bUndo = m_pFormModel->IsUndoEnabled(); + + if( bUndo ) + { + OUString aUndoStr(SvxResId(RID_STR_UNDO_CONTAINER_INSERT)); + aUndoStr = aUndoStr.replaceFirst("#", aStr); + m_pFormModel->BegUndo(aUndoStr); + } + + if (nRelPos >= o3tl::make_unsigned(xContainer->getCount())) + nRelPos = static_cast<sal_uInt32>(xContainer->getCount()); + + // UndoAction + if ( bUndo && m_pPropChangeList->CanUndo()) + { + m_pFormModel->AddUndo(std::make_unique<FmUndoContainerAction>(*m_pFormModel, + FmUndoContainerAction::Inserted, + xContainer, + xElement, + nRelPos)); + } + + // Element has to be of the expected type by the container + if (xContainer->getElementType() == + cppu::UnoType<XForm>::get()) + + { + Reference< XForm > xElementAsForm(xElement, UNO_QUERY); + xContainer->insertByIndex(nRelPos, makeAny(xElementAsForm)); + } + else if (xContainer->getElementType() == + cppu::UnoType<XFormComponent>::get()) + + { + Reference< XFormComponent > xElementAsComponent(xElement, UNO_QUERY); + xContainer->insertByIndex(nRelPos, makeAny(xElementAsComponent)); + } + else + { + OSL_FAIL("NavigatorTreeModel::Insert : the parent container needs an elementtype I don't know !"); + } + + if( bUndo ) + m_pFormModel->EndUndo(); + } + + // register as PropertyChangeListener + Reference< XPropertySet > xSet(xElement, UNO_QUERY); + if( xSet.is() ) + xSet->addPropertyChangeListener( FM_PROP_NAME, m_pPropChangeList.get() ); + + + // Remove data from model + if (dynamic_cast<const FmFormData*>( pEntry) != nullptr) + { + Reference< XContainer > xContainer(xElement, UNO_QUERY); + if (xContainer.is()) + xContainer->addContainerListener(m_pPropChangeList.get()); + } + + if (pFolder) + pFolder->GetChildList()->insert( std::unique_ptr<FmEntryData>(pEntry), nRelPos ); + else + GetRootList()->insert( std::unique_ptr<FmEntryData>(pEntry), nRelPos ); + + + // notify UI + FmNavInsertedHint aInsertedHint( pEntry, nRelPos ); + Broadcast( aInsertedHint ); + + m_pPropChangeList->UnLock(); + if (IsListening(*m_pFormModel)) + StartListening(*m_pFormModel); + } + + + void NavigatorTreeModel::Remove(FmEntryData* pEntry, bool bAlterModel) + { + + // get form and parent + if (!pEntry || !m_pFormModel) + return; + + if (IsListening(*m_pFormModel)) + EndListening(*m_pFormModel); + + const bool bUndo = m_pFormModel->IsUndoEnabled(); + + m_pPropChangeList->Lock(); + FmFormData* pFolder = static_cast<FmFormData*>( pEntry->GetParent() ); + Reference< XChild > xElement ( pEntry->GetChildIFace() ); + if (bAlterModel) + { + OUString aStr; + if (dynamic_cast<const FmFormData*>( pEntry) != nullptr) + aStr = SvxResId(RID_STR_FORM); + else + aStr = SvxResId(RID_STR_CONTROL); + + if( bUndo ) + { + OUString aUndoStr(SvxResId(RID_STR_UNDO_CONTAINER_REMOVE)); + aUndoStr = aUndoStr.replaceFirst("#", aStr); + m_pFormModel->BegUndo(aUndoStr); + } + } + + // now real deletion of data form model + if (dynamic_cast<const FmFormData*>( pEntry) != nullptr) + RemoveForm(static_cast<FmFormData*>(pEntry)); + else + RemoveFormComponent(static_cast<FmControlData*>(pEntry)); + + + if (bAlterModel) + { + Reference< XIndexContainer > xContainer(xElement->getParent(), UNO_QUERY); + // remove from Container + sal_Int32 nContainerIndex = getElementPos(xContainer.get(), xElement); + // UndoAction + if (nContainerIndex >= 0) + { + if ( bUndo && m_pPropChangeList->CanUndo()) + { + m_pFormModel->AddUndo(std::make_unique<FmUndoContainerAction>(*m_pFormModel, + FmUndoContainerAction::Removed, + xContainer, + xElement, nContainerIndex )); + } + else if( !m_pPropChangeList->CanUndo() ) + { + FmUndoContainerAction::DisposeElement( xElement ); + } + + xContainer->removeByIndex(nContainerIndex ); + } + + if( bUndo ) + m_pFormModel->EndUndo(); + } + + // remove from parent + if (pFolder) + pFolder->GetChildList()->removeNoDelete( pEntry ); + else + { + GetRootList()->removeNoDelete( pEntry ); + + // If root has no more form, reset CurForm at shell + if ( !GetRootList()->size() ) + m_pFormShell->GetImpl()->forgetCurrentForm_Lock(); + } + + + // notify UI + FmNavRemovedHint aRemovedHint( pEntry ); + Broadcast( aRemovedHint ); + + // delete entry + delete pEntry; + + m_pPropChangeList->UnLock(); + StartListening(*m_pFormModel); + } + + + void NavigatorTreeModel::RemoveForm(FmFormData const * pFormData) + { + + // get form and parent + if (!pFormData || !m_pFormModel) + return; + + FmEntryDataList* pChildList = pFormData->GetChildList(); + for ( size_t i = pChildList->size(); i > 0; ) + { + FmEntryData* pEntryData = pChildList->at( --i ); + + + // Child is form -> recursive call + if( dynamic_cast<const FmFormData*>( pEntryData) != nullptr ) + RemoveForm( static_cast<FmFormData*>(pEntryData)); + else if( dynamic_cast<const FmControlData*>( pEntryData) != nullptr ) + RemoveFormComponent(static_cast<FmControlData*>(pEntryData)); + } + + + // unregister as PropertyChangeListener + Reference< XPropertySet > xSet( pFormData->GetPropertySet() ); + if ( xSet.is() ) + xSet->removePropertyChangeListener( FM_PROP_NAME, m_pPropChangeList.get() ); + } + + + void NavigatorTreeModel::RemoveFormComponent(FmControlData const * pControlData) + { + + // get control and parent + if (!pControlData) + return; + + + // unregister as PropertyChangeListener + Reference< XPropertySet > xSet( pControlData->GetPropertySet() ); + if (xSet.is()) + xSet->removePropertyChangeListener( FM_PROP_NAME, m_pPropChangeList.get()); + } + + + void NavigatorTreeModel::FillBranch( FmFormData* pFormData ) + { + + // insert forms from root + if( pFormData == nullptr ) + { + Reference< XIndexContainer > xForms = GetForms(); + if (!xForms.is()) + return; + + Reference< XForm > xSubForm; + for (sal_Int32 i=0; i<xForms->getCount(); ++i) + { + DBG_ASSERT( xForms->getByIndex(i).getValueType() == cppu::UnoType<XForm>::get(), + "NavigatorTreeModel::FillBranch : the root container should supply only elements of type XForm"); + + xForms->getByIndex(i) >>= xSubForm; + FmFormData* pSubFormData = new FmFormData(xSubForm, pFormData); + Insert( pSubFormData ); + + // new branch, if SubForm contains Subforms itself + FillBranch( pSubFormData ); + } + } + + + // insert components + else + { + Reference< XIndexContainer > xComponents( GetFormComponents(pFormData)); + if( !xComponents.is() ) return; + + FmControlData* pNewControlData; + FmFormData* pSubFormData; + + Reference< XFormComponent > xCurrentComponent; + for (sal_Int32 j=0; j<xComponents->getCount(); ++j) + { + xComponents->getByIndex(j) >>= xCurrentComponent; + Reference< XForm > xSubForm(xCurrentComponent, UNO_QUERY); + + if (xSubForm.is()) + { // actual component is a form + pSubFormData = new FmFormData(xSubForm, pFormData); + Insert(pSubFormData); + + + // new branch, if SubForm contains Subforms itself + FillBranch(pSubFormData); + } + else + { + pNewControlData = new FmControlData(xCurrentComponent, pFormData); + Insert(pNewControlData); + } + } + } + } + + + void NavigatorTreeModel::InsertForm(const Reference< XForm > & xForm, sal_uInt32 nRelPos) + { + FmFormData* pFormData = static_cast<FmFormData*>(FindData( xForm, GetRootList() )); + if (pFormData) + return; + + + // set ParentData + Reference< XInterface > xIFace( xForm->getParent()); + Reference< XForm > xParentForm(xIFace, UNO_QUERY); + FmFormData* pParentData = nullptr; + if (xParentForm.is()) + pParentData = static_cast<FmFormData*>(FindData( xParentForm, GetRootList() )); + + pFormData = new FmFormData(xForm, pParentData); + Insert( pFormData, nRelPos ); + } + + + void NavigatorTreeModel::InsertFormComponent(const Reference< XFormComponent > & xComp, sal_uInt32 nRelPos) + { + + // set ParentData + Reference< XInterface > xIFace( xComp->getParent()); + Reference< XForm > xForm(xIFace, UNO_QUERY); + if (!xForm.is()) + return; + + FmFormData* pParentData = static_cast<FmFormData*>(FindData( xForm, GetRootList() )); + if( !pParentData ) + { + pParentData = new FmFormData(xForm, nullptr); + Insert( pParentData ); + } + + if (!FindData(xComp, pParentData->GetChildList(),false)) + { + + // set new EntryData + FmEntryData* pNewEntryData = new FmControlData(xComp, pParentData); + + + // insert new EntryData + Insert( pNewEntryData, nRelPos ); + } + } + + void NavigatorTreeModel::ReplaceFormComponent( + const Reference< XFormComponent > & xOld, + const Reference< XFormComponent > & xNew + ) + { + FmEntryData* pData = FindData(xOld, GetRootList()); + assert(dynamic_cast<const FmControlData*>( pData)); //NavigatorTreeModel::ReplaceFormComponent : invalid argument + if (!dynamic_cast<const FmControlData*>( pData)) + return; + static_cast<FmControlData*>(pData)->ModelReplaced(xNew); + + FmNavModelReplacedHint aReplacedHint( pData ); + Broadcast( aReplacedHint ); + } + + FmEntryData* NavigatorTreeModel::FindData(const Reference< XInterface > & xElement, FmEntryDataList* pDataList, bool bRecurs) + { + // normalize + Reference< XInterface > xIFace( xElement, UNO_QUERY ); + + for ( size_t i = 0; i < pDataList->size(); i++ ) + { + FmEntryData* pEntryData = pDataList->at( i ); + if ( pEntryData->GetElement().get() == xIFace.get() ) + return pEntryData; + else if (bRecurs) + { + pEntryData = FindData( xElement, pEntryData->GetChildList() ); + if (pEntryData) + return pEntryData; + } + } + return nullptr; + } + + + FmEntryData* NavigatorTreeModel::FindData( const OUString& rText, FmFormData const * pParentData, bool bRecurs ) + { + FmEntryDataList* pDataList; + if( !pParentData ) + pDataList = GetRootList(); + else + pDataList = pParentData->GetChildList(); + + OUString aEntryText; + FmEntryData* pEntryData; + FmEntryData* pChildData; + + for( size_t i = 0; i < pDataList->size(); i++ ) + { + pEntryData = pDataList->at( i ); + aEntryText = pEntryData->GetText(); + + if (rText == aEntryText) + return pEntryData; + + if (FmFormData* pFormData = bRecurs ? dynamic_cast<FmFormData*>(pEntryData) : nullptr) + { + pChildData = FindData(rText, pFormData, true); + if( pChildData ) + return pChildData; + } + } + + return nullptr; + } + + void NavigatorTreeModel::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint ) + { + if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint) + { + const SdrHint* pSdrHint = static_cast<const SdrHint*>(&rHint); + switch( pSdrHint->GetKind() ) + { + case SdrHintKind::ObjectInserted: + InsertSdrObj(pSdrHint->GetObject()); + break; + case SdrHintKind::ObjectRemoved: + RemoveSdrObj(pSdrHint->GetObject()); + break; + default: + break; + } + } + // is shell gone? + else if (rHint.GetId() == SfxHintId::Dying) + { + UpdateContent(nullptr); + } + // changed mark of controls? + else if (const FmNavViewMarksChanged* pvmcHint = dynamic_cast<const FmNavViewMarksChanged*>(&rHint)) + { + BroadcastMarkedObjects(pvmcHint->GetAffectedView()->GetMarkedObjectList()); + } + } + + void NavigatorTreeModel::InsertSdrObj( const SdrObject* pObj ) + { + const FmFormObj* pFormObject = FmFormObj::GetFormObject( pObj ); + if ( pFormObject ) + { + try + { + Reference< XFormComponent > xFormComponent( pFormObject->GetUnoControlModel(), UNO_QUERY_THROW ); + Reference< XIndexAccess > xContainer( xFormComponent->getParent(), UNO_QUERY_THROW ); + + sal_Int32 nPos = getElementPos( xContainer, xFormComponent ); + InsertFormComponent( xFormComponent, nPos ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + else if ( pObj->IsGroupObject() ) + { + SdrObjListIter aIter( pObj->GetSubList() ); + while ( aIter.IsMore() ) + InsertSdrObj( aIter.Next() ); + } + } + + + void NavigatorTreeModel::RemoveSdrObj( const SdrObject* pObj ) + { + const FmFormObj* pFormObject = FmFormObj::GetFormObject( pObj ); + if ( pFormObject ) + { + try + { + Reference< XFormComponent > xFormComponent( pFormObject->GetUnoControlModel(), UNO_QUERY_THROW ); + FmEntryData* pEntryData = FindData( xFormComponent, GetRootList() ); + if ( pEntryData ) + Remove( pEntryData ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + } + } + else if ( pObj->IsGroupObject() ) + { + SdrObjListIter aIter( pObj->GetSubList() ); + while ( aIter.IsMore() ) + RemoveSdrObj( aIter.Next() ); + } + } + + bool NavigatorTreeModel::InsertFormComponent(FmNavRequestSelectHint& rHint, SdrObject* pObject) + { + if ( dynamic_cast<const SdrObjGroup*>( pObject) != nullptr ) + { // descend recursively + const SdrObjList *pChildren = static_cast<SdrObjGroup*>(pObject)->GetSubList(); + for ( size_t i=0; i<pChildren->GetObjCount(); ++i ) + { + SdrObject* pCurrent = pChildren->GetObj(i); + if (!InsertFormComponent(rHint, pCurrent)) + return false; + } + } + else + { + FmFormObj* pFormObject = FmFormObj::GetFormObject( pObject ); + if ( !pFormObject ) + return false; + + try + { + Reference< XFormComponent > xFormViewControl( pFormObject->GetUnoControlModel(), UNO_QUERY_THROW ); + FmEntryData* pControlData = FindData( xFormViewControl, GetRootList() ); + if ( !pControlData ) + return false; + + rHint.AddItem( pControlData ); + return true; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svx"); + return false; + } + } + + return true; + } + + void NavigatorTreeModel::BroadcastMarkedObjects(const SdrMarkList& mlMarked) + { + // search all objects, which can be handled, out of marked objects + FmNavRequestSelectHint rshRequestSelection; + bool bIsMixedSelection = false; + + for (size_t i=0; (i<mlMarked.GetMarkCount()) && !bIsMixedSelection; ++i) + { + SdrObject* pobjCurrent = mlMarked.GetMark(i)->GetMarkedSdrObj(); + bIsMixedSelection |= !InsertFormComponent(rshRequestSelection, pobjCurrent); + // if Not-Form-Control, InsertFormComponent returns sal_False ! + } + + rshRequestSelection.SetMixedSelection(bIsMixedSelection); + if (bIsMixedSelection) + rshRequestSelection.ClearItems(); + + Broadcast(rshRequestSelection); + // an empty list causes NavigatorTree to remove his selection + } + + + void NavigatorTreeModel::UpdateContent( const Reference< css::form::XForms > & xForms ) + { + + // refill model form root upward + Clear(); + if (xForms.is()) + { + xForms->addContainerListener(m_pPropChangeList.get()); + + FillBranch(nullptr); + + // select same control in tree as in view + // (or all of them), if there is one ... + if(!m_pFormShell) return; // no shell + + FmFormView* pFormView = m_pFormShell->GetFormView(); + DBG_ASSERT(pFormView != nullptr, "NavigatorTreeModel::UpdateContent : no FormView"); + BroadcastMarkedObjects(pFormView->GetMarkedObjectList()); + } + } + + + void NavigatorTreeModel::UpdateContent( FmFormShell* pShell ) + { + + // If shell is unchanged, do nothing + FmFormPage* pNewPage = pShell ? pShell->GetCurPage() : nullptr; + if ((pShell == m_pFormShell) && (m_pFormPage == pNewPage)) + return; + + + // unregister as Listener + if( m_pFormShell ) + { + if (m_pFormModel) + EndListening( *m_pFormModel ); + m_pFormModel = nullptr; + EndListening( *m_pFormShell ); + Clear(); + } + + + // entire update + m_pFormShell = pShell; + if (m_pFormShell) + { + m_pFormPage = pNewPage; + UpdateContent(m_pFormPage->GetForms()); + } else + m_pFormPage = nullptr; + + + // register as Listener again + if( m_pFormShell ) + { + StartListening( *m_pFormShell ); + m_pFormModel = m_pFormShell->GetFormModel(); + if( m_pFormModel ) + StartListening( *m_pFormModel ); + } + } + + + Reference< XIndexContainer > NavigatorTreeModel::GetFormComponents( FmFormData const * pFormData ) + { + + // get components from form + if (pFormData) + return Reference< XIndexContainer > (pFormData->GetFormIface(), UNO_QUERY); + + return Reference< XIndexContainer > (); + } + + + bool NavigatorTreeModel::Rename( FmEntryData* pEntryData, const OUString& rNewText ) + { + + // If name already exist, error message + pEntryData->SetText( rNewText ); + + + // get PropertySet + Reference< XFormComponent > xFormComponent; + + if( dynamic_cast<const FmFormData*>( pEntryData) != nullptr ) + { + FmFormData* pFormData = static_cast<FmFormData*>(pEntryData); + xFormComponent = pFormData->GetFormIface(); + } + + if( dynamic_cast<const FmControlData*>( pEntryData) != nullptr ) + { + FmControlData* pControlData = static_cast<FmControlData*>(pEntryData); + xFormComponent = pControlData->GetFormComponent(); + } + + if( !xFormComponent.is() ) return false; + Reference< XPropertySet > xSet(xFormComponent, UNO_QUERY); + if( !xSet.is() ) return false; + + + // set name + xSet->setPropertyValue( FM_PROP_NAME, makeAny(rNewText) ); + + return true; + } + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/sdbdatacolumn.cxx b/svx/source/form/sdbdatacolumn.cxx new file mode 100644 index 000000000..19a7fd016 --- /dev/null +++ b/svx/source/form/sdbdatacolumn.cxx @@ -0,0 +1,55 @@ +/* -*- 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 <sdbdatacolumn.hxx> + + +namespace svxform +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::sdbc; + using namespace ::com::sun::star::util; + using namespace ::com::sun::star::io; + using namespace ::com::sun::star::container; + + + //= DataColumn - a class wrapping an object implementing a sdb::DataColumn service + + DataColumn::DataColumn(const Reference< css::beans::XPropertySet>& _rxIFace) + { + m_xPropertySet = _rxIFace; + m_xColumn.set(_rxIFace, UNO_QUERY); + m_xColumnUpdate.set(_rxIFace, UNO_QUERY); + + if (!m_xPropertySet.is() || !m_xColumn.is()) + { + m_xPropertySet = nullptr; + m_xColumn = nullptr; + m_xColumnUpdate = nullptr; + } + } + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/sqlparserclient.cxx b/svx/source/form/sqlparserclient.cxx new file mode 100644 index 000000000..232f0418d --- /dev/null +++ b/svx/source/form/sqlparserclient.cxx @@ -0,0 +1,50 @@ +/* -*- 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 <sqlparserclient.hxx> + +#include <connectivity/sqlparse.hxx> + +using namespace ::dbtools; +using namespace ::connectivity; + +namespace svxform +{ + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + + OSQLParserClient::OSQLParserClient(const Reference< XComponentContext >& rxContext) + : m_pParser(std::make_shared<OSQLParser>(rxContext, getParseContext())) + { + } + + std::unique_ptr< ::connectivity::OSQLParseNode > OSQLParserClient::predicateTree( + OUString& _rErrorMessage, + const OUString& _rStatement, + const css::uno::Reference< css::util::XNumberFormatter >& _rxFormatter, + const css::uno::Reference< css::beans::XPropertySet >& _rxField + ) const + { + return m_pParser->predicateTree(_rErrorMessage, _rStatement, _rxFormatter, _rxField); + } + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/tabwin.cxx b/svx/source/form/tabwin.cxx new file mode 100644 index 000000000..d0470b471 --- /dev/null +++ b/svx/source/form/tabwin.cxx @@ -0,0 +1,304 @@ +/* -*- 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 <tabwin.hxx> +#include <fmservs.hxx> + +#include <svx/strings.hrc> +#include <svx/svxids.hrc> +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/types.hxx> + +#include <helpids.h> +#include <svx/fmshell.hxx> +#include <fmshimp.hxx> + +#include <fmprop.hxx> + +#include <svx/dialmgr.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/objitem.hxx> +#include <sfx2/frame.hxx> +#include <svx/dataaccessdescriptor.hxx> +#include <tools/diagnose_ex.h> +#include <tabwin.hrc> + +const long STD_WIN_SIZE_X = 120; +const long STD_WIN_SIZE_Y = 150; + +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::datatransfer; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::form; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star; +using namespace ::svxform; +using namespace ::svx; +using namespace ::dbtools; + +struct ColumnInfo +{ + OUString sColumnName; + explicit ColumnInfo(const OUString& i_sColumnName) + : sColumnName(i_sColumnName) + { + } +}; + +void FmFieldWin::addToList(const uno::Reference< container::XNameAccess>& i_xColumns ) +{ + const uno::Sequence< OUString > aEntries = i_xColumns->getElementNames(); + for ( const OUString& rEntry : aEntries ) + { + uno::Reference< beans::XPropertySet> xColumn(i_xColumns->getByName(rEntry),UNO_QUERY_THROW); + OUString sLabel; + if ( xColumn->getPropertySetInfo()->hasPropertyByName(FM_PROP_LABEL) ) + xColumn->getPropertyValue(FM_PROP_LABEL) >>= sLabel; + m_aListBoxData.emplace_back(new ColumnInfo(rEntry)); + OUString sId(OUString::number(reinterpret_cast<sal_Int64>(m_aListBoxData.back().get()))); + if ( !sLabel.isEmpty() ) + m_xListBox->append(sId, sLabel); + else + m_xListBox->append(sId, rEntry); + } +} + +IMPL_LINK(FmFieldWin, DragBeginHdl, bool&, rUnsetDragIcon, bool) +{ + rUnsetDragIcon = false; + + ColumnInfo* pSelected = reinterpret_cast<ColumnInfo*>(m_xListBox->get_selected_id().toInt64()); + if (!pSelected) + { + // no drag without a field + return true; + } + + svx::ODataAccessDescriptor aDescriptor; + aDescriptor[ DataAccessDescriptorProperty::DataSource ] <<= GetDatabaseName(); + aDescriptor[ DataAccessDescriptorProperty::Connection ] <<= GetConnection().getTyped(); + aDescriptor[ DataAccessDescriptorProperty::Command ] <<= GetObjectName(); + aDescriptor[ DataAccessDescriptorProperty::CommandType ]<<= GetObjectType(); + aDescriptor[ DataAccessDescriptorProperty::ColumnName ] <<= pSelected->sColumnName; + + m_xHelper->setDescriptor(aDescriptor); + + return false; +} + +FmFieldWin::FmFieldWin(SfxBindings* _pBindings, SfxChildWindow* _pMgr, weld::Window* _pParent) + : SfxModelessDialogController(_pBindings, _pMgr, _pParent, "svx/ui/formfielddialog.ui", "FormFieldDialog") + , SfxControllerItem(SID_FM_FIELDS_CONTROL, *_pBindings) + , comphelper::OPropertyChangeListener(m_aMutex) + , m_xListBox(m_xBuilder->weld_tree_view("treeview")) + , m_nObjectType(0) +{ + m_xDialog->set_help_id(HID_FIELD_SEL_WIN); + m_xListBox->set_help_id(HID_FIELD_SEL); + + m_xListBox->connect_row_activated(LINK(this, FmFieldWin, RowActivatedHdl)); + m_xHelper.set(new OColumnTransferable( + ColumnTransferFormatFlags::FIELD_DESCRIPTOR | ColumnTransferFormatFlags::CONTROL_EXCHANGE | ColumnTransferFormatFlags::COLUMN_DESCRIPTOR + )); + rtl::Reference<TransferDataContainer> xHelper(m_xHelper.get()); + m_xListBox->enable_drag_source(xHelper, DND_ACTION_COPY); + m_xListBox->connect_drag_begin(LINK(this, FmFieldWin, DragBeginHdl)); + + UpdateContent(nullptr); + m_xDialog->set_size_request(STD_WIN_SIZE_X, STD_WIN_SIZE_Y); +} + +FmFieldWin::~FmFieldWin() +{ + if (m_xChangeListener.is()) + { + m_xChangeListener->dispose(); + m_xChangeListener.clear(); + } + ::SfxControllerItem::dispose(); +} + +IMPL_LINK_NOARG(FmFieldWin, RowActivatedHdl, weld::TreeView&, bool) +{ + return createSelectionControls(); +} + +bool FmFieldWin::createSelectionControls() +{ + ColumnInfo* pSelected = reinterpret_cast<ColumnInfo*>(m_xListBox->get_selected_id().toInt64()); + if (pSelected) + { + // build a descriptor for the currently selected field + ODataAccessDescriptor aDescr; + aDescr.setDataSource(GetDatabaseName()); + + aDescr[ DataAccessDescriptorProperty::Connection ] <<= GetConnection().getTyped(); + + aDescr[ DataAccessDescriptorProperty::Command ] <<= GetObjectName(); + aDescr[ DataAccessDescriptorProperty::CommandType ] <<= GetObjectType(); + aDescr[ DataAccessDescriptorProperty::ColumnName ] <<= pSelected->sColumnName; + + // transfer this to the SFX world + SfxUnoAnyItem aDescriptorItem( SID_FM_DATACCESS_DESCRIPTOR, makeAny( aDescr.createPropertyValueSequence() ) ); + const SfxPoolItem* pArgs[] = + { + &aDescriptorItem, nullptr + }; + + // execute the create slot + GetBindings().Execute( SID_FM_CREATE_FIELDCONTROL, pArgs ); + } + + return nullptr != pSelected; +} + +void FmFieldWin::_propertyChanged(const css::beans::PropertyChangeEvent& evt) +{ + css::uno::Reference< css::form::XForm > xForm(evt.Source, css::uno::UNO_QUERY); + UpdateContent(xForm); +} + +void FmFieldWin::StateChanged(sal_uInt16 nSID, SfxItemState eState, const SfxPoolItem* pState) +{ + if (!pState || SID_FM_FIELDS_CONTROL != nSID) + return; + + if (eState >= SfxItemState::DEFAULT) + { + FmFormShell* pShell = dynamic_cast<FmFormShell*>( static_cast<const SfxObjectItem*>(pState)->GetShell() ); + UpdateContent(pShell); + } + else + UpdateContent(nullptr); +} + +void FmFieldWin::UpdateContent(FmFormShell const * pShell) +{ + m_xListBox->clear(); + m_aListBoxData.clear(); + OUString aTitle(SvxResId(RID_STR_FIELDSELECTION)); + m_xDialog->set_title(aTitle); + + if (!pShell || !pShell->GetImpl()) + return; + + Reference<XForm> const xForm = pShell->GetImpl()->getCurrentForm_Lock(); + if ( xForm.is() ) + UpdateContent( xForm ); +} + +void FmFieldWin::UpdateContent(const css::uno::Reference< css::form::XForm > & xForm) +{ + try + { + // delete ListBox + m_xListBox->clear(); + m_aListBoxData.clear(); + OUString aTitle(SvxResId(RID_STR_FIELDSELECTION)); + m_xDialog->set_title(aTitle); + + if (!xForm.is()) + return; + + Reference< XPropertySet > xSet(xForm, UNO_QUERY); + + m_aObjectName = ::comphelper::getString(xSet->getPropertyValue(FM_PROP_COMMAND)); + m_aDatabaseName = ::comphelper::getString(xSet->getPropertyValue(FM_PROP_DATASOURCE)); + m_nObjectType = ::comphelper::getINT32(xSet->getPropertyValue(FM_PROP_COMMANDTYPE)); + + // get the connection of the form + m_aConnection.reset( + connectRowset( Reference< XRowSet >( xForm, UNO_QUERY ), ::comphelper::getProcessComponentContext(), nullptr ), + SharedConnection::NoTakeOwnership + ); + // TODO: When incompatible changes (such as extending the "virtualdbtools" interface by ensureRowSetConnection) + // are allowed, again, we should change this: dbtools should consistently use SharedConnection all over + // the place, and connectRowset should be replaced with ensureRowSetConnection + + // get the fields of the object + + if ( m_aConnection.is() && !m_aObjectName.isEmpty() ) + { + Reference< XComponent > xKeepFieldsAlive; + Reference< XNameAccess > xColumns = getFieldsByCommandDescriptor( m_aConnection, m_nObjectType, m_aObjectName,xKeepFieldsAlive ); + if ( xColumns.is() ) + addToList(xColumns); + } + + // set prefix + OUString aPrefix; + + switch (m_nObjectType) + { + case CommandType::TABLE: + aPrefix = SvxResId(RID_RSC_TABWIN_PREFIX[0]); + break; + case CommandType::QUERY: + aPrefix = SvxResId(RID_RSC_TABWIN_PREFIX[1]); + break; + default: + aPrefix = SvxResId(RID_RSC_TABWIN_PREFIX[2]); + break; + } + + // listen for changes at ControlSource in PropertySet + if (m_xChangeListener.is()) + { + m_xChangeListener->dispose(); + m_xChangeListener.clear(); + } + m_xChangeListener = new ::comphelper::OPropertyChangeMultiplexer(this, xSet); + m_xChangeListener->addProperty(FM_PROP_DATASOURCE); + m_xChangeListener->addProperty(FM_PROP_COMMAND); + m_xChangeListener->addProperty(FM_PROP_COMMANDTYPE); + + // set title + aTitle += " " + aPrefix + " " + m_aObjectName; + m_xDialog->set_title(aTitle); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx", "FmTabWin::UpdateContent" ); + } +} + +void FmFieldWin::FillInfo( SfxChildWinInfo& rInfo ) const +{ + rInfo.bVisible = false; +} + +SFX_IMPL_MODELESSDIALOGCONTOLLER(FmFieldWinMgr, SID_FM_ADD_FIELD) + +FmFieldWinMgr::FmFieldWinMgr(vcl::Window* _pParent, sal_uInt16 _nId, + SfxBindings* _pBindings, SfxChildWinInfo const * _pInfo) + :SfxChildWindow(_pParent, _nId) +{ + auto xDlg = std::make_shared<FmFieldWin>(_pBindings, this, _pParent->GetFrameWeld()); + SetController(xDlg); + SetHideNotDelete(true); + xDlg->Initialize(_pInfo); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/tbxform.cxx b/svx/source/form/tbxform.cxx new file mode 100644 index 000000000..837aa154a --- /dev/null +++ b/svx/source/form/tbxform.cxx @@ -0,0 +1,257 @@ +/* -*- 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 <svl/intitem.hxx> +#include <svl/eitem.hxx> +#include <svl/stritem.hxx> +#include <vcl/event.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/settings.hxx> + +#include <svx/dialmgr.hxx> +#include <svx/labelitemwindow.hxx> +#include <svx/svxids.hrc> +#include <svx/strings.hrc> +#include <tbxform.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; + +SvxFmAbsRecWin::SvxFmAbsRecWin(vcl::Window* pParent, SfxToolBoxControl* pController) + : InterimItemWindow(pParent, "svx/ui/absrecbox.ui", "AbsRecBox") + , m_xWidget(m_xBuilder->weld_entry("entry")) + , m_pController(pController) +{ + m_xWidget->connect_key_press(LINK(this, SvxFmAbsRecWin, KeyInputHdl)); + m_xWidget->connect_activate(LINK(this, SvxFmAbsRecWin, ActivatedHdl)); + m_xWidget->connect_focus_out(LINK(this, SvxFmAbsRecWin, FocusOutHdl)); + + SetSizePixel(m_xWidget->get_preferred_size()); +} + +void SvxFmAbsRecWin::dispose() +{ + m_xWidget.reset(); + InterimItemWindow::dispose(); +} + +void SvxFmAbsRecWin::GetFocus() +{ + if (m_xWidget) + m_xWidget->grab_focus(); + InterimItemWindow::GetFocus(); +} + +SvxFmAbsRecWin::~SvxFmAbsRecWin() +{ + disposeOnce(); +} + +void SvxFmAbsRecWin::FirePosition( bool _bForce ) +{ + if (!_bForce && !m_xWidget->get_value_changed_from_saved()) + return; + + sal_Int64 nRecord = m_xWidget->get_text().toInt64(); + if (nRecord < 1) + nRecord = 1; + + SfxInt32Item aPositionParam( FN_PARAM_1, static_cast<sal_Int32>(nRecord) ); + + Any a; + Sequence< PropertyValue > aArgs( 1 ); + aArgs[0].Name = "Position"; + aPositionParam.QueryValue( a ); + aArgs[0].Value = a; + m_pController->Dispatch( ".uno:AbsoluteRecord", + aArgs ); + m_pController->updateStatus(); + + m_xWidget->save_value(); +} + +IMPL_LINK_NOARG(SvxFmAbsRecWin, FocusOutHdl, weld::Widget&, void) +{ + FirePosition( false ); +} + +IMPL_LINK(SvxFmAbsRecWin, KeyInputHdl, const KeyEvent&, rKEvt, bool) +{ + return ChildKeyInput(rKEvt); +} + +IMPL_LINK_NOARG(SvxFmAbsRecWin, ActivatedHdl, weld::Entry&, bool) +{ + if (!m_xWidget->get_text().isEmpty()) + FirePosition( true ); + return true; +} + +SFX_IMPL_TOOLBOX_CONTROL( SvxFmTbxCtlAbsRec, SfxInt32Item ); + +SvxFmTbxCtlAbsRec::SvxFmTbxCtlAbsRec( sal_uInt16 nSlotId, sal_uInt16 nId, ToolBox& rTbx ) + :SfxToolBoxControl( nSlotId, nId, rTbx ) +{ +} + + +SvxFmTbxCtlAbsRec::~SvxFmTbxCtlAbsRec() +{ +} + +void SvxFmTbxCtlAbsRec::StateChanged( sal_uInt16 nSID, SfxItemState eState, const SfxPoolItem* pState ) +{ + sal_uInt16 nId = GetId(); + ToolBox* pToolBox = &GetToolBox(); + SvxFmAbsRecWin* pWin = static_cast<SvxFmAbsRecWin*>( pToolBox->GetItemWindow(nId) ); + + assert(pWin && "Control not found!"); + + if (pState) + { + const SfxInt32Item* pItem = dynamic_cast< const SfxInt32Item* >( pState ); + DBG_ASSERT( pItem, "SvxFmTbxCtlAbsRec::StateChanged: invalid item!" ); + pWin->set_text(OUString::number(pItem ? pItem->GetValue() : -1)); + } + + bool bEnable = SfxItemState::DISABLED != eState && pState; + if (!bEnable) + pWin->set_text(OUString()); + + + // enabling/disabling of the window + pToolBox->EnableItem(nId, bEnable); + SfxToolBoxControl::StateChanged( nSID, eState,pState ); +} + +VclPtr<InterimItemWindow> SvxFmTbxCtlAbsRec::CreateItemWindow( vcl::Window* pParent ) +{ + return VclPtrInstance<SvxFmAbsRecWin>(pParent, this); +} + +SFX_IMPL_TOOLBOX_CONTROL( SvxFmTbxCtlRecText, SfxBoolItem ); + +SvxFmTbxCtlRecText::SvxFmTbxCtlRecText( sal_uInt16 nSlotId, sal_uInt16 nId, ToolBox& rTbx ) + :SfxToolBoxControl( nSlotId, nId, rTbx ) +{ + rTbx.SetItemWindowNonInteractive(nId, true); +} + +SvxFmTbxCtlRecText::~SvxFmTbxCtlRecText() +{ +} + +VclPtr<InterimItemWindow> SvxFmTbxCtlRecText::CreateItemWindow( vcl::Window* pParent ) +{ + OUString aText(SvxResId(RID_STR_REC_TEXT)); + VclPtrInstance<LabelItemWindow> xFixedText(pParent, aText); + + xFixedText->Show(); + + return xFixedText; +} + +SFX_IMPL_TOOLBOX_CONTROL( SvxFmTbxCtlRecFromText, SfxBoolItem ); + +SvxFmTbxCtlRecFromText::SvxFmTbxCtlRecFromText( sal_uInt16 nSlotId, sal_uInt16 nId, ToolBox& rTbx ) + :SfxToolBoxControl( nSlotId, nId, rTbx ) +{ + rTbx.SetItemWindowNonInteractive(nId, true); +} + +SvxFmTbxCtlRecFromText::~SvxFmTbxCtlRecFromText() +{ +} + +VclPtr<InterimItemWindow> SvxFmTbxCtlRecFromText::CreateItemWindow( vcl::Window* pParent ) +{ + OUString aText(SvxResId(RID_STR_REC_FROM_TEXT)); + VclPtrInstance<LabelItemWindow> xFixedText(pParent, aText); + + xFixedText->Show(); + + return xFixedText; +} + +SFX_IMPL_TOOLBOX_CONTROL( SvxFmTbxCtlRecTotal, SfxStringItem ); + +SvxFmTbxCtlRecTotal::SvxFmTbxCtlRecTotal( sal_uInt16 nSlotId, sal_uInt16 nId, ToolBox& rTbx ) + : SfxToolBoxControl( nSlotId, nId, rTbx ) +{ + rTbx.SetItemWindowNonInteractive(nId, true); +} + +SvxFmTbxCtlRecTotal::~SvxFmTbxCtlRecTotal() +{ +} + +VclPtr<InterimItemWindow> SvxFmTbxCtlRecTotal::CreateItemWindow( vcl::Window* pParent ) +{ + OUString const aSample("123456"); + m_xFixedText.reset(VclPtr<LabelItemWindow>::Create(pParent, aSample)); + m_xFixedText->set_label(""); + + m_xFixedText->Show(); + + return m_xFixedText; +} + +void SvxFmTbxCtlRecTotal::StateChanged( sal_uInt16 nSID, SfxItemState eState, const SfxPoolItem* pState ) +{ + // setting the FixedText + if (GetSlotId() != SID_FM_RECORD_TOTAL) + return; + + OUString aText; + if (pState) + aText = static_cast<const SfxStringItem*>(pState)->GetValue(); + else + aText = "?"; + + m_xFixedText->set_label(aText); + + SfxToolBoxControl::StateChanged( nSID, eState,pState ); +} + +SFX_IMPL_TOOLBOX_CONTROL( SvxFmTbxNextRec, SfxBoolItem ); + + +SvxFmTbxNextRec::SvxFmTbxNextRec( sal_uInt16 nSlotId, sal_uInt16 nId, ToolBox& rTbx ) + :SfxToolBoxControl( nSlotId, nId, rTbx ) +{ + rTbx.SetItemBits(nId, rTbx.GetItemBits(nId) | ToolBoxItemBits::REPEAT); + + AllSettings aSettings = rTbx.GetSettings(); + MouseSettings aMouseSettings = aSettings.GetMouseSettings(); + aMouseSettings.SetButtonRepeat(aMouseSettings.GetButtonRepeat() / 4); + aSettings.SetMouseSettings(aMouseSettings); + rTbx.SetSettings(aSettings, true); +} + +SFX_IMPL_TOOLBOX_CONTROL( SvxFmTbxPrevRec, SfxBoolItem ); + + +SvxFmTbxPrevRec::SvxFmTbxPrevRec( sal_uInt16 nSlotId, sal_uInt16 nId, ToolBox& rTbx ) + :SfxToolBoxControl( nSlotId, nId, rTbx ) +{ + rTbx.SetItemBits(nId, rTbx.GetItemBits(nId) | ToolBoxItemBits::REPEAT); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/typemap.cxx b/svx/source/form/typemap.cxx new file mode 100644 index 000000000..b169b745c --- /dev/null +++ b/svx/source/form/typemap.cxx @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_options.h> + +#include <sfx2/objitem.hxx> +#include <sfx2/msg.hxx> +#include <svl/memberid.h> +#include <editeng/wghtitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/postitem.hxx> +#include <svx/clipfmtitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/autokernitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/emphasismarkitem.hxx> + +#include <editeng/memberids.h> +#define SFX_TYPEMAP +#include <svxslots.hxx> + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/form/xfm_addcondition.cxx b/svx/source/form/xfm_addcondition.cxx new file mode 100644 index 000000000..6c8d6ef35 --- /dev/null +++ b/svx/source/form/xfm_addcondition.cxx @@ -0,0 +1,157 @@ +/* -*- 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 <xfm_addcondition.hxx> + +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <comphelper/processfactory.hxx> +#include <vcl/svapp.hxx> +#include <datanavi.hxx> +#include <fmservs.hxx> + +namespace svxform +{ + +#define PROPERTY_ID_BINDING 5724 +#define PROPERTY_ID_FORM_MODEL 5725 +#define PROPERTY_ID_FACET_NAME 5726 +#define PROPERTY_ID_CONDITION_VALUE 5727 + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::xforms; + + + //= OAddConditionDialog + + + Reference< XInterface > OAddConditionDialog_Create( const Reference< XMultiServiceFactory > & _rxORB ) + { + return OAddConditionDialog::Create( _rxORB ); + } + + + Sequence< OUString > OAddConditionDialog_GetSupportedServiceNames() + { + return { "com.sun.star.xforms.ui.dialogs.AddCondition" }; + } + + + OUString OAddConditionDialog_GetImplementationName() + { + return "org.openoffice.comp.svx.OAddConditionDialog"; + } + + OAddConditionDialog::OAddConditionDialog( const Reference< XComponentContext >& _rxORB ) + :OAddConditionDialogBase( _rxORB ) + { + registerProperty( + "Binding", + PROPERTY_ID_BINDING, + PropertyAttribute::TRANSIENT, + &m_xBinding, + cppu::UnoType<decltype(m_xBinding)>::get() + ); + + registerProperty( + "FacetName", + PROPERTY_ID_FACET_NAME, + PropertyAttribute::TRANSIENT, + &m_sFacetName, + cppu::UnoType<decltype(m_sFacetName)>::get() + ); + + registerProperty( + "ConditionValue", + PROPERTY_ID_CONDITION_VALUE, + PropertyAttribute::TRANSIENT, + &m_sConditionValue, + cppu::UnoType<decltype(m_sConditionValue)>::get() + ); + + registerProperty( + "FormModel", + PROPERTY_ID_FORM_MODEL, + PropertyAttribute::TRANSIENT, + &m_xWorkModel, + cppu::UnoType<decltype(m_xWorkModel)>::get() + ); + } + + + Sequence<sal_Int8> SAL_CALL OAddConditionDialog::getImplementationId( ) + { + return css::uno::Sequence<sal_Int8>(); + } + + + Reference< XInterface > OAddConditionDialog::Create( const Reference< XMultiServiceFactory >& _rxFactory ) + { + return *( new OAddConditionDialog( comphelper::getComponentContext(_rxFactory) ) ); + } + + + OUString SAL_CALL OAddConditionDialog::getImplementationName() + { + return OAddConditionDialog_GetImplementationName(); + } + + + Sequence< OUString > SAL_CALL OAddConditionDialog::getSupportedServiceNames() + { + return OAddConditionDialog_GetSupportedServiceNames(); + } + + + Reference<XPropertySetInfo> SAL_CALL OAddConditionDialog::getPropertySetInfo() + { + return createPropertySetInfo( getInfoHelper() ); + } + + ::cppu::IPropertyArrayHelper& OAddConditionDialog::getInfoHelper() + { + return *getArrayHelper(); + } + + ::cppu::IPropertyArrayHelper* OAddConditionDialog::createArrayHelper( ) const + { + Sequence< Property > aProperties; + describeProperties( aProperties ); + return new ::cppu::OPropertyArrayHelper( aProperties ); + } + + std::unique_ptr<weld::DialogController> OAddConditionDialog::createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) + { + if ( !m_xBinding.is() || m_sFacetName.isEmpty() ) + throw RuntimeException( OUString(), *this ); + + return std::make_unique<AddConditionDialog>(Application::GetFrameWeld(rParent), m_sFacetName, m_xBinding); + } + + void OAddConditionDialog::executedDialog( sal_Int16 _nExecutionResult ) + { + OAddConditionDialogBase::executedDialog( _nExecutionResult ); + if ( _nExecutionResult == RET_OK ) + m_sConditionValue = static_cast<AddConditionDialog*>(m_xDialog.get())->GetCondition(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |