diff options
Diffstat (limited to 'extensions/source')
305 files changed, 78313 insertions, 0 deletions
diff --git a/extensions/source/abpilot/abp.component b/extensions/source/abpilot/abp.component new file mode 100644 index 0000000000..91b9638c42 --- /dev/null +++ b/extensions/source/abpilot/abp.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="org.openoffice.comp.abp.OAddressBookSourcePilot" + constructor="org_openoffice_comp_abp_OAddressBookSourcePilot"> + <service name="com.sun.star.ui.dialogs.AddressBookSourcePilot"/> + </implementation> +</component> diff --git a/extensions/source/abpilot/abpfinalpage.cxx b/extensions/source/abpilot/abpfinalpage.cxx new file mode 100644 index 0000000000..78756b28b5 --- /dev/null +++ b/extensions/source/abpilot/abpfinalpage.cxx @@ -0,0 +1,227 @@ +/* -*- 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 "abpfinalpage.hxx" +#include "addresssettings.hxx" +#include "abspilot.hxx" +#include <osl/diagnose.h> +#include <tools/urlobj.hxx> +#include <svtools/inettbc.hxx> +#include <unotools/ucbhelper.hxx> +#include <unotools/pathoptions.hxx> +#include <svl/filenotation.hxx> +#include <sfx2/docfilt.hxx> +#include <o3tl/string_view.hxx> + +namespace abp +{ + + using namespace ::svt; + using namespace ::utl; + + static std::shared_ptr<const SfxFilter> lcl_getBaseFilter() + { + std::shared_ptr<const SfxFilter> pFilter = SfxFilter::GetFilterByName("StarOffice XML (Base)"); + OSL_ENSURE(pFilter,"Filter: StarOffice XML (Base) could not be found!"); + return pFilter; + } + + FinalPage::FinalPage(weld::Container* pPage, OAddressBookSourcePilot* pWizard) + : AddressBookSourcePage(pPage, pWizard, "modules/sabpilot/ui/datasourcepage.ui", + "DataSourcePage") + , m_xLocation(new SvtURLBox(m_xBuilder->weld_combo_box("location"))) + , m_xBrowse(m_xBuilder->weld_button("browse")) + , m_xRegisterName(m_xBuilder->weld_check_button("available")) + , m_xEmbed(m_xBuilder->weld_check_button("embed")) + , m_xNameLabel(m_xBuilder->weld_label("nameft")) + , m_xLocationLabel(m_xBuilder->weld_label("locationft")) + , m_xName(m_xBuilder->weld_entry("name")) + , m_xDuplicateNameError(m_xBuilder->weld_label("warning")) + { + m_xLocation->SetSmartProtocol(INetProtocol::File); + m_xLocation->DisableHistory(); + + m_xLocationController.reset( new svx::DatabaseLocationInputController(pWizard->getORB(), + *m_xLocation, *m_xBrowse, *pWizard->getDialog()) ); + + m_xName->connect_changed( LINK(this, FinalPage, OnEntryNameModified) ); + m_xLocation->connect_changed( LINK(this, FinalPage, OnComboNameModified) ); + m_xRegisterName->connect_toggled( LINK( this, FinalPage, OnRegister ) ); + m_xRegisterName->set_active(true); + m_xEmbed->connect_toggled( LINK( this, FinalPage, OnEmbed ) ); + m_xEmbed->set_active(true); + } + + FinalPage::~FinalPage() + { + m_xLocationController.reset(); + } + + bool FinalPage::isValidName() const + { + OUString sCurrentName(m_xName->get_text()); + + if (sCurrentName.isEmpty()) + // the name must not be empty + return false; + + if ( m_aInvalidDataSourceNames.find( sCurrentName ) != m_aInvalidDataSourceNames.end() ) + // there already is a data source with this name + return false; + + return true; + } + + void FinalPage::setFields() + { + AddressSettings& rSettings = getSettings(); + + INetURLObject aURL( rSettings.sDataSourceName ); + if( aURL.GetProtocol() == INetProtocol::NotValid ) + { + OUString sPath = SvtPathOptions().GetWorkPath() + + "/" + rSettings.sDataSourceName; + + std::shared_ptr<const SfxFilter> pFilter = lcl_getBaseFilter(); + if ( pFilter ) + { + OUString sExt = pFilter->GetDefaultExtension(); + sPath += o3tl::getToken(sExt,1,'*'); + } + + aURL.SetURL(sPath); + } + OSL_ENSURE( aURL.GetProtocol() != INetProtocol::NotValid ,"No valid file name!"); + rSettings.sDataSourceName = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + m_xLocationController->setURL( rSettings.sDataSourceName ); + OUString sName = aURL.getName( ); + sal_Int32 nPos = sName.indexOf(aURL.GetFileExtension()); + if ( nPos != -1 ) + { + sName = sName.replaceAt(nPos-1, 4, u""); + } + m_xName->set_text(sName); + + OnRegister(*m_xRegisterName); + } + + + void FinalPage::initializePage() + { + AddressBookSourcePage::initializePage(); + + setFields(); + } + + bool FinalPage::commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) + { + if (!AddressBookSourcePage::commitPage(_eReason)) + return false; + + if ( ( ::vcl::WizardTypes::eTravelBackward != _eReason ) + && ( !m_xLocationController->prepareCommit() ) + ) + return false; + + AddressSettings& rSettings = getSettings(); + rSettings.sDataSourceName = m_xLocationController->getURL(); + rSettings.bRegisterDataSource = m_xRegisterName->get_active(); + if ( rSettings.bRegisterDataSource ) + rSettings.sRegisteredDataSourceName = m_xName->get_text(); + rSettings.bEmbedDataSource = m_xEmbed->get_active(); + + return true; + } + + void FinalPage::Activate() + { + AddressBookSourcePage::Activate(); + + // get the names of all data sources + ODataSourceContext aContext( getORB() ); + aContext.getDataSourceNames( m_aInvalidDataSourceNames ); + + // give the name edit the focus + m_xLocation->grab_focus(); + + // default the finish button + getDialog()->defaultButton( WizardButtonFlags::FINISH ); + + OnEmbed(*m_xEmbed); + } + + void FinalPage::Deactivate() + { + AddressBookSourcePage::Deactivate(); + + // default the "next" button, again + getDialog()->defaultButton( WizardButtonFlags::NEXT ); + // disable the finish button + getDialog()->enableButtons( WizardButtonFlags::FINISH, false ); + } + + + bool FinalPage::canAdvance() const + { + return false; + } + + void FinalPage::implCheckName() + { + bool bValidName = isValidName(); + bool bEmptyName = m_xName->get_text().isEmpty(); + bool bEmptyLocation = m_xLocation->get_active_text().isEmpty(); + + // enable or disable the finish button + getDialog()->enableButtons( WizardButtonFlags::FINISH, !bEmptyLocation && (!m_xRegisterName->get_active() || bValidName) ); + + // show the error message for an invalid name + m_xDuplicateNameError->set_visible(!bValidName && !bEmptyName); + } + + IMPL_LINK_NOARG( FinalPage, OnEntryNameModified, weld::Entry&, void ) + { + implCheckName(); + } + + IMPL_LINK_NOARG( FinalPage, OnComboNameModified, weld::ComboBox&, void ) + { + implCheckName(); + } + + IMPL_LINK_NOARG(FinalPage, OnRegister, weld::Toggleable&, void) + { + bool bEnable = m_xRegisterName->get_active(); + m_xNameLabel->set_sensitive(bEnable); + m_xName->set_sensitive(bEnable); + implCheckName(); + } + + IMPL_LINK_NOARG(FinalPage, OnEmbed, weld::Toggleable&, void) + { + bool bEmbed = m_xEmbed->get_active(); + m_xLocationLabel->set_sensitive(!bEmbed); + m_xLocation->set_sensitive(!bEmbed); + m_xBrowse->set_sensitive(!bEmbed); + } + +} // namespace abp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/abpfinalpage.hxx b/extensions/source/abpilot/abpfinalpage.hxx new file mode 100644 index 0000000000..6c6c5d69d9 --- /dev/null +++ b/extensions/source/abpilot/abpfinalpage.hxx @@ -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 . + */ + +#pragma once + +#include "abspage.hxx" +#include "abptypes.hxx" + +#include <svx/databaselocationinput.hxx> +#include <vcl/vclptr.hxx> + +namespace abp +{ + + class FinalPage final : public AddressBookSourcePage + { + std::unique_ptr<SvtURLBox> m_xLocation; + std::unique_ptr<weld::Button> m_xBrowse; + std::unique_ptr<weld::CheckButton> m_xRegisterName; + std::unique_ptr<weld::CheckButton> m_xEmbed; + std::unique_ptr<weld::Label> m_xNameLabel; + std::unique_ptr<weld::Label> m_xLocationLabel; + std::unique_ptr<weld::Entry> m_xName; + std::unique_ptr<weld::Label> m_xDuplicateNameError; + + std::unique_ptr<svx::DatabaseLocationInputController> + m_xLocationController; + + StringBag m_aInvalidDataSourceNames; + + public: + explicit FinalPage(weld::Container* pPage, OAddressBookSourcePilot* pController); + virtual ~FinalPage() override; + + private: + // OWizardPage overridables + virtual void initializePage() override; + virtual bool commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) override; + + // BuilderPage overridables + virtual void Activate() override; + virtual void Deactivate() override; + + // OImportPage overridables + virtual bool canAdvance() const override; + + DECL_LINK(OnEntryNameModified, weld::Entry&, void); + DECL_LINK(OnComboNameModified, weld::ComboBox&, void); + DECL_LINK(OnRegister, weld::Toggleable&, void); + DECL_LINK(OnEmbed, weld::Toggleable&, void); + + bool isValidName() const; + void implCheckName(); + void setFields(); + }; + +} // namespace abp +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/abptypes.hxx b/extensions/source/abpilot/abptypes.hxx new file mode 100644 index 0000000000..0339253ffa --- /dev/null +++ b/extensions/source/abpilot/abptypes.hxx @@ -0,0 +1,40 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <map> +#include <set> + +#include <rtl/ustring.hxx> + + +namespace abp +{ + + + typedef std::set<OUString> StringBag; + + typedef std::map<OUString, OUString> MapString2String; + + +} // namespace abp +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/abspage.cxx b/extensions/source/abpilot/abspage.cxx new file mode 100644 index 0000000000..4ea1f3dded --- /dev/null +++ b/extensions/source/abpilot/abspage.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 "abspage.hxx" +#include "abspilot.hxx" + +namespace abp +{ + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + + AddressBookSourcePage::AddressBookSourcePage(weld::Container* pPage, OAddressBookSourcePilot* pDialog, const OUString& rUIXMLDescription, const OUString& rID) + : AddressBookSourcePage_Base(pPage, pDialog, rUIXMLDescription, rID) + , m_pDialog(pDialog) + { + } + + void AddressBookSourcePage::Activate() + { + AddressBookSourcePage_Base::Activate(); + m_pDialog->updateTravelUI(); + } + + void AddressBookSourcePage::Deactivate() + { + AddressBookSourcePage_Base::Deactivate(); + m_pDialog->enableButtons(WizardButtonFlags::NEXT, true); + } + + OAddressBookSourcePilot* AddressBookSourcePage::getDialog() + { + return m_pDialog; + } + + const OAddressBookSourcePilot* AddressBookSourcePage::getDialog() const + { + return m_pDialog; + } + + AddressSettings& AddressBookSourcePage::getSettings() + { + return m_pDialog->getSettings(); + } + + const AddressSettings& AddressBookSourcePage::getSettings() const + { + return m_pDialog->getSettings(); + } + + const Reference< XComponentContext > & AddressBookSourcePage::getORB() const + { + return m_pDialog->getORB(); + } + +} // namespace abp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/abspage.hxx b/extensions/source/abpilot/abspage.hxx new file mode 100644 index 0000000000..407713fab9 --- /dev/null +++ b/extensions/source/abpilot/abspage.hxx @@ -0,0 +1,56 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/wizardmachine.hxx> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <strings.hrc> +#include <componentmodule.hxx> + +namespace abp +{ + class OAddressBookSourcePilot; + struct AddressSettings; + + typedef ::vcl::OWizardPage AddressBookSourcePage_Base; + /// the base class for all tab pages in the address book source wizard + class AddressBookSourcePage : public AddressBookSourcePage_Base + { + OAddressBookSourcePilot* m_pDialog; + + protected: + AddressBookSourcePage(weld::Container* pPage, OAddressBookSourcePilot* pController, const OUString& rUIXMLDescription, const OUString& rID); + + protected: + // helper + OAddressBookSourcePilot* getDialog(); + const OAddressBookSourcePilot* getDialog() const; + const css::uno::Reference< css::uno::XComponentContext > & + getORB() const; + AddressSettings& getSettings(); + const AddressSettings& getSettings() const; + + // BuilderPage overridables + virtual void Activate() override; + virtual void Deactivate() override; + }; +} // namespace abp +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/abspilot.cxx b/extensions/source/abpilot/abspilot.cxx new file mode 100644 index 0000000000..1b04a34ceb --- /dev/null +++ b/extensions/source/abpilot/abspilot.cxx @@ -0,0 +1,449 @@ +/* -*- 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 "abspilot.hxx" +#include <helpids.h> +#include <strings.hrc> +#include <componentmodule.hxx> +#include <tools/debug.hxx> +#include "typeselectionpage.hxx" +#include "admininvokationpage.hxx" +#include "tableselectionpage.hxx" +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <osl/diagnose.h> +#include "abpfinalpage.hxx" +#include "fieldmappingpage.hxx" +#include "fieldmappingimpl.hxx" + +using vcl::RoadmapWizardTypes::PathId; + +namespace abp +{ + + +#define STATE_SELECT_ABTYPE 0 +#define STATE_INVOKE_ADMIN_DIALOG 1 +#define STATE_TABLE_SELECTION 2 +#define STATE_MANUAL_FIELD_MAPPING 3 +#define STATE_FINAL_CONFIRM 4 + +#define PATH_COMPLETE 1 +#define PATH_NO_SETTINGS 2 +#define PATH_NO_FIELDS 3 +#define PATH_NO_SETTINGS_NO_FIELDS 4 + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + + OAddressBookSourcePilot::OAddressBookSourcePilot(weld::Window* _pParent, const Reference< XComponentContext >& _rxORB) + :OAddressBookSourcePilot_Base( _pParent ) + ,m_xORB(_rxORB) + ,m_aNewDataSource(_rxORB) + ,m_eNewDataSourceType( AST_INVALID ) + { + declarePath( PATH_COMPLETE, + {STATE_SELECT_ABTYPE, + STATE_INVOKE_ADMIN_DIALOG, + STATE_TABLE_SELECTION, + STATE_MANUAL_FIELD_MAPPING, + STATE_FINAL_CONFIRM} + ); + declarePath( PATH_NO_SETTINGS, + {STATE_SELECT_ABTYPE, + STATE_TABLE_SELECTION, + STATE_MANUAL_FIELD_MAPPING, + STATE_FINAL_CONFIRM} + ); + declarePath( PATH_NO_FIELDS, + {STATE_SELECT_ABTYPE, + STATE_INVOKE_ADMIN_DIALOG, + STATE_TABLE_SELECTION, + STATE_FINAL_CONFIRM} + ); + declarePath( PATH_NO_SETTINGS_NO_FIELDS, + {STATE_SELECT_ABTYPE, + STATE_TABLE_SELECTION, + STATE_FINAL_CONFIRM} + ); + + m_xPrevPage->set_help_id(HID_ABSPILOT_PREVIOUS); + m_xNextPage->set_help_id(HID_ABSPILOT_NEXT); + m_xCancel->set_help_id(HID_ABSPILOT_CANCEL); + m_xFinish->set_help_id(HID_ABSPILOT_FINISH); + m_xHelp->set_help_id(UID_ABSPILOT_HELP); + + // some initial settings +#ifdef UNX +#ifdef MACOSX + m_aSettings.eType = AST_MACAB; +#else +// FIXME: if KDE use KAB instead + m_aSettings.eType = AST_EVOLUTION; +#endif +#else + m_aSettings.eType = AST_OTHER; +#endif + m_aSettings.sDataSourceName = compmodule::ModuleRes(RID_STR_DEFAULT_NAME); + m_aSettings.bRegisterDataSource = false; + m_aSettings.bEmbedDataSource = false; + m_aSettings.bIgnoreNoTable = false; + + defaultButton(WizardButtonFlags::NEXT); + enableButtons(WizardButtonFlags::FINISH, false); + ActivatePage(); + m_xAssistant->set_current_page(0); + + typeSelectionChanged( m_aSettings.eType ); + + OUString sDialogTitle = compmodule::ModuleRes(RID_STR_ABSOURCEDIALOGTITLE); + setTitleBase(sDialogTitle); + m_xAssistant->set_help_id(HID_ABSPILOT); + } + + OUString OAddressBookSourcePilot::getStateDisplayName( WizardState _nState ) const + { + TranslateId pResId; + switch ( _nState ) + { + case STATE_SELECT_ABTYPE: pResId = RID_STR_SELECT_ABTYPE; break; + case STATE_INVOKE_ADMIN_DIALOG: pResId = RID_STR_INVOKE_ADMIN_DIALOG; break; + case STATE_TABLE_SELECTION: pResId = RID_STR_TABLE_SELECTION; break; + case STATE_MANUAL_FIELD_MAPPING: pResId = RID_STR_MANUAL_FIELD_MAPPING; break; + case STATE_FINAL_CONFIRM: pResId = RID_STR_FINAL_CONFIRM; break; + } + DBG_ASSERT( pResId, "OAddressBookSourcePilot::getStateDisplayName: don't know this state!" ); + + OUString sDisplayName; + if (pResId) + { + sDisplayName = compmodule::ModuleRes(pResId); + } + + return sDisplayName; + } + + void OAddressBookSourcePilot::implCommitAll() + { + // in real, the data source already exists in the data source context + // Thus, if the user changed the name, we have to rename the data source + if ( m_aSettings.sDataSourceName != m_aNewDataSource.getName() ) + m_aNewDataSource.rename( m_aSettings.sDataSourceName ); + + // 1. the data source + m_aNewDataSource.store(m_aSettings); + + // 2. check if we need to register the data source + if ( m_aSettings.bRegisterDataSource ) + m_aNewDataSource.registerDataSource(m_aSettings.sRegisteredDataSourceName); + + // 3. write the data source / table names into the configuration + addressconfig::writeTemplateAddressSource( getORB(), m_aSettings.bRegisterDataSource ? m_aSettings.sRegisteredDataSourceName : m_aSettings.sDataSourceName, m_aSettings.sSelectedTable ); + + // 4. write the field mapping + fieldmapping::writeTemplateAddressFieldMapping( getORB(), std::map(m_aSettings.aFieldMapping) ); + } + + void OAddressBookSourcePilot::implCleanup() + { + if ( m_aNewDataSource.isValid() ) + m_aNewDataSource.remove(); + } + + short OAddressBookSourcePilot::run() + { + short nRet = OAddressBookSourcePilot_Base::run(); + + implCleanup(); + + return nRet; + } + + bool OAddressBookSourcePilot::onFinish() + { + if ( !OAddressBookSourcePilot_Base::onFinish() ) + return false; + + implCommitAll(); + + addressconfig::markPilotSuccess( getORB() ); + + return true; + } + + void OAddressBookSourcePilot::enterState( WizardState _nState ) + { + switch ( _nState ) + { + case STATE_SELECT_ABTYPE: + impl_updateRoadmap( static_cast< TypeSelectionPage* >( GetPage( STATE_SELECT_ABTYPE ) )->getSelectedType() ); + break; + + case STATE_FINAL_CONFIRM: + if ( !needManualFieldMapping( ) ) + implDoAutoFieldMapping(); + break; + + case STATE_TABLE_SELECTION: + implDefaultTableName(); + break; + } + + OAddressBookSourcePilot_Base::enterState(_nState); + } + + + bool OAddressBookSourcePilot::prepareLeaveCurrentState( CommitPageReason _eReason ) + { + if ( !OAddressBookSourcePilot_Base::prepareLeaveCurrentState( _eReason ) ) + return false; + + if ( _eReason == vcl::WizardTypes::eTravelBackward ) + return true; + + bool bAllow = true; + + switch ( getCurrentState() ) + { + case STATE_SELECT_ABTYPE: + implCreateDataSource(); + if ( needAdminInvokationPage() ) + break; + [[fallthrough]]; + + case STATE_INVOKE_ADMIN_DIALOG: + if ( !connectToDataSource( false ) ) + { + // connecting did not succeed -> do not allow proceeding + bAllow = false; + break; + } + + + // now that we connected to the data source, check whether we need the "table selection" page + const StringBag& aTables = m_aNewDataSource.getTableNames(); + + if ( aTables.empty() ) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xAssistant.get(), + VclMessageType::Question, VclButtonsType::YesNo, + compmodule::ModuleRes(getSettings().eType == AST_EVOLUTION_GROUPWISE ? RID_STR_QRY_NO_EVO_GW : RID_STR_QRY_NOTABLES))); + + if (RET_YES != xBox->run()) + { + // cannot ask the user, or the user chose to use this data source, though there are no tables + bAllow = false; + break; + } + + m_aSettings.bIgnoreNoTable = true; + } + + if ( aTables.size() == 1 ) + // remember the one and only table we have + m_aSettings.sSelectedTable = *aTables.begin(); + + break; + } + + impl_updateRoadmap( m_aSettings.eType ); + return bAllow; + } + + void OAddressBookSourcePilot::implDefaultTableName() + { + const StringBag& rTableNames = getDataSource().getTableNames(); + if ( rTableNames.end() != rTableNames.find( getSettings().sSelectedTable ) ) + // already a valid table selected + return; + + const char* pGuess = nullptr; + switch ( getSettings().eType ) + { + case AST_THUNDERBIRD : pGuess = "Personal Address book"; break; + case AST_EVOLUTION : + case AST_EVOLUTION_GROUPWISE: + case AST_EVOLUTION_LDAP : pGuess = "Personal"; break; + default: + OSL_FAIL( "OAddressBookSourcePilot::implDefaultTableName: unhandled case!" ); + return; + } + const OUString sGuess = OUString::createFromAscii( pGuess ); + if ( rTableNames.end() != rTableNames.find( sGuess ) ) + getSettings().sSelectedTable = sGuess; + } + + void OAddressBookSourcePilot::implDoAutoFieldMapping() + { + DBG_ASSERT( !needManualFieldMapping( ), "OAddressBookSourcePilot::implDoAutoFieldMapping: invalid call!" ); + + fieldmapping::defaultMapping( getORB(), m_aSettings.aFieldMapping ); + } + + void OAddressBookSourcePilot::implCreateDataSource() + { + if (m_aNewDataSource.isValid()) + { // we already have a data source object + if ( m_aSettings.eType == m_eNewDataSourceType ) + // and it already has the correct type + return; + + // it has a wrong type -> remove it + m_aNewDataSource.remove(); + } + + ODataSourceContext aContext( getORB() ); + aContext.disambiguate( m_aSettings.sDataSourceName ); + + switch (m_aSettings.eType) + { + case AST_THUNDERBIRD: + m_aNewDataSource = aContext.createNewThunderbird( m_aSettings.sDataSourceName ); + break; + + case AST_EVOLUTION: + m_aNewDataSource = aContext.createNewEvolution( m_aSettings.sDataSourceName ); + break; + + case AST_EVOLUTION_GROUPWISE: + m_aNewDataSource = aContext.createNewEvolutionGroupwise( m_aSettings.sDataSourceName ); + break; + + case AST_EVOLUTION_LDAP: + m_aNewDataSource = aContext.createNewEvolutionLdap( m_aSettings.sDataSourceName ); + break; + + case AST_KAB: + m_aNewDataSource = aContext.createNewKab( m_aSettings.sDataSourceName ); + break; + + case AST_MACAB: + m_aNewDataSource = aContext.createNewMacab( m_aSettings.sDataSourceName ); + break; + + case AST_OTHER: + m_aNewDataSource = aContext.createNewOther( m_aSettings.sDataSourceName ); + break; + + case AST_INVALID: + OSL_FAIL( "OAddressBookSourcePilot::implCreateDataSource: illegal data source type!" ); + break; + } + m_eNewDataSourceType = m_aSettings.eType; + } + + bool OAddressBookSourcePilot::connectToDataSource( bool _bForceReConnect ) + { + DBG_ASSERT( m_aNewDataSource.isValid(), "OAddressBookSourcePilot::implConnect: invalid current data source!" ); + + weld::WaitObject aWaitCursor(m_xAssistant.get()); + if ( _bForceReConnect && m_aNewDataSource.isConnected( ) ) + m_aNewDataSource.disconnect( ); + + return m_aNewDataSource.connect(m_xAssistant.get()); + } + + std::unique_ptr<BuilderPage> OAddressBookSourcePilot::createPage(WizardState _nState) + { + OUString sIdent(OUString::number(_nState)); + weld::Container* pPageContainer = m_xAssistant->append_page(sIdent); + + std::unique_ptr<vcl::OWizardPage> xRet; + + switch (_nState) + { + case STATE_SELECT_ABTYPE: + xRet = std::make_unique<TypeSelectionPage>(pPageContainer, this); + break; + case STATE_INVOKE_ADMIN_DIALOG: + xRet = std::make_unique<AdminDialogInvokationPage>(pPageContainer, this); + break; + case STATE_TABLE_SELECTION: + xRet = std::make_unique<TableSelectionPage>(pPageContainer, this); + break; + case STATE_MANUAL_FIELD_MAPPING: + xRet = std::make_unique<FieldMappingPage>(pPageContainer, this); + break; + case STATE_FINAL_CONFIRM: + xRet = std::make_unique<FinalPage>(pPageContainer, this); + break; + default: + assert(false && "OAddressBookSourcePilot::createPage: invalid state!"); + break; + } + + m_xAssistant->set_page_title(sIdent, getStateDisplayName(_nState)); + + return xRet; + } + + void OAddressBookSourcePilot::impl_updateRoadmap( AddressSourceType _eType ) + { + bool bSettingsPage = needAdminInvokationPage( _eType ); + bool bTablesPage = needTableSelection( _eType ); + bool bFieldsPage = needManualFieldMapping( _eType ); + + bool bConnected = m_aNewDataSource.isConnected(); + bool bCanSkipTables = + ( m_aNewDataSource.hasTable( m_aSettings.sSelectedTable ) + || m_aSettings.bIgnoreNoTable + ); + + enableState( STATE_INVOKE_ADMIN_DIALOG, bSettingsPage ); + + enableState( STATE_TABLE_SELECTION, + bTablesPage && ( bConnected ? !bCanSkipTables : !bSettingsPage ) + // if we do not need a settings page, we connect upon "Next" on the first page + ); + + enableState( STATE_MANUAL_FIELD_MAPPING, + bFieldsPage && bConnected && m_aNewDataSource.hasTable( m_aSettings.sSelectedTable ) + ); + + enableState( STATE_FINAL_CONFIRM, + bConnected && bCanSkipTables + ); + } + + void OAddressBookSourcePilot::typeSelectionChanged( AddressSourceType _eType ) + { + PathId nCurrentPathID( PATH_COMPLETE ); + bool bSettingsPage = needAdminInvokationPage( _eType ); + bool bFieldsPage = needManualFieldMapping( _eType ); + if ( !bSettingsPage ) + if ( !bFieldsPage ) + nCurrentPathID = PATH_NO_SETTINGS_NO_FIELDS; + else + nCurrentPathID = PATH_NO_SETTINGS; + else + if ( !bFieldsPage ) + nCurrentPathID = PATH_NO_FIELDS; + else + nCurrentPathID = PATH_COMPLETE; + activatePath( nCurrentPathID, true ); + + m_aNewDataSource.disconnect(); + m_aSettings.bIgnoreNoTable = false; + impl_updateRoadmap( _eType ); + } + +} // namespace abp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/abspilot.hxx b/extensions/source/abpilot/abspilot.hxx new file mode 100644 index 0000000000..45c965d2be --- /dev/null +++ b/extensions/source/abpilot/abspilot.hxx @@ -0,0 +1,119 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/roadmapwizard.hxx> +#include "addresssettings.hxx" +#include "datasourcehandling.hxx" + +using vcl::WizardTypes::WizardState; +using vcl::WizardTypes::CommitPageReason; + +namespace abp +{ + typedef ::vcl::RoadmapWizardMachine OAddressBookSourcePilot_Base; + class OAddressBookSourcePilot final : public OAddressBookSourcePilot_Base + { + css::uno::Reference< css::uno::XComponentContext > + m_xORB; + AddressSettings m_aSettings; + + ODataSource m_aNewDataSource; + AddressSourceType m_eNewDataSourceType; + + public: + /// ctor + OAddressBookSourcePilot( + weld::Window* _pParent, + const css::uno::Reference< css::uno::XComponentContext >& _rxORB); + + virtual short run() override; + + /// get the service factory which was used to create the dialog + const css::uno::Reference< css::uno::XComponentContext >& + getORB() const { return m_xORB; } + AddressSettings& getSettings() { return m_aSettings; } + const AddressSettings& getSettings() const { return m_aSettings; } + + const ODataSource& getDataSource() const { return m_aNewDataSource; } + + bool connectToDataSource( bool _bForceReConnect ); + + void travelNext( ) { OAddressBookSourcePilot_Base::travelNext(); } + + /// to be called when the selected type changed + void typeSelectionChanged( AddressSourceType _eType ); + + private: + // OWizardMachine overridables + virtual std::unique_ptr<BuilderPage> createPage( WizardState _nState ) override; + virtual void enterState( WizardState _nState ) override; + virtual bool prepareLeaveCurrentState( CommitPageReason _eReason ) override; + virtual bool onFinish() override; + + // RoadmapWizard + virtual OUString getStateDisplayName( WizardState _nState ) const override; + + /** creates a new data source of the type indicated by m_aSettings + <p>If another data source has been created before, this one is deleted.</p> + */ + void implCreateDataSource(); + + /// does an automatic field mapping (possible for all types except AST_OTHER) + void implDoAutoFieldMapping(); + + /// guesses a default for the table name, if no valid table is selected + void implDefaultTableName(); + + static bool needAdminInvokationPage( AddressSourceType _eType ) + { + return ( AST_OTHER == _eType ); + } + /// check if with the current settings, we would need to invoke he administration dialog for more details about the data source + bool needAdminInvokationPage() const + { + return needAdminInvokationPage( m_aSettings.eType ); + } + + static bool needManualFieldMapping( AddressSourceType _eType ) + { + return ( AST_OTHER == _eType ) || ( AST_KAB == _eType ) || + ( AST_EVOLUTION == _eType ) || ( AST_EVOLUTION_GROUPWISE == _eType ) || + ( AST_EVOLUTION_LDAP == _eType ); + } + /// checks if we need a manual (user-guided) field mapping + bool needManualFieldMapping() const + { + return needManualFieldMapping( m_aSettings.eType ); + } + + /// determines whether the given address book type does provide one table only + static bool needTableSelection( AddressSourceType _eType ) + { + return ( AST_KAB != _eType ); + } + + void implCleanup(); + void implCommitAll(); + + void impl_updateRoadmap( AddressSourceType _eType ); + }; +} // namespace abp +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/addresssettings.hxx b/extensions/source/abpilot/addresssettings.hxx new file mode 100644 index 0000000000..542a2fe67e --- /dev/null +++ b/extensions/source/abpilot/addresssettings.hxx @@ -0,0 +1,56 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include "abptypes.hxx" + + +namespace abp +{ + + enum AddressSourceType + { + AST_THUNDERBIRD, + AST_EVOLUTION, + AST_EVOLUTION_GROUPWISE, + AST_EVOLUTION_LDAP, + AST_KAB, + AST_MACAB, + AST_OTHER, + + AST_INVALID + }; + + struct AddressSettings + { + AddressSourceType eType; + OUString sDataSourceName; + OUString sRegisteredDataSourceName; + OUString sSelectedTable; + bool bIgnoreNoTable; + MapString2String aFieldMapping; + bool bRegisterDataSource; + bool bEmbedDataSource; + }; + + +} // namespace abp +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/admininvokationimpl.cxx b/extensions/source/abpilot/admininvokationimpl.cxx new file mode 100644 index 0000000000..7f7498cbea --- /dev/null +++ b/extensions/source/abpilot/admininvokationimpl.cxx @@ -0,0 +1,117 @@ +/* -*- 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 "admininvokationimpl.hxx" +#include <comphelper/diagnose_ex.hxx> +#include <tools/debug.hxx> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/sdbc/DriverManager.hpp> +#include <comphelper/propertysequence.hxx> +#include <strings.hrc> +#include <componentmodule.hxx> +#include <utility> +#include <vcl/stdtext.hxx> +#include <vcl/weld.hxx> + +namespace abp +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::awt; + using namespace ::com::sun::star::ui::dialogs; + using namespace ::com::sun::star::sdbc; + + OAdminDialogInvokation::OAdminDialogInvokation(const Reference< XComponentContext >& _rxContext, + css::uno::Reference< css::beans::XPropertySet > _xDataSource, + weld::Window* _pMessageParent) + :m_xContext(_rxContext) + ,m_xDataSource(std::move(_xDataSource)) + ,m_pMessageParent(_pMessageParent) + { + DBG_ASSERT(m_xContext.is(), "OAdminDialogInvokation::OAdminDialogInvokation: invalid service factory!"); + DBG_ASSERT(m_xDataSource.is(), "OAdminDialogInvokation::OAdminDialogInvokation: invalid preferred name!"); + assert(m_pMessageParent && "OAdminDialogInvokation::OAdminDialogInvokation: invalid message parent!"); + } + + + bool OAdminDialogInvokation::invokeAdministration() + { + if (!m_xContext.is()) + return false; + + try + { + // the service name of the administration dialog + static const char16_t s_sAdministrationServiceName[] = u"com.sun.star.sdb.DatasourceAdministrationDialog"; + static constexpr OUStringLiteral s_sDataSourceTypeChangeDialog = u"com.sun.star.sdb.DataSourceTypeChangeDialog"; + + // the parameters for the call + Sequence<Any> aArguments(comphelper::InitAnyPropertySequence( + { + {"ParentWindow", Any(m_pMessageParent->GetXWindow())}, + {"Title", Any(compmodule::ModuleRes(RID_STR_ADMINDIALOGTITLE))}, + {"InitialSelection", Any(m_xDataSource)}, // the name of the new data source + })); + + + // create the dialog + Reference< XExecutableDialog > xDialog; + { + // creating the dialog service is potentially expensive (if all the libraries invoked need to be loaded) + // so we display a wait cursor + weld::WaitObject aWaitCursor(m_pMessageParent); + Reference<XInterface> x = m_xContext->getServiceManager()->createInstanceWithArgumentsAndContext(s_sDataSourceTypeChangeDialog, aArguments, m_xContext); + xDialog.set( x, UNO_QUERY ); + + // just for a smoother UI: What the dialog does upon execution, is (amongst other things) creating + // the DriverManager service + // If this context has never been accessed before, this may be expensive (it includes loading of + // at least one library). + // As this wizard is intended to run on the first office start, it is very likely that the + // context needs to be freshly created + // Thus, we access the context here (within the WaitCursor), which means the user sees a waitcursor + // while his/her office blocks a few seconds... + DriverManager::create( m_xContext ); + } + + if (xDialog.is()) + { // execute it + if (xDialog->execute()) + return true; + } + else + ShowServiceNotAvailableError(m_pMessageParent, s_sAdministrationServiceName, true); + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.abpilot", + "caught an exception while executing the dialog!"); + } + return false; + } + + +} // namespace abp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/admininvokationimpl.hxx b/extensions/source/abpilot/admininvokationimpl.hxx new file mode 100644 index 0000000000..fef7a11eb3 --- /dev/null +++ b/extensions/source/abpilot/admininvokationimpl.hxx @@ -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 . + */ + +#pragma once + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + +namespace weld { class Window; } + +namespace abp +{ + /** outsourced from AdminDialogInvokationPage, 'cause this class here, in opposite to + the page, needs exception handling to be enabled. + */ + class OAdminDialogInvokation + { + private: + css::uno::Reference< css::uno::XComponentContext > + m_xContext; + css::uno::Reference< css::beans::XPropertySet > m_xDataSource; + weld::Window* m_pMessageParent; + + public: + OAdminDialogInvokation( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext, + css::uno::Reference< css::beans::XPropertySet > _xDataSource, + weld::Window* _pMessageParent + ); + + bool invokeAdministration(); + }; +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/admininvokationpage.cxx b/extensions/source/abpilot/admininvokationpage.cxx new file mode 100644 index 0000000000..2f51d86398 --- /dev/null +++ b/extensions/source/abpilot/admininvokationpage.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 "admininvokationpage.hxx" +#include "abspilot.hxx" +#include "admininvokationimpl.hxx" + +namespace abp +{ + AdminDialogInvokationPage::AdminDialogInvokationPage(weld::Container* pPage, OAddressBookSourcePilot* pController) + : AddressBookSourcePage(pPage, pController, "modules/sabpilot/ui/invokeadminpage.ui", "InvokeAdminPage") + , m_xInvokeAdminDialog(m_xBuilder->weld_button("settings")) + , m_xErrorMessage(m_xBuilder->weld_label("warning")) + { + m_xInvokeAdminDialog->connect_clicked(LINK(this, AdminDialogInvokationPage, OnInvokeAdminDialog)); + } + + AdminDialogInvokationPage::~AdminDialogInvokationPage() + { + } + + void AdminDialogInvokationPage::Activate() + { + AddressBookSourcePage::Activate(); + m_xInvokeAdminDialog->grab_focus(); + } + + void AdminDialogInvokationPage::implUpdateErrorMessage() + { + const bool bIsConnected = getDialog()->getDataSource().isConnected(); + m_xErrorMessage->set_visible( !bIsConnected ); + } + + void AdminDialogInvokationPage::initializePage() + { + AddressBookSourcePage::initializePage(); + m_xErrorMessage->hide(); + // if we're entering this page, we assume we had no connection trial with this data source + } + + void AdminDialogInvokationPage::implTryConnect() + { + getDialog()->connectToDataSource( true ); + + // show our error message if and only if we could not connect + implUpdateErrorMessage(); + + // the status of the next button may have changed + updateDialogTravelUI(); + + // automatically go to the next page (if successfully connected) + if ( canAdvance() ) + getDialog()->travelNext(); + } + + bool AdminDialogInvokationPage::canAdvance() const + { + return AddressBookSourcePage::canAdvance() && getDialog()->getDataSource().isConnected(); + } + + // davido: Do we need it? + IMPL_LINK_NOARG(AdminDialogInvokationPage, OnInvokeAdminDialog, weld::Button&, void) + { + OAdminDialogInvokation aInvokation(getORB(), getDialog()->getDataSource().getDataSource(), getDialog()->getDialog()); + if ( aInvokation.invokeAdministration() ) + { + // try to connect to this data source + implTryConnect(); + } + } + +} // namespace abp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/admininvokationpage.hxx b/extensions/source/abpilot/admininvokationpage.hxx new file mode 100644 index 0000000000..3d4f676e2d --- /dev/null +++ b/extensions/source/abpilot/admininvokationpage.hxx @@ -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 . + */ + +#pragma once + +#include "abspage.hxx" + + +namespace abp +{ + class AdminDialogInvokationPage final : public AddressBookSourcePage + { + std::unique_ptr<weld::Button> m_xInvokeAdminDialog; + std::unique_ptr<weld::Label> m_xErrorMessage; + + public: + explicit AdminDialogInvokationPage(weld::Container* pPage, OAddressBookSourcePilot* pDialog); + virtual ~AdminDialogInvokationPage() override; + private: + // BuilderPage overridables + virtual void Activate() override; + + // OWizard overridables + virtual void initializePage() override; + + // OImportPage overridables + virtual bool canAdvance() const override; + + DECL_LINK( OnInvokeAdminDialog, weld::Button&, void ); + + void implTryConnect(); + void implUpdateErrorMessage(); + }; + +} // namespace abp +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/datasourcehandling.cxx b/extensions/source/abpilot/datasourcehandling.cxx new file mode 100644 index 0000000000..fd5820fce1 --- /dev/null +++ b/extensions/source/abpilot/datasourcehandling.cxx @@ -0,0 +1,620 @@ +/* -*- 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 <strings.hrc> +#include "abptypes.hxx" +#include <componentmodule.hxx> +#include "datasourcehandling.hxx" +#include "addresssettings.hxx" + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/sdb/DatabaseContext.hpp> +#include <com/sun/star/sdb/SQLContext.hpp> +#include <com/sun/star/sdb/XCompletedConnection.hpp> +#include <com/sun/star/sdb/XDatabaseRegistrations.hpp> +#include <com/sun/star/sdb/XDocumentDataSource.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/uri/UriReferenceFactory.hpp> +#include <com/sun/star/uri/VndSunStarPkgUrlReferenceFactory.hpp> + +#include <comphelper/interaction.hxx> +#include <comphelper/processfactory.hxx> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <unotools/sharedunocomponent.hxx> +#include <vcl/stdtext.hxx> +#include <vcl/weld.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/viewfrm.hxx> +#include <comphelper/propertysequence.hxx> + +namespace +{ + +/// Returns the URL of this object shell. +OUString lcl_getOwnURL(SfxObjectShell const * pObjectShell) +{ + OUString aRet; + + if (!pObjectShell) + return aRet; + + const INetURLObject& rURLObject = pObjectShell->GetMedium()->GetURLObject(); + aRet = rURLObject.GetMainURL(INetURLObject::DecodeMechanism::NONE); + return aRet; +} + +} + +namespace abp +{ + + + using namespace ::utl; + using namespace ::comphelper; + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::sdb; + using namespace ::com::sun::star::sdbc; + using namespace ::com::sun::star::task; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::sdbcx; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::frame; + + static Reference< XDatabaseContext > lcl_getDataSourceContext( const Reference< XComponentContext >& _rxContext ) + { + Reference<XDatabaseContext> xContext = DatabaseContext::create(_rxContext); + return xContext; + } + + + /// creates a new data source and inserts it into the context + static void lcl_implCreateAndInsert( + const Reference< XComponentContext >& _rxContext, const OUString& _rName, + Reference< XPropertySet >& /* [out] */ _rxNewDataSource ) + { + + // get the data source context + Reference< XDatabaseContext > xContext = lcl_getDataSourceContext( _rxContext ); + + DBG_ASSERT( !xContext->hasByName( _rName ), "lcl_implCreateAndInsert: name already used!" ); + + + // create a new data source + Reference< XPropertySet > xNewDataSource; + if (xContext.is()) + xNewDataSource.set( xContext->createInstance(), UNO_QUERY ); + DBG_ASSERT( xNewDataSource.is(), "lcl_implCreateAndInsert: could not create a new data source!" ); + + + // insert the data source into the context + DBG_ASSERT( xContext.is(), "lcl_implCreateAndInsert: missing an interface on the context (XNamingService)!" ); + if (xContext.is()) + { + // xDynamicContext->registerObject( _rName, xNewDataSource ); + _rxNewDataSource = xNewDataSource; + } + } + + + /// creates and inserts a data source, and sets its URL property to the string given + static ODataSource lcl_implCreateAndSetURL( + const Reference< XComponentContext >& _rxORB, const OUString& _rName, + const char* _pInitialAsciiURL ) + { + ODataSource aReturn( _rxORB ); + try + { + // create the new data source + Reference< XPropertySet > xNewDataSource; + lcl_implCreateAndInsert( _rxORB, _rName, xNewDataSource ); + + + // set the URL property + if (xNewDataSource.is()) + { + xNewDataSource->setPropertyValue( + "URL", + Any( OUString::createFromAscii( _pInitialAsciiURL ) ) + ); + } + + aReturn.setDataSource( xNewDataSource, _rName ); + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.abpilot", + "caught an exception while creating the data source!"); + } + + return aReturn; + } + + static void lcl_registerDataSource( + const Reference< XComponentContext >& _rxORB, const OUString& _sName, + const OUString& _sURL ) + { + OSL_ENSURE( !_sName.isEmpty(), "lcl_registerDataSource: invalid name!" ); + OSL_ENSURE( !_sURL.isEmpty(), "lcl_registerDataSource: invalid URL!" ); + try + { + Reference< XDatabaseContext > xRegistrations( DatabaseContext::create(_rxORB) ); + if ( xRegistrations->hasRegisteredDatabase( _sName ) ) + xRegistrations->changeDatabaseLocation( _sName, _sURL ); + else + xRegistrations->registerDatabaseLocation( _sName, _sURL ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.abpilot"); + } + } + + struct ODataSourceContextImpl + { + Reference< XComponentContext > xORB; + Reference< XNameAccess > xContext; /// the UNO data source context + StringBag aDataSourceNames; /// for quicker name checks (without the UNO overhead) + + explicit ODataSourceContextImpl(const Reference< XComponentContext >& _rxORB) + : xORB(_rxORB) + { + } + ODataSourceContextImpl(const ODataSourceContextImpl&) = delete; + ODataSourceContextImpl& operator=(const ODataSourceContextImpl&) = delete; + }; + + ODataSourceContext::ODataSourceContext(const Reference< XComponentContext >& _rxORB) + :m_pImpl( new ODataSourceContextImpl( _rxORB ) ) + { + try + { + // create the UNO context + m_pImpl->xContext.set( lcl_getDataSourceContext( _rxORB ), UNO_QUERY_THROW ); + + // collect the data source names + Sequence< OUString > aDSNames = m_pImpl->xContext->getElementNames(); + const OUString* pDSNames = aDSNames.getConstArray(); + const OUString* pDSNamesEnd = pDSNames + aDSNames.getLength(); + + for ( ;pDSNames != pDSNamesEnd; ++pDSNames ) + m_pImpl->aDataSourceNames.insert( *pDSNames ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.abpilot", "ODataSourceContext::ODataSourceContext" ); + } + } + ODataSourceContext::~ODataSourceContext() + { + } + + + void ODataSourceContext::disambiguate(OUString& _rDataSourceName) + { + OUString sCheck( _rDataSourceName ); + StringBag::const_iterator aPos = m_pImpl->aDataSourceNames.find( sCheck ); + + sal_Int32 nPostfix = 1; + while ( ( m_pImpl->aDataSourceNames.end() != aPos ) && ( nPostfix < 65535 ) ) + { // there already is a data source with this name + sCheck = _rDataSourceName + OUString::number( nPostfix++ ); + + aPos = m_pImpl->aDataSourceNames.find( sCheck ); + } + + _rDataSourceName = sCheck; + } + + + void ODataSourceContext::getDataSourceNames( StringBag& _rNames ) const + { + _rNames = m_pImpl->aDataSourceNames; + } + + ODataSource ODataSourceContext::createNewThunderbird( const OUString& _rName ) + { + return lcl_implCreateAndSetURL( m_pImpl->xORB, _rName, "sdbc:address:thunderbird" ); + } + + + ODataSource ODataSourceContext::createNewEvolutionLdap( const OUString& _rName) + { + return lcl_implCreateAndSetURL( m_pImpl->xORB, _rName, "sdbc:address:evolution:ldap" ); + } + + ODataSource ODataSourceContext::createNewEvolutionGroupwise( const OUString& _rName) + { + return lcl_implCreateAndSetURL( m_pImpl->xORB, _rName, "sdbc:address:evolution:groupwise" ); + } + + ODataSource ODataSourceContext::createNewEvolution( const OUString& _rName) + { + return lcl_implCreateAndSetURL( m_pImpl->xORB, _rName, "sdbc:address:evolution:local" ); + } + + + ODataSource ODataSourceContext::createNewKab( const OUString& _rName) + { + return lcl_implCreateAndSetURL( m_pImpl->xORB, _rName, "sdbc:address:kab" ); + } + + + ODataSource ODataSourceContext::createNewMacab( const OUString& _rName) + { + return lcl_implCreateAndSetURL( m_pImpl->xORB, _rName, "sdbc:address:macab" ); + } + + + // tdf117101: Spreadsheet by default + ODataSource ODataSourceContext::createNewOther( const OUString& _rName) + { + return lcl_implCreateAndSetURL( m_pImpl->xORB, _rName, "sdbc:calc:" ); + } + + struct ODataSourceImpl + { + public: + Reference< XComponentContext > xORB; /// the service factory + Reference< XPropertySet > xDataSource; /// the UNO data source + ::utl::SharedUNOComponent< XConnection > + xConnection; + StringBag aTables; // the cached table names + OUString sName; + + explicit ODataSourceImpl(const Reference< XComponentContext >& _rxORB) + : xORB(_rxORB) + { + } + }; + + + ODataSource::ODataSource( const ODataSource& _rSource ) + { + *this = _rSource; + } + + ODataSource& ODataSource::operator=( const ODataSource& _rSource ) + { + if( this != &_rSource ) + { + m_pImpl.reset( new ODataSourceImpl( *_rSource.m_pImpl ) ); + } + return *this; + } + + ODataSource& ODataSource::operator=(ODataSource&& _rSource) noexcept + { + m_pImpl = std::move(_rSource.m_pImpl); + return *this; + } + + ODataSource::ODataSource( const Reference< XComponentContext >& _rxORB ) + :m_pImpl(new ODataSourceImpl(_rxORB)) + { + } + + ODataSource::~ODataSource( ) + { + } + + void ODataSource::store(const AddressSettings& rSettings) + { + if (!isValid()) + // nothing to do + return; + try + { + Reference< XDocumentDataSource > xDocAccess( m_pImpl->xDataSource, UNO_QUERY ); + Reference< XStorable > xStorable; + if ( xDocAccess.is() ) + xStorable.set(xDocAccess->getDatabaseDocument(), css::uno::UNO_QUERY); + OSL_ENSURE( xStorable.is(),"DataSource is no XStorable!" ); + if ( xStorable.is() ) + { + SfxViewFrame* pFrame = SfxViewFrame::Current(); + SfxObjectShell* pObjectShell = pFrame ? pFrame->GetObjectShell() : nullptr; + OUString aOwnURL = lcl_getOwnURL(pObjectShell); // empty if pObjectShell is nullptr + if (aOwnURL.isEmpty() || !rSettings.bEmbedDataSource) + { + // Cannot or should not embed. + xStorable->storeAsURL(m_pImpl->sName,Sequence<PropertyValue>()); + } + else + { + // Embed. + OUString aStreamRelPath = "EmbeddedDatabase"; + auto xContext(comphelper::getProcessComponentContext()); + auto xUri = css::uri::UriReferenceFactory::create(xContext)->parse(aOwnURL); + assert(xUri.is()); + xUri = css::uri::VndSunStarPkgUrlReferenceFactory::create(xContext)->createVndSunStarPkgUrlReference(xUri); + assert(xUri.is()); + OUString const sTmpName = xUri->getUriReference() + "/" + aStreamRelPath; + assert(pObjectShell); + uno::Reference<embed::XStorage> xStorage = pObjectShell->GetStorage(); + uno::Sequence<beans::PropertyValue> aSequence = comphelper::InitPropertySequence( + { + {"TargetStorage", uno::Any(xStorage)}, + {"StreamRelPath", uno::Any(aStreamRelPath)}, + {"BaseURI", uno::Any(aOwnURL)} + }); + xStorable->storeAsURL(sTmpName, aSequence); + m_pImpl->sName = sTmpName; + + // Refer to the sub-storage name in the document settings, so + // we can load it again next time the file is imported. + uno::Reference<lang::XMultiServiceFactory> xFactory(pObjectShell->GetModel(), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xPropertySet(xFactory->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY); + xPropertySet->setPropertyValue("EmbeddedDatabaseName", uno::Any(aStreamRelPath)); + } + } + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.abpilot", + "caught an exception while creating the data source!"); + } + } + + void ODataSource::registerDataSource( const OUString& _sRegisteredDataSourceName) + { + if (!isValid()) + // nothing to do + return; + + try + { + // invalidate ourself + lcl_registerDataSource(m_pImpl->xORB,_sRegisteredDataSourceName,m_pImpl->sName); + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.abpilot", + "caught an exception while creating the data source!"); + } + } + + + void ODataSource::setDataSource( const Reference< XPropertySet >& _rxDS,const OUString& _sName ) + { + if (m_pImpl->xDataSource.get() == _rxDS.get()) + // nothing to do + return; + + if ( isConnected() ) + disconnect(); + + m_pImpl->sName = _sName; + m_pImpl->xDataSource = _rxDS; + } + + + void ODataSource::remove() + { + if (!isValid()) + // nothing to do + return; + + try + { + // invalidate ourself + m_pImpl->xDataSource.clear(); + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.abpilot", + "caught an exception while creating the data source!"); + } + } + + + bool ODataSource::rename( const OUString& _rName ) + { + if (!isValid()) + // nothing to do + return false; + + m_pImpl->sName = _rName; + return true; + } + + + OUString ODataSource::getName() const + { + if ( !isValid() ) + return OUString(); + return m_pImpl->sName; + } + + + bool ODataSource::hasTable( const OUString& _rTableName ) const + { + if ( !isConnected() ) + return false; + + const StringBag& aTables( getTableNames() ); + return aTables.find( _rTableName ) != aTables.end(); + } + + + const StringBag& ODataSource::getTableNames() const + { + m_pImpl->aTables.clear(); + if ( !isConnected() ) + { + OSL_FAIL( "ODataSource::getTableNames: not connected!" ); + } + else + { + try + { + // get the tables container from the connection + Reference< XTablesSupplier > xSuppTables( m_pImpl->xConnection.getTyped(), UNO_QUERY ); + Reference< XNameAccess > xTables; + if ( xSuppTables.is( ) ) + xTables = xSuppTables->getTables(); + DBG_ASSERT( xTables.is(), "ODataSource::getTableNames: could not retrieve the tables container!" ); + + // get the names + Sequence< OUString > aTableNames; + if ( xTables.is( ) ) + aTableNames = xTables->getElementNames( ); + + // copy the names + const OUString* pTableNames = aTableNames.getConstArray(); + const OUString* pTableNamesEnd = pTableNames + aTableNames.getLength(); + for (;pTableNames < pTableNamesEnd; ++pTableNames) + m_pImpl->aTables.insert( *pTableNames ); + } + catch(const Exception&) + { + } + } + + // now the table cache is up-to-date + return m_pImpl->aTables; + } + + + bool ODataSource::connect(weld::Window* _pMessageParent) + { + if ( isConnected( ) ) + // nothing to do + return true; + + + // create the interaction handler (needed for authentication and error handling) + Reference< XInteractionHandler > xInteractions; + try + { + xInteractions = InteractionHandler::createWithParent(m_pImpl->xORB, nullptr); + } + catch(const Exception&) + { + } + + + // failure to create the interaction handler is a serious issue ... + if (!xInteractions.is()) + { + if ( _pMessageParent ) + ShowServiceNotAvailableError( _pMessageParent, u"com.sun.star.task.InteractionHandler", true ); + return false; + } + + + // open the connection + Any aError; + Reference< XConnection > xConnection; + try + { + Reference< XCompletedConnection > xComplConn( m_pImpl->xDataSource, UNO_QUERY ); + DBG_ASSERT( xComplConn.is(), "ODataSource::connect: missing the XCompletedConnection interface on the data source!" ); + if ( xComplConn.is() ) + xConnection = xComplConn->connectWithCompletion( xInteractions ); + } + catch( const SQLContext& e ) { aError <<= e; } + catch( const SQLWarning& e ) { aError <<= e; } + catch( const SQLException& e ) { aError <<= e; } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION("extensions.abpilot", ""); + } + + + // handle errors + if ( aError.hasValue() && _pMessageParent ) + { + try + { + SQLException aException; + aError >>= aException; + if ( aException.Message.isEmpty() ) + { + // prepend some context info + SQLContext aDetailedError(compmodule::ModuleRes(RID_STR_NOCONNECTION), // message + {}, {}, 0, + aError, // next exception + compmodule::ModuleRes(RID_STR_PLEASECHECKSETTINGS)); // details + // handle (aka display) the new context info + xInteractions->handle( new OInteractionRequest( Any( aDetailedError ) ) ); + } + else + { + // handle (aka display) the original error + xInteractions->handle( new OInteractionRequest( Any( aException ) ) ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION("extensions.abpilot", + "caught an exception while trying to display the error!"); + } + } + + if ( !xConnection.is() ) + return false; + + + // success + m_pImpl->xConnection.reset( xConnection ); + m_pImpl->aTables.clear(); + + return true; + } + + + void ODataSource::disconnect( ) + { + m_pImpl->xConnection.clear(); + m_pImpl->aTables.clear(); + } + + + bool ODataSource::isConnected( ) const + { + return m_pImpl->xConnection.is(); + } + + + bool ODataSource::isValid() const + { + return m_pImpl && m_pImpl->xDataSource.is(); + } + + Reference< XPropertySet > ODataSource::getDataSource() const + { + return m_pImpl ? m_pImpl->xDataSource : Reference< XPropertySet >(); + } + + +} // namespace abp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/datasourcehandling.hxx b/extensions/source/abpilot/datasourcehandling.hxx new file mode 100644 index 0000000000..c6058e45e3 --- /dev/null +++ b/extensions/source/abpilot/datasourcehandling.hxx @@ -0,0 +1,181 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <memory> + +#include "abptypes.hxx" + + +namespace com::sun::star { + namespace beans { + class XPropertySet; + } +} + +namespace weld { class Window; } + + +namespace abp +{ + + struct ODataSourceContextImpl; + class ODataSource; + /// a non-UNO wrapper for the data source context + class ODataSourceContext + { + private: + std::unique_ptr<ODataSourceContextImpl> m_pImpl; + + public: + explicit ODataSourceContext( + const css::uno::Reference< css::uno::XComponentContext >& _rxORB + ); + ~ODataSourceContext(); + + /// retrieves the names of all data sources + void getDataSourceNames( StringBag& _rNames ) const; + + /// disambiguates the given name by appending successive numbers + void disambiguate(OUString& _rDataSourceName); + + /// creates a new Thunderbird data source + ODataSource createNewThunderbird( const OUString& _rName ); + + /// creates a new Evolution local data source + ODataSource createNewEvolution( const OUString& _rName ); + + /// creates a new Evolution LDAP data source + ODataSource createNewEvolutionLdap( const OUString& _rName ); + + /// creates a new Evolution GROUPWISE data source + ODataSource createNewEvolutionGroupwise( const OUString& _rName ); + + /// creates a new KDE address book data source + ODataSource createNewKab( const OUString& _rName ); + + /// creates a new macOS address book data source + ODataSource createNewMacab( const OUString& _rName ); + + /// creates a new Other data source; tdf117101: Spreadsheet by default + ODataSource createNewOther( const OUString& _rName ); + }; + + struct ODataSourceImpl; + struct AddressSettings; + /** a non-UNO wrapper for a data source + <p>This class allows to access data sources without the need to compile the respective file with + exception handling enabled (hopefully :).</p> + <p>In addition to wrapping a UNO data source, an instance of this class can handle at most + one valid connection, as obtained from the data source.</p> + */ + class ODataSource + { + private: + std::unique_ptr<ODataSourceImpl> m_pImpl; + + public: + + // - ctor/dtor/assignment + + /// constructs an object which is initially invalid + explicit ODataSource( + const css::uno::Reference< css::uno::XComponentContext >& _rxORB + ); + + /// copy ctor + ODataSource( const ODataSource& _rSource ); + + /// dtor + ~ODataSource( ); + + /// copy assignment + ODataSource& operator=( const ODataSource& _rSource ); + + /// move assignment + ODataSource& operator=(ODataSource&& _rSource) noexcept; + + /// checks whether or not the object represents a valid data source + bool isValid() const; + + + /// removes the data source represented by the object from the data source context + void remove(); + // TODO: put this into the context class + + /// returns the name of the data source + OUString + getName() const; + + /// renames the data source + bool rename( const OUString& _rName ); + // TODO: put this into the context class + + + // - connection handling + + /** connects to the data source represented by this object + @param _pMessageParent + the window to use as parent for any error messages. If this is <NULL/>, no messages are displayed + at all. + @see isConnected + */ + bool connect(weld::Window* _pMessageParent); + + /// returns <TRUE/> if the object has a valid connection, obtained from its data source + bool isConnected( ) const; + + /// disconnects from the data source (i.e. disposes the UNO connection hold internally) + void disconnect( ); + + /// stores the database file + void store(const AddressSettings& rSettings); + + /// register the data source under the given name in the configuration + void registerDataSource( const OUString& _sRegisteredDataSourceName ); + + + /** retrieves the tables names from the connection + <p>to be called when <method>isConnected</method> returns <TRUE/> only</p> + */ + const StringBag& getTableNames() const; + + /** determines whether a given table exists + */ + bool hasTable( const OUString& _rTableName ) const; + + /// return the internal data source object + css::uno::Reference< css::beans::XPropertySet > getDataSource() const; + + + /** set a new data source. + <p>Available to selected clients only</p> + */ + void setDataSource( + const css::uno::Reference< css::beans::XPropertySet >& _rxDS + ,const OUString& _sName + ); + }; + + +} // namespace abp +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/fieldmappingimpl.cxx b/extensions/source/abpilot/fieldmappingimpl.cxx new file mode 100644 index 0000000000..b8e8ac5668 --- /dev/null +++ b/extensions/source/abpilot/fieldmappingimpl.cxx @@ -0,0 +1,316 @@ +/* -*- 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 "fieldmappingimpl.hxx" +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/ui/AddressBookSourceDialog.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/sdb/CommandType.hpp> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/weld.hxx> +#include <com/sun/star/util/AliasProgrammaticPair.hpp> +#include <strings.hrc> +#include <componentmodule.hxx> +#include <unotools/confignode.hxx> +#include <sal/log.hxx> + + +namespace abp +{ + + + using namespace ::utl; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::awt; + using namespace ::com::sun::star::util; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::sdb; + using namespace ::com::sun::star::ui; + using namespace ::com::sun::star::ui::dialogs; + + + const char16_t sDriverSettingsNodeName[] = u"/org.openoffice.Office.DataAccess/DriverSettings/com.sun.star.comp.sdbc.MozabDriver"; + constexpr OUString sAddressBookNodeName = u"/org.openoffice.Office.DataAccess/AddressBook"_ustr; + + namespace fieldmapping + { + bool invokeDialog( const Reference< XComponentContext >& _rxORB, class weld::Window* _pParent, + const Reference< XPropertySet >& _rxDataSource, AddressSettings& _rSettings ) + { + _rSettings.aFieldMapping.clear(); + + DBG_ASSERT( _rxORB.is(), "fieldmapping::invokeDialog: invalid service factory!" ); + DBG_ASSERT( _rxDataSource.is(), "fieldmapping::invokeDialog: invalid data source!" ); + if ( !_rxORB.is() || !_rxDataSource.is() ) + return false; + + try + { + + // create an instance of the dialog service + Reference< XWindow > xDialogParent = _pParent->GetXWindow(); + OUString sTitle(compmodule::ModuleRes(RID_STR_FIELDDIALOGTITLE)); + Reference< XExecutableDialog > xDialog = AddressBookSourceDialog::createWithDataSource(_rxORB, + // the parent window + xDialogParent, + _rxDataSource, + _rSettings.bRegisterDataSource ? _rSettings.sRegisteredDataSourceName : _rSettings.sDataSourceName, + // the table to use + _rSettings.sSelectedTable, + sTitle); + + // execute the dialog + if ( xDialog->execute() ) + { + // retrieve the field mapping as set by he user + Reference< XPropertySet > xDialogProps( xDialog, UNO_QUERY ); + + Sequence< AliasProgrammaticPair > aMapping; + bool bSuccess = + xDialogProps->getPropertyValue("FieldMapping") >>= aMapping; + DBG_ASSERT( bSuccess, "fieldmapping::invokeDialog: invalid property type for FieldMapping!" ); + + // and copy it into the map + const AliasProgrammaticPair* pMapping = aMapping.getConstArray(); + const AliasProgrammaticPair* pMappingEnd = pMapping + aMapping.getLength(); + for (;pMapping != pMappingEnd; ++pMapping) + _rSettings.aFieldMapping[ pMapping->ProgrammaticName ] = pMapping->Alias; + + return true; + } + + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.abpilot", + "caught an exception while executing the dialog!"); + } + return false; + } + + + void defaultMapping( const Reference< XComponentContext >& _rxContext, MapString2String& _rFieldAssignment ) + { + _rFieldAssignment.clear(); + + try + { + // what we have: + // a) For the address data source, we need a mapping from programmatic names (1) to real column names + // b) The SDBC driver has a fixed set of columns, which, when returned, are named according to + // some configuration entries. E.g., the driver displays the field which it knows contains + // the first name as "First Name" - the latter string is stored in the config. + // For this, the driver uses programmatic names, too, but they differ from the programmatic names the + // template documents have. + // So what we need first is a mapping from programmatic names (1) to programmatic names (2) + const char* pMappingProgrammatics[] = + { + "FirstName", "FirstName", + "LastName", "LastName", + "Street", "HomeAddress", + "Zip", "HomeZipCode", + "City", "HomeCity", + "State", "HomeState", + "Country", "HomeCountry", + "PhonePriv", "HomePhone", + "PhoneComp", "WorkPhone", + "PhoneCell", "CellularNumber", + "Pager", "PagerNumber", + "Fax", "FaxNumber", + "EMail", "PrimaryEmail", + "URL", "WebPage1", + "Note", "Notes", + "Altfield1", "Custom1", + "Altfield2", "Custom2", + "Altfield3", "Custom3", + "Altfield4", "Custom4", + "Title", "JobTitle", + "Company", "Company", + "Department", "Department" + }; + // (this list is not complete: both lists of programmatic names are larger in real, + // but this list above is the intersection) + + + // access the configuration information which the driver uses for determining its column names + OUString sDriverAliasesNodeName( + OUString::Concat(sDriverSettingsNodeName) + + "/ColumnAliases"); + + // create a config node for this + OConfigurationTreeRoot aDriverFieldAliasing = OConfigurationTreeRoot::createWithComponentContext( + _rxContext, sDriverAliasesNodeName, -1, OConfigurationTreeRoot::CM_READONLY); + + // loop through all programmatic pairs + DBG_ASSERT( 0 == std::size( pMappingProgrammatics ) % 2, + "fieldmapping::defaultMapping: invalid programmatic map!" ); + // number of pairs + sal_Int32 const nIntersectedProgrammatics = std::size( pMappingProgrammatics ) / 2; + + const char** pProgrammatic = pMappingProgrammatics; + OUString sAddressProgrammatic; + OUString sDriverProgrammatic; + OUString sDriverUI; + for ( sal_Int32 i=0; + i < nIntersectedProgrammatics; + ++i + ) + { + sAddressProgrammatic = OUString::createFromAscii( *pProgrammatic++ ); + sDriverProgrammatic = OUString::createFromAscii( *pProgrammatic++ ); + + if ( aDriverFieldAliasing.hasByName( sDriverProgrammatic ) ) + { + aDriverFieldAliasing.getNodeValue( sDriverProgrammatic ) >>= sDriverUI; + if ( 0 == sDriverUI.getLength() ) + { + OSL_FAIL( "fieldmapping::defaultMapping: invalid driver UI column name!"); + } + else + _rFieldAssignment[ sAddressProgrammatic ] = sDriverUI; + } + else + { + OSL_FAIL( "fieldmapping::defaultMapping: invalid driver programmatic name!" ); + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION("extensions.abpilot", + "code is assumed to throw no exceptions!"); + // the config nodes we're using herein should not do this... + } + } + + + void writeTemplateAddressFieldMapping( const Reference< XComponentContext >& _rxContext, MapString2String&& aFieldAssignment ) + { + // access the configuration information which the driver uses for determining its column names + + // create a config node for this + OConfigurationTreeRoot aAddressBookSettings = OConfigurationTreeRoot::createWithComponentContext( + _rxContext, sAddressBookNodeName); + + OConfigurationNode aFields = aAddressBookSettings.openNode( OUString( "Fields" ) ); + + // loop through all existent fields + Sequence< OUString > aExistentFields = aFields.getNodeNames(); + const OUString* pExistentFields = aExistentFields.getConstArray(); + const OUString* pExistentFieldsEnd = pExistentFields + aExistentFields.getLength(); + + static constexpr OUString sProgrammaticNodeName( u"ProgrammaticFieldName"_ustr ); + static constexpr OUString sAssignedNodeName( u"AssignedFieldName"_ustr ); + + for ( ; pExistentFields != pExistentFieldsEnd; ++pExistentFields ) + { + SAL_WARN_IF( + ((aFields.openNode(*pExistentFields) + .getNodeValue(sProgrammaticNodeName).get<OUString>()) + != *pExistentFields), + "extensions.abpilot", + "fieldmapping::writeTemplateAddressFieldMapping: inconsistent config data!"); + // there should be a redundancy in the config data... if this asserts, there isn't anymore! + + // do we have a new alias for the programmatic? + MapString2String::iterator aPos = aFieldAssignment.find( *pExistentFields ); + if ( aFieldAssignment.end() != aPos ) + { // yes + // -> set a new value + OConfigurationNode aExistentField = aFields.openNode( *pExistentFields ); + aExistentField.setNodeValue( sAssignedNodeName, Any( aPos->second ) ); + // and remove the mapping entry + aFieldAssignment.erase( *pExistentFields ); + } + else + { // no + // -> remove it + aFields.removeNode( *pExistentFields ); + } + } + + // now everything remaining in aFieldAssignment marks a mapping entry which was not present + // in the config before + for (auto const& elem : aFieldAssignment) + { + DBG_ASSERT( !aFields.hasByName( elem.first ), + "fieldmapping::writeTemplateAddressFieldMapping: inconsistence!" ); + // in case the config node for the fields already has the node named <aNewMapping->first>, + // the entry should have been removed from aNewMapping (in the above loop) + OConfigurationNode aNewField = aFields.createNode( elem.first ); + aNewField.setNodeValue( sProgrammaticNodeName, Any( elem.first ) ); + aNewField.setNodeValue( sAssignedNodeName, Any( elem.second ) ); + } + + // commit the changes done + aAddressBookSettings.commit(); + } + + + } // namespace fieldmapping + + + namespace addressconfig + { + + + void writeTemplateAddressSource( const Reference< XComponentContext >& _rxContext, + const OUString& _rDataSourceName, const OUString& _rTableName ) + { + // access the configuration information which the driver uses for determining its column names + + // create a config node for this + OConfigurationTreeRoot aAddressBookSettings = OConfigurationTreeRoot::createWithComponentContext( + _rxContext, sAddressBookNodeName); + + aAddressBookSettings.setNodeValue( OUString( "DataSourceName" ), Any( _rDataSourceName ) ); + aAddressBookSettings.setNodeValue( OUString( "Command" ), Any( _rTableName ) ); + aAddressBookSettings.setNodeValue( OUString( "CommandType" ), Any( sal_Int16(CommandType::TABLE) ) ); + + // commit the changes done + aAddressBookSettings.commit(); + } + + + void markPilotSuccess( const Reference< XComponentContext >& _rxContext ) + { + // access the configuration information which the driver uses for determining its column names + + // create a config node for this + OConfigurationTreeRoot aAddressBookSettings = OConfigurationTreeRoot::createWithComponentContext( + _rxContext, sAddressBookNodeName); + + // set the flag + aAddressBookSettings.setNodeValue( OUString( "AutoPilotCompleted" ), Any( true ) ); + + // commit the changes done + aAddressBookSettings.commit(); + } + + + } // namespace addressconfig + + +} // namespace abp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/fieldmappingimpl.hxx b/extensions/source/abpilot/fieldmappingimpl.hxx new file mode 100644 index 0000000000..e4a2dd1c96 --- /dev/null +++ b/extensions/source/abpilot/fieldmappingimpl.hxx @@ -0,0 +1,111 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include "abptypes.hxx" +#include <com/sun/star/uno/Reference.hxx> +#include "addresssettings.hxx" + +namespace com::sun::star { + namespace lang { + class XMultiServiceFactory; + } + namespace uno { + class XComponentContext; + } + namespace beans { + class XPropertySet; + } +} +namespace weld { class Window; } + +namespace abp +{ + + + namespace fieldmapping + { + + + /** invokes the field mapping dialog + @param _rxORB + service factory to use for creating UNO services + @param _pParent + window to use as parent for the dialog and error messages + @param _rSettings + current settings. Upon return, the field mapping member of this + structure will be filled with the settings the user did in the + field mapping dialog. + */ + bool invokeDialog( + const css::uno::Reference< css::uno::XComponentContext >& _rxORB, + class weld::Window* _pParent, + const css::uno::Reference< css::beans::XPropertySet >& _rxDataSource, + AddressSettings& _rSettings + ); + + + /** creates a default field mapping for usage with the address book SDBC driver + <p>The column names as used by the SDBC driver for address books is stored in the configuration, + and this function creates a mapping which uses this configuration information.</p> + */ + void defaultMapping( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext, + MapString2String& /* [out] */ _rFieldAssignment + ); + + + /** writes a field mapping for the template document address source + */ + void writeTemplateAddressFieldMapping( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext, + MapString2String&& _rFieldAssignment + ); + + + } // namespace fieldmapping + + + namespace addressconfig + { + + + /** writes the data source / table name given into the configuration, to where the template documents + expect it. + */ + void writeTemplateAddressSource( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext, + const OUString& _rDataSourceName, + const OUString& _rTableName + ); + + /** writes the configuration entry which states the pilot has been completed successfully + */ + void markPilotSuccess( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext + ); + + + } // namespace addressconfig + + +} // namespace abp +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/fieldmappingpage.cxx b/extensions/source/abpilot/fieldmappingpage.cxx new file mode 100644 index 0000000000..1652723fda --- /dev/null +++ b/extensions/source/abpilot/fieldmappingpage.cxx @@ -0,0 +1,77 @@ +/* -*- 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 "fieldmappingpage.hxx" +#include "fieldmappingimpl.hxx" +#include "addresssettings.hxx" +#include "abspilot.hxx" + + +namespace abp +{ + FieldMappingPage::FieldMappingPage(weld::Container* pPage, OAddressBookSourcePilot* pController) + : AddressBookSourcePage(pPage, pController, "modules/sabpilot/ui/fieldassignpage.ui", "FieldAssignPage") + , m_xInvokeDialog(m_xBuilder->weld_button("assign")) + , m_xHint(m_xBuilder->weld_label("hint")) + { + m_xInvokeDialog->connect_clicked(LINK(this, FieldMappingPage, OnInvokeDialog)); + } + + FieldMappingPage::~FieldMappingPage() + { + } + + void FieldMappingPage::Activate() + { + AddressBookSourcePage::Activate(); + m_xInvokeDialog->grab_focus(); + } + + void FieldMappingPage::initializePage() + { + AddressBookSourcePage::initializePage(); + implUpdateHint(); + } + + void FieldMappingPage::implUpdateHint() + { + const AddressSettings& rSettings = getSettings(); + OUString sHint; + if ( rSettings.aFieldMapping.empty() ) + sHint = compmodule::ModuleRes(RID_STR_NOFIELDSASSIGNED); + m_xHint->set_label(sHint); + } + + IMPL_LINK_NOARG( FieldMappingPage, OnInvokeDialog, weld::Button&, void ) + { + AddressSettings& rSettings = getSettings(); + + // invoke the dialog doing the mapping + if ( fieldmapping::invokeDialog( getORB(), getDialog()->getDialog(), getDialog()->getDataSource().getDataSource(), rSettings ) ) + { + if ( !rSettings.aFieldMapping.empty() ) + getDialog()->travelNext(); + else + implUpdateHint(); + } + } + +} // namespace abp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/fieldmappingpage.hxx b/extensions/source/abpilot/fieldmappingpage.hxx new file mode 100644 index 0000000000..e3ef93794f --- /dev/null +++ b/extensions/source/abpilot/fieldmappingpage.hxx @@ -0,0 +1,46 @@ +/* -*- 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 . + */ + +#pragma once + +#include "abspage.hxx" +#include <vcl/weld.hxx> + +namespace abp +{ + class FieldMappingPage final : public AddressBookSourcePage + { + std::unique_ptr<weld::Button> m_xInvokeDialog; + std::unique_ptr<weld::Label> m_xHint; + public: + explicit FieldMappingPage(weld::Container* pPage, OAddressBookSourcePilot* pController); + virtual ~FieldMappingPage() override; + private: + // OWizardPage overridables + virtual void initializePage() override; + + // BuilderPage overridables + virtual void Activate() override; + + DECL_LINK(OnInvokeDialog, weld::Button&, void); + + void implUpdateHint(); + }; +} // namespace abp +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/moduleabp.cxx b/extensions/source/abpilot/moduleabp.cxx new file mode 100644 index 0000000000..0a8f8fd4d2 --- /dev/null +++ b/extensions/source/abpilot/moduleabp.cxx @@ -0,0 +1,22 @@ +/* -*- 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 <componentmodule.cxx> + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/tableselectionpage.cxx b/extensions/source/abpilot/tableselectionpage.cxx new file mode 100644 index 0000000000..abf8e58296 --- /dev/null +++ b/extensions/source/abpilot/tableselectionpage.cxx @@ -0,0 +1,101 @@ +/* -*- 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 "tableselectionpage.hxx" +#include "abptypes.hxx" +#include "addresssettings.hxx" +#include "abspilot.hxx" +#include <tools/debug.hxx> + + +namespace abp +{ + + TableSelectionPage::TableSelectionPage(weld::Container* pPage, OAddressBookSourcePilot* pController) + : AddressBookSourcePage(pPage, pController, "modules/sabpilot/ui/selecttablepage.ui", "SelectTablePage") + , m_xTableList(m_xBuilder->weld_tree_view("table")) + { + m_xTableList->connect_changed( LINK( this, TableSelectionPage, OnTableSelected ) ); + m_xTableList->connect_row_activated( LINK( this, TableSelectionPage, OnTableDoubleClicked ) ); + } + + TableSelectionPage::~TableSelectionPage() + { + } + + void TableSelectionPage::Activate() + { + AddressBookSourcePage::Activate(); + + m_xTableList->grab_focus(); + } + + void TableSelectionPage::initializePage() + { + AddressBookSourcePage::initializePage(); + + const AddressSettings& rSettings = getSettings(); + + m_xTableList->clear(); + + // get the table names + const StringBag& aTableNames = getDialog()->getDataSource().getTableNames(); + DBG_ASSERT( aTableNames.size() > 1, "TableSelectionPage::initializePage: to be called for more than one table only!"); + // this page should never bother the user if there is 1 or less tables. + + // fill the list + for (auto const& tableName : aTableNames) + m_xTableList->append_text(tableName); + + // initially select the proper table + m_xTableList->select_text(rSettings.sSelectedTable); + } + + IMPL_LINK_NOARG( TableSelectionPage, OnTableDoubleClicked, weld::TreeView&, bool ) + { + if (m_xTableList->count_selected_rows() == 1) + getDialog()->travelNext(); + return true; + } + + IMPL_LINK_NOARG( TableSelectionPage, OnTableSelected, weld::TreeView&, void ) + { + updateDialogTravelUI(); + } + + bool TableSelectionPage::commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) + { + if (!AddressBookSourcePage::commitPage(_eReason)) + return false; + + AddressSettings& rSettings = getSettings(); + rSettings.sSelectedTable = m_xTableList->get_selected_text(); + + return true; + } + + bool TableSelectionPage::canAdvance() const + { + return AddressBookSourcePage::canAdvance() + && (m_xTableList->count_selected_rows() > 0); + } + +} // namespace abp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/tableselectionpage.hxx b/extensions/source/abpilot/tableselectionpage.hxx new file mode 100644 index 0000000000..a150bf844f --- /dev/null +++ b/extensions/source/abpilot/tableselectionpage.hxx @@ -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 . + */ + +#pragma once + +#include "abspage.hxx" +#include <vcl/weld.hxx> + +namespace abp +{ + class TableSelectionPage final : public AddressBookSourcePage + { + std::unique_ptr<weld::TreeView> m_xTableList; + + public: + explicit TableSelectionPage(weld::Container* pPage, OAddressBookSourcePilot* pController); + virtual ~TableSelectionPage() override; + private: + // OWizardPage overridables + virtual void initializePage() override; + virtual bool commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) override; + + // BuilderPage overridables + virtual void Activate() override; + + // OImportPage overridables + virtual bool canAdvance() const override; + + DECL_LINK(OnTableSelected, weld::TreeView&, void); + DECL_LINK(OnTableDoubleClicked, weld::TreeView&, bool); + }; + +} // namespace abp +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/typeselectionpage.cxx b/extensions/source/abpilot/typeselectionpage.cxx new file mode 100644 index 0000000000..79cfd5d6b0 --- /dev/null +++ b/extensions/source/abpilot/typeselectionpage.cxx @@ -0,0 +1,224 @@ +/* -*- 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 "typeselectionpage.hxx" +#include "addresssettings.hxx" +#include "abspilot.hxx" +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <com/sun/star/sdbc/XDriver.hpp> +#include <com/sun/star/sdbc/DriverManager.hpp> + +namespace abp +{ + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::sdbc; + + // TypeSelectionPage + TypeSelectionPage::TypeSelectionPage(weld::Container* pPage, OAddressBookSourcePilot* pDialog) + : AddressBookSourcePage(pPage, pDialog, "modules/sabpilot/ui/selecttypepage.ui", "SelectTypePage") + , m_xEvolution(m_xBuilder->weld_radio_button("evolution")) + , m_xEvolutionGroupwise(m_xBuilder->weld_radio_button("groupwise")) + , m_xEvolutionLdap(m_xBuilder->weld_radio_button("evoldap")) + , m_xThunderbird(m_xBuilder->weld_radio_button("thunderbird")) + , m_xKab(m_xBuilder->weld_radio_button("kde")) + , m_xMacab(m_xBuilder->weld_radio_button("macosx")) + , m_xOther(m_xBuilder->weld_radio_button("other")) + { + //TODO: For now, try to keep offering the same choices like before the + // Mozilla cleanup, even if the status of what driver actually + // provides which functionality is somewhat unclear, see the discussions + // of fdo#57285 "Address Book Data Source Wizard lists 'macOS address + // book' on Linux" and fdo#57322 "Moz-free LDAP Address Book driver." + // In accordance with ancient OOo 3.3, this is as follows: + // + // On Linux: + // - EVOLUTION, EVOLUTION_GROUPWISE, EVOLUTION_LDAP (if applicable) + // - KAB (if applicable) + // - OTHER + // + // On macOS: + // - MACAB (if applicable) + // - OTHER + // + // On Windows: + // - THUNDERBIRD + // - OTHER + +#if !defined(_WIN32) + bool bHaveEvolution = false; + bool bHaveKab = false; + bool bHaveMacab = false; + + Reference< XDriverManager2 > xManager = DriverManager::create( pDialog->getORB() ); + + try + { + // check whether Evolution is available + Reference< XDriver > xDriver( xManager->getDriverByURL("sdbc:address:evolution:local") ); + if ( xDriver.is() ) + bHaveEvolution = true; + } + catch (...) + { + } + + // check whether KDE address book is available + try + { + Reference< XDriver > xDriver( xManager->getDriverByURL("sdbc:address:kab") ); + if ( xDriver.is() ) + bHaveKab = true; + } + catch (...) + { + } + + try + { + // check whether macOS address book is available + Reference< XDriver > xDriver( xManager->getDriverByURL("sdbc:address:macab") ); + if ( xDriver.is() ) + bHaveMacab = true; + } + catch(...) + { + } +#else + bool const bHaveEvolution = false; + bool const bHaveKab = false; + bool const bHaveMacab = false; +#endif + + // Items are displayed in list order + m_aAllTypes.push_back( ButtonItem( m_xEvolution.get(), AST_EVOLUTION, bHaveEvolution ) ); + m_aAllTypes.push_back( ButtonItem( m_xEvolutionGroupwise.get(), AST_EVOLUTION_GROUPWISE, bHaveEvolution ) ); + m_aAllTypes.push_back( ButtonItem( m_xEvolutionLdap.get(), AST_EVOLUTION_LDAP, bHaveEvolution ) ); + m_aAllTypes.push_back( ButtonItem( m_xThunderbird.get(), AST_THUNDERBIRD, true ) ); + m_aAllTypes.push_back( ButtonItem( m_xKab.get(), AST_KAB, bHaveKab ) ); + m_aAllTypes.push_back( ButtonItem( m_xMacab.get(), AST_MACAB, bHaveMacab ) ); + m_aAllTypes.push_back( ButtonItem( m_xOther.get(), AST_OTHER, true ) ); + + Link<weld::Toggleable&,void> aTypeSelectionHandler = LINK(this, TypeSelectionPage, OnTypeSelected ); + for (auto const& elem : m_aAllTypes) + { + if (!elem.m_bVisible) + elem.m_pItem->hide(); + else + { + elem.m_pItem->connect_toggled( aTypeSelectionHandler ); + elem.m_pItem->show(); + } + } + } + + TypeSelectionPage::~TypeSelectionPage() + { + for (auto & elem : m_aAllTypes) + { + elem.m_bVisible = false; + } + } + + void TypeSelectionPage::Activate() + { + AddressBookSourcePage::Activate(); + + for (auto const& elem : m_aAllTypes) + { + if( elem.m_pItem->get_active() && elem.m_bVisible ) + { + elem.m_pItem->grab_focus(); + break; + } + } + + getDialog()->enableButtons(WizardButtonFlags::PREVIOUS, false); + } + + void TypeSelectionPage::Deactivate() + { + AddressBookSourcePage::Deactivate(); + getDialog()->enableButtons(WizardButtonFlags::PREVIOUS, true); + } + + void TypeSelectionPage::selectType( AddressSourceType _eType ) + { + for (auto const& elem : m_aAllTypes) + { + elem.m_pItem->set_active( _eType == elem.m_eType ); + } + } + + AddressSourceType TypeSelectionPage::getSelectedType() const + { + for (auto const& elem : m_aAllTypes) + { + if ( elem.m_pItem->get_active() && elem.m_bVisible ) + return elem.m_eType; + } + + return AST_INVALID; + } + + void TypeSelectionPage::initializePage() + { + AddressBookSourcePage::initializePage(); + + const AddressSettings& rSettings = getSettings(); + selectType(rSettings.eType); + } + + bool TypeSelectionPage::commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) + { + if (!AddressBookSourcePage::commitPage(_eReason)) + return false; + + if (AST_INVALID == getSelectedType( )) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xContainer.get(), + VclMessageType::Warning, VclButtonsType::Ok, + compmodule::ModuleRes(RID_STR_NEEDTYPESELECTION))); + xBox->run(); + return false; + } + + AddressSettings& rSettings = getSettings(); + rSettings.eType = getSelectedType(); + + return true; + } + + bool TypeSelectionPage::canAdvance() const + { + return AddressBookSourcePage::canAdvance() + && (AST_INVALID != getSelectedType()); + } + + IMPL_LINK(TypeSelectionPage, OnTypeSelected, weld::Toggleable&, rButton, void) + { + if (!rButton.get_active()) + return; + getDialog()->typeSelectionChanged( getSelectedType() ); + updateDialogTravelUI(); + } + +} // namespace abp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/typeselectionpage.hxx b/extensions/source/abpilot/typeselectionpage.hxx new file mode 100644 index 0000000000..7c908057d6 --- /dev/null +++ b/extensions/source/abpilot/typeselectionpage.hxx @@ -0,0 +1,81 @@ +/* -*- 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 . + */ + +#pragma once + +#include "abspage.hxx" +#include "addresssettings.hxx" +#include <vcl/weld.hxx> + +namespace abp +{ + + class TypeSelectionPage final : public AddressBookSourcePage + { + std::unique_ptr<weld::RadioButton> m_xEvolution; + std::unique_ptr<weld::RadioButton> m_xEvolutionGroupwise; + std::unique_ptr<weld::RadioButton> m_xEvolutionLdap; + std::unique_ptr<weld::RadioButton> m_xThunderbird; + std::unique_ptr<weld::RadioButton> m_xKab; + std::unique_ptr<weld::RadioButton> m_xMacab; + std::unique_ptr<weld::RadioButton> m_xOther; + + struct ButtonItem { + weld::RadioButton* m_pItem; + AddressSourceType m_eType; + bool m_bVisible; + + ButtonItem( weld::RadioButton *pItem, + AddressSourceType eType, + bool bVisible ) : + m_pItem( pItem ), + m_eType( eType ), + m_bVisible( bVisible ) + {} + }; + + std::vector< ButtonItem > m_aAllTypes; + + public: + explicit TypeSelectionPage(weld::Container* pPage, OAddressBookSourcePilot* pController); + virtual ~TypeSelectionPage() override; + + // retrieves the currently selected type + AddressSourceType getSelectedType() const; + + private: + // OWizardPage overridables + virtual void initializePage() override; + virtual bool commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) override; + + // BuilderPage overridables + virtual void Activate() override; + virtual void Deactivate() override; + + // OImportPage overridables + virtual bool canAdvance() const override; + + DECL_LINK( OnTypeSelected, weld::Toggleable&, void ); + + void selectType( AddressSourceType _eType ); + }; + + +} // namespace abp +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/unodialogabp.cxx b/extensions/source/abpilot/unodialogabp.cxx new file mode 100644 index 0000000000..4409a1911f --- /dev/null +++ b/extensions/source/abpilot/unodialogabp.cxx @@ -0,0 +1,156 @@ +/* -*- 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 "unodialogabp.hxx" +#include "abspilot.hxx" +#include <comphelper/sequence.hxx> +#include <vcl/svapp.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/awt/XWindow.hpp> + +#define PROPERTY_ID_DATASOURCENAME 3 + +namespace abp +{ + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::ui::dialogs; + + OABSPilotUno::OABSPilotUno(const Reference< XComponentContext >& _rxORB) + :OGenericUnoDialog(_rxORB) + { + registerProperty( "DataSourceName", PROPERTY_ID_DATASOURCENAME, PropertyAttribute::READONLY , + &m_sDataSourceName, cppu::UnoType<decltype(m_sDataSourceName)>::get() ); + } + + Any SAL_CALL OABSPilotUno::queryInterface( const Type& aType ) + { + Any aReturn = svt::OGenericUnoDialog::queryInterface( aType ); + return aReturn.hasValue() ? aReturn : OABSPilotUno_JBase::queryInterface( aType ); + } + + void SAL_CALL OABSPilotUno::acquire( ) noexcept + { + svt::OGenericUnoDialog::acquire(); + } + + void SAL_CALL OABSPilotUno::release( ) noexcept + { + svt::OGenericUnoDialog::release(); + } + + Sequence< Type > SAL_CALL OABSPilotUno::getTypes( ) + { + return ::comphelper::concatSequences( + svt::OGenericUnoDialog::getTypes(), + OABSPilotUno_JBase::getTypes() + ); + } + + Sequence<sal_Int8> SAL_CALL OABSPilotUno::getImplementationId( ) + { + return css::uno::Sequence<sal_Int8>(); + } + + OUString SAL_CALL OABSPilotUno::getImplementationName() + { + return "org.openoffice.comp.abp.OAddressBookSourcePilot"; + } + + css::uno::Sequence<OUString> SAL_CALL OABSPilotUno::getSupportedServiceNames() + { + return { "com.sun.star.ui.dialogs.AddressBookSourcePilot" }; + } + + Reference<XPropertySetInfo> SAL_CALL OABSPilotUno::getPropertySetInfo() + { + Reference<XPropertySetInfo> xInfo( createPropertySetInfo( getInfoHelper() ) ); + return xInfo; + } + + + ::cppu::IPropertyArrayHelper& OABSPilotUno::getInfoHelper() + { + return *getArrayHelper(); + } + + + ::cppu::IPropertyArrayHelper* OABSPilotUno::createArrayHelper( ) const + { + Sequence< Property > aProps; + describeProperties(aProps); + return new ::cppu::OPropertyArrayHelper(aProps); + } + + void SAL_CALL OABSPilotUno::initialize( const Sequence< Any >& aArguments ) + { + Reference<awt::XWindow> xParentWindow; + if (aArguments.getLength() == 1 && (aArguments[0] >>= xParentWindow) ) { + Sequence< Any > aNewArgs{ Any(PropertyValue( + "ParentWindow", 0, Any(xParentWindow), PropertyState_DIRECT_VALUE )) }; + OGenericUnoDialog::initialize(aNewArgs); + } else { + OGenericUnoDialog::initialize(aArguments); + } + } + + std::unique_ptr<weld::DialogController> OABSPilotUno::createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) + { + return std::make_unique<OAddressBookSourcePilot>(Application::GetFrameWeld(rParent), m_aContext); + } + + Any SAL_CALL OABSPilotUno::execute( const Sequence< NamedValue >& /*lArgs*/ ) + { + // not interested in the context, not interested in the args + // -> call the execute method of the XExecutableDialog + static_cast< XExecutableDialog* >( this )->execute(); + + // result interest not really ... + // We show this dialog one times only! + // User has one chance to accept it or not. + // (or he can start it again by using wizard-menu!) + // So we should deregister it on our general job execution service by using right protocol parameters. + css::uno::Sequence< css::beans::NamedValue > lProtocol { { "Deactivate", css::uno::Any( true ) } }; + return Any( lProtocol ); + } + + void OABSPilotUno::executedDialog(sal_Int16 _nExecutionResult) + { + if ( _nExecutionResult == RET_OK ) + { + const AddressSettings& aSettings = static_cast<OAddressBookSourcePilot*>(m_xDialog.get())->getSettings(); + m_sDataSourceName = aSettings.bRegisterDataSource ? aSettings.sRegisteredDataSourceName : aSettings.sDataSourceName; + } + } + + +} // namespace abp + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +org_openoffice_comp_abp_OAddressBookSourcePilot( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new abp::OABSPilotUno(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/abpilot/unodialogabp.hxx b/extensions/source/abpilot/unodialogabp.hxx new file mode 100644 index 0000000000..b8bd940afa --- /dev/null +++ b/extensions/source/abpilot/unodialogabp.hxx @@ -0,0 +1,78 @@ +/* -*- 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 . + */ + +#pragma once + +#include <svtools/genericunodialog.hxx> +#include <comphelper/proparrhlp.hxx> +#include <componentmodule.hxx> +#include <com/sun/star/task/XJob.hpp> +#include <cppuhelper/implbase1.hxx> + +namespace abp +{ + class OABSPilotUno; + typedef ::cppu::ImplHelper1< css::task::XJob > OABSPilotUno_JBase; + typedef ::comphelper::OPropertyArrayUsageHelper< OABSPilotUno > OABSPilotUno_PBase; + /// the UNO wrapper for the address book source pilot + class OABSPilotUno + : public svt::OGenericUnoDialog + , public OABSPilotUno_JBase + , public OABSPilotUno_PBase + { + OUString m_sDataSourceName; + + public: + explicit OABSPilotUno(const css::uno::Reference< css::uno::XComponentContext >& _rxORB); + + private: + // XInterface (disambiguation) + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& aType ) override; + virtual void SAL_CALL acquire( ) noexcept override; + virtual void SAL_CALL release( ) noexcept override; + + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + virtual css::uno::Sequence<sal_Int8> SAL_CALL getImplementationId( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo> SAL_CALL getPropertySetInfo() override; + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; + + // OPropertyArrayUsageHelper + virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const override; + + // XJob + virtual css::uno::Any SAL_CALL execute( const css::uno::Sequence< css::beans::NamedValue >& lArgs ) override; + + // XInitialisation + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + using svt::OGenericUnoDialog::execute; + // OGenericUnoDialog overridables + virtual std::unique_ptr<weld::DialogController> createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) override; + virtual void executedDialog(sal_Int16 _nExecutionResult) override; + }; + +} // namespace abp +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/activex/README.txt b/extensions/source/activex/README.txt new file mode 100644 index 0000000000..50c2091507 --- /dev/null +++ b/extensions/source/activex/README.txt @@ -0,0 +1,33 @@ +Description. + +The StarOffice ActiveX control shows an example of access to UNO through COM technology. +It requires a properly installed StarOffice version 6.0/6.1 or OpenOffice 1.0. +This is a Lite ActiveX control so it can be used only in containers that +allows to use such controls. + +Pressing to any link to staroffice document should activate the control. +So the document will be opened in ReadOnly mode. + +Also it can be activated with an <OBJECT> tag from a html-page. +Without any parameters for an object tag a new writer document will be +opened for editing. Possible parameters are + src - full URL to the file that should be edited/viewed; + it can contain "private:factory/..." URLs to open new documents + for edit, for example "private:factory/swriter" + readonly - the default value is "true", in case it is set to any other + value the document is opened for editing + +As any ActiveX control this one should be registered. +To let MSIE register it itself the "CODEBASE" parameter +for the "OBJECT" tag should be specified +with a URL to the library "so_activex.dll". +The example of registration with "OBJECT" tag is in example.html. + +Also it can be done using regsvr32 application. +To do it please write +<Path to Windows installation>\System32\regsvr32 so_activex.dll + +To unregister the control please use /u option: +<Path to Windows installation>\system32\regsvr32 so_activex.dll /u + + diff --git a/extensions/source/activex/SOActionsApproval.cxx b/extensions/source/activex/SOActionsApproval.cxx new file mode 100644 index 0000000000..6fd6d08881 --- /dev/null +++ b/extensions/source/activex/SOActionsApproval.cxx @@ -0,0 +1,39 @@ +/* -*- 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 . + */ + +// SOActionsApproval.cxx : Implementation of CHelpApp and DLL registration. + +#include <sal/config.h> + +#include <cstddef> + +#include "StdAfx2.h" + +#include <so_activex.h> +#include "SOActionsApproval.h" +#include <sal/macros.h> + +COM_DECLSPEC_NOTHROW STDMETHODIMP SOActionsApproval::InterfaceSupportsErrorInfo(REFIID riid) +{ + if (InlineIsEqualGUID(IID_ISOActionsApproval, riid)) + return S_OK; + return S_FALSE; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/activex/SOActionsApproval.h b/extensions/source/activex/SOActionsApproval.h new file mode 100644 index 0000000000..9b8f4c1e99 --- /dev/null +++ b/extensions/source/activex/SOActionsApproval.h @@ -0,0 +1,98 @@ +/* -*- 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 . + */ + +// SOActionsApproval.h: Definition of the SOActionsApproval class + +#pragma once + +#ifdef _MSC_VER +#pragma once +#endif + +#include "resource.h" +#include <ExDispID.h> +#include <ExDisp.h> +#include <shlguid.h> + +#include <atlctl.h> + +#include <so_activex.h> + +// SOActionsApproval + +class SOActionsApproval : + public IDispatchImpl<ISOActionsApproval, &IID_ISOActionsApproval, &LIBID_SO_ACTIVEXLib>, + public ISupportErrorInfo, + public CComObjectRoot, + public CComCoClass<SOActionsApproval,&CLSID_SOActionsApproval> +{ +public: + SOActionsApproval() {} + virtual ~SOActionsApproval() {} + +BEGIN_COM_MAP(SOActionsApproval) + COM_INTERFACE_ENTRY(IDispatch) + COM_INTERFACE_ENTRY(ISOActionsApproval) + COM_INTERFACE_ENTRY(ISupportErrorInfo) +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winconsistent-missing-override" +#endif +END_COM_MAP() +#if defined __clang__ +#pragma clang diagnostic pop +#endif +DECLARE_NOT_AGGREGATABLE(SOActionsApproval) +// Remove the comment from the line above if you don't want your object to +// support aggregation. + +DECLARE_REGISTRY_RESOURCEID(IDR_SODOCUMENTEVENTLISTENER) + +// ISupportsErrorInfo + STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid) override; + +// ISOActionsApproval + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE approveAction( + /* [in] */ long nActionID, + /* [retval][out] */ boolean *pbApproval) override + { + // only PreventClose is approved + USES_CONVERSION; + *pbApproval = ( nActionID == 1 ); + + return S_OK; + } + + virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Bridge_implementedInterfaces( + /* [retval][out] */ SAFEARRAY __RPC_FAR * __RPC_FAR *pVal) override + { + *pVal = SafeArrayCreateVector( VT_BSTR, 0, 1 ); + + if( !*pVal ) + return E_FAIL; + + LONG ix = 0; + CComBSTR aInterface( OLESTR( "com.sun.star.embed.XActionsApproval" ) ); + SafeArrayPutElement( *pVal, &ix, aInterface ); + + return S_OK; + } +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/activex/SOActionsApproval.rgs b/extensions/source/activex/SOActionsApproval.rgs new file mode 100644 index 0000000000..5433208139 --- /dev/null +++ b/extensions/source/activex/SOActionsApproval.rgs @@ -0,0 +1,24 @@ +HKCR +{ +9F3697AC-7A18-4335-AF0A-65FAC2C35CC1 + so_activex.SOActionsApproval.1 = s 'SOActionsApproval Class' + { + CLSID = s '{9F3697AC-7A18-4335-AF0A-65FAC2C35CC1}' + } + so_activex.SOActionsApproval = s 'SOActionsApproval Class' + { + CLSID = s '{9F3697AC-7A18-4335-AF0A-65FAC2C35CC1}' + } + NoRemove CLSID + { + ForceRemove {9F3697AC-7A18-4335-AF0A-65FAC2C35CC1} = s 'SOActionsApproval Class' + { + ProgID = s 'so_activex.SOActionsApproval.1' + VersionIndependentProgID = s 'so_activex.SOActionsApproval' + InprocServer32 = s '%MODULE%' + { + val ThreadingModel = s 'both' + } + } + } +} diff --git a/extensions/source/activex/SOActiveX.cxx b/extensions/source/activex/SOActiveX.cxx new file mode 100644 index 0000000000..b72ab0d66c --- /dev/null +++ b/extensions/source/activex/SOActiveX.cxx @@ -0,0 +1,1154 @@ +/* -*- 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 . + */ + +// SOActiveX.cpp : Implementation of CSOActiveX + +#include "StdAfx2.h" +#include <so_activex.h> +#include "SOActiveX.h" +#include "SOComWindowPeer.h" +#include "SODispatchInterceptor.h" +#include "SOActionsApproval.h" +#include "com_uno_helper.h" + +#define STAROFFICE_WINDOWCLASS L"SOParentWindow" + + +static void OutputError_Impl( HWND hw, HRESULT ErrorCode ) +{ + LPWSTR sMessage = nullptr; + FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + ErrorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + reinterpret_cast<LPWSTR>(&sMessage), + 0, + nullptr + ); + MessageBoxW( hw, sMessage, nullptr, MB_OK | MB_ICONINFORMATION ); + HeapFree( GetProcessHeap(), 0, sMessage ); +} + +HRESULT ExecuteFunc( IDispatch* idispUnoObject, + OLECHAR const * sFuncName, + CComVariant* params, + unsigned int count, + CComVariant* pResult ) +{ + if( !idispUnoObject ) + return E_FAIL; + + DISPID id; + HRESULT hr = idispUnoObject->GetIDsOfNames( IID_NULL, const_cast<OLECHAR **>(&sFuncName), 1, LOCALE_USER_DEFAULT, &id); + if( !SUCCEEDED( hr ) ) return hr; + + DISPPARAMS dispparams= { params, nullptr, count, 0}; + + // DEBUG + EXCEPINFO myInfo; + hr = idispUnoObject->Invoke( id, IID_NULL,LOCALE_USER_DEFAULT, DISPATCH_METHOD, + &dispparams, pResult, &myInfo, nullptr); + + // for debugging purposes + // USES_CONVERSION; + // if ( !SUCCEEDED( hr ) ) + // ::MessageBox( NULL, OLE2A( myInfo.bstrDescription ), OLE2A( myInfo.bstrSource ), MB_OK | MB_ICONINFORMATION ); + + return hr; +} + +static HRESULT GetIDispByFunc( IDispatch* idispUnoObject, + OLECHAR const * sFuncName, + CComVariant* params, + unsigned int count, + CComPtr<IDispatch>& pdispResult ) +{ + if( !idispUnoObject ) + return E_FAIL; + + CComVariant result; + HRESULT hr = ExecuteFunc( idispUnoObject, sFuncName, params, count, &result ); + if( !SUCCEEDED( hr ) ) return hr; + + if( result.vt != VT_DISPATCH || result.pdispVal == nullptr ) + return E_FAIL; + + pdispResult = CComPtr<IDispatch>( result.pdispVal ); + + return S_OK; +} + +static HRESULT PutPropertiesToIDisp( IDispatch* pdispObject, + OLECHAR const ** sMemberNames, + CComVariant* pVariant, + unsigned int count ) +{ + for( unsigned int ind = 0; ind < count; ind++ ) + { + DISPID id; + HRESULT hr = pdispObject->GetIDsOfNames( IID_NULL, const_cast<OLECHAR **>(&sMemberNames[ind]), 1, LOCALE_USER_DEFAULT, &id ); + if( !SUCCEEDED( hr ) ) return hr; + + hr = CComDispatchDriver::PutProperty( pdispObject, id, &pVariant[ind] ); + if( !SUCCEEDED( hr ) ) return hr; + } + + return S_OK; +} + +HRESULT GetPropertiesFromIDisp( IDispatch* pdispObject, + OLECHAR const ** sMemberNames, + CComVariant* pVariant, + unsigned int count ) +{ + for( unsigned int ind = 0; ind < count; ind++ ) + { + DISPID id; + HRESULT hr = pdispObject->GetIDsOfNames( IID_NULL, const_cast<OLECHAR **>(&sMemberNames[ind]), 1, LOCALE_USER_DEFAULT, &id ); + if( !SUCCEEDED( hr ) ) return hr; + + hr = CComDispatchDriver::GetProperty( pdispObject, id, &pVariant[ind] ); + if( !SUCCEEDED( hr ) ) return hr; + } + + return S_OK; +} + +// CSOActiveX + +CSOActiveX::CSOActiveX() +: mCookie(0) +, mCurFileUrl( L"private:factory/swriter" ) +, mbLoad( FALSE ) +, mbViewOnly( TRUE ) +, mParentWin( nullptr ) +, mOffWin( nullptr ) +, mpDispatchInterceptor( nullptr ) +, mnVersion( SO_NOT_DETECTED ) +, mbReadyForActivation( FALSE ) +, mbDrawLocked( false ) +{ + CLSID const clsFactory = {0x82154420,0x0FBF,0x11d4,{0x83, 0x13,0x00,0x50,0x04,0x52,0x6A,0xB4}}; + HRESULT hr = CoCreateInstance( clsFactory, nullptr, CLSCTX_ALL, __uuidof(IDispatch), reinterpret_cast<void**>(&mpDispFactory)); + if( !SUCCEEDED( hr ) ) + OutputError_Impl( nullptr, hr ); + + mPWinClass.style = CS_HREDRAW|CS_VREDRAW; + mPWinClass.lpfnWndProc = DefWindowProcW; + mPWinClass.cbClsExtra = 0; + mPWinClass.cbWndExtra = 0; + mPWinClass.hInstance = GetModuleHandleW(nullptr); //myInstance; + mPWinClass.hIcon = nullptr; + mPWinClass.hCursor = nullptr; + mPWinClass.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_BACKGROUND); + mPWinClass.lpszMenuName = nullptr; + mPWinClass.lpszClassName = STAROFFICE_WINDOWCLASS; + + RegisterClassW(&mPWinClass); +} + +CSOActiveX::~CSOActiveX() +{ + Cleanup(); + +} + +HRESULT CSOActiveX::Cleanup() +{ + CComVariant dummyResult; + + if( mpDispatchInterceptor ) + { + if( mpDispFrame ) + { + // remove dispatch interceptor + CComQIPtr< IDispatch, &IID_IDispatch > pIDispDispInter( mpDispatchInterceptor ); + CComVariant aVariant( pIDispDispInter ); + ExecuteFunc( mpDispFrame, + L"releaseDispatchProviderInterceptor", + &aVariant, + 1, + &dummyResult ); + } + + mpDispatchInterceptor->ClearParent(); + mpDispatchInterceptor->Release(); + mpDispatchInterceptor = nullptr; + } + + mpDispTempFile = CComPtr< IDispatch >(); + mbReadyForActivation = FALSE; + + if( mpInstanceLocker ) + { + ExecuteFunc( mpInstanceLocker, L"dispose", nullptr, 0, &dummyResult ); + mpInstanceLocker = CComPtr< IDispatch >(); + } + + if( mpDispFrame ) + { + bool bCloserActivated = false; + + CComPtr<IDispatch> pDispDocumentCloser; + CComVariant aDocCloser( L"com.sun.star.embed.DocumentCloser" ); + HRESULT hr = GetIDispByFunc( mpDispFactory, + L"createInstance", + &aDocCloser, + 1, + pDispDocumentCloser ); + if ( SUCCEEDED( hr ) && pDispDocumentCloser ) + { + SAFEARRAY* pInitFrame = SafeArrayCreateVector(VT_VARIANT, 0, 1); + LONG nInitInd = 0; + CComVariant pFrameVariant( mpDispFrame ); + SafeArrayPutElement( pInitFrame, &nInitInd, &pFrameVariant ); + CComVariant aVarInitFrame; + aVarInitFrame.vt = VT_ARRAY | VT_VARIANT; aVarInitFrame.parray = pInitFrame; + hr = ExecuteFunc( pDispDocumentCloser, L"initialize", &aVarInitFrame, 1, &dummyResult ); + if( SUCCEEDED( hr ) ) + { + // the following call will let the closing happen + hr = ExecuteFunc( pDispDocumentCloser, L"dispose", nullptr, 0, &dummyResult ); + bCloserActivated = SUCCEEDED( hr ); + } + } + + if ( !bCloserActivated ) + { + CComVariant aPropVar; + aPropVar.vt = VT_BOOL; aPropVar.boolVal = VARIANT_TRUE; + if ( !SUCCEEDED( ExecuteFunc( mpDispFrame, L"close", &aPropVar, 1, &dummyResult ) ) ) + ExecuteFunc( mpDispFrame, L"dispose", nullptr, 0, &dummyResult ); + } + + mpDispFrame = CComPtr< IDispatch >(); + } + + if( ::IsWindow( mOffWin ) ) + ::DestroyWindow( mOffWin ); + + TerminateOffice(); + + return S_OK; +} + +HRESULT CSOActiveX::TerminateOffice() +{ + // create desktop + CComPtr<IDispatch> pdispDesktop; + CComVariant aDesktopServiceName( L"com.sun.star.frame.Desktop" ); + + HRESULT hr = GetIDispByFunc( mpDispFactory, L"createInstance", &aDesktopServiceName, 1, pdispDesktop ); + if( !pdispDesktop || !SUCCEEDED( hr ) ) return hr; + + // create tree of frames + CComPtr<IDispatch> pdispChildren; + hr = GetIDispByFunc( pdispDesktop, L"getFrames", nullptr, 0, pdispChildren ); + if( !pdispChildren || !SUCCEEDED( hr ) ) return hr; + + CComVariant aFrames; + CComVariant nFlag( 4 ); + hr = ExecuteFunc( pdispChildren, L"queryFrames", &nFlag, 1, &aFrames ); + if ( SUCCEEDED( hr ) ) + { + if ( ( aFrames.vt == ( VT_ARRAY | VT_DISPATCH ) || aFrames.vt == ( VT_ARRAY | VT_VARIANT ) ) + && ( !aFrames.parray || (aFrames.parray->cDims == 1 && aFrames.parray->rgsabound[0].cElements == 0) ) ) + { + // there is no frames open + // TODO: check whether the frames are hidden if they are open? + CComVariant dummyResult; + hr = ExecuteFunc( pdispDesktop, L"terminate", nullptr, 0, &dummyResult ); + } + } + + return hr; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP CSOActiveX::InitNew () +{ + mnVersion = GetVersionConnected(); + mbLoad = TRUE; + return S_OK; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP CSOActiveX::Load ( LPSTREAM /*pStm*/ ) +{ + mnVersion = GetVersionConnected(); + mbLoad = TRUE; + + // may be later? + // for now just ignore + + return S_OK; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP CSOActiveX::Load( LPPROPERTYBAG pPropBag, LPERRORLOG /*pErrorLog*/ ) +{ + mnVersion = GetVersionConnected(); + + IPropertyBag2* pPropBag2; + HRESULT hr = pPropBag->QueryInterface( IID_IPropertyBag2, reinterpret_cast<void**>(&pPropBag2) ); + //ATLASSERT( hr >= 0 ); + + if( !SUCCEEDED( hr ) ) + return hr; + + unsigned long aNum; + hr = pPropBag2->CountProperties( &aNum ); + //ATLASSERT( hr >= 0 ); + if( !SUCCEEDED( hr ) ) + return hr; + + PROPBAG2* aPropNames = new PROPBAG2[aNum]; + unsigned long aReaded; + + hr = pPropBag2->GetPropertyInfo( 0, + aNum, + aPropNames, + &aReaded ); + //ATLASSERT( hr >= 0 ); + if( !SUCCEEDED( hr ) ) + { + delete[] aPropNames; + return hr; + } + + CComVariant* aVal = new CComVariant[aNum]; + HRESULT* hvs = new HRESULT[aNum]; + hr = pPropBag2->Read( aNum, + aPropNames, + nullptr, + aVal, + hvs ); + //ATLASSERT( hr >= 0 ); + if( !SUCCEEDED( hr ) ) + { + delete[] hvs; + delete[] aVal; + delete[] aPropNames; + return hr; + } + + for( unsigned long ind = 0; ind < aNum; ind++ ) + { + // all information from the 'object' tag is in strings + if (aVal[ind].vt == VT_BSTR && !wcscmp(aPropNames[ind].pstrName, L"src")) + { + mCurFileUrl.AssignBSTR(aVal[ind].bstrVal); + } + else if( aVal[ind].vt == VT_BSTR + && !wcscmp(aPropNames[ind].pstrName, L"readonly")) + { + if (!wcscmp(aVal[ind].bstrVal, L"true")) + { + // the default value + mbViewOnly = TRUE; + } + else + { + mbViewOnly = FALSE; + } + } + } + + delete[] hvs; + delete[] aVal; + delete[] aPropNames; + + if( !mpDispFactory ) + return hr; + + mbReadyForActivation = FALSE; + hr = CBindStatusCallback<CSOActiveX>::Download( + this, &CSOActiveX::CallbackCreateXInputStream, mCurFileUrl, m_spClientSite, FALSE); + if (hr == MK_S_ASYNCHRONOUS) + hr = S_OK; + + if ( !SUCCEEDED( hr ) ) + { + // trigger initialization without stream + mbLoad = TRUE; + + Invalidate(); + UpdateWindow(); + } + + return hr; +} + +HRESULT CSOActiveX::GetUnoStruct( OLECHAR const * sStructName, CComPtr<IDispatch>& pdispResult ) +{ + CComVariant aComStruct( sStructName ); + return GetIDispByFunc( mpDispFactory, L"Bridge_GetStruct", &aComStruct, 1, pdispResult ); +} + +HRESULT CSOActiveX::GetUrlStruct( OLECHAR const * sUrl, CComPtr<IDispatch>& pdispUrl ) +{ + HRESULT hr = GetUnoStruct( L"com.sun.star.util.URL", pdispUrl ); + if( !SUCCEEDED( hr ) ) return hr; + + OLECHAR const * sURLMemberName = L"Complete"; + DISPID nURLID; + hr = pdispUrl->GetIDsOfNames( IID_NULL, const_cast<OLECHAR **>(&sURLMemberName), 1, LOCALE_USER_DEFAULT, &nURLID ); + if( !SUCCEEDED( hr ) ) return hr; + CComVariant aComUrl( sUrl ); + hr = CComDispatchDriver::PutProperty( pdispUrl, nURLID, &aComUrl ); + if( !SUCCEEDED( hr ) ) return hr; + + CComPtr<IDispatch> pdispTransformer; + CComVariant aServiceName( L"com.sun.star.util.URLTransformer" ); + hr = GetIDispByFunc( mpDispFactory, + L"createInstance", + &aServiceName, + 1, + pdispTransformer ); + if( !SUCCEEDED( hr ) ) return hr; + + CComVariant dummyResult; + CComVariant aParam[2]; + aParam[1].ppdispVal = &pdispUrl; + aParam[1].vt = VT_DISPATCH | VT_BYREF; + aParam[0] = CComVariant( L"file:///" ); + + hr = ExecuteFunc( pdispTransformer, L"parseSmart", aParam, 2, &dummyResult ); + if( !SUCCEEDED( hr ) || dummyResult.vt != VT_BOOL || !dummyResult.boolVal ) return hr; + + return S_OK; +} + +HRESULT CSOActiveX::SetLayoutManagerProps() +{ + if ( !mpDispFrame ) + return E_FAIL; + + CComVariant pVarLayoutMgr; + OLECHAR const * sLMPropName = L"LayoutManager"; + HRESULT hr = GetPropertiesFromIDisp( mpDispFrame, &sLMPropName, &pVarLayoutMgr, 1 ); + if( pVarLayoutMgr.vt != VT_DISPATCH || pVarLayoutMgr.pdispVal == nullptr ) + return E_FAIL; + + CComPtr<IDispatch> pdispLM( pVarLayoutMgr.pdispVal ); + + + if( !SUCCEEDED( hr ) || !pdispLM ) + return E_FAIL; + + OLECHAR const * sATName = L"AutomaticToolbars"; + CComVariant pATProp; + pATProp.vt = VT_BOOL; pATProp.boolVal = VARIANT_FALSE ; + hr = PutPropertiesToIDisp( pdispLM, &sATName, &pATProp, 1 ); + + return hr; +} + +HRESULT CSOActiveX::CreateFrameOldWay( HWND hwnd, int width, int height ) +{ + if( !mpDispFactory ) + return E_FAIL; + + // create window handle holder + CComPtr< CComObject< SOComWindowPeer > > pPeerToSend = new CComObject<SOComWindowPeer>(); + pPeerToSend->SetHWNDInternally( hwnd ); + CComQIPtr< IDispatch, &IID_IDispatch > pIDispToSend( pPeerToSend ); + + // create rectangle structure + CComPtr<IDispatch> pdispRectangle; + HRESULT hr = GetUnoStruct( L"com.sun.star.awt.Rectangle", pdispRectangle ); + if( !SUCCEEDED( hr ) ) return hr; + + OLECHAR const * sRectMemberNames[4] = { L"X", + L"Y", + L"Width", + L"Height" }; + CComVariant pRectVariant[4]; + pRectVariant[0] = pRectVariant[1] = pRectVariant[2] = pRectVariant[3] = CComVariant( 0 ); + + hr = PutPropertiesToIDisp( pdispRectangle, sRectMemberNames, pRectVariant, 4 ); + if( !SUCCEEDED( hr ) ) return hr; + + // create WindowDescriptor structure + CComPtr<IDispatch> pdispWinDescr; + hr = GetUnoStruct( L"com.sun.star.awt.WindowDescriptor", pdispWinDescr ); + if( !SUCCEEDED( hr ) ) return hr; + + // fill in descriptor with info + OLECHAR const * sDescriptorMemberNames[6] = { L"Type", + L"WindowServiceName", + L"ParentIndex", + L"Parent", + L"Bounds", + L"WindowAttributes" }; + CComVariant pDescriptorVar[6]; + pDescriptorVar[0] = CComVariant( 0 ); + pDescriptorVar[1] = CComVariant( L"workwindow" ); + pDescriptorVar[2] = CComVariant( 1 ); + pDescriptorVar[3] = CComVariant( pIDispToSend ); + pDescriptorVar[4] = CComVariant( pdispRectangle ); + pDescriptorVar[5] = CComVariant( 33 ); + hr = PutPropertiesToIDisp( pdispWinDescr, sDescriptorMemberNames, pDescriptorVar, 6 ); + if( !SUCCEEDED( hr ) ) return hr; + + // create XToolkit instance + CComPtr<IDispatch> pdispToolkit; + CComVariant aServiceName( L"com.sun.star.awt.Toolkit" ); + hr = GetIDispByFunc( mpDispFactory, L"createInstance", &aServiceName, 1, pdispToolkit ); + if( !SUCCEEDED( hr ) ) return hr; + + // create window with toolkit + CComVariant aWinDescr( pdispWinDescr ); + hr = GetIDispByFunc( pdispToolkit, L"createWindow", &aWinDescr, 1, mpDispWin ); + if( !SUCCEEDED( hr ) ) return hr; + + // create frame + aServiceName = CComVariant( L"com.sun.star.frame.Task" ); + hr = GetIDispByFunc( mpDispFactory, L"createInstance", &aServiceName, 1, mpDispFrame ); + if( !SUCCEEDED( hr ) || !mpDispFrame ) + { + // the interface com.sun.star.frame.Task is removed in 6.1 + // but the interface com.sun.star.frame.Frame has some bugs in 6.0 + aServiceName = CComVariant( L"com.sun.star.frame.Frame" ); + hr = GetIDispByFunc( mpDispFactory, L"createInstance", &aServiceName, 1, mpDispFrame ); + if( !SUCCEEDED( hr ) ) return hr; + } + + // initialize frame + CComVariant dummyResult; + CComVariant aDispWin( mpDispWin ); + hr = ExecuteFunc( mpDispFrame, L"initialize", &aDispWin, 1, &dummyResult ); + if( !SUCCEEDED( hr ) ) return hr; + + // set some properties to the layout manager, ignore errors for now + SetLayoutManagerProps(); + + // create desktop + CComPtr<IDispatch> pdispDesktop; + aServiceName = CComVariant( L"com.sun.star.frame.Desktop" ); + hr = GetIDispByFunc( mpDispFactory, L"createInstance", &aServiceName, 1, pdispDesktop ); + if( !SUCCEEDED( hr ) ) return hr; + + // create tree of frames + CComPtr<IDispatch> pdispChildren; + hr = GetIDispByFunc( pdispDesktop, L"getFrames", nullptr, 0, pdispChildren ); + if( !SUCCEEDED( hr ) ) return hr; + + // insert new frame into desktop hierarchy + CComVariant aDispFrame( mpDispFrame ); + hr = ExecuteFunc( pdispChildren, L"append", &aDispFrame, 1, &dummyResult ); + if( !SUCCEEDED( hr ) ) return hr; + + // initialize window + CComVariant aTransparent( long(0xFFFFFFFF) ); + hr = ExecuteFunc( mpDispWin, L"setBackground", &aTransparent, 1, &dummyResult ); + if( !SUCCEEDED( hr ) ) return hr; + + CComVariant aTrue( TRUE ); + hr = ExecuteFunc( mpDispWin, L"setVisible", &aTrue, 1, &dummyResult ); + if( !SUCCEEDED( hr ) ) return hr; + + CComVariant aPosArgs[5]; + aPosArgs[4] = CComVariant( 0 ); + aPosArgs[3] = CComVariant( 0 ); + aPosArgs[2] = CComVariant( width ); + aPosArgs[1] = CComVariant( height ); + aPosArgs[0] = CComVariant( 12 ); + hr = ExecuteFunc( mpDispWin, L"setPosSize", aPosArgs, 5, &dummyResult ); + if( !SUCCEEDED( hr ) ) return hr; + + // create frame locker if there is such service + aServiceName = CComVariant( L"com.sun.star.embed.InstanceLocker" ); + hr = GetIDispByFunc( mpDispFactory, L"createInstance", &aServiceName, 1, mpInstanceLocker ); + if( SUCCEEDED( hr ) && mpInstanceLocker ) + { + SAFEARRAY* pInitVals = SafeArrayCreateVector(VT_VARIANT, 0, 3); + + // the first sequence element + LONG nInitInd = 0; + CComVariant pFrameVariant( mpDispFrame ); + SafeArrayPutElement( pInitVals, &nInitInd, &pFrameVariant ); + + // the second sequence element + nInitInd = 1; + CComVariant pStrArr( 1 ); + SafeArrayPutElement( pInitVals, &nInitInd, &pStrArr ); + + // the third sequence element + nInitInd = 2; + CComPtr<IDispatch> pdispValueObj; + hr = GetIDispByFunc( mpDispFactory, L"Bridge_GetValueObject", nullptr, 0, pdispValueObj ); + if( !SUCCEEDED( hr ) || !pdispValueObj ) return hr; + + CComVariant aValueArgs[2]; + aValueArgs[1] = CComVariant( L"com.sun.star.embed.XActionsApproval" ); + CComPtr< CComObject< SOActionsApproval > > pApproval( new CComObject<SOActionsApproval>() ); + aValueArgs[0] = CComVariant ( pApproval ); + + hr = ExecuteFunc( pdispValueObj, L"Set", aValueArgs, 2, &dummyResult ); + if( !SUCCEEDED( hr ) ) return hr; + + CComVariant aValueObj( pdispValueObj ); + SafeArrayPutElement( pInitVals, &nInitInd, &aValueObj ); + + // execute initialize() + CComVariant aVarInitVals; + aVarInitVals.vt = VT_ARRAY | VT_VARIANT; aVarInitVals.parray = pInitVals; + hr = ExecuteFunc( mpInstanceLocker, L"initialize", &aVarInitVals, 1, &dummyResult ); + if( !SUCCEEDED( hr ) ) return hr; + } + + return S_OK; +} + +HRESULT CSOActiveX::CallLoadComponentFromURL1PBool( OLECHAR const * sUrl, OLECHAR const * sArgName, BOOL sArgVal ) +{ + SAFEARRAY* pPropVals = SafeArrayCreateVector(VT_DISPATCH, 0, 1); + LONG ix = 0; + CComPtr<IDispatch> pdispPropVal; + HRESULT hr = GetUnoStruct( L"com.sun.star.beans.PropertyValue", pdispPropVal ); + if( !SUCCEEDED( hr ) ) return hr; + + OLECHAR const * sPropMemberNames[2] = { L"Name", L"Value" }; + CComVariant pPropVar[2]; + pPropVar[0] = CComVariant( sArgName ); + pPropVar[1].vt = VT_BOOL; pPropVar[1].boolVal = sArgVal ? VARIANT_TRUE : VARIANT_FALSE ; + hr = PutPropertiesToIDisp( pdispPropVal, sPropMemberNames, pPropVar, 2 ); + if( !SUCCEEDED( hr ) ) return hr; + + SafeArrayPutElement( pPropVals, &ix, pdispPropVal ); + + CComVariant aDispArgs[4]; + aDispArgs[3] = CComVariant( sUrl ); + aDispArgs[2] = CComVariant( L"_self" ); + aDispArgs[1] = CComVariant( 0 ); + // aDispArgs[0] = CComVariant( pPropVals ); such constructor is not defined ??! + aDispArgs[0].vt = VT_ARRAY | VT_DISPATCH; aDispArgs[0].parray = pPropVals; + + CComVariant dummyResult; + hr = ExecuteFunc( mpDispFrame, L"loadComponentFromURL", aDispArgs, 4, &dummyResult ); + if( !SUCCEEDED( hr ) ) return hr; + + return S_OK; +} + +HRESULT CSOActiveX::CallDispatchMethod( OLECHAR const * sUrl, + CComVariant* aArgNames, + CComVariant* aArgVals, + unsigned int count ) +{ + CComPtr<IDispatch> pdispURL; + HRESULT hr = GetUrlStruct( sUrl, pdispURL ); + if( !SUCCEEDED( hr ) ) return hr; + + CComPtr<IDispatch> pdispXDispatch; + CComVariant aArgs[3]; + aArgs[2] = CComVariant( pdispURL ); + aArgs[1] = CComVariant( L"" ); + aArgs[0] = CComVariant( int(0) ); + hr = GetIDispByFunc( mpDispFrame, + L"queryDispatch", + aArgs, + 3, + pdispXDispatch ); + if( !SUCCEEDED( hr ) ) return hr; + + SAFEARRAY* pPropVals = SafeArrayCreateVector(VT_DISPATCH, 0, count); + for( LONG ix = 0; ix < static_cast<LONG>(count); ix ++ ) + { + CComPtr<IDispatch> pdispPropVal; + hr = GetUnoStruct( L"com.sun.star.beans.PropertyValue", pdispPropVal ); + if( !SUCCEEDED( hr ) ) return hr; + + OLECHAR const * sPropMemberNames[2] = { L"Name", L"Value" }; + CComVariant pPropVar[2]; + pPropVar[0] = aArgNames[ix]; + pPropVar[1] = aArgVals[ix]; + hr = PutPropertiesToIDisp( pdispPropVal, sPropMemberNames, pPropVar, 2 ); + if( !SUCCEEDED( hr ) ) return hr; + + SafeArrayPutElement( pPropVals, &ix, pdispPropVal ); + } + + CComVariant aDispArgs[2]; + aDispArgs[1] = CComVariant( pdispURL ); + // aDispArgs[0] = CComVariant( pPropVals ); such constructor is not defined ??! + aDispArgs[0].vt = VT_ARRAY | VT_DISPATCH; aDispArgs[0].parray = pPropVals; + + CComVariant dummyResult; + hr = ExecuteFunc( pdispXDispatch, L"dispatch", aDispArgs, 2, &dummyResult ); + if( !SUCCEEDED( hr ) ) return hr; + + return S_OK; +} + +void CSOActiveX::CallbackCreateXInputStream( CBindStatusCallback<CSOActiveX>* /*pbsc*/, BYTE* pBytes, DWORD dwSize ) +{ + if ( mbReadyForActivation ) + return; + + bool bSuccess = false; + bool bFinishDownload = false; + if ( !pBytes ) + { + // means the download is finished, dwSize contains hresult + bFinishDownload = true; + if ( SUCCEEDED( dwSize ) ) + bSuccess = true; + } + else + { + HRESULT hr = S_OK; + + if ( !mpDispTempFile ) + { + CComVariant aServiceName( L"com.sun.star.io.TempFile" ); + hr = GetIDispByFunc( mpDispFactory, + L"createInstance", + &aServiceName, + 1, + mpDispTempFile ); + } + + if( SUCCEEDED( hr ) && mpDispTempFile ) + { + SAFEARRAY* pDataArray = SafeArrayCreateVector(VT_I1, 0, dwSize); + + if ( pDataArray ) + { + hr = SafeArrayLock( pDataArray ); + if ( SUCCEEDED( hr ) ) + { + for( DWORD ix = 0; ix < dwSize; ix++ ) + static_cast<BYTE*>(pDataArray->pvData)[ix] = pBytes[ix]; + hr = SafeArrayUnlock( pDataArray ); + if ( SUCCEEDED( hr ) ) + { + CComVariant aArgs[1]; + aArgs[0].vt = VT_ARRAY | VT_I1; aArgs[0].parray = pDataArray; + CComVariant dummyResult; + hr = ExecuteFunc( mpDispTempFile, L"writeBytes", aArgs, 1, &dummyResult ); + if( SUCCEEDED( hr ) ) + bSuccess = true; + } + } + } + } + } + + if ( !bSuccess ) + { + // the download failed, let StarOffice download + bFinishDownload = true; + mpDispTempFile = CComPtr< IDispatch >(); + } + + if ( bFinishDownload ) + { + // trigger the loading now + mbLoad = TRUE; + mbReadyForActivation = TRUE; + + Invalidate(); + UpdateWindow(); + } +} + +HRESULT CSOActiveX::LoadURLToFrame( ) +{ + CComVariant aArgNames[4] = { L"ReadOnly", L"ViewOnly", L"AsTemplate", L"InputStream" }; + CComVariant aArgVals[4]; + unsigned int nCount = 3; // the 4-th argument is used only if the stream can be retrieved + + aArgVals[0].vt = VT_BOOL; aArgVals[0].boolVal = mbViewOnly ? VARIANT_TRUE : VARIANT_FALSE; + aArgVals[1].vt = VT_BOOL; aArgVals[1].boolVal = mbViewOnly ? VARIANT_TRUE : VARIANT_FALSE; + aArgVals[2].vt = VT_BOOL; aArgVals[2].boolVal = VARIANT_FALSE; + + if ( mpDispTempFile ) + { + aArgVals[3] = CComVariant( mpDispTempFile ); + nCount = 4; + } + + HRESULT hr = CallDispatchMethod( mCurFileUrl, aArgNames, aArgVals, nCount ); + if( !SUCCEEDED( hr ) ) return hr; + + // try to get the model and set the presentation specific property, the setting will fail for other document formats + CComPtr<IDispatch> pdispController; + hr = GetIDispByFunc( mpDispFrame, L"getController", nullptr, 0, pdispController ); + if ( SUCCEEDED( hr ) && pdispController ) + { + CComPtr<IDispatch> pdispModel; + hr = GetIDispByFunc( pdispController, L"getModel", nullptr, 0, pdispModel ); + if ( SUCCEEDED( hr ) && pdispModel ) + { + CComPtr<IDispatch> pdispPres; + hr = GetIDispByFunc( pdispModel, L"getPresentation", nullptr, 0, pdispPres ); + if ( SUCCEEDED( hr ) && pdispPres ) + { + // this is a presentation + // let the slide show be shown in the document window + OLECHAR const * pPropName = L"IsFullScreen"; + CComVariant pPresProp; + pPresProp.vt = VT_BOOL; pPresProp.boolVal = VARIANT_FALSE ; + hr = PutPropertiesToIDisp( pdispPres, &pPropName, &pPresProp, 1 ); + + // start the slide show + if ( SUCCEEDED( hr ) ) + { + CComVariant dummyResult; + ExecuteFunc( pdispPres, L"Start", nullptr, 0, &dummyResult ); + } + } + } + } + + // create dispatch interceptor + mpDispatchInterceptor = new CComObject< SODispatchInterceptor >(); + mpDispatchInterceptor->AddRef(); + mpDispatchInterceptor->SetParent( this ); + CComQIPtr< IDispatch, &IID_IDispatch > pIDispDispInter( mpDispatchInterceptor ); + + // register dispatch interceptor in the frame + CComVariant aDispVariant( pIDispDispInter ); + CComVariant dummyResult; + hr = ExecuteFunc( mpDispFrame, + L"registerDispatchProviderInterceptor", + &aDispVariant, + 1, + &dummyResult ); + + if( !SUCCEEDED( hr ) ) return hr; + + return S_OK; +} + +SOVersion CSOActiveX::GetVersionConnected() +{ + SOVersion bResult = SO_NOT_DETECTED; + if( mpDispFactory ) + { + // create ConfigurationProvider instance + CComPtr<IDispatch> pdispConfProv; + CComVariant aServiceName( L"com.sun.star.configuration.ConfigurationProvider" ); + HRESULT hr = GetIDispByFunc( mpDispFactory, + L"createInstance", + &aServiceName, + 1, + pdispConfProv ); + + if( SUCCEEDED( hr ) && pdispConfProv ) + { + CComPtr<IDispatch> pdispConfAccess; + + SAFEARRAY* pInitParams = SafeArrayCreateVector( VT_VARIANT, 0, 1 ); + + if( pInitParams ) + { + LONG ix = 0; + CComVariant aConfPath( L"org.openoffice.Setup" ); + SafeArrayPutElement( pInitParams, &ix, &aConfPath ); + + CComVariant aArgs[2]; + aArgs[1] = CComVariant( L"com.sun.star.configuration.ConfigurationAccess" ); + aArgs[0].vt = VT_ARRAY | VT_VARIANT; aArgs[0].parray = pInitParams; + + hr = GetIDispByFunc( pdispConfProv, + L"createInstanceWithArguments", + aArgs, + 2, + pdispConfAccess ); + + if( SUCCEEDED( hr ) && pdispConfAccess ) + { + CComVariant aOfficeName; + + CComVariant aProductName( L"Product/ooName" ); + hr = ExecuteFunc( pdispConfAccess, + L"getByHierarchicalName", + &aProductName, + 1, + &aOfficeName ); + + if( SUCCEEDED( hr ) && aOfficeName.vt == VT_BSTR ) + { + CComVariant aOfficeVersion; + + CComVariant aProductVersion( L"Product/ooSetupVersion" ); + hr = ExecuteFunc( pdispConfAccess, + L"getByHierarchicalName", + &aProductVersion, + 1, + &aOfficeVersion ); + + if( SUCCEEDED( hr ) && aOfficeVersion.vt == VT_BSTR ) + { + if (!wcscmp(aOfficeName.bstrVal, L"StarOffice")) + { + if (!wcsncmp(aOfficeVersion.bstrVal, L"6.1", 3)) + bResult = SO_61; + else if (!wcsncmp(aOfficeVersion.bstrVal, L"6.0", 3)) + bResult = SO_60; + else if (!wcsncmp(aOfficeVersion.bstrVal, L"5.2", 3)) + bResult = SO_52; + else + bResult = SO_UNKNOWN; + } + else // OpenOffice + { + if (!wcsncmp(aOfficeVersion.bstrVal, L"1.1", 3)) + bResult = OO_11; + else if (!wcsncmp(aOfficeVersion.bstrVal, L"1.0", 3)) + bResult = OO_10; + else + bResult = OO_UNKNOWN; + } + } + } + } + } + } + } + + return bResult; +} + +namespace { + +class LockingGuard +{ + bool& mbLocked; +public: + explicit LockingGuard( bool& bLocked ) + : mbLocked( bLocked ) + { + mbLocked = true; + } + + ~LockingGuard() + { + mbLocked = false; + } +}; + +} + +HRESULT CSOActiveX::OnDrawAdvanced( ATL_DRAWINFO& di ) +{ + // This method is called only in main thread, no need to lock it + + // Get read of reentrance problems + if ( mbDrawLocked ) + return S_OK; + LockingGuard aGuard( mbDrawLocked ); + + if( m_spInPlaceSite && mCurFileUrl && mbReadyForActivation ) + { + HWND hwnd; + HRESULT hr = m_spInPlaceSite->GetWindow( &hwnd ); + if( !SUCCEEDED( hr ) ) return hr; + + if( mParentWin != hwnd || !mOffWin ) + { + if( mpDispFrame ) + { + CComVariant dummyResult; + CComVariant aPropVar; + aPropVar.vt = VT_BOOL; aPropVar.boolVal = VARIANT_FALSE; + (void) ExecuteFunc( mpDispFrame, L"close", &aPropVar, 1, &dummyResult ); + mpDispFrame = CComPtr<IDispatch>(); + } + + mParentWin = hwnd; + mOffWin = CreateWindowW( + STAROFFICE_WINDOWCLASS, + L"OfficeContainer", + WS_CHILD | WS_CLIPCHILDREN | WS_BORDER, + di.prcBounds->left, + di.prcBounds->top, + di.prcBounds->right - di.prcBounds->left, + di.prcBounds->bottom - di.prcBounds->top, + mParentWin, + nullptr, + nullptr, + nullptr ); + + ::ShowWindow( mOffWin, SW_SHOW ); + } + else + { + RECT aRect; + ::GetWindowRect( mOffWin, &aRect ); + + if( aRect.left != di.prcBounds->left || aRect.top != di.prcBounds->top + || aRect.right != di.prcBounds->right || aRect.bottom != di.prcBounds->bottom ) + { + // on this state the office window should exist already + ::SetWindowPos( mOffWin, + HWND_TOP, + di.prcBounds->left, + di.prcBounds->top, + di.prcBounds->right - di.prcBounds->left, + di.prcBounds->bottom - di.prcBounds->top, + SWP_NOZORDER ); + + CComVariant aPosArgs[5]; + aPosArgs[4] = CComVariant( 0 ); + aPosArgs[3] = CComVariant( 0 ); + aPosArgs[2] = CComVariant( int(di.prcBounds->right - di.prcBounds->left) ); + aPosArgs[1] = CComVariant( int(di.prcBounds->bottom - di.prcBounds->top) ); + aPosArgs[0] = CComVariant( 12 ); + CComVariant dummyResult; + hr = ExecuteFunc( mpDispWin, L"setPosSize", aPosArgs, 5, &dummyResult ); + if( !SUCCEEDED( hr ) ) return hr; + } + } + + if (mnVersion == SO_NOT_DETECTED) + { + OutputError_Impl( mOffWin, CS_E_INVALID_VERSION ); + return E_FAIL; + } + + if( ! mpDispFrame ) + { + hr = CreateFrameOldWay( mOffWin, + di.prcBounds->right - di.prcBounds->left, + di.prcBounds->bottom - di.prcBounds->top ); + + if( !SUCCEEDED( hr ) ) + { + // if the frame can not be opened do not try any more + mbReadyForActivation = FALSE; + OutputError_Impl( mOffWin, STG_E_ABNORMALAPIEXIT ); + return hr; + } + } + + if( mbLoad ) + { + hr = LoadURLToFrame(); + mbLoad = FALSE; + + if( !SUCCEEDED( hr ) ) + { + // if the document can not be opened do not try any more + mbReadyForActivation = FALSE; + + OutputError_Impl( mOffWin, STG_E_ABNORMALAPIEXIT ); + + return hr; + } + } + } + else + { + // activate the fallback + CComControl<CSOActiveX>::OnDrawAdvanced( di ); + } + + return S_OK; +} + +HRESULT CSOActiveX::OnDraw( ATL_DRAWINFO& di ) +{ + // fallback that is activated by the parent class + if ( di.hdcDraw ) + FillRect( di.hdcDraw, reinterpret_cast<RECT const *>(di.prcBounds), reinterpret_cast<HBRUSH>(COLOR_BACKGROUND) ); + + return S_OK; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP CSOActiveX::SetClientSite( IOleClientSite* aClientSite ) +{ + HRESULT hr = IOleObjectImpl<CSOActiveX>::SetClientSite( aClientSite ); + + if( !aClientSite ) + { + //ATLASSERT( mWebBrowser2 ); + if( mWebBrowser2 ) + AtlUnadvise( mWebBrowser2, DIID_DWebBrowserEvents2, mCookie ); + return hr; + } + + CComPtr<IOleContainer> aContainer; + m_spClientSite->GetContainer( &aContainer ); +// ATLASSERT( aContainer ); + + if( SUCCEEDED( hr ) && aContainer ) + { + CComQIPtr<IServiceProvider, &IID_IServiceProvider> aServiceProvider( aContainer ); + //ATLASSERT( aServiceProvider ); + + if( aServiceProvider ) + { + aServiceProvider->QueryService( SID_SInternetExplorer, + IID_IWebBrowser, + reinterpret_cast<void**>(&mWebBrowser2) ); +// ATLASSERT( mWebBrowser2 ); + if( mWebBrowser2 ) + AtlAdvise( mWebBrowser2, GetUnknown(), DIID_DWebBrowserEvents2, &mCookie ); + } + } + + return hr; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP CSOActiveX::Invoke(DISPID dispidMember, + REFIID riid, + LCID lcid, + WORD wFlags, + DISPPARAMS* pDispParams, + VARIANT* pvarResult, + EXCEPINFO* pExcepInfo, + UINT* puArgErr) +{ + if (riid != IID_NULL) + return DISP_E_UNKNOWNINTERFACE; + + if (!pDispParams) + return DISP_E_PARAMNOTOPTIONAL; + + if ( dispidMember == DISPID_ONQUIT ) + Cleanup(); + + IDispatchImpl<ISOActiveX, &IID_ISOActiveX, + &LIBID_SO_ACTIVEXLib>::Invoke( + dispidMember, riid, lcid, wFlags, pDispParams, + pvarResult, pExcepInfo, puArgErr); + + return S_OK; +} + +HRESULT CSOActiveX::GetURL( const OLECHAR* url, + const OLECHAR* target ) +{ + CComVariant aEmpty1, aEmpty2, aEmpty3; + CComVariant aUrl( url ); + CComVariant aTarget; + if ( target ) + aTarget = CComVariant( target ); + + return mWebBrowser2->Navigate2( &aUrl, + &aEmpty1, + &aTarget, + &aEmpty2, + &aEmpty3 ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/activex/SOActiveX.h b/extensions/source/activex/SOActiveX.h new file mode 100644 index 0000000000..49f33d7960 --- /dev/null +++ b/extensions/source/activex/SOActiveX.h @@ -0,0 +1,205 @@ +/* -*- 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 . + */ + +// SOActiveX.h : Declaration of the CSOActiveX + +#pragma once + +#include "resource.h" + +#include <ExDispID.h> +#include <ExDisp.h> +#include <shlguid.h> + +#include <atlctl.h> + +#include <so_activex.h> + +class SODispatchInterceptor; + +enum SOVersion { + SO_NOT_DETECTED = 0, + SO_52, + SO_60, + SO_61, + SO_UNKNOWN, + OO_10, + OO_11, + OO_UNKNOWN +}; + + +// CSOActiveX +class ATL_NO_VTABLE CSOActiveX : + public CComObjectRootEx<CComSingleThreadModel>, + public IDispatchImpl<ISOActiveX, &IID_ISOActiveX, &LIBID_SO_ACTIVEXLib>, + public CComControl<CSOActiveX>, + public IPersistStreamInitImpl<CSOActiveX>, + public IOleControlImpl<CSOActiveX>, + public IOleObjectImpl<CSOActiveX>, + public IOleInPlaceActiveObjectImpl<CSOActiveX>, + public IViewObjectExImpl<CSOActiveX>, + public IOleInPlaceObjectWindowlessImpl<CSOActiveX>, +// public IConnectionPointContainerImpl<CSOActiveX>, + public CComCoClass<CSOActiveX, &CLSID_SOActiveX>, +// public CProxy_ItryPluginEvents< CSOActiveX >, + public IPersistPropertyBagImpl< CSOActiveX >, + public IProvideClassInfo2Impl< &CLSID_SOActiveX, + &DIID__ISOActiveXEvents, + &LIBID_SO_ACTIVEXLib >, + public IObjectSafetyImpl< CSOActiveX, + INTERFACESAFE_FOR_UNTRUSTED_DATA > +{ +protected: + CComPtr<IWebBrowser2> mWebBrowser2; + DWORD mCookie; + + CComPtr<IDispatch> mpDispFactory; + CComPtr<IDispatch> mpDispFrame; + CComPtr<IDispatch> mpInstanceLocker; + CComPtr<IDispatch> mpDispWin; + CComBSTR mCurFileUrl; + BOOL mbLoad; + BOOL mbViewOnly; + WNDCLASSW mPWinClass; + HWND mParentWin; + HWND mOffWin; + + SODispatchInterceptor* mpDispatchInterceptor; + SOVersion mnVersion; + + BOOL mbReadyForActivation; + CComPtr<IDispatch> mpDispTempFile; + + bool mbDrawLocked; + +public: + CSOActiveX(); + ~CSOActiveX() override; + +DECLARE_REGISTRY_RESOURCEID(IDR_SOACTIVEX) + +DECLARE_PROTECT_FINAL_CONSTRUCT() + +BEGIN_COM_MAP(CSOActiveX) + COM_INTERFACE_ENTRY(ISOActiveX) + COM_INTERFACE_ENTRY(IDispatch) + COM_INTERFACE_ENTRY(IViewObjectEx) + COM_INTERFACE_ENTRY(IViewObject2) + COM_INTERFACE_ENTRY(IViewObject) + COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless) + COM_INTERFACE_ENTRY(IOleInPlaceObject) + COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless) + COM_INTERFACE_ENTRY(IOleInPlaceActiveObject) + COM_INTERFACE_ENTRY(IOleControl) + COM_INTERFACE_ENTRY(IOleObject) + COM_INTERFACE_ENTRY(IPersistStreamInit) + COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit) +// COM_INTERFACE_ENTRY(IConnectionPointContainer) + COM_INTERFACE_ENTRY(IProvideClassInfo) + COM_INTERFACE_ENTRY(IProvideClassInfo2) + COM_INTERFACE_ENTRY(IPersistPropertyBag) + COM_INTERFACE_ENTRY(IObjectSafety) +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winconsistent-missing-override" +#endif +END_COM_MAP() +#if defined __clang__ +#pragma clang diagnostic pop +#endif + +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winvalid-offsetof" + // offset of on non-standard-layout type '_PropMapClass' (aka 'CSOActiveX'), + // expanded from macro 'PROP_DATA_ENTRY' +#endif +BEGIN_PROP_MAP(CSOActiveX) + PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4) + PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4) + // Example entries + // PROP_ENTRY("Property Description", dispid, clsid) + // PROP_PAGE(CLSID_StockColorPage) +END_PROP_MAP() +#if defined __clang__ +#pragma clang diagnostic pop +#endif + +BEGIN_CONNECTION_POINT_MAP(CSOActiveX) +END_CONNECTION_POINT_MAP() + +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winconsistent-missing-override" +#endif +BEGIN_MSG_MAP(CSOActiveX) +#if defined __clang__ +#pragma clang diagnostic pop +#endif + CHAIN_MSG_MAP(CComControl<CSOActiveX>) + DEFAULT_REFLECTION_HANDLER() +END_MSG_MAP() +// Handler prototypes: +// LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); +// LRESULT CommandHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled); +// LRESULT NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled); + + + +// IViewObjectEx + static DECLARE_VIEW_STATUS(VIEWSTATUS_SOLIDBKGND | VIEWSTATUS_OPAQUE) + +// ISOActiveX +public: + + STDMETHOD(SetClientSite)( IOleClientSite* aClientSite ) override; + STDMETHOD(Invoke)( DISPID dispidMember, + REFIID riid, + LCID lcid, + WORD wFlags, + DISPPARAMS* pDispParams, + VARIANT* pvarResult, + EXCEPINFO* pExcepInfo, + UINT* puArgErr) override; + STDMETHOD(Load) ( LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog ) override; + STDMETHOD(Load) ( LPSTREAM pStm ) override; + STDMETHOD(InitNew) () override; + HRESULT OnDrawAdvanced(ATL_DRAWINFO& di) override; + HRESULT OnDraw(ATL_DRAWINFO& di) override; + + HRESULT SetLayoutManagerProps(); + HRESULT CreateFrameOldWay( HWND hwnd, int width, int height ); + HRESULT GetUnoStruct( OLECHAR const * sStructName, CComPtr<IDispatch>& pdispResult ); + HRESULT LoadURLToFrame(); + HRESULT CallDispatchMethod( OLECHAR const * sUrl, CComVariant* sArgNames, CComVariant* sArgVal, unsigned int count ); + HRESULT CallLoadComponentFromURL1PBool( OLECHAR const * sUrl, OLECHAR const * sArgName, BOOL sArgVal ); + HRESULT GetUrlStruct( OLECHAR const * sUrl, CComPtr<IDispatch>& pdispUrl ); + HRESULT Cleanup(); + HRESULT TerminateOffice(); + HRESULT GetURL( const OLECHAR* url, + const OLECHAR* target ); + + void CallbackCreateXInputStream( CBindStatusCallback<CSOActiveX>* pbsc, BYTE* pBytes, DWORD dwSize ); + + + SOVersion GetVersionConnected(); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/activex/SOActiveX.rgs b/extensions/source/activex/SOActiveX.rgs new file mode 100644 index 0000000000..d3814df3b2 --- /dev/null +++ b/extensions/source/activex/SOActiveX.rgs @@ -0,0 +1,33 @@ +HKCR +{ + so_activex.SOActiveX.1 = s 'SOActiveX Class' + { + CLSID = s '{67F2A879-82D5-4A6D-8CC5-FFB3C114B69D}' + } + so_activex.SOActiveX = s 'SOActiveX Class' + { + CLSID = s '{67F2A879-82D5-4A6D-8CC5-FFB3C114B69D}' + CurVer = s 'so_activex.SOActiveX.1' + } + NoRemove CLSID + { + ForceRemove {67F2A879-82D5-4A6D-8CC5-FFB3C114B69D} = s 'SOActiveX Class' + { + ProgID = s 'so_activex.SOActiveX.1' + VersionIndependentProgID = s 'so_activex.SOActiveX' + ForceRemove 'Programmable' + InprocServer32 = s '%MODULE%' + { + val ThreadingModel = s 'Apartment' + } + ForceRemove 'Control' + ForceRemove 'ToolboxBitmap32' = s '%MODULE%, 101' + 'MiscStatus' = s '0' + { + '1' = s '131473' + } + 'TypeLib' = s '{61FA3F13-8061-4796-B055-3697ED28CB38}' + 'Version' = s '1.0' + } + } +} diff --git a/extensions/source/activex/SOComWindowPeer.cxx b/extensions/source/activex/SOComWindowPeer.cxx new file mode 100644 index 0000000000..59510c24a7 --- /dev/null +++ b/extensions/source/activex/SOComWindowPeer.cxx @@ -0,0 +1,38 @@ +/* -*- 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 . + */ + +// SOComWindowPeer.cxx : Implementation of CHelpApp and DLL registration. + +#include <sal/config.h> + +#include <cstddef> + +#include "StdAfx2.h" +#include <so_activex.h> +#include "SOComWindowPeer.h" +#include <sal/macros.h> + +COM_DECLSPEC_NOTHROW STDMETHODIMP SOComWindowPeer::InterfaceSupportsErrorInfo(REFIID riid) +{ + if (InlineIsEqualGUID(IID_ISOComWindowPeer, riid)) + return S_OK; + return S_FALSE; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/activex/SOComWindowPeer.h b/extensions/source/activex/SOComWindowPeer.h new file mode 100644 index 0000000000..58136e8136 --- /dev/null +++ b/extensions/source/activex/SOComWindowPeer.h @@ -0,0 +1,152 @@ +/* -*- 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 . + */ + +// SOComWindowPeer.h: Definition of the SOComWindowPeer class + +#pragma once + +#ifdef _MSC_VER +#pragma once +#endif + +#include "resource.h" +#include <ExDispID.h> +#include <ExDisp.h> +#include <shlguid.h> + +#include <atlctl.h> + +#include <so_activex.h> + +// SOComWindowPeer + +class SOComWindowPeer : + public IDispatchImpl<ISOComWindowPeer, &IID_ISOComWindowPeer, &LIBID_SO_ACTIVEXLib>, + public ISupportErrorInfo, + public CComObjectRoot, + public CComCoClass<SOComWindowPeer,&CLSID_SOComWindowPeer> +{ + HWND m_hwnd; +public: + SOComWindowPeer() : m_hwnd( nullptr ) {} + virtual ~SOComWindowPeer() { } + +BEGIN_COM_MAP(SOComWindowPeer) + COM_INTERFACE_ENTRY(IDispatch) + COM_INTERFACE_ENTRY(ISOComWindowPeer) + COM_INTERFACE_ENTRY(ISupportErrorInfo) +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winconsistent-missing-override" +#endif +END_COM_MAP() +#if defined __clang__ +#pragma clang diagnostic pop +#endif +DECLARE_NOT_AGGREGATABLE(SOComWindowPeer) +// Remove the comment from the line above if you don't want your object to +// support aggregation. + +DECLARE_REGISTRY_RESOURCEID(IDR_SOCOMWINDOWPEER) + +// ISupportsErrorInfo + STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid) override; + +// ISOComWindowPeer + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE getWindowHandle( + /* [in] */ SAFEARRAY __RPC_FAR * /*procId*/, + /* [in] */ short /*s*/, + /* [retval][out] */ long __RPC_FAR *ret) override + { + *ret = HandleToLong( m_hwnd ); + return S_OK; + } + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE getToolkit( + /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *retVal) override + { + *retVal = nullptr; + return S_OK; + } + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE setPointer( + /* [in] */ IDispatch __RPC_FAR* /*xPointer*/) override + { + return S_OK; + } + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE setBackground( + /* [in] */ int /*nColor*/) override + { + return S_OK; + } + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE invalidate( + /* [in] */ short /*__MIDL_0015*/) override + { + return S_OK; + } + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE invalidateRect( + /* [in] */ IDispatch __RPC_FAR* /*aRect*/, + /* [in] */ short /*nFlags*/) override + { + return S_OK; + } + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE dispose( void) override + { + return S_OK; + } + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE addEventListener( + /* [in] */ IDispatch __RPC_FAR* /*xListener*/) override + { + return S_OK; + } + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE removeEventListener( + /* [in] */ IDispatch __RPC_FAR* /*xListener*/) override + { + return S_OK; + } + + virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Bridge_implementedInterfaces( + /* [retval][out] */ SAFEARRAY __RPC_FAR * __RPC_FAR *pVal) override + { + *pVal = SafeArrayCreateVector( VT_BSTR, 0, 2 ); + + if( !*pVal ) + return E_FAIL; + + LONG ix = 0; + CComBSTR aInterface( OLESTR( "com.sun.star.awt.XSystemDependentWindowPeer" ) ); + SafeArrayPutElement( *pVal, &ix, aInterface ); + + ix = 1; + aInterface = CComBSTR( OLESTR( "com.sun.star.awt.XWindowPeer" ) ); + SafeArrayPutElement( *pVal, &ix, aInterface ); + + return S_OK; + } + + void SetHWNDInternally( HWND hwnd ) { m_hwnd = hwnd; } +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/activex/SOComWindowPeer.rgs b/extensions/source/activex/SOComWindowPeer.rgs new file mode 100644 index 0000000000..42e985a31a --- /dev/null +++ b/extensions/source/activex/SOComWindowPeer.rgs @@ -0,0 +1,23 @@ +HKCR +{ + so_activex.SOComWindowPeer.1 = s 'SOComWindowPeer Class' + { + CLSID = s '{EE51BD3E-8BB6-4FB8-B319-F65B1BE3B21D}' + } + so_activex.SOComWindowPeer = s 'SOComWindowPeer Class' + { + CLSID = s '{EE51BD3E-8BB6-4FB8-B319-F65B1BE3B21D}' + } + NoRemove CLSID + { + ForceRemove {EE51BD3E-8BB6-4FB8-B319-F65B1BE3B21D} = s 'SOComWindowPeer Class' + { + ProgID = s 'so_activex.SOComWindowPeer.1' + VersionIndependentProgID = s 'so_activex.SOComWindowPeer' + InprocServer32 = s '%MODULE%' + { + val ThreadingModel = s 'both' + } + } + } +} diff --git a/extensions/source/activex/SODispatchInterceptor.cxx b/extensions/source/activex/SODispatchInterceptor.cxx new file mode 100644 index 0000000000..97ea07568f --- /dev/null +++ b/extensions/source/activex/SODispatchInterceptor.cxx @@ -0,0 +1,228 @@ +/* -*- 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 . + */ + +// SODispatchInterceptor.cxx : Implementation of CHelpApp and DLL registration. + +#include <sal/config.h> + +#include <cstddef> + +#include <stdio.h> +#include "StdAfx2.h" +#include <so_activex.h> +#include "SOActiveX.h" +#include "SODispatchInterceptor.h" +#include "com_uno_helper.h" +#include <sal/macros.h> + +COM_DECLSPEC_NOTHROW STDMETHODIMP SODispatchInterceptor::InterfaceSupportsErrorInfo(REFIID riid) +{ + if (InlineIsEqualGUID(IID_ISODispatchInterceptor, riid)) + return S_OK; + return S_FALSE; +} + +STDMETHODIMP SODispatchInterceptor::queryDispatch(IDispatch* aURL, BSTR aTargetFrameName, + long nSearchFlags, IDispatch** retVal) +{ + if ( !aURL || !retVal ) return E_FAIL; + + CComVariant aTargetUrl; + OLECHAR const * sURLMemberName = L"Complete"; + DISPID nURLID; + HRESULT hr = aURL->GetIDsOfNames( IID_NULL, const_cast<OLECHAR **>(&sURLMemberName), 1, LOCALE_USER_DEFAULT, &nURLID ); + if( !SUCCEEDED( hr ) ) return hr; + + hr = CComDispatchDriver::GetProperty( aURL, nURLID, &aTargetUrl ); + if( !SUCCEEDED( hr ) ) return hr; + + if( aTargetUrl.vt != VT_BSTR ) return E_FAIL; + + if (!wcsncmp(aTargetUrl.bstrVal, L".uno:OpenHyperlink", 18)) + { + CComQIPtr< IDispatch, &IID_IDispatch > pIDisp( this ); + if( pIDisp ) + { + this->AddRef(); + *retVal = pIDisp; + } + } + else + { + if( !m_xSlave ) + { + *retVal = nullptr; + return S_OK; + } + + CComVariant aResult; + CComVariant aArgs[3]; + aArgs[0] = CComVariant( nSearchFlags ); + aArgs[1] = CComVariant( aTargetFrameName ); + aArgs[2] = CComVariant( aURL ); + + hr = ExecuteFunc( m_xSlave, L"queryDispatch", aArgs, 3, &aResult ); + if( !SUCCEEDED( hr ) || aResult.vt != VT_DISPATCH || aResult.pdispVal == nullptr ) + { + *retVal = nullptr; + return S_OK; + } + + *retVal = aResult.pdispVal; + + CComQIPtr< IUnknown, &IID_IUnknown > pIUnk( *retVal ); + if( pIUnk ) + (*retVal)->AddRef(); + } + + return S_OK; +} + +STDMETHODIMP SODispatchInterceptor::queryDispatches(SAFEARRAY* aDescripts, SAFEARRAY** retVal) +{ + if ( !aDescripts || !retVal || SafeArrayGetDim( aDescripts ) != 1 ) + return E_FAIL; + + LONG nLB, nUB; + + HRESULT hr = SafeArrayGetLBound( aDescripts, 1, &nLB ); + if( !SUCCEEDED( hr ) ) return hr; + + hr = SafeArrayGetUBound( aDescripts, 1, &nUB ); + if( !SUCCEEDED( hr ) ) return hr; + if( nUB < nLB ) return E_FAIL; + + *retVal = SafeArrayCreateVector( VT_DISPATCH, 0, nUB - nLB ); + + for ( LONG ind = nLB; ind <= nUB; ind ++ ) + { + CComPtr<IDispatch> pElem; + SafeArrayGetElement( aDescripts, &ind, pElem ); + if( pElem ) + { + OLECHAR const * pMemberNames[3] = { L"FeatureURL", L"FrameName", L"SearchFlags" }; + CComVariant pValues[3]; + hr = GetPropertiesFromIDisp( pElem, pMemberNames, pValues, 3 ); + if( !SUCCEEDED( hr ) ) return hr; + if( pValues[0].vt != VT_DISPATCH || pValues[0].pdispVal == nullptr + || pValues[1].vt != VT_BSTR || pValues[2].vt != VT_I4 ) + return E_FAIL; + + CComPtr<IDispatch> aRes; + hr = queryDispatch( pValues[0].pdispVal, pValues[1].bstrVal, pValues[2].lVal, &aRes ); + SafeArrayPutElement( *retVal, &ind, aRes ); + } + } + + return S_OK; +} + + +STDMETHODIMP SODispatchInterceptor::dispatch(IDispatch* aURL, SAFEARRAY* aArgs) +{ + // get url from aURL + OLECHAR const * pUrlName = L"Complete"; + CComVariant pValue; + HRESULT hr = GetPropertiesFromIDisp( aURL, &pUrlName, &pValue, 1 ); + if( !SUCCEEDED( hr ) ) return hr; + if( pValue.vt != VT_BSTR || pValue.bstrVal == nullptr ) + return E_FAIL; + + if (!wcsncmp(pValue.bstrVal, L".uno:OpenHyperlink", 18)) + { + LONG nLB = 0, nUB = 0; + // long nDim = SafeArrayGetDim( aArgs ); + + hr = SafeArrayGetLBound( aArgs, 1, &nLB ); + if( !SUCCEEDED( hr ) ) return hr; + + hr = SafeArrayGetUBound( aArgs, 1, &nUB ); + if( !SUCCEEDED( hr ) ) return hr; + if( nUB < nLB ) return E_FAIL; + + for ( LONG ind = nLB; ind <= nUB; ind ++ ) + { + CComVariant pVarElem; + SafeArrayGetElement( aArgs, &ind, &pVarElem ); + if( pVarElem.vt == VT_DISPATCH && pVarElem.pdispVal != nullptr ) + { + OLECHAR const * pMemberNames[2] = { L"Name", L"Value" }; + CComVariant pValues[2]; + hr = GetPropertiesFromIDisp( pVarElem.pdispVal, pMemberNames, pValues, 2 ); + if( !SUCCEEDED( hr ) ) return hr; + + if( pValues[0].vt == VT_BSTR && pValues[1].vt == VT_BSTR ) + { + if (!wcsncmp(pValues[0].bstrVal, L"URL", 3)) + { + EnterCriticalSection( &mMutex ); + if( m_xParentControl ) + { + // call GetUrl to the browser instance + m_xParentControl->GetURL( pValues[1].bstrVal, L"_self" ); + } + LeaveCriticalSection( &mMutex ); + + break; + } + } + } + } + } + + return S_OK; +} + +STDMETHODIMP SODispatchInterceptor::addStatusListener(IDispatch* /*xControl*/, IDispatch* /*aURL*/) +{ + // not implemented + return S_OK; +} + +STDMETHODIMP SODispatchInterceptor::removeStatusListener(IDispatch* /*xControl*/, + IDispatch* /*aURL*/) +{ + // not implemented + return S_OK; +} + +STDMETHODIMP SODispatchInterceptor::getInterceptedURLs(SAFEARRAY** pVal) +{ + *pVal = SafeArrayCreateVector( VT_BSTR, 0, 3 ); + + if( !*pVal ) + return E_FAIL; + + LONG ix = 0; + CComBSTR aPattern( OLESTR( "ftp://*" ) ); + SafeArrayPutElement( *pVal, &ix, aPattern ); + + ix = 1; + aPattern = CComBSTR( OLESTR( "http://*" ) ); + SafeArrayPutElement( *pVal, &ix, aPattern ); + + ix = 2; + aPattern = CComBSTR( OLESTR( "file://*" ) ); + SafeArrayPutElement( *pVal, &ix, aPattern ); + + return S_OK; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/activex/SODispatchInterceptor.h b/extensions/source/activex/SODispatchInterceptor.h new file mode 100644 index 0000000000..3c06043482 --- /dev/null +++ b/extensions/source/activex/SODispatchInterceptor.h @@ -0,0 +1,175 @@ +/* -*- 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 . + */ + +// SODispatchInterceptor.h: Definition of the SODispatchInterceptor class + +#pragma once + +#ifdef _MSC_VER +#pragma once +#endif + +#include "resource.h" +#include <ExDispID.h> +#include <ExDisp.h> +#include <shlguid.h> + +#include <atlctl.h> + +#include <so_activex.h> + +class CSOActiveX; + + +// SODispatchInterceptor + +class SODispatchInterceptor : + public IDispatchImpl<ISODispatchInterceptor, &IID_ISODispatchInterceptor, &LIBID_SO_ACTIVEXLib>, + public ISupportErrorInfo, + public CComObjectRoot, + public CComCoClass<SODispatchInterceptor,&CLSID_SODispatchInterceptor> +{ + CComPtr<IDispatch> m_xMaster; + CComPtr<IDispatch> m_xSlave; + CSOActiveX* m_xParentControl; + CRITICAL_SECTION mMutex; +public: + SODispatchInterceptor() : m_xParentControl( nullptr ) { InitializeCriticalSection(&mMutex); } + virtual ~SODispatchInterceptor() { ATLASSERT( !m_xParentControl ); DeleteCriticalSection(&mMutex); } + +BEGIN_COM_MAP(SODispatchInterceptor) + COM_INTERFACE_ENTRY(IDispatch) + COM_INTERFACE_ENTRY(ISODispatchInterceptor) + COM_INTERFACE_ENTRY(ISupportErrorInfo) +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winconsistent-missing-override" +#endif +END_COM_MAP() +#if defined __clang__ +#pragma clang diagnostic pop +#endif +DECLARE_NOT_AGGREGATABLE(SODispatchInterceptor) +// Remove the comment from the line above if you don't want your object to +// support aggregation. + +DECLARE_REGISTRY_RESOURCEID(IDR_SODISPATCHINTERCEPTOR) + + void SetParent( CSOActiveX* pParent ) + { + ATLASSERT( !m_xParentControl ); + EnterCriticalSection( &mMutex ); + m_xParentControl = pParent; + LeaveCriticalSection( &mMutex ); + } + + void ClearParent() + { + EnterCriticalSection( &mMutex ); + m_xParentControl = nullptr; + LeaveCriticalSection( &mMutex ); + } + +// ISupportsErrorInfo + STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid) override; + +// ISODispatchInterceptor + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE getSlaveDispatchProvider( + /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *retVal) override + { + *retVal = m_xSlave; + return S_OK; + } + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE setSlaveDispatchProvider( + /* [in] */ IDispatch __RPC_FAR *xNewDispatchProvider) override + { + m_xSlave = xNewDispatchProvider; + return S_OK; + } + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE getMasterDispatchProvider( + /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *retVal) override + { + *retVal = m_xMaster; + return S_OK; + } + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE setMasterDispatchProvider( + /* [in] */ IDispatch __RPC_FAR *xNewSupplier) override + { + m_xMaster = xNewSupplier; + return S_OK; + } + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE queryDispatch( + /* [in] */ IDispatch __RPC_FAR *aURL, + /* [in] */ BSTR aTargetFrameName, + /* [in] */ long nSearchFlags, + /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *retVal) override; + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE queryDispatches( + /* [in] */ SAFEARRAY __RPC_FAR * aDescripts, + /* [retval][out] */ SAFEARRAY __RPC_FAR * __RPC_FAR *retVal) override; + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE dispatch( + /* [in] */ IDispatch __RPC_FAR *aURL, + /* [in] */ SAFEARRAY __RPC_FAR * aArgs) override; + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE addStatusListener( + /* [in] */ IDispatch __RPC_FAR *xControl, + /* [in] */ IDispatch __RPC_FAR *aURL) override; + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE removeStatusListener( + /* [in] */ IDispatch __RPC_FAR *xControl, + /* [in] */ IDispatch __RPC_FAR *aURL) override; + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE getInterceptedURLs( + /* [retval][out] */ SAFEARRAY __RPC_FAR * __RPC_FAR *pVal) override; + + virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Bridge_implementedInterfaces( + /* [retval][out] */ SAFEARRAY __RPC_FAR * __RPC_FAR *pVal) override + { + *pVal = SafeArrayCreateVector( VT_BSTR, 0, 4 ); + + if( !*pVal ) + return E_FAIL; + + LONG ix = 0; + CComBSTR aInterface( OLESTR( "com.sun.star.frame.XDispatchProviderInterceptor" ) ); + SafeArrayPutElement( *pVal, &ix, aInterface ); + + ix = 1; + aInterface = CComBSTR( OLESTR( "com.sun.star.frame.XDispatchProvider" ) ); + SafeArrayPutElement( *pVal, &ix, aInterface ); + + ix = 2; + aInterface = CComBSTR( OLESTR( "com.sun.star.frame.XDispatch" ) ); + SafeArrayPutElement( *pVal, &ix, aInterface ); + + ix = 3; + aInterface = CComBSTR( OLESTR( "com.sun.star.frame.XInterceptorInfo" ) ); + SafeArrayPutElement( *pVal, &ix, aInterface ); + + return S_OK; + } +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/activex/SODispatchInterceptor.rgs b/extensions/source/activex/SODispatchInterceptor.rgs new file mode 100644 index 0000000000..19fe0b5f0e --- /dev/null +++ b/extensions/source/activex/SODispatchInterceptor.rgs @@ -0,0 +1,23 @@ +HKCR +{ + so_activex.SODispatchInterceptor.1 = s 'SODispatchInterceptor Class' + { + CLSID = s '{C5D6D568-57DA-4D6C-819A-451CB565E682}' + } + so_activex.SODispatchInterceptor = s 'SODispatchInterceptor Class' + { + CLSID = s '{C5D6D568-57DA-4D6C-819A-451CB565E682}' + } + NoRemove CLSID + { + ForceRemove {C5D6D568-57DA-4D6C-819A-451CB565E682} = s 'SODispatchInterceptor Class' + { + ProgID = s 'so_activex.SODispatchInterceptor.1' + VersionIndependentProgID = s 'so_activex.SODispatchInterceptor' + InprocServer32 = s '%MODULE%' + { + val ThreadingModel = s 'both' + } + } + } +} diff --git a/extensions/source/activex/StdAfx2.cxx b/extensions/source/activex/StdAfx2.cxx new file mode 100644 index 0000000000..c2df5c5c0e --- /dev/null +++ b/extensions/source/activex/StdAfx2.cxx @@ -0,0 +1,30 @@ +/* -*- 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 . + */ + +// stdafx1.cpp : source file that includes just the standard includes +// stdafx1.pch will be the pre-compiled header +// stdafx1.obj will contain the pre-compiled type information + +#include "StdAfx2.h" + +#ifdef _ATL_STATIC_REGISTRY +#include <statreg.h> +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/activex/StdAfx2.h b/extensions/source/activex/StdAfx2.h new file mode 100644 index 0000000000..b2ab095e91 --- /dev/null +++ b/extensions/source/activex/StdAfx2.h @@ -0,0 +1,68 @@ +/* -*- 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 . + */ + +// stdafx1.h : include file for standard system include files, +// or project specific include files that are used frequently, +// but are changed infrequently + +#pragma once + +#define STRICT +#define _ATL_APARTMENT_THREADED +#define _ATL_STATIC_REGISTRY + +#pragma warning(push) +// local variable is initialized but not referenced - in atlctl.h +#pragma warning(disable : 4189) +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wall" +#pragma clang diagnostic ignored "-Wattributes" +#pragma clang diagnostic ignored "-Wdelete-incomplete" +#pragma clang diagnostic ignored "-Wdynamic-class-memaccess" +#pragma clang diagnostic ignored "-Wint-to-pointer-cast" +#pragma clang diagnostic ignored "-Winvalid-noreturn" +#pragma clang diagnostic ignored "-Wmicrosoft" +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#pragma clang diagnostic ignored "-Wnon-pod-varargs" +#pragma clang diagnostic ignored "-Wnonportable-include-path" +#pragma clang diagnostic ignored "-Wsequence-point" +#pragma clang diagnostic ignored "-Wsign-compare" +#pragma clang diagnostic ignored "-Wtypename-missing" +#endif + +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#include <atlbase.h> + +//You may derive a class from CComModule and use it if you want to override +//something, but do not change the name of _Module +extern CComModule _Module; +#include <atlcom.h> +#include <atlctl.h> +#undef min + +#if defined __clang__ +#pragma clang diagnostic pop +#endif +#pragma warning(pop) + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/activex/com_uno_helper.h b/extensions/source/activex/com_uno_helper.h new file mode 100644 index 0000000000..89aa0a7d16 --- /dev/null +++ b/extensions/source/activex/com_uno_helper.h @@ -0,0 +1,44 @@ +/* -*- 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 "StdAfx2.h" + +HRESULT ExecuteFunc( IDispatch* idispUnoObject, + OLECHAR const * sFuncName, + CComVariant* params, + unsigned int count, + CComVariant* pResult ); + +HRESULT GetIDispByFunc( IDispatch* idispUnoObject, + OLECHAR* sFuncName, + CComVariant* params, + unsigned int count, + CComPtr<IDispatch>& pdispResult ); + +HRESULT PutPropertiesToIDisp( IDispatch* pdispObject, + OLECHAR** sMemberNames, + CComVariant* pVariant, + unsigned int count ); + +HRESULT GetPropertiesFromIDisp( IDispatch* pdispObject, + OLECHAR const ** sMemberNames, + CComVariant* pVariant, + unsigned int count ); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/activex/example.html b/extensions/source/activex/example.html new file mode 100644 index 0000000000..96e764c8f0 --- /dev/null +++ b/extensions/source/activex/example.html @@ -0,0 +1,43 @@ +<!-- + * 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 . +--> +<HTML> +<HEAD> +<TITLE>Document Title</TITLE> +</HEAD> +<BODY> + +<center> +First you should edit the example.html file!!! +</center> +<center> +<!-- Please edit CODEBASE parameter --> +<!-- In case ActiveX control is already registered the parameter can be removed --> +<OBJECT CLASSID="clsid:67F2A879-82D5-4A6D-8CC5-FFB3C114B69D" width="500" height="500" + CODEBASE="..\..\..\WINexample.out\bin\so_activex.dll"> +<!-- Full URL to a document + <PARAM NAME="src" VALUE="file:///d:/example.sxw"> +--> +<!-- Just view the document, do not edit + <PARAM NAME="readonly" VALUE="true"> +--> +</OBJECT> + +</center> + +</BODY> +</HTML> diff --git a/extensions/source/activex/resource.h b/extensions/source/activex/resource.h new file mode 100644 index 0000000000..b7a77a5c91 --- /dev/null +++ b/extensions/source/activex/resource.h @@ -0,0 +1,46 @@ +/* -*- 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 . + */ + +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by so_activex.rc + +#define IDS_PROJNAME 100 +#define IDB_SOACTIVEX 101 +#define IDR_SOACTIVEX 102 +#define IDB_SOCOMWINDOWPEER 103 +#define IDR_SOCOMWINDOWPEER 104 +#define IDB_SODISPATCHINTERCEPTOR 105 +#define IDR_SODISPATCHINTERCEPTOR 106 +#define IDB_SODOCUMENTEVENTLISTENER 107 +#define IDR_SODOCUMENTEVENTLISTENER 108 + + +// Next default values for new objects + +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 201 +#define _APS_NEXT_COMMAND_VALUE 32768 +#define _APS_NEXT_CONTROL_VALUE 201 +#define _APS_NEXT_SYMED_VALUE 107 +#endif +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/activex/so_activex.cxx b/extensions/source/activex/so_activex.cxx new file mode 100644 index 0000000000..a4ce480613 --- /dev/null +++ b/extensions/source/activex/so_activex.cxx @@ -0,0 +1,766 @@ +/* -*- 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 . + */ + +// so_activex.cpp : Implementation of DLL Exports. + +// Note: Proxy/Stub Information +// To build a separate proxy/stub DLL, +// run nmake -f so_activexps.mk in the project directory. + +#include <stdio.h> +#include "StdAfx2.h" +#include "resource.h" +#include <initguid.h> +#include <so_activex.h> + +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wextra-tokens" + // "#endif !_MIDL_USE_GUIDDEF_" in midl-generated code +#endif +#include <so_activex_i.c> +#if defined __clang__ +#pragma clang diagnostic pop +#endif + +#include "SOActiveX.h" + +#include <comphelper\documentconstants.hxx> +#include <sal/types.h> +#include <exception> + +CComModule _Module; + +BEGIN_OBJECT_MAP(ObjectMap) +OBJECT_ENTRY(CLSID_SOActiveX, CSOActiveX) +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#endif +END_OBJECT_MAP() +#if defined __clang__ +#pragma clang diagnostic pop +#endif + +#define X64_LIB_NAME L"so_activex_x64.dll" +#define X32_LIB_NAME L"so_activex.dll" + +const REGSAM n64KeyAccess = KEY_ALL_ACCESS | KEY_WOW64_64KEY; +const REGSAM n32KeyAccess = KEY_ALL_ACCESS; + +#ifdef _AMD64_ +const bool bX64 = true; +#define REG_DELETE_KEY_A( key, aPath, nKeyAccess ) RegDeleteKeyExA( key, aPath, nKeyAccess, 0 ) +#else +const bool bX64 = false; +#define REG_DELETE_KEY_A( key, aPath, nKeyAccess ) RegDeleteKeyA( key, aPath ) +#endif + +// DLL Entry Point + +extern "C" +BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) +{ + if (dwReason == DLL_PROCESS_ATTACH) + { + _Module.Init(ObjectMap, hInstance, &LIBID_SO_ACTIVEXLib); + DisableThreadLibraryCalls(hInstance); + } + else if (dwReason == DLL_PROCESS_DETACH) + _Module.Term(); + return TRUE; // ok +} + + +// Used to determine whether the DLL can be unloaded by OLE + +STDAPI DllCanUnloadNow() +{ + return (_Module.GetLockCount()==0) ? S_OK : S_FALSE; +} + + +// Returns a class factory to create an object of the requested type + +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) +{ + return _Module.GetClassObject(rclsid, riid, ppv); +} + + +// DllRegisterServer - Adds entries to the system registry + +namespace +{ +// Wraps an updatable Win32 error; keeps first incoming error. +// Ctor defines if upd will throw std::exception on error or +// return false. +class Status +{ +public: + explicit Status(bool bTrow) + : m_bThrow(bTrow) + { + } + // used to check success of an operation, and update the status if it's still ERROR_SUCCESS + bool upd(LSTATUS nNewStatus) + { + if (m_nStatus == ERROR_SUCCESS) + m_nStatus = nNewStatus; + if (m_bThrow && nNewStatus != ERROR_SUCCESS) + throw std::exception(); + return nNewStatus == ERROR_SUCCESS; + }; + LSTATUS get() { return m_nStatus; } + operator bool() { return m_nStatus == ERROR_SUCCESS; } + +private: + LSTATUS m_nStatus = ERROR_SUCCESS; + const bool m_bThrow; +}; + +class HRegKey +{ +public: + ~HRegKey() + { + if (m_hkey) + RegCloseKey(m_hkey); + } + PHKEY operator&() { return &m_hkey; } + operator HKEY() { return m_hkey; } + +private: + HKEY m_hkey = nullptr; +}; +} + +// for now database component and chart are always installed +#define SUPPORTED_EXT_NUM 30 +const char* const aFileExt[] = { ".vor", + ".sds", ".sda", ".sdd", ".sdp", ".sdc", ".sdw", ".smf", + ".stw", ".stc", ".sti", ".std", + ".sxw", ".sxc", ".sxi", ".sxd", ".sxg", ".sxm", + ".ott", ".otg", ".otp", ".ots", ".otf", + ".odt", ".oth", ".odm", ".odg", ".odp", ".ods", ".odf"}; +const sal_Unicode* const aMimeType[] = { + u"application/vnd.stardivision.writer", + + u"application/vnd.stardivision.chart", + u"application/vnd.stardivision.draw", + u"application/vnd.stardivision.impress", + u"application/vnd.stardivision.impress-packed", + u"application/vnd.stardivision.calc", + u"application/vnd.stardivision.writer", + u"application/vnd.stardivision.math", + + MIMETYPE_VND_SUN_XML_WRITER_TEMPLATE_ASCII.getStr(), + MIMETYPE_VND_SUN_XML_CALC_TEMPLATE_ASCII.getStr(), + MIMETYPE_VND_SUN_XML_IMPRESS_TEMPLATE_ASCII.getStr(), + MIMETYPE_VND_SUN_XML_DRAW_TEMPLATE_ASCII.getStr(), + + MIMETYPE_VND_SUN_XML_WRITER_ASCII.getStr(), + MIMETYPE_VND_SUN_XML_CALC_ASCII.getStr(), + MIMETYPE_VND_SUN_XML_IMPRESS_ASCII.getStr(), + MIMETYPE_VND_SUN_XML_DRAW_ASCII.getStr(), + MIMETYPE_VND_SUN_XML_WRITER_GLOBAL_ASCII.getStr(), + MIMETYPE_VND_SUN_XML_MATH_ASCII.getStr(), + + MIMETYPE_OASIS_OPENDOCUMENT_TEXT_TEMPLATE_ASCII.getStr(), + MIMETYPE_OASIS_OPENDOCUMENT_TEXT_GLOBAL_TEMPLATE_ASCII.getStr(), + MIMETYPE_OASIS_OPENDOCUMENT_DRAWING_TEMPLATE_ASCII.getStr(), + MIMETYPE_OASIS_OPENDOCUMENT_PRESENTATION_TEMPLATE_ASCII.getStr(), + MIMETYPE_OASIS_OPENDOCUMENT_SPREADSHEET_TEMPLATE_ASCII.getStr(), + MIMETYPE_OASIS_OPENDOCUMENT_FORMULA_TEMPLATE_ASCII.getStr(), + + MIMETYPE_OASIS_OPENDOCUMENT_TEXT_ASCII.getStr(), + MIMETYPE_OASIS_OPENDOCUMENT_TEXT_WEB_ASCII.getStr(), + MIMETYPE_OASIS_OPENDOCUMENT_TEXT_GLOBAL_ASCII.getStr(), + MIMETYPE_OASIS_OPENDOCUMENT_DRAWING_ASCII.getStr(), + MIMETYPE_OASIS_OPENDOCUMENT_PRESENTATION_ASCII.getStr(), + MIMETYPE_OASIS_OPENDOCUMENT_SPREADSHEET_ASCII.getStr(), + MIMETYPE_OASIS_OPENDOCUMENT_FORMULA_ASCII.getStr() }; + +const int nForModes[] = { 16, + 1, 2, 4, 4, 8, 16, 32, + 16, 8, 4, 2, + 16, 8, 4, 2, 16, 32, + 16, 2, 4, 8, 32, + 16, 16, 16, 2, 4, 8, 32 }; + +const char* const aClassID = "{67F2A879-82D5-4A6D-8CC5-FFB3C114B69D}"; +const char* const aTypeLib = "{61FA3F13-8061-4796-B055-3697ED28CB38}"; + +// ISOComWindowPeer interface information +const char* const aInterIDWinPeer = "{BF5D10F3-8A10-4A0B-B150-2B6AA2D7E118}"; +const char* const aProxyStubWinPeer = "{00020424-0000-0000-C000-000000000046}"; + +// ISODispatchInterceptor interface information +const char* const aInterIDDispInt = "{9337694C-B27D-4384-95A4-9D8E0EABC9E5}"; +const char* const aProxyStubDispInt = "{00020424-0000-0000-C000-000000000046}"; + +// ISOActionsApproval interface information +const char* const aInterIDActApprove = "{029E9F1E-2B3F-4297-9160-8197DE7ED54F}"; +const char* const aProxyStubActApprove = "{00020424-0000-0000-C000-000000000046}"; + +// The following prefix is required for HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER ( not for HKEY_CLASSES_ROOT ) +const char* const aLocalPrefix = "Software\\Classes\\"; + +static LSTATUS createKey( HKEY hkey, + const char* aKeyToCreate, + REGSAM nKeyAccess, + const char* aValue = nullptr, + const char* aChildName = nullptr, + const char* aChildValue = nullptr ) +{ + Status s(false); // no throw + HRegKey hkey1; + if (s.upd(RegCreateKeyExA(hkey, aKeyToCreate, 0, nullptr, REG_OPTION_NON_VOLATILE, nKeyAccess, + nullptr, &hkey1, nullptr))) + { + if (aValue) + s.upd(RegSetValueExA(hkey1, "", 0, REG_SZ, reinterpret_cast<const BYTE*>(aValue), + sal::static_int_cast<DWORD>(strlen(aValue)))); + if (aChildName) + s.upd(RegSetValueExA(hkey1, aChildName, 0, REG_SZ, + reinterpret_cast<const BYTE*>(aChildValue), + sal::static_int_cast<DWORD>(strlen(aChildValue)))); + } + return s.get(); +} + +static LSTATUS createKey(HKEY hkey, + const wchar_t* aKeyToCreate, + REGSAM nKeyAccess, + const wchar_t* aValue = nullptr, + const wchar_t* aChildName = nullptr, + const wchar_t* aChildValue = nullptr ) +{ + Status s(false); // no throw + HRegKey hkey1; + if (s.upd(RegCreateKeyExW(hkey, aKeyToCreate, 0, nullptr, REG_OPTION_NON_VOLATILE, nKeyAccess, + nullptr, &hkey1, nullptr))) + { + if (aValue) + s.upd(RegSetValueExW(hkey1, L"", 0, REG_SZ, reinterpret_cast<const BYTE*>(aValue), + sal::static_int_cast<DWORD>(wcslen(aValue) * sizeof(wchar_t)))); + if (aChildName) + s.upd(RegSetValueExW( + hkey1, aChildName, 0, REG_SZ, reinterpret_cast<const BYTE*>(aChildValue), + sal::static_int_cast<DWORD>(wcslen(aChildValue) * sizeof(wchar_t)))); + } + return s.get(); +} + +EXTERN_C __declspec(dllexport) HRESULT STDAPICALLTYPE DllUnregisterServerNative( int nMode, BOOL bForAllUsers, BOOL bFor64Bit ); +static HRESULT DllRegisterServerNative_Impl( int nMode, bool bForAllUsers, REGSAM nKeyAccess, const wchar_t* pProgramPath, const wchar_t* pLibName ) +{ + char aSubKey[513]; + int ind; + const char* aPrefix = aLocalPrefix; // bForAllUsers ? "" : aLocalPrefix; + + // In case SO7 is installed for this user he can have local registry entries that will prevent him from + // using SO8 ActiveX control. The fix is just to clean up the local entries related to ActiveX control. + // Unfortunately it can be done only for the user who installs the office. + if ( bForAllUsers ) + DllUnregisterServerNative( nMode, false, false ); + + Status s(true); // throw + try + { + if (pProgramPath && wcslen(pProgramPath) < 1024) + { + wchar_t pActiveXPath[1124]; + wchar_t pActiveXPath101[1124]; + + swprintf(pActiveXPath, L"%s\\%s", pProgramPath, pLibName); + swprintf(pActiveXPath101, L"%s\\%s, 101", pProgramPath, pLibName); + + { + wsprintfA(aSubKey, "%sCLSID\\%s", aPrefix, aClassID); + HRegKey hkey; + s.upd(RegCreateKeyExA(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, + aSubKey, 0, nullptr, REG_OPTION_NON_VOLATILE, nKeyAccess, + nullptr, &hkey, nullptr)); + s.upd(RegSetValueExA(hkey, "", 0, REG_SZ, + reinterpret_cast<const BYTE*>("SOActiveX Class"), 17)); + s.upd(createKey(hkey, "Control", nKeyAccess)); + s.upd(createKey(hkey, "EnableFullPage", nKeyAccess)); + s.upd(createKey(hkey, L"InprocServer32", nKeyAccess, pActiveXPath, + L"ThreadingModel", L"Apartment")); + s.upd(createKey(hkey, "MiscStatus", nKeyAccess, "0")); + s.upd(createKey(hkey, "MiscStatus\\1", nKeyAccess, "131473")); + s.upd(createKey(hkey, "ProgID", nKeyAccess, "so_activex.SOActiveX.1")); + s.upd(createKey(hkey, "Programmable", nKeyAccess)); + s.upd(createKey(hkey, L"ToolboxBitmap32", nKeyAccess, pActiveXPath101)); + s.upd(createKey(hkey, "TypeLib", nKeyAccess, aTypeLib)); + s.upd(createKey(hkey, "Version", nKeyAccess, "1.0")); + s.upd(createKey(hkey, "VersionIndependentProgID", nKeyAccess, + "so_activex.SOActiveX")); + } + { + HRegKey hkey; + s.upd(RegCreateKeyExA(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, + aPrefix, 0, nullptr, REG_OPTION_NON_VOLATILE, nKeyAccess, + nullptr, &hkey, nullptr)); + s.upd(createKey(hkey, "so_activex.SOActiveX", nKeyAccess, "SOActiveX Class")); + { + HRegKey hkey1; + s.upd(RegCreateKeyExA(hkey, "so_activex.SOActiveX", 0, nullptr, + REG_OPTION_NON_VOLATILE, nKeyAccess, nullptr, &hkey1, + nullptr)); + s.upd(createKey(hkey1, "CLSID", nKeyAccess, aClassID)); + s.upd(createKey(hkey1, "CurVer", nKeyAccess, "so_activex.SOActiveX.1")); + } + s.upd(createKey(hkey, "so_activex.SOActiveX.1", nKeyAccess, "SOActiveX Class")); + { + HRegKey hkey1; + s.upd(RegCreateKeyExA(hkey, "so_activex.SOActiveX.1", 0, nullptr, + REG_OPTION_NON_VOLATILE, nKeyAccess, nullptr, &hkey1, + nullptr)); + s.upd(createKey(hkey1, "CLSID", nKeyAccess, aClassID)); + } + { + HRegKey hkey1; + s.upd(RegCreateKeyExA(hkey, "TypeLib", 0, nullptr, REG_OPTION_NON_VOLATILE, + nKeyAccess, nullptr, &hkey1, nullptr)); + { + HRegKey hkey2; + s.upd(RegCreateKeyExA(hkey1, aTypeLib, 0, nullptr, REG_OPTION_NON_VOLATILE, + nKeyAccess, nullptr, &hkey2, nullptr)); + s.upd(createKey(hkey2, "1.0", nKeyAccess, "wrap_activex 1.0 Type Library")); + { + HRegKey hkey3; + s.upd(RegCreateKeyExA(hkey2, "1.0", 0, nullptr, REG_OPTION_NON_VOLATILE, + nKeyAccess, nullptr, &hkey3, nullptr)); + { + HRegKey hkey4; + s.upd(RegCreateKeyExA(hkey3, "0", 0, nullptr, + REG_OPTION_NON_VOLATILE, nKeyAccess, nullptr, + &hkey4, nullptr)); + s.upd(createKey(hkey4, L"win32", nKeyAccess, pActiveXPath)); + } + s.upd(createKey(hkey3, "FLAGS", nKeyAccess, "0")); + s.upd(createKey(hkey3, L"HELPDIR", nKeyAccess, pProgramPath)); + } + } + } + { + HRegKey hkey1; + s.upd(RegCreateKeyExA(hkey, "Interface", 0, nullptr, REG_OPTION_NON_VOLATILE, + nKeyAccess, nullptr, &hkey1, nullptr)); + s.upd(createKey(hkey1, aInterIDWinPeer, nKeyAccess, "ISOComWindowPeer")); + { + HRegKey hkey2; + s.upd(RegCreateKeyExA(hkey1, aInterIDWinPeer, 0, nullptr, + REG_OPTION_NON_VOLATILE, nKeyAccess, nullptr, &hkey2, + nullptr)); + s.upd(createKey(hkey2, "ProxyStubClsid", nKeyAccess, aProxyStubWinPeer)); + s.upd(createKey(hkey2, "ProxyStubClsid32", nKeyAccess, aProxyStubWinPeer)); + s.upd(createKey(hkey2, "TypeLib", nKeyAccess, aTypeLib, "Version", "1.0")); + } + s.upd(createKey(hkey1, aInterIDActApprove, nKeyAccess, "ISOActionsApproval")); + { + HRegKey hkey2; + s.upd(RegCreateKeyExA(hkey1, aInterIDActApprove, 0, nullptr, + REG_OPTION_NON_VOLATILE, nKeyAccess, nullptr, &hkey2, + nullptr)); + s.upd(createKey(hkey2, "ProxyStubClsid", nKeyAccess, aProxyStubActApprove)); + s.upd( + createKey(hkey2, "ProxyStubClsid32", nKeyAccess, aProxyStubActApprove)); + s.upd(createKey(hkey2, "TypeLib", nKeyAccess, aTypeLib, "Version", "1.0")); + } + s.upd(createKey(hkey1, aInterIDDispInt, nKeyAccess, "ISODispatchInterceptor")); + { + HRegKey hkey2; + s.upd(RegCreateKeyExA(hkey1, aInterIDDispInt, 0, nullptr, + REG_OPTION_NON_VOLATILE, nKeyAccess, nullptr, &hkey2, + nullptr)); + s.upd(createKey(hkey2, "ProxyStubClsid", nKeyAccess, aProxyStubDispInt)); + s.upd(createKey(hkey2, "ProxyStubClsid32", nKeyAccess, aProxyStubDispInt)); + s.upd(createKey(hkey2, "TypeLib", nKeyAccess, aTypeLib, "Version", "1.0")); + } + } + } + } + + for (ind = 0; ind < SUPPORTED_EXT_NUM; ind++) + { + if (nForModes[ind] & nMode) + { + wsprintfA(aSubKey, "%sMIME\\DataBase\\Content Type\\%ls", aPrefix, aMimeType[ind]); + HRegKey hkey; + s.upd(RegCreateKeyExA(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, + aSubKey, 0, nullptr, REG_OPTION_NON_VOLATILE, nKeyAccess, + nullptr, &hkey, nullptr)); + s.upd(RegSetValueExA(hkey, "CLSID", 0, REG_SZ, + reinterpret_cast<const BYTE*>(aClassID), + sal::static_int_cast<DWORD>(strlen(aClassID)))); + } + } + + { + wsprintfA(aSubKey, "%sCLSID\\%s", aPrefix, aClassID); + HRegKey hkey; + s.upd(RegOpenKeyExA(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, aSubKey, 0, + nKeyAccess, &hkey)); + for (ind = 0; ind < SUPPORTED_EXT_NUM; ind++) + { + wsprintfA(aSubKey, "EnableFullPage\\%s", aFileExt[ind]); + HRegKey hkey1; + s.upd(RegCreateKeyExA(hkey, aSubKey, 0, nullptr, REG_OPTION_NON_VOLATILE, + nKeyAccess, nullptr, &hkey1, nullptr)); + } + } + } + catch (const std::exception&) {} + + return HRESULT_FROM_WIN32(s.get()); +} + +EXTERN_C __declspec(dllexport) HRESULT STDAPICALLTYPE DllRegisterServerNative( int nMode, BOOL bForAllUsers, BOOL bFor64Bit, const wchar_t* pProgramPath ) +{ + HRESULT hr = S_OK; + if ( bFor64Bit ) + hr = DllRegisterServerNative_Impl( nMode, bForAllUsers, n64KeyAccess, pProgramPath, X64_LIB_NAME ); + + if ( SUCCEEDED( hr ) ) + hr = DllRegisterServerNative_Impl( nMode, bForAllUsers, n32KeyAccess, pProgramPath, X32_LIB_NAME ); + + return hr; +} + + +// DllUnregisterServer - Removes entries from the system registry +static HRESULT DeleteKeyTree( HKEY hkey, const char* pPath, REGSAM nKeyAccess ) +{ + char pSubKeyName[256]; + // first delete the subkeys + while (true) + { + HRegKey hkey1; + if (ERROR_SUCCESS != RegOpenKeyExA(hkey, pPath, 0, nKeyAccess, &hkey1) + || ERROR_SUCCESS != RegEnumKeyA(hkey1, 0, pSubKeyName, 256) + || ERROR_SUCCESS != DeleteKeyTree(hkey1, pSubKeyName, nKeyAccess)) + break; + } + + // delete the key itself + return REG_DELETE_KEY_A( hkey, pPath, nKeyAccess & ( KEY_WOW64_64KEY | KEY_WOW64_32KEY ) ); +} + +static HRESULT DllUnregisterServerNative_Impl( int nMode, bool bForAllUsers, REGSAM nKeyAccess ) +{ + char aSubKey[513]; + const char* aPrefix = aLocalPrefix; // bForAllUsers ? "" : aLocalPrefix; + + Status s(false); // no throw + for( int ind = 0; ind < SUPPORTED_EXT_NUM; ind++ ) + { + if( nForModes[ind] & nMode ) + { + DWORD nSubKeys = 0, nValues = 0; + wsprintfA(aSubKey, "%sMIME\\DataBase\\Content Type\\%ls", aPrefix, aMimeType[ind]); + Status s1(false); // no throw + { + HRegKey hkey; + if (s1.upd(RegCreateKeyExA(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, + aSubKey, 0, nullptr, REG_OPTION_NON_VOLATILE, nKeyAccess, + nullptr, &hkey, nullptr))) + { + s1.upd(RegDeleteValueA(hkey, "CLSID")); + s1.upd(RegQueryInfoKeyA(hkey, nullptr, nullptr, nullptr, &nSubKeys, nullptr, + nullptr, &nValues, nullptr, nullptr, nullptr, nullptr)); + } + } + if (s1 && !nSubKeys && !nValues) + DeleteKeyTree(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, aSubKey, + nKeyAccess); + s.upd(s1.get()); + + wsprintfA(aSubKey, "%s%s", aPrefix, aFileExt[ind]); + Status s2(false); // no throw + { + HRegKey hkey; + if (s2.upd(RegCreateKeyExA(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, + aSubKey, 0, nullptr, REG_OPTION_NON_VOLATILE, nKeyAccess, + nullptr, &hkey, nullptr))) + { + s2.upd(RegQueryInfoKeyA(hkey, nullptr, nullptr, nullptr, &nSubKeys, nullptr, + nullptr, &nValues, nullptr, nullptr, nullptr, nullptr)); + } + } + if (s2 && !nSubKeys && !nValues) + DeleteKeyTree(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, aSubKey, + nKeyAccess); + s.upd(s2.get()); + } + } + + wsprintfA(aSubKey, "%sCLSID\\%s", aPrefix, aClassID); + s.upd(DeleteKeyTree(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, aSubKey, + nKeyAccess)); + + wsprintfA(aSubKey, "%sso_activex.SOActiveX", aPrefix); + s.upd(DeleteKeyTree(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, aSubKey, + nKeyAccess)); + + wsprintfA(aSubKey, "%sso_activex.SOActiveX.1", aPrefix); + s.upd(DeleteKeyTree(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, aSubKey, + nKeyAccess)); + + wsprintfA(aSubKey, "%s\\TypeLib\\%s", aPrefix, aTypeLib); + s.upd(DeleteKeyTree(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, aSubKey, + nKeyAccess)); + + wsprintfA(aSubKey, "%s\\Interface\\%s", aPrefix, aInterIDWinPeer); + s.upd(DeleteKeyTree(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, aSubKey, + nKeyAccess)); + + wsprintfA(aSubKey, "%s\\Interface\\%s", aPrefix, aInterIDDispInt); + s.upd(DeleteKeyTree(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, aSubKey, + nKeyAccess)); + + wsprintfA(aSubKey, "%s\\Interface\\%s", aPrefix, aInterIDActApprove); + s.upd(DeleteKeyTree(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, aSubKey, + nKeyAccess)); + + return HRESULT_FROM_WIN32(s.get()); +} + +STDAPI DllUnregisterServerNative( int nMode, BOOL bForAllUsers, BOOL bFor64Bit ) +{ + HRESULT hr = DllUnregisterServerNative_Impl( nMode, bForAllUsers, n32KeyAccess ); + if ( SUCCEEDED( hr ) && bFor64Bit ) + hr = DllUnregisterServerNative_Impl( nMode, bForAllUsers, n64KeyAccess ); + + return hr; +} + + +// DllRegisterServerDoc - Adds entries to the system registry + +#define SUPPORTED_MSEXT_NUM 7 +const char* const aMSFileExt[] = { ".dot", ".doc", ".xlt", ".xls", ".pot", ".ppt", ".pps" }; +const char* const aMSMimeType[] = { "application/msword", + "application/msword", + "application/vnd.ms-excel", + "application/vnd.ms-excel", + "application/vnd.ms-powerpoint", + "application/vnd.ms-powerpoint", + "application/vnd.ms-powerpoint" }; +const int nForMSModes[] = { 1, 1, 2, 2, 4, 4, 4 }; + +EXTERN_C __declspec(dllexport) HRESULT STDAPICALLTYPE DllUnregisterServerDoc( int nMode, BOOL bForAllUsers, BOOL bFor64Bit ); +static HRESULT DllRegisterServerDoc_Impl( int nMode, bool bForAllUsers, REGSAM nKeyAccess ) +{ + char aSubKey[513]; + int ind; + const char* aPrefix = aLocalPrefix; // bForAllUsers ? "" : aLocalPrefix; + + // In case SO7 is installed for this user he can have local registry entries that will prevent him from + // using SO8 ActiveX control. The fix is just to clean up the local entries related to ActiveX control. + // Unfortunately it can be done only for the user who installs the office. + if ( bForAllUsers ) + DllUnregisterServerDoc( nMode, false, false ); + + Status s(true); // throw + try + { + for (ind = 0; ind < SUPPORTED_MSEXT_NUM; ind++) + { + if (nForMSModes[ind] & nMode) + { + { + wsprintfA(aSubKey, "%sMIME\\DataBase\\Content Type\\%s", aPrefix, + aMSMimeType[ind]); + HRegKey hkey; + s.upd(RegCreateKeyExA(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, + aSubKey, 0, nullptr, REG_OPTION_NON_VOLATILE, nKeyAccess, + nullptr, &hkey, nullptr)); + s.upd(RegSetValueExA(hkey, "Extension", 0, REG_SZ, + reinterpret_cast<const BYTE*>(aMSFileExt[ind]), + sal::static_int_cast<DWORD>(strlen(aMSFileExt[ind])))); + s.upd(RegSetValueExA(hkey, "CLSID", 0, REG_SZ, + reinterpret_cast<const BYTE*>(aClassID), + sal::static_int_cast<DWORD>(strlen(aClassID)))); + } + { + wsprintfA(aSubKey, "%s%s", aPrefix, aMSFileExt[ind]); + HRegKey hkey; + s.upd(RegCreateKeyExA(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, + aSubKey, 0, nullptr, REG_OPTION_NON_VOLATILE, nKeyAccess, + nullptr, &hkey, nullptr)); + s.upd(RegSetValueExA(hkey, "Content Type", 0, REG_SZ, + reinterpret_cast<const BYTE*>(aMSMimeType[ind]), + sal::static_int_cast<DWORD>(strlen(aMSMimeType[ind])))); + } + } + } + + wsprintfA(aSubKey, "%sCLSID\\%s", aPrefix, aClassID); + HRegKey hkey; + s.upd(RegCreateKeyExA(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, aSubKey, 0, + nullptr, REG_OPTION_NON_VOLATILE, nKeyAccess, nullptr, &hkey, + nullptr)); + s.upd(createKey(hkey, "EnableFullPage", nKeyAccess)); + for (ind = 0; ind < SUPPORTED_MSEXT_NUM; ind++) + { + if (nForMSModes[ind] & nMode) + { + wsprintfA(aSubKey, "EnableFullPage\\%s", aMSFileExt[ind]); + HRegKey hkey1; + s.upd(RegCreateKeyExA(hkey, aSubKey, 0, nullptr, REG_OPTION_NON_VOLATILE, + nKeyAccess, nullptr, &hkey1, nullptr)); + } + } + } + catch (const std::exception&) {} + + return HRESULT_FROM_WIN32(s.get()); +} + +EXTERN_C __declspec(dllexport) HRESULT STDAPICALLTYPE DllRegisterServerDoc( int nMode, BOOL bForAllUsers, BOOL bFor64Bit ) +{ + HRESULT hr = S_OK; + if ( bFor64Bit ) + hr = DllRegisterServerDoc_Impl( nMode, bForAllUsers, n64KeyAccess ); + + if ( SUCCEEDED( hr ) ) + hr = DllRegisterServerDoc_Impl( nMode, bForAllUsers, n32KeyAccess ); + + return hr; +} + + +// DllUnregisterServerDoc - Removes entries from the system registry + +static HRESULT DllUnregisterServerDoc_Impl( int nMode, bool bForAllUsers, REGSAM nKeyAccess ) +{ + char aSubKey[513]; + const char* aPrefix = aLocalPrefix; // bForAllUsers ? "" : aLocalPrefix; + + Status s(false); // no throw + for (int ind = 0; ind < SUPPORTED_MSEXT_NUM; ind++) + { + if (nForMSModes[ind] & nMode) + { + DWORD nSubKeys = 0, nValues = 0; + Status s1(false); // no throw + { + wsprintfA(aSubKey, "%sMIME\\DataBase\\Content Type\\%s", aPrefix, aMSMimeType[ind]); + HRegKey hkey; + if (s1.upd(RegCreateKeyExA(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, + aSubKey, 0, nullptr, REG_OPTION_NON_VOLATILE, nKeyAccess, + nullptr, &hkey, nullptr))) + { + s.upd(RegDeleteValueA(hkey, "Extension")); + s.upd(RegDeleteValueA(hkey, "CLSID")); + s1.upd(RegQueryInfoKeyA(hkey, nullptr, nullptr, nullptr, &nSubKeys, nullptr, + nullptr, &nValues, nullptr, nullptr, nullptr, nullptr)); + } + } + if (s1 && !nSubKeys && !nValues) + DeleteKeyTree(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, aSubKey, + nKeyAccess); + s.upd(s1.get()); + + Status s2(false); // no throw + { + wsprintfA(aSubKey, "%s%s", aPrefix, aMSFileExt[ind]); + HRegKey hkey; + if (s2.upd(RegCreateKeyExA(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, + aSubKey, 0, nullptr, REG_OPTION_NON_VOLATILE, nKeyAccess, + nullptr, &hkey, nullptr))) + { + s.upd(RegDeleteValueA(hkey, "Content Type")); + s2.upd(RegQueryInfoKeyA(hkey, nullptr, nullptr, nullptr, &nSubKeys, nullptr, + nullptr, &nValues, nullptr, nullptr, nullptr, nullptr)); + } + } + if (s2 && !nSubKeys && !nValues) + DeleteKeyTree(bForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, aSubKey, + nKeyAccess); + s.upd(s2.get()); + } + } + + return HRESULT_FROM_WIN32(s.get()); +} + +STDAPI DllUnregisterServerDoc( int nMode, BOOL bForAllUsers, BOOL bFor64Bit ) +{ + HRESULT hr = S_OK; + if ( bFor64Bit ) + hr = DllUnregisterServerDoc_Impl( nMode, bForAllUsers, n64KeyAccess ); + + if ( SUCCEEDED( hr ) ) + hr = DllUnregisterServerDoc_Impl( nMode, bForAllUsers, n32KeyAccess ); + + return hr; +} + + +// DllRegisterServer - regsvr32 entry point + +STDAPI DllRegisterServer() +{ + HRESULT aResult = E_FAIL; + + HMODULE aCurModule{}; + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS + | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast<LPCWSTR>(&DllRegisterServer), &aCurModule); + if( aCurModule ) + { + wchar_t pProgramPath[1024]; + wchar_t* pPathEnd = nullptr; + DWORD nLen = GetModuleFileNameW( aCurModule, pProgramPath, SAL_N_ELEMENTS(pProgramPath) ); + if ( nLen && nLen < SAL_N_ELEMENTS(pProgramPath) ) + pPathEnd = wcsrchr(pProgramPath, '\\'); + if (pPathEnd) + { + *pPathEnd = 0; + aResult = DllRegisterServerNative( 31, TRUE, bX64, pProgramPath ); + if( SUCCEEDED( aResult ) ) + aResult = DllRegisterServerDoc( 31, TRUE, bX64 ); + else + { + aResult = DllRegisterServerNative( 31, FALSE, bX64, pProgramPath ); + if( SUCCEEDED( aResult ) ) + aResult = DllRegisterServerDoc( 31, FALSE, bX64 ); + } + } + } + + return aResult; +} + + +// DllUnregisterServer - regsvr32 entry point + +STDAPI DllUnregisterServer() +{ + DllUnregisterServerDoc( 63, FALSE, bX64 ); + DllUnregisterServerNative( 63, FALSE, bX64 ); + DllUnregisterServerDoc( 63, TRUE, bX64 ); + return DllUnregisterServerNative( 63, TRUE, bX64 ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/activex/so_activex.def b/extensions/source/activex/so_activex.def new file mode 100644 index 0000000000..9f81e7917c --- /dev/null +++ b/extensions/source/activex/so_activex.def @@ -0,0 +1,13 @@ +; iervp.def : Declares the module parameters.
+
+LIBRARY
+
+EXPORTS
+ DllCanUnloadNow PRIVATE
+ DllGetClassObject PRIVATE
+ DllRegisterServer PRIVATE
+ DllUnregisterServer PRIVATE
+ DllRegisterServerNative PRIVATE
+ DllRegisterServerDoc PRIVATE
+ DllUnregisterServerNative PRIVATE
+ DllUnregisterServerDoc PRIVATE
diff --git a/extensions/source/activex/so_activex.idl b/extensions/source/activex/so_activex.idl new file mode 100644 index 0000000000..a84dde2190 --- /dev/null +++ b/extensions/source/activex/so_activex.idl @@ -0,0 +1,230 @@ +/* -*- 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 . + */ + +// so_activex.idl : IDL source for so_activex.dll + + +// This file will be processed by the MIDL tool to +// produce the type library (so_activex.tlb) and marshalling code. + +import "oaidl.idl"; +import "ocidl.idl"; +#include <olectl.h> + + + [ + object, + uuid(DACF7E3F-626B-4BF9-964B-F4910C843711), + dual, + helpstring("ISOActiveX Interface"), + pointer_default(unique) + ] + interface ISOActiveX : IDispatch + { + }; + +[ + object, + uuid(BF5D10F3-8A10-4A0B-B150-2B6AA2D7E118), + dual, + helpstring("ISOComWindowPeer Interface"), + pointer_default(unique) +] +interface ISOComWindowPeer : IDispatch +{ + [id(1), helpstring("method getWindowHandle")] + HRESULT getWindowHandle( [in] SAFEARRAY(VARIANT) procId, + [in] short s, + [out,retval] long* ret); + + [id(2), helpstring("method getToolkit")] + HRESULT getToolkit( [out,retval] IDispatch** retVal ); + + [id(3), helpstring("method setPointer")] + HRESULT setPointer( [in] IDispatch* xPointer ); + + [id(4), helpstring("method setBackground")] + HRESULT setBackground( [in] int nColor ); + + [id(5), helpstring("method invalidate")] + HRESULT invalidate( [in] short ); + + [id(6), helpstring("method invalidateRect")] + HRESULT invalidateRect( [in] IDispatch* aRect, [in] short nFlags ); + + [id(7), helpstring("method dispose")] + HRESULT dispose(); + + [id(8), helpstring("method addEventListener")] + HRESULT addEventListener( [in] IDispatch* xListener ); + + [id(9), helpstring("method removeEventListener")] + HRESULT removeEventListener( [in] IDispatch* xListener ); + + [propget, id(10), helpstring("property_implementedInterfaces")] + HRESULT Bridge_implementedInterfaces([out, retval] SAFEARRAY(BSTR) *pVal); +}; + +[ + object, + uuid(9337694C-B27D-4384-95A4-9D8E0EABC9E5), + dual, + helpstring("ISODispatchInterceptor Interface"), + pointer_default(unique) +] +interface ISODispatchInterceptor : IDispatch +{ +//com.sun.star.frame.XDispatchProviderInterceptor + + [id(1), helpstring("method getSlaveDispatchProvider")] + HRESULT getSlaveDispatchProvider( [out,retval] IDispatch** retVal ); + + [id(2), helpstring("method setSlaveDispatchProvider")] + HRESULT setSlaveDispatchProvider( [in] IDispatch* xNewDispatchProvider ); + + [id(3), helpstring("method getMasterDispatchProvider")] + HRESULT getMasterDispatchProvider( [out,retval] IDispatch** retVal ); + + [id(4), helpstring("method setMasterDispatchProvider")] + HRESULT setMasterDispatchProvider( [in] IDispatch* xNewSupplier ); + +// com.sun.star.frame.XDispatchProvider + + [id(5), helpstring("method queryDispatch")] + HRESULT queryDispatch( [in] IDispatch* aURL, + [in] BSTR aTargetFrameName, + [in] long nSearchFlags, + [out,retval] IDispatch** retVal ); + + [id(6), helpstring("method queryDispatches")] + HRESULT queryDispatches( [in] SAFEARRAY(IDispatch*) aDescripts, + [out,retval] SAFEARRAY(VARIANT)* retVal ); + + +// com.sun.star.frame.XDispatch + + [id(7), helpstring("method dispatch")] + HRESULT dispatch( [in] IDispatch* aURL, + [in] SAFEARRAY(VARIANT) aArgs ); + + [id(8), helpstring("method addStatusListener")] + HRESULT addStatusListener( [in] IDispatch* xControl, + [in] IDispatch* aURL ); + + [id(9), helpstring("method removeStatusListener")] + HRESULT removeStatusListener( [in] IDispatch* xControl, + [in] IDispatch* aURL ); + +// com.sun.star.frame.XInterceptorInfo + + + [id(10), helpstring("method getInterceptedURLs")] + HRESULT getInterceptedURLs( [out,retval] SAFEARRAY(BSTR)* pVal ); + +// the common UNO-COM staff + [propget, id(11), helpstring("property_implementedInterfaces")] + HRESULT Bridge_implementedInterfaces([out, retval] SAFEARRAY(BSTR) *pVal); + +}; + + + + +[ + object, + uuid(029E9F1E-2B3F-4297-9160-8197DE7ED54F), + dual, + helpstring("ISOActionsApproval Interface"), + pointer_default(unique) +] +interface ISOActionsApproval : IDispatch +{ +//com.sun.star.embed.XActionsApproval + + [id(1), helpstring("method approveAction")] + HRESULT approveAction( [in] long aActionID, + [out,retval] boolean* pbApproval ); + +// the common UNO-COM staff + [propget, id(2), helpstring("property_implementedInterfaces")] + HRESULT Bridge_implementedInterfaces([out, retval] SAFEARRAY(BSTR) *pVal); +}; + + + +[ + uuid(61FA3F13-8061-4796-B055-3697ED28CB38), + version(1.0), + helpstring("so_activex 1.0 Type Library") +] +library SO_ACTIVEXLib +{ + importlib("stdole32.tlb"); + importlib("stdole2.tlb"); + + [ + uuid(7F760565-5719-4F04-BA86-112C474B10EA), + helpstring("_ISOActiveXEvents Interface") + ] + dispinterface _ISOActiveXEvents + { + properties: + methods: + }; + + [ + uuid(67F2A879-82D5-4A6D-8CC5-FFB3C114B69D), + helpstring("SOActiveX Class") + ] + coclass SOActiveX + { + [default] interface ISOActiveX; + [default, source] dispinterface _ISOActiveXEvents; + }; + + [ + uuid(EE51BD3E-8BB6-4FB8-B319-F65B1BE3B21D), + helpstring("SOComWindowPeer Class") + ] + coclass SOComWindowPeer + { + [default] interface ISOComWindowPeer; + }; + + [ + uuid(C5D6D568-57DA-4D6C-819A-451CB565E682), + helpstring("SODispatchInterceptor Class") + ] + coclass SODispatchInterceptor + { + [default] interface ISODispatchInterceptor; + }; + + [ + uuid(9F3697AC-7A18-4335-AF0A-65FAC2C35CC1), + helpstring("SOActionsApproval Class") + ] + coclass SOActionsApproval + { + [default] interface ISOActionsApproval; + }; + +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/activex/so_activex.rc b/extensions/source/activex/so_activex.rc new file mode 100644 index 0000000000..3891fa965d --- /dev/null +++ b/extensions/source/activex/so_activex.rc @@ -0,0 +1,124 @@ +/* + * 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 . + */ + +//Microsoft Developer Studio generated resource script. + +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS + + +// Generated from the TEXTINCLUDE 2 resource. + +#include <winresrc.h> +#define LB_ADDSTRING (WM_USER+1) +#define CB_ADDSTRING (WM_USER+3) +#define IDC_STATIC (-1) + + +#undef APSTUDIO_READONLY_SYMBOLS + + +// Russian resources + +//#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS) +//#ifdef _WIN32 +//LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT +//#pragma code_page(1251) +//#endif //_WIN32 + + + +// Bitmap + + +//IDB_SOACTIVEX BITMAP DISCARDABLE "soacti.bmp" + + + + +// REGISTRY + + +IDR_SOACTIVEX REGISTRY DISCARDABLE "SOActiveX.rgs" +IDR_SOCOMWINDOWPEER REGISTRY DISCARDABLE "SOComWindowPeer.rgs" +IDR_SODISPATCHINTERCEPTOR REGISTRY DISCARDABLE "SODispatchInterceptor.rgs" +//#endif // Russian resources + + + + +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED + + +// TEXTINCLUDE + + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "1 TYPELIB ""so_activex.tlb""\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + + +// String Table + + +STRINGTABLE DISCARDABLE +BEGIN + IDS_PROJNAME "so_activex" +END + +#endif // English (U.S.) resources + + + + +#ifndef APSTUDIO_INVOKED + + +// Generated from the TEXTINCLUDE 3 resource. + + +1 TYPELIB SO_ACTIVEX_TLB + + +#endif // not APSTUDIO_INVOKED + diff --git a/extensions/source/bibliography/bib.component b/extensions/source/bibliography/bib.component new file mode 100644 index 0000000000..19841cc9e9 --- /dev/null +++ b/extensions/source/bibliography/bib.component @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.extensions.Bibliography" + constructor="extensions_BibliographyLoader_get_implementation"> + <service name="com.sun.star.frame.Bibliography"/> + <service name="com.sun.star.frame.FrameLoader"/> + </implementation> +</component> diff --git a/extensions/source/bibliography/bibbeam.cxx b/extensions/source/bibliography/bibbeam.cxx new file mode 100644 index 0000000000..a38c723852 --- /dev/null +++ b/extensions/source/bibliography/bibbeam.cxx @@ -0,0 +1,267 @@ +/* -*- 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 <toolkit/helper/vclunohelper.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/awt/PosSize.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <vcl/taskpanelist.hxx> +#include <tools/debug.hxx> +#include "bibbeam.hxx" +#include "datman.hxx" +#include "bibtools.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::uno; + + +#define ID_TOOLBAR 1 +#define ID_GRIDWIN 2 + +namespace bib +{ + + void HandleTaskPaneList( vcl::Window* pWindow, bool bAddToList ) + { + vcl::Window* pParent = pWindow->GetParent(); + + DBG_ASSERT( pParent, "-GetTaskPaneList(): everybody here should have a parent!" ); + + SystemWindow* pSysWin = pParent->GetSystemWindow(); + if( pSysWin ) + { + TaskPaneList* pTaskPaneList = pSysWin->GetTaskPaneList(); + if( pTaskPaneList ) + { + if( bAddToList ) + pTaskPaneList->AddWindow( pWindow ); + else + pTaskPaneList->RemoveWindow( pWindow ); + } + } + } + + + class BibGridwin + :public vcl::Window //DockingWindow + { + private: + Reference< awt::XWindow > m_xGridWin; + Reference< awt::XControlModel > m_xGridModel; + Reference< awt::XControl > m_xControl; + Reference< awt::XControlContainer > m_xControlContainer; + Reference< frame::XDispatchProviderInterception> m_xDispatchProviderInterception; + + protected: + + virtual void Resize() override; + + public: + + BibGridwin(vcl::Window* pParent, WinBits nStyle ); + virtual ~BibGridwin() override; + virtual void dispose() override; + + void createGridWin(const Reference< awt::XControlModel > & xDbForm); + void disposeGridWin(); + + const Reference< awt::XControlContainer >& getControlContainer() const { return m_xControlContainer; } + const Reference< frame::XDispatchProviderInterception>& getDispatchProviderInterception() const { return m_xDispatchProviderInterception; } + + virtual void GetFocus() override; + }; + + BibGridwin::BibGridwin( vcl::Window* _pParent, WinBits _nStyle ) : Window( _pParent, _nStyle ) + { + m_xControlContainer = VCLUnoHelper::CreateControlContainer(this); + + AddToTaskPaneList( this ); + } + + BibGridwin::~BibGridwin() + { + disposeOnce(); + } + + void BibGridwin::dispose() + { + RemoveFromTaskPaneList( this ); + + disposeGridWin(); + vcl::Window::dispose(); + } + + void BibGridwin::Resize() + { + if(m_xGridWin.is()) + { + ::Size aSize = GetOutputSizePixel(); + m_xGridWin->setPosSize(0, 0, aSize.Width(),aSize.Height(), awt::PosSize::SIZE); + } + } + + void BibGridwin::createGridWin(const uno::Reference< awt::XControlModel > & xGModel) + { + m_xGridModel = xGModel; + + if( !m_xControlContainer.is()) + return; + + uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext(); + + if ( !m_xGridModel.is()) + return; + + uno::Reference< XPropertySet > xPropSet( m_xGridModel, UNO_QUERY ); + + if ( xPropSet.is() && m_xGridModel.is() ) + { + uno::Any aAny = xPropSet->getPropertyValue( "DefaultControl" ); + OUString aControlName; + aAny >>= aControlName; + + m_xControl.set( xContext->getServiceManager()->createInstanceWithContext(aControlName, xContext), UNO_QUERY_THROW ); + m_xControl->setModel( m_xGridModel ); + } + + if ( !m_xControl.is() ) + return; + + // Peer as Child to the FrameWindow + m_xControlContainer->addControl("GridControl", m_xControl); + m_xGridWin.set(m_xControl, UNO_QUERY ); + m_xDispatchProviderInterception.set(m_xControl, UNO_QUERY ); + m_xGridWin->setVisible( true ); + m_xControl->setDesignMode( true ); + // initially switch on the design mode - switch it off _after_ loading the form + + ::Size aSize = GetOutputSizePixel(); + m_xGridWin->setPosSize(0, 0, aSize.Width(),aSize.Height(), awt::PosSize::POSSIZE); + } + + void BibGridwin::disposeGridWin() + { + if ( m_xControl.is() ) + { + Reference< awt::XControl > xDel( m_xControl ); + m_xControl = nullptr; + m_xGridWin = nullptr; + + m_xControlContainer->removeControl( xDel ); + xDel->dispose(); + } + } + + void BibGridwin::GetFocus() + { + if(m_xGridWin.is()) + m_xGridWin->setFocus(); + } + + BibBeamer::BibBeamer( vcl::Window* _pParent, BibDataManager* _pDM ) + :BibSplitWindow( _pParent, WB_3DLOOK | WB_NOSPLITDRAW ) + ,pDatMan( _pDM ) + ,pToolBar( nullptr ) + ,pGridWin( nullptr ) + { + createToolBar(); + createGridWin(); + pDatMan->SetToolbar(pToolBar); + pGridWin->Show(); + connectForm( pDatMan ); + } + + BibBeamer::~BibBeamer() + { + disposeOnce(); + } + + void BibBeamer::dispose() + { + if ( isFormConnected() ) + disconnectForm(); + + if ( pToolBar ) + pDatMan->SetToolbar(nullptr); + + pToolBar.disposeAndClear(); + pGridWin.disposeAndClear(); + BibSplitWindow::dispose(); + } + + void BibBeamer::createToolBar() + { + pToolBar= VclPtr<BibToolBar>::Create(this, LINK( this, BibBeamer, RecalcLayout_Impl )); + ::Size aSize=pToolBar->get_preferred_size(); + InsertItem(ID_TOOLBAR, pToolBar, aSize.Height(), 0, 0, SplitWindowItemFlags::Fixed ); + if ( m_xController.is() ) + pToolBar->SetXController( m_xController ); + } + + void BibBeamer::createGridWin() + { + pGridWin = VclPtr<BibGridwin>::Create(this,0); + + InsertItem(ID_GRIDWIN, pGridWin, 40, 1, 0, SplitWindowItemFlags::RelativeSize ); + + pGridWin->createGridWin( pDatMan->updateGridModel() ); + } + + Reference< awt::XControlContainer > BibBeamer::getControlContainer() + { + Reference< awt::XControlContainer > xReturn; + if ( pGridWin ) + xReturn = pGridWin->getControlContainer(); + return xReturn; + } + + Reference< frame::XDispatchProviderInterception > BibBeamer::getDispatchProviderInterception() const + { + Reference< frame::XDispatchProviderInterception > xReturn; + if ( pGridWin ) + xReturn = pGridWin->getDispatchProviderInterception(); + return xReturn; + } + + void BibBeamer::SetXController(const uno::Reference< frame::XController > & xCtr) + { + m_xController = xCtr; + + if ( pToolBar ) + pToolBar->SetXController( m_xController ); + + } + + void BibBeamer::GetFocus() + { + if( pGridWin ) + pGridWin->GrabFocus(); + } + + IMPL_LINK_NOARG( BibBeamer, RecalcLayout_Impl, void*, void ) + { + tools::Long nHeight = pToolBar->get_preferred_size().Height(); + SetItemSize( ID_TOOLBAR, nHeight ); + } + +} // namespace bib + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/bibbeam.hxx b/extensions/source/bibliography/bibbeam.hxx new file mode 100644 index 0000000000..4cc32d7da9 --- /dev/null +++ b/extensions/source/bibliography/bibbeam.hxx @@ -0,0 +1,71 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/awt/XControlContainer.hpp> +#include <com/sun/star/frame/XDispatchProviderInterception.hpp> +#include "toolbar.hxx" +#include "formcontrolcontainer.hxx" +#include "bibshortcuthandler.hxx" + +class BibDataManager; + + +namespace bib +{ + + + class BibGridwin; + class BibBeamer final + :public BibSplitWindow + ,public FormControlContainer + { + css::uno::Reference< css::frame::XController > m_xController; + + BibDataManager* pDatMan; + VclPtr<BibToolBar> pToolBar; + VclPtr<BibGridwin> pGridWin; + + DECL_LINK( RecalcLayout_Impl, void*, void ); + + void createToolBar(); + void createGridWin(); + + // FormControlContainer ---------- + virtual css::uno::Reference< css::awt::XControlContainer > + getControlContainer() override; + public: + css::uno::Reference< css::frame::XDispatchProviderInterception > + getDispatchProviderInterception() const; + + BibBeamer(vcl::Window* pParent,BibDataManager* pDatMan ); + virtual ~BibBeamer() override; + virtual void dispose() override; + + void SetXController(const css::uno::Reference< css::frame::XController > &); + + virtual void GetFocus() override; + }; + + +} // namespace bib + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/bibconfig.cxx b/extensions/source/bibliography/bibconfig.cxx new file mode 100644 index 0000000000..358adf8c58 --- /dev/null +++ b/extensions/source/bibliography/bibconfig.cxx @@ -0,0 +1,297 @@ +/* -*- 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 "bibconfig.hxx" +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/sdb/DatabaseContext.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <o3tl/any.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdb; + + +constexpr OUString cDataSourceHistory = u"DataSourceHistory"_ustr; + +Sequence<OUString> const & BibConfig::GetPropertyNames() +{ + static Sequence<OUString> aNames = + { + "CurrentDataSource/DataSourceName", + "CurrentDataSource/Command", + "CurrentDataSource/CommandType", + "BeamerHeight", + "ViewHeight", + "QueryText", + "QueryField", + "ShowColumnAssignmentWarning" + }; + return aNames; +} + +BibConfig::BibConfig() + : ConfigItem("Office.DataAccess/Bibliography", ConfigItemMode::NONE) + , nTblOrQuery(0) + , nBeamerSize(0) + , nViewSize(0) + , bShowColumnAssignmentWarning(false) +{ + //Names of the default columns + aColumnDefaults[0] = "Identifier"; + aColumnDefaults[1] = "BibliographyType"; + aColumnDefaults[2] = "Author"; + aColumnDefaults[3] = "Title"; + aColumnDefaults[4] = "Year"; + aColumnDefaults[5] = "ISBN"; + aColumnDefaults[6] = "Booktitle"; + aColumnDefaults[7] = "Chapter"; + aColumnDefaults[8] = "Edition"; + aColumnDefaults[9] = "Editor"; + aColumnDefaults[10] = "Howpublished"; + aColumnDefaults[11] = "Institution"; + aColumnDefaults[12] = "Journal"; + aColumnDefaults[13] = "Month"; + aColumnDefaults[14] = "Note"; + aColumnDefaults[15] = "Annote"; + aColumnDefaults[16] = "Number"; + aColumnDefaults[17] = "Organizations"; + aColumnDefaults[18] = "Pages"; + aColumnDefaults[19] = "Publisher"; + aColumnDefaults[20] = "Address"; + aColumnDefaults[21] = "School"; + aColumnDefaults[22] = "Series"; + aColumnDefaults[23] = "ReportType"; + aColumnDefaults[24] = "Volume"; + aColumnDefaults[25] = "URL"; + aColumnDefaults[26] = "Custom1"; + aColumnDefaults[27] = "Custom2"; + aColumnDefaults[28] = "Custom3"; + aColumnDefaults[29] = "Custom4"; + aColumnDefaults[30] = "Custom5"; + aColumnDefaults[31] = "LocalURL"; + + + const Sequence< OUString > aPropertyNames = GetPropertyNames(); + const Sequence<Any> aPropertyValues = GetProperties( aPropertyNames ); + const Any* pValues = aPropertyValues.getConstArray(); + if(aPropertyValues.getLength() == aPropertyNames.getLength()) + { + for(int nProp = 0; nProp < aPropertyNames.getLength(); nProp++) + { + if(pValues[nProp].hasValue()) + { + switch(nProp) + { + case 0: pValues[nProp] >>= sDataSource; break; + case 1: pValues[nProp] >>= sTableOrQuery; break; + case 2: pValues[nProp] >>= nTblOrQuery; break; + case 3: pValues[nProp] >>= nBeamerSize; break; + case 4: pValues[nProp] >>= nViewSize ; break; + case 5: pValues[nProp] >>= sQueryText ; break; + case 6: pValues[nProp] >>= sQueryField; break; + case 7: + bShowColumnAssignmentWarning = *o3tl::doAccess<bool>(pValues[nProp]); + break; + } + } + } + } + const Sequence< OUString > aNodeNames = GetNodeNames(cDataSourceHistory); + for(OUString const & nodeName : aNodeNames) + { + Sequence<OUString> aHistoryNames(3); + OUString* pHistoryNames = aHistoryNames.getArray(); + + OUString sPrefix = OUString::Concat(cDataSourceHistory) + "/" + nodeName + "/"; + pHistoryNames[0] = sPrefix + "DataSourceName"; + pHistoryNames[1] = sPrefix + "Command"; + pHistoryNames[2] = sPrefix + "CommandType"; + + Sequence<Any> aHistoryValues = GetProperties( aHistoryNames ); + const Any* pHistoryValues = aHistoryValues.getConstArray(); + + if(aHistoryValues.getLength() == aHistoryNames.getLength()) + { + Mapping* pMapping = new Mapping; + pHistoryValues[0] >>= pMapping->sURL; + pHistoryValues[1] >>= pMapping->sTableName; + pHistoryValues[2] >>= pMapping->nCommandType; + //field assignment is contained in another set + sPrefix += "Fields"; + const Sequence< OUString > aAssignmentNodeNames = GetNodeNames(sPrefix); + Sequence<OUString> aAssignmentPropertyNames(aAssignmentNodeNames.getLength() * 2); + OUString* pAssignmentPropertyNames = aAssignmentPropertyNames.getArray(); + sal_Int16 nFieldIdx = 0; + for(OUString const & assignName : aAssignmentNodeNames) + { + OUString sSubPrefix = sPrefix + "/" + assignName; + pAssignmentPropertyNames[nFieldIdx] = sSubPrefix; + pAssignmentPropertyNames[nFieldIdx++] += "/ProgrammaticFieldName"; + pAssignmentPropertyNames[nFieldIdx] = sSubPrefix; + pAssignmentPropertyNames[nFieldIdx++] += "/AssignedFieldName"; + } + Sequence<Any> aAssignmentValues = GetProperties(aAssignmentPropertyNames); + const Any* pAssignmentValues = aAssignmentValues.getConstArray(); + OUString sTempLogical; + OUString sTempReal; + sal_Int16 nSetMapping = 0; + nFieldIdx = 0; + for(sal_Int32 nFieldVal = 0; nFieldVal < aAssignmentValues.getLength() / 2; nFieldVal++) + { + pAssignmentValues[nFieldIdx++] >>= sTempLogical; + pAssignmentValues[nFieldIdx++] >>= sTempReal; + if(!(sTempLogical.isEmpty() || sTempReal.isEmpty())) + { + pMapping->aColumnPairs[nSetMapping].sLogicalColumnName = sTempLogical; + pMapping->aColumnPairs[nSetMapping++].sRealColumnName = sTempReal; + } + } + mvMappings.push_back(std::unique_ptr<Mapping>(pMapping)); + } + } +} + +BibConfig::~BibConfig() +{ + assert(!IsModified()); // should have been committed +} + +BibDBDescriptor BibConfig::GetBibliographyURL() +{ + BibDBDescriptor aRet; + aRet.sDataSource = sDataSource; + aRet.sTableOrQuery = sTableOrQuery; + aRet.nCommandType = nTblOrQuery; + return aRet; +}; + +void BibConfig::SetBibliographyURL(const BibDBDescriptor& rDesc) +{ + sDataSource = rDesc.sDataSource; + sTableOrQuery = rDesc.sTableOrQuery; + nTblOrQuery = rDesc.nCommandType; + SetModified(); +}; + +void BibConfig::Notify( const css::uno::Sequence<OUString>& ) +{ +} + +void BibConfig::ImplCommit() +{ + PutProperties( + GetPropertyNames(), + {css::uno::Any(sDataSource), css::uno::Any(sTableOrQuery), + css::uno::Any(nTblOrQuery), css::uno::Any(nBeamerSize), + css::uno::Any(nViewSize), css::uno::Any(sQueryText), + css::uno::Any(sQueryField), + css::uno::Any(bShowColumnAssignmentWarning)}); + ClearNodeSet(cDataSourceHistory); + Sequence< PropertyValue > aNodeValues(mvMappings.size() * 3); + PropertyValue* pNodeValues = aNodeValues.getArray(); + + sal_Int32 nIndex = 0; + for(sal_Int32 i = 0; i < static_cast<sal_Int32>(mvMappings.size()); i++) + { + const Mapping* pMapping = mvMappings[i].get(); + OUString sPrefix = OUString::Concat(cDataSourceHistory) + "/_" + OUString::number(i) + "/"; + pNodeValues[nIndex].Name = sPrefix + "DataSourceName"; + pNodeValues[nIndex++].Value <<= pMapping->sURL; + pNodeValues[nIndex].Name = sPrefix + "Command"; + pNodeValues[nIndex++].Value <<= pMapping->sTableName; + pNodeValues[nIndex].Name = sPrefix + "CommandType"; + pNodeValues[nIndex++].Value <<= pMapping->nCommandType; + SetSetProperties(cDataSourceHistory, aNodeValues); + + sPrefix += "Fields"; + sal_Int32 nFieldAssignment = 0; + OUString sFieldName = "/ProgrammaticFieldName"; + OUString sDatabaseFieldName = "/AssignedFieldName"; + ClearNodeSet( sPrefix ); + + while(nFieldAssignment < COLUMN_COUNT && + !pMapping->aColumnPairs[nFieldAssignment].sLogicalColumnName.isEmpty()) + { + OUString sSubPrefix = sPrefix + "/_" + OUString::number(nFieldAssignment); + Sequence< PropertyValue > aAssignmentValues + { + comphelper::makePropertyValue(sSubPrefix + sFieldName, pMapping->aColumnPairs[nFieldAssignment].sLogicalColumnName), + comphelper::makePropertyValue(sSubPrefix + sDatabaseFieldName, pMapping->aColumnPairs[nFieldAssignment].sRealColumnName) + }; + SetSetProperties( sPrefix, aAssignmentValues ); + nFieldAssignment++; + } + } +} + +const Mapping* BibConfig::GetMapping(const BibDBDescriptor& rDesc) const +{ + for(std::unique_ptr<Mapping> const & i : mvMappings) + { + Mapping& rMapping = *i; + bool bURLEqual = rDesc.sDataSource == rMapping.sURL; + if(rDesc.sTableOrQuery == rMapping.sTableName && bURLEqual) + return &rMapping; + } + return nullptr; +} + +void BibConfig::SetMapping(const BibDBDescriptor& rDesc, const Mapping* pSetMapping) +{ + for(size_t i = 0; i < mvMappings.size(); i++) + { + Mapping& rMapping = *mvMappings[i]; + bool bURLEqual = rDesc.sDataSource == rMapping.sURL; + if(rDesc.sTableOrQuery == rMapping.sTableName && bURLEqual) + { + mvMappings.erase(mvMappings.begin()+i); + break; + } + } + mvMappings.push_back(std::make_unique<Mapping>(*pSetMapping)); + SetModified(); +} + +DBChangeDialogConfig_Impl::DBChangeDialogConfig_Impl() +{ +} + +DBChangeDialogConfig_Impl::~DBChangeDialogConfig_Impl() +{ +} + +const Sequence<OUString>& DBChangeDialogConfig_Impl::GetDataSourceNames() +{ + if(!aSourceNames.hasElements()) + { + Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + Reference<XDatabaseContext> xDBContext = DatabaseContext::create(xContext); + aSourceNames = xDBContext->getElementNames(); + } + return aSourceNames; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/bibconfig.hxx b/extensions/source/bibliography/bibconfig.hxx new file mode 100644 index 0000000000..0d81491ff0 --- /dev/null +++ b/extensions/source/bibliography/bibconfig.hxx @@ -0,0 +1,153 @@ +/* -*- 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 . + */ + +#pragma once + +#include <unotools/configitem.hxx> +#include <tools/long.hxx> +#include <vector> +#include <memory> + +struct Mapping; +typedef std::vector<std::unique_ptr<Mapping> > MappingArray; + + +#define COLUMN_COUNT 32 +#define IDENTIFIER_POS 0 +#define AUTHORITYTYPE_POS 1 +#define AUTHOR_POS 2 +#define TITLE_POS 3 +#define YEAR_POS 4 +#define ISBN_POS 5 +#define BOOKTITLE_POS 6 +#define CHAPTER_POS 7 +#define EDITION_POS 8 +#define EDITOR_POS 9 +#define HOWPUBLISHED_POS 10 +#define INSTITUTION_POS 11 +#define JOURNAL_POS 12 +#define MONTH_POS 13 +#define NOTE_POS 14 +#define ANNOTE_POS 15 +#define NUMBER_POS 16 +#define ORGANIZATIONS_POS 17 +#define PAGES_POS 18 +#define PUBLISHER_POS 19 +#define ADDRESS_POS 20 +#define SCHOOL_POS 21 +#define SERIES_POS 22 +#define REPORTTYPE_POS 23 +#define VOLUME_POS 24 +#define URL_POS 25 +#define CUSTOM1_POS 26 +#define CUSTOM2_POS 27 +#define CUSTOM3_POS 28 +#define CUSTOM4_POS 29 +#define CUSTOM5_POS 30 +#define LOCAL_URL_POS 31 + +struct StringPair +{ + OUString sRealColumnName; + OUString sLogicalColumnName; +}; + +struct Mapping +{ + OUString sTableName; + OUString sURL; + sal_Int16 nCommandType; + StringPair aColumnPairs[COLUMN_COUNT]; + + Mapping() : + nCommandType(0){} +}; + +struct BibDBDescriptor +{ + OUString sDataSource; + OUString sTableOrQuery; + sal_Int32 nCommandType; +}; + + +class BibConfig : public utl::ConfigItem +{ + OUString sDataSource; + OUString sTableOrQuery; + sal_Int32 nTblOrQuery; + + OUString sQueryField; + OUString sQueryText; + MappingArray mvMappings; + tools::Long nBeamerSize; + tools::Long nViewSize; + bool bShowColumnAssignmentWarning; + + OUString aColumnDefaults[COLUMN_COUNT]; + + static css::uno::Sequence<OUString> const & GetPropertyNames(); + + virtual void ImplCommit() override; + +public: + BibConfig(); + virtual ~BibConfig() override; + + virtual void Notify( const css::uno::Sequence<OUString>& aPropertyNames) override; + + BibDBDescriptor GetBibliographyURL(); + void SetBibliographyURL(const BibDBDescriptor& rDesc); + + const Mapping* GetMapping(const BibDBDescriptor& rDesc) const; + void SetMapping(const BibDBDescriptor& rDesc, const Mapping* pMapping); + + const OUString& GetDefColumnName(sal_uInt16 nIndex) const + {return aColumnDefaults[nIndex];} + + + void setBeamerSize(tools::Long nSize) {SetModified(); nBeamerSize = nSize;} + tools::Long getBeamerSize()const {return nBeamerSize;} + void setViewSize(tools::Long nSize) {SetModified(); nViewSize = nSize;} + tools::Long getViewSize() const {return nViewSize;} + + const OUString& getQueryField() const {return sQueryField;} + void setQueryField(const OUString& rSet) {SetModified(); sQueryField = rSet;} + + const OUString& getQueryText() const {return sQueryText;} + void setQueryText(const OUString& rSet) {SetModified(); sQueryText = rSet;} + + bool IsShowColumnAssignmentWarning() const + { return bShowColumnAssignmentWarning;} + void SetShowColumnAssignmentWarning(bool bSet) + { bShowColumnAssignmentWarning = bSet;} +}; + +class DBChangeDialogConfig_Impl +{ + css::uno::Sequence<OUString> aSourceNames; +public: + DBChangeDialogConfig_Impl(); + ~DBChangeDialogConfig_Impl(); + + const css::uno::Sequence<OUString>& GetDataSourceNames(); + +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/bibcont.cxx b/extensions/source/bibliography/bibcont.cxx new file mode 100644 index 0000000000..b67cf337ec --- /dev/null +++ b/extensions/source/bibliography/bibcont.cxx @@ -0,0 +1,241 @@ +/* -*- 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 <vcl/event.hxx> +#include "bibconfig.hxx" + + +#include "bibcont.hxx" + + +BibShortCutHandler::~BibShortCutHandler() +{ +} + +bool BibShortCutHandler::HandleShortCutKey( const KeyEvent& ) +{ + return false; +} + + +BibWindow::BibWindow( vcl::Window* pParent, WinBits nStyle ) : Window( pParent, nStyle ), BibShortCutHandler( this ) +{ +} + +BibWindow::~BibWindow() +{ +} + + +BibSplitWindow::BibSplitWindow( vcl::Window* pParent, WinBits nStyle ) : SplitWindow( pParent, nStyle ), BibShortCutHandler( this ) +{ +} + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +//split window size is a percent value +#define WIN_MIN_HEIGHT 10 +#define WIN_STEP_SIZE 5 + +BibWindowContainer::BibWindowContainer( vcl::Window* pParent, BibShortCutHandler* pChildWin ) : + BibWindow( pParent, WB_3DLOOK ), + pChild( pChildWin ) +{ + if(pChild!=nullptr) + { + vcl::Window* pChildWindow = GetChild(); + pChildWindow->SetParent(this); + pChildWindow->Show(); + pChildWindow->SetPosPixel(Point(0,0)); + } +} + +BibWindowContainer::~BibWindowContainer() +{ + disposeOnce(); +} + +void BibWindowContainer::dispose() +{ + if( pChild ) + { + VclPtr<vcl::Window> pDel = GetChild(); + pChild = nullptr; // prevents GetFocus for child while deleting! + pDel.disposeAndClear(); + } + vcl::Window::dispose(); +} + +void BibWindowContainer::Resize() +{ + if( pChild ) + GetChild()->SetSizePixel( GetOutputSizePixel() ); +} + +void BibWindowContainer::GetFocus() +{ + if( pChild ) + GetChild()->GrabFocus(); +} + +bool BibWindowContainer::HandleShortCutKey( const KeyEvent& rKeyEvent ) +{ + return pChild && pChild->HandleShortCutKey( rKeyEvent ); +} + + +BibBookContainer::BibBookContainer(vcl::Window* pParent): + BibSplitWindow(pParent,WB_3DLOOK), + pTopWin(nullptr), + pBottomWin(nullptr), + aIdle("extensions BibBookContainer Split Idle") +{ + pBibMod = OpenBibModul(); + aIdle.SetInvokeHandler(LINK( this, BibBookContainer, SplitHdl)); + aIdle.SetPriority(TaskPriority::LOWEST); +} + +BibBookContainer::~BibBookContainer() +{ + disposeOnce(); +} + +void BibBookContainer::dispose() +{ + if( pTopWin ) + { + VclPtr<vcl::Window> pDel = pTopWin; + pTopWin = nullptr; // prevents GetFocus for child while deleting! + pDel.disposeAndClear(); + } + + if( pBottomWin ) + { + VclPtr<vcl::Window> pDel = pBottomWin; + pBottomWin = nullptr; // prevents GetFocus for child while deleting! + pDel.disposeAndClear(); + } + + CloseBibModul( pBibMod ); + pTopWin.clear(); + pBottomWin.clear(); + BibSplitWindow::dispose(); +} + +void BibBookContainer::Split() +{ + aIdle.Start(); +} +IMPL_LINK_NOARG( BibBookContainer, SplitHdl, Timer*, void) +{ + tools::Long nSize= GetItemSize( TOP_WINDOW); + BibConfig* pConfig = BibModul::GetConfig(); + pConfig->setBeamerSize(nSize); + nSize = GetItemSize( BOTTOM_WINDOW); + pConfig->setViewSize(nSize); +} + +void BibBookContainer::createTopFrame( BibShortCutHandler* pWin ) +{ + if(pTopWin) + { + RemoveItem(TOP_WINDOW); + pTopWin.disposeAndClear(); + } + pTopWin=VclPtr<BibWindowContainer>::Create(this,pWin); + pTopWin->Show(); + BibConfig* pConfig = BibModul::GetConfig(); + tools::Long nSize = pConfig->getBeamerSize(); + InsertItem(TOP_WINDOW, pTopWin, nSize, 1, 0, SplitWindowItemFlags::PercentSize ); + +} + +void BibBookContainer::createBottomFrame( BibShortCutHandler* pWin ) +{ + if(pBottomWin) + { + RemoveItem(BOTTOM_WINDOW); + pBottomWin.disposeAndClear(); + } + + pBottomWin=VclPtr<BibWindowContainer>::Create(this,pWin); + + BibConfig* pConfig = BibModul::GetConfig(); + tools::Long nSize = pConfig->getViewSize(); + InsertItem(BOTTOM_WINDOW, pBottomWin, nSize, 1, 0, SplitWindowItemFlags::PercentSize ); + +} + +void BibBookContainer::GetFocus() +{ + if( pBottomWin ) + pBottomWin->GrabFocus(); +} + +bool BibBookContainer::PreNotify( NotifyEvent& rNEvt ) +{ + bool bHandled = false; + if( NotifyEventType::KEYINPUT == rNEvt.GetType() ) + { + const KeyEvent* pKEvt = rNEvt.GetKeyEvent(); + const vcl::KeyCode aKeyCode = pKEvt->GetKeyCode(); + sal_uInt16 nKey = aKeyCode.GetCode(); + const sal_uInt16 nModifier = aKeyCode.GetModifier(); + + if( KEY_MOD2 == nModifier ) + { + if( KEY_UP == nKey || KEY_DOWN == nKey ) + { + if(pTopWin && pBottomWin) + { + sal_uInt16 nFirstWinId = KEY_UP == nKey ? TOP_WINDOW : BOTTOM_WINDOW; + sal_uInt16 nSecondWinId = KEY_UP == nKey ? BOTTOM_WINDOW : TOP_WINDOW; + tools::Long nHeight = GetItemSize( nFirstWinId ); + nHeight -= WIN_STEP_SIZE; + if(nHeight < WIN_MIN_HEIGHT) + nHeight = WIN_MIN_HEIGHT; + SetItemSize( nFirstWinId, nHeight ); + SetItemSize( nSecondWinId, 100 - nHeight ); + } + bHandled = true; + } + else if( pKEvt->GetCharCode() && HandleShortCutKey( *pKEvt ) ) + bHandled = true; + } + } + + return bHandled; +} + +bool BibBookContainer::HandleShortCutKey( const KeyEvent& rKeyEvent ) +{ + bool bRet = false; + + if( pTopWin ) + bRet = pTopWin->HandleShortCutKey( rKeyEvent ); + + if( !bRet && pBottomWin ) + bRet = pBottomWin->HandleShortCutKey( rKeyEvent ); + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/bibcont.hxx b/extensions/source/bibliography/bibcont.hxx new file mode 100644 index 0000000000..d90952ac32 --- /dev/null +++ b/extensions/source/bibliography/bibcont.hxx @@ -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 . + */ + +#pragma once + +#include <vcl/timer.hxx> +#include <vcl/idle.hxx> +#include "bibshortcuthandler.hxx" + +#include "bibmod.hxx" + +#define TOP_WINDOW 1 +#define BOTTOM_WINDOW 2 + +class BibWindowContainer : public BibWindow //Window +{ + private: + // !BibShortCutHandler is also always a Window! + BibShortCutHandler* pChild; + + protected: + virtual void Resize() override; + + public: + BibWindowContainer( vcl::Window* pParent, BibShortCutHandler* pChild); + virtual ~BibWindowContainer() override; + virtual void dispose() override; + + inline vcl::Window* GetChild(); + + virtual void GetFocus() override; + + virtual bool HandleShortCutKey( const KeyEvent& rKeyEvent ) override; // returns true, if key was handled + + using Window::GetChild; +}; + +inline vcl::Window* BibWindowContainer::GetChild() +{ + return pChild ? pChild->GetWindow() : nullptr; +} + + +class BibBookContainer: public BibSplitWindow +{ + private: + + VclPtr<BibWindowContainer> pTopWin; + VclPtr<BibWindowContainer> pBottomWin; + HdlBibModul pBibMod; + Idle aIdle; + + DECL_LINK( SplitHdl, Timer*, void ); + + protected: + + virtual void Split() override; + + virtual bool PreNotify( NotifyEvent& rNEvt ) override; + + public: + + explicit BibBookContainer(vcl::Window* pParent ); + virtual ~BibBookContainer() override; + virtual void dispose() override; + + // !BibShortCutHandler is also always a Window! + void createTopFrame( BibShortCutHandler* pWin ); + + void createBottomFrame( BibShortCutHandler* pWin ); + + virtual void GetFocus() override; + + virtual bool HandleShortCutKey( const KeyEvent& rKeyEvent ) override; // returns true, if key was handled +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/bibload.cxx b/extensions/source/bibliography/bibload.cxx new file mode 100644 index 0000000000..c8d88d8d01 --- /dev/null +++ b/extensions/source/bibliography/bibload.cxx @@ -0,0 +1,609 @@ +/* -*- 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 <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <svl/itemprop.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/awt/XVclWindowPeer.hpp> +#include <com/sun/star/sdbc/ResultSetType.hpp> +#include <com/sun/star/sdbc/ResultSetConcurrency.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/sdb/XColumn.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <com/sun/star/sdbc/XRowSet.hpp> +#include <com/sun/star/frame/XFrameLoader.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/text/BibliographyDataField.hpp> +#include <com/sun/star/form/XLoadable.hpp> +#include <com/sun/star/frame/XLayoutManager.hpp> +#include <vcl/window.hxx> +#include <vcl/svapp.hxx> + +#include "bibresid.hxx" +#include <strings.hrc> +#include "bibcont.hxx" +#include "bibbeam.hxx" +#include "bibmod.hxx" +#include "bibview.hxx" +#include "framectr.hxx" +#include "datman.hxx" +#include "bibconfig.hxx" +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> +#include <o3tl/string_view.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::form; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::frame; + +namespace { + +class BibliographyLoader : public cppu::WeakImplHelper + < XServiceInfo, XNameAccess, XPropertySet, XFrameLoader > +{ + HdlBibModul m_pBibMod; + rtl::Reference<BibDataManager> m_xDatMan; + Reference< XNameAccess > m_xColumns; + Reference< XResultSet > m_xCursor; + +private: + + void loadView(const Reference< XFrame > & aFrame, + const Reference< XLoadEventListener > & aListener); + + BibDataManager* GetDataManager()const; + Reference< XNameAccess > const & GetDataColumns() const; + Reference< XResultSet > const & GetDataCursor() const; + Reference< sdb::XColumn > GetIdentifierColumn() const; + +public: + BibliographyLoader(); + virtual ~BibliographyLoader() override; + + // XServiceInfo + OUString SAL_CALL getImplementationName() override; + sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + //XNameAccess + virtual Any SAL_CALL getByName(const OUString& aName) override; + virtual Sequence< OUString > SAL_CALL getElementNames() override; + virtual sal_Bool SAL_CALL hasByName(const OUString& aName) override; + + //XElementAccess + virtual Type SAL_CALL getElementType() override; + virtual sal_Bool SAL_CALL hasElements() override; + + //XPropertySet + virtual Reference< XPropertySetInfo > SAL_CALL getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue(const OUString& PropertyName, const Any& aValue) override; + virtual Any SAL_CALL getPropertyValue(const OUString& PropertyName) override; + virtual void SAL_CALL addPropertyChangeListener(const OUString& PropertyName, const Reference< XPropertyChangeListener > & aListener) override; + virtual void SAL_CALL removePropertyChangeListener(const OUString& PropertyName, const Reference< XPropertyChangeListener > & aListener) override; + virtual void SAL_CALL addVetoableChangeListener(const OUString& PropertyName, const Reference< XVetoableChangeListener > & aListener) override; + virtual void SAL_CALL removeVetoableChangeListener(const OUString& PropertyName, const Reference< XVetoableChangeListener > & aListener) override; + + // XLoader + virtual void SAL_CALL load(const Reference< XFrame > & aFrame, const OUString& aURL, + const Sequence< PropertyValue >& aArgs, + const Reference< XLoadEventListener > & aListener) override; + virtual void SAL_CALL cancel() override; +}; + +} + +BibliographyLoader::BibliographyLoader() : + m_pBibMod(nullptr) +{ +} + +BibliographyLoader::~BibliographyLoader() +{ + Reference< lang::XComponent > xComp(m_xCursor, UNO_QUERY); + if (xComp.is()) + xComp->dispose(); + if(m_pBibMod) + CloseBibModul(m_pBibMod); +} + + +// XServiceInfo +OUString BibliographyLoader::getImplementationName() +{ + return "com.sun.star.extensions.Bibliography"; +} + +// XServiceInfo +sal_Bool BibliographyLoader::supportsService(const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +// XServiceInfo +Sequence< OUString > BibliographyLoader::getSupportedServiceNames() +{ + return { "com.sun.star.frame.FrameLoader", "com.sun.star.frame.Bibliography" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_BibliographyLoader_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new BibliographyLoader()); +} + +void BibliographyLoader::cancel() +{ + //! + //! +} + +void BibliographyLoader::load(const Reference< XFrame > & rFrame, const OUString& rURL, + const Sequence< PropertyValue >& /*rArgs*/, + const Reference< XLoadEventListener > & rListener) +{ + + SolarMutexGuard aGuard; + + m_pBibMod = OpenBibModul(); + + std::u16string_view aPartName = o3tl::getToken(rURL, 1, '/' ); + Reference<XPropertySet> xPrSet(rFrame, UNO_QUERY); + if(xPrSet.is()) + { + Any aTitle; + aTitle <<= BibResId(RID_BIB_STR_FRAME_TITLE); + xPrSet->setPropertyValue("Title", aTitle); + } + if(aPartName == u"View" || aPartName == u"View1") + { + loadView(rFrame, rListener); + } +} + + +void BibliographyLoader::loadView(const Reference< XFrame > & rFrame, + const Reference< XLoadEventListener > & rListener) +{ + SolarMutexGuard aGuard; + //! + if(!m_pBibMod) + m_pBibMod = OpenBibModul(); + + m_xDatMan = BibModul::createDataManager(); + BibDBDescriptor aBibDesc = BibModul::GetConfig()->GetBibliographyURL(); + + if(aBibDesc.sDataSource.isEmpty()) + { + DBChangeDialogConfig_Impl aConfig; + const Sequence<OUString> aSources = aConfig.GetDataSourceNames(); + if(aSources.hasElements()) + aBibDesc.sDataSource = aSources.getConstArray()[0]; + } + + m_xDatMan->createDatabaseForm( aBibDesc ); + + Reference<awt::XWindow> aWindow = rFrame->getContainerWindow(); + + VclPtr<vcl::Window> pParent = VCLUnoHelper::GetWindow( aWindow ); + + VclPtrInstance<BibBookContainer> pMyWindow( pParent ); + pMyWindow->Show(); + + VclPtrInstance< ::bib::BibView> pView( pMyWindow, m_xDatMan.get(), WB_VSCROLL | WB_HSCROLL | WB_3DLOOK ); + pView->Show(); + m_xDatMan->SetView( pView ); + + VclPtrInstance< ::bib::BibBeamer> pBeamer( pMyWindow, m_xDatMan.get() ); + pBeamer->Show(); + pMyWindow->createTopFrame(pBeamer); + + pMyWindow->createBottomFrame(pView); + + Reference< awt::XWindow > xWin ( pMyWindow->GetComponentInterface(), UNO_QUERY ); + + Reference< XController > xCtrRef( new BibFrameController_Impl( xWin, m_xDatMan.get() ) ); + + xCtrRef->attachFrame(rFrame); + rFrame->setComponent( xWin, xCtrRef); + pBeamer->SetXController(xCtrRef); + + if (aWindow) + { + // not earlier because SetFocus() is triggered in setVisible() + aWindow->setVisible(true); + } + + Reference<XLoadable>(m_xDatMan)->load(); + m_xDatMan->RegisterInterceptor(pBeamer); + + if ( rListener.is() ) + rListener->loadFinished( this ); + + // attach menu bar + Reference< XPropertySet > xPropSet( rFrame, UNO_QUERY ); + Reference< css::frame::XLayoutManager > xLayoutManager; + if ( xPropSet.is() ) + { + try + { + Any a = xPropSet->getPropertyValue("LayoutManager"); + a >>= xLayoutManager; + } + catch ( const uno::Exception& ) + { + } + } + + if ( xLayoutManager.is() ) + xLayoutManager->createElement( "private:resource/menubar/menubar" ); +} + +BibDataManager* BibliographyLoader::GetDataManager()const +{ + if(!m_xDatMan.is()) + { + if(!m_pBibMod) + const_cast< BibliographyLoader* >( this )->m_pBibMod = OpenBibModul(); + const_cast< BibliographyLoader* >( this )->m_xDatMan = BibModul::createDataManager(); + } + return m_xDatMan.get(); +} + +Reference< XNameAccess > const & BibliographyLoader::GetDataColumns() const +{ + if (!m_xColumns.is()) + { + Reference< XMultiServiceFactory > xMgr = comphelper::getProcessServiceFactory(); + Reference< XRowSet > xRowSet(xMgr->createInstance("com.sun.star.sdb.RowSet"), UNO_QUERY); + Reference< XPropertySet > xResultSetProps(xRowSet, UNO_QUERY); + DBG_ASSERT(xResultSetProps.is() , "BibliographyLoader::GetDataCursor : invalid row set (no XResultSet or no XPropertySet) !"); + + BibDBDescriptor aBibDesc = BibModul::GetConfig()->GetBibliographyURL(); + + Any aBibUrlAny; aBibUrlAny <<= aBibDesc.sDataSource; + xResultSetProps->setPropertyValue("DataSourceName", aBibUrlAny); + Any aCommandType; aCommandType <<= aBibDesc.nCommandType; + xResultSetProps->setPropertyValue("CommandType", aCommandType); + Any aTableName; aTableName <<= aBibDesc.sTableOrQuery; + xResultSetProps->setPropertyValue("Command", aTableName); + Any aResultSetType; aResultSetType <<= sal_Int32(ResultSetType::SCROLL_INSENSITIVE); + xResultSetProps->setPropertyValue("ResultSetType", aResultSetType); + Any aResultSetCurrency; aResultSetCurrency <<= sal_Int32(ResultSetConcurrency::UPDATABLE); + xResultSetProps->setPropertyValue("ResultSetConcurrency", aResultSetCurrency); + + bool bSuccess = false; + try + { + xRowSet->execute(); + bSuccess = true; + } + catch(const SQLException&) + { + DBG_UNHANDLED_EXCEPTION("extensions.biblio"); + } + catch(const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.biblio"); + bSuccess = false; + } + + if (!bSuccess) + { + Reference< XComponent > xSetComp(xRowSet, UNO_QUERY); + if (xSetComp.is()) + xSetComp->dispose(); + xRowSet = nullptr; + } + else + const_cast<BibliographyLoader*>(this)->m_xCursor = xRowSet.get(); + + Reference< sdbcx::XColumnsSupplier > xSupplyCols(m_xCursor, UNO_QUERY); + if (xSupplyCols.is()) + const_cast<BibliographyLoader*>(this)->m_xColumns = xSupplyCols->getColumns(); + } + + return m_xColumns; +} + +Reference< sdb::XColumn > BibliographyLoader::GetIdentifierColumn() const +{ + BibDataManager* pDatMan = GetDataManager(); + Reference< XNameAccess > xColumns = GetDataColumns(); + OUString sIdentifierColumnName = pDatMan->GetIdentifierMapping(); + + Reference< sdb::XColumn > xReturn; + if (xColumns.is() && xColumns->hasByName(sIdentifierColumnName)) + { + xReturn.set(xColumns->getByName(sIdentifierColumnName), UNO_QUERY); + } + return xReturn; +} + +Reference< XResultSet > const & BibliographyLoader::GetDataCursor() const +{ + if (!m_xCursor.is()) + GetDataColumns(); + if (m_xCursor.is()) + m_xCursor->first(); + return m_xCursor; +} + +static OUString lcl_AddProperty(const Reference< XNameAccess >& xColumns, + const Mapping* pMapping, const OUString& rColumnName) +{ + OUString sColumnName(rColumnName); + if(pMapping) + { + for(const auto & aColumnPair : pMapping->aColumnPairs) + { + if(aColumnPair.sLogicalColumnName == rColumnName) + { + sColumnName = aColumnPair.sRealColumnName; + break; + } + } + } + OUString uColumnName(sColumnName); + OUString uRet; + Reference< sdb::XColumn > xCol; + if (xColumns->hasByName(uColumnName)) + xCol.set(xColumns->getByName(uColumnName), UNO_QUERY); + if (xCol.is()) + uRet = xCol->getString(); + return uRet; +} + +Any BibliographyLoader::getByName(const OUString& rName) +{ + Any aRet; + try + { + BibDataManager* pDatMan = GetDataManager(); + Reference< XResultSet > xCursor = GetDataCursor(); + Reference< sdbcx::XColumnsSupplier > xSupplyCols(xCursor, UNO_QUERY); + Reference< XNameAccess > xColumns; + if (!xSupplyCols.is()) + return aRet; + xColumns = xSupplyCols->getColumns(); + DBG_ASSERT(xSupplyCols.is(), "BibliographyLoader::getByName : invalid columns returned by the data cursor (may be the result set is not alive ?) !"); + if (!xColumns.is()) + return aRet; + + const OUString sIdentifierMapping = pDatMan->GetIdentifierMapping(); + Reference< sdb::XColumn > xColumn; + if (xColumns->hasByName(sIdentifierMapping)) + xColumn.set(xColumns->getByName(sIdentifierMapping), UNO_QUERY); + if (xColumn.is()) + { + do + { + if ((rName == xColumn->getString()) && !xColumn->wasNull()) + { + Sequence<PropertyValue> aPropSequ(COLUMN_COUNT); + PropertyValue* pValues = aPropSequ.getArray(); + BibConfig* pConfig = BibModul::GetConfig(); + BibDBDescriptor aBibDesc = BibModul::GetConfig()->GetBibliographyURL(); + const Mapping* pMapping = pConfig->GetMapping(aBibDesc); + for(sal_uInt16 nEntry = 0; nEntry < COLUMN_COUNT; nEntry++) + { + const OUString& sColName = pConfig->GetDefColumnName( + nEntry); + pValues[nEntry].Name = sColName; + pValues[nEntry].Value <<= lcl_AddProperty(xColumns, pMapping, sColName); + } + aRet <<= aPropSequ; + + break; + } + } + while(xCursor->next()); + } + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("extensions.biblio"); + } + return aRet; +} + +Sequence< OUString > BibliographyLoader::getElementNames() +{ + Sequence< OUString > aRet(10); + int nRealNameCount = 0; + try + { + Reference< XResultSet > xCursor(GetDataCursor()); + Reference< sdb::XColumn > xIdColumn(GetIdentifierColumn()); + if (xIdColumn.is()) // implies xCursor.is() + { + do + { + OUString sTemp = xIdColumn->getString(); + if (!sTemp.isEmpty() && !xIdColumn->wasNull()) + { + int nLen = aRet.getLength(); + if(nLen == nRealNameCount) + aRet.realloc(nLen + 10); + OUString* pArray = aRet.getArray(); + pArray[nRealNameCount] = sTemp; + nRealNameCount++; + } + } + while (xCursor->next()); + } + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("extensions.biblio"); + } + + aRet.realloc(nRealNameCount); + return aRet; +} + +sal_Bool BibliographyLoader::hasByName(const OUString& rName) +{ + bool bRet = false; + try + { + Reference< XResultSet > xCursor = GetDataCursor(); + Reference< sdb::XColumn > xIdColumn = GetIdentifierColumn(); + + if (xIdColumn.is()) // implies xCursor.is() + { + do + { + OUString sCurrentId = xIdColumn->getString(); + if (!xIdColumn->wasNull() && rName.startsWith(sCurrentId)) + { + bRet = true; + break; + } + } + while(xCursor->next()); + } + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("extensions.biblio"); + } + return bRet; +} + +Type BibliographyLoader::getElementType() +{ + return cppu::UnoType<Sequence<PropertyValue>>::get(); +} + +sal_Bool BibliographyLoader::hasElements() +{ + Reference< XNameAccess > xColumns = GetDataColumns(); + return xColumns.is() && xColumns->getElementNames().hasElements(); +} + +Reference< XPropertySetInfo > BibliographyLoader::getPropertySetInfo() +{ + static const SfxItemPropertyMapEntry aBibProps_Impl[] = + { + { u"BibliographyDataFieldNames"_ustr, 0, cppu::UnoType<Sequence<PropertyValue>>::get(), PropertyAttribute::READONLY, 0}, + }; + static Reference< XPropertySetInfo > xRet = + SfxItemPropertySet(aBibProps_Impl).getPropertySetInfo(); + return xRet; +} + +void BibliographyLoader::setPropertyValue(const OUString& /*PropertyName*/, + const Any& /*aValue*/) +{ + throw UnknownPropertyException(); + //no changeable properties +} + +Any BibliographyLoader::getPropertyValue(const OUString& rPropertyName) +{ + Any aRet; + static const sal_uInt16 aInternalMapping[] = + { + IDENTIFIER_POS , // BibliographyDataField_IDENTIFIER + AUTHORITYTYPE_POS , // BibliographyDataField_BIBILIOGRAPHIC_TYPE + ADDRESS_POS , // BibliographyDataField_ADDRESS + ANNOTE_POS , // BibliographyDataField_ANNOTE + AUTHOR_POS , // BibliographyDataField_AUTHOR + BOOKTITLE_POS , // BibliographyDataField_BOOKTITLE + CHAPTER_POS , // BibliographyDataField_CHAPTER + EDITION_POS , // BibliographyDataField_EDITION + EDITOR_POS , // BibliographyDataField_EDITOR + HOWPUBLISHED_POS , // BibliographyDataField_HOWPUBLISHED + INSTITUTION_POS , // BibliographyDataField_INSTITUTION + JOURNAL_POS , // BibliographyDataField_JOURNAL + MONTH_POS , // BibliographyDataField_MONTH + NOTE_POS , // BibliographyDataField_NOTE + NUMBER_POS , // BibliographyDataField_NUMBER + ORGANIZATIONS_POS , // BibliographyDataField_ORGANIZATIONS + PAGES_POS , // BibliographyDataField_PAGES + PUBLISHER_POS , // BibliographyDataField_PUBLISHER + SCHOOL_POS , // BibliographyDataField_SCHOOL + SERIES_POS , // BibliographyDataField_SERIES + TITLE_POS , // BibliographyDataField_TITLE + REPORTTYPE_POS , // BibliographyDataField_REPORT_TYPE + VOLUME_POS , // BibliographyDataField_VOLUME + YEAR_POS , // BibliographyDataField_YEAR + URL_POS , // BibliographyDataField_URL + CUSTOM1_POS , // BibliographyDataField_CUSTOM1 + CUSTOM2_POS , // BibliographyDataField_CUSTOM2 + CUSTOM3_POS , // BibliographyDataField_CUSTOM3 + CUSTOM4_POS , // BibliographyDataField_CUSTOM4 + CUSTOM5_POS , // BibliographyDataField_CUSTOM5 + ISBN_POS , // BibliographyDataField_ISBN + LOCAL_URL_POS // BibliographyDataField_LOCAL_URL + }; + if(rPropertyName != "BibliographyDataFieldNames") + throw UnknownPropertyException(rPropertyName); + Sequence<PropertyValue> aSeq(COLUMN_COUNT); + PropertyValue* pArray = aSeq.getArray(); + BibConfig* pConfig = BibModul::GetConfig(); + for(sal_uInt16 i = 0; i <= text::BibliographyDataField::LOCAL_URL ; i++) + { + pArray[i].Name = pConfig->GetDefColumnName(aInternalMapping[i]); + pArray[i].Value <<= static_cast<sal_Int16>(i); + } + aRet <<= aSeq; + return aRet; +} + +void BibliographyLoader::addPropertyChangeListener( + const OUString& /*PropertyName*/, const Reference< XPropertyChangeListener > & /*aListener*/) +{ + //no bound properties +} + +void BibliographyLoader::removePropertyChangeListener( + const OUString& /*PropertyName*/, const Reference< XPropertyChangeListener > & /*aListener*/) +{ + //no bound properties +} + +void BibliographyLoader::addVetoableChangeListener( + const OUString& /*PropertyName*/, const Reference< XVetoableChangeListener > & /*aListener*/) +{ + //no vetoable properties +} + +void BibliographyLoader::removeVetoableChangeListener( + const OUString& /*PropertyName*/, const Reference< XVetoableChangeListener > & /*aListener*/) +{ + //no vetoable properties +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/bibmod.cxx b/extensions/source/bibliography/bibmod.cxx new file mode 100644 index 0000000000..3a6d066720 --- /dev/null +++ b/extensions/source/bibliography/bibmod.cxx @@ -0,0 +1,89 @@ +/* -*- 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 <unotools/resmgr.hxx> + +#include "bibmod.hxx" +#include "bibresid.hxx" +#include "datman.hxx" +#include "bibconfig.hxx" +#include <ucbhelper/content.hxx> + +static BibModul* pBibModul=nullptr; +static sal_uInt32 nBibModulCount=0; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::ucb; + +HdlBibModul OpenBibModul() +{ + if(pBibModul==nullptr) + { + pBibModul=new BibModul(); + } + nBibModulCount++; + return &pBibModul; +} + +void CloseBibModul(HdlBibModul ppBibModul) +{ + nBibModulCount--; + if(nBibModulCount==0 && ppBibModul!=nullptr) + { + delete pBibModul; + pBibModul=nullptr; + } +} + +OUString BibResId(TranslateId aId) +{ + return Translate::get(aId, pBibModul->GetResLocale()); +} + +BibConfig* BibModul::pBibConfig = nullptr; + +BibModul::BibModul() + : m_aResLocale(Translate::Create("pcr")) +{ +} + +BibModul::~BibModul() +{ + if (pBibConfig && pBibConfig->IsModified()) + pBibConfig->Commit(); + delete pBibConfig; + pBibConfig = nullptr; +} + +rtl::Reference<BibDataManager> BibModul::createDataManager() +{ + return new BibDataManager(); +} + +BibConfig* BibModul::GetConfig() +{ + if(! pBibConfig) + pBibConfig = new BibConfig; + return pBibConfig; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/bibmod.hxx b/extensions/source/bibliography/bibmod.hxx new file mode 100644 index 0000000000..a695f93d6c --- /dev/null +++ b/extensions/source/bibliography/bibmod.hxx @@ -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 . + */ + +#pragma once + +#include <locale> +#include <rtl/ref.hxx> + +class BibDataManager; +class BibConfig; + +class BibModul +{ + private: + std::locale m_aResLocale; + static BibConfig* pBibConfig; + + public: + BibModul(); + ~BibModul(); + + const std::locale& GetResLocale() const { return m_aResLocale; } + static BibConfig* GetConfig(); + + static rtl::Reference<BibDataManager> createDataManager(); + +}; + +typedef BibModul** HdlBibModul; + +HdlBibModul OpenBibModul(); +void CloseBibModul(HdlBibModul ppBibModul); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/bibresid.hxx b/extensions/source/bibliography/bibresid.hxx new file mode 100644 index 0000000000..6e17e76f2a --- /dev/null +++ b/extensions/source/bibliography/bibresid.hxx @@ -0,0 +1,27 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <unotools/resmgr.hxx> + +OUString BibResId(TranslateId aId); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/bibshortcuthandler.hxx b/extensions/source/bibliography/bibshortcuthandler.hxx new file mode 100644 index 0000000000..79563543bd --- /dev/null +++ b/extensions/source/bibliography/bibshortcuthandler.hxx @@ -0,0 +1,66 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/window.hxx> +#include <vcl/splitwin.hxx> + +// additional classes to handle shortcuts +// code in bibcont.cxx + + +class BibShortCutHandler +{ +private: + VclPtr<vcl::Window> pBaseClass; // in cases, where BibShortCutHandler also has to be a window + +protected: + explicit BibShortCutHandler( vcl::Window* pBaseClass ); + +public: + virtual ~BibShortCutHandler(); + virtual bool HandleShortCutKey( const KeyEvent& rKeyEvent ); // returns true, if key was handled + + inline vcl::Window* GetWindow(); +}; + +inline BibShortCutHandler::BibShortCutHandler( vcl::Window* _pBaseClass ) : pBaseClass( _pBaseClass ) +{ +} + +inline vcl::Window* BibShortCutHandler::GetWindow() +{ + return pBaseClass; +} + +class BibWindow : public vcl::Window, public BibShortCutHandler +{ +public: + BibWindow( vcl::Window* pParent, WinBits nStyle); + virtual ~BibWindow() override; +}; + +class BibSplitWindow : public SplitWindow, public BibShortCutHandler +{ +public: + BibSplitWindow( vcl::Window* pParent, WinBits nStyle); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/bibtools.hxx b/extensions/source/bibliography/bibtools.hxx new file mode 100644 index 0000000000..9b0d388fa5 --- /dev/null +++ b/extensions/source/bibliography/bibtools.hxx @@ -0,0 +1,42 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/window.hxx> + +namespace bib +{ + // source in bibbeam.cxx + + void HandleTaskPaneList( vcl::Window* pWindow, bool bAddToList ); + // pWindow: just an system window or something which is child of a system window + + inline void AddToTaskPaneList( vcl::Window* pWindowToBeHandled ) + { + HandleTaskPaneList( pWindowToBeHandled, true ); + } + + inline void RemoveFromTaskPaneList( vcl::Window* pWindowToBeHandled ) + { + HandleTaskPaneList( pWindowToBeHandled, false ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/bibview.cxx b/extensions/source/bibliography/bibview.cxx new file mode 100644 index 0000000000..b175fb78a5 --- /dev/null +++ b/extensions/source/bibliography/bibview.cxx @@ -0,0 +1,189 @@ +/* -*- 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 <strings.hrc> +#include "general.hxx" +#include "bibview.hxx" +#include "datman.hxx" +#include "bibresid.hxx" +#include "bibmod.hxx" +#include "bibconfig.hxx" + + +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::form; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; + +namespace +{ + class MessageWithCheck : public weld::MessageDialogController + { + private: + std::unique_ptr<weld::CheckButton> m_xWarningOnBox; + public: + MessageWithCheck(weld::Window *pParent) + : MessageDialogController(pParent, "modules/sbibliography/ui/querydialog.ui", "QueryDialog", "ask") + , m_xWarningOnBox(m_xBuilder->weld_check_button("ask")) + { + } + bool get_active() const { return m_xWarningOnBox->get_active(); } + }; +} + +namespace bib +{ + + + BibView::BibView( vcl::Window* _pParent, BibDataManager* _pManager, WinBits _nStyle ) + :BibWindow( _pParent, _nStyle ) + ,m_pDatMan( _pManager ) + ,m_xDatMan( _pManager ) + ,m_pGeneralPage( nullptr ) + ,m_aFormControlContainer(this) + { + if ( m_xDatMan.is() ) + m_aFormControlContainer.connectForm( m_xDatMan ); + } + + + BibView::~BibView() + { + disposeOnce(); + } + + void BibView::dispose() + { + VclPtr<BibGeneralPage> pGeneralPage = m_pGeneralPage; + m_pGeneralPage.clear(); + pGeneralPage.disposeAndClear(); // dispose will commit any uncommitted weld::Entry changes + + if ( m_aFormControlContainer.isFormConnected() ) + m_aFormControlContainer.disconnectForm(); + + BibWindow::dispose(); + } + + void BibView::UpdatePages() + { + // TODO: + // this is _strange_: Why not updating the existent general page? + // I consider the current behaviour a HACK. + if ( m_pGeneralPage ) + { + m_pGeneralPage->Hide(); + m_pGeneralPage.disposeAndClear(); + } + + m_pGeneralPage = VclPtr<BibGeneralPage>::Create( this, m_pDatMan ); + m_pGeneralPage->Show(); + + if( HasFocus() ) + // "delayed" GetFocus() because GetFocus() is initially called before GeneralPage is created + m_pGeneralPage->GrabFocus(); + + OUString sErrorString( m_pGeneralPage->GetErrorString() ); + if ( sErrorString.isEmpty() ) + return; + + bool bExecute = BibModul::GetConfig()->IsShowColumnAssignmentWarning(); + if(!m_pDatMan->HasActiveConnection()) + { + //no connection is available -> the data base has to be assigned + m_pDatMan->DispatchDBChangeDialog(); + bExecute = false; + } + else if(bExecute) + { + sErrorString += "\n" + BibResId(RID_MAP_QUESTION); + + MessageWithCheck aQueryBox(GetFrameWeld()); + aQueryBox.set_primary_text(sErrorString); + + short nResult = aQueryBox.run(); + BibModul::GetConfig()->SetShowColumnAssignmentWarning(!aQueryBox.get_active()); + + if( RET_YES != nResult ) + { + bExecute = false; + } + } + if(bExecute) + { + Application::PostUserEvent( LINK( this, BibView, CallMappingHdl ), nullptr, true ); + } + } + + BibViewFormControlContainer::BibViewFormControlContainer(BibView *pBibView) : mpBibView(pBibView) {} + + void BibViewFormControlContainer::_loaded( const EventObject& _rEvent ) + { + mpBibView->UpdatePages(); + FormControlContainer::_loaded( _rEvent ); + mpBibView->Resize(); + } + + void BibViewFormControlContainer::_reloaded( const EventObject& _rEvent ) + { + mpBibView->UpdatePages(); + FormControlContainer::_loaded( _rEvent ); + mpBibView->Resize(); + } + + IMPL_LINK_NOARG( BibView, CallMappingHdl, void*, void) + { + m_pDatMan->CreateMappingDialog(GetFrameWeld()); + } + + void BibView::Resize() + { + if ( m_pGeneralPage ) + { + ::Size aSz( GetOutputSizePixel() ); + m_pGeneralPage->SetSizePixel( aSz ); + } + Window::Resize(); + } + + Reference< awt::XControlContainer > BibViewFormControlContainer::getControlContainer() + { + return nullptr; + } + + void BibView::GetFocus() + { + if( m_pGeneralPage ) + m_pGeneralPage->GrabFocus(); + } + + bool BibView::HandleShortCutKey( const KeyEvent& rKeyEvent ) + { + return m_pGeneralPage && m_pGeneralPage->HandleShortCutKey( rKeyEvent ); + } + + +} // namespace bib + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/bibview.hxx b/extensions/source/bibliography/bibview.hxx new file mode 100644 index 0000000000..3bed8ed134 --- /dev/null +++ b/extensions/source/bibliography/bibview.hxx @@ -0,0 +1,83 @@ +/* -*- 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 . + */ +#pragma once + +#include <com/sun/star/awt/XControlContainer.hpp> +#include "formcontrolcontainer.hxx" +#include "bibshortcuthandler.hxx" + +class BibGeneralPage; +class BibDataManager; + +namespace com::sun::star::awt{ class XFocusListener;} + +namespace bib +{ + class BibView; + class BibViewFormControlContainer : public FormControlContainer + { + private: + VclPtr<BibView> mpBibView; + protected: + // FormControlContainer + virtual css::uno::Reference< css::awt::XControlContainer > + getControlContainer() override; + // XLoadListener equivalents + virtual void _loaded( const css::lang::EventObject& _rEvent ) override; + virtual void _reloaded( const css::lang::EventObject& _rEvent ) override; + public: + using FormControlContainer::connectForm; + using FormControlContainer::disconnectForm; + using FormControlContainer::isFormConnected; + explicit BibViewFormControlContainer(BibView *pBibView); + }; + + class BibView : public BibWindow + { + private: + BibDataManager* m_pDatMan; + css::uno::Reference< css::form::XLoadable> m_xDatMan; + VclPtr<BibGeneralPage> m_pGeneralPage; + BibViewFormControlContainer m_aFormControlContainer; + + private: + DECL_LINK(CallMappingHdl, void*, void); + + public: + // Window overridables + virtual void Resize() override; + + public: + BibView( vcl::Window* _pParent, BibDataManager* _pDatMan, WinBits nStyle ); + virtual ~BibView() override; + virtual void dispose() override; + + void UpdatePages(); + + virtual void GetFocus() override; + + virtual bool HandleShortCutKey( const KeyEvent& rKeyEvent ) override; // returns true, if key was handled + }; + + +} // namespace bib + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/datman.cxx b/extensions/source/bibliography/datman.cxx new file mode 100644 index 0000000000..a15a3e889c --- /dev/null +++ b/extensions/source/bibliography/datman.cxx @@ -0,0 +1,1395 @@ +/* -*- 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/any.hxx> +#include <sal/log.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/sdbc/ResultSetType.hpp> +#include <com/sun/star/sdbc/ResultSetConcurrency.hpp> +#include <com/sun/star/sdbcx/XRowLocate.hpp> +#include <com/sun/star/sdbc/DataType.hpp> +#include <com/sun/star/sdb/XSingleSelectQueryComposer.hpp> +#include <com/sun/star/sdbc/XDatabaseMetaData.hpp> +#include <com/sun/star/sdbc/XDataSource.hpp> +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/sdb/DatabaseContext.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdb/XCompletedConnection.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/form/ListSourceType.hpp> +#include <com/sun/star/form/XLoadable.hpp> +#include <com/sun/star/form/runtime/FormController.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <com/sun/star/form/XGridColumnFactory.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <tools/debug.hxx> +#include <tools/urlobj.hxx> +#include <vcl/weld.hxx> +#include "datman.hxx" +#include "bibresid.hxx" +#include "bibmod.hxx" +#include "bibview.hxx" +#include "toolbar.hxx" +#include "bibconfig.hxx" +#include "bibbeam.hxx" +#include "general.hxx" +#include <strings.hrc> +#include <helpids.h> +#include <connectivity/dbtools.hxx> +#include <memory> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +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::form; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::lang; + +// PropertyNames +constexpr OUStringLiteral FM_PROP_LABEL = u"Label"; +constexpr OUString FM_PROP_CONTROLSOURCE = u"DataField"_ustr; +constexpr OUStringLiteral FM_PROP_NAME = u"Name"; + +static Reference< XConnection > getConnection(const OUString& _rURL) +{ + // first get the sdb::DataSource corresponding to the url + Reference< XDataSource > xDataSource; + // is it a favorite title ? + Reference<XComponentContext> xContext = comphelper::getProcessComponentContext(); + Reference< XDatabaseContext > xNamingContext = DatabaseContext::create(xContext); + if (xNamingContext->hasByName(_rURL)) + { + DBG_ASSERT(xNamingContext.is(), "::getDataSource : no NamingService interface on the sdb::DatabaseAccessContext !"); + try + { + xDataSource.set(xNamingContext->getRegisteredObject(_rURL), UNO_QUERY); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.biblio", ""); + } + } + // build the connection from the data source + Reference< XConnection > xConn; + if (xDataSource.is()) + { + // need user/pwd for this + Reference< XCompletedConnection > xComplConn(xDataSource, UNO_QUERY); + try + { + Reference<task::XInteractionHandler> xIHdl( task::InteractionHandler::createWithParent(xContext, nullptr), UNO_QUERY_THROW); + xConn = xComplConn->connectWithCompletion(xIHdl); + } + catch (const SQLException&) + { + // TODO : a real error handling + } + catch (const Exception&) + { + } + } + return xConn; +} + +static Reference< XConnection > getConnection(const Reference< XInterface > & xRowSet) +{ + Reference< XConnection > xConn; + try + { + Reference< XPropertySet > xFormProps(xRowSet, UNO_QUERY); + if (!xFormProps.is()) + return xConn; + + xConn.set(xFormProps->getPropertyValue("ActiveConnection"), UNO_QUERY); + if (!xConn.is()) + { + SAL_INFO("extensions.biblio", "no active connection"); + } + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.biblio", ""); + } + + return xConn; +} + +static Reference< XNameAccess > getColumns(const Reference< XForm > & _rxForm) +{ + Reference< XNameAccess > xReturn; + // check if the form is alive + Reference< XColumnsSupplier > xSupplyCols( _rxForm, UNO_QUERY ); + if (xSupplyCols.is()) + xReturn = xSupplyCols->getColumns(); + + if (!xReturn.is() || !xReturn->getElementNames().hasElements()) + { // no... + xReturn = nullptr; + // -> get the table the form is bound to and ask it for their columns + Reference< XTablesSupplier > xSupplyTables( getConnection( _rxForm ), UNO_QUERY ); + Reference< XPropertySet > xFormProps( _rxForm, UNO_QUERY ); + if (xFormProps.is() && xSupplyTables.is()) + { + try + { + DBG_ASSERT(*o3tl::forceAccess<sal_Int32>(xFormProps->getPropertyValue("CommandType")) == CommandType::TABLE, + "::getColumns : invalid form (has no table as data source) !"); + OUString sTable; + xFormProps->getPropertyValue("Command") >>= sTable; + Reference< XNameAccess > xTables = xSupplyTables->getTables(); + if (xTables.is() && xTables->hasByName(sTable)) + xSupplyCols.set(xTables->getByName(sTable), UNO_QUERY); + if (xSupplyCols.is()) + xReturn = xSupplyCols->getColumns(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "extensions.biblio", "::getColumns"); + } + + } + } + return xReturn; +} + +namespace { + +class MappingDialog_Impl : public weld::GenericDialogController +{ + BibDataManager* pDatMan; + + OUString sNone; + bool bModified; + + std::unique_ptr<weld::Button> m_xOKBT; + std::unique_ptr<weld::ComboBox> m_xIdentifierLB; + std::unique_ptr<weld::ComboBox> m_xAuthorityTypeLB; + std::unique_ptr<weld::ComboBox> m_xAuthorLB; + std::unique_ptr<weld::ComboBox> m_xTitleLB; + std::unique_ptr<weld::ComboBox> m_xMonthLB; + std::unique_ptr<weld::ComboBox> m_xYearLB; + std::unique_ptr<weld::ComboBox> m_xISBNLB; + std::unique_ptr<weld::ComboBox> m_xBooktitleLB; + std::unique_ptr<weld::ComboBox> m_xChapterLB; + std::unique_ptr<weld::ComboBox> m_xEditionLB; + std::unique_ptr<weld::ComboBox> m_xEditorLB; + std::unique_ptr<weld::ComboBox> m_xHowpublishedLB; + std::unique_ptr<weld::ComboBox> m_xInstitutionLB; + std::unique_ptr<weld::ComboBox> m_xJournalLB; + std::unique_ptr<weld::ComboBox> m_xNoteLB; + std::unique_ptr<weld::ComboBox> m_xAnnoteLB; + std::unique_ptr<weld::ComboBox> m_xNumberLB; + std::unique_ptr<weld::ComboBox> m_xOrganizationsLB; + std::unique_ptr<weld::ComboBox> m_xPagesLB; + std::unique_ptr<weld::ComboBox> m_xPublisherLB; + std::unique_ptr<weld::ComboBox> m_xAddressLB; + std::unique_ptr<weld::ComboBox> m_xSchoolLB; + std::unique_ptr<weld::ComboBox> m_xSeriesLB; + std::unique_ptr<weld::ComboBox> m_xReportTypeLB; + std::unique_ptr<weld::ComboBox> m_xVolumeLB; + std::unique_ptr<weld::ComboBox> m_xURLLB; + std::unique_ptr<weld::ComboBox> m_xCustom1LB; + std::unique_ptr<weld::ComboBox> m_xCustom2LB; + std::unique_ptr<weld::ComboBox> m_xCustom3LB; + std::unique_ptr<weld::ComboBox> m_xCustom4LB; + std::unique_ptr<weld::ComboBox> m_xCustom5LB; + std::unique_ptr<weld::ComboBox> m_xLocalURLLB; + weld::ComboBox* aListBoxes[COLUMN_COUNT]; + + DECL_LINK(OkHdl, weld::Button&, void); + DECL_LINK(ListBoxSelectHdl, weld::ComboBox&, void); + +public: + MappingDialog_Impl(weld::Window* pParent, BibDataManager* pDatMan); +}; + +} + +static sal_uInt16 lcl_FindLogicalName(BibConfig const * pConfig , + std::u16string_view rLogicalColumnName) +{ + for(sal_uInt16 i = 0; i < COLUMN_COUNT; i++) + { + if(rLogicalColumnName == pConfig->GetDefColumnName(i)) + return i; + } + return USHRT_MAX; +} + +MappingDialog_Impl::MappingDialog_Impl(weld::Window* pParent, BibDataManager* pMan) + : GenericDialogController(pParent, "modules/sbibliography/ui/mappingdialog.ui", "MappingDialog") + , pDatMan(pMan) + , sNone(BibResId(RID_BIB_STR_NONE)) + , bModified(false) + , m_xOKBT(m_xBuilder->weld_button("ok")) + , m_xIdentifierLB(m_xBuilder->weld_combo_box("identifierCombobox")) + , m_xAuthorityTypeLB(m_xBuilder->weld_combo_box("authorityTypeCombobox")) + , m_xAuthorLB(m_xBuilder->weld_combo_box("authorCombobox")) + , m_xTitleLB(m_xBuilder->weld_combo_box("titleCombobox")) + , m_xMonthLB(m_xBuilder->weld_combo_box("monthCombobox")) + , m_xYearLB(m_xBuilder->weld_combo_box("yearCombobox")) + , m_xISBNLB(m_xBuilder->weld_combo_box("ISBNCombobox")) + , m_xBooktitleLB(m_xBuilder->weld_combo_box("bookTitleCombobox")) + , m_xChapterLB(m_xBuilder->weld_combo_box("chapterCombobox")) + , m_xEditionLB(m_xBuilder->weld_combo_box("editionCombobox")) + , m_xEditorLB(m_xBuilder->weld_combo_box("editorCombobox")) + , m_xHowpublishedLB(m_xBuilder->weld_combo_box("howPublishedCombobox")) + , m_xInstitutionLB(m_xBuilder->weld_combo_box("institutionCombobox")) + , m_xJournalLB(m_xBuilder->weld_combo_box("journalCombobox")) + , m_xNoteLB(m_xBuilder->weld_combo_box("noteCombobox")) + , m_xAnnoteLB(m_xBuilder->weld_combo_box("annoteCombobox")) + , m_xNumberLB(m_xBuilder->weld_combo_box("numberCombobox")) + , m_xOrganizationsLB(m_xBuilder->weld_combo_box("organizationCombobox")) + , m_xPagesLB(m_xBuilder->weld_combo_box("pagesCombobox")) + , m_xPublisherLB(m_xBuilder->weld_combo_box("publisherCombobox")) + , m_xAddressLB(m_xBuilder->weld_combo_box("addressCombobox")) + , m_xSchoolLB(m_xBuilder->weld_combo_box("schoolCombobox")) + , m_xSeriesLB(m_xBuilder->weld_combo_box("seriesCombobox")) + , m_xReportTypeLB(m_xBuilder->weld_combo_box("reportTypeCombobox")) + , m_xVolumeLB(m_xBuilder->weld_combo_box("volumeCombobox")) + , m_xURLLB(m_xBuilder->weld_combo_box("URLCombobox")) + , m_xCustom1LB(m_xBuilder->weld_combo_box("custom1Combobox")) + , m_xCustom2LB(m_xBuilder->weld_combo_box("custom2Combobox")) + , m_xCustom3LB(m_xBuilder->weld_combo_box("custom3Combobox")) + , m_xCustom4LB(m_xBuilder->weld_combo_box("custom4Combobox")) + , m_xCustom5LB(m_xBuilder->weld_combo_box("custom5Combobox")) + , m_xLocalURLLB(m_xBuilder->weld_combo_box("LocalURLCombobox")) +{ + m_xOKBT->connect_clicked(LINK(this, MappingDialog_Impl, OkHdl)); + OUString sTitle = m_xDialog->get_title(); + sTitle = sTitle.replaceFirst("%1", pDatMan->getActiveDataTable()); + m_xDialog->set_title(sTitle); + + aListBoxes[0] = m_xIdentifierLB.get(); + aListBoxes[1] = m_xAuthorityTypeLB.get(); + aListBoxes[2] = m_xAuthorLB.get(); + aListBoxes[3] = m_xTitleLB.get(); + aListBoxes[4] = m_xYearLB.get(); + aListBoxes[5] = m_xISBNLB.get(); + aListBoxes[6] = m_xBooktitleLB.get(); + aListBoxes[7] = m_xChapterLB.get(); + aListBoxes[8] = m_xEditionLB.get(); + aListBoxes[9] = m_xEditorLB.get(); + aListBoxes[10] = m_xHowpublishedLB.get(); + aListBoxes[11] = m_xInstitutionLB.get(); + aListBoxes[12] = m_xJournalLB.get(); + aListBoxes[13] = m_xMonthLB.get(); + aListBoxes[14] = m_xNoteLB.get(); + aListBoxes[15] = m_xAnnoteLB.get(); + aListBoxes[16] = m_xNumberLB.get(); + aListBoxes[17] = m_xOrganizationsLB.get(); + aListBoxes[18] = m_xPagesLB.get(); + aListBoxes[19] = m_xPublisherLB.get(); + aListBoxes[20] = m_xAddressLB.get(); + aListBoxes[21] = m_xSchoolLB.get(); + aListBoxes[22] = m_xSeriesLB.get(); + aListBoxes[23] = m_xReportTypeLB.get(); + aListBoxes[24] = m_xVolumeLB.get(); + aListBoxes[25] = m_xURLLB.get(); + aListBoxes[26] = m_xCustom1LB.get(); + aListBoxes[27] = m_xCustom2LB.get(); + aListBoxes[28] = m_xCustom3LB.get(); + aListBoxes[29] = m_xCustom4LB.get(); + aListBoxes[30] = m_xCustom5LB.get(); + aListBoxes[31] = m_xLocalURLLB.get(); + + aListBoxes[0]->append_text(sNone); + Reference< XNameAccess > xFields = getColumns( pDatMan->getForm() ); + DBG_ASSERT(xFields.is(), "MappingDialog_Impl::MappingDialog_Impl : gave me an invalid form !"); + if (xFields.is()) + { + const Sequence<OUString> aFieldNames = xFields->getElementNames(); + for(const OUString& rName : aFieldNames) + aListBoxes[0]->append_text(rName); + } + + Link<weld::ComboBox&,void> aLnk = LINK(this, MappingDialog_Impl, ListBoxSelectHdl); + + aListBoxes[0]->set_active(0); + aListBoxes[0]->connect_changed(aLnk); + for(sal_uInt16 i = 1; i < COLUMN_COUNT; i++) + { + for(sal_Int32 j = 0, nEntryCount = aListBoxes[0]->get_count(); j < nEntryCount; ++j) + aListBoxes[i]->append_text(aListBoxes[0]->get_text(j)); + aListBoxes[i]->set_active(0); + aListBoxes[i]->connect_changed(aLnk); + } + BibConfig* pConfig = BibModul::GetConfig(); + BibDBDescriptor aDesc; + aDesc.sDataSource = pDatMan->getActiveDataSource(); + aDesc.sTableOrQuery = pDatMan->getActiveDataTable(); + aDesc.nCommandType = CommandType::TABLE; + const Mapping* pMapping = pConfig->GetMapping(aDesc); + if(pMapping) + { + for(const auto & aColumnPair : pMapping->aColumnPairs) + { + sal_uInt16 nListBoxIndex = lcl_FindLogicalName( pConfig, aColumnPair.sLogicalColumnName); + if(nListBoxIndex < COLUMN_COUNT) + { + aListBoxes[nListBoxIndex]->set_active_text(aColumnPair.sRealColumnName); + } + } + } +} + +IMPL_LINK(MappingDialog_Impl, ListBoxSelectHdl, weld::ComboBox&, rListBox, void) +{ + const sal_Int32 nEntryPos = rListBox.get_active(); + if (0 < nEntryPos) + { + for(auto & pListBoxe : aListBoxes) + { + if (&rListBox != pListBoxe && pListBoxe->get_active() == nEntryPos) + pListBoxe->set_active(0); + } + } + bModified = true; +} + +IMPL_LINK_NOARG(MappingDialog_Impl, OkHdl, weld::Button&, void) +{ + if(bModified) + { + Mapping aNew; + aNew.sTableName = pDatMan->getActiveDataTable(); + aNew.sURL = pDatMan->getActiveDataSource(); + + sal_uInt16 nWriteIndex = 0; + BibConfig* pConfig = BibModul::GetConfig(); + for(sal_uInt16 nEntry = 0; nEntry < COLUMN_COUNT; nEntry++) + { + OUString sSel = aListBoxes[nEntry]->get_active_text(); + if(sSel != sNone) + { + aNew.aColumnPairs[nWriteIndex].sRealColumnName = sSel; + aNew.aColumnPairs[nWriteIndex].sLogicalColumnName = pConfig->GetDefColumnName(nEntry); + nWriteIndex++; + } + } + BibDBDescriptor aDesc; + aDesc.sDataSource = pDatMan->getActiveDataSource(); + aDesc.sTableOrQuery = pDatMan->getActiveDataTable(); + aDesc.nCommandType = CommandType::TABLE; + pDatMan->ResetIdentifierMapping(); + pConfig->SetMapping(aDesc, &aNew); + } + m_xDialog->response(bModified ? RET_OK : RET_CANCEL); +} + +namespace { + +class DBChangeDialog_Impl : public weld::GenericDialogController +{ + DBChangeDialogConfig_Impl aConfig; + + std::unique_ptr<weld::TreeView> m_xSelectionLB; + + DECL_LINK(DoubleClickHdl, weld::TreeView&, bool); +public: + DBChangeDialog_Impl(weld::Window* pParent, const BibDataManager* pMan); + + OUString GetCurrentURL()const; +}; + +} + +DBChangeDialog_Impl::DBChangeDialog_Impl(weld::Window* pParent, const BibDataManager* pDatMan ) + : GenericDialogController(pParent, "modules/sbibliography/ui/choosedatasourcedialog.ui", "ChooseDataSourceDialog") + , m_xSelectionLB(m_xBuilder->weld_tree_view("treeview")) +{ + m_xSelectionLB->set_size_request(-1, m_xSelectionLB->get_height_rows(6)); + m_xSelectionLB->connect_row_activated(LINK(this, DBChangeDialog_Impl, DoubleClickHdl)); + m_xSelectionLB->make_sorted(); + + try + { + OUString sActiveSource = pDatMan->getActiveDataSource(); + for (const OUString& rSourceName : aConfig.GetDataSourceNames()) + m_xSelectionLB->append_text(rSourceName); + m_xSelectionLB->select_text(sActiveSource); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.biblio", ""); + } +} + +IMPL_LINK_NOARG(DBChangeDialog_Impl, DoubleClickHdl, weld::TreeView&, bool) +{ + m_xDialog->response(RET_OK); + return true; +} + +OUString DBChangeDialog_Impl::GetCurrentURL()const +{ + return m_xSelectionLB->get_selected_text(); +} + +// XDispatchProvider +BibInterceptorHelper::BibInterceptorHelper( const ::bib::BibBeamer* pBibBeamer, css::uno::Reference< css::frame::XDispatch > const & xDispatch) +{ + if( pBibBeamer ) + { + xInterception = pBibBeamer->getDispatchProviderInterception(); + if( xInterception.is() ) + xInterception->registerDispatchProviderInterceptor( this ); + } + if( xDispatch.is() ) + xFormDispatch = xDispatch; +} + +BibInterceptorHelper::~BibInterceptorHelper( ) +{ +} + +void BibInterceptorHelper::ReleaseInterceptor() +{ + if ( xInterception.is() ) + xInterception->releaseDispatchProviderInterceptor( this ); + xInterception.clear(); +} + +css::uno::Reference< css::frame::XDispatch > SAL_CALL + BibInterceptorHelper::queryDispatch( const css::util::URL& aURL, const OUString& aTargetFrameName, sal_Int32 nSearchFlags ) +{ + Reference< XDispatch > xReturn; + + OUString aCommand( aURL.Path ); + if ( aCommand == "FormSlots/ConfirmDeletion" ) + xReturn = xFormDispatch; + else + if ( xSlaveDispatchProvider.is() ) + xReturn = xSlaveDispatchProvider->queryDispatch( aURL, aTargetFrameName, nSearchFlags); + + return xReturn; +} + +css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL + BibInterceptorHelper::queryDispatches( const css::uno::Sequence< css::frame::DispatchDescriptor >& aDescripts ) +{ + Sequence< Reference< XDispatch> > aReturn( aDescripts.getLength() ); + Reference< XDispatch >* pReturn = aReturn.getArray(); + for ( const DispatchDescriptor& rDescript : aDescripts ) + { + *pReturn++ = queryDispatch( rDescript.FeatureURL, rDescript.FrameName, rDescript.SearchFlags ); + } + return aReturn; +} + +// XDispatchProviderInterceptor +css::uno::Reference< css::frame::XDispatchProvider > SAL_CALL + BibInterceptorHelper::getSlaveDispatchProvider( ) +{ + return xSlaveDispatchProvider; +} + +void SAL_CALL BibInterceptorHelper::setSlaveDispatchProvider( const css::uno::Reference< css::frame::XDispatchProvider >& xNewSlaveDispatchProvider ) +{ + xSlaveDispatchProvider = xNewSlaveDispatchProvider; +} + +css::uno::Reference< css::frame::XDispatchProvider > SAL_CALL + BibInterceptorHelper::getMasterDispatchProvider( ) +{ + return xMasterDispatchProvider; +} + +void SAL_CALL BibInterceptorHelper::setMasterDispatchProvider( const css::uno::Reference< css::frame::XDispatchProvider >& xNewMasterDispatchProvider ) +{ + xMasterDispatchProvider = xNewMasterDispatchProvider; +} + + +constexpr OUStringLiteral gGridName(u"theGrid"); + +BibDataManager::BibDataManager() + :pBibView( nullptr ) + ,pToolbar(nullptr) +{ +} + + +BibDataManager::~BibDataManager() +{ + Reference< XLoadable > xLoad( m_xForm, UNO_QUERY ); + Reference< XPropertySet > xPrSet( m_xForm, UNO_QUERY ); + Reference< XComponent > xComp( m_xForm, UNO_QUERY ); + if ( m_xForm.is() ) + { + Reference< XComponent > xConnection; + xPrSet->getPropertyValue("ActiveConnection") >>= xConnection; + if (xLoad.is()) + xLoad->unload(); + if (xComp.is()) + xComp->dispose(); + if(xConnection.is()) + xConnection->dispose(); + m_xForm = nullptr; + } + if( m_xInterceptorHelper.is() ) + { + m_xInterceptorHelper->ReleaseInterceptor(); + m_xInterceptorHelper.clear(); + } +} + +void BibDataManager::InsertFields(const Reference< XFormComponent > & _rxGrid) +{ + if ( !_rxGrid.is() ) + return; + + try + { + Reference< XNameContainer > xColContainer( _rxGrid, UNO_QUERY ); + // remove the old fields + if ( xColContainer->hasElements() ) + { + const Sequence<OUString> aOldNames = xColContainer->getElementNames(); + for ( const OUString& rName : aOldNames ) + xColContainer->removeByName( rName ); + } + + Reference< XNameAccess > xFields = getColumns( m_xForm ); + if (!xFields.is()) + return; + + Reference< XGridColumnFactory > xColFactory( _rxGrid, UNO_QUERY ); + + Reference< XPropertySet > xField; + + const Sequence<OUString> aFieldNames = xFields->getElementNames(); + for ( const OUString& rField : aFieldNames ) + { + xFields->getByName( rField ) >>= xField; + + OUString sCurrentModelType; + sal_Int32 nType = 0; + bool bIsFormatted = false; + bool bFormattedIsNumeric = true; + xField->getPropertyValue("Type") >>= nType; + switch(nType) + { + case DataType::BIT: + case DataType::BOOLEAN: + sCurrentModelType = "CheckBox"; + break; + + case DataType::BINARY: + case DataType::VARBINARY: + case DataType::LONGVARBINARY: + case DataType::BLOB: + sCurrentModelType = "TextField"; + break; + + case DataType::VARCHAR: + case DataType::LONGVARCHAR: + case DataType::CHAR: + case DataType::CLOB: + bFormattedIsNumeric = false; + [[fallthrough]]; + default: + sCurrentModelType = "FormattedField"; + bIsFormatted = true; + break; + } + + Reference< XPropertySet > xCurrentCol = xColFactory->createColumn(sCurrentModelType); + if (bIsFormatted) + { + OUString sFormatKey("FormatKey"); + xCurrentCol->setPropertyValue(sFormatKey, xField->getPropertyValue(sFormatKey)); + Any aFormatted(bFormattedIsNumeric); + xCurrentCol->setPropertyValue("TreatAsNumber", aFormatted); + } + Any aColName( rField ); + xCurrentCol->setPropertyValue(FM_PROP_CONTROLSOURCE, aColName); + xCurrentCol->setPropertyValue(FM_PROP_LABEL, aColName); + + xColContainer->insertByName( rField, Any( xCurrentCol ) ); + } + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.biblio", ""); + } +} + +Reference< awt::XControlModel > BibDataManager::updateGridModel() +{ + return updateGridModel( m_xForm ); +} + +Reference< awt::XControlModel > const & BibDataManager::updateGridModel(const Reference< XForm > & xDbForm) +{ + try + { + Reference< XPropertySet > aFormPropSet( xDbForm, UNO_QUERY ); + OUString sName; + aFormPropSet->getPropertyValue("Command") >>= sName; + + if ( !m_xGridModel.is() ) + { + m_xGridModel = createGridModel( gGridName ); + + Reference< XNameContainer > xNameCont(xDbForm, UNO_QUERY); + xNameCont->insertByName( sName, Any( m_xGridModel ) ); + } + + // insert the fields + Reference< XFormComponent > xFormComp( m_xGridModel, UNO_QUERY ); + InsertFields( xFormComp ); + } + catch (const Exception&) + { + OSL_FAIL("::updateGridModel: something went wrong !"); + } + + return m_xGridModel; +} + +Reference< XForm > BibDataManager::createDatabaseForm(BibDBDescriptor& rDesc) +{ + Reference< XForm > xResult; + try + { + Reference< XMultiServiceFactory > xMgr = comphelper::getProcessServiceFactory(); + m_xForm.set( xMgr->createInstance( "com.sun.star.form.component.Form" ), UNO_QUERY ); + + Reference< XPropertySet > aPropertySet( m_xForm, UNO_QUERY ); + + aDataSourceURL = rDesc.sDataSource; + if(aPropertySet.is()) + { + Any aVal; + aVal <<= sal_Int32(ResultSetType::SCROLL_INSENSITIVE); + aPropertySet->setPropertyValue("ResultSetType",aVal ); + aVal <<= sal_Int32(ResultSetConcurrency::READ_ONLY); + aPropertySet->setPropertyValue("ResultSetConcurrency", aVal); + + //Caching for Performance + aVal <<= sal_Int32(50); + aPropertySet->setPropertyValue("FetchSize", aVal); + + Reference< XConnection > xConnection = getConnection(rDesc.sDataSource); + aVal <<= xConnection; + aPropertySet->setPropertyValue("ActiveConnection", aVal); + + Reference< XTablesSupplier > xSupplyTables(xConnection, UNO_QUERY); + Reference< XNameAccess > xTables = xSupplyTables.is() ? + xSupplyTables->getTables() : Reference< XNameAccess > (); + + Sequence< OUString > aTableNameSeq; + if (xTables.is()) + aTableNameSeq = xTables->getElementNames(); + + if(aTableNameSeq.hasElements()) + { + if(!rDesc.sTableOrQuery.isEmpty()) + aActiveDataTable = rDesc.sTableOrQuery; + else + { + rDesc.sTableOrQuery = aActiveDataTable = aTableNameSeq[0]; + rDesc.nCommandType = CommandType::TABLE; + } + + aVal <<= aActiveDataTable; + aPropertySet->setPropertyValue("Command", aVal); + aVal <<= rDesc.nCommandType; + aPropertySet->setPropertyValue("CommandType", aVal); + + + Reference< XDatabaseMetaData > xMetaData = xConnection->getMetaData(); + aQuoteChar = xMetaData->getIdentifierQuoteString(); + + Reference< XMultiServiceFactory > xFactory(xConnection, UNO_QUERY); + if ( xFactory.is() ) + m_xParser.set( xFactory->createInstance("com.sun.star.sdb.SingleSelectQueryComposer"), UNO_QUERY ); + + OUString aString("SELECT * FROM "); + + OUString sCatalog, sSchema, sName; + ::dbtools::qualifiedNameComponents( xMetaData, aActiveDataTable, sCatalog, sSchema, sName, ::dbtools::EComposeRule::InDataManipulation ); + aString += ::dbtools::composeTableNameForSelect( xConnection, sCatalog, sSchema, sName ); + + m_xParser->setElementaryQuery(aString); + BibConfig* pConfig = BibModul::GetConfig(); + pConfig->setQueryField(getQueryField()); + startQueryWith(pConfig->getQueryText()); + + xResult = m_xForm; + } + } + } + catch (const Exception&) + { + OSL_FAIL("::createDatabaseForm: something went wrong !"); + } + + return xResult; +} + +Sequence< OUString > BibDataManager::getDataSources() const +{ + Sequence< OUString > aTableNameSeq; + + try + { + Reference< XTablesSupplier > xSupplyTables( getConnection( m_xForm ), UNO_QUERY ); + Reference< XNameAccess > xTables; + if (xSupplyTables.is()) + xTables = xSupplyTables->getTables(); + if (xTables.is()) + aTableNameSeq = xTables->getElementNames(); + } + catch (const Exception&) + { + OSL_FAIL("::getDataSources: something went wrong !"); + } + + return aTableNameSeq; +} + + +void BibDataManager::setFilter(const OUString& rQuery) +{ + if(!m_xParser.is()) + return; + try + { + m_xParser->setFilter( rQuery ); + OUString aQuery = m_xParser->getFilter(); + Reference< XPropertySet > xFormProps( m_xForm, UNO_QUERY_THROW ); + xFormProps->setPropertyValue( "Filter", Any( aQuery ) ); + xFormProps->setPropertyValue( "ApplyFilter", Any( true ) ); + reload(); + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("extensions.biblio"); + } + + +} + +OUString BibDataManager::getFilter() const +{ + + OUString aQueryString; + try + { + Reference< XPropertySet > xFormProps( m_xForm, UNO_QUERY_THROW ); + OSL_VERIFY( xFormProps->getPropertyValue( "Filter" ) >>= aQueryString ); + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("extensions.biblio"); + } + + + return aQueryString; + +} + +Sequence< OUString > BibDataManager::getQueryFields() const +{ + Sequence< OUString > aFieldSeq; + Reference< XNameAccess > xFields = getColumns( m_xForm ); + if (xFields.is()) + aFieldSeq = xFields->getElementNames(); + return aFieldSeq; +} + +OUString BibDataManager::getQueryField() const +{ + BibConfig* pConfig = BibModul::GetConfig(); + OUString aFieldString = pConfig->getQueryField(); + if(aFieldString.isEmpty()) + { + const Sequence< OUString > aSeq = getQueryFields(); + if(aSeq.hasElements()) + { + aFieldString=aSeq[0]; + } + } + return aFieldString; +} + +void BibDataManager::startQueryWith(const OUString& rQuery) +{ + BibConfig* pConfig = BibModul::GetConfig(); + pConfig->setQueryText( rQuery ); + + OUString aQueryString; + if(!rQuery.isEmpty()) + { + aQueryString=aQuoteChar + getQueryField() + aQuoteChar + " like '"; + OUString sQuery = rQuery.replaceAll("?","_").replaceAll("*","%"); + aQueryString += sQuery + "%'"; + } + setFilter(aQueryString); +} + +void BibDataManager::setActiveDataSource(const OUString& rURL) +{ + OUString sTmp(aDataSourceURL); + aDataSourceURL = rURL; + + Reference< XPropertySet > aPropertySet( m_xForm, UNO_QUERY ); + if(!aPropertySet.is()) + return; + + unload(); + + Reference< XComponent > xOldConnection; + aPropertySet->getPropertyValue("ActiveConnection") >>= xOldConnection; + + Reference< XConnection > xConnection = getConnection(rURL); + if(!xConnection.is()) + { + aDataSourceURL = sTmp; + return; + } + Any aVal; aVal <<= xConnection; + aPropertySet->setPropertyValue("ActiveConnection", aVal); + Reference< XMultiServiceFactory > xFactory(xConnection, UNO_QUERY); + if ( xFactory.is() ) + m_xParser.set( xFactory->createInstance("com.sun.star.sdb.SingleSelectQueryComposer"), UNO_QUERY ); + + if(xOldConnection.is()) + xOldConnection->dispose(); + + Sequence< OUString > aTableNameSeq; + Reference< XTablesSupplier > xSupplyTables(xConnection, UNO_QUERY); + if(xSupplyTables.is()) + { + Reference< XNameAccess > xAccess = xSupplyTables->getTables(); + aTableNameSeq = xAccess->getElementNames(); + } + if(aTableNameSeq.hasElements()) + { + aActiveDataTable = aTableNameSeq[0]; + aVal <<= aActiveDataTable; + aPropertySet->setPropertyValue("Command", aVal); + aPropertySet->setPropertyValue("CommandType", Any(CommandType::TABLE)); + //Caching for Performance + aVal <<= sal_Int32(50); + aPropertySet->setPropertyValue("FetchSize", aVal); + OUString aString("SELECT * FROM "); + // quote the table name which may contain catalog.schema.table + Reference<XDatabaseMetaData> xMetaData = xConnection->getMetaData(); + aQuoteChar = xMetaData->getIdentifierQuoteString(); + + OUString sCatalog, sSchema, sName; + ::dbtools::qualifiedNameComponents( xMetaData, aActiveDataTable, sCatalog, sSchema, sName, ::dbtools::EComposeRule::InDataManipulation ); + aString += ::dbtools::composeTableNameForSelect( xConnection, sCatalog, sSchema, sName ); + + m_xParser->setElementaryQuery(aString); + BibConfig* pConfig = BibModul::GetConfig(); + pConfig->setQueryField(getQueryField()); + startQueryWith(pConfig->getQueryText()); + setActiveDataTable(aActiveDataTable); + } + FeatureStateEvent aEvent; + util::URL aURL; + aEvent.IsEnabled = true; + aEvent.Requery = false; + aEvent.FeatureDescriptor = getActiveDataTable(); + + aEvent.State <<= getDataSources(); + + if(pToolbar) + { + aURL.Complete =".uno:Bib/source"; + aEvent.FeatureURL = aURL; + pToolbar->statusChanged( aEvent ); + } + + updateGridModel(); + load(); +} + + +void BibDataManager::setActiveDataTable(const OUString& rTable) +{ + ResetIdentifierMapping(); + try + { + Reference< XPropertySet > aPropertySet( m_xForm, UNO_QUERY ); + + if(aPropertySet.is()) + { + Reference< XConnection > xConnection = getConnection( m_xForm ); + Reference< XTablesSupplier > xSupplyTables(xConnection, UNO_QUERY); + Reference< XNameAccess > xAccess = xSupplyTables->getTables(); + Sequence< OUString > aTableNameSeq = xAccess->getElementNames(); + sal_uInt32 nCount = aTableNameSeq.getLength(); + + const OUString* pTableNames = aTableNameSeq.getConstArray(); + const OUString* pTableNamesEnd = pTableNames + nCount; + + for ( ; pTableNames != pTableNamesEnd; ++pTableNames ) + { + if ( rTable == *pTableNames ) + { + aActiveDataTable = rTable; + Any aVal; aVal <<= rTable; + aPropertySet->setPropertyValue( "Command", aVal ); + break; + } + } + if (pTableNames != pTableNamesEnd) + { + Reference< XDatabaseMetaData > xMetaData = xConnection->getMetaData(); + aQuoteChar = xMetaData->getIdentifierQuoteString(); + + Reference< XMultiServiceFactory > xFactory(xConnection, UNO_QUERY); + if ( xFactory.is() ) + m_xParser.set( xFactory->createInstance("com.sun.star.sdb.SingleSelectQueryComposer"), UNO_QUERY ); + + OUString aString("SELECT * FROM "); + + OUString sCatalog, sSchema, sName; + ::dbtools::qualifiedNameComponents( xMetaData, aActiveDataTable, sCatalog, sSchema, sName, ::dbtools::EComposeRule::InDataManipulation ); + aString += ::dbtools::composeTableNameForSelect( xConnection, sCatalog, sSchema, sName ); + + m_xParser->setElementaryQuery(aString); + + BibConfig* pConfig = BibModul::GetConfig(); + pConfig->setQueryField(getQueryField()); + startQueryWith(pConfig->getQueryText()); + + BibDBDescriptor aDesc; + aDesc.sDataSource = aDataSourceURL; + aDesc.sTableOrQuery = aActiveDataTable; + aDesc.nCommandType = CommandType::TABLE; + BibModul::GetConfig()->SetBibliographyURL(aDesc); + } + } + } + catch (const Exception&) + { + OSL_FAIL("::setActiveDataTable: something went wrong !"); + } +} + + +void SAL_CALL BibDataManager::load( ) +{ + if ( isLoaded() ) + // nothing to do + return; + + Reference< XLoadable > xFormAsLoadable( m_xForm, UNO_QUERY ); + DBG_ASSERT( xFormAsLoadable.is() || !m_xForm.is(), "BibDataManager::load: invalid form!"); + if ( xFormAsLoadable.is() ) + { + xFormAsLoadable->load(); + + std::unique_lock g(m_aMutex); + EventObject aEvt( static_cast< XWeak* >( this ) ); + m_aLoadListeners.notifyEach( g, &XLoadListener::loaded, aEvt ); + } +} + + +void SAL_CALL BibDataManager::unload( ) +{ + if ( !isLoaded() ) + // nothing to do + return; + + Reference< XLoadable >xFormAsLoadable( m_xForm, UNO_QUERY ); + DBG_ASSERT( xFormAsLoadable.is() || !m_xForm.is(), "BibDataManager::unload: invalid form!"); + if ( !xFormAsLoadable.is() ) + return; + + EventObject aEvt( static_cast< XWeak* >( this ) ); + + { + std::unique_lock g(m_aMutex); + m_aLoadListeners.notifyEach( g, &XLoadListener::unloading, aEvt ); + } + + xFormAsLoadable->unload(); + + { + std::unique_lock g(m_aMutex); + m_aLoadListeners.notifyEach( g, &XLoadListener::unloaded, aEvt ); + } +} + + +void SAL_CALL BibDataManager::reload( ) +{ + if ( !isLoaded() ) + // nothing to do + return; + + Reference< XLoadable >xFormAsLoadable( m_xForm, UNO_QUERY ); + DBG_ASSERT( xFormAsLoadable.is() || !m_xForm.is(), "BibDataManager::unload: invalid form!"); + if ( !xFormAsLoadable.is() ) + return; + + EventObject aEvt( static_cast< XWeak* >( this ) ); + + { + std::unique_lock g(m_aMutex); + m_aLoadListeners.notifyEach( g, &XLoadListener::reloading, aEvt ); + } + + xFormAsLoadable->reload(); + + { + std::unique_lock g(m_aMutex); + m_aLoadListeners.notifyEach( g, &XLoadListener::reloaded, aEvt ); + } +} + + +sal_Bool SAL_CALL BibDataManager::isLoaded( ) +{ + Reference< XLoadable >xFormAsLoadable( m_xForm, UNO_QUERY ); + DBG_ASSERT( xFormAsLoadable.is() || !m_xForm.is(), "BibDataManager::isLoaded: invalid form!"); + + bool bLoaded = false; + if ( xFormAsLoadable.is() ) + bLoaded = xFormAsLoadable->isLoaded(); + return bLoaded; +} + + +void SAL_CALL BibDataManager::addLoadListener( const Reference< XLoadListener >& aListener ) +{ + std::unique_lock g(m_aMutex); + m_aLoadListeners.addInterface( g, aListener ); +} + + +void SAL_CALL BibDataManager::removeLoadListener( const Reference< XLoadListener >& aListener ) +{ + std::unique_lock g(m_aMutex); + m_aLoadListeners.removeInterface( g, aListener ); +} + + +Reference< awt::XControlModel > BibDataManager::createGridModel(const OUString& rName) +{ + Reference< awt::XControlModel > xModel; + + try + { + // create the control model + Reference< XMultiServiceFactory > xMgr = ::comphelper::getProcessServiceFactory(); + Reference< XInterface > xObject = xMgr->createInstance("com.sun.star.form.component.GridControl"); + xModel.set( xObject, UNO_QUERY ); + + // set the + Reference< XPropertySet > xPropSet( xModel, UNO_QUERY ); + xPropSet->setPropertyValue( "Name", Any( rName ) ); + + // set the name of the to-be-created control + Any aAny(OUString("com.sun.star.form.control.InteractionGridControl")); + xPropSet->setPropertyValue( "DefaultControl",aAny ); + + // the helpURL + OUString uProp("HelpURL"); + Reference< XPropertySetInfo > xPropInfo = xPropSet->getPropertySetInfo(); + if (xPropInfo->hasPropertyByName(uProp)) + { + xPropSet->setPropertyValue( + uProp, Any(OUString(INET_HID_SCHEME + HID_BIB_DB_GRIDCTRL))); + } + } + catch (const Exception&) + { + OSL_FAIL("::createGridModel: something went wrong !"); + } + + return xModel; +} + +OUString BibDataManager::getControlName(sal_Int32 nFormatKey ) +{ + OUString aResStr; + switch (nFormatKey) + { + case DataType::BIT: + case DataType::BOOLEAN: + aResStr="CheckBox"; + break; + case DataType::TINYINT: + case DataType::SMALLINT: + case DataType::INTEGER: + aResStr="NumericField"; + break; + case DataType::REAL: + case DataType::DOUBLE: + case DataType::NUMERIC: + case DataType::DECIMAL: + aResStr="FormattedField"; + break; + case DataType::TIMESTAMP: + aResStr="FormattedField"; + break; + case DataType::DATE: + aResStr="DateField"; + break; + case DataType::TIME: + aResStr="TimeField"; + break; + case DataType::CHAR: + case DataType::VARCHAR: + case DataType::LONGVARCHAR: + default: + aResStr="TextField"; + break; + } + return aResStr; +} + +Reference< awt::XControlModel > BibDataManager::loadControlModel( + const OUString& rName, bool bForceListBox) +{ + Reference< awt::XControlModel > xModel; + OUString aName = "View_" + rName; + + try + { + Reference< XNameAccess > xFields = getColumns( m_xForm ); + if (!xFields.is()) + return xModel; + Reference< XPropertySet > xField; + + Any aElement; + + if(xFields->hasByName(rName)) + { + aElement = xFields->getByName(rName); + aElement >>= xField; + + sal_Int32 nFormatKey = 0; + xField->getPropertyValue("Type") >>= nFormatKey; + + OUString aInstanceName("com.sun.star.form.component."); + + if (bForceListBox) + aInstanceName += "ListBox"; + else + aInstanceName += getControlName(nFormatKey); + + Reference< XComponentContext > xContext = comphelper::getProcessComponentContext(); + Reference< XInterface > xObject = xContext->getServiceManager()->createInstanceWithContext(aInstanceName, xContext); + xModel.set( xObject, UNO_QUERY ); + Reference< XPropertySet > xPropSet( xModel, UNO_QUERY ); + Any aFieldName; aFieldName <<= aName; + + xPropSet->setPropertyValue( FM_PROP_NAME,aFieldName); + xPropSet->setPropertyValue( FM_PROP_CONTROLSOURCE, Any( rName ) ); + xPropSet->setPropertyValue("NativeWidgetLook", Any( true ) ); + + if (bForceListBox) + { + uno::Any aAny; + + //uno::Reference< beans::XPropertySet > xPropSet(xControl, UNO_QUERY); + aAny <<= sal_Int16(1); + xPropSet->setPropertyValue("BoundColumn", aAny); + aAny <<= ListSourceType_VALUELIST; + xPropSet->setPropertyValue("ListSourceType", aAny); + + uno::Sequence<OUString> aListSource(TYPE_COUNT); + OUString* pListSourceArr = aListSource.getArray(); + //pListSourceArr[0] = "select TypeName, TypeIndex from TypeNms"; + for(sal_Int32 i = 0; i < TYPE_COUNT; ++i) + pListSourceArr[i] = OUString::number(i); + aAny <<= aListSource; + + xPropSet->setPropertyValue("ListSource", aAny); + + uno::Sequence<OUString> aValues(TYPE_COUNT + 1); + OUString* pValuesArr = aValues.getArray(); + pValuesArr[0] = BibResId(ST_TYPE_ARTICLE); + pValuesArr[1] = BibResId(ST_TYPE_BOOK); + pValuesArr[2] = BibResId(ST_TYPE_BOOKLET); + pValuesArr[3] = BibResId(ST_TYPE_CONFERENCE); + pValuesArr[4] = BibResId(ST_TYPE_INBOOK ); + pValuesArr[5] = BibResId(ST_TYPE_INCOLLECTION); + pValuesArr[6] = BibResId(ST_TYPE_INPROCEEDINGS); + pValuesArr[7] = BibResId(ST_TYPE_JOURNAL ); + pValuesArr[8] = BibResId(ST_TYPE_MANUAL ); + pValuesArr[9] = BibResId(ST_TYPE_MASTERSTHESIS); + pValuesArr[10] = BibResId(ST_TYPE_MISC ); + pValuesArr[11] = BibResId(ST_TYPE_PHDTHESIS ); + pValuesArr[12] = BibResId(ST_TYPE_PROCEEDINGS ); + pValuesArr[13] = BibResId(ST_TYPE_TECHREPORT ); + pValuesArr[14] = BibResId(ST_TYPE_UNPUBLISHED ); + pValuesArr[15] = BibResId(ST_TYPE_EMAIL ); + pValuesArr[16] = BibResId(ST_TYPE_WWW ); + pValuesArr[17] = BibResId(ST_TYPE_CUSTOM1 ); + pValuesArr[18] = BibResId(ST_TYPE_CUSTOM2 ); + pValuesArr[19] = BibResId(ST_TYPE_CUSTOM3 ); + pValuesArr[20] = BibResId(ST_TYPE_CUSTOM4 ); + pValuesArr[21] = BibResId(ST_TYPE_CUSTOM5 ); + // empty string if an invalid value no values is set + pValuesArr[TYPE_COUNT].clear(); + + aAny <<= aValues; + + xPropSet->setPropertyValue("StringItemList", aAny); + + xPropSet->setPropertyValue( "Dropdown", Any(true) ); + } + + Reference< XFormComponent > aFormComp(xModel,UNO_QUERY ); + + Reference< XNameContainer > xNameCont( m_xForm, UNO_QUERY ); + xNameCont->insertByName(aName, Any( aFormComp ) ); + + // now if the form where we inserted the new model is already loaded, notify the model of this + // Note that this implementation below is a HACK as it relies on the fact that the model adds itself + // as load listener to its parent, which is an implementation detail of the model. + // + // the better solution would be the following: + // in the current scenario, we insert a control model into a form. This results in the control model + // adding itself as load listener to the form. Now, the form should realize that it's already loaded + // and notify the model (which it knows as XLoadListener only) immediately. This seems to make sense. + // (as an analogon to the XStatusListener semantics). + // + // But this would be way too risky for this last-day fix here. + Reference< XLoadable > xLoad( m_xForm, UNO_QUERY ); + if ( xLoad.is() && xLoad->isLoaded() ) + { + Reference< XLoadListener > xListener( aFormComp, UNO_QUERY ); + if ( xListener.is() ) + { + EventObject aLoadSource; + aLoadSource.Source = xLoad; + xListener->loaded( aLoadSource ); + } + } + } + } + catch (const Exception&) + { + OSL_FAIL("::loadControlModel: something went wrong !"); + } + return xModel; +} + +void BibDataManager::CreateMappingDialog(weld::Window* pParent) +{ + MappingDialog_Impl aDlg(pParent, this); + if (RET_OK == aDlg.run() && pBibView) + { + reload(); + } +} + +OUString BibDataManager::CreateDBChangeDialog(weld::Window* pParent) +{ + OUString uRet; + DBChangeDialog_Impl aDlg(pParent, this); + if (aDlg.run() == RET_OK) + { + OUString sNewURL = aDlg.GetCurrentURL(); + if(sNewURL != getActiveDataSource()) + { + uRet = sNewURL; + } + } + return uRet; +} + +void BibDataManager::DispatchDBChangeDialog() +{ + if (pToolbar) + pToolbar->SendDispatch(pToolbar->GetChangeSourceId(), Sequence< PropertyValue >()); +} + +const OUString& BibDataManager::GetIdentifierMapping() +{ + if(sIdentifierMapping.isEmpty()) + { + BibConfig* pConfig = BibModul::GetConfig(); + BibDBDescriptor aDesc; + aDesc.sDataSource = getActiveDataSource(); + aDesc.sTableOrQuery = getActiveDataTable(); + aDesc.nCommandType = CommandType::TABLE; + const Mapping* pMapping = pConfig->GetMapping(aDesc); + sIdentifierMapping = pConfig->GetDefColumnName(IDENTIFIER_POS); + if(pMapping) + { + for(const auto & aColumnPair : pMapping->aColumnPairs) + { + if(aColumnPair.sLogicalColumnName == sIdentifierMapping) + { + sIdentifierMapping = aColumnPair.sRealColumnName; + break; + } + } + } + } + return sIdentifierMapping; +} + +void BibDataManager::SetToolbar(BibToolBar* pSet) +{ + pToolbar = pSet; + if(pToolbar) + pToolbar->SetDatMan(*this); +} + +uno::Reference< form::runtime::XFormController > const & BibDataManager::GetFormController() +{ + if(!m_xFormCtrl.is()) + { + Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext(); + m_xFormCtrl = form::runtime::FormController::create(xContext); + m_xFormCtrl->setModel(uno::Reference< awt::XTabControllerModel > (getForm(), UNO_QUERY)); + m_xFormDispatch.set( m_xFormCtrl, UNO_QUERY); + } + return m_xFormCtrl; +} + +void BibDataManager::RegisterInterceptor( const ::bib::BibBeamer* pBibBeamer) +{ + DBG_ASSERT( !m_xInterceptorHelper.is(), "BibDataManager::RegisterInterceptor: called twice!" ); + + if( pBibBeamer ) + m_xInterceptorHelper = new BibInterceptorHelper( pBibBeamer, m_xFormDispatch); +} + + +bool BibDataManager::HasActiveConnection() const +{ + return getConnection( m_xForm ).is(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/datman.hxx b/extensions/source/bibliography/datman.hxx new file mode 100644 index 0000000000..405cf83d85 --- /dev/null +++ b/extensions/source/bibliography/datman.hxx @@ -0,0 +1,166 @@ +/* -*- 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 . + */ + +#pragma once + +#include "bibview.hxx" + +#include <com/sun/star/awt/XControlModel.hpp> +#include <com/sun/star/form/XForm.hpp> +#include <com/sun/star/sdb/XSingleSelectQueryComposer.hpp> +#include <com/sun/star/form/runtime/XFormController.hpp> +#include <comphelper/compbase.hxx> +#include <comphelper/interfacecontainer4.hxx> +#include <com/sun/star/form/XLoadable.hpp> +#include <com/sun/star/frame/XDispatchProviderInterceptor.hpp> +#include <com/sun/star/frame/XDispatchProviderInterception.hpp> +#include <cppuhelper/implbase.hxx> +#include <vcl/vclptr.hxx> + +namespace weld { class Window; } + +namespace bib +{ + class BibView; + class BibBeamer; +} + +class BibToolBar; +struct BibDBDescriptor; + +class BibInterceptorHelper + :public cppu::WeakImplHelper< css::frame::XDispatchProviderInterceptor > +{ +private: + css::uno::Reference< css::frame::XDispatchProvider > xMasterDispatchProvider; + css::uno::Reference< css::frame::XDispatchProvider > xSlaveDispatchProvider; + css::uno::Reference< css::frame::XDispatch > xFormDispatch; + css::uno::Reference< css::frame::XDispatchProviderInterception > xInterception; + +protected: + virtual ~BibInterceptorHelper( ) override; + +public: + BibInterceptorHelper( const ::bib::BibBeamer* pBibBeamer, css::uno::Reference< css::frame::XDispatch > const & xDispatch); + + void ReleaseInterceptor(); + + // XDispatchProvider + virtual css::uno::Reference< css::frame::XDispatch > SAL_CALL queryDispatch( const css::util::URL& aURL, const OUString& aTargetFrameName, sal_Int32 nSearchFlags ) override; + virtual css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL queryDispatches( const css::uno::Sequence< css::frame::DispatchDescriptor >& aDescripts ) override; + // XDispatchProviderInterceptor + virtual css::uno::Reference< css::frame::XDispatchProvider > SAL_CALL getSlaveDispatchProvider( ) override; + virtual void SAL_CALL setSlaveDispatchProvider( const css::uno::Reference< css::frame::XDispatchProvider >& xNewSlaveDispatchProvider ) override; + virtual css::uno::Reference< css::frame::XDispatchProvider > SAL_CALL getMasterDispatchProvider( ) override; + virtual void SAL_CALL setMasterDispatchProvider( const css::uno::Reference< css::frame::XDispatchProvider >& xNewMasterDispatchProvider ) override; +}; + +typedef comphelper::WeakComponentImplHelper < css::form::XLoadable + > BibDataManager_Base; +class BibDataManager final : public BibDataManager_Base +{ +private: + css::uno::Reference< css::form::XForm > m_xForm; + css::uno::Reference< css::awt::XControlModel > m_xGridModel; + css::uno::Reference< css::sdb::XSingleSelectQueryComposer > m_xParser; + css::uno::Reference< css::form::runtime::XFormController > m_xFormCtrl; + css::uno::Reference< css::frame::XDispatch > m_xFormDispatch; + rtl::Reference<BibInterceptorHelper> m_xInterceptorHelper; + + OUString aActiveDataTable; + OUString aDataSourceURL; + OUString aQuoteChar; + + ::comphelper::OInterfaceContainerHelper4<css::form::XLoadListener> m_aLoadListeners; + + VclPtr< ::bib::BibView> pBibView; + VclPtr<BibToolBar> pToolbar; + + OUString sIdentifierMapping; + + void InsertFields(const css::uno::Reference< css::form::XFormComponent > & xGrid); + + css::uno::Reference< css::awt::XControlModel > const & + updateGridModel(const css::uno::Reference< css::form::XForm > & xDbForm); + static css::uno::Reference< css::awt::XControlModel > + createGridModel( const OUString& rName ); + + // XLoadable + virtual void SAL_CALL load( ) override; + virtual void SAL_CALL unload( ) override; + virtual void SAL_CALL reload( ) override; + virtual sal_Bool SAL_CALL isLoaded( ) override; + virtual void SAL_CALL addLoadListener( const css::uno::Reference< css::form::XLoadListener >& aListener ) override; + virtual void SAL_CALL removeLoadListener( const css::uno::Reference< css::form::XLoadListener >& aListener ) override; + + using WeakComponentImplHelperBase::disposing; + +public: + + BibDataManager(); + virtual ~BibDataManager() override; + + css::uno::Reference< css::form::XForm > createDatabaseForm( BibDBDescriptor& aDesc); + + css::uno::Reference< css::awt::XControlModel > updateGridModel(); + + css::uno::Sequence< OUString> getDataSources() const; + + const OUString& getActiveDataSource() const {return aDataSourceURL;} + void setActiveDataSource(const OUString& rURL); + + const OUString& getActiveDataTable() const { return aActiveDataTable;} + void setActiveDataTable(const OUString& rTable); + + void setFilter(const OUString& rQuery); + OUString getFilter() const; + + css::uno::Sequence< OUString> getQueryFields() const; + OUString getQueryField() const; + void startQueryWith(const OUString& rQuery); + + const css::uno::Reference< css::sdb::XSingleSelectQueryComposer >& getParser() const { return m_xParser; } + const css::uno::Reference< css::form::XForm >& getForm() const { return m_xForm; } + + + static OUString getControlName(sal_Int32 nFormatKey ); + + css::uno::Reference< css::awt::XControlModel > loadControlModel(const OUString& rName, + bool bForceListBox); + + void CreateMappingDialog(weld::Window* pParent); + OUString CreateDBChangeDialog(weld::Window* pParent); + + void DispatchDBChangeDialog(); + + void SetView( ::bib::BibView* pView ) { pBibView = pView; } + + void SetToolbar(BibToolBar* pSet); + + const OUString& GetIdentifierMapping(); + void ResetIdentifierMapping() {sIdentifierMapping.clear();} + + css::uno::Reference< css::form::runtime::XFormController > const & GetFormController(); + void RegisterInterceptor( const ::bib::BibBeamer* pBibBeamer); + + bool HasActiveConnection() const; +}; + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/formcontrolcontainer.cxx b/extensions/source/bibliography/formcontrolcontainer.cxx new file mode 100644 index 0000000000..f8d43a8a7c --- /dev/null +++ b/extensions/source/bibliography/formcontrolcontainer.cxx @@ -0,0 +1,136 @@ +/* -*- 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 "formcontrolcontainer.hxx" +#include <sal/log.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <algorithm> + +namespace bib +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::form; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::awt; + + FormControlContainer::FormControlContainer( ) + :OLoadListener( m_aMutex ) + { + } + + FormControlContainer::~FormControlContainer( ) + { + SAL_WARN_IF( isFormConnected(), "extensions.biblio", "FormControlContainer::~FormControlContainer: you should disconnect in your derived class!" ); + if ( isFormConnected() ) + disconnectForm(); + } + + void FormControlContainer::disconnectForm() + { + ::osl::MutexGuard aGuard( m_aMutex ); + SAL_WARN_IF( !isFormConnected(), "extensions.biblio", "FormControlContainer::connectForm: not connected!" ); + if ( isFormConnected() ) + { + m_xFormAdapter->dispose(); + m_xFormAdapter.clear(); + } + } + + void FormControlContainer::connectForm( const Reference< XLoadable >& _rxForm ) + { + SAL_WARN_IF( isFormConnected(), "extensions.biblio", "FormControlContainer::connectForm: already connected!" ); + + SAL_WARN_IF( !_rxForm.is(), "extensions.biblio", "FormControlContainer::connectForm: invalid form!" ); + if ( !isFormConnected() && _rxForm.is() ) + { + m_xFormAdapter = new OLoadListenerAdapter( _rxForm ); + m_xFormAdapter->Init( this ); + + implSetDesignMode( !m_xForm.is() || !m_xForm->isLoaded() ); + } + + m_xForm = _rxForm; + } + + namespace { + + struct ControlModeSwitch + { + bool bDesign; + explicit ControlModeSwitch( bool _bDesign ) : bDesign( _bDesign ) { } + + void operator() ( const Reference< XControl >& _rxControl ) const + { + if ( _rxControl.is() ) + _rxControl->setDesignMode( bDesign ); + } + }; + + } + + void FormControlContainer::implSetDesignMode( bool _bDesign ) + { + try + { + Reference< XControlContainer > xControlCont = getControlContainer(); + if ( xControlCont.is() ) + { + const Sequence<Reference<XControl>> aControls = xControlCont->getControls(); + + std::for_each( + aControls.begin(), + aControls.end(), + ControlModeSwitch( _bDesign ) + ); + } + } + catch( const Exception&) + { + TOOLS_WARN_EXCEPTION( "extensions.biblio", "FormControlContainer::implSetDesignMode" ); + } + } + + void FormControlContainer::_loaded( const css::lang::EventObject& /*_rEvent*/ ) + { + implSetDesignMode( false ); + } + + void FormControlContainer::_unloading( const css::lang::EventObject& /*_rEvent*/ ) + { + implSetDesignMode( true ); + } + + void FormControlContainer::_reloading( const css::lang::EventObject& /*_rEvent*/ ) + { + implSetDesignMode( true ); + } + + void FormControlContainer::_reloaded( const css::lang::EventObject& /*_rEvent*/ ) + { + implSetDesignMode( false ); + } + + +} // namespace bib + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/formcontrolcontainer.hxx b/extensions/source/bibliography/formcontrolcontainer.hxx new file mode 100644 index 0000000000..b15105ad08 --- /dev/null +++ b/extensions/source/bibliography/formcontrolcontainer.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/basemutex.hxx> +#include "loadlisteneradapter.hxx" +#include <com/sun/star/awt/XControlContainer.hpp> +#include <rtl/ref.hxx> + + +namespace bib +{ + + class FormControlContainer + :public ::cppu::BaseMutex + ,public ::bib::OLoadListener + { + private: + rtl::Reference<OLoadListenerAdapter> m_xFormAdapter; + css::uno::Reference< css::form::XLoadable > m_xForm; + private: + void implSetDesignMode( bool _bDesign ); + + protected: + FormControlContainer( ); + virtual ~FormControlContainer( ) override; + + bool isFormConnected() const { return m_xFormAdapter.is(); } + void connectForm( const css::uno::Reference< css::form::XLoadable >& _rxForm ); + void disconnectForm(); + + virtual css::uno::Reference< css::awt::XControlContainer > + getControlContainer() = 0; + + protected: + // XLoadListener equivalents + virtual void _loaded( const css::lang::EventObject& _rEvent ) override; + virtual void _unloading( const css::lang::EventObject& _rEvent ) override; + virtual void _reloading( const css::lang::EventObject& _rEvent ) override; + virtual void _reloaded( const css::lang::EventObject& _rEvent ) override; + + }; + + +} // namespace bib + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/framectr.cxx b/extensions/source/bibliography/framectr.cxx new file mode 100644 index 0000000000..758c8b3484 --- /dev/null +++ b/extensions/source/bibliography/framectr.cxx @@ -0,0 +1,874 @@ +/* -*- 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 <comphelper/types.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequence.hxx> +#include "framectr.hxx" +#include "datman.hxx" +#include <toolkit/helper/vclunohelper.hxx> +#include "bibconfig.hxx" +#include <cppuhelper/implbase.hxx> +#include <utility> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/awt/XTextComponent.hpp> +#include <com/sun/star/awt/XVclWindowPeer.hpp> +#include <com/sun/star/form/XConfirmDeleteListener.hpp> +#include <com/sun/star/form/runtime/XFormController.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/sdbcx/Privilege.hpp> +#include <com/sun/star/sdbc/XResultSetUpdate.hpp> +#include <com/sun/star/sdb/FilterDialog.hpp> +#include <com/sun/star/sdb/RowChangeAction.hpp> +#include <com/sun/star/frame/CommandGroup.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp> +#include <comphelper/multicontainer2.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <sot/exchange.hxx> +#include <sot/formats.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/weld.hxx> +#include <osl/mutex.hxx> + +#include <unordered_map> + +using namespace osl; +using namespace cppu; +using namespace com::sun::star::sdbc; +using namespace com::sun::star::frame; +using namespace com::sun::star::uno; +using namespace com::sun::star; + +namespace { + +struct DispatchInfo +{ + const char* pCommand; + sal_Int16 nGroupId; + bool bActiveConnection; +}; + +struct CacheDispatchInfo +{ + sal_Int16 nGroupId; + bool bActiveConnection; +}; + +} + +// Attention: commands must be sorted by command groups. Implementation is dependent +// on this!! +const DispatchInfo SupportedCommandsArray[] = +{ + { ".uno:Undo" , frame::CommandGroup::EDIT , false }, + { ".uno:Cut" , frame::CommandGroup::EDIT , false }, + { ".uno:Copy" , frame::CommandGroup::EDIT , false }, + { ".uno:Paste" , frame::CommandGroup::EDIT , false }, + { ".uno:SelectAll" , frame::CommandGroup::EDIT , false }, + { ".uno:CloseDoc" , frame::CommandGroup::DOCUMENT , false }, + { ".uno:StatusBarVisible" , frame::CommandGroup::VIEW , false }, + { ".uno:AvailableToolbars" , frame::CommandGroup::VIEW , false }, + { ".uno:Bib/standardFilter" , frame::CommandGroup::DATA , true }, + { ".uno:Bib/DeleteRecord" , frame::CommandGroup::DATA , true }, + { ".uno:Bib/InsertRecord" , frame::CommandGroup::DATA , true }, + { ".uno:Bib/query" , frame::CommandGroup::DATA , true }, + { ".uno:Bib/autoFilter" , frame::CommandGroup::DATA , true }, + { ".uno:Bib/source" , frame::CommandGroup::DATA , true }, + { ".uno:Bib/removeFilter" , frame::CommandGroup::DATA , true }, + { ".uno:Bib/sdbsource" , frame::CommandGroup::DATA , true }, + { ".uno:Bib/Mapping" , frame::CommandGroup::DATA , true }, +}; + +typedef std::unordered_map< OUString, CacheDispatchInfo > CmdToInfoCache; + +static const CmdToInfoCache& GetCommandToInfoCache() +{ + static CmdToInfoCache aCmdToInfoCache = []() { + CmdToInfoCache aCache; + for (const auto& command : SupportedCommandsArray) + { + OUString aCommand(OUString::createFromAscii(command.pCommand)); + + CacheDispatchInfo aDispatchInfo; + aDispatchInfo.nGroupId = command.nGroupId; + aDispatchInfo.bActiveConnection = command.bActiveConnection; + aCache.emplace(aCommand, aDispatchInfo); + } + return aCache; + }(); + + return aCmdToInfoCache; +} + + +class BibFrameCtrl_Impl : public cppu::WeakImplHelper < XFrameActionListener > +{ +public: + Mutex aMutex; + comphelper::OMultiTypeInterfaceContainerHelper2 aLC; + + BibFrameController_Impl* pController; + + BibFrameCtrl_Impl() + : aLC( aMutex ) + , pController(nullptr) + {} + + virtual void SAL_CALL frameAction(const FrameActionEvent& aEvent) override; + virtual void SAL_CALL disposing( const lang::EventObject& Source ) override; +}; + +void BibFrameCtrl_Impl::frameAction(const FrameActionEvent& ) +{ +} + +void BibFrameCtrl_Impl::disposing( const lang::EventObject& /*Source*/ ) +{ + ::SolarMutexGuard aGuard; + if ( pController ) + pController->getFrame()->removeFrameActionListener( this ); +} + +BibFrameController_Impl::BibFrameController_Impl( uno::Reference< awt::XWindow > xComponent, + BibDataManager* pDataManager) + :m_xWindow(std::move( xComponent )) + ,m_xDatMan( pDataManager ) +{ + m_bDisposing = false; + m_xImpl = new BibFrameCtrl_Impl; + m_xImpl->pController = this; +} + +BibFrameController_Impl::~BibFrameController_Impl() +{ + m_xImpl->pController = nullptr; + m_xDatMan.clear(); +} + +OUString SAL_CALL BibFrameController_Impl::getImplementationName() +{ + return "com.sun.star.comp.extensions.Bibliography"; +} + +sal_Bool SAL_CALL BibFrameController_Impl::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService( this, sServiceName ); +} + +css::uno::Sequence< OUString > SAL_CALL BibFrameController_Impl::getSupportedServiceNames() +{ + // return only top level services ... + // base services are included there and should be asked by uno-rtti. + return { "com.sun.star.frame.Bibliography" }; +} + +void BibFrameController_Impl::attachFrame( const uno::Reference< XFrame > & xArg ) +{ + m_xFrame = xArg; + m_xFrame->addFrameActionListener( m_xImpl ); +} + +sal_Bool BibFrameController_Impl::attachModel( const uno::Reference< XModel > & /*xModel*/ ) +{ + return false; +} + +sal_Bool BibFrameController_Impl::suspend( sal_Bool bSuspend ) +{ + if ( bSuspend ) + getFrame()->removeFrameActionListener( m_xImpl ); + else + getFrame()->addFrameActionListener( m_xImpl ); + return true; +} + +uno::Any BibFrameController_Impl::getViewData() +{ + return uno::Any(); +} + +void BibFrameController_Impl::restoreViewData( const uno::Any& /*Value*/ ) +{ +} + +uno::Reference< XFrame > BibFrameController_Impl::getFrame() +{ + return m_xFrame; +} + +uno::Reference< XModel > BibFrameController_Impl::getModel() +{ + return uno::Reference< XModel > (); +} + +void BibFrameController_Impl::dispose() +{ + m_bDisposing = true; + lang::EventObject aObject; + uno::Reference< XFrame > xFrame = getFrame(); + + if (xFrame.is()) + xFrame->removeFrameActionListener( m_xImpl ); + m_xFrame.clear(); + + aObject.Source = static_cast<XController*>(this); + m_xImpl->aLC.disposeAndClear(aObject); + m_xDatMan.clear(); + m_aStatusListeners.clear(); + m_xLastQueriedFocusWin.clear(); + m_xWindow.clear(); + m_xImpl.clear(); +} + +void BibFrameController_Impl::addEventListener( const uno::Reference< lang::XEventListener > & aListener ) +{ + m_xImpl->aLC.addInterface( cppu::UnoType<lang::XEventListener>::get(), aListener ); +} + +void BibFrameController_Impl::removeEventListener( const uno::Reference< lang::XEventListener > & aListener ) +{ + m_xImpl->aLC.removeInterface( cppu::UnoType<lang::XEventListener>::get(), aListener ); +} + +uno::Reference< frame::XDispatch > BibFrameController_Impl::queryDispatch( const util::URL& aURL, const OUString& /*aTarget*/, sal_Int32 /*nSearchFlags*/ ) +{ + if ( !m_bDisposing ) + { + const CmdToInfoCache& rCmdCache = GetCommandToInfoCache(); + CmdToInfoCache::const_iterator pIter = rCmdCache.find( aURL.Complete ); + if ( pIter != rCmdCache.end() ) + { + if (( m_xDatMan->HasActiveConnection() ) || + ( !pIter->second.bActiveConnection )) + return static_cast<frame::XDispatch*>(this); + } + } + + return uno::Reference< frame::XDispatch > (); +} + +uno::Sequence<uno::Reference< XDispatch > > BibFrameController_Impl::queryDispatches( const uno::Sequence<DispatchDescriptor>& aDescripts ) +{ + uno::Sequence< uno::Reference< XDispatch > > aDispatches( aDescripts.getLength() ); + auto aDispatchesRange = asNonConstRange(aDispatches); + for ( sal_Int32 i=0; i<aDescripts.getLength(); ++i ) + aDispatchesRange[i] = queryDispatch( aDescripts[i].FeatureURL, aDescripts[i].FrameName, aDescripts[i].SearchFlags ); + return aDispatches; +} + +uno::Sequence< ::sal_Int16 > SAL_CALL BibFrameController_Impl::getSupportedCommandGroups() +{ + uno::Sequence< ::sal_Int16 > aDispatchInfo{ frame::CommandGroup::EDIT, + frame::CommandGroup::DOCUMENT, + frame::CommandGroup::DATA, + frame::CommandGroup::VIEW }; + + return aDispatchInfo; +} + +uno::Sequence< frame::DispatchInformation > SAL_CALL BibFrameController_Impl::getConfigurableDispatchInformation( ::sal_Int16 nCommandGroup ) +{ + const CmdToInfoCache& rCmdCache = GetCommandToInfoCache(); + + frame::DispatchInformation aDispatchInfo; + std::vector< frame::DispatchInformation > aDispatchInfoVector; + + if (( nCommandGroup == frame::CommandGroup::EDIT ) || + ( nCommandGroup == frame::CommandGroup::DOCUMENT ) || + ( nCommandGroup == frame::CommandGroup::DATA ) || + ( nCommandGroup == frame::CommandGroup::VIEW )) + { + bool bGroupFound = false; + for (auto const& item : rCmdCache) + { + if ( item.second.nGroupId == nCommandGroup ) + { + bGroupFound = true; + aDispatchInfo.Command = item.first; + aDispatchInfo.GroupId = item.second.nGroupId; + aDispatchInfoVector.push_back( aDispatchInfo ); + } + else if ( bGroupFound ) + break; + } + } + + return comphelper::containerToSequence( aDispatchInfoVector ); +} + +static bool canInsertRecords(const Reference< beans::XPropertySet>& _rxCursorSet) +{ + sal_Int32 nPriv = 0; + _rxCursorSet->getPropertyValue("Privileges") >>= nPriv; + return _rxCursorSet.is() && (nPriv & sdbcx::Privilege::INSERT) != 0; +} + +bool BibFrameController_Impl::SaveModified(const Reference< form::runtime::XFormController>& xController) +{ + if (!xController.is()) + return false; + + Reference< XResultSetUpdate> _xCursor(xController->getModel(), UNO_QUERY); + + if (!_xCursor.is()) + return false; + + Reference< beans::XPropertySet> _xSet(_xCursor, UNO_QUERY); + if (!_xSet.is()) + return false; + + // need to save? + bool bIsNew = ::comphelper::getBOOL(_xSet->getPropertyValue("IsNew")); + bool bIsModified = ::comphelper::getBOOL(_xSet->getPropertyValue("IsModified")); + bool bResult = !bIsModified; + if (bIsModified) + { + try + { + if (bIsNew) + _xCursor->insertRow(); + else + _xCursor->updateRow(); + bResult = true; + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.biblio", ""); + } + } + return bResult; +} + +static vcl::Window* lcl_GetFocusChild( vcl::Window const * pParent ) +{ + sal_uInt16 nChildren = pParent->GetChildCount(); + for( sal_uInt16 nChild = 0; nChild < nChildren; ++nChild) + { + vcl::Window* pChild = pParent->GetChild( nChild ); + if(pChild->HasFocus()) + return pChild; + vcl::Window* pSubChild = lcl_GetFocusChild( pChild ); + if(pSubChild) + return pSubChild; + } + return nullptr; +} + +//class XDispatch +void BibFrameController_Impl::dispatch(const util::URL& _rURL, const uno::Sequence< beans::PropertyValue >& aArgs) +{ + if ( m_bDisposing ) + return; + + ::SolarMutexGuard aGuard; + weld::Window* pParent = Application::GetFrameWeld(m_xWindow); + weld::WaitObject aWaitObject(pParent); + + OUString aCommand( _rURL.Path); + if(aCommand == "Bib/Mapping") + { + m_xDatMan->CreateMappingDialog(pParent); + } + else if(aCommand == "Bib/source") + { + ChangeDataSource(aArgs); + } + else if(aCommand == "Bib/sdbsource") + { + OUString aURL = m_xDatMan->CreateDBChangeDialog(pParent); + if(!aURL.isEmpty()) + { + try + { + uno::Sequence< beans::PropertyValue > aNewDataSource + { + comphelper::makePropertyValue( {}, OUString() ), + comphelper::makePropertyValue( {}, aURL ) + }; + ChangeDataSource(aNewDataSource); + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.biblio", + "Exception caught while changing the data source"); + } + } + } + else if(aCommand == "Bib/autoFilter") + { + sal_uInt16 nCount = m_aStatusListeners.size(); + for ( sal_uInt16 n=0; n<nCount; n++ ) + { + BibStatusDispatch *pObj = m_aStatusListeners[n].get(); + if ( pObj->aURL.Path == "Bib/removeFilter" ) + { + FeatureStateEvent aEvent; + aEvent.FeatureURL = pObj->aURL; + aEvent.IsEnabled = true; + aEvent.Requery = false; + aEvent.Source = static_cast<XDispatch *>(this); + pObj->xListener->statusChanged( aEvent ); + //break; because there are more than one + } + } + + const beans::PropertyValue* pPropertyValue = aArgs.getConstArray(); + uno::Any aValue=pPropertyValue[0].Value; + OUString aQuery; + aValue >>= aQuery; + + aValue=pPropertyValue[1].Value; + OUString aQueryField; + aValue >>= aQueryField; + BibConfig* pConfig = BibModul::GetConfig(); + pConfig->setQueryField(aQueryField); + m_xDatMan->startQueryWith(aQuery); + } + else if(aCommand == "Bib/standardFilter") + { + try + { + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + + // create the dialog object + uno::Reference< ui::dialogs::XExecutableDialog > xDialog = sdb::FilterDialog::createWithQuery(xContext, m_xDatMan->getParser(), + Reference<sdbc::XRowSet>(m_xDatMan->getForm(), uno::UNO_QUERY_THROW), m_xWindow); + // execute it + if ( xDialog->execute( ) ) + { + // the dialog has been executed successfully, and the filter on the query composer + // has been changed + OUString sNewFilter = m_xDatMan->getParser()->getFilter(); + m_xDatMan->setFilter( sNewFilter ); + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.biblio", "BibFrameController_Impl::dispatch" ); + } + + sal_uInt16 nCount = m_aStatusListeners.size(); + for ( sal_uInt16 n=0; n<nCount; n++ ) + { + BibStatusDispatch *pObj = m_aStatusListeners[n].get(); + if ( pObj->aURL.Path == "Bib/removeFilter" && m_xDatMan->getParser().is()) + { + FeatureStateEvent aEvent; + aEvent.FeatureURL = pObj->aURL; + aEvent.IsEnabled = !m_xDatMan->getParser()->getFilter().isEmpty(); + aEvent.Requery = false; + aEvent.Source = static_cast<XDispatch *>(this); + pObj->xListener->statusChanged( aEvent ); + } + } + } + else if(aCommand == "Bib/removeFilter") + { + RemoveFilter(); + } + else if( _rURL.Complete == ".uno:CloseDoc" || aCommand == "CloseDoc" ) + { + Application::PostUserEvent( LINK( this, BibFrameController_Impl, + DisposeHdl ) ); + + } + else if(aCommand == "Bib/InsertRecord") + { + Reference<form::runtime::XFormController > xFormCtrl = m_xDatMan->GetFormController(); + if(SaveModified(xFormCtrl)) + { + try + { + Reference< sdbc::XResultSet > xCursor( m_xDatMan->getForm(), UNO_QUERY ); + xCursor->last(); + + Reference< XResultSetUpdate > xUpdateCursor( m_xDatMan->getForm(), UNO_QUERY ); + xUpdateCursor->moveToInsertRow(); + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.biblio", + "Exception in last() or moveToInsertRow()"); + } + } + } + else if(aCommand == "Bib/DeleteRecord") + { + Reference< css::sdbc::XResultSet > xCursor(m_xDatMan->getForm(), UNO_QUERY); + Reference< XResultSetUpdate > xUpdateCursor(xCursor, UNO_QUERY); + Reference< beans::XPropertySet > xSet(m_xDatMan->getForm(), UNO_QUERY); + bool bIsNew = ::comphelper::getBOOL(xSet->getPropertyValue("IsNew")); + if(!bIsNew) + { + sal_uInt32 nCount = 0; + xSet->getPropertyValue("RowCount") >>= nCount; + // determine next position + bool bSuccess = false; + bool bLeft = false; + bool bRight = false; + try + { + bLeft = xCursor->isLast() && nCount > 1; + bRight= !xCursor->isLast(); + // ask for confirmation + Reference< form::XConfirmDeleteListener > xConfirm(m_xDatMan->GetFormController(),UNO_QUERY); + if (xConfirm.is()) + { + sdb::RowChangeEvent aEvent; + aEvent.Source.set(xCursor, UNO_QUERY); + aEvent.Action = sdb::RowChangeAction::DELETE; + aEvent.Rows = 1; + bSuccess = xConfirm->confirmDelete(aEvent); + } + + // delete it + if (bSuccess) + xUpdateCursor->deleteRow(); + } + catch(const Exception&) + { + bSuccess = false; + } + if (bSuccess) + { + if (bLeft || bRight) + xCursor->relative(bRight ? 1 : -1); + else + { + bool bCanInsert = canInsertRecords(xSet); + // can another entry be inserted? + try + { + if (bCanInsert) + xUpdateCursor->moveToInsertRow(); + else + // move data entry to reset state + xCursor->first(); + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.biblio", + "DeleteRecord: exception caught!"); + } + } + } + } + } + else if(aCommand == "Cut") + { + vcl::Window* pChild = m_xLastQueriedFocusWin.get(); + if(pChild) + { + KeyEvent aEvent( 0, KeyFuncType::CUT ); + pChild->KeyInput( aEvent ); + } + } + else if(aCommand == "Copy") + { + vcl::Window* pChild = m_xLastQueriedFocusWin.get(); + if(pChild) + { + KeyEvent aEvent( 0, KeyFuncType::COPY ); + pChild->KeyInput( aEvent ); + } + } + else if(aCommand == "Paste") + { + vcl::Window* pChild = m_xLastQueriedFocusWin.get(); + if(pChild) + { + KeyEvent aEvent( 0, KeyFuncType::PASTE ); + pChild->KeyInput( aEvent ); + } + } +} +IMPL_LINK_NOARG( BibFrameController_Impl, DisposeHdl, void*, void ) +{ + if (m_xFrame.is()) + m_xFrame->dispose(); +}; + +void BibFrameController_Impl::addStatusListener( + const uno::Reference< frame::XStatusListener > & aListener, + const util::URL& aURL) +{ + BibConfig* pConfig = BibModul::GetConfig(); + // create a new Reference and insert into listener array + m_aStatusListeners.push_back( std::make_unique<BibStatusDispatch>( aURL, aListener ) ); + + // send first status synchronously + FeatureStateEvent aEvent; + aEvent.FeatureURL = aURL; + aEvent.Requery = false; + aEvent.Source = static_cast<XDispatch *>(this); + if ( aURL.Path == "StatusBarVisible" ) + { + aEvent.IsEnabled = false; + aEvent.State <<= false; + } + else if ( aURL.Path == "Bib/hierarchical" ) + { + aEvent.IsEnabled = true; + aEvent.State <<= OUString(); + } + else if(aURL.Path == "Bib/MenuFilter") + { + aEvent.IsEnabled = true; + aEvent.FeatureDescriptor=m_xDatMan->getQueryField(); + + aEvent.State <<= m_xDatMan->getQueryFields(); + + } + else if ( aURL.Path == "Bib/source") + { + aEvent.IsEnabled = true; + aEvent.FeatureDescriptor=m_xDatMan->getActiveDataTable(); + + aEvent.State <<= m_xDatMan->getDataSources(); + } + else if( aURL.Path == "Bib/sdbsource" || + aURL.Path == "Bib/Mapping" || + aURL.Path == "Bib/autoFilter" || + aURL.Path == "Bib/standardFilter" ) + { + aEvent.IsEnabled = true; + } + else if(aURL.Path == "Bib/query") + { + aEvent.IsEnabled = true; + aEvent.State <<= pConfig->getQueryText(); + } + else if (aURL.Path == "Bib/removeFilter" ) + { + OUString aFilterStr=m_xDatMan->getFilter(); + aEvent.IsEnabled = !aFilterStr.isEmpty(); + } + else if(aURL.Path == "Cut") + { + m_xLastQueriedFocusWin = lcl_GetFocusChild( VCLUnoHelper::GetWindow( m_xWindow ) ); + if (m_xLastQueriedFocusWin) + { + Reference<css::awt::XTextComponent> xEdit(m_xLastQueriedFocusWin->GetComponentInterface(), css::uno::UNO_QUERY); + aEvent.IsEnabled = xEdit && xEdit->isEditable() && !xEdit->getSelectedText().isEmpty(); + } + } + if(aURL.Path == "Copy") + { + m_xLastQueriedFocusWin = lcl_GetFocusChild( VCLUnoHelper::GetWindow( m_xWindow ) ); + if (m_xLastQueriedFocusWin) + { + Reference<css::awt::XTextComponent> xEdit(m_xLastQueriedFocusWin->GetComponentInterface(), css::uno::UNO_QUERY); + aEvent.IsEnabled = xEdit && !xEdit->getSelectedText().isEmpty(); + } + } + else if(aURL.Path == "Paste" ) + { + aEvent.IsEnabled = false; + m_xLastQueriedFocusWin = lcl_GetFocusChild( VCLUnoHelper::GetWindow( m_xWindow ) ); + if (m_xLastQueriedFocusWin) + { + Reference<css::awt::XTextComponent> xEdit(m_xLastQueriedFocusWin->GetComponentInterface(), css::uno::UNO_QUERY); + if (xEdit && !xEdit->isEditable()) + { + uno::Reference< datatransfer::clipboard::XClipboard > xClip = m_xLastQueriedFocusWin->GetClipboard(); + if(xClip.is()) + { + uno::Reference< datatransfer::XTransferable > xDataObj; + + try + { + SolarMutexReleaser aReleaser; + xDataObj = xClip->getContents(); + } + catch( const uno::Exception& ) + { + } + + if ( xDataObj.is() ) + { + datatransfer::DataFlavor aFlavor; + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor ); + try + { + uno::Any aData = xDataObj->getTransferData( aFlavor ); + OUString aText; + aData >>= aText; + aEvent.IsEnabled = !aText.isEmpty(); + } + catch( const uno::Exception& ) + { + } + } + } + } + } + } + else if(aURL.Path == "Bib/DeleteRecord") + { + Reference< beans::XPropertySet > xSet(m_xDatMan->getForm(), UNO_QUERY); + bool bIsNew = ::comphelper::getBOOL(xSet->getPropertyValue("IsNew")); + if(!bIsNew) + { + sal_uInt32 nCount = 0; + xSet->getPropertyValue("RowCount") >>= nCount; + aEvent.IsEnabled = nCount > 0; + } + } + else if (aURL.Path == "Bib/InsertRecord") + { + Reference< beans::XPropertySet > xSet(m_xDatMan->getForm(), UNO_QUERY); + aEvent.IsEnabled = canInsertRecords(xSet); + } + aListener->statusChanged( aEvent ); +} + +void BibFrameController_Impl::removeStatusListener( + const uno::Reference< frame::XStatusListener > & aObject, const util::URL& aURL) +{ + // search listener array for given listener + // for checking equality always "cast" to XInterface + if ( m_bDisposing ) + return; + + sal_uInt16 nCount = m_aStatusListeners.size(); + for ( sal_uInt16 n=0; n<nCount; n++ ) + { + BibStatusDispatch *pObj = m_aStatusListeners[n].get(); + bool bFlag=pObj->xListener.is(); + if (!bFlag || (pObj->xListener == aObject && + ( aURL.Complete.isEmpty() || pObj->aURL.Path == aURL.Path ))) + { + m_aStatusListeners.erase( m_aStatusListeners.begin() + n ); + break; + } + } +} + +void BibFrameController_Impl::RemoveFilter() +{ + OUString aQuery; + m_xDatMan->startQueryWith(aQuery); + + sal_uInt16 nCount = m_aStatusListeners.size(); + + bool bRemoveFilter=false; + bool bQueryText=false; + + for ( sal_uInt16 n=0; n<nCount; n++ ) + { + BibStatusDispatch *pObj = m_aStatusListeners[n].get(); + if ( pObj->aURL.Path == "Bib/removeFilter" ) + { + FeatureStateEvent aEvent; + aEvent.FeatureURL = pObj->aURL; + aEvent.IsEnabled = false; + aEvent.Requery = false; + aEvent.Source = static_cast<XDispatch *>(this); + pObj->xListener->statusChanged( aEvent ); + bRemoveFilter=true; + } + else if(pObj->aURL.Path == "Bib/query") + { + FeatureStateEvent aEvent; + aEvent.FeatureURL = pObj->aURL; + aEvent.IsEnabled = true; + aEvent.Requery = false; + aEvent.Source = static_cast<XDispatch *>(this); + aEvent.State <<= aQuery; + pObj->xListener->statusChanged( aEvent ); + bQueryText=true; + } + + if(bRemoveFilter && bQueryText) + break; + + } +} + +void BibFrameController_Impl::ChangeDataSource(const uno::Sequence< beans::PropertyValue >& aArgs) +{ + const beans::PropertyValue* pPropertyValue = aArgs.getConstArray(); + uno::Any aValue=pPropertyValue[0].Value; + OUString aDBTableName; + aValue >>= aDBTableName; + + + if(aArgs.getLength() > 1) + { + uno::Any aDB = pPropertyValue[1].Value; + OUString aURL; + aDB >>= aURL; + m_xDatMan->setActiveDataSource(aURL); + aDBTableName = m_xDatMan->getActiveDataTable(); + } + else + { + Reference<css::form::XLoadable> xLoadable(m_xDatMan); + xLoadable->unload(); + m_xDatMan->setActiveDataTable(aDBTableName); + m_xDatMan->updateGridModel(); + xLoadable->load(); + } + + + sal_uInt16 nCount = m_aStatusListeners.size(); + + bool bMenuFilter=false; + bool bQueryText=false; + for ( sal_uInt16 n=0; n<nCount; n++ ) + { + BibStatusDispatch *pObj = m_aStatusListeners[n].get(); + if (pObj->aURL.Path == "Bib/MenuFilter") + { + FeatureStateEvent aEvent; + aEvent.FeatureURL = pObj->aURL; + aEvent.IsEnabled = true; + aEvent.Requery = false; + aEvent.Source = static_cast<XDispatch *>(this); + aEvent.FeatureDescriptor=m_xDatMan->getQueryField(); + + uno::Sequence<OUString> aStringSeq=m_xDatMan->getQueryFields(); + aEvent.State <<= aStringSeq; + + pObj->xListener->statusChanged( aEvent ); + bMenuFilter=true; + } + else if (pObj->aURL.Path == "Bib/query") + { + FeatureStateEvent aEvent; + aEvent.FeatureURL = pObj->aURL; + aEvent.IsEnabled = true; + aEvent.Requery = false; + aEvent.Source = static_cast<XDispatch *>(this); + BibConfig* pConfig = BibModul::GetConfig(); + aEvent.State <<= pConfig->getQueryText(); + pObj->xListener->statusChanged( aEvent ); + bQueryText=true; + } + + if (bMenuFilter && bQueryText) + break; + + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/framectr.hxx b/extensions/source/bibliography/framectr.hxx new file mode 100644 index 0000000000..9aac2cee0d --- /dev/null +++ b/extensions/source/bibliography/framectr.hxx @@ -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 . + */ + +#pragma once + +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/frame/XDispatchInformationProvider.hpp> +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> +#include <tools/link.hxx> +#include <utility> +#include <vcl/window.hxx> +#include <vector> +#include <memory> + +#include "bibmod.hxx" +class BibDataManager; +class BibFrameCtrl_Impl; +namespace com::sun::star{ + namespace form::runtime { + class XFormController; + } +} +class BibStatusDispatch +{ +public: + css::util::URL aURL; + css::uno::Reference< css::frame::XStatusListener > xListener; + BibStatusDispatch( css::util::URL _aURL, css::uno::Reference< css::frame::XStatusListener > xRef ) + : aURL(std::move( _aURL )) + , xListener(std::move( xRef )) + {} +}; + +typedef std::vector<std::unique_ptr<BibStatusDispatch> > BibStatusDispatchArr; + +class BibFrameController_Impl : public cppu::WeakImplHelper < + css::lang::XServiceInfo, + css::frame::XController, + css::frame::XDispatch, + css::frame::XDispatchProvider, + css::frame::XDispatchInformationProvider +> +{ +friend class BibFrameCtrl_Impl; + rtl::Reference<BibFrameCtrl_Impl> m_xImpl; + BibStatusDispatchArr m_aStatusListeners; + css::uno::Reference< css::awt::XWindow > m_xWindow; + css::uno::Reference< css::frame::XFrame > m_xFrame; + bool m_bDisposing; + rtl::Reference<BibDataManager> m_xDatMan; + VclPtr<vcl::Window> m_xLastQueriedFocusWin; + + DECL_LINK( DisposeHdl, void*, void ); + + static bool SaveModified(const css::uno::Reference< css::form::runtime::XFormController>& xController); +public: + BibFrameController_Impl( css::uno::Reference< css::awt::XWindow > xComponent, + BibDataManager* pDatMan); + virtual ~BibFrameController_Impl() override; + + + void ChangeDataSource(const css::uno::Sequence< css::beans::PropertyValue >& aArgs); + void RemoveFilter(); + + // css::lang::XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& sServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // css::frame::XController + virtual void SAL_CALL attachFrame( const css::uno::Reference< css::frame::XFrame > & xFrame ) override; + virtual sal_Bool SAL_CALL attachModel( const css::uno::Reference< css::frame::XModel > & xModel ) override; + virtual sal_Bool SAL_CALL suspend( sal_Bool bSuspend ) override; + virtual css::uno::Any SAL_CALL getViewData() override; + virtual void SAL_CALL restoreViewData( const css::uno::Any& Value ) override; + virtual css::uno::Reference< css::frame::XFrame > SAL_CALL getFrame() override; + virtual css::uno::Reference< css::frame::XModel > SAL_CALL getModel() override; + + // css::lang::XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener > & aListener ) override; + virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener > & aListener ) override; + + // css::frame::XDispatchProvider + virtual css::uno::Reference< css::frame::XDispatch > SAL_CALL queryDispatch( const css::util::URL& aURL, const OUString& aTargetFrameName, sal_Int32 nSearchFlags) override; + virtual css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL queryDispatches( const css::uno::Sequence< css::frame::DispatchDescriptor >& aDescripts) override; + + //class css::frame::XDispatch + virtual void SAL_CALL dispatch(const css::util::URL& aURL, const css::uno::Sequence< css::beans::PropertyValue >& aArgs) override; + virtual void SAL_CALL addStatusListener(const css::uno::Reference< css::frame::XStatusListener > & xControl, const css::util::URL& aURL) override; + virtual void SAL_CALL removeStatusListener(const css::uno::Reference< css::frame::XStatusListener > & xControl, const css::util::URL& aURL) override; + + // css::frame::XDispatchInformationProvider + virtual css::uno::Sequence< ::sal_Int16 > SAL_CALL getSupportedCommandGroups( ) override; + virtual css::uno::Sequence< css::frame::DispatchInformation > SAL_CALL getConfigurableDispatchInformation( ::sal_Int16 CommandGroup ) override; + }; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/general.cxx b/extensions/source/bibliography/general.cxx new file mode 100644 index 0000000000..cba8449321 --- /dev/null +++ b/extensions/source/bibliography/general.cxx @@ -0,0 +1,878 @@ +/* -*- 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 <comphelper/processfactory.hxx> +#include <com/sun/star/beans/XPropertyChangeListener.hpp> +#include <com/sun/star/form/XBoundComponent.hpp> +#include <com/sun/star/sdbc/XRowSet.hpp> +#include <com/sun/star/sdb/XColumn.hpp> +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/uri/UriReferenceFactory.hpp> + +#include <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <cppuhelper/implbase.hxx> +#include <utility> +#include <vcl/event.hxx> +#include <vcl/mnemonic.hxx> +#include "general.hxx" +#include "bibresid.hxx" +#include "datman.hxx" +#include "bibconfig.hxx" +#include <strings.hrc> +#include "bibmod.hxx" +#include <helpids.h> +#include <algorithm> +#include <sfx2/filedlghelper.hxx> +#include <sfx2/objsh.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::form; +using namespace ::com::sun::star::sdb; + +namespace +{ +/// Tries to split rText into rURL and nPageNumber. +bool SplitUrlAndPage(const OUString& rText, OUString& rUrl, int& nPageNumber) +{ + uno::Reference<uri::XUriReferenceFactory> xUriReferenceFactory + = uri::UriReferenceFactory::create(comphelper::getProcessComponentContext()); + uno::Reference<uri::XUriReference> xUriRef; + try + { + xUriRef = xUriReferenceFactory->parse(rText); + } + catch (const uno::Exception& rException) + { + SAL_WARN("extensions.biblio", + "SplitUrlAndPage: failed to parse url: " << rException.Message); + return false; + } + + OUString aPagePrefix("page="); + if (!xUriRef->getFragment().startsWith(aPagePrefix)) + { + return false; + } + + nPageNumber = o3tl::toInt32(xUriRef->getFragment().subView(aPagePrefix.getLength())); + xUriRef->clearFragment(); + rUrl = xUriRef->getUriReference(); + return true; +} + +/// Merges rUrl and rPageSB to a URL string. +OUString MergeUrlAndPage(const OUString& rUrl, const weld::SpinButton& rPageSB) +{ + if (!rPageSB.get_sensitive()) + { + return rUrl; + } + + uno::Reference<uri::XUriReferenceFactory> xUriReferenceFactory + = uri::UriReferenceFactory::create(comphelper::getProcessComponentContext()); + uno::Reference<uri::XUriReference> xUriRef; + try + { + xUriRef = xUriReferenceFactory->parse(rUrl); + } + catch (const uno::Exception& rException) + { + SAL_WARN("extensions.biblio", + "MergeUrlAndPage: failed to parse url: " << rException.Message); + return rUrl; + } + + OUString aFragment("page=" + OUString::number(rPageSB.get_value())); + xUriRef->setFragment(aFragment); + return xUriRef->getUriReference(); +} +} + +static OUString lcl_GetColumnName( const Mapping* pMapping, sal_uInt16 nIndexPos ) +{ + BibConfig* pBibConfig = BibModul::GetConfig(); + OUString sRet = pBibConfig->GetDefColumnName(nIndexPos); + if(pMapping) + for(const auto & aColumnPair : pMapping->aColumnPairs) + { + if(aColumnPair.sLogicalColumnName == sRet) + { + sRet = aColumnPair.sRealColumnName; + break; + } + } + return sRet; +} + +BibGeneralPage::BibGeneralPage(vcl::Window* pParent, BibDataManager* pMan) + : InterimItemWindow(pParent, "modules/sbibliography/ui/generalpage.ui", "GeneralPage") + , BibShortCutHandler(this) + , xScrolledWindow(m_xBuilder->weld_scrolled_window("scrolledwindow")) + , xGrid(m_xBuilder->weld_widget("grid")) + , xIdentifierFT(m_xBuilder->weld_label("shortname")) + , xIdentifierED(m_xBuilder->weld_entry("shortnamecontrol")) + , xAuthTypeFT(m_xBuilder->weld_label("authtype")) + , xAuthTypeLB(m_xBuilder->weld_combo_box("authtypecontrol")) + , xYearFT(m_xBuilder->weld_label("year")) + , xYearED(m_xBuilder->weld_entry("yearcontrol")) + , xAuthorFT(m_xBuilder->weld_label("authors")) + , xAuthorED(m_xBuilder->weld_entry("authorscontrol")) + , xTitleFT(m_xBuilder->weld_label("title")) + , xTitleED(m_xBuilder->weld_entry("titlecontrol")) + , xPublisherFT(m_xBuilder->weld_label("publisher")) + , xPublisherED(m_xBuilder->weld_entry("publishercontrol")) + , xAddressFT(m_xBuilder->weld_label("address")) + , xAddressED(m_xBuilder->weld_entry("addresscontrol")) + , xISBNFT(m_xBuilder->weld_label("isbn")) + , xISBNED(m_xBuilder->weld_entry("isbncontrol")) + , xChapterFT(m_xBuilder->weld_label("chapter")) + , xChapterED(m_xBuilder->weld_entry("chaptercontrol")) + , xPagesFT(m_xBuilder->weld_label("pages")) + , xPagesED(m_xBuilder->weld_entry("pagescontrol")) + , xEditorFT(m_xBuilder->weld_label("editor")) + , xEditorED(m_xBuilder->weld_entry("editorcontrol")) + , xEditionFT(m_xBuilder->weld_label("edition")) + , xEditionED(m_xBuilder->weld_entry("editioncontrol")) + , xBooktitleFT(m_xBuilder->weld_label("booktitle")) + , xBooktitleED(m_xBuilder->weld_entry("booktitlecontrol")) + , xVolumeFT(m_xBuilder->weld_label("volume")) + , xVolumeED(m_xBuilder->weld_entry("volumecontrol")) + , xHowpublishedFT(m_xBuilder->weld_label("publicationtype")) + , xHowpublishedED(m_xBuilder->weld_entry("publicationtypecontrol")) + , xOrganizationsFT(m_xBuilder->weld_label("organization")) + , xOrganizationsED(m_xBuilder->weld_entry("organizationcontrol")) + , xInstitutionFT(m_xBuilder->weld_label("institution")) + , xInstitutionED(m_xBuilder->weld_entry("institutioncontrol")) + , xSchoolFT(m_xBuilder->weld_label("university")) + , xSchoolED(m_xBuilder->weld_entry("universitycontrol")) + , xReportTypeFT(m_xBuilder->weld_label("reporttype")) + , xReportTypeED(m_xBuilder->weld_entry("reporttypecontrol")) + , xMonthFT(m_xBuilder->weld_label("month")) + , xMonthED(m_xBuilder->weld_entry("monthcontrol")) + , xJournalFT(m_xBuilder->weld_label("journal")) + , xJournalED(m_xBuilder->weld_entry("journalcontrol")) + , xNumberFT(m_xBuilder->weld_label("number")) + , xNumberED(m_xBuilder->weld_entry("numbercontrol")) + , xSeriesFT(m_xBuilder->weld_label("series")) + , xSeriesED(m_xBuilder->weld_entry("seriescontrol")) + , xAnnoteFT(m_xBuilder->weld_label("annotation")) + , xAnnoteED(m_xBuilder->weld_entry("annotationcontrol")) + , xNoteFT(m_xBuilder->weld_label("note")) + , xNoteED(m_xBuilder->weld_entry("notecontrol")) + , xURLFT(m_xBuilder->weld_label("url")) + , xURLED(m_xBuilder->weld_entry("urlcontrol")) + , xCustom1FT(m_xBuilder->weld_label("custom1")) + , xCustom1ED(m_xBuilder->weld_entry("custom1control")) + , xCustom2FT(m_xBuilder->weld_label("custom2")) + , xCustom2ED(m_xBuilder->weld_entry("custom2control")) + , xCustom3FT(m_xBuilder->weld_label("custom3")) + , xCustom3ED(m_xBuilder->weld_entry("custom3control")) + , xCustom4FT(m_xBuilder->weld_label("custom4")) + , xCustom4ED(m_xBuilder->weld_entry("custom4control")) + , xCustom5FT(m_xBuilder->weld_label("custom5")) + , xCustom5ED(m_xBuilder->weld_entry("custom5control")) + , m_xLocalURLFT(m_xBuilder->weld_label("localurl")) + , m_xLocalURLED(m_xBuilder->weld_entry("localurlcontrol")) + , m_xLocalBrowseButton(m_xBuilder->weld_button("localbrowse")) + , m_xLocalPageCB(m_xBuilder->weld_check_button("localpagecb")) + , m_xLocalPageSB(m_xBuilder->weld_spin_button("localpagesb")) + , pDatMan(pMan) +{ + SetStyle(GetStyle() | WB_DIALOGCONTROL); + + BibConfig* pBibConfig = BibModul::GetConfig(); + BibDBDescriptor aDesc; + aDesc.sDataSource = pDatMan->getActiveDataSource(); + aDesc.sTableOrQuery = pDatMan->getActiveDataTable(); + aDesc.nCommandType = CommandType::TABLE; + const Mapping* pMapping = pBibConfig->GetMapping(aDesc); + + xIdentifierED->connect_key_press(LINK(this, BibGeneralPage, FirstElementKeyInputHdl)); + + AddControlWithError(lcl_GetColumnName(pMapping, IDENTIFIER_POS), + xIdentifierFT->get_label(), *xIdentifierED, + sTableErrorString, HID_BIB_IDENTIFIER_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, AUTHORITYTYPE_POS), + xAuthTypeFT->get_label(), *xAuthTypeLB, + sTableErrorString, HID_BIB_AUTHORITYTYPE_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, YEAR_POS), + xYearFT->get_label(), *xYearED, + sTableErrorString, HID_BIB_YEAR_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, AUTHOR_POS), + xAuthorFT->get_label(), *xAuthorED, + sTableErrorString, HID_BIB_AUTHOR_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, TITLE_POS), + xTitleFT->get_label(), *xTitleED, + sTableErrorString, HID_BIB_TITLE_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, PUBLISHER_POS), + xPublisherFT->get_label(), *xPublisherED, + sTableErrorString, HID_BIB_PUBLISHER_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, ADDRESS_POS), + xAddressFT->get_label(), *xAddressED, + sTableErrorString, HID_BIB_ADDRESS_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, ISBN_POS), + xISBNFT->get_label(), *xISBNED, + sTableErrorString, HID_BIB_ISBN_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, CHAPTER_POS), + xChapterFT->get_label(), *xChapterED, + sTableErrorString, HID_BIB_CHAPTER_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, PAGES_POS), + xPagesFT->get_label(), *xPagesED, + sTableErrorString, HID_BIB_PAGES_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, EDITOR_POS), + xEditorFT->get_label(), *xEditorED, + sTableErrorString, HID_BIB_EDITOR_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, EDITION_POS), + xEditionFT->get_label(), *xEditionED, + sTableErrorString, HID_BIB_EDITION_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, BOOKTITLE_POS), + xBooktitleFT->get_label(), *xBooktitleED, + sTableErrorString, HID_BIB_BOOKTITLE_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, VOLUME_POS), + xVolumeFT->get_label(), *xVolumeED, + sTableErrorString, HID_BIB_VOLUME_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, HOWPUBLISHED_POS), + xHowpublishedFT->get_label(), *xHowpublishedED, + sTableErrorString, HID_BIB_HOWPUBLISHED_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, ORGANIZATIONS_POS), + xOrganizationsFT->get_label(), *xOrganizationsED, + sTableErrorString, HID_BIB_ORGANIZATIONS_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, INSTITUTION_POS), + xInstitutionFT->get_label(), *xInstitutionED, + sTableErrorString, HID_BIB_INSTITUTION_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, SCHOOL_POS), + xSchoolFT->get_label(), *xSchoolED, + sTableErrorString, HID_BIB_SCHOOL_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, REPORTTYPE_POS), + xReportTypeFT->get_label(), *xReportTypeED, + sTableErrorString, HID_BIB_REPORTTYPE_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, MONTH_POS), + xMonthFT->get_label(), *xMonthED, + sTableErrorString, HID_BIB_MONTH_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, JOURNAL_POS), + xJournalFT->get_label(), *xJournalED, + sTableErrorString, HID_BIB_JOURNAL_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, NUMBER_POS), + xNumberFT->get_label(), *xNumberED, + sTableErrorString, HID_BIB_NUMBER_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, SERIES_POS), + xSeriesFT->get_label(), *xSeriesED, + sTableErrorString, HID_BIB_SERIES_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, ANNOTE_POS), + xAnnoteFT->get_label(), *xAnnoteED, + sTableErrorString, HID_BIB_ANNOTE_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, NOTE_POS), + xNoteFT->get_label(), *xNoteED, + sTableErrorString, HID_BIB_NOTE_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, URL_POS), + xURLFT->get_label(), *xURLED, + sTableErrorString, HID_BIB_URL_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, CUSTOM1_POS), + xCustom1FT->get_label(), *xCustom1ED, + sTableErrorString, HID_BIB_CUSTOM1_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, CUSTOM2_POS), + xCustom2FT->get_label(), *xCustom2ED, + sTableErrorString, HID_BIB_CUSTOM2_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, CUSTOM3_POS), + xCustom3FT->get_label(), *xCustom3ED, + sTableErrorString, HID_BIB_CUSTOM3_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, CUSTOM4_POS), + xCustom4FT->get_label(), *xCustom4ED, + sTableErrorString, HID_BIB_CUSTOM4_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, CUSTOM5_POS), + xCustom5FT->get_label(), *xCustom5ED, + sTableErrorString, HID_BIB_CUSTOM5_POS); + + AddControlWithError(lcl_GetColumnName(pMapping, LOCAL_URL_POS), + m_xLocalURLFT->get_label(), *m_xLocalURLED, + sTableErrorString, HID_BIB_LOCAL_URL_POS); + + m_xLocalBrowseButton->connect_clicked(LINK(this, BibGeneralPage, BrowseHdl)); + m_xLocalPageCB->connect_toggled(LINK(this, BibGeneralPage, PageNumHdl)); + + m_xLocalURLED->connect_key_press(LINK(this, BibGeneralPage, LastElementKeyInputHdl)); + + if(!sTableErrorString.isEmpty()) + sTableErrorString = BibResId(ST_ERROR_PREFIX) + sTableErrorString; + + SetText(BibResId(ST_TYPE_TITLE)); + + Size aSize(LogicToPixel(Size(0, 209), MapMode(MapUnit::MapAppFont))); + set_height_request(aSize.Height()); +} + +IMPL_LINK_NOARG(BibGeneralPage, BrowseHdl, weld::Button&, void) +{ + sfx2::FileDialogHelper aFileDlg(ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE, + FileDialogFlags::NONE, GetFrameWeld()); + OUString aPath = m_xLocalURLED->get_text(); + if (!aPath.isEmpty()) + { + aFileDlg.SetDisplayDirectory(aPath); + } + else + { + OUString aBaseURL; + if (SfxObjectShell* pShell = SfxObjectShell::Current()) + { + aBaseURL = pShell->getDocumentBaseURL(); + } + if (!aBaseURL.isEmpty()) + { + aFileDlg.SetDisplayDirectory(aBaseURL); + } + } + + if (aFileDlg.Execute() != ERRCODE_NONE) + { + return; + } + + weld::Entry& rEntry = *m_xLocalURLED; + rEntry.set_text(aFileDlg.GetPath()); +}; + +IMPL_LINK(BibGeneralPage, PageNumHdl, weld::Toggleable&, rPageCB, void) +{ + weld::SpinButton& rPageSB = *m_xLocalPageSB; + if (rPageCB.get_active()) + { + rPageSB.set_sensitive(true); + rPageSB.set_value(1); + } + else + { + rPageSB.set_sensitive(false); + } +} + +IMPL_LINK(BibGeneralPage, FirstElementKeyInputHdl, const KeyEvent&, rKeyEvent, bool) +{ + sal_uInt16 nCode = rKeyEvent.GetKeyCode().GetCode(); + bool bShift = rKeyEvent.GetKeyCode().IsShift(); + bool bCtrl = rKeyEvent.GetKeyCode().IsMod1(); + bool bAlt = rKeyEvent.GetKeyCode().IsMod2(); + if (KEY_TAB == nCode && bShift && !bCtrl && !bAlt) + { + SaveChanges(); + uno::Reference<sdbc::XRowSet> xRowSet(pDatMan->getForm(), UNO_QUERY); + if (xRowSet.is() && !xRowSet->isFirst()) + xRowSet->previous(); + m_xLocalURLED->grab_focus(); + m_xLocalURLED->select_region(0, -1); + GainFocusHdl(*m_xLocalURLED); + return true; + } + return false; +} + +void BibGeneralPage::SaveChanges() +{ + Reference< XForm > xForm = pDatMan->getForm(); + Reference< beans::XPropertySet > xProps( xForm, UNO_QUERY ); + Reference< sdbc::XResultSetUpdate > xResUpd( xProps, UNO_QUERY ); + if (!xResUpd.is() ) + return; + + Any aModified = xProps->getPropertyValue( "IsModified" ); + bool bFlag = false; + if ( !( aModified >>= bFlag ) || !bFlag ) + return; + + try + { + Any aNew = xProps->getPropertyValue( "IsNew" ); + aNew >>= bFlag; + if ( bFlag ) + xResUpd->insertRow(); + else + xResUpd->updateRow(); + } + catch( const uno::Exception&) {} +} + +IMPL_LINK(BibGeneralPage, LastElementKeyInputHdl, const KeyEvent&, rKeyEvent, bool) +{ + sal_uInt16 nCode = rKeyEvent.GetKeyCode().GetCode(); + bool bShift = rKeyEvent.GetKeyCode().IsShift(); + bool bCtrl = rKeyEvent.GetKeyCode().IsMod1(); + bool bAlt = rKeyEvent.GetKeyCode().IsMod2(); + if (KEY_TAB != nCode || bShift || bCtrl || bAlt) + return false; + SaveChanges(); + uno::Reference<sdbc::XRowSet> xRowSet(pDatMan->getForm(), UNO_QUERY); + if (xRowSet.is()) + { + if (xRowSet->isLast()) + { + uno::Reference<sdbc::XResultSetUpdate> xUpdateCursor(xRowSet, UNO_QUERY); + if (xUpdateCursor.is()) + xUpdateCursor->moveToInsertRow(); + } + else + (void)xRowSet->next(); + } + xIdentifierED->grab_focus(); + xIdentifierED->select_region(0, -1); + GainFocusHdl(*xIdentifierED); + return true; +} + +BibGeneralPage::~BibGeneralPage() +{ + disposeOnce(); +} + +class ChangeListener : public cppu::WeakImplHelper<css::beans::XPropertyChangeListener> +{ +public: + explicit ChangeListener(css::uno::Reference<css::beans::XPropertySet> xPropSet) + : m_xPropSet(std::move(xPropSet)) + , m_bSelfChanging(false) + { + } + + virtual void SAL_CALL disposing(lang::EventObject const &) override + { + } + + virtual void start() = 0; + virtual void stop() + { + WriteBack(); + } + + virtual void WriteBack() = 0; + +protected: + css::uno::Reference<css::beans::XPropertySet> m_xPropSet; + bool m_bSelfChanging; +}; + +namespace +{ + class EntryChangeListener : public ChangeListener + { + public: + explicit EntryChangeListener(weld::Entry& rEntry, const css::uno::Reference<css::beans::XPropertySet>& rPropSet, + BibGeneralPage& rPage) + : ChangeListener(rPropSet) + , m_rEntry(rEntry) + , m_rPage(rPage) + { + rEntry.connect_focus_out(LINK(this, EntryChangeListener, LoseFocusHdl)); + setValue(rPropSet->getPropertyValue("Text")); + } + + virtual void SAL_CALL propertyChange(const css::beans::PropertyChangeEvent& evt) override + { + if (m_bSelfChanging) + return; + setValue(evt.NewValue); + } + + virtual void start() override + { + m_xPropSet->addPropertyChangeListener("Text", this); + } + + virtual void stop() override + { + m_xPropSet->removePropertyChangeListener("Text", this); + ChangeListener::stop(); + } + + private: + weld::Entry& m_rEntry; + BibGeneralPage& m_rPage; + + DECL_LINK(LoseFocusHdl, weld::Widget&, void); + + /// Updates the UI widget(s) based on rValue. + void setValue(const css::uno::Any& rValue) + { + OUString sNewName; + rValue >>= sNewName; + if (&m_rEntry == &m_rPage.GetLocalURLED()) + { + OUString aUrl; + int nPageNumber; + if (SplitUrlAndPage(sNewName, aUrl, nPageNumber)) + { + m_rEntry.set_text(aUrl); + m_rPage.GetLocalPageCB().set_active(true); + m_rPage.GetLocalPageSB().set_sensitive(true); + m_rPage.GetLocalPageSB().set_value(nPageNumber); + } + else + { + m_rEntry.set_text(sNewName); + m_rPage.GetLocalPageCB().set_active(false); + m_rPage.GetLocalPageSB().set_sensitive(false); + m_rPage.GetLocalPageSB().set_value(0); + } + } + else + { + m_rEntry.set_text(sNewName); + } + + m_rEntry.save_value(); + if (&m_rEntry == &m_rPage.GetLocalURLED()) + { + m_rPage.GetLocalPageSB().save_value(); + } + } + + /// Updates m_xPropSet based on the UI widget(s). + virtual void WriteBack() override + { + bool bLocalURL = &m_rEntry == &m_rPage.GetLocalURLED() + && m_rPage.GetLocalPageSB().get_value_changed_from_saved(); + if (!m_rEntry.get_value_changed_from_saved() && !bLocalURL) + return; + + m_bSelfChanging = true; + + OUString aText; + if (&m_rEntry == &m_rPage.GetLocalURLED()) + { + aText = MergeUrlAndPage(m_rEntry.get_text(), m_rPage.GetLocalPageSB()); + } + else + { + aText = m_rEntry.get_text(); + } + m_xPropSet->setPropertyValue("Text", Any(aText)); + + css::uno::Reference<css::form::XBoundComponent> xBound(m_xPropSet, css::uno::UNO_QUERY); + if (xBound.is()) + xBound->commit(); + + m_bSelfChanging = false; + m_rEntry.save_value(); + if (&m_rEntry == &m_rPage.GetLocalURLED()) + { + m_rPage.GetLocalPageSB().save_value(); + } + } + + }; + + IMPL_LINK_NOARG(EntryChangeListener, LoseFocusHdl, weld::Widget&, void) + { + WriteBack(); + } + + class ComboBoxChangeListener : public ChangeListener + { + public: + explicit ComboBoxChangeListener(weld::ComboBox& rComboBox, const css::uno::Reference<css::beans::XPropertySet>& rPropSet) + : ChangeListener(rPropSet) + , m_rComboBox(rComboBox) + { + rComboBox.connect_changed(LINK(this, ComboBoxChangeListener, ChangeHdl)); + setValue(rPropSet->getPropertyValue("SelectedItems")); + } + + virtual void SAL_CALL propertyChange(const css::beans::PropertyChangeEvent& evt) override + { + if (m_bSelfChanging) + return; + setValue(evt.NewValue); + } + + virtual void start() override + { + m_xPropSet->addPropertyChangeListener("SelectedItems", this); + } + + virtual void stop() override + { + m_xPropSet->removePropertyChangeListener("SelectedItems", this); + ChangeListener::stop(); + } + + private: + weld::ComboBox& m_rComboBox; + + DECL_LINK(ChangeHdl, weld::ComboBox&, void); + + void setValue(const css::uno::Any& rValue) + { + sal_Int16 nSelection = -1; + Sequence<sal_Int16> aSelection; + rValue >>= aSelection; + if (aSelection.hasElements()) + nSelection = aSelection[0]; + + m_rComboBox.set_active(nSelection); + m_rComboBox.save_value(); + } + + virtual void WriteBack() override + { + if (!m_rComboBox.get_value_changed_from_saved()) + return; + m_bSelfChanging = true; + + Sequence<sal_Int16> aSelection{ o3tl::narrowing<sal_Int16>(m_rComboBox.get_active()) }; + m_xPropSet->setPropertyValue("SelectedItems", Any(aSelection)); + + css::uno::Reference<css::form::XBoundComponent> xBound(m_xPropSet, css::uno::UNO_QUERY); + if (xBound.is()) + xBound->commit(); + + m_bSelfChanging = false; + m_rComboBox.save_value(); + } + }; + + IMPL_LINK_NOARG(ComboBoxChangeListener, ChangeHdl, weld::ComboBox&, void) + { + WriteBack(); + } +} + +void BibGeneralPage::dispose() +{ + for (auto& listener : maChangeListeners) + listener->stop(); + maChangeListeners.clear(); + + SaveChanges(); + + xScrolledWindow.reset(); + xGrid.reset(); + xIdentifierFT.reset(); + xIdentifierED.reset(); + xAuthTypeFT.reset(); + xAuthTypeLB.reset(); + xYearFT.reset(); + xYearED.reset(); + xAuthorFT.reset(); + xAuthorED.reset(); + xTitleFT.reset(); + xTitleED.reset(); + xPublisherFT.reset(); + xPublisherED.reset(); + xAddressFT.reset(); + xAddressED.reset(); + xISBNFT.reset(); + xISBNED.reset(); + xChapterFT.reset(); + xChapterED.reset(); + xPagesFT.reset(); + xPagesED.reset(); + xEditorFT.reset(); + xEditorED.reset(); + xEditionFT.reset(); + xEditionED.reset(); + xBooktitleFT.reset(); + xBooktitleED.reset(); + xVolumeFT.reset(); + xVolumeED.reset(); + xHowpublishedFT.reset(); + xHowpublishedED.reset(); + xOrganizationsFT.reset(); + xOrganizationsED.reset(); + xInstitutionFT.reset(); + xInstitutionED.reset(); + xSchoolFT.reset(); + xSchoolED.reset(); + xReportTypeFT.reset(); + xReportTypeED.reset(); + xMonthFT.reset(); + xMonthED.reset(); + xJournalFT.reset(); + xJournalED.reset(); + xNumberFT.reset(); + xNumberED.reset(); + xSeriesFT.reset(); + xSeriesED.reset(); + xAnnoteFT.reset(); + xAnnoteED.reset(); + xNoteFT.reset(); + xNoteED.reset(); + xURLFT.reset(); + xURLED.reset(); + xCustom1FT.reset(); + xCustom1ED.reset(); + xCustom2FT.reset(); + xCustom2ED.reset(); + xCustom3FT.reset(); + xCustom3ED.reset(); + xCustom4FT.reset(); + xCustom4ED.reset(); + xCustom5FT.reset(); + xCustom5ED.reset(); + m_xLocalURLFT.reset(); + m_xLocalURLED.reset(); + m_xLocalBrowseButton.reset(); + m_xLocalPageCB.reset(); + m_xLocalPageSB.reset(); + InterimItemWindow::dispose(); +} + +weld::Entry& BibGeneralPage::GetLocalURLED() { return *m_xLocalURLED; } + +weld::CheckButton& BibGeneralPage::GetLocalPageCB() { return *m_xLocalPageCB; } + +weld::SpinButton& BibGeneralPage::GetLocalPageSB() { return *m_xLocalPageSB; } + +bool BibGeneralPage::AddXControl(const OUString& rName, weld::Entry& rEntry) +{ + uno::Reference< awt::XControlModel > xCtrModel; + try + { + xCtrModel = pDatMan->loadControlModel(rName, false); + if ( xCtrModel.is() ) + { + uno::Reference< beans::XPropertySet > xPropSet( xCtrModel, UNO_QUERY ); + + if( xPropSet.is()) + { + maChangeListeners.emplace_back(new EntryChangeListener(rEntry, xPropSet, *this)); + maChangeListeners.back()->start(); + if (&rEntry == m_xLocalURLED.get()) + { + m_aURLListener = maChangeListeners.back(); + m_xLocalPageSB->connect_focus_out(LINK(this, BibGeneralPage, LosePageFocusHdl)); + } + } + } + } + catch(const Exception&) + { + OSL_FAIL("BibGeneralPage::AddXControl: something went wrong!"); + } + return xCtrModel.is(); +} + +IMPL_LINK_NOARG(BibGeneralPage, LosePageFocusHdl, weld::Widget&, void) +{ + m_aURLListener->WriteBack(); +} + +IMPL_LINK(BibGeneralPage, GainFocusHdl, weld::Widget&, rWidget, void) +{ + int x, y, width, height; + if (!rWidget.get_extents_relative_to(*xGrid, x, y, width, height)) + return; + + int bottom = y + height; + int nVScrollPos = xScrolledWindow->vadjustment_get_value(); + if (y < nVScrollPos || bottom > nVScrollPos + xScrolledWindow->vadjustment_get_page_size()) + xScrolledWindow->vadjustment_set_value(y); + + int right = x + width; + int nHScrollPos = xScrolledWindow->hadjustment_get_value(); + if (x < nHScrollPos || right > nHScrollPos + xScrolledWindow->hadjustment_get_page_size()) + xScrolledWindow->hadjustment_set_value(x); +} + +template<class Target> void BibGeneralPage::AddControlWithError(const OUString& rColumnName, const OUString& rColumnUIName, + Target& rWidget, OUString& rErrorString, const OUString& rHelpId) +{ + rWidget.set_help_id(rHelpId); + rWidget.connect_focus_in(LINK(this, BibGeneralPage, GainFocusHdl)); + bool bSuccess = AddXControl(rColumnName, rWidget); + if (!bSuccess) + { + if( !rErrorString.isEmpty() ) + rErrorString += "\n"; + + rErrorString += MnemonicGenerator::EraseAllMnemonicChars(rColumnUIName); + } +} + +bool BibGeneralPage::AddXControl(const OUString& rName, weld::ComboBox& rList) +{ + uno::Reference< awt::XControlModel > xCtrModel; + try + { + xCtrModel = pDatMan->loadControlModel(rName, true); + if ( xCtrModel.is() ) + { + uno::Reference< beans::XPropertySet > xPropSet( xCtrModel, UNO_QUERY ); + + if( xPropSet.is()) + { + css::uno::Sequence<OUString> aEntries; + xPropSet->getPropertyValue("StringItemList") >>= aEntries; + for (const OUString& rString : std::as_const(aEntries)) + rList.append_text(rString); + + sal_Int16 nSelection = -1; + Sequence<sal_Int16> aSelection; + xPropSet->getPropertyValue("SelectedItems") >>= aSelection; + if (aSelection.hasElements()) + nSelection = aSelection[0]; + + rList.set_active(nSelection); + rList.save_value(); + + maChangeListeners.emplace_back(new ComboBoxChangeListener(rList, xPropSet)); + maChangeListeners.back()->start(); + } + } + } + catch(const Exception&) + { + OSL_FAIL("BibGeneralPage::AddXControl: something went wrong!"); + } + return xCtrModel.is(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/general.hxx b/extensions/source/bibliography/general.hxx new file mode 100644 index 0000000000..9708174ffa --- /dev/null +++ b/extensions/source/bibliography/general.hxx @@ -0,0 +1,158 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <vcl/InterimItemWindow.hxx> +#include "bibshortcuthandler.hxx" + + +class BibDataManager; +#define TYPE_COUNT 22 +#define FIELD_COUNT 31 + +class ChangeListener; + +class BibGeneralPage : public InterimItemWindow + , public BibShortCutHandler +{ + std::unique_ptr<weld::ScrolledWindow> xScrolledWindow; + std::unique_ptr<weld::Widget> xGrid; + + std::unique_ptr<weld::Label> xIdentifierFT; + std::unique_ptr<weld::Entry> xIdentifierED; + + std::unique_ptr<weld::Label> xAuthTypeFT; + std::unique_ptr<weld::ComboBox> xAuthTypeLB; + std::unique_ptr<weld::Label> xYearFT; + std::unique_ptr<weld::Entry> xYearED; + + std::unique_ptr<weld::Label> xAuthorFT; + std::unique_ptr<weld::Entry> xAuthorED; + std::unique_ptr<weld::Label> xTitleFT; + std::unique_ptr<weld::Entry> xTitleED; + + std::unique_ptr<weld::Label> xPublisherFT; + std::unique_ptr<weld::Entry> xPublisherED; + std::unique_ptr<weld::Label> xAddressFT; + std::unique_ptr<weld::Entry> xAddressED; + std::unique_ptr<weld::Label> xISBNFT; + std::unique_ptr<weld::Entry> xISBNED; + + std::unique_ptr<weld::Label> xChapterFT; + std::unique_ptr<weld::Entry> xChapterED; + std::unique_ptr<weld::Label> xPagesFT; + std::unique_ptr<weld::Entry> xPagesED; + + std::unique_ptr<weld::Label> xEditorFT; + std::unique_ptr<weld::Entry> xEditorED; + std::unique_ptr<weld::Label> xEditionFT; + std::unique_ptr<weld::Entry> xEditionED; + + std::unique_ptr<weld::Label> xBooktitleFT; + std::unique_ptr<weld::Entry> xBooktitleED; + std::unique_ptr<weld::Label> xVolumeFT; + std::unique_ptr<weld::Entry> xVolumeED; + std::unique_ptr<weld::Label> xHowpublishedFT; + std::unique_ptr<weld::Entry> xHowpublishedED; + + std::unique_ptr<weld::Label> xOrganizationsFT; + std::unique_ptr<weld::Entry> xOrganizationsED; + std::unique_ptr<weld::Label> xInstitutionFT; + std::unique_ptr<weld::Entry> xInstitutionED; + std::unique_ptr<weld::Label> xSchoolFT; + std::unique_ptr<weld::Entry> xSchoolED; + + std::unique_ptr<weld::Label> xReportTypeFT; + std::unique_ptr<weld::Entry> xReportTypeED; + std::unique_ptr<weld::Label> xMonthFT; + std::unique_ptr<weld::Entry> xMonthED; + + std::unique_ptr<weld::Label> xJournalFT; + std::unique_ptr<weld::Entry> xJournalED; + std::unique_ptr<weld::Label> xNumberFT; + std::unique_ptr<weld::Entry> xNumberED; + std::unique_ptr<weld::Label> xSeriesFT; + std::unique_ptr<weld::Entry> xSeriesED; + + std::unique_ptr<weld::Label> xAnnoteFT; + std::unique_ptr<weld::Entry> xAnnoteED; + std::unique_ptr<weld::Label> xNoteFT; + std::unique_ptr<weld::Entry> xNoteED; + std::unique_ptr<weld::Label> xURLFT; + std::unique_ptr<weld::Entry> xURLED; + + std::unique_ptr<weld::Label> xCustom1FT; + std::unique_ptr<weld::Entry> xCustom1ED; + std::unique_ptr<weld::Label> xCustom2FT; + std::unique_ptr<weld::Entry> xCustom2ED; + std::unique_ptr<weld::Label> xCustom3FT; + std::unique_ptr<weld::Entry> xCustom3ED; + std::unique_ptr<weld::Label> xCustom4FT; + std::unique_ptr<weld::Entry> xCustom4ED; + std::unique_ptr<weld::Label> xCustom5FT; + std::unique_ptr<weld::Entry> xCustom5ED; + std::unique_ptr<weld::Label> m_xLocalURLFT; + std::unique_ptr<weld::Entry> m_xLocalURLED; + std::unique_ptr<weld::Button> m_xLocalBrowseButton; + std::unique_ptr<weld::CheckButton> m_xLocalPageCB; + std::unique_ptr<weld::SpinButton> m_xLocalPageSB; + + OUString sTableErrorString; + + std::vector<rtl::Reference<ChangeListener>> maChangeListeners; + rtl::Reference<ChangeListener> m_aURLListener; + + BibDataManager* pDatMan; + + bool AddXControl(const OUString& rName, weld::Entry& rEntry); + bool AddXControl(const OUString& rName, weld::ComboBox& rList); + + template<class Target> void AddControlWithError(const OUString& rColumnName, const OUString& rColumnUIName, + Target& rWidget, OUString& rErrorString, const OUString& rHelpId); + + void SaveChanges(); + + DECL_LINK(GainFocusHdl, weld::Widget&, void); + + DECL_LINK(FirstElementKeyInputHdl, const KeyEvent&, bool); + DECL_LINK(LastElementKeyInputHdl, const KeyEvent&, bool); + DECL_LINK(BrowseHdl, weld::Button&, void); + DECL_LINK(PageNumHdl, weld::Toggleable&, void); + DECL_LINK(LosePageFocusHdl, weld::Widget&, void); + +public: + BibGeneralPage(vcl::Window* pParent, BibDataManager* pDatMan); + virtual ~BibGeneralPage() override; + virtual void dispose() override; + + const OUString& GetErrorString() const + { + return sTableErrorString; + } + + weld::Entry& GetLocalURLED(); + weld::CheckButton& GetLocalPageCB(); + weld::SpinButton& GetLocalPageSB(); +}; + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/loadlisteneradapter.cxx b/extensions/source/bibliography/loadlisteneradapter.cxx new file mode 100644 index 0000000000..27f4ea2e50 --- /dev/null +++ b/extensions/source/bibliography/loadlisteneradapter.cxx @@ -0,0 +1,185 @@ +/* -*- 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 "loadlisteneradapter.hxx" +#include <osl/diagnose.h> +#include <rtl/ref.hxx> + + +namespace bib +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::form; + + OComponentListener::~OComponentListener() + { + ::osl::MutexGuard aGuard( m_rMutex ); + if ( m_xAdapter.is() ) + m_xAdapter->dispose(); + } + + + void OComponentListener::setAdapter( OComponentAdapterBase* pAdapter ) + { + ::osl::MutexGuard aGuard( m_rMutex ); + m_xAdapter = pAdapter; + } + + OComponentAdapterBase::OComponentAdapterBase( const Reference< XComponent >& _rxComp ) + :m_xComponent( _rxComp ) + ,m_pListener( nullptr ) + ,m_bListening( false ) + { + OSL_ENSURE( m_xComponent.is(), "OComponentAdapterBase::OComponentAdapterBase: invalid component!" ); + } + + + void OComponentAdapterBase::Init( OComponentListener* _pListener ) + { + OSL_ENSURE( !m_pListener, "OComponentAdapterBase::Init: already initialized!" ); + OSL_ENSURE( _pListener, "OComponentAdapterBase::Init: invalid listener!" ); + + m_pListener = _pListener; + if ( m_pListener ) + m_pListener->setAdapter( this ); + + startComponentListening( ); + m_bListening = true; + } + + + OComponentAdapterBase::~OComponentAdapterBase() + { + } + + + void OComponentAdapterBase::dispose() + { + if ( !m_bListening ) + return; + + ::rtl::Reference< OComponentAdapterBase > xPreventDelete(this); + + disposing(); + + m_pListener->setAdapter(nullptr); + + m_pListener = nullptr; + m_bListening = false; + + m_xComponent = nullptr; + } + + // XEventListener + + + void SAL_CALL OComponentAdapterBase::disposing( const EventObject& ) + { + if (m_pListener) + { + // disconnect the listener + m_pListener->setAdapter( nullptr ); + m_pListener = nullptr; + } + + m_bListening = false; + m_xComponent = nullptr; + } + + OLoadListenerAdapter::OLoadListenerAdapter( const Reference< XLoadable >& _rxLoadable ) + :OComponentAdapterBase( Reference< XComponent >( _rxLoadable, UNO_QUERY ) ) + { + } + + + void OLoadListenerAdapter::startComponentListening() + { + Reference< XLoadable > xLoadable( getComponent(), UNO_QUERY ); + OSL_ENSURE( xLoadable.is(), "OLoadListenerAdapter::OLoadListenerAdapter: invalid object!" ); + if ( xLoadable.is() ) + xLoadable->addLoadListener( this ); + } + + + void SAL_CALL OLoadListenerAdapter::acquire( ) noexcept + { + OLoadListenerAdapter_Base::acquire(); + } + + + void SAL_CALL OLoadListenerAdapter::release( ) noexcept + { + OLoadListenerAdapter_Base::release(); + } + + + void SAL_CALL OLoadListenerAdapter::disposing( const EventObject& _rSource ) + { + OComponentAdapterBase::disposing( _rSource ); + } + + + void OLoadListenerAdapter::disposing() + { + Reference< XLoadable > xLoadable( getComponent(), UNO_QUERY ); + if ( xLoadable.is() ) + xLoadable->removeLoadListener( this ); + } + + + void SAL_CALL OLoadListenerAdapter::loaded( const EventObject& _rEvent ) + { + if ( getLoadListener( ) ) + getLoadListener( )->_loaded( _rEvent ); + } + + + void SAL_CALL OLoadListenerAdapter::unloading( const EventObject& _rEvent ) + { + if ( getLoadListener( ) ) + getLoadListener( )->_unloading( _rEvent ); + } + + + void SAL_CALL OLoadListenerAdapter::unloaded( const EventObject& ) + { + } + + + void SAL_CALL OLoadListenerAdapter::reloading( const EventObject& _rEvent ) + { + if ( getLoadListener( ) ) + getLoadListener( )->_reloading( _rEvent ); + } + + + void SAL_CALL OLoadListenerAdapter::reloaded( const EventObject& _rEvent ) + { + if ( getLoadListener( ) ) + getLoadListener( )->_reloaded( _rEvent ); + } + + +} // namespace bib + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/loadlisteneradapter.hxx b/extensions/source/bibliography/loadlisteneradapter.hxx new file mode 100644 index 0000000000..1031dedca3 --- /dev/null +++ b/extensions/source/bibliography/loadlisteneradapter.hxx @@ -0,0 +1,151 @@ +/* -*- 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 . + */ + +#pragma once + +#include <osl/mutex.hxx> +#include <com/sun/star/lang/XComponent.hpp> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/form/XLoadable.hpp> +#include <rtl/ref.hxx> + + +namespace bib +{ + + + class OComponentAdapterBase; + + class OComponentListener + { + friend class OComponentAdapterBase; + + private: + rtl::Reference<OComponentAdapterBase> m_xAdapter; + ::osl::Mutex& m_rMutex; + protected: + explicit OComponentListener( ::osl::Mutex& _rMutex ) + :m_rMutex( _rMutex ) + { + } + + virtual ~OComponentListener(); + + protected: + void setAdapter( OComponentAdapterBase* _pAdapter ); + }; + + class OComponentAdapterBase + { + friend class OComponentListener; + + private: + css::uno::Reference< css::lang::XComponent > m_xComponent; + OComponentListener* m_pListener; + bool m_bListening : 1; + + // impl method for dispose - virtual, 'cause you at least need to remove the listener from the broadcaster + virtual void disposing() = 0; + + protected: + // attribute access for derivees + const css::uno::Reference< css::lang::XComponent >& + getComponent() const { return m_xComponent; } + OComponentListener* getListener() { return m_pListener; } + + // to be called by derivees which started listening at the component + virtual void startComponentListening() = 0; + + virtual ~OComponentAdapterBase(); + + public: + explicit OComponentAdapterBase( + const css::uno::Reference< css::lang::XComponent >& _rxComp + ); + + // late construction + // can be called from within you ctor, to have you're object fully initialized at the moment of + // the call (which would not be the case when calling this ctor) + void Init( OComponentListener* _pListener ); + + // base for ref-counting, implemented by OComponentAdapter + virtual void SAL_CALL acquire( ) noexcept = 0; + virtual void SAL_CALL release( ) noexcept = 0; + + /// dispose the object - stop listening and such + void dispose(); + + protected: + // XEventListener + /// @throws css::uno::RuntimeException + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ); + }; + + class OLoadListener : public OComponentListener + { + friend class OLoadListenerAdapter; + + protected: + explicit OLoadListener( ::osl::Mutex& _rMutex ) : OComponentListener( _rMutex ) { } + + // XLoadListener equivalents + virtual void _loaded( const css::lang::EventObject& aEvent ) = 0; + virtual void _unloading( const css::lang::EventObject& aEvent ) = 0; + virtual void _reloading( const css::lang::EventObject& aEvent ) = 0; + virtual void _reloaded( const css::lang::EventObject& aEvent ) = 0; + }; + + typedef ::cppu::WeakImplHelper< css::form::XLoadListener > OLoadListenerAdapter_Base; + class OLoadListenerAdapter + :public OLoadListenerAdapter_Base + ,public OComponentAdapterBase + { + protected: + OLoadListener* getLoadListener( ) { return static_cast< OLoadListener* >( getListener() ); } + + protected: + virtual void disposing() override; + virtual void startComponentListening() override; + + public: + explicit OLoadListenerAdapter( + const css::uno::Reference< css::form::XLoadable >& _rxLoadable + ); + + + virtual void SAL_CALL acquire( ) noexcept override; + virtual void SAL_CALL release( ) noexcept override; + + protected: + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& _rSource ) override; + + // XLoadListener + virtual void SAL_CALL loaded( const css::lang::EventObject& aEvent ) override; + virtual void SAL_CALL unloading( const css::lang::EventObject& aEvent ) override; + virtual void SAL_CALL unloaded( const css::lang::EventObject& aEvent ) override; + virtual void SAL_CALL reloading( const css::lang::EventObject& aEvent ) override; + virtual void SAL_CALL reloaded( const css::lang::EventObject& aEvent ) override; + }; + + +} // namespace bib + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/toolbar.cxx b/extensions/source/bibliography/toolbar.cxx new file mode 100644 index 0000000000..e4041c5a80 --- /dev/null +++ b/extensions/source/bibliography/toolbar.cxx @@ -0,0 +1,620 @@ +/* -*- 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 <comphelper/propertyvalue.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/frame/FrameSearchFlag.hpp> +#include "datman.hxx" +#include "toolbar.hxx" +#include <o3tl/any.hxx> +#include <svtools/miscopt.hxx> +#include <svtools/imgdef.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/mnemonic.hxx> +#include <vcl/event.hxx> +#include <vcl/weldutils.hxx> +#include <bitmaps.hlst> + +#include "bibtools.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; + + +// Constants -------------------------------------------------------------- + + +BibToolBarListener::BibToolBarListener(BibToolBar *pTB, OUString aStr, ToolBoxItemId nId): + nIndex(nId), + aCommand(std::move(aStr)), + pToolBar(pTB) +{ +} + +BibToolBarListener::~BibToolBarListener() +{ +} + +void BibToolBarListener::statusChanged(const css::frame::FeatureStateEvent& rEvt) +{ + if(rEvt.FeatureURL.Complete == aCommand) + { + SolarMutexGuard aGuard; + pToolBar->EnableItem(nIndex,rEvt.IsEnabled); + + css::uno::Any aState=rEvt.State; + if(auto bChecked = o3tl::tryAccess<bool>(aState)) + { + pToolBar->CheckItem(nIndex, *bChecked); + } + + } +}; + + +BibTBListBoxListener::BibTBListBoxListener(BibToolBar *pTB, const OUString& aStr, ToolBoxItemId nId): + BibToolBarListener(pTB,aStr,nId) +{ +} + +BibTBListBoxListener::~BibTBListBoxListener() +{ +} + +void BibTBListBoxListener::statusChanged(const css::frame::FeatureStateEvent& rEvt) +{ + if(rEvt.FeatureURL.Complete != GetCommand()) + return; + + SolarMutexGuard aGuard; + pToolBar->EnableSourceList(rEvt.IsEnabled); + + Any aState = rEvt.State; + if(auto pStringSeq = o3tl::tryAccess<Sequence<OUString>>(aState)) + { + pToolBar->UpdateSourceList(false); + pToolBar->ClearSourceList(); + + const OUString* pStringArray = pStringSeq->getConstArray(); + + sal_uInt32 nCount = pStringSeq->getLength(); + OUString aEntry; + for( sal_uInt32 i=0; i<nCount; i++ ) + { + aEntry = pStringArray[i]; + pToolBar->InsertSourceEntry(aEntry); + } + pToolBar->UpdateSourceList(true); + } + + pToolBar->SelectSourceEntry(rEvt.FeatureDescriptor); +}; + +BibTBQueryMenuListener::BibTBQueryMenuListener(BibToolBar *pTB, const OUString& aStr, ToolBoxItemId nId): + BibToolBarListener(pTB,aStr,nId) +{ +} + +BibTBQueryMenuListener::~BibTBQueryMenuListener() +{ +} + +void BibTBQueryMenuListener::statusChanged(const frame::FeatureStateEvent& rEvt) +{ + if(rEvt.FeatureURL.Complete != GetCommand()) + return; + + SolarMutexGuard aGuard; + pToolBar->EnableSourceList(rEvt.IsEnabled); + + uno::Any aState=rEvt.State; + auto pStringSeq = o3tl::tryAccess<Sequence<OUString>>(aState); + if(!pStringSeq) + return; + + pToolBar->ClearFilterMenu(); + + const OUString* pStringArray = pStringSeq->getConstArray(); + + sal_uInt32 nCount = pStringSeq->getLength(); + for( sal_uInt32 i=0; i<nCount; i++ ) + { + sal_uInt16 nID = pToolBar->InsertFilterItem(pStringArray[i]); + if(pStringArray[i]==rEvt.FeatureDescriptor) + { + pToolBar->SelectFilterItem(nID); + } + } +}; + +BibTBEditListener::BibTBEditListener(BibToolBar *pTB, const OUString& aStr, ToolBoxItemId nId): + BibToolBarListener(pTB,aStr,nId) +{ +} + +BibTBEditListener::~BibTBEditListener() +{ +} + +void BibTBEditListener::statusChanged(const frame::FeatureStateEvent& rEvt) +{ + if(rEvt.FeatureURL.Complete == GetCommand()) + { + SolarMutexGuard aGuard; + pToolBar->EnableQuery(rEvt.IsEnabled); + + uno::Any aState=rEvt.State; + if(auto aStr = o3tl::tryAccess<OUString>(aState)) + { + pToolBar->SetQueryString(*aStr); + } + } +} + +ComboBoxControl::ComboBoxControl(vcl::Window* pParent) + : InterimItemWindow(pParent, "modules/sbibliography/ui/combobox.ui", "ComboBox") + , m_xFtSource(m_xBuilder->weld_label("label")) + , m_xLBSource(m_xBuilder->weld_combo_box("combobox")) +{ + m_xFtSource->set_toolbar_background(); + m_xLBSource->set_toolbar_background(); + m_xLBSource->set_size_request(100, -1); + SetSizePixel(get_preferred_size()); +} + +void ComboBoxControl::dispose() +{ + m_xLBSource.reset(); + m_xFtSource.reset(); + InterimItemWindow::dispose(); +} + +ComboBoxControl::~ComboBoxControl() +{ + disposeOnce(); +} + +EditControl::EditControl(vcl::Window* pParent) + : InterimItemWindow(pParent, "modules/sbibliography/ui/editbox.ui", "EditBox") + , m_xFtQuery(m_xBuilder->weld_label("label")) + , m_xEdQuery(m_xBuilder->weld_entry("entry")) +{ + m_xFtQuery->set_toolbar_background(); + m_xEdQuery->set_toolbar_background(); + m_xEdQuery->set_size_request(100, -1); + SetSizePixel(get_preferred_size()); +} + +void EditControl::dispose() +{ + m_xEdQuery.reset(); + m_xFtQuery.reset(); + InterimItemWindow::dispose(); +} + +EditControl::~EditControl() +{ + disposeOnce(); +} + +BibToolBar::BibToolBar(vcl::Window* pParent, Link<void*,void> aLink) + : ToolBox(pParent, "toolbar", "modules/sbibliography/ui/toolbar.ui") + , aIdle("BibToolBar") + , xSource(VclPtr<ComboBoxControl>::Create(this)) + , pLbSource(xSource->get_widget()) + , xQuery(VclPtr<EditControl>::Create(this)) + , pEdQuery(xQuery->get_widget()) + , xBuilder(Application::CreateBuilder(nullptr, "modules/sbibliography/ui/autofiltermenu.ui")) + , xPopupMenu(xBuilder->weld_menu("menu")) + , nMenuId(0) + , aLayoutManager(aLink) + , nSymbolsSize(SFX_SYMBOLS_SIZE_SMALL) +{ + nSymbolsSize = SvtMiscOptions::GetCurrentSymbolsSize(); + + xSource->Show(); + pLbSource->connect_changed(LINK( this, BibToolBar, SelHdl)); + + SvtMiscOptions().AddListenerLink( LINK( this, BibToolBar, OptionsChanged_Impl ) ); + Application::AddEventListener( LINK( this, BibToolBar, SettingsChanged_Impl ) ); + + aIdle.SetInvokeHandler(LINK( this, BibToolBar, SendSelHdl)); + aIdle.SetPriority(TaskPriority::LOWEST); + + SetDropdownClickHdl( LINK( this, BibToolBar, MenuHdl)); + + xQuery->Show(); + + nTBC_SOURCE = GetItemId(".uno:Bib/source"); + nTBC_QUERY = GetItemId(".uno:Bib/query"); + nTBC_BT_AUTOFILTER = GetItemId(".uno:Bib/autoFilter"); + nTBC_BT_COL_ASSIGN = GetItemId("TBC_BT_COL_ASSIGN"); + nTBC_BT_CHANGESOURCE = GetItemId(".uno:Bib/sdbsource"); + nTBC_BT_FILTERCRIT = GetItemId(".uno:Bib/standardFilter"); + nTBC_BT_REMOVEFILTER = GetItemId(".uno:Bib/removeFilter"); + + SetItemWindow(nTBC_SOURCE, xSource.get()); + SetItemWindow(nTBC_QUERY , xQuery.get()); + + ApplyImageList(); + + ::bib::AddToTaskPaneList( this ); +} + +BibToolBar::~BibToolBar() +{ + disposeOnce(); +} + +void BibToolBar::dispose() +{ + SvtMiscOptions().RemoveListenerLink( LINK( this, BibToolBar, OptionsChanged_Impl ) ); + Application::RemoveEventListener( LINK( this, BibToolBar, SettingsChanged_Impl ) ); + ::bib::RemoveFromTaskPaneList( this ); + pEdQuery = nullptr; + xQuery.disposeAndClear(); + pLbSource = nullptr; + xSource.disposeAndClear(); + xPopupMenu.reset(); + xBuilder.reset(); + ToolBox::dispose(); +} + +void BibToolBar::InitListener() +{ + ToolBox::ImplToolItems::size_type nCount=GetItemCount(); + + uno::Reference< frame::XDispatch > xDisp(xController,UNO_QUERY); + uno::Reference< util::XURLTransformer > xTrans( util::URLTransformer::create(comphelper::getProcessComponentContext()) ); + if( !xTrans.is() ) + return; + + util::URL aQueryURL; + aQueryURL.Complete = ".uno:Bib/MenuFilter"; + xTrans->parseStrict( aQueryURL); + rtl::Reference<BibToolBarListener> pQuery=new BibTBQueryMenuListener(this, aQueryURL.Complete, nTBC_BT_AUTOFILTER); + xDisp->addStatusListener(pQuery, aQueryURL); + + for(ToolBox::ImplToolItems::size_type nPos=0;nPos<nCount;nPos++) + { + ToolBoxItemId nId=GetItemId(nPos); + if (!nId) + continue; + + util::URL aURL; + aURL.Complete = GetItemCommand(nId); + if(aURL.Complete.isEmpty()) + continue; + + xTrans->parseStrict( aURL ); + + css::uno::Reference< css::frame::XStatusListener> xListener; + if (nId == nTBC_SOURCE) + { + xListener=new BibTBListBoxListener(this,aURL.Complete,nId); + } + else if (nId == nTBC_QUERY) + { + xListener=new BibTBEditListener(this,aURL.Complete,nId); + } + else + { + xListener=new BibToolBarListener(this,aURL.Complete,nId); + } + + aListenerArr.push_back( xListener ); + xDisp->addStatusListener(xListener,aURL); + } +} + +void BibToolBar::SetXController(const uno::Reference< frame::XController > & xCtr) +{ + xController=xCtr; + InitListener(); + +} + +void BibToolBar::Select() +{ + ToolBoxItemId nId=GetCurItemId(); + + if (nId != nTBC_BT_AUTOFILTER) + { + SendDispatch(nId,Sequence<PropertyValue>() ); + } + else + { + Sequence<PropertyValue> aPropVal + { + comphelper::makePropertyValue("QueryText", pEdQuery->get_text()), + comphelper::makePropertyValue("QueryField", aQueryField) + }; + SendDispatch(nId,aPropVal); + } +} + +void BibToolBar::SendDispatch(ToolBoxItemId nId, const Sequence< PropertyValue >& rArgs) +{ + OUString aCommand = GetItemCommand(nId); + + uno::Reference< frame::XDispatchProvider > xDSP( xController, UNO_QUERY ); + + if( !xDSP.is() || aCommand.isEmpty() ) + return; + + uno::Reference< util::XURLTransformer > xTrans( util::URLTransformer::create(comphelper::getProcessComponentContext()) ); + if( !xTrans.is() ) + return; + + // load the file + util::URL aURL; + aURL.Complete = aCommand; + + xTrans->parseStrict( aURL ); + + uno::Reference< frame::XDispatch > xDisp = xDSP->queryDispatch( aURL, OUString(), frame::FrameSearchFlag::SELF ); + + if ( xDisp.is() ) + xDisp->dispatch( aURL, rArgs); + +} + +void BibToolBar::Click() +{ + ToolBoxItemId nId = GetCurItemId(); + + vcl::Window* pWin = GetParent(); + + if (nId == nTBC_BT_COL_ASSIGN ) + { + if (pDatMan) + pDatMan->CreateMappingDialog(pWin ? pWin->GetFrameWeld() : nullptr); + CheckItem( nId, false ); + } + else if (nId == nTBC_BT_CHANGESOURCE) + { + if (pDatMan) + { + OUString sNew = pDatMan->CreateDBChangeDialog(pWin ? pWin->GetFrameWeld() : nullptr); + if (!sNew.isEmpty()) + pDatMan->setActiveDataSource(sNew); + } + CheckItem( nId, false ); + } +} + +void BibToolBar::ClearFilterMenu() +{ + xPopupMenu->clear(); + nMenuId=0; +} + +sal_uInt16 BibToolBar::InsertFilterItem(const OUString& rMenuEntry) +{ + nMenuId++; + xPopupMenu->append_check(OUString::number(nMenuId), rMenuEntry); + return nMenuId; +} + +void BibToolBar::SelectFilterItem(sal_uInt16 nId) +{ + OUString sId = OUString::number(nId); + xPopupMenu->set_active(sId, true); + sSelMenuItem = sId; + aQueryField = MnemonicGenerator::EraseAllMnemonicChars(xPopupMenu->get_label(sId)); +} + +void BibToolBar::EnableSourceList(bool bFlag) +{ + xSource->set_sensitive(bFlag); +} + +void BibToolBar::ClearSourceList() +{ + pLbSource->clear(); +} + +void BibToolBar::UpdateSourceList(bool bFlag) +{ + if (bFlag) + pLbSource->thaw(); + else + pLbSource->freeze(); +} + +void BibToolBar::InsertSourceEntry(const OUString& aEntry) +{ + pLbSource->append_text(aEntry); +} + +void BibToolBar::SelectSourceEntry(const OUString& aStr) +{ + pLbSource->set_active_text(aStr); +} + +void BibToolBar::EnableQuery(bool bFlag) +{ + xQuery->set_sensitive(bFlag); +} + +void BibToolBar::SetQueryString(const OUString& aStr) +{ + pEdQuery->set_text(aStr); +} + +bool BibToolBar::PreNotify( NotifyEvent& rNEvt ) +{ + bool bResult = true; + + NotifyEventType nSwitch=rNEvt.GetType(); + if (pEdQuery && pEdQuery->has_focus() && nSwitch == NotifyEventType::KEYINPUT) + { + const vcl::KeyCode& aKeyCode=rNEvt.GetKeyEvent()->GetKeyCode(); + sal_uInt16 nKey = aKeyCode.GetCode(); + if(nKey == KEY_RETURN) + { + Sequence<PropertyValue> aPropVal + { + comphelper::makePropertyValue("QueryText", pEdQuery->get_text()), + comphelper::makePropertyValue("QueryField", aQueryField) + }; + SendDispatch(nTBC_BT_AUTOFILTER, aPropVal); + return bResult; + } + + } + + bResult=ToolBox::PreNotify(rNEvt); + + return bResult; +} + +IMPL_LINK_NOARG( BibToolBar, SelHdl, weld::ComboBox&, void ) +{ + aIdle.Start(); +} + +IMPL_LINK_NOARG( BibToolBar, SendSelHdl, Timer*, void ) +{ + Sequence<PropertyValue> aPropVal + { + comphelper::makePropertyValue("DataSourceName", MnemonicGenerator::EraseAllMnemonicChars( pLbSource->get_active_text() )) + }; + SendDispatch(nTBC_SOURCE, aPropVal); +} + +IMPL_LINK_NOARG(BibToolBar, MenuHdl, ToolBox*, void) +{ + ToolBoxItemId nId = GetCurItemId(); + if (nId != nTBC_BT_AUTOFILTER) + return; + + EndSelection(); // before SetDropMode (SetDropMode calls SetItemImage) + + SetItemDown(nTBC_BT_AUTOFILTER, true); + + tools::Rectangle aRect(GetItemRect(nTBC_BT_AUTOFILTER)); + weld::Window* pParent = weld::GetPopupParent(*this, aRect); + OUString sId = xPopupMenu->popup_at_rect(pParent, aRect); + + if (!sId.isEmpty()) + { + xPopupMenu->set_active(sSelMenuItem, false); + xPopupMenu->set_active(sId, true); + sSelMenuItem = sId; + aQueryField = MnemonicGenerator::EraseAllMnemonicChars(xPopupMenu->get_label(sId)); + Sequence<PropertyValue> aPropVal + { + comphelper::makePropertyValue("QueryText", pEdQuery->get_text()), + comphelper::makePropertyValue("QueryField", aQueryField) + }; + SendDispatch(nTBC_BT_AUTOFILTER, aPropVal); + } + + MouseEvent aLeave( Point(), 0, MouseEventModifiers::LEAVEWINDOW | MouseEventModifiers::SYNTHETIC ); + MouseMove( aLeave ); + SetItemDown(nTBC_BT_AUTOFILTER, false); +} + +void BibToolBar::statusChanged(const frame::FeatureStateEvent& rEvent) +{ + for(uno::Reference<frame::XStatusListener> & rListener : aListenerArr) + { + rListener->statusChanged(rEvent); + } +} + +void BibToolBar::DataChanged( const DataChangedEvent& rDCEvt ) +{ + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + ApplyImageList(); + ToolBox::DataChanged( rDCEvt ); +} + +IMPL_LINK_NOARG( BibToolBar, OptionsChanged_Impl, LinkParamNone*, void ) +{ + bool bRebuildToolBar = false; + sal_Int16 eSymbolsSize = SvtMiscOptions::GetCurrentSymbolsSize(); + if ( nSymbolsSize != eSymbolsSize ) + { + nSymbolsSize = eSymbolsSize; + bRebuildToolBar = true; + } + + if ( bRebuildToolBar ) + RebuildToolbar(); +} + +IMPL_LINK_NOARG( BibToolBar, SettingsChanged_Impl, VclSimpleEvent&, void ) +{ + // Check if toolbar button size have changed and we have to use system settings + sal_Int16 eSymbolsSize = SvtMiscOptions::GetCurrentSymbolsSize(); + if ( eSymbolsSize != nSymbolsSize ) + { + nSymbolsSize = eSymbolsSize; + RebuildToolbar(); + } +} + +void BibToolBar::RebuildToolbar() +{ + ApplyImageList(); + // We have to call parent asynchronously as SetSize works also asynchronously! + Application::PostUserEvent( aLayoutManager ); +} + +void BibToolBar::ApplyImageList() +{ + SetItemImage(nTBC_BT_AUTOFILTER, Image(StockImage::Yes, nSymbolsSize == SFX_SYMBOLS_SIZE_SMALL ? RID_EXTBMP_AUTOFILTER_SC : RID_EXTBMP_AUTOFILTER_LC)); + SetItemImage(nTBC_BT_FILTERCRIT, Image(StockImage::Yes, nSymbolsSize == SFX_SYMBOLS_SIZE_SMALL ? RID_EXTBMP_FILTERCRIT_SC : RID_EXTBMP_FILTERCRIT_LC)); + SetItemImage(nTBC_BT_REMOVEFILTER, Image(StockImage::Yes, nSymbolsSize == SFX_SYMBOLS_SIZE_SMALL ? RID_EXTBMP_REMOVE_FILTER_SORT_SC : RID_EXTBMP_REMOVE_FILTER_SORT_LC)); + AdjustToolBox(); +} + +void BibToolBar::AdjustToolBox() +{ + Size aOldSize = GetSizePixel(); + Size aSize = CalcWindowSizePixel(); + if ( !aSize.Width() ) + aSize.setWidth( aOldSize.Width() ); + else if ( !aSize.Height() ) + aSize.setHeight( aOldSize.Height() ); + + Size aTbSize = GetSizePixel(); + if ( + (aSize.Width() && aSize.Width() != aTbSize.Width()) || + (aSize.Height() && aSize.Height() != aTbSize.Height()) + ) + { + SetPosSizePixel( GetPosPixel(), aSize ); + Invalidate(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/bibliography/toolbar.hxx b/extensions/source/bibliography/toolbar.hxx new file mode 100644 index 0000000000..d4e45b2e0b --- /dev/null +++ b/extensions/source/bibliography/toolbar.hxx @@ -0,0 +1,217 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/frame/XStatusListener.hpp> + +#include <vcl/InterimItemWindow.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/timer.hxx> +#include <cppuhelper/implbase.hxx> +#include <vector> + +class BibDataManager; +class BibToolBar; + +class BibToolBarListener: public cppu::WeakImplHelper < css::frame::XStatusListener> +{ +private: + + ToolBoxItemId nIndex; + OUString aCommand; + +protected: + + VclPtr<BibToolBar> pToolBar; + +public: + + BibToolBarListener(BibToolBar *pTB, OUString aStr, ToolBoxItemId nId); + virtual ~BibToolBarListener() override; + + const OUString& GetCommand() const { return aCommand;} + + // css::lang::XEventListener + // we do not hold References to dispatches, so there is nothing to do on disposal + virtual void SAL_CALL disposing(const css::lang::EventObject& /*Source*/) override {}; + + // css::frame::XStatusListener + virtual void SAL_CALL statusChanged(const css::frame::FeatureStateEvent& Event) override; + +}; + +class BibTBListBoxListener: public BibToolBarListener +{ +public: + + BibTBListBoxListener(BibToolBar *pTB, const OUString& aStr, ToolBoxItemId nId); + virtual ~BibTBListBoxListener() override; + + virtual void SAL_CALL statusChanged(const css::frame::FeatureStateEvent& Event) override; + +}; + +class BibTBEditListener: public BibToolBarListener +{ +public: + + BibTBEditListener(BibToolBar *pTB, const OUString& aStr, ToolBoxItemId nId); + virtual ~BibTBEditListener() override; + + virtual void SAL_CALL statusChanged(const css::frame::FeatureStateEvent& Event) override; + +}; + +class BibTBQueryMenuListener: public BibToolBarListener +{ +public: + + BibTBQueryMenuListener(BibToolBar *pTB, const OUString& aStr, ToolBoxItemId nId); + virtual ~BibTBQueryMenuListener() override; + + virtual void SAL_CALL statusChanged(const css::frame::FeatureStateEvent& Event) override; + +}; + + +typedef std::vector< css::uno::Reference< css::frame::XStatusListener> > BibToolBarListenerArr; + +class ComboBoxControl final : public InterimItemWindow +{ +public: + ComboBoxControl(vcl::Window* pParent); + virtual ~ComboBoxControl() override; + virtual void dispose() override; + + weld::ComboBox* get_widget() { return m_xLBSource.get(); } + + void set_sensitive(bool bSensitive) + { + m_xFtSource->set_sensitive(bSensitive); + m_xLBSource->set_sensitive(bSensitive); + Enable(bSensitive); + } + +private: + std::unique_ptr<weld::Label> m_xFtSource; + std::unique_ptr<weld::ComboBox> m_xLBSource; +}; + +class EditControl final : public InterimItemWindow +{ +public: + EditControl(vcl::Window* pParent); + virtual ~EditControl() override; + virtual void dispose() override; + + weld::Entry* get_widget() { return m_xEdQuery.get(); } + + void set_sensitive(bool bSensitive) + { + m_xFtQuery->set_sensitive(bSensitive); + m_xEdQuery->set_sensitive(bSensitive); + Enable(bSensitive); + } + +private: + std::unique_ptr<weld::Label> m_xFtQuery; + std::unique_ptr<weld::Entry> m_xEdQuery; +}; + +class BibToolBar: public ToolBox +{ + private: + + BibToolBarListenerArr aListenerArr; + css::uno::Reference< css::frame::XController > xController; + Idle aIdle; + VclPtr<ComboBoxControl> xSource; + weld::ComboBox* pLbSource; + VclPtr<EditControl> xQuery; + weld::Entry* pEdQuery; + std::unique_ptr<weld::Builder> xBuilder; + std::unique_ptr<weld::Menu> xPopupMenu; + sal_uInt16 nMenuId; + OUString sSelMenuItem; + OUString aQueryField; + Link<void*,void> aLayoutManager; + sal_Int16 nSymbolsSize; + + ToolBoxItemId nTBC_SOURCE; + ToolBoxItemId nTBC_QUERY; + ToolBoxItemId nTBC_BT_AUTOFILTER; + ToolBoxItemId nTBC_BT_COL_ASSIGN; + ToolBoxItemId nTBC_BT_CHANGESOURCE; + ToolBoxItemId nTBC_BT_FILTERCRIT; + ToolBoxItemId nTBC_BT_REMOVEFILTER; + + BibDataManager* pDatMan; + DECL_LINK( SelHdl, weld::ComboBox&, void ); + DECL_LINK( SendSelHdl, Timer*, void ); + DECL_LINK( MenuHdl, ToolBox*, void ); + DECL_LINK( OptionsChanged_Impl, LinkParamNone*, void ); + DECL_LINK( SettingsChanged_Impl, VclSimpleEvent&, void ); + + void ApplyImageList(); + void RebuildToolbar(); + + protected: + + void DataChanged( const DataChangedEvent& rDCEvt ) override; + void InitListener(); + virtual void Select() override; + virtual void Click() override; + virtual bool PreNotify( NotifyEvent& rNEvt ) override; + + + public: + + BibToolBar(vcl::Window* pParent, Link<void*,void> aLink); + virtual ~BibToolBar() override; + virtual void dispose() override; + + ToolBoxItemId GetChangeSourceId() const { return nTBC_BT_CHANGESOURCE; } + + void SetXController(const css::uno::Reference< css::frame::XController > &); + + void ClearSourceList(); + void UpdateSourceList(bool bFlag); + void EnableSourceList(bool bFlag); + void InsertSourceEntry(const OUString& ); + void SelectSourceEntry(const OUString& ); + + void EnableQuery(bool bFlag); + void SetQueryString(const OUString& ); + void AdjustToolBox(); + + void ClearFilterMenu(); + sal_uInt16 InsertFilterItem(const OUString& ); + void SelectFilterItem(sal_uInt16 nId); + + /// @throws css::uno::RuntimeException + void statusChanged(const css::frame::FeatureStateEvent& Event); + + void SetDatMan(BibDataManager& rDatMan) {pDatMan = &rDatMan;} + void SendDispatch(ToolBoxItemId nId, const css::uno::Sequence< css::beans::PropertyValue >& rArgs); +}; + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/config/WinUserInfo/WinUserInfoBe.component b/extensions/source/config/WinUserInfo/WinUserInfoBe.component new file mode 100644 index 0000000000..5dd7d21ad7 --- /dev/null +++ b/extensions/source/config/WinUserInfo/WinUserInfoBe.component @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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/. + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.configuration.backend.WinUserInfoBe" + constructor="extensions_WinUserInfoBe_get_implementation"> + <service name="com.sun.star.configuration.backend.WinUserInfoBe"/> + </implementation> +</component> diff --git a/extensions/source/config/WinUserInfo/WinUserInfoBe.cxx b/extensions/source/config/WinUserInfo/WinUserInfoBe.cxx new file mode 100644 index 0000000000..9726c98695 --- /dev/null +++ b/extensions/source/config/WinUserInfo/WinUserInfoBe.cxx @@ -0,0 +1,433 @@ +/* -*- 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/. + */ + +#include "WinUserInfoBe.hxx" + +#include <com/sun/star/beans/Optional.hpp> +#include <comphelper/base64.hxx> +#include <comphelper/configuration.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <map> +#include <o3tl/char16_t2wchar_t.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <officecfg/UserProfile.hxx> + +#include <Iads.h> +#include <Adshlp.h> +#include <Lmcons.h> +#define SECURITY_WIN32 +#include <Security.h> + +#include <systools/win32/comtools.hxx> +#include <systools/win32/oleauto.hxx> + +namespace extensions +{ +namespace config +{ +namespace WinUserInfo +{ +class WinUserInfoBe_Impl +{ +public: + virtual ~WinUserInfoBe_Impl(){}; + virtual OUString GetGivenName() = 0; + virtual OUString GetSn() { return ""; } + virtual OUString GetFathersname() { return ""; } + virtual OUString GetInitials() { return ""; } + virtual OUString GetStreet() { return ""; } + virtual OUString GetCity() { return ""; } + virtual OUString GetState() { return ""; } + virtual OUString GetApartment() { return ""; } + virtual OUString GetPostalCode() { return ""; } + virtual OUString GetCountry() { return ""; } + virtual OUString GetOrganization() { return ""; } + virtual OUString GetPosition() { return ""; } + virtual OUString GetTitle() { return ""; } + virtual OUString GetHomePhone() { return ""; } + virtual OUString GetTelephoneNumber() { return ""; } + virtual OUString GetFaxNumber() { return ""; } + virtual OUString GetMail() { return ""; } +}; +} +} +} + +namespace +{ +constexpr OUString givenname(u"givenname"_ustr); +constexpr OUString sn(u"sn"_ustr); +constexpr char fathersname[]("fathersname"); +constexpr OUString initials(u"initials"_ustr); +constexpr OUString street(u"street"_ustr); +constexpr OUString l(u"l"_ustr); +constexpr OUString st(u"st"_ustr); +constexpr char apartment[]("apartment"); +constexpr OUString postalcode(u"postalcode"_ustr); +constexpr OUString c(u"c"_ustr); +constexpr OUString o(u"o"_ustr); +constexpr char position[]("position"); +constexpr OUString title(u"title"_ustr); +constexpr OUString homephone(u"homephone"_ustr); +constexpr OUString telephonenumber(u"telephonenumber"_ustr); +constexpr OUString facsimiletelephonenumber(u"facsimiletelephonenumber"_ustr); +constexpr OUString mail(u"mail"_ustr); + +// Backend class implementing access to Active Directory user data. It caches its encoded data +// in a configuration entry, to allow reusing it when user later doesn't have access to AD DC +// (otherwise the user would get different data when connected vs not connected). +class ADsUserAccess : public extensions::config::WinUserInfo::WinUserInfoBe_Impl +{ +public: + ADsUserAccess() + { + try + { + sal::systools::CoInitializeGuard aCoInitializeGuard(COINIT_APARTMENTTHREADED); + + sal::systools::COMReference<IADsADSystemInfo> pADsys(CLSID_ADSystemInfo, nullptr, + CLSCTX_INPROC_SERVER); + + sal::systools::BStr sUserDN; + sal::systools::ThrowIfFailed(pADsys->get_UserName(&sUserDN), "get_UserName failed"); + // If this user is an AD user, then without an active connection to the domain, all the + // above will succeed, and m_sUserDN will be correctly initialized, but the following + // call to ADsGetObject will fail, and we will attempt reading cached values. + m_sUserDN = sUserDN; + OUString sLdapUserDN = "LDAP://" + m_sUserDN; + sal::systools::COMReference<IADsUser> pUser; + sal::systools::ThrowIfFailed(ADsGetObject(o3tl::toW(sLdapUserDN.getStr()), IID_IADsUser, + reinterpret_cast<void**>(&pUser)), + "ADsGetObject failed"); + // Fetch all the required information right now, when we know to have access to AD + // (later the connection may already be lost) + m_aMap[givenname] = Str(pUser, &IADsUser::get_FirstName); + m_aMap[sn] = Str(pUser, &IADsUser::get_LastName); + m_aMap[initials] = Str(pUser, L"initials"); + m_aMap[street] = Str(pUser, L"streetAddress"); + m_aMap[l] = Str(pUser, L"l"); + m_aMap[st] = Str(pUser, L"st"); + m_aMap[postalcode] = Str(pUser, L"postalCode"); + m_aMap[c] = Str(pUser, L"co"); + m_aMap[o] = Str(pUser, L"company"); + m_aMap[title] = Str(pUser, &IADsUser::get_Title); + m_aMap[homephone] = Str(pUser, L"homePhone"); + m_aMap[telephonenumber] = Str(pUser, L"TelephoneNumber"); + m_aMap[facsimiletelephonenumber] = Str(pUser, L"facsimileTelephoneNumber"); + m_aMap[mail] = Str(pUser, &IADsUser::get_EmailAddress); + + CacheData(); + } + catch (sal::systools::ComError&) + { + // Maybe we temporarily lost connection to AD; try to get cached data + GetCachedData(); + } + } + + virtual OUString GetGivenName() override { return m_aMap[givenname]; } + virtual OUString GetSn() override { return m_aMap[sn]; } + virtual OUString GetInitials() override { return m_aMap[initials]; } + virtual OUString GetStreet() override { return m_aMap[street]; } + virtual OUString GetCity() override { return m_aMap[l]; } + virtual OUString GetState() override { return m_aMap[st]; } + virtual OUString GetPostalCode() override { return m_aMap[postalcode]; } + virtual OUString GetCountry() override { return m_aMap[c]; } + virtual OUString GetOrganization() override { return m_aMap[o]; } + virtual OUString GetTitle() override { return m_aMap[title]; } + virtual OUString GetHomePhone() override { return m_aMap[homephone]; } + virtual OUString GetTelephoneNumber() override { return m_aMap[telephonenumber]; } + virtual OUString GetFaxNumber() override { return m_aMap[facsimiletelephonenumber]; } + virtual OUString GetMail() override { return m_aMap[mail]; } + +private: + typedef HRESULT (__stdcall IADsUser::*getstrfunc)(BSTR*); + static OUString Str(IADsUser* pUser, getstrfunc func) + { + sal::systools::BStr sBstr; + if (FAILED((pUser->*func)(&sBstr))) + return ""; + return OUString(sBstr); + } + static OUString Str(IADsUser* pUser, const wchar_t* property) + { + sal::systools::BStr sBstrProp{ o3tl::toU(property) }; + struct AutoVariant : public VARIANT + { + AutoVariant() { VariantInit(this); } + ~AutoVariant() { VariantClear(this); } + } varArr; + if (FAILED(pUser->GetEx(sBstrProp, &varArr))) + return ""; + SAFEARRAY* sa = V_ARRAY(&varArr); + LONG nStart, nEnd; + if (FAILED(SafeArrayGetLBound(sa, 1, &nStart)) || FAILED(SafeArrayGetUBound(sa, 1, &nEnd))) + return ""; + AutoVariant varItem; + for (LONG i = nStart; i <= nEnd; i++) + { + if (FAILED(SafeArrayGetElement(sa, &i, &varItem))) + continue; + if (varItem.vt == VT_BSTR) + return OUString(o3tl::toU(V_BSTR(&varItem))); + VariantClear(&varItem); + } + return ""; + } + + void CacheData() + { + try + { + OUString sCachedData = "user=" + m_sUserDN // user DN + + "\0" + givenname + "=" + GetGivenName() // 1st name + + "\0" + sn + "=" + GetSn() // sn + + "\0" + initials + "=" + GetInitials() // initials + + "\0" + street + "=" + GetStreet() // street + + "\0" + l + "=" + GetCity() // l + + "\0" + st + "=" + GetState() // st + + "\0" + postalcode + "=" + GetPostalCode() // p.code + + "\0" + c + "=" + GetCountry() // c + + "\0" + o + "=" + GetOrganization() // o + + "\0" + title + "=" + GetTitle() // title + + "\0" + homephone + "=" + GetHomePhone() // h.phone + + "\0" + telephonenumber + "=" + GetTelephoneNumber() // tel + + "\0" + facsimiletelephonenumber + "=" + GetFaxNumber() // fax + + "\0" + mail + "=" + GetMail(); // mail + const css::uno::Sequence<sal_Int8> seqCachedData( + reinterpret_cast<const sal_Int8*>(sCachedData.getStr()), + sCachedData.getLength() * sizeof(sal_Unicode)); + OUStringBuffer sOutBuf; + comphelper::Base64::encode(sOutBuf, seqCachedData); + + std::shared_ptr<comphelper::ConfigurationChanges> batch( + comphelper::ConfigurationChanges::create()); + officecfg::UserProfile::WinUserInfo::Cache::set(sOutBuf.makeStringAndClear(), batch); + batch->commit(); + } + catch (const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.config", + "ADsUserAccess: access to configuration data failed:"); + } + } + + void GetCachedData() + { + if (m_sUserDN.isEmpty()) + throw css::uno::RuntimeException(); + + OUString sCache = officecfg::UserProfile::WinUserInfo::Cache::get(); + + if (sCache.isEmpty()) + throw css::uno::RuntimeException(); + + { + css::uno::Sequence<sal_Int8> seqCachedData; + comphelper::Base64::decode(seqCachedData, sCache); + sCache = OUString(reinterpret_cast<const sal_Unicode*>(seqCachedData.getConstArray()), + seqCachedData.getLength() / sizeof(sal_Unicode)); + } + + OUString sUserDN; + std::map<const OUString, OUString> aMap; + sal_Int32 nIndex = 0; + do + { + const OUString sEntry = sCache.getToken(0, '\0', nIndex); + sal_Int32 nEqIndex = 0; + const OUString sEntryName = sEntry.getToken(0, '=', nEqIndex); + OUString sEntryVal; + if (nEqIndex >= 0) + sEntryVal = sEntry.copy(nEqIndex); + if (sEntryName == "user") + sUserDN = sEntryVal; + else + aMap[sEntryName] = sEntryVal; + } while (nIndex >= 0); + + if (sUserDN != m_sUserDN) + throw css::uno::RuntimeException(); + m_aMap = std::move(aMap); + } + + OUString m_sUserDN; // used to check if the cached data is for current user + std::map<const OUString, OUString> m_aMap; +}; + +class SysInfoUserAccess : public extensions::config::WinUserInfo::WinUserInfoBe_Impl +{ +public: + SysInfoUserAccess() + { + try + { + ULONG nSize = 0; + GetUserNameExW(NameDisplay, nullptr, &nSize); + if (GetLastError() != ERROR_MORE_DATA) + throw css::uno::RuntimeException(); + auto pNameBuf(std::make_unique<wchar_t[]>(nSize)); + if (!GetUserNameExW(NameDisplay, pNameBuf.get(), &nSize)) + throw css::uno::RuntimeException(); + m_sName = o3tl::toU(pNameBuf.get()); + } + catch (css::uno::RuntimeException&) + { + // GetUserNameEx may fail in some cases (e.g., for built-in AD domain + // administrator account on non-DC systems), where GetUserName will + // still give a name. + DWORD nSize = UNLEN + 1; + auto pNameBuf(std::make_unique<wchar_t[]>(nSize)); + if (!GetUserNameW(pNameBuf.get(), &nSize)) + throw css::uno::RuntimeException(); + m_sName = o3tl::toU(pNameBuf.get()); + } + } + + virtual OUString GetGivenName() override { return m_sName; } + +private: + OUString m_sName; +}; +} + +namespace extensions +{ +namespace config +{ +namespace WinUserInfo +{ +WinUserInfoBe::WinUserInfoBe() + : WinUserInfoMutexHolder() + , BackendBase(mMutex) +{ + try + { + m_pImpl.reset(new ADsUserAccess()); + } + catch (css::uno::RuntimeException&) + { + m_pImpl.reset(new SysInfoUserAccess); + } +} + +WinUserInfoBe::~WinUserInfoBe() {} + +void WinUserInfoBe::setPropertyValue(OUString const&, css::uno::Any const&) +{ + throw css::lang::IllegalArgumentException("setPropertyValue not supported", + static_cast<cppu::OWeakObject*>(this), -1); +} + +css::uno::Any WinUserInfoBe::getPropertyValue(OUString const& PropertyName) +{ + OUString sValue; + // Only process the first argument of possibly multiple space- or comma-separated arguments + OUString sToken = PropertyName.getToken(0, ' ').getToken(0, ','); + if (sToken == givenname) + { + sValue = m_pImpl->GetGivenName(); + } + else if (sToken == sn) + { + sValue = m_pImpl->GetSn(); + } + else if (sToken == fathersname) + { + sValue = m_pImpl->GetFathersname(); + } + else if (sToken == initials) + { + sValue = m_pImpl->GetInitials(); + } + else if (sToken == street) + { + sValue = m_pImpl->GetStreet(); + } + else if (sToken == l) + { + sValue = m_pImpl->GetCity(); + } + else if (sToken == st) + { + sValue = m_pImpl->GetState(); + } + else if (sToken == apartment) + { + sValue = m_pImpl->GetApartment(); + } + else if (sToken == postalcode) + { + sValue = m_pImpl->GetPostalCode(); + } + else if (sToken == c) + { + sValue = m_pImpl->GetCountry(); + } + else if (sToken == o) + { + sValue = m_pImpl->GetOrganization(); + } + else if (sToken == position) + { + sValue = m_pImpl->GetPosition(); + } + else if (sToken == title) + { + sValue = m_pImpl->GetTitle(); + } + else if (sToken == homephone) + { + sValue = m_pImpl->GetHomePhone(); + } + else if (sToken == telephonenumber) + { + sValue = m_pImpl->GetTelephoneNumber(); + } + else if (sToken == facsimiletelephonenumber) + { + sValue = m_pImpl->GetFaxNumber(); + } + else if (sToken == mail) + { + sValue = m_pImpl->GetMail(); + } + else + throw css::beans::UnknownPropertyException(sToken, static_cast<cppu::OWeakObject*>(this)); + + return css::uno::Any(css::beans::Optional<css::uno::Any>( + !sValue.isEmpty(), sValue.isEmpty() ? css::uno::Any() : css::uno::Any(sValue))); +} + +OUString SAL_CALL WinUserInfoBe::getImplementationName() +{ + return "com.sun.star.comp.configuration.backend.WinUserInfoBe"; +} + +sal_Bool SAL_CALL WinUserInfoBe::supportsService(const OUString& aServiceName) +{ + return cppu::supportsService(this, aServiceName); +} + +css::uno::Sequence<OUString> SAL_CALL WinUserInfoBe::getSupportedServiceNames() +{ + return { "com.sun.star.configuration.backend.WinUserInfoBe" }; +} +} +} +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_WinUserInfoBe_get_implementation(css::uno::XComponentContext*, + css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new extensions::config::WinUserInfo::WinUserInfoBe()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/config/WinUserInfo/WinUserInfoBe.hxx b/extensions/source/config/WinUserInfo/WinUserInfoBe.hxx new file mode 100644 index 0000000000..30ca088c3a --- /dev/null +++ b/extensions/source/config/WinUserInfo/WinUserInfoBe.hxx @@ -0,0 +1,101 @@ +/* -*- 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/. + */ + +#pragma once + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <cppuhelper/compbase.hxx> +#include <memory> + +namespace com +{ +namespace sun +{ +namespace star +{ +namespace uno +{ +class XComponentContext; +} +} +} +} + +namespace extensions +{ +namespace config +{ +namespace WinUserInfo +{ +class WinUserInfoBe_Impl; + +typedef cppu::WeakComponentImplHelper<css::beans::XPropertySet, css::lang::XServiceInfo> + BackendBase; + +struct WinUserInfoMutexHolder +{ + osl::Mutex mMutex; +}; +/** + Implements the PlatformBackend service, a specialization of the + XPropertySet service for retrieving Active Directory user profile + configuration settings. +*/ +class WinUserInfoBe : private WinUserInfoMutexHolder, public BackendBase +{ +public: + explicit WinUserInfoBe(); + virtual ~WinUserInfoBe() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + + virtual sal_Bool SAL_CALL supportsService(const OUString& aServiceName) override; + + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + // XPropertySet + virtual css::uno::Reference<css::beans::XPropertySetInfo> SAL_CALL getPropertySetInfo() override + { + return css::uno::Reference<css::beans::XPropertySetInfo>(); + } + + virtual void SAL_CALL setPropertyValue(OUString const&, css::uno::Any const&) override; + + virtual css::uno::Any SAL_CALL getPropertyValue(OUString const& PropertyName) override; + + virtual void SAL_CALL addPropertyChangeListener( + OUString const&, css::uno::Reference<css::beans::XPropertyChangeListener> const&) override + { + } + + virtual void SAL_CALL removePropertyChangeListener( + OUString const&, css::uno::Reference<css::beans::XPropertyChangeListener> const&) override + { + } + + virtual void SAL_CALL addVetoableChangeListener( + OUString const&, css::uno::Reference<css::beans::XVetoableChangeListener> const&) override + { + } + + virtual void SAL_CALL removeVetoableChangeListener( + OUString const&, css::uno::Reference<css::beans::XVetoableChangeListener> const&) override + { + } + +private: + std::unique_ptr<WinUserInfoBe_Impl> m_pImpl; +}; +} +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/config/ldap/ldapaccess.cxx b/extensions/source/config/ldap/ldapaccess.cxx new file mode 100644 index 0000000000..7e35408b3b --- /dev/null +++ b/extensions/source/config/ldap/ldapaccess.cxx @@ -0,0 +1,289 @@ +/* -*- 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 "ldapaccess.hxx" + +#include <osl/diagnose.h> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <com/sun/star/ldap/LdapConnectionException.hpp> + + +namespace extensions::config::ldap { + + +typedef int LdapErrCode; + +struct LdapMessageHolder +{ + LdapMessageHolder() : msg(nullptr) {} + ~LdapMessageHolder() + { + if (msg) + ldap_msgfree(msg); + } + LdapMessageHolder(const LdapMessageHolder&) = delete; + LdapMessageHolder& operator=(const LdapMessageHolder&) = delete; + + LDAPMessage * msg; +}; + +LdapConnection::~LdapConnection() +{ + if (isValid()) disconnect(); +} + + +void LdapConnection::disconnect() +{ + if (mConnection != nullptr) + { + ldap_unbind_s(mConnection) ; + mConnection = nullptr; + } +} + + +static void checkLdapReturnCode(const char *aOperation, + LdapErrCode aRetCode) +{ + if (aRetCode == LDAP_SUCCESS) { return ; } + + OUString message; + + if (aOperation != nullptr) + { + message += OUString::createFromAscii(aOperation) + ": "; + } + message += OUString::createFromAscii(ldap_err2string(aRetCode)) + " (" ; + +#ifndef LDAP_OPT_SIZELIMIT // for use with OpenLDAP + char* stub = nullptr; + ldap_get_lderrno(aConnection, NULL, &stub) ; + if (stub != nullptr) + { + message += OUString::createFromAscii(stub) ; + // It would seem the message returned is actually + // not a copy of a string but rather some static + // string itself. At any rate freeing it seems to + // cause some undue problems at least on Windows. + // This call is thus disabled for the moment. + //ldap_memfree(stub) ; + } + else +#endif + { message += "No additional information"; } + + message += ")" ; + throw ldap::LdapGenericException(message, nullptr, aRetCode) ; +} + +void LdapConnection::connectSimple(const LdapDefinition& aDefinition) +{ + OSL_ENSURE(!isValid(), "Re-connecting to an LDAP connection that is already established"); + if (isValid()) disconnect(); + + mLdapDefinition = aDefinition; + connectSimple(); +} + +void LdapConnection::connectSimple() +{ + if (isValid()) + return; + + // Connect to the server + initConnection() ; + // Set Protocol V3 + int version = LDAP_VERSION3; + ldap_set_option(mConnection, + LDAP_OPT_PROTOCOL_VERSION, + &version); + +#ifdef LDAP_X_OPT_CONNECT_TIMEOUT // OpenLDAP doesn't support this and the func + /* timeout is specified in milliseconds -> 4 seconds*/ + int timeout = 4000; +#ifdef _WIN32 + ldap_set_optionW( mConnection, + LDAP_X_OPT_CONNECT_TIMEOUT, + &timeout ); +#else + ldap_set_option( mConnection, + LDAP_X_OPT_CONNECT_TIMEOUT, + &timeout ); +#endif +#endif + + // Do the bind +#ifdef _WIN32 + LdapErrCode retCode = ldap_simple_bind_sW(mConnection, + const_cast<PWSTR>(o3tl::toW(mLdapDefinition.mAnonUser.getStr())), + const_cast<PWSTR>(o3tl::toW(mLdapDefinition.mAnonCredentials.getStr())) ); +#else + LdapErrCode retCode = ldap_simple_bind_s(mConnection, + OUStringToOString( mLdapDefinition.mAnonUser, RTL_TEXTENCODING_UTF8 ).getStr(), + OUStringToOString( mLdapDefinition.mAnonCredentials, RTL_TEXTENCODING_UTF8 ).getStr()) ; +#endif + + checkLdapReturnCode("SimpleBind", retCode) ; +} + +void LdapConnection::initConnection() +{ + if (mLdapDefinition.mServer.isEmpty()) + { + throw ldap::LdapConnectionException("Cannot initialise connection to LDAP: No server specified."); + } + + if (mLdapDefinition.mPort == 0) mLdapDefinition.mPort = LDAP_PORT; + +#ifdef _WIN32 + mConnection = ldap_initW(const_cast<PWSTR>(o3tl::toW(mLdapDefinition.mServer.getStr())), + mLdapDefinition.mPort) ; +#else + mConnection = ldap_init(OUStringToOString( mLdapDefinition.mServer, RTL_TEXTENCODING_UTF8 ).getStr(), + mLdapDefinition.mPort) ; +#endif + if (mConnection == nullptr) + { + throw ldap::LdapConnectionException( + "Cannot initialise connection to LDAP server " + + mLdapDefinition.mServer + ":" + OUString::number(mLdapDefinition.mPort)); + } +} + + void LdapConnection::getUserProfile( + const OUString& aUser, LdapData * data) +{ + OSL_ASSERT(data != nullptr); + if (!isValid()) { connectSimple(); } + + OUString aUserDn =findUserDn( aUser ); + + LdapMessageHolder result; +#ifdef _WIN32 + LdapErrCode retCode = ldap_search_sW(mConnection, + const_cast<PWSTR>(o3tl::toW(aUserDn.getStr())), + LDAP_SCOPE_BASE, + const_cast<PWSTR>( L"(objectclass=*)" ), + nullptr, + 0, // Attributes + values + &result.msg) ; +#else + LdapErrCode retCode = ldap_search_s(mConnection, + OUStringToOString( aUserDn, RTL_TEXTENCODING_UTF8 ).getStr(), + LDAP_SCOPE_BASE, + "(objectclass=*)", + nullptr, + 0, // Attributes + values + &result.msg) ; +#endif + checkLdapReturnCode("getUserProfile", retCode) ; + + BerElement * ptr; +#ifdef _WIN32 + PWCHAR attr = ldap_first_attributeW(mConnection, result.msg, &ptr); + while (attr) { + PWCHAR * values = ldap_get_valuesW(mConnection, result.msg, attr); + if (values) { + const OUString aAttr( o3tl::toU( attr ) ); + const OUString aValues( o3tl::toU( *values ) ); + data->emplace( aAttr, aValues ); + ldap_value_freeW(values); + } + attr = ldap_next_attributeW(mConnection, result.msg, ptr); +#else + char * attr = ldap_first_attribute(mConnection, result.msg, &ptr); + while (attr) { + char ** values = ldap_get_values(mConnection, result.msg, attr); + if (values) { + data->emplace( + OStringToOUString(attr, RTL_TEXTENCODING_ASCII_US), + OStringToOUString(*values, RTL_TEXTENCODING_UTF8)); + ldap_value_free(values); + } + attr = ldap_next_attribute(mConnection, result.msg, ptr); +#endif + } +} + + OUString LdapConnection::findUserDn(const OUString& aUser) +{ + if (!isValid()) { connectSimple(); } + + if (aUser.isEmpty()) + { + throw lang::IllegalArgumentException( + "LdapConnection::findUserDn -User id is empty", + nullptr, 0) ; + } + + OUString filter = "(&(objectclass=" + + mLdapDefinition.mUserObjectClass + + ")(" + + mLdapDefinition.mUserUniqueAttr + + "=" + + aUser + + "))"; + + LdapMessageHolder result; +#ifdef _WIN32 + PWCHAR attributes [2] = { const_cast<PWCHAR>( L"1.1" ), nullptr }; + LdapErrCode retCode = ldap_search_sW(mConnection, + const_cast<PWSTR>(o3tl::toW(mLdapDefinition.mBaseDN.getStr())), + LDAP_SCOPE_SUBTREE, + const_cast<PWSTR>(o3tl::toW(filter.getStr())), attributes, 0, &result.msg) ; +#else + char * attributes [2] = { const_cast<char *>(LDAP_NO_ATTRS), nullptr }; + LdapErrCode retCode = ldap_search_s(mConnection, + OUStringToOString( mLdapDefinition.mBaseDN, RTL_TEXTENCODING_UTF8 ).getStr(), + LDAP_SCOPE_SUBTREE, + OUStringToOString( filter, RTL_TEXTENCODING_UTF8 ).getStr(), attributes, 0, &result.msg) ; +#endif + checkLdapReturnCode("FindUserDn", retCode) ; + OUString userDn ; + LDAPMessage *entry = ldap_first_entry(mConnection, result.msg) ; + + if (entry != nullptr) + { +#ifdef _WIN32 + PWCHAR charsDn = ldap_get_dnW(mConnection, entry) ; + + userDn = OUString( o3tl::toU( charsDn ) ); + ldap_memfreeW(charsDn) ; +#else + char *charsDn = ldap_get_dn(mConnection, entry) ; + + userDn = OStringToOUString( charsDn, RTL_TEXTENCODING_UTF8 ); + ldap_memfree(charsDn) ; +#endif + } + else + { + OSL_FAIL( "LdapConnection::findUserDn-could not get DN for User "); + } + + return userDn ; +} + + +} // extensions::config::ldap + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/config/ldap/ldapaccess.hxx b/extensions/source/config/ldap/ldapaccess.hxx new file mode 100644 index 0000000000..36a0708b1e --- /dev/null +++ b/extensions/source/config/ldap/ldapaccess.hxx @@ -0,0 +1,133 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <map> + +#ifdef _WIN32 +#if !defined WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <winldap.h> +#else // !defined _WIN32 +#include <ldap.h> +#endif // _WIN32 + +#include <com/sun/star/ldap/LdapGenericException.hpp> + +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +namespace extensions::config::ldap +{ +namespace uno = css::uno; +namespace lang = css::lang; +namespace ldap = css::ldap; + +struct LdapUserProfile; + +/** Struct containing the information on LDAP connection */ +struct LdapDefinition +{ + /** LDAP server name */ + OUString mServer; + /** LDAP server port number */ + sal_Int32 mPort; + /** Repository base DN */ + OUString mBaseDN; + /** DN to use for "anonymous" connection */ + OUString mAnonUser; + /** Credentials to use for "anonymous" connection */ + OUString mAnonCredentials; + /** User Entity Object Class */ + OUString mUserObjectClass; + /** User Entity Unique Attribute */ + OUString mUserUniqueAttr; + + LdapDefinition() + : mPort(0) + { + } +}; + +typedef std::map<OUString, OUString> LdapData; // key/value pairs + +/** Class encapsulating all LDAP functionality */ +class LdapConnection +{ + friend struct LdapMessageHolder; + +public: + /** Default constructor */ + LdapConnection() + : mConnection(nullptr) + , mLdapDefinition() + { + } + /** Destructor, releases the connection */ + ~LdapConnection(); + /** Make connection to LDAP server + @throws ldap::LdapConnectionException + @throws ldap::LdapGenericException + */ + void connectSimple(const LdapDefinition& aDefinition); + + /** + Gets LdapUserProfile from LDAP repository for specified user + @param aUser name of logged on user + @param aUserProfileMap Map containing LDAP->00o mapping + @param aUserProfile struct for holding OOo values + + @throws css::ldap::LdapGenericException + if an LDAP error occurs. + */ + void getUserProfile(const OUString& aUser, LdapData* data); + + /** finds DN of user + @return DN of User + @throws lang::IllegalArgumentException + @throws ldap::LdapConnectionException + @throws ldap::LdapGenericException + */ + OUString findUserDn(const OUString& aUser); + +private: + /// @throws ldap::LdapConnectionException + void initConnection(); + void disconnect(); + /** + Indicates whether the connection is in a valid state. + @return sal_True if connection is valid, sal_False otherwise + */ + bool isValid() const { return mConnection != nullptr; } + + /// @throws ldap::LdapConnectionException + /// @throws ldap::LdapGenericException + void connectSimple(); + + /** LDAP connection object */ + LDAP* mConnection; + LdapDefinition mLdapDefinition; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/config/ldap/ldapbe2.component b/extensions/source/config/ldap/ldapbe2.component new file mode 100644 index 0000000000..8f6ea3f806 --- /dev/null +++ b/extensions/source/config/ldap/ldapbe2.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.configuration.backend.LdapUserProfileBe" + constructor="extensions_ldp_LdapUserProfileBe_get_implementation"> + <service name="com.sun.star.configuration.backend.LdapUserProfileBe"/> + </implementation> +</component> diff --git a/extensions/source/config/ldap/ldapuserprofilebe.cxx b/extensions/source/config/ldap/ldapuserprofilebe.cxx new file mode 100644 index 0000000000..a735b5fbd7 --- /dev/null +++ b/extensions/source/config/ldap/ldapuserprofilebe.cxx @@ -0,0 +1,214 @@ +/* -*- 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 "ldapaccess.hxx" +#include "ldapuserprofilebe.hxx" +#include <sal/log.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <rtl/instance.hxx> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/Optional.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <comphelper/scopeguard.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <osl/security.hxx> + + +namespace extensions::config::ldap { + +LdapUserProfileBe::LdapUserProfileBe( const uno::Reference<uno::XComponentContext>& xContext) +: BackendBase(m_aMutex) +{ + LdapDefinition aDefinition; + OUString loggedOnUser; + // true initially to handle reentrant call; will become false if readLdapConfiguration fails + bool bHaveLdapConfiguration = true; + + // This whole rigmarole is to prevent an infinite recursion where reading + // the configuration for the backend would create another instance of the + // backend, which would try and read the configuration which would... + { + static osl::Mutex aInitMutex; + osl::MutexGuard aInitGuard(aInitMutex); + + static bool bReentrantCall; // = false + OSL_ENSURE(!bReentrantCall, "configuration: Ldap Backend constructor called reentrantly - probably a registration error."); + + if (!bReentrantCall) + { + bReentrantCall = true ; + comphelper::ScopeGuard aReentrantCallGuard([]() { bReentrantCall = false; }); + // Don't throw on fail: this will crash if LDAP is misconfigured, and user opens + // Expert Configuration dialog. Instead, just don't fill data_, which will make the + // backend return empty values. This happens in SvtUserOptions::Impl::GetValue_Impl + // anyway even in throwing scenario, but doing it here also improves performance + // because of avoiding repeated attempts to create the backend. + bHaveLdapConfiguration = readLdapConfiguration( + xContext, &aDefinition, &loggedOnUser); + if (!bHaveLdapConfiguration) + SAL_WARN("extensions.config", "LdapUserProfileBackend: LDAP not configured"); + } + } + + if (bHaveLdapConfiguration) + { + LdapConnection connection; + connection.connectSimple(aDefinition); + connection.getUserProfile(loggedOnUser, &data_); + } +} + +LdapUserProfileBe::~LdapUserProfileBe() +{ +} + + +bool LdapUserProfileBe::readLdapConfiguration( + css::uno::Reference< css::uno::XComponentContext > const & context, + LdapDefinition * definition, OUString * loggedOnUser) +{ + OSL_ASSERT(context.is() && definition != nullptr && loggedOnUser != nullptr); + + uno::Reference< XInterface > xIface; + try + { + uno::Reference< lang::XMultiServiceFactory > xCfgProvider( + css::configuration::theDefaultProvider::get(context)); + + css::beans::NamedValue aPath("nodepath", uno::Any(OUString("org.openoffice.LDAP/UserDirectory")) ); + + uno::Sequence< uno::Any > aArgs{ uno::Any(aPath) }; + + xIface = xCfgProvider->createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess", aArgs); + + uno::Reference<container::XNameAccess > xAccess(xIface, uno::UNO_QUERY_THROW); + xAccess->getByName("ServerDefinition") >>= xIface; + + uno::Reference<container::XNameAccess > xChildAccess(xIface, uno::UNO_QUERY_THROW); + + if (!getLdapStringParam(xChildAccess, "Server", definition->mServer)) + return false; + if (!getLdapStringParam(xChildAccess, "BaseDN", definition->mBaseDN)) + return false; + + definition->mPort=0; + xChildAccess->getByName("Port") >>= definition->mPort ; + if (definition->mPort == 0) + return false; + + if (!getLdapStringParam(xAccess, "UserObjectClass", definition->mUserObjectClass)) + return false; + if (!getLdapStringParam(xAccess, "UserUniqueAttribute", definition->mUserUniqueAttr)) + return false; + + getLdapStringParam(xAccess, "SearchUser", definition->mAnonUser); + getLdapStringParam(xAccess, "SearchPassword", definition->mAnonCredentials); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.config", "LdapUserProfileBackend: access to configuration data failed"); + return false; + } + + osl::Security aSecurityContext; + if (!aSecurityContext.getUserName(*loggedOnUser)) + SAL_WARN("extensions.config", "LdapUserProfileBackend - could not get Logged on user from system"); + + sal_Int32 nIndex = loggedOnUser->indexOf('/'); + if (nIndex > 0) + *loggedOnUser = loggedOnUser->copy(nIndex+1); + + return true; +} + + +bool LdapUserProfileBe::getLdapStringParam( + uno::Reference<container::XNameAccess> const & xAccess, + const OUString& aLdapSetting, + OUString& aServerParameter) +{ + xAccess->getByName(aLdapSetting) >>= aServerParameter; + + return !aServerParameter.isEmpty(); +} + +void LdapUserProfileBe::setPropertyValue( + OUString const &, css::uno::Any const &) +{ + throw css::lang::IllegalArgumentException( + "setPropertyValue not supported", + static_cast< cppu::OWeakObject * >(this), -1); +} + +css::uno::Any LdapUserProfileBe::getPropertyValue( + OUString const & PropertyName) +{ + for (sal_Int32 i = 0;;) { + sal_Int32 j = PropertyName.indexOf(',', i); + if (j == -1) { + j = PropertyName.getLength(); + } + if (j == i) { + throw css::beans::UnknownPropertyException( + PropertyName, static_cast< cppu::OWeakObject * >(this)); + } + LdapData::iterator k(data_.find(PropertyName.copy(i, j - i))); + if (k != data_.end()) { + return css::uno::Any( + css::beans::Optional< css::uno::Any >( + true, css::uno::Any(k->second))); + } + if (j == PropertyName.getLength()) { + break; + } + i = j + 1; + } + return css::uno::Any(css::beans::Optional< css::uno::Any >()); +} + + +OUString SAL_CALL LdapUserProfileBe::getImplementationName() +{ + return "com.sun.star.comp.configuration.backend.LdapUserProfileBe"; +} + +sal_Bool SAL_CALL LdapUserProfileBe::supportsService(const OUString& aServiceName) +{ + return cppu::supportsService(this, aServiceName); +} + +uno::Sequence<OUString> +SAL_CALL LdapUserProfileBe::getSupportedServiceNames() +{ + return { "com.sun.star.configuration.backend.LdapUserProfileBe" }; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_ldp_LdapUserProfileBe_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new extensions::config::ldap::LdapUserProfileBe(context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/config/ldap/ldapuserprofilebe.hxx b/extensions/source/config/ldap/ldapuserprofilebe.hxx new file mode 100644 index 0000000000..2f05365328 --- /dev/null +++ b/extensions/source/config/ldap/ldapuserprofilebe.hxx @@ -0,0 +1,113 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> + +#include "ldapaccess.hxx" + +namespace com::sun::star::uno { + class XComponentContext; +} + +namespace extensions::config::ldap { + +namespace uno = css::uno ; +namespace lang = css::lang ; +namespace container = css::container; + +struct LdapDefinition; + +typedef cppu::WeakComponentImplHelper<css::beans::XPropertySet, + lang::XServiceInfo> BackendBase ; + +/** + Implements the PlatformBackend service, a specialization of the + XPropertySet service for retrieving LDAP user profile + configuration settings from an LDAP repository. + */ +class LdapUserProfileBe : private cppu::BaseMutex, public BackendBase +{ + public: + + explicit LdapUserProfileBe(const uno::Reference<uno::XComponentContext>& xContext); + virtual ~LdapUserProfileBe() override ; + + // XServiceInfo + virtual OUString SAL_CALL + getImplementationName( ) override ; + + virtual sal_Bool SAL_CALL + supportsService( const OUString& aServiceName ) override ; + + virtual uno::Sequence<OUString> SAL_CALL + getSupportedServiceNames( ) override ; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL + getPropertySetInfo() override + { return css::uno::Reference< css::beans::XPropertySetInfo >(); } + + virtual void SAL_CALL setPropertyValue( + OUString const &, css::uno::Any const &) override; + + virtual css::uno::Any SAL_CALL getPropertyValue( + OUString const & PropertyName) override; + + virtual void SAL_CALL addPropertyChangeListener( + OUString const &, + css::uno::Reference< css::beans::XPropertyChangeListener > const &) override + {} + + virtual void SAL_CALL removePropertyChangeListener( + OUString const &, + css::uno::Reference< css::beans::XPropertyChangeListener > const &) override + {} + + virtual void SAL_CALL addVetoableChangeListener( + OUString const &, + css::uno::Reference< css::beans::XVetoableChangeListener > const &) override + {} + + virtual void SAL_CALL removeVetoableChangeListener( + OUString const &, + css::uno::Reference< css::beans::XVetoableChangeListener > const &) override + {} + + private: + /** Check if LDAP is configured */ + static bool readLdapConfiguration( + uno::Reference<uno::XComponentContext> const & context, + LdapDefinition * definition, OUString * loggedOnUser); + + static bool getLdapStringParam(uno::Reference<container::XNameAccess> const & xAccess, + const OUString& aLdapSetting, + OUString& aServerParameter); + + LdapData data_; +} ; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/commonpagesdbp.cxx b/extensions/source/dbpilots/commonpagesdbp.cxx new file mode 100644 index 0000000000..3aa0031967 --- /dev/null +++ b/extensions/source/dbpilots/commonpagesdbp.cxx @@ -0,0 +1,448 @@ +/* -*- 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 "commonpagesdbp.hxx" +#include <strings.hrc> +#include <bitmaps.hlst> +#include <componentmodule.hxx> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/sdb/XCompletedConnection.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <com/sun/star/sdb/XQueriesSupplier.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdb/SQLContext.hpp> +#include <com/sun/star/sdbc/SQLWarning.hpp> +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <comphelper/interaction.hxx> +#include <connectivity/dbtools.hxx> +#include <sfx2/docfilt.hxx> +#include <unotools/pathoptions.hxx> +#include <sfx2/filedlghelper.hxx> +#include <svl/filenotation.hxx> +#include <osl/diagnose.h> + +namespace dbp +{ + + + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::sdb; + using namespace ::com::sun::star::sdbc; + using namespace ::com::sun::star::sdbcx; + using namespace ::com::sun::star::task; + using namespace ::comphelper; + + OTableSelectionPage::OTableSelectionPage(weld::Container* pPage, OControlWizard* pWizard) + : OControlWizardPage(pPage, pWizard, "modules/sabpilot/ui/tableselectionpage.ui", "TableSelectionPage") + , m_xTable(m_xBuilder->weld_tree_view("table")) + , m_xDatasource(m_xBuilder->weld_tree_view("datasource")) + , m_xSearchDatabase(m_xBuilder->weld_button("search")) + , m_xSourceBox(m_xBuilder->weld_container("sourcebox")) + { + try + { + m_xDSContext = getContext().xDatasourceContext; + if (m_xDSContext.is()) + fillListBox(*m_xDatasource, m_xDSContext->getElementNames()); + } + catch (const Exception&) + { + OSL_FAIL("OTableSelectionPage::OTableSelectionPage: could not collect the data source names!"); + } + + m_xDatasource->connect_changed(LINK(this, OTableSelectionPage, OnListboxSelection)); + m_xTable->connect_changed(LINK(this, OTableSelectionPage, OnListboxSelection)); + m_xTable->connect_row_activated(LINK(this, OTableSelectionPage, OnListboxDoubleClicked)); + m_xSearchDatabase->connect_clicked(LINK(this, OTableSelectionPage, OnSearchClicked)); + } + + OTableSelectionPage::~OTableSelectionPage() + { + } + + void OTableSelectionPage::Activate() + { + OControlWizardPage::Activate(); + m_xDatasource->grab_focus(); + } + + bool OTableSelectionPage::canAdvance() const + { + if (!OControlWizardPage::canAdvance()) + return false; + + if (0 == m_xDatasource->count_selected_rows()) + return false; + + if (0 == m_xTable->count_selected_rows()) + return false; + + return true; + } + + void OTableSelectionPage::initializePage() + { + OControlWizardPage::initializePage(); + + const OControlWizardContext& rContext = getContext(); + try + { + OUString sDataSourceName; + rContext.xForm->getPropertyValue("DataSourceName") >>= sDataSourceName; + + Reference< XConnection > xConnection; + bool bEmbedded = ::dbtools::isEmbeddedInDatabase( rContext.xForm, xConnection ); + if ( bEmbedded ) + { + m_xSourceBox->hide(); + m_xDatasource->append_text(sDataSourceName); + } + m_xDatasource->select_text(sDataSourceName); + + implFillTables(xConnection); + + OUString sCommand; + OSL_VERIFY( rContext.xForm->getPropertyValue("Command") >>= sCommand ); + sal_Int32 nCommandType = CommandType::TABLE; + OSL_VERIFY( rContext.xForm->getPropertyValue("CommandType") >>= nCommandType ); + + // search the entry of the given type with the given name + for (sal_Int32 nLookup = 0; nLookup < m_xTable->n_children(); ++nLookup) + { + if (sCommand == m_xTable->get_text(nLookup)) + { + if (m_xTable->get_id(nLookup).toInt32() == nCommandType) + { + m_xTable->select( nLookup ); + break; + } + } + } + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.abpilot", "OTableSelectionPage::initializePage"); + } + } + + bool OTableSelectionPage::commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) + { + if (!OControlWizardPage::commitPage(_eReason)) + return false; + + const OControlWizardContext& rContext = getContext(); + try + { + Reference< XConnection > xOldConn; + if ( !rContext.bEmbedded ) + { + xOldConn = getFormConnection(); + + OUString sDataSource = m_xDatasource->get_selected_text(); + rContext.xForm->setPropertyValue("DataSourceName", Any( sDataSource ) ); + } + OUString sCommand = m_xTable->get_selected_text(); + sal_Int32 nCommandType = m_xTable->get_selected_id().toInt32(); + + rContext.xForm->setPropertyValue("Command", Any( sCommand ) ); + rContext.xForm->setPropertyValue("CommandType", Any( nCommandType ) ); + + if ( !rContext.bEmbedded ) + setFormConnection( xOldConn, false ); + + if (!updateContext()) + return false; + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.dbpilots", "OTableSelectionPage::commitPage"); + } + + return true; + } + + IMPL_LINK_NOARG( OTableSelectionPage, OnSearchClicked, weld::Button&, void ) + { + ::sfx2::FileDialogHelper aFileDlg( + ui::dialogs::TemplateDescription::FILEOPEN_READONLY_VERSION, + FileDialogFlags::NONE, getDialog()->getDialog()); + aFileDlg.SetDisplayDirectory( SvtPathOptions().GetWorkPath() ); + + std::shared_ptr<const SfxFilter> pFilter = SfxFilter::GetFilterByName("StarOffice XML (Base)"); + OSL_ENSURE(pFilter,"Filter: StarOffice XML (Base) could not be found!"); + if ( pFilter ) + { + aFileDlg.AddFilter(pFilter->GetUIName(),pFilter->GetDefaultExtension()); + } + + if (ERRCODE_NONE == aFileDlg.Execute()) + { + OUString sDataSourceName = aFileDlg.GetPath(); + ::svt::OFileNotation aFileNotation(sDataSourceName); + sDataSourceName = aFileNotation.get(::svt::OFileNotation::N_SYSTEM); + m_xDatasource->append_text(sDataSourceName); + m_xDatasource->select_text(sDataSourceName); + LINK(this, OTableSelectionPage, OnListboxSelection).Call(*m_xDatasource); + } + } + + IMPL_LINK(OTableSelectionPage, OnListboxDoubleClicked, weld::TreeView&, _rBox, bool) + { + if (_rBox.count_selected_rows()) + getDialog()->travelNext(); + return true; + } + + IMPL_LINK(OTableSelectionPage, OnListboxSelection, weld::TreeView&, _rBox, void) + { + if (m_xDatasource.get() == &_rBox) + { // new data source selected + implFillTables(); + } + + updateDialogTravelUI(); + } + + namespace + { + void lcl_fillEntries(weld::TreeView& rListBox, const Sequence<OUString>& rNames, const OUString& rImage, sal_Int32 nCommandType) + { + for (auto const & name : rNames) + { + rListBox.append(OUString::number(nCommandType), name, rImage); + } + } + } + + void OTableSelectionPage::implFillTables(const Reference< XConnection >& _rxConn) + { + m_xTable->clear(); + + weld::WaitObject aWaitCursor(getDialog()->getDialog()); + + // will be the table tables of the selected data source + Sequence< OUString > aTableNames; + Sequence< OUString > aQueryNames; + + // connect to the data source + Any aSQLException; + Reference< XConnection > xConn = _rxConn; + if ( !xConn.is() ) + { + if (!m_xDSContext.is()) + return; + // connect to the data source + try + { + OUString sCurrentDatasource = m_xDatasource->get_selected_text(); + if (!sCurrentDatasource.isEmpty()) + { + // obtain the DS object + Reference< XCompletedConnection > xDatasource; + // check if I know this one otherwise transform it into a file URL + if ( !m_xDSContext->hasByName(sCurrentDatasource) ) + { + ::svt::OFileNotation aFileNotation(sCurrentDatasource); + sCurrentDatasource = aFileNotation.get(::svt::OFileNotation::N_URL); + } + + if (m_xDSContext->getByName(sCurrentDatasource) >>= xDatasource) + { // connect + // get the default SDB interaction handler + Reference< XInteractionHandler > xHandler = getDialog()->getInteractionHandler(getDialog()->getDialog()); + if (!xHandler.is() ) + return; + xConn = xDatasource->connectWithCompletion(xHandler); + setFormConnection( xConn ); + } + else + { + OSL_FAIL("OTableSelectionPage::implFillTables: invalid data source object returned by the context"); + } + } + } + catch(const SQLContext& e) { aSQLException <<= e; } + catch(const SQLWarning& e) { aSQLException <<= e; } + catch(const SQLException& e) { aSQLException <<= e; } + catch (const Exception&) + { + OSL_FAIL("OTableSelectionPage::implFillTables: could not fill the table list!"); + } + } + + // will be the table tables of the selected data source + if ( xConn.is() ) + { + try + { + // get the tables + Reference< XTablesSupplier > xSupplTables(xConn, UNO_QUERY); + if ( xSupplTables.is() ) + { + Reference< XNameAccess > xTables = xSupplTables->getTables(); + if (xTables.is()) + aTableNames = xTables->getElementNames(); + } + + // and the queries + Reference< XQueriesSupplier > xSuppQueries( xConn, UNO_QUERY ); + if ( xSuppQueries.is() ) + { + Reference< XNameAccess > xQueries = xSuppQueries->getQueries(); + if ( xQueries.is() ) + aQueryNames = xQueries->getElementNames(); + } + } + catch(const SQLContext& e) { aSQLException <<= e; } + catch(const SQLWarning& e) { aSQLException <<= e; } + catch(const SQLException& e) { aSQLException <<= e; } + catch (const Exception&) + { + OSL_FAIL("OTableSelectionPage::implFillTables: could not fill the table list!"); + } + } + + + if ( aSQLException.hasValue() ) + { // an SQLException (or derivee) was thrown ... + Reference< XInteractionRequest > xRequest = new OInteractionRequest(aSQLException); + try + { + // get the default SDB interaction handler + Reference< XInteractionHandler > xHandler = getDialog()->getInteractionHandler(getDialog()->getDialog()); + if ( xHandler.is() ) + xHandler->handle(xRequest); + } + catch(const Exception&) { } + return; + } + + lcl_fillEntries(*m_xTable, aTableNames, BMP_TABLE, CommandType::TABLE); + lcl_fillEntries(*m_xTable, aQueryNames, BMP_QUERY, CommandType::QUERY); + } + + OMaybeListSelectionPage::OMaybeListSelectionPage(weld::Container* pPage, OControlWizard* pWizard, const OUString& rUIXMLDescription, const OUString& rID) + : OControlWizardPage(pPage, pWizard, rUIXMLDescription, rID) + , m_pYes(nullptr) + , m_pNo(nullptr) + , m_pList(nullptr) + { + } + + OMaybeListSelectionPage::~OMaybeListSelectionPage() + { + } + + void OMaybeListSelectionPage::announceControls(weld::RadioButton& _rYesButton, weld::RadioButton& _rNoButton, weld::ComboBox& _rSelection) + { + m_pYes = &_rYesButton; + m_pNo = &_rNoButton; + m_pList = &_rSelection; + + m_pYes->connect_toggled(LINK(this, OMaybeListSelectionPage, OnRadioSelected)); + m_pNo->connect_toggled(LINK(this, OMaybeListSelectionPage, OnRadioSelected)); + implEnableWindows(); + } + + IMPL_LINK(OMaybeListSelectionPage, OnRadioSelected, weld::Toggleable&, rButton, void) + { + if (!rButton.get_active()) + return; + implEnableWindows(); + } + + void OMaybeListSelectionPage::implInitialize(const OUString& _rSelection) + { + DBG_ASSERT(m_pYes, "OMaybeListSelectionPage::implInitialize: no controls announced!"); + bool bIsSelection = ! _rSelection.isEmpty(); + m_pYes->set_active(bIsSelection); + m_pNo->set_active(!bIsSelection); + m_pList->set_sensitive(bIsSelection); + + m_pList->set_active_text(bIsSelection ? _rSelection : OUString()); + } + + void OMaybeListSelectionPage::implCommit(OUString& _rSelection) + { + _rSelection = m_pYes->get_active() ? m_pList->get_active_text() : OUString(); + } + + void OMaybeListSelectionPage::implEnableWindows() + { + m_pList->set_sensitive(m_pYes->get_active()); + } + + void OMaybeListSelectionPage::Activate() + { + OControlWizardPage::Activate(); + + DBG_ASSERT(m_pYes, "OMaybeListSelectionPage::Activate: no controls announced!"); + if (m_pYes->get_active()) + m_pList->grab_focus(); + else + m_pNo->grab_focus(); + } + + ODBFieldPage::ODBFieldPage(weld::Container* pPage, OControlWizard* pWizard) + : OMaybeListSelectionPage(pPage, pWizard, "modules/sabpilot/ui/optiondbfieldpage.ui", "OptionDBField") + , m_xDescription(m_xBuilder->weld_label("explLabel")) + , m_xStoreYes(m_xBuilder->weld_radio_button("yesRadiobutton")) + , m_xStoreNo(m_xBuilder->weld_radio_button("noRadiobutton")) + , m_xStoreWhere(m_xBuilder->weld_combo_box("storeInFieldCombobox")) + { + SetPageTitle(compmodule::ModuleRes(RID_STR_OPTION_DB_FIELD_TITLE)); + + announceControls(*m_xStoreYes, *m_xStoreNo, *m_xStoreWhere); + } + + ODBFieldPage::~ODBFieldPage() + { + } + + void ODBFieldPage::initializePage() + { + OMaybeListSelectionPage::initializePage(); + + // fill the fields page + fillListBox(*m_xStoreWhere, getContext().aFieldNames); + + implInitialize(getDBFieldSetting()); + } + + bool ODBFieldPage::commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) + { + if (!OMaybeListSelectionPage::commitPage(_eReason)) + return false; + + implCommit(getDBFieldSetting()); + + return true; + } + + +} // namespace dbp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/commonpagesdbp.hxx b/extensions/source/dbpilots/commonpagesdbp.hxx new file mode 100644 index 0000000000..66115a1445 --- /dev/null +++ b/extensions/source/dbpilots/commonpagesdbp.hxx @@ -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 . + */ + +#pragma once + +#include "controlwizard.hxx" +#include <vcl/weld.hxx> +#include <com/sun/star/sdb/XDatabaseContext.hpp> + +namespace dbp +{ + class OTableSelectionPage final : public OControlWizardPage + { + std::unique_ptr<weld::TreeView> m_xTable; + std::unique_ptr<weld::TreeView> m_xDatasource; + std::unique_ptr<weld::Button> m_xSearchDatabase; + std::unique_ptr<weld::Container> m_xSourceBox; + + css::uno::Reference< css::sdb::XDatabaseContext > + m_xDSContext; + + public: + explicit OTableSelectionPage(weld::Container* pPage, OControlWizard* pParent); + virtual ~OTableSelectionPage() override; + + private: + // BuilderPage overridables + void Activate() override; + + // OWizardPage overridables + virtual void initializePage() override; + virtual bool commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) override; + + DECL_LINK( OnListboxSelection, weld::TreeView&, void ); + DECL_LINK( OnListboxDoubleClicked, weld::TreeView&, bool ); + DECL_LINK( OnSearchClicked, weld::Button&, void ); + + void implFillTables(const css::uno::Reference< css::sdbc::XConnection >& + _rxConn = css::uno::Reference< css::sdbc::XConnection >()); + + // OControlWizardPage overridables + virtual bool canAdvance() const override; + }; + + class OMaybeListSelectionPage : public OControlWizardPage + { + weld::RadioButton* m_pYes; + weld::RadioButton* m_pNo; + weld::ComboBox* m_pList; + + public: + OMaybeListSelectionPage(weld::Container* pPage, OControlWizard* pWizard, const OUString& rUIXMLDescription, const OUString& rID); + virtual ~OMaybeListSelectionPage() override; + + protected: + DECL_LINK( OnRadioSelected, weld::Toggleable&, void ); + + // BuilderPage overridables + void Activate() override; + + // own helper + void announceControls( + weld::RadioButton& _rYesButton, + weld::RadioButton& _rNoButton, + weld::ComboBox& _rSelection); + + void implEnableWindows(); + + void implInitialize(const OUString& _rSelection); + void implCommit(OUString& _rSelection); + }; + + class ODBFieldPage : public OMaybeListSelectionPage + { + std::unique_ptr<weld::Label> m_xDescription; + std::unique_ptr<weld::RadioButton> m_xStoreYes; + std::unique_ptr<weld::RadioButton> m_xStoreNo; + std::unique_ptr<weld::ComboBox> m_xStoreWhere; + + public: + explicit ODBFieldPage(weld::Container* pPage, OControlWizard* pWizard); + virtual ~ODBFieldPage() override; + + protected: + void setDescriptionText(const OUString& rDesc) + { + m_xDescription->set_label(rDesc); + } + + // OWizardPage overridables + virtual void initializePage() override; + virtual bool commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) override; + + // own overridables + virtual OUString& getDBFieldSetting() = 0; + }; + + +} // namespace dbp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/controlwizard.cxx b/extensions/source/dbpilots/controlwizard.cxx new file mode 100644 index 0000000000..17ec948ed0 --- /dev/null +++ b/extensions/source/dbpilots/controlwizard.cxx @@ -0,0 +1,661 @@ +/* -*- 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 "controlwizard.hxx" +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <com/sun/star/sdb/DatabaseContext.hpp> +#include <com/sun/star/sdb/XQueriesSupplier.hpp> +#include <com/sun/star/sdbc/XPreparedStatement.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/sheet/XSpreadsheetView.hpp> +#include <com/sun/star/drawing/XDrawView.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/sdbc/SQLWarning.hpp> +#include <com/sun/star/sdb/SQLContext.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <comphelper/types.hxx> +#include <connectivity/dbtools.hxx> +#include <comphelper/interaction.hxx> +#include <vcl/stdtext.hxx> +#include <connectivity/conncleanup.hxx> +#include <com/sun/star/sdbc/DataType.hpp> +#include <tools/urlobj.hxx> + +#define WIZARD_SIZE_X 60 +#define WIZARD_SIZE_Y 23 + +namespace dbp +{ + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::awt; + using namespace ::com::sun::star::lang; + 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::drawing; + using namespace ::com::sun::star::frame; + using namespace ::com::sun::star::sheet; + using namespace ::com::sun::star::form; + using namespace ::com::sun::star::task; + using namespace ::comphelper; + using namespace ::dbtools; + + struct OAccessRegulator + { + friend class OControlWizardPage; + + protected: + OAccessRegulator() { } + }; + + OControlWizardPage::OControlWizardPage(weld::Container* pPage, OControlWizard* pWizard, const OUString& rUIXMLDescription, const OUString& rID) + : OControlWizardPage_Base(pPage, pWizard, rUIXMLDescription, rID) + , m_pDialog(pWizard) + { + m_xContainer->set_size_request(m_xContainer->get_approximate_digit_width() * WIZARD_SIZE_X, + m_xContainer->get_text_height() * WIZARD_SIZE_Y); + } + + OControlWizardPage::~OControlWizardPage() + { + } + + OControlWizard* OControlWizardPage::getDialog() + { + return m_pDialog; + } + + const OControlWizard* OControlWizardPage::getDialog() const + { + return m_pDialog; + } + + bool OControlWizardPage::updateContext() + { + return m_pDialog->updateContext(OAccessRegulator()); + } + + Reference< XConnection > OControlWizardPage::getFormConnection() const + { + return m_pDialog->getFormConnection(OAccessRegulator()); + } + + void OControlWizardPage::setFormConnection( const Reference< XConnection >& _rxConn, bool _bAutoDispose ) + { + m_pDialog->setFormConnection( OAccessRegulator(), _rxConn, _bAutoDispose ); + } + + const OControlWizardContext& OControlWizardPage::getContext() const + { + return m_pDialog->getContext(); + } + + void OControlWizardPage::fillListBox(weld::TreeView& _rList, const Sequence< OUString >& _rItems) + { + _rList.clear(); + const OUString* pItems = _rItems.getConstArray(); + const OUString* pEnd = pItems + _rItems.getLength(); + sal_Int32 nIndex = 0; + for (;pItems < pEnd; ++pItems, ++nIndex) + { + _rList.append(OUString::number(nIndex), *pItems); + } + } + + void OControlWizardPage::fillListBox(weld::ComboBox& _rList, const Sequence< OUString >& _rItems) + { + _rList.clear(); + const OUString* pItems = _rItems.getConstArray(); + const OUString* pEnd = pItems + _rItems.getLength(); + for (;pItems < pEnd; ++pItems) + { + _rList.append_text(*pItems); + } + } + + void OControlWizardPage::enableFormDatasourceDisplay() + { + if (m_xFormContentType) + // nothing to do + return; + + m_xFrame = m_xBuilder->weld_frame("sourceframe"); + m_xFrame->show(); + m_xFormContentType = m_xBuilder->weld_label("contenttype"); + m_xFormContentTypeLabel = m_xBuilder->weld_label("contenttypelabel"); + m_xFormDatasource = m_xBuilder->weld_label("datasource"); + m_xFormDatasourceLabel = m_xBuilder->weld_label("datasourcelabel"); + m_xFormTable = m_xBuilder->weld_label("formtable"); + + const OControlWizardContext& rContext = getContext(); + if ( rContext.bEmbedded ) + { + m_xFormDatasourceLabel->hide(); + m_xFormDatasource->hide(); + } + } + + void OControlWizardPage::initializePage() + { + if (m_xFormDatasource && m_xFormContentTypeLabel && m_xFormTable) + { + const OControlWizardContext& rContext = getContext(); + OUString sDataSource; + OUString sCommand; + sal_Int32 nCommandType = CommandType::COMMAND; + try + { + rContext.xForm->getPropertyValue("DataSourceName") >>= sDataSource; + rContext.xForm->getPropertyValue("Command") >>= sCommand; + rContext.xForm->getPropertyValue("CommandType") >>= nCommandType; + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.dbpilots", "OControlWizardPage::initializePage"); + } + + INetURLObject aURL( sDataSource ); + if( aURL.GetProtocol() != INetProtocol::NotValid ) + sDataSource = aURL.GetLastName(INetURLObject::DecodeMechanism::WithCharset); + m_xFormDatasource->set_label(sDataSource); + m_xFormTable->set_label(sCommand); + + TranslateId pCommandTypeResourceId; + switch (nCommandType) + { + case CommandType::TABLE: + pCommandTypeResourceId = RID_STR_TYPE_TABLE; + break; + + case CommandType::QUERY: + pCommandTypeResourceId = RID_STR_TYPE_QUERY; + break; + + default: + pCommandTypeResourceId = RID_STR_TYPE_COMMAND; + break; + } + m_xFormContentType->set_label(compmodule::ModuleRes(pCommandTypeResourceId)); + } + + OControlWizardPage_Base::initializePage(); + } + + OControlWizard::OControlWizard(weld::Window* _pParent, + const Reference< XPropertySet >& _rxObjectModel, const Reference< XComponentContext >& _rxContext ) + : WizardMachine(_pParent, WizardButtonFlags::CANCEL | WizardButtonFlags::PREVIOUS | WizardButtonFlags::NEXT | WizardButtonFlags::FINISH) + , m_xContext(_rxContext) + { + m_aContext.xObjectModel = _rxObjectModel; + initContext(); + + defaultButton(WizardButtonFlags::NEXT); + enableButtons(WizardButtonFlags::FINISH, false); + } + + OControlWizard::~OControlWizard() + { + } + + short OControlWizard::run() + { + // get the class id of the control we're dealing with + sal_Int16 nClassId = FormComponentType::CONTROL; + try + { + getContext().xObjectModel->getPropertyValue("ClassId") >>= nClassId; + } + catch(const Exception&) + { + OSL_FAIL("OControlWizard::activate: could not obtain the class id!"); + } + if (!approveControl(nClassId)) + { + // TODO: MessageBox or exception + return RET_CANCEL; + } + + ActivatePage(); + + m_xAssistant->set_current_page(0); + + return OControlWizard_Base::run(); + } + + void OControlWizard::implDetermineShape() + { + Reference< XIndexAccess > xPageObjects = m_aContext.xDrawPage; + DBG_ASSERT(xPageObjects.is(), "OControlWizard::implDetermineShape: invalid page!"); + + // for comparing the model + Reference< XControlModel > xModelCompare(m_aContext.xObjectModel, UNO_QUERY); + + if (!xPageObjects.is()) + return; + + // loop through all objects of the page + sal_Int32 nObjects = xPageObjects->getCount(); + Reference< XControlShape > xControlShape; + Reference< XControlModel > xControlModel; + for (sal_Int32 i=0; i<nObjects; ++i) + { + if (xPageObjects->getByIndex(i) >>= xControlShape) + { // it _is_ a control shape + xControlModel = xControlShape->getControl(); + DBG_ASSERT(xControlModel.is(), "OControlWizard::implDetermineShape: control shape without model!"); + if (xModelCompare.get() == xControlModel.get()) + { + m_aContext.xObjectShape = xControlShape; + break; + } + } + } + } + + + void OControlWizard::implDetermineForm() + { + Reference< XChild > xModelAsChild(m_aContext.xObjectModel, UNO_QUERY); + Reference< XInterface > xControlParent; + if (xModelAsChild.is()) + xControlParent = xModelAsChild->getParent(); + + m_aContext.xForm.set(xControlParent, UNO_QUERY); + m_aContext.xRowSet.set(xControlParent, UNO_QUERY); + DBG_ASSERT(m_aContext.xForm.is() && m_aContext.xRowSet.is(), + "OControlWizard::implDetermineForm: missing some interfaces of the control parent!"); + + } + + + void OControlWizard::implDeterminePage() + { + try + { + // get the document model + Reference< XChild > xControlAsChild(m_aContext.xObjectModel, UNO_QUERY); + Reference< XChild > xModelSearch(xControlAsChild->getParent(), UNO_QUERY); + + Reference< XModel > xModel(xModelSearch, UNO_QUERY); + while (xModelSearch.is() && !xModel.is()) + { + xModelSearch.set(xModelSearch->getParent(), UNO_QUERY); + xModel.set(xModelSearch, UNO_QUERY); + } + + Reference< XDrawPage > xPage; + if (xModel.is()) + { + m_aContext.xDocumentModel = xModel; + + Reference< XDrawPageSupplier > xPageSupp(xModel, UNO_QUERY); + if (xPageSupp.is()) + { // it's a document with only one page -> Writer + xPage = xPageSupp->getDrawPage(); + } + else + { + // get the controller currently working on this model + Reference< XController > xController = xModel->getCurrentController(); + DBG_ASSERT(xController.is(), "OControlWizard::implDeterminePage: no current controller!"); + + // maybe it's a spreadsheet + Reference< XSpreadsheetView > xView(xController, UNO_QUERY); + if (xView.is()) + { // okay, it is one + Reference< XSpreadsheet > xSheet = xView->getActiveSheet(); + xPageSupp.set(xSheet, UNO_QUERY); + DBG_ASSERT(xPageSupp.is(), "OControlWizard::implDeterminePage: a spreadsheet which is no page supplier!"); + if (xPageSupp.is()) + xPage = xPageSupp->getDrawPage(); + } + else + { // can be a draw/impress doc only + Reference< XDrawView > xDrawView(xController, UNO_QUERY); + DBG_ASSERT(xDrawView.is(), "OControlWizard::implDeterminePage: no alternatives left ... can't determine the page!"); + if (xDrawView.is()) + xPage = xDrawView->getCurrentPage(); + } + } + } + else + { + DBG_ASSERT(xPage.is(), "OControlWizard::implDeterminePage: can't determine the page (no model)!"); + } + m_aContext.xDrawPage = xPage; + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.dbpilots", "OControlWizard::implDeterminePage"); + } + } + + + void OControlWizard::implGetDSContext() + { + try + { + DBG_ASSERT(m_xContext.is(), "OControlWizard::implGetDSContext: invalid service factory!"); + + m_aContext.xDatasourceContext = DatabaseContext::create(m_xContext); + } + catch(const Exception&) + { + OSL_FAIL("OControlWizard::implGetDSContext: invalid database context!"); + } + } + + + Reference< XConnection > OControlWizard::getFormConnection(const OAccessRegulator&) const + { + return getFormConnection(); + } + + Reference< XConnection > OControlWizard::getFormConnection() const + { + Reference< XConnection > xConn; + try + { + if ( !::dbtools::isEmbeddedInDatabase(m_aContext.xForm,xConn) ) + m_aContext.xForm->getPropertyValue("ActiveConnection") >>= xConn; + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.dbpilots", "OControlWizard::getFormConnection"); + } + return xConn; + } + + + void OControlWizard::setFormConnection( const OAccessRegulator& _rAccess, const Reference< XConnection >& _rxConn, bool _bAutoDispose ) + { + try + { + Reference< XConnection > xOldConn = getFormConnection(_rAccess); + if (xOldConn.get() == _rxConn.get()) + return; + + disposeComponent(xOldConn); + + // set the new connection + if ( _bAutoDispose ) + { + // for this, use an AutoDisposer (so the conn is cleaned up when the form dies or gets another connection) + Reference< XRowSet > xFormRowSet( m_aContext.xForm, UNO_QUERY ); + new OAutoConnectionDisposer( xFormRowSet, _rxConn ); + } + else + { + m_aContext.xForm->setPropertyValue("ActiveConnection", Any( _rxConn ) ); + } + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION( "extensions.dbpilots", "OControlWizard::setFormConnection"); + } + } + + + bool OControlWizard::updateContext(const OAccessRegulator&) + { + return initContext(); + } + + Reference< XInteractionHandler > OControlWizard::getInteractionHandler(weld::Window* _pWindow) const + { + Reference< XInteractionHandler > xHandler; + try + { + xHandler.set( InteractionHandler::createWithParent(m_xContext, nullptr), UNO_QUERY_THROW ); + } + catch(const Exception&) { } + if (!xHandler.is()) + { + ShowServiceNotAvailableError(_pWindow, u"com.sun.star.task.InteractionHandler", true); + } + return xHandler; + } + + bool OControlWizard::initContext() + { + DBG_ASSERT(m_aContext.xObjectModel.is(), "OGroupBoxWizard::initContext: have no control model to work with!"); + if (!m_aContext.xObjectModel.is()) + return false; + + // reset the context + m_aContext.xForm.clear(); + m_aContext.xRowSet.clear(); + m_aContext.xDocumentModel.clear(); + m_aContext.xDrawPage.clear(); + m_aContext.xObjectShape.clear(); + m_aContext.aFieldNames.realloc(0); + + m_aContext.xObjectContainer.clear(); + m_aContext.aTypes.clear(); + m_aContext.bEmbedded = false; + + Any aSQLException; + Reference< XPreparedStatement > xStatement; + try + { + // get the datasource context + implGetDSContext(); + + // first, determine the form the control belongs to + implDetermineForm(); + + // need the page, too + implDeterminePage(); + + // the shape of the control + implDetermineShape(); + + // get the columns of the object the settings refer to + Reference< XNameAccess > xColumns; + + if (m_aContext.xForm.is()) + { + // collect some properties of the form + OUString sObjectName = ::comphelper::getString(m_aContext.xForm->getPropertyValue("Command")); + sal_Int32 nObjectType = ::comphelper::getINT32(m_aContext.xForm->getPropertyValue("CommandType")); + + // calculate the connection the rowset is working with + Reference< XConnection > xConnection; + m_aContext.bEmbedded = ::dbtools::isEmbeddedInDatabase( m_aContext.xForm, xConnection ); + if ( !m_aContext.bEmbedded ) + xConnection = ::dbtools::connectRowset( m_aContext.xRowSet, m_xContext, nullptr ); + + // get the fields + if (xConnection.is()) + { + switch (nObjectType) + { + case 0: + { + Reference< XTablesSupplier > xSupplyTables(xConnection, UNO_QUERY); + if (xSupplyTables.is() && xSupplyTables->getTables().is() && xSupplyTables->getTables()->hasByName(sObjectName)) + { + Reference< XColumnsSupplier > xSupplyColumns; + m_aContext.xObjectContainer = xSupplyTables->getTables(); + m_aContext.xObjectContainer->getByName(sObjectName) >>= xSupplyColumns; + DBG_ASSERT(xSupplyColumns.is(), "OControlWizard::initContext: invalid table columns!"); + xColumns = xSupplyColumns->getColumns(); + } + } + break; + case 1: + { + Reference< XQueriesSupplier > xSupplyQueries(xConnection, UNO_QUERY); + if (xSupplyQueries.is() && xSupplyQueries->getQueries().is() && xSupplyQueries->getQueries()->hasByName(sObjectName)) + { + Reference< XColumnsSupplier > xSupplyColumns; + m_aContext.xObjectContainer = xSupplyQueries->getQueries(); + m_aContext.xObjectContainer->getByName(sObjectName) >>= xSupplyColumns; + DBG_ASSERT(xSupplyColumns.is(), "OControlWizard::initContext: invalid query columns!"); + xColumns = xSupplyColumns->getColumns(); + } + } + break; + default: + { + xStatement = xConnection->prepareStatement(sObjectName); + + // not interested in any results, only in the fields + Reference< XPropertySet > xStatementProps(xStatement, UNO_QUERY); + xStatementProps->setPropertyValue("MaxRows", Any(sal_Int32(0))); + + // TODO: think about handling local SQLExceptions here ... + Reference< XColumnsSupplier > xSupplyCols(xStatement->executeQuery(), UNO_QUERY); + if (xSupplyCols.is()) + xColumns = xSupplyCols->getColumns(); + } + } + } + } + + if (xColumns.is()) + { + m_aContext.aFieldNames = xColumns->getElementNames(); + const OUString* pBegin = m_aContext.aFieldNames.getConstArray(); + const OUString* pEnd = pBegin + m_aContext.aFieldNames.getLength(); + for(;pBegin != pEnd;++pBegin) + { + sal_Int32 nFieldType = DataType::OTHER; + try + { + Reference< XPropertySet > xColumn; + xColumns->getByName(*pBegin) >>= xColumn; + xColumn->getPropertyValue("Type") >>= nFieldType; + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION( + "extensions.dbpilots", + "unexpected exception while gathering column information!"); + } + m_aContext.aTypes.emplace(*pBegin,nFieldType); + } + } + } + catch(const SQLContext& e) { aSQLException <<= e; } + catch(const SQLWarning& e) { aSQLException <<= e; } + catch(const SQLException& e) { aSQLException <<= e; } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION( "extensions.dbpilots", "OControlWizard::initContext: could not retrieve the control context"); + } + + ::comphelper::disposeComponent(xStatement); + + if (aSQLException.hasValue()) + { // an SQLException (or derivee) was thrown ... + + // prepend an extra SQLContext explaining what we were doing + SQLContext aContext(compmodule::ModuleRes(RID_STR_COULDNOTOPENTABLE), {}, {}, 0, + aSQLException, {}); + + // create an interaction handler to display this exception + Reference< XInteractionHandler > xHandler = getInteractionHandler(m_xAssistant.get()); + if ( !xHandler.is() ) + return false; + + Reference< XInteractionRequest > xRequest = new OInteractionRequest(Any(aContext)); + try + { + xHandler->handle(xRequest); + } + catch(const Exception&) { } + return false; + } + + return m_aContext.aFieldNames.hasElements(); + } + + + void OControlWizard::commitControlSettings(OControlWizardSettings const * _pSettings) + { + DBG_ASSERT(m_aContext.xObjectModel.is(), "OControlWizard::commitControlSettings: have no control model to work with!"); + if (!m_aContext.xObjectModel.is()) + return; + + // the only thing we have at the moment is the label + try + { + Reference< XPropertySetInfo > xInfo = m_aContext.xObjectModel->getPropertySetInfo(); + if (xInfo.is() && xInfo->hasPropertyByName("Label")) + { + OUString sControlLabel(_pSettings->sControlLabel); + m_aContext.xObjectModel->setPropertyValue( + "Label", + Any(sControlLabel) + ); + } + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION( "extensions.dbpilots", "OControlWizard::commitControlSettings: could not commit the basic control settings!"); + } + } + + + void OControlWizard::initControlSettings(OControlWizardSettings* _pSettings) + { + DBG_ASSERT(m_aContext.xObjectModel.is(), "OControlWizard::initControlSettings: have no control model to work with!"); + if (!m_aContext.xObjectModel.is()) + return; + + // initialize some settings from the control model give + try + { + OUString sLabelPropertyName("Label"); + Reference< XPropertySetInfo > xInfo = m_aContext.xObjectModel->getPropertySetInfo(); + if (xInfo.is() && xInfo->hasPropertyByName(sLabelPropertyName)) + { + OUString sControlLabel; + m_aContext.xObjectModel->getPropertyValue(sLabelPropertyName) >>= sControlLabel; + _pSettings->sControlLabel = sControlLabel; + } + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION( "extensions.dbpilots", "OControlWizard::initControlSettings: could not retrieve the basic control settings!"); + } + } + + + bool OControlWizard::needDatasourceSelection() + { + // lemme see ... + return !getContext().aFieldNames.hasElements(); + // if we got fields, the data source is valid ... + } + + +} // namespace dbp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/controlwizard.hxx b/extensions/source/dbpilots/controlwizard.hxx new file mode 100644 index 0000000000..12138899f9 --- /dev/null +++ b/extensions/source/dbpilots/controlwizard.hxx @@ -0,0 +1,148 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <vcl/weld.hxx> +#include <vcl/wizardmachine.hxx> +#include "dbptypes.hxx" +#include <strings.hrc> +#include <componentmodule.hxx> +#include "wizardcontext.hxx" + +class ResId; + +namespace dbp +{ + struct OControlWizardSettings + { + OUString sControlLabel; + }; + + class OControlWizard; + typedef ::vcl::OWizardPage OControlWizardPage_Base; + class OControlWizardPage : public OControlWizardPage_Base + { + OControlWizard* m_pDialog; + std::unique_ptr<weld::Label> m_xFormDatasourceLabel; + std::unique_ptr<weld::Label> m_xFormDatasource; + std::unique_ptr<weld::Label> m_xFormContentTypeLabel; + std::unique_ptr<weld::Label> m_xFormContentType; + std::unique_ptr<weld::Label> m_xFormTable; + std::unique_ptr<weld::Frame> m_xFrame; + + protected: + OControlWizard* getDialog(); + const OControlWizard* getDialog() const; + const OControlWizardContext& getContext() const; + bool updateContext(); + void setFormConnection(const css::uno::Reference< css::sdbc::XConnection >& _rxConn, bool _bAutoDispose = true ); + css::uno::Reference< css::sdbc::XConnection > + getFormConnection() const; + public: + OControlWizardPage(weld::Container* pPage, OControlWizard* pWizard, const OUString& rUIXMLDescription, const OUString& rID); + virtual ~OControlWizardPage() override; + + protected: + static void fillListBox( + weld::TreeView& _rList, + const css::uno::Sequence< OUString >& _rItems); + static void fillListBox( + weld::ComboBox& _rList, + const css::uno::Sequence< OUString >& _rItems); + + protected: + void enableFormDatasourceDisplay(); + + protected: + // OWizardPage overridables + virtual void initializePage() override; + }; + + struct OAccessRegulator; + + typedef ::vcl::WizardMachine OControlWizard_Base; + class OControlWizard : public OControlWizard_Base + { + private: + OControlWizardContext m_aContext; + css::uno::Reference< css::uno::XComponentContext > + m_xContext; + + public: + OControlWizard( + weld::Window* _pParent, + const css::uno::Reference< css::beans::XPropertySet >& _rxObjectModel, + const css::uno::Reference< css::uno::XComponentContext >& _rxContext + ); + virtual ~OControlWizard() override; + + // make the same base class methods public + using OControlWizard_Base::travelNext; + + public: + const css::uno::Reference< css::uno::XComponentContext >& + getComponentContext() const { return m_xContext; } + + const OControlWizardContext& getContext() const { return m_aContext; } + bool updateContext(const OAccessRegulator&); + void setFormConnection(const OAccessRegulator&, const css::uno::Reference< css::sdbc::XConnection >& _rxConn, bool _bAutoDispose ); + css::uno::Reference< css::sdbc::XConnection > + getFormConnection(const OAccessRegulator&) const; + + /** returns the com.sun.star.task.InteractionHandler + @param _pWindow The window will be used when an error message has to be shown. + */ + css::uno::Reference< css::task::XInteractionHandler > getInteractionHandler(weld::Window* _pWindow) const; + + protected: + // initialize the derivees settings (which have to be derived from OControlWizardSettings) + // with some common data extracted from the control model + void initControlSettings(OControlWizardSettings* _pSettings); + // commit the control-relevant settings + void commitControlSettings(OControlWizardSettings const * _pSettings); + + bool needDatasourceSelection(); + + css::uno::Reference< css::sdbc::XConnection > + getFormConnection() const; + + virtual bool approveControl(sal_Int16 _nClassId) = 0; + + virtual short run() override; + + private: + bool initContext(); + + void implGetDSContext(); + void implDetermineForm(); + void implDeterminePage(); + void implDetermineShape(); + }; + + +} // namespace dbp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/dbp.component b/extensions/source/dbpilots/dbp.component new file mode 100644 index 0000000000..5cff1fb3c3 --- /dev/null +++ b/extensions/source/dbpilots/dbp.component @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="org.openoffice.comp.dbp.OGridWizard" + constructor="extensions_dbp_OGridWizard_get_implementation"> + <service name="com.sun.star.sdb.GridControlAutoPilot"/> + </implementation> + <implementation name="org.openoffice.comp.dbp.OGroupBoxWizard" + constructor="extensions_dbp_OGroupBoxWizard_get_implementation"> + <service name="com.sun.star.sdb.GroupBoxAutoPilot"/> + </implementation> + <implementation name="org.openoffice.comp.dbp.OListComboWizard" + constructor="extensions_dbp_OListComboWizard_get_implementation"> + <service name="com.sun.star.sdb.ListComboBoxAutoPilot"/> + </implementation> +</component> diff --git a/extensions/source/dbpilots/dbptools.cxx b/extensions/source/dbpilots/dbptools.cxx new file mode 100644 index 0000000000..1b647e84cd --- /dev/null +++ b/extensions/source/dbpilots/dbptools.cxx @@ -0,0 +1,62 @@ +/* -*- 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 "dbptools.hxx" +#include <tools/debug.hxx> +#include <osl/diagnose.h> + + +namespace dbp +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::container; + + + void disambiguateName(const Reference< XNameAccess >& _rxContainer, OUString& _rElementsName) + { + DBG_ASSERT(_rxContainer.is(), "::dbp::disambiguateName: invalid container!"); + if (!_rxContainer.is()) + return; + + try + { + OUString sBase(_rElementsName); + for (sal_Int32 i=1; i<0x7FFFFFFF; ++i) + { + _rElementsName = sBase; + _rElementsName += OUString::number(i); + if (!_rxContainer->hasByName(_rElementsName)) + return; + } + // can't do anything ... no free names + _rElementsName = sBase; + } + catch(const Exception&) + { + OSL_FAIL("::dbp::disambiguateName: something went (strangely) wrong!"); + } + } + + +} // namespace dbp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/dbptools.hxx b/extensions/source/dbpilots/dbptools.hxx new file mode 100644 index 0000000000..d7577f1877 --- /dev/null +++ b/extensions/source/dbpilots/dbptools.hxx @@ -0,0 +1,37 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/container/XNameAccess.hpp> + + +namespace dbp +{ + + + void disambiguateName( + const css::uno::Reference< css::container::XNameAccess >& _rxContainer, + OUString& _rElementsName); + + +} // namespace dbp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/dbptypes.hxx b/extensions/source/dbpilots/dbptypes.hxx new file mode 100644 index 0000000000..8b86fe5064 --- /dev/null +++ b/extensions/source/dbpilots/dbptypes.hxx @@ -0,0 +1,36 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <rtl/ustring.hxx> + +#include <map> +#include <set> + +namespace dbp +{ +typedef std::set<OUString> StringBag; +typedef std::map<sal_uInt32, OUString> MapInt2String; + +} // namespace dbp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/gridwizard.cxx b/extensions/source/dbpilots/gridwizard.cxx new file mode 100644 index 0000000000..9950700887 --- /dev/null +++ b/extensions/source/dbpilots/gridwizard.cxx @@ -0,0 +1,446 @@ +/* -*- 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 <vector> + +#include "gridwizard.hxx" +#include <com/sun/star/sdbc/DataType.hpp> +#include <com/sun/star/form/XGridColumnFactory.hpp> +#include <com/sun/star/awt/MouseWheelBehavior.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include "dbptools.hxx" +#include <helpids.h> + +#define GW_STATE_DATASOURCE_SELECTION 0 +#define GW_STATE_FIELDSELECTION 1 + + +namespace dbp +{ + + + 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::container; + using namespace ::com::sun::star::form; + using namespace ::com::sun::star::awt; + + OGridWizard::OGridWizard(weld::Window* _pParent, + const Reference< XPropertySet >& _rxObjectModel, const Reference< XComponentContext >& _rxContext ) + : OControlWizard(_pParent, _rxObjectModel, _rxContext) + , m_bHadDataSelection(true) + { + initControlSettings(&m_aSettings); + + m_xPrevPage->set_help_id(HID_GRIDWIZARD_PREVIOUS); + m_xNextPage->set_help_id(HID_GRIDWIZARD_NEXT); + m_xCancel->set_help_id(HID_GRIDWIZARD_CANCEL); + m_xFinish->set_help_id(HID_GRIDWIZARD_FINISH); + setTitleBase(compmodule::ModuleRes(RID_STR_GRIDWIZARD_TITLE)); + + // if we do not need the data source selection page ... + if (!needDatasourceSelection()) + { // ... skip it! + skip(); + m_bHadDataSelection = false; + } + } + + bool OGridWizard::approveControl(sal_Int16 _nClassId) + { + if (FormComponentType::GRIDCONTROL != _nClassId) + return false; + + Reference< XGridColumnFactory > xColumnFactory(getContext().xObjectModel, UNO_QUERY); + return xColumnFactory.is(); + } + + void OGridWizard::implApplySettings() + { + const OControlWizardContext& rContext = getContext(); + + // the factory for the columns + Reference< XGridColumnFactory > xColumnFactory(rContext.xObjectModel, UNO_QUERY); + DBG_ASSERT(xColumnFactory.is(), "OGridWizard::implApplySettings: should never have made it 'til here!"); + // (if we're here, what the hell happened in approveControl??) + + // the container for the columns + Reference< XNameContainer > xColumnContainer(rContext.xObjectModel, UNO_QUERY); + DBG_ASSERT(xColumnContainer.is(), "OGridWizard::implApplySettings: no container!"); + + if (!xColumnFactory.is() || !xColumnContainer.is()) + return; + + static constexpr OUString s_sMouseWheelBehavior = u"MouseWheelBehavior"_ustr; + static constexpr OUString s_sEmptyString = u""_ustr; + + // collect "descriptors" for the to-be-created (grid)columns + std::vector< OUString > aColumnServiceNames; // service names to be used with the XGridColumnFactory + std::vector< OUString > aColumnLabelPostfixes; // postfixes to append to the column labels + std::vector< OUString > aFormFieldNames; // data field names + + aColumnServiceNames.reserve(getSettings().aSelectedFields.getLength()); + aColumnLabelPostfixes.reserve(getSettings().aSelectedFields.getLength()); + aFormFieldNames.reserve(getSettings().aSelectedFields.getLength()); + + // loop through the selected field names + const OUString* pSelectedFields = getSettings().aSelectedFields.getConstArray(); + const OUString* pEnd = pSelectedFields + getSettings().aSelectedFields.getLength(); + for (;pSelectedFields < pEnd; ++pSelectedFields) + { + // get the information for the selected column + sal_Int32 nFieldType = DataType::OTHER; + OControlWizardContext::TNameTypeMap::const_iterator aFind = rContext.aTypes.find(*pSelectedFields); + if ( aFind != rContext.aTypes.end() ) + nFieldType = aFind->second; + + aFormFieldNames.push_back(*pSelectedFields); + switch (nFieldType) + { + case DataType::BIT: + case DataType::BOOLEAN: + aColumnServiceNames.push_back(OUString("CheckBox")); + aColumnLabelPostfixes.push_back(s_sEmptyString); + break; + + case DataType::TINYINT: + case DataType::SMALLINT: + case DataType::INTEGER: + aColumnServiceNames.push_back(OUString("NumericField")); + aColumnLabelPostfixes.push_back(s_sEmptyString); + break; + + case DataType::FLOAT: + case DataType::REAL: + case DataType::DOUBLE: + case DataType::NUMERIC: + case DataType::DECIMAL: + aColumnServiceNames.push_back(OUString("FormattedField")); + aColumnLabelPostfixes.push_back(s_sEmptyString); + break; + + case DataType::DATE: + aColumnServiceNames.push_back(OUString("DateField")); + aColumnLabelPostfixes.push_back(s_sEmptyString); + break; + + case DataType::TIME: + aColumnServiceNames.push_back(OUString("TimeField")); + aColumnLabelPostfixes.push_back(s_sEmptyString); + break; + + case DataType::TIMESTAMP: + aColumnServiceNames.push_back(OUString("DateField")); + aColumnLabelPostfixes.push_back(compmodule::ModuleRes(RID_STR_DATEPOSTFIX)); + + aFormFieldNames.push_back(*pSelectedFields); + aColumnServiceNames.push_back(OUString("TimeField")); + aColumnLabelPostfixes.push_back(compmodule::ModuleRes(RID_STR_TIMEPOSTFIX)); + break; + + default: + aColumnServiceNames.push_back(OUString("TextField")); + aColumnLabelPostfixes.push_back(s_sEmptyString); + } + } + + DBG_ASSERT( aFormFieldNames.size() == aColumnServiceNames.size() + && aColumnServiceNames.size() == aColumnLabelPostfixes.size(), + "OGridWizard::implApplySettings: inconsistent descriptor sequences!"); + + // now loop through the descriptions and create the (grid)columns out of th descriptors + { + Reference< XNameAccess > xExistenceChecker(xColumnContainer); + + std::vector< OUString >::const_iterator pColumnLabelPostfix = aColumnLabelPostfixes.begin(); + std::vector< OUString >::const_iterator pFormFieldName = aFormFieldNames.begin(); + + for (const auto& rColumnServiceName : aColumnServiceNames) + { + // create a (grid)column for the (resultset)column + try + { + Reference< XPropertySet > xColumn( xColumnFactory->createColumn(rColumnServiceName), UNO_SET_THROW ); + Reference< XPropertySetInfo > xColumnPSI( xColumn->getPropertySetInfo(), UNO_SET_THROW ); + + OUString sColumnName(rColumnServiceName); + disambiguateName(xExistenceChecker, sColumnName); + + // the data field the column should be bound to + xColumn->setPropertyValue("DataField", Any(*pFormFieldName)); + // the label + xColumn->setPropertyValue("Label", Any(*pFormFieldName + *pColumnLabelPostfix)); + // the width (<void/> => column will be auto-sized) + xColumn->setPropertyValue("Width", Any()); + + if ( xColumnPSI->hasPropertyByName( s_sMouseWheelBehavior ) ) + xColumn->setPropertyValue( s_sMouseWheelBehavior, Any( MouseWheelBehavior::SCROLL_DISABLED ) ); + + // insert the column + xColumnContainer->insertByName(sColumnName, Any(xColumn)); + } + catch(const Exception&) + { + SAL_WARN( "extensions.dbpilots", "OGridWizard::implApplySettings: " + "unexpected exception while creating the grid column for field " << + *pFormFieldName ); + } + + ++pColumnLabelPostfix; + ++pFormFieldName; + } + } + } + + std::unique_ptr<BuilderPage> OGridWizard::createPage(WizardState _nState) + { + OUString sIdent(OUString::number(_nState)); + weld::Container* pPageContainer = m_xAssistant->append_page(sIdent); + + switch (_nState) + { + case GW_STATE_DATASOURCE_SELECTION: + return std::make_unique<OTableSelectionPage>(pPageContainer, this); + case GW_STATE_FIELDSELECTION: + return std::make_unique<OGridFieldsSelection>(pPageContainer, this); + } + + return nullptr; + } + + vcl::WizardTypes::WizardState OGridWizard::determineNextState( WizardState _nCurrentState ) const + { + switch (_nCurrentState) + { + case GW_STATE_DATASOURCE_SELECTION: + return GW_STATE_FIELDSELECTION; + case GW_STATE_FIELDSELECTION: + return WZS_INVALID_STATE; + } + + return WZS_INVALID_STATE; + } + + void OGridWizard::enterState(WizardState _nState) + { + OControlWizard::enterState(_nState); + + enableButtons(WizardButtonFlags::PREVIOUS, m_bHadDataSelection ? (GW_STATE_DATASOURCE_SELECTION < _nState) : GW_STATE_FIELDSELECTION < _nState); + enableButtons(WizardButtonFlags::NEXT, GW_STATE_FIELDSELECTION != _nState); + if (_nState < GW_STATE_FIELDSELECTION) + enableButtons(WizardButtonFlags::FINISH, false); + + if (GW_STATE_FIELDSELECTION == _nState) + defaultButton(WizardButtonFlags::FINISH); + } + + + bool OGridWizard::leaveState(WizardState _nState) + { + if (!OControlWizard::leaveState(_nState)) + return false; + + if (GW_STATE_FIELDSELECTION == _nState) + defaultButton(WizardButtonFlags::NEXT); + + return true; + } + + + bool OGridWizard::onFinish() + { + if ( !OControlWizard::onFinish() ) + return false; + + implApplySettings(); + + return true; + } + + OGridFieldsSelection::OGridFieldsSelection(weld::Container* pPage, OGridWizard* pWizard) + : OGridPage(pPage, pWizard, "modules/sabpilot/ui/gridfieldsselectionpage.ui", "GridFieldsSelection") + , m_xExistFields(m_xBuilder->weld_tree_view("existingfields")) + , m_xSelectOne(m_xBuilder->weld_button("fieldright")) + , m_xSelectAll(m_xBuilder->weld_button("allfieldsright")) + , m_xDeselectOne(m_xBuilder->weld_button("fieldleft")) + , m_xDeselectAll(m_xBuilder->weld_button("allfieldsleft")) + , m_xSelFields(m_xBuilder->weld_tree_view("selectedfields")) + { + enableFormDatasourceDisplay(); + + m_xSelectOne->connect_clicked(LINK(this, OGridFieldsSelection, OnMoveOneEntry)); + m_xSelectAll->connect_clicked(LINK(this, OGridFieldsSelection, OnMoveAllEntries)); + m_xDeselectOne->connect_clicked(LINK(this, OGridFieldsSelection, OnMoveOneEntry)); + m_xDeselectAll->connect_clicked(LINK(this, OGridFieldsSelection, OnMoveAllEntries)); + + m_xExistFields->connect_changed(LINK(this, OGridFieldsSelection, OnEntrySelected)); + m_xSelFields->connect_changed(LINK(this, OGridFieldsSelection, OnEntrySelected)); + m_xExistFields->connect_row_activated(LINK(this, OGridFieldsSelection, OnEntryDoubleClicked)); + m_xSelFields->connect_row_activated(LINK(this, OGridFieldsSelection, OnEntryDoubleClicked)); + } + + OGridFieldsSelection::~OGridFieldsSelection() + { + } + + void OGridFieldsSelection::Activate() + { + OGridPage::Activate(); + m_xExistFields->grab_focus(); + } + + bool OGridFieldsSelection::canAdvance() const + { + return false; + // we're the last page in our wizard + } + + void OGridFieldsSelection::initializePage() + { + OGridPage::initializePage(); + + const OControlWizardContext& rContext = getContext(); + fillListBox(*m_xExistFields, rContext.aFieldNames); + + m_xSelFields->clear(); + const OGridSettings& rSettings = getSettings(); + const OUString* pSelected = rSettings.aSelectedFields.getConstArray(); + const OUString* pEnd = pSelected + rSettings.aSelectedFields.getLength(); + for (; pSelected < pEnd; ++pSelected) + { + m_xSelFields->append_text(*pSelected); + m_xExistFields->remove_text(*pSelected); + } + + implCheckButtons(); + } + + bool OGridFieldsSelection::commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) + { + if (!OGridPage::commitPage(_eReason)) + return false; + + OGridSettings& rSettings = getSettings(); + const sal_Int32 nSelected = m_xSelFields->n_children(); + + rSettings.aSelectedFields.realloc(nSelected); + OUString* pSelected = rSettings.aSelectedFields.getArray(); + + for (sal_Int32 i=0; i<nSelected; ++i, ++pSelected) + *pSelected = m_xSelFields->get_text(i); + + return true; + } + + void OGridFieldsSelection::implCheckButtons() + { + m_xSelectOne->set_sensitive(m_xExistFields->count_selected_rows() != 0); + m_xSelectAll->set_sensitive(m_xExistFields->n_children() != 0); + + m_xDeselectOne->set_sensitive(m_xSelFields->count_selected_rows() != 0); + m_xDeselectAll->set_sensitive(m_xSelFields->n_children() != 0); + + getDialog()->enableButtons(WizardButtonFlags::FINISH, 0 != m_xSelFields->n_children()); + } + + IMPL_LINK(OGridFieldsSelection, OnEntryDoubleClicked, weld::TreeView&, rList, bool) + { + weld::Button* pSimulateButton = m_xExistFields.get() == &rList ? m_xSelectOne.get() : m_xDeselectOne.get(); + if (pSimulateButton->get_sensitive()) + OnMoveOneEntry(*pSimulateButton); + return true; + } + + IMPL_LINK_NOARG(OGridFieldsSelection, OnEntrySelected, weld::TreeView&, void) + { + implCheckButtons(); + } + + IMPL_LINK(OGridFieldsSelection, OnMoveOneEntry, weld::Button&, rButton, void) + { + bool bMoveRight = (m_xSelectOne.get() == &rButton); + weld::TreeView& rMoveTo = bMoveRight ? *m_xSelFields : *m_xExistFields; + + // the index of the selected entry + const sal_Int32 nSelected = bMoveRight ? m_xExistFields->get_selected_index() : m_xSelFields->get_selected_index(); + // the (original) relative position of the entry + int nRelativeIndex = bMoveRight ? m_xExistFields->get_id(nSelected).toInt32() : m_xSelFields->get_id(nSelected).toInt32(); + + sal_Int32 nInsertPos = -1; + if (!bMoveRight) + { // need to determine an insert pos which reflects the original + nInsertPos = 0; + while (nInsertPos < rMoveTo.n_children()) + { + if (rMoveTo.get_id(nInsertPos).toInt32() > nRelativeIndex) + break; + ++nInsertPos; + } + } + + // the text of the entry to move + OUString sMovingEntry = bMoveRight ? m_xExistFields->get_text(nSelected) : m_xSelFields->get_text(nSelected); + + // insert the entry preserving it's "relative position" entry data + OUString sId(OUString::number(nRelativeIndex)); + rMoveTo.insert(nullptr, nInsertPos, &sMovingEntry, &sId, nullptr, nullptr, false, nullptr); + + // remove the entry from its old list + if (bMoveRight) + { + sal_Int32 nSelectPos = m_xExistFields->get_selected_index(); + m_xExistFields->remove(nSelected); + if ((nSelectPos != -1) && (nSelectPos < m_xExistFields->n_children())) + m_xExistFields->select(nSelectPos); + + m_xExistFields->grab_focus(); + } + else + { + sal_Int32 nSelectPos = m_xSelFields->get_selected_index(); + m_xSelFields->remove(nSelected); + if ((nSelectPos != -1) && (nSelectPos < m_xSelFields->n_children())) + m_xSelFields->select(nSelectPos); + + m_xSelFields->grab_focus(); + } + + implCheckButtons(); + } + + IMPL_LINK(OGridFieldsSelection, OnMoveAllEntries, weld::Button&, rButton, void) + { + bool bMoveRight = (m_xSelectAll.get() == &rButton); + m_xExistFields->clear(); + m_xSelFields->clear(); + fillListBox(bMoveRight ? *m_xSelFields : *m_xExistFields, getContext().aFieldNames); + + implCheckButtons(); + } + +} // namespace dbp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/gridwizard.hxx b/extensions/source/dbpilots/gridwizard.hxx new file mode 100644 index 0000000000..dc2c069e9f --- /dev/null +++ b/extensions/source/dbpilots/gridwizard.hxx @@ -0,0 +1,103 @@ +/* -*- 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 . + */ + +#pragma once + +#include "controlwizard.hxx" +#include "commonpagesdbp.hxx" + +using vcl::WizardTypes::WizardState; +using vcl::WizardTypes::CommitPageReason; + +namespace dbp +{ + struct OGridSettings : public OControlWizardSettings + { + css::uno::Sequence< OUString > aSelectedFields; + }; + + class OGridWizard final : public OControlWizard + { + OGridSettings m_aSettings; + bool m_bHadDataSelection : 1; + + public: + OGridWizard(weld::Window* _pParent, + const css::uno::Reference< css::beans::XPropertySet >& _rxObjectModel, + const css::uno::Reference< css::uno::XComponentContext >& _rxContext); + + OGridSettings& getSettings() { return m_aSettings; } + + private: + // OWizardMachine overridables + virtual std::unique_ptr<BuilderPage> createPage( WizardState _nState ) override; + virtual WizardState determineNextState( WizardState _nCurrentState ) const override; + virtual void enterState( WizardState _nState ) override; + virtual bool leaveState( WizardState _nState ) override; + virtual bool onFinish() override; + + virtual bool approveControl(sal_Int16 _nClassId) override; + + void implApplySettings(); + }; + + class OGridPage : public OControlWizardPage + { + public: + OGridPage(weld::Container* pPage, OGridWizard* pWizard, const OUString& rUIXMLDescription, const OUString& rID) + : OControlWizardPage(pPage, pWizard, rUIXMLDescription, rID) + { + } + protected: + OGridSettings& getSettings() { return static_cast<OGridWizard*>(getDialog())->getSettings(); } + }; + + class OGridFieldsSelection final : public OGridPage + { + std::unique_ptr<weld::TreeView> m_xExistFields; + std::unique_ptr<weld::Button> m_xSelectOne; + std::unique_ptr<weld::Button> m_xSelectAll; + std::unique_ptr<weld::Button> m_xDeselectOne; + std::unique_ptr<weld::Button> m_xDeselectAll; + std::unique_ptr<weld::TreeView> m_xSelFields; + + public: + explicit OGridFieldsSelection(weld::Container* pPage, OGridWizard* pWizard); + virtual ~OGridFieldsSelection() override; + + private: + // BuilderPage overridables + virtual void Activate() override; + + // OWizardPage overridables + virtual void initializePage() override; + virtual bool commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) override; + virtual bool canAdvance() const override; + + DECL_LINK(OnMoveOneEntry, weld::Button&, void); + DECL_LINK(OnMoveAllEntries, weld::Button&, void); + DECL_LINK(OnEntrySelected, weld::TreeView&, void); + DECL_LINK(OnEntryDoubleClicked, weld::TreeView&, bool); + + void implCheckButtons(); + }; +} // namespace dbp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/groupboxwiz.cxx b/extensions/source/dbpilots/groupboxwiz.cxx new file mode 100644 index 0000000000..3f88e3fc93 --- /dev/null +++ b/extensions/source/dbpilots/groupboxwiz.cxx @@ -0,0 +1,468 @@ +/* -*- 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 "groupboxwiz.hxx" +#include "commonpagesdbp.hxx" +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include "optiongrouplayouter.hxx" +#include <helpids.h> +#include <o3tl/safeint.hxx> + +#define GBW_STATE_OPTIONLIST 0 +#define GBW_STATE_DEFAULTOPTION 1 +#define GBW_STATE_OPTIONVALUES 2 +#define GBW_STATE_DBFIELD 3 +#define GBW_STATE_FINALIZE 4 + +namespace dbp +{ + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::form; + + OGroupBoxWizard::OGroupBoxWizard(weld::Window* _pParent, + const Reference< XPropertySet >& _rxObjectModel, const Reference< XComponentContext >& _rxContext ) + : OControlWizard(_pParent, _rxObjectModel, _rxContext) + , m_bVisitedDefault(false) + , m_bVisitedDB(false) + { + initControlSettings(&m_aSettings); + + m_xPrevPage->set_help_id(HID_GROUPWIZARD_PREVIOUS); + m_xNextPage->set_help_id(HID_GROUPWIZARD_NEXT); + m_xCancel->set_help_id(HID_GROUPWIZARD_CANCEL); + m_xFinish->set_help_id(HID_GROUPWIZARD_FINISH); + setTitleBase(compmodule::ModuleRes(RID_STR_GROUPWIZARD_TITLE)); + } + + bool OGroupBoxWizard::approveControl(sal_Int16 _nClassId) + { + return FormComponentType::GROUPBOX == _nClassId; + } + + std::unique_ptr<BuilderPage> OGroupBoxWizard::createPage(::vcl::WizardTypes::WizardState _nState) + { + OUString sIdent(OUString::number(_nState)); + weld::Container* pPageContainer = m_xAssistant->append_page(sIdent); + + switch (_nState) + { + case GBW_STATE_OPTIONLIST: + return std::make_unique<ORadioSelectionPage>(pPageContainer, this); + + case GBW_STATE_DEFAULTOPTION: + return std::make_unique<ODefaultFieldSelectionPage>(pPageContainer, this); + + case GBW_STATE_OPTIONVALUES: + return std::make_unique<OOptionValuesPage>(pPageContainer, this); + + case GBW_STATE_DBFIELD: + return std::make_unique<OOptionDBFieldPage>(pPageContainer, this); + + case GBW_STATE_FINALIZE: + return std::make_unique<OFinalizeGBWPage>(pPageContainer, this); + } + + return nullptr; + } + + vcl::WizardTypes::WizardState OGroupBoxWizard::determineNextState( ::vcl::WizardTypes::WizardState _nCurrentState ) const + { + switch (_nCurrentState) + { + case GBW_STATE_OPTIONLIST: + return GBW_STATE_DEFAULTOPTION; + + case GBW_STATE_DEFAULTOPTION: + return GBW_STATE_OPTIONVALUES; + + case GBW_STATE_OPTIONVALUES: + if (getContext().aFieldNames.hasElements()) + return GBW_STATE_DBFIELD; + else + return GBW_STATE_FINALIZE; + + case GBW_STATE_DBFIELD: + return GBW_STATE_FINALIZE; + } + + return WZS_INVALID_STATE; + } + + void OGroupBoxWizard::enterState(::vcl::WizardTypes::WizardState _nState) + { + // some stuff to do before calling the base class (modifying our settings) + switch (_nState) + { + case GBW_STATE_DEFAULTOPTION: + if (!m_bVisitedDefault) + { // assume that the first of the radio buttons should be selected + DBG_ASSERT(m_aSettings.aLabels.size(), "OGroupBoxWizard::enterState: should never have reached this state!"); + m_aSettings.sDefaultField = m_aSettings.aLabels[0]; + } + m_bVisitedDefault = true; + break; + + case GBW_STATE_DBFIELD: + if (!m_bVisitedDB) + { // try to generate a default for the DB field + // (simply use the first field in the DB names collection) + if (getContext().aFieldNames.hasElements()) + m_aSettings.sDBField = getContext().aFieldNames[0]; + } + m_bVisitedDB = true; + break; + } + + // setting the def button... to be done before the base class is called, too, 'cause the base class + // calls the pages, which are allowed to override our def button behaviour + defaultButton(GBW_STATE_FINALIZE == _nState ? WizardButtonFlags::FINISH : WizardButtonFlags::NEXT); + + // allow "finish" on the last page only + enableButtons(WizardButtonFlags::FINISH, GBW_STATE_FINALIZE == _nState); + // allow previous on all pages but the first one + enableButtons(WizardButtonFlags::PREVIOUS, GBW_STATE_OPTIONLIST != _nState); + // allow next on all pages but the last one + enableButtons(WizardButtonFlags::NEXT, GBW_STATE_FINALIZE != _nState); + + OControlWizard::enterState(_nState); + } + + bool OGroupBoxWizard::onFinish() + { + // commit the basic control settings + commitControlSettings(&m_aSettings); + + // create the radio buttons + try + { + OOptionGroupLayouter aLayouter( getComponentContext() ); + aLayouter.doLayout(getContext(), getSettings()); + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.dbpilots", + "caught an exception while creating the radio shapes!"); + } + + return OControlWizard::onFinish(); + } + + ORadioSelectionPage::ORadioSelectionPage(weld::Container* pPage, OControlWizard* pWizard) + : OGBWPage(pPage, pWizard, "modules/sabpilot/ui/groupradioselectionpage.ui", "GroupRadioSelectionPage") + , m_xRadioName(m_xBuilder->weld_entry("radiolabels")) + , m_xMoveRight(m_xBuilder->weld_button("toright")) + , m_xMoveLeft(m_xBuilder->weld_button("toleft")) + , m_xExistingRadios(m_xBuilder->weld_tree_view("radiobuttons")) + { + if (getContext().aFieldNames.hasElements()) + { + enableFormDatasourceDisplay(); + } + + m_xMoveLeft->connect_clicked(LINK(this, ORadioSelectionPage, OnMoveEntry)); + m_xMoveRight->connect_clicked(LINK(this, ORadioSelectionPage, OnMoveEntry)); + m_xRadioName->connect_changed(LINK(this, ORadioSelectionPage, OnNameModified)); + m_xExistingRadios->connect_changed(LINK(this, ORadioSelectionPage, OnEntrySelected)); + + implCheckMoveButtons(); + m_xExistingRadios->set_selection_mode(SelectionMode::Multiple); + + getDialog()->defaultButton(m_xMoveRight.get()); + } + + ORadioSelectionPage::~ORadioSelectionPage() + { + } + + void ORadioSelectionPage::Activate() + { + OGBWPage::Activate(); + m_xRadioName->grab_focus(); + } + + void ORadioSelectionPage::initializePage() + { + OGBWPage::initializePage(); + + m_xRadioName->set_text(""); + + // no need to initialize the list of radios here + // (we're the only one affecting this special setting, so it will be in the same state as last time this + // page was committed) + + implCheckMoveButtons(); + } + + bool ORadioSelectionPage::commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) + { + if (!OGBWPage::commitPage(_eReason)) + return false; + + // copy the names of the radio buttons to be inserted + // and initialize the values + OOptionGroupSettings& rSettings = getSettings(); + rSettings.aLabels.clear(); + rSettings.aValues.clear(); + rSettings.aLabels.reserve(m_xExistingRadios->n_children()); + rSettings.aValues.reserve(m_xExistingRadios->n_children()); + for (sal_Int32 i=0; i<m_xExistingRadios->n_children(); ++i) + { + rSettings.aLabels.push_back(m_xExistingRadios->get_text(i)); + rSettings.aValues.push_back(OUString::number(i + 1)); + } + + return true; + } + + IMPL_LINK( ORadioSelectionPage, OnMoveEntry, weld::Button&, rButton, void ) + { + bool bMoveLeft = (m_xMoveLeft.get() == &rButton); + if (bMoveLeft) + { + while (m_xExistingRadios->count_selected_rows()) + m_xExistingRadios->remove(m_xExistingRadios->get_selected_index()); + } + else + { + m_xExistingRadios->append_text(m_xRadioName->get_text()); + m_xRadioName->set_text(""); + } + + implCheckMoveButtons(); + + //adjust the focus + if (bMoveLeft) + m_xExistingRadios->grab_focus(); + else + m_xRadioName->grab_focus(); + } + + IMPL_LINK_NOARG( ORadioSelectionPage, OnEntrySelected, weld::TreeView&, void ) + { + implCheckMoveButtons(); + } + + IMPL_LINK_NOARG( ORadioSelectionPage, OnNameModified, weld::Entry&, void ) + { + implCheckMoveButtons(); + } + + bool ORadioSelectionPage::canAdvance() const + { + return 0 != m_xExistingRadios->n_children(); + } + + void ORadioSelectionPage::implCheckMoveButtons() + { + bool bHaveSome = (0 != m_xExistingRadios->n_children()); + bool bSelectedSome = (0 != m_xExistingRadios->count_selected_rows()); + bool bUnfinishedInput = !m_xRadioName->get_text().isEmpty(); + + m_xMoveLeft->set_sensitive(bSelectedSome); + m_xMoveRight->set_sensitive(bUnfinishedInput); + + OControlWizard* pDialogController = getDialog(); + + pDialogController->enableButtons(WizardButtonFlags::NEXT, bHaveSome); + + weld::Dialog* pDialog = pDialogController->getDialog(); + + if (bUnfinishedInput) + { + if (!pDialog->is_default_widget(m_xMoveRight.get())) + pDialogController->defaultButton(m_xMoveRight.get()); + } + else + { + if (pDialog->is_default_widget(m_xMoveRight.get())) + pDialogController->defaultButton(WizardButtonFlags::NEXT); + } + } + + ODefaultFieldSelectionPage::ODefaultFieldSelectionPage(weld::Container* pPage, OControlWizard* pWizard) + : OMaybeListSelectionPage(pPage, pWizard, "modules/sabpilot/ui/defaultfieldselectionpage.ui", "DefaultFieldSelectionPage") + , m_xDefSelYes(m_xBuilder->weld_radio_button("defaultselectionyes")) + , m_xDefSelNo(m_xBuilder->weld_radio_button("defaultselectionno")) + , m_xDefSelection(m_xBuilder->weld_combo_box("defselectionfield")) + { + announceControls(*m_xDefSelYes, *m_xDefSelNo, *m_xDefSelection); + } + + ODefaultFieldSelectionPage::~ODefaultFieldSelectionPage() + { + } + + void ODefaultFieldSelectionPage::initializePage() + { + OMaybeListSelectionPage::initializePage(); + + const OOptionGroupSettings& rSettings = getSettings(); + + // fill the listbox + m_xDefSelection->clear(); + for (auto const& label : rSettings.aLabels) + m_xDefSelection->append_text(label); + + implInitialize(rSettings.sDefaultField); + } + + bool ODefaultFieldSelectionPage::commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) + { + if (!OMaybeListSelectionPage::commitPage(_eReason)) + return false; + + OOptionGroupSettings& rSettings = getSettings(); + implCommit(rSettings.sDefaultField); + + return true; + } + + OOptionValuesPage::OOptionValuesPage(weld::Container* pPage, OControlWizard* pWizard) + : OGBWPage(pPage, pWizard, "modules/sabpilot/ui/optionvaluespage.ui", "OptionValuesPage") + , m_xValue(m_xBuilder->weld_entry("optionvalue")) + , m_xOptions(m_xBuilder->weld_tree_view("radiobuttons")) + , m_nLastSelection(::vcl::WizardTypes::WizardState(-1)) + { + m_xOptions->connect_changed(LINK(this, OOptionValuesPage, OnOptionSelected)); + } + + OOptionValuesPage::~OOptionValuesPage() + { + } + + IMPL_LINK_NOARG( OOptionValuesPage, OnOptionSelected, weld::TreeView&, void ) + { + implTraveledOptions(); + } + + void OOptionValuesPage::Activate() + { + OGBWPage::Activate(); + m_xValue->grab_focus(); + } + + void OOptionValuesPage::implTraveledOptions() + { + if (::vcl::WizardTypes::WizardState(-1) != m_nLastSelection) + { + // save the value for the last option + DBG_ASSERT(o3tl::make_unsigned(m_nLastSelection) < m_aUncommittedValues.size(), "OOptionValuesPage::implTraveledOptions: invalid previous selection index!"); + m_aUncommittedValues[m_nLastSelection] = m_xValue->get_text(); + } + + m_nLastSelection = m_xOptions->get_selected_index(); + DBG_ASSERT(o3tl::make_unsigned(m_nLastSelection) < m_aUncommittedValues.size(), "OOptionValuesPage::implTraveledOptions: invalid new selection index!"); + m_xValue->set_text(m_aUncommittedValues[m_nLastSelection]); + } + + void OOptionValuesPage::initializePage() + { + OGBWPage::initializePage(); + + const OOptionGroupSettings& rSettings = getSettings(); + DBG_ASSERT(rSettings.aLabels.size(), "OOptionValuesPage::initializePage: no options!!"); + DBG_ASSERT(rSettings.aLabels.size() == rSettings.aValues.size(), "OOptionValuesPage::initializePage: inconsistent data!"); + + // fill the list with all available options + m_xOptions->clear(); + m_nLastSelection = -1; + for (auto const& label : rSettings.aLabels) + m_xOptions->append_text(label); + + // remember the values ... can't set them directly in the settings without the explicit commit call + // so we need have a copy of the values + m_aUncommittedValues = rSettings.aValues; + + // select the first entry + m_xOptions->select(0); + implTraveledOptions(); + } + + bool OOptionValuesPage::commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) + { + if (!OGBWPage::commitPage(_eReason)) + return false; + + OOptionGroupSettings& rSettings = getSettings(); + + // commit the current value + implTraveledOptions(); + // copy the uncommitted values + rSettings.aValues = m_aUncommittedValues; + + return true; + } + + OOptionDBFieldPage::OOptionDBFieldPage(weld::Container* pPage, OControlWizard* pWizard) + : ODBFieldPage(pPage, pWizard) + { + setDescriptionText(compmodule::ModuleRes(RID_STR_GROUPWIZ_DBFIELD)); + } + + OUString& OOptionDBFieldPage::getDBFieldSetting() + { + return static_cast<OGroupBoxWizard*>(getDialog())->getSettings().sDBField; + } + + OFinalizeGBWPage::OFinalizeGBWPage(weld::Container* pPage, OControlWizard* pWizard) + : OGBWPage(pPage, pWizard, "modules/sabpilot/ui/optionsfinalpage.ui", "OptionsFinalPage") + , m_xName(m_xBuilder->weld_entry("nameit")) + { + } + + OFinalizeGBWPage::~OFinalizeGBWPage() + { + } + + void OFinalizeGBWPage::Activate() + { + OGBWPage::Activate(); + m_xName->grab_focus(); + } + + bool OFinalizeGBWPage::canAdvance() const + { + return false; + } + + void OFinalizeGBWPage::initializePage() + { + OGBWPage::initializePage(); + + const OOptionGroupSettings& rSettings = getSettings(); + m_xName->set_text(rSettings.sControlLabel); + } + + bool OFinalizeGBWPage::commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) + { + if (!OGBWPage::commitPage(_eReason)) + return false; + + getSettings().sControlLabel = m_xName->get_text(); + + return true; + } + +} // namespace dbp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/groupboxwiz.hxx b/extensions/source/dbpilots/groupboxwiz.hxx new file mode 100644 index 0000000000..73950ed369 --- /dev/null +++ b/extensions/source/dbpilots/groupboxwiz.hxx @@ -0,0 +1,180 @@ +/* -*- 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 . + */ + +#pragma once + +#include "controlwizard.hxx" +#include "commonpagesdbp.hxx" + +using vcl::WizardTypes::WizardState; +using vcl::WizardTypes::CommitPageReason; + +namespace dbp +{ + + struct OOptionGroupSettings : public OControlWizardSettings + { + std::vector<OUString> aLabels; + std::vector<OUString> aValues; + OUString sDefaultField; + OUString sDBField; + }; + + class OGroupBoxWizard final : public OControlWizard + { + OOptionGroupSettings m_aSettings; + + bool m_bVisitedDefault : 1; + bool m_bVisitedDB : 1; + + public: + OGroupBoxWizard( + weld::Window* _pParent, + const css::uno::Reference< css::beans::XPropertySet >& _rxObjectModel, + const css::uno::Reference< css::uno::XComponentContext >& _rxContext + ); + + OOptionGroupSettings& getSettings() { return m_aSettings; } + + private: + // OWizardMachine overridables + virtual std::unique_ptr<BuilderPage> createPage( WizardState _nState ) override; + virtual WizardState determineNextState( WizardState _nCurrentState ) const override; + virtual void enterState( WizardState _nState ) override; + virtual bool onFinish() override; + + virtual bool approveControl(sal_Int16 _nClassId) override; + }; + + class OGBWPage : public OControlWizardPage + { + public: + OGBWPage(weld::Container* pPage, OControlWizard* pWizard, const OUString& rUIXMLDescription, const OUString& rID) + : OControlWizardPage(pPage, pWizard, rUIXMLDescription, rID) + { + } + + protected: + OOptionGroupSettings& getSettings() { return static_cast<OGroupBoxWizard*>(getDialog())->getSettings(); } + }; + + class ORadioSelectionPage final : public OGBWPage + { + std::unique_ptr<weld::Entry> m_xRadioName; + std::unique_ptr<weld::Button> m_xMoveRight; + std::unique_ptr<weld::Button> m_xMoveLeft; + std::unique_ptr<weld::TreeView> m_xExistingRadios; + + public: + explicit ORadioSelectionPage(weld::Container* pPage, OControlWizard* pWizard); + virtual ~ORadioSelectionPage() override; + + private: + // BuilderPage overridables + void Activate() override; + + // OWizardPage overridables + virtual void initializePage() override; + virtual bool commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) override; + virtual bool canAdvance() const override; + + DECL_LINK( OnMoveEntry, weld::Button&, void ); + DECL_LINK( OnEntrySelected, weld::TreeView&, void ); + DECL_LINK( OnNameModified, weld::Entry&, void ); + + void implCheckMoveButtons(); + }; + + class ODefaultFieldSelectionPage final : public OMaybeListSelectionPage + { + std::unique_ptr<weld::RadioButton> m_xDefSelYes; + std::unique_ptr<weld::RadioButton> m_xDefSelNo; + std::unique_ptr<weld::ComboBox> m_xDefSelection; + + public: + explicit ODefaultFieldSelectionPage(weld::Container* pPage, OControlWizard* pWizard); + virtual ~ODefaultFieldSelectionPage() override; + + private: + // OWizardPage overridables + virtual void initializePage() override; + virtual bool commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) override; + + OOptionGroupSettings& getSettings() { return static_cast<OGroupBoxWizard*>(getDialog())->getSettings(); } + }; + + class OOptionValuesPage final : public OGBWPage + { + std::unique_ptr<weld::Entry> m_xValue; + std::unique_ptr<weld::TreeView> m_xOptions; + + std::vector<OUString> m_aUncommittedValues; + ::vcl::WizardTypes::WizardState + m_nLastSelection; + + public: + explicit OOptionValuesPage(weld::Container* pPage, OControlWizard* pWizard); + virtual ~OOptionValuesPage() override; + + private: + // BuilderPage overridables + void Activate() override; + + // OWizardPage overridables + virtual void initializePage() override; + virtual bool commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) override; + + void implTraveledOptions(); + + DECL_LINK( OnOptionSelected, weld::TreeView&, void ); + }; + + class OOptionDBFieldPage : public ODBFieldPage + { + public: + explicit OOptionDBFieldPage(weld::Container* pPage, OControlWizard* pWizard); + + protected: + // ODBFieldPage overridables + virtual OUString& getDBFieldSetting() override; + }; + + class OFinalizeGBWPage final : public OGBWPage + { + std::unique_ptr<weld::Entry> m_xName; + + public: + explicit OFinalizeGBWPage(weld::Container* pPage, OControlWizard* pWizard); + virtual ~OFinalizeGBWPage() override; + + private: + // BuilderPage overridables + void Activate() override; + + // OWizardPage overridables + virtual void initializePage() override; + virtual bool commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) override; + virtual bool canAdvance() const override; + }; + + +} // namespace dbp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/listcombowizard.cxx b/extensions/source/dbpilots/listcombowizard.cxx new file mode 100644 index 0000000000..886e7ff131 --- /dev/null +++ b/extensions/source/dbpilots/listcombowizard.cxx @@ -0,0 +1,485 @@ +/* -*- 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 "listcombowizard.hxx" +#include "commonpagesdbp.hxx" +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/form/ListSourceType.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <connectivity/dbtools.hxx> +#include <helpids.h> +#include <osl/diagnose.h> + + +namespace dbp +{ + + + 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::sdbcx; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::form; + using namespace ::dbtools; + + OListComboWizard::OListComboWizard(weld::Window* _pParent, + const Reference< XPropertySet >& _rxObjectModel, const Reference< XComponentContext >& _rxContext ) + : OControlWizard(_pParent, _rxObjectModel, _rxContext) + , m_bListBox(false) + , m_bHadDataSelection(true) + { + initControlSettings(&m_aSettings); + + m_xPrevPage->set_help_id(HID_LISTWIZARD_PREVIOUS); + m_xNextPage->set_help_id(HID_LISTWIZARD_NEXT); + m_xCancel->set_help_id(HID_LISTWIZARD_CANCEL); + m_xFinish->set_help_id(HID_LISTWIZARD_FINISH); + + // if we do not need the data source selection page ... + if (!needDatasourceSelection()) + { // ... skip it! + skip(); + m_bHadDataSelection = false; + } + } + + bool OListComboWizard::approveControl(sal_Int16 _nClassId) + { + switch (_nClassId) + { + case FormComponentType::LISTBOX: + m_bListBox = true; + setTitleBase(compmodule::ModuleRes(RID_STR_LISTWIZARD_TITLE)); + return true; + case FormComponentType::COMBOBOX: + m_bListBox = false; + setTitleBase(compmodule::ModuleRes(RID_STR_COMBOWIZARD_TITLE)); + return true; + } + return false; + } + + std::unique_ptr<BuilderPage> OListComboWizard::createPage(WizardState _nState) + { + OUString sIdent(OUString::number(_nState)); + weld::Container* pPageContainer = m_xAssistant->append_page(sIdent); + + switch (_nState) + { + case LCW_STATE_DATASOURCE_SELECTION: + return std::make_unique<OTableSelectionPage>(pPageContainer, this); + case LCW_STATE_TABLESELECTION: + return std::make_unique<OContentTableSelection>(pPageContainer, this); + case LCW_STATE_FIELDSELECTION: + return std::make_unique<OContentFieldSelection>(pPageContainer, this); + case LCW_STATE_FIELDLINK: + return std::make_unique<OLinkFieldsPage>(pPageContainer, this); + case LCW_STATE_COMBODBFIELD: + return std::make_unique<OComboDBFieldPage>(pPageContainer, this); + } + + return nullptr; + } + + vcl::WizardTypes::WizardState OListComboWizard::determineNextState( WizardState _nCurrentState ) const + { + switch (_nCurrentState) + { + case LCW_STATE_DATASOURCE_SELECTION: + return LCW_STATE_TABLESELECTION; + case LCW_STATE_TABLESELECTION: + return LCW_STATE_FIELDSELECTION; + case LCW_STATE_FIELDSELECTION: + return getFinalState(); + } + + return WZS_INVALID_STATE; + } + + void OListComboWizard::enterState(WizardState _nState) + { + OControlWizard::enterState(_nState); + + enableButtons(WizardButtonFlags::PREVIOUS, m_bHadDataSelection ? (LCW_STATE_DATASOURCE_SELECTION < _nState) : LCW_STATE_TABLESELECTION < _nState); + enableButtons(WizardButtonFlags::NEXT, getFinalState() != _nState); + if (_nState < getFinalState()) + enableButtons(WizardButtonFlags::FINISH, false); + + if (getFinalState() == _nState) + defaultButton(WizardButtonFlags::FINISH); + } + + + bool OListComboWizard::leaveState(WizardState _nState) + { + if (!OControlWizard::leaveState(_nState)) + return false; + + if (getFinalState() == _nState) + defaultButton(WizardButtonFlags::NEXT); + + return true; + } + + + void OListComboWizard::implApplySettings() + { + try + { + // for quoting identifiers, we need the connection meta data + Reference< XConnection > xConn = getFormConnection(); + DBG_ASSERT(xConn.is(), "OListComboWizard::implApplySettings: no connection, unable to quote!"); + Reference< XDatabaseMetaData > xMetaData; + if (xConn.is()) + xMetaData = xConn->getMetaData(); + + // do some quotings + if (xMetaData.is()) + { + OUString sQuoteString = xMetaData->getIdentifierQuoteString(); + if (isListBox()) // only when we have a listbox this should be not empty + getSettings().sLinkedListField = quoteName(sQuoteString, getSettings().sLinkedListField); + + OUString sCatalog, sSchema, sName; + ::dbtools::qualifiedNameComponents( xMetaData, getSettings().sListContentTable, sCatalog, sSchema, sName, ::dbtools::EComposeRule::InDataManipulation ); + getSettings().sListContentTable = ::dbtools::composeTableNameForSelect( xConn, sCatalog, sSchema, sName ); + + getSettings().sListContentField = quoteName(sQuoteString, getSettings().sListContentField); + } + + // ListSourceType: SQL + getContext().xObjectModel->setPropertyValue("ListSourceType", Any(sal_Int32(ListSourceType_SQL))); + + if (isListBox()) + { + // BoundColumn: 1 + getContext().xObjectModel->setPropertyValue("BoundColumn", Any(sal_Int16(1))); + + // build the statement to set as list source + OUString sStatement = "SELECT " + + getSettings().sListContentField + ", " + getSettings().sLinkedListField + + " FROM " + getSettings().sListContentTable; + Sequence< OUString > aListSource { sStatement }; + getContext().xObjectModel->setPropertyValue("ListSource", Any(aListSource)); + } + else + { + // build the statement to set as list source + OUString sStatement = "SELECT DISTINCT " + + getSettings().sListContentField + + " FROM " + getSettings().sListContentTable; + getContext().xObjectModel->setPropertyValue( "ListSource", Any(sStatement)); + } + + // the bound field + getContext().xObjectModel->setPropertyValue("DataField", Any(getSettings().sLinkedFormField)); + } + catch(const Exception&) + { + OSL_FAIL("OListComboWizard::implApplySettings: could not set the property values for the listbox!"); + } + } + + + bool OListComboWizard::onFinish() + { + if ( !OControlWizard::onFinish() ) + return false; + + implApplySettings(); + return true; + } + + Reference< XNameAccess > OLCPage::getTables() const + { + Reference< XConnection > xConn = getFormConnection(); + DBG_ASSERT(xConn.is(), "OLCPage::getTables: should have an active connection when reaching this page!"); + + Reference< XTablesSupplier > xSuppTables(xConn, UNO_QUERY); + Reference< XNameAccess > xTables; + if (xSuppTables.is()) + xTables = xSuppTables->getTables(); + + DBG_ASSERT(xTables.is() || !xConn.is(), "OLCPage::getTables: got no tables from the connection!"); + + return xTables; + } + + + Sequence< OUString > OLCPage::getTableFields() + { + Reference< XNameAccess > xTables = getTables(); + Sequence< OUString > aColumnNames; + if (xTables.is()) + { + try + { + // the list table as XColumnsSupplier + Reference< XColumnsSupplier > xSuppCols; + xTables->getByName(getSettings().sListContentTable) >>= xSuppCols; + DBG_ASSERT(xSuppCols.is(), "OLCPage::getTableFields: no columns supplier!"); + + // the columns + Reference< XNameAccess > xColumns; + if (xSuppCols.is()) + xColumns = xSuppCols->getColumns(); + + // the column names + if (xColumns.is()) + aColumnNames = xColumns->getElementNames(); + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION( "extensions.dbpilots", "OLinkFieldsPage::initializePage: caught an exception while retrieving the columns"); + } + } + return aColumnNames; + } + + OContentTableSelection::OContentTableSelection(weld::Container* pPage, OListComboWizard* pWizard) + : OLCPage(pPage, pWizard, "modules/sabpilot/ui/contenttablepage.ui", "TableSelectionPage") + , m_xSelectTable(m_xBuilder->weld_tree_view("table")) + { + enableFormDatasourceDisplay(); + + m_xSelectTable->connect_row_activated(LINK(this, OContentTableSelection, OnTableDoubleClicked)); + m_xSelectTable->connect_changed(LINK(this, OContentTableSelection, OnTableSelected)); + } + + OContentTableSelection::~OContentTableSelection() + { + } + + void OContentTableSelection::Activate() + { + OLCPage::Activate(); + m_xSelectTable->grab_focus(); + } + + bool OContentTableSelection::canAdvance() const + { + if (!OLCPage::canAdvance()) + return false; + + return 0 != m_xSelectTable->count_selected_rows(); + } + + IMPL_LINK_NOARG( OContentTableSelection, OnTableSelected, weld::TreeView&, void ) + { + updateDialogTravelUI(); + } + + IMPL_LINK( OContentTableSelection, OnTableDoubleClicked, weld::TreeView&, _rListBox, bool ) + { + if (_rListBox.count_selected_rows()) + getDialog()->travelNext(); + return true; + } + + void OContentTableSelection::initializePage() + { + OLCPage::initializePage(); + + // fill the list with the table name + m_xSelectTable->clear(); + try + { + Reference< XNameAccess > xTables = getTables(); + Sequence< OUString > aTableNames; + if (xTables.is()) + aTableNames = xTables->getElementNames(); + fillListBox(*m_xSelectTable, aTableNames); + } + catch(const Exception&) + { + OSL_FAIL("OContentTableSelection::initializePage: could not retrieve the table names!"); + } + + m_xSelectTable->select_text(getSettings().sListContentTable); + } + + + bool OContentTableSelection::commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) + { + if (!OLCPage::commitPage(_eReason)) + return false; + + OListComboSettings& rSettings = getSettings(); + rSettings.sListContentTable = m_xSelectTable->get_selected_text(); + if (rSettings.sListContentTable.isEmpty() && (::vcl::WizardTypes::eTravelBackward != _eReason)) + // need to select a table + return false; + + return true; + } + + OContentFieldSelection::OContentFieldSelection(weld::Container* pPage, OListComboWizard* pWizard) + : OLCPage(pPage, pWizard, "modules/sabpilot/ui/contentfieldpage.ui", "FieldSelectionPage") + , m_xSelectTableField(m_xBuilder->weld_tree_view("selectfield")) + , m_xDisplayedField(m_xBuilder->weld_entry("displayfield")) + , m_xInfo(m_xBuilder->weld_label("info")) + { + m_xInfo->set_label(compmodule::ModuleRes( isListBox() ? RID_STR_FIELDINFO_LISTBOX : RID_STR_FIELDINFO_COMBOBOX)); + m_xSelectTableField->connect_changed(LINK(this, OContentFieldSelection, OnFieldSelected)); + m_xSelectTableField->connect_row_activated(LINK(this, OContentFieldSelection, OnTableDoubleClicked)); + } + + OContentFieldSelection::~OContentFieldSelection() + { + } + + void OContentFieldSelection::initializePage() + { + OLCPage::initializePage(); + + // fill the list of fields + fillListBox(*m_xSelectTableField, getTableFields()); + + m_xSelectTableField->select_text(getSettings().sListContentField); + m_xDisplayedField->set_text(getSettings().sListContentField); + } + + bool OContentFieldSelection::canAdvance() const + { + if (!OLCPage::canAdvance()) + return false; + + return 0 != m_xSelectTableField->count_selected_rows(); + } + + IMPL_LINK_NOARG( OContentFieldSelection, OnTableDoubleClicked, weld::TreeView&, bool ) + { + if (m_xSelectTableField->count_selected_rows()) + getDialog()->travelNext(); + return true; + } + + IMPL_LINK_NOARG( OContentFieldSelection, OnFieldSelected, weld::TreeView&, void ) + { + updateDialogTravelUI(); + m_xDisplayedField->set_text(m_xSelectTableField->get_selected_text()); + } + + bool OContentFieldSelection::commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) + { + if (!OLCPage::commitPage(_eReason)) + return false; + + getSettings().sListContentField = m_xSelectTableField->get_selected_text(); + + return true; + } + + OLinkFieldsPage::OLinkFieldsPage(weld::Container* pPage, OListComboWizard* pWizard) + : OLCPage(pPage, pWizard, "modules/sabpilot/ui/fieldlinkpage.ui", "FieldLinkPage") + , m_xValueListField(m_xBuilder->weld_combo_box("valuefield")) + , m_xTableField(m_xBuilder->weld_combo_box("listtable")) + { + m_xValueListField->connect_changed(LINK(this, OLinkFieldsPage, OnSelectionModified)); + m_xTableField->connect_changed(LINK(this, OLinkFieldsPage, OnSelectionModified)); + } + + OLinkFieldsPage::~OLinkFieldsPage() + { + } + + void OLinkFieldsPage::Activate() + { + OLCPage::Activate(); + m_xValueListField->grab_focus(); + } + + void OLinkFieldsPage::initializePage() + { + OLCPage::initializePage(); + + // fill the value list + fillListBox(*m_xValueListField, getContext().aFieldNames); + // fill the table field list + fillListBox(*m_xTableField, getTableFields()); + + // the initial selections + m_xValueListField->set_entry_text(getSettings().sLinkedFormField); + m_xTableField->set_entry_text(getSettings().sLinkedListField); + + implCheckFinish(); + } + + bool OLinkFieldsPage::canAdvance() const + { + // we're on the last page here, no travelNext allowed ... + return false; + } + + void OLinkFieldsPage::implCheckFinish() + { + bool bInvalidSelection = (-1 == m_xValueListField->find_text(m_xValueListField->get_active_text())); + bInvalidSelection |= (-1 == m_xTableField->find_text(m_xTableField->get_active_text())); + getDialog()->enableButtons(WizardButtonFlags::FINISH, !bInvalidSelection); + } + + IMPL_LINK_NOARG(OLinkFieldsPage, OnSelectionModified, weld::ComboBox&, void) + { + implCheckFinish(); + } + + bool OLinkFieldsPage::commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) + { + if (!OLCPage::commitPage(_eReason)) + return false; + + getSettings().sLinkedFormField = m_xValueListField->get_active_text(); + getSettings().sLinkedListField = m_xTableField->get_active_text(); + + return true; + } + + OComboDBFieldPage::OComboDBFieldPage(weld::Container* pPage, OControlWizard* pWizard) + : ODBFieldPage(pPage, pWizard) + { + setDescriptionText(compmodule::ModuleRes(RID_STR_COMBOWIZ_DBFIELD)); + } + + OUString& OComboDBFieldPage::getDBFieldSetting() + { + return static_cast<OListComboWizard*>(getDialog())->getSettings().sLinkedFormField; + } + + void OComboDBFieldPage::Activate() + { + ODBFieldPage::Activate(); + getDialog()->enableButtons(WizardButtonFlags::FINISH, true); + } + + bool OComboDBFieldPage::canAdvance() const + { + // we're on the last page here, no travelNext allowed ... + return false; + } + +} // namespace dbp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/listcombowizard.hxx b/extensions/source/dbpilots/listcombowizard.hxx new file mode 100644 index 0000000000..5e61296f89 --- /dev/null +++ b/extensions/source/dbpilots/listcombowizard.hxx @@ -0,0 +1,178 @@ +/* -*- 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 . + */ + +#pragma once + +#include "controlwizard.hxx" +#include "commonpagesdbp.hxx" + +using vcl::WizardTypes::WizardState; +using vcl::WizardTypes::CommitPageReason; + +namespace dbp +{ + +#define LCW_STATE_DATASOURCE_SELECTION 0 +#define LCW_STATE_TABLESELECTION 1 +#define LCW_STATE_FIELDSELECTION 2 +#define LCW_STATE_FIELDLINK 3 +#define LCW_STATE_COMBODBFIELD 4 + + struct OListComboSettings : public OControlWizardSettings + { + OUString sListContentTable; + OUString sListContentField; + OUString sLinkedFormField; + OUString sLinkedListField; + }; + + class OListComboWizard final : public OControlWizard + { + OListComboSettings m_aSettings; + bool m_bListBox : 1; + bool m_bHadDataSelection : 1; + + public: + OListComboWizard( + weld::Window* pParent, + const css::uno::Reference< css::beans::XPropertySet >& _rxObjectModel, + const css::uno::Reference< css::uno::XComponentContext >& _rxContext + ); + + OListComboSettings& getSettings() { return m_aSettings; } + + bool isListBox() const { return m_bListBox; } + + private: + // OWizardMachine overridables + virtual std::unique_ptr<BuilderPage> createPage( WizardState _nState ) override; + virtual WizardState determineNextState( WizardState _nCurrentState ) const override; + virtual void enterState( WizardState _nState ) override; + virtual bool leaveState( WizardState _nState ) override; + virtual bool onFinish() override; + + virtual bool approveControl(sal_Int16 _nClassId) override; + + WizardState getFinalState() const { return isListBox() ? LCW_STATE_FIELDLINK : LCW_STATE_COMBODBFIELD; } + + void implApplySettings(); + }; + + class OLCPage : public OControlWizardPage + { + public: + OLCPage(weld::Container* pPage, OListComboWizard* pWizard, const OUString& rUIXMLDescription, const OUString& rID) + : OControlWizardPage(pPage, pWizard, rUIXMLDescription, rID) + { + } + + protected: + OListComboSettings& getSettings() { return static_cast<OListComboWizard*>(getDialog())->getSettings(); } + bool isListBox() { return static_cast<OListComboWizard*>(getDialog())->isListBox(); } + + protected: + css::uno::Reference< css::container::XNameAccess > getTables() const; + css::uno::Sequence< OUString > getTableFields(); + }; + + class OContentTableSelection final : public OLCPage + { + std::unique_ptr<weld::TreeView> m_xSelectTable; + + public: + explicit OContentTableSelection(weld::Container* pPage, OListComboWizard* pWizard); + virtual ~OContentTableSelection() override; + + private: + // BuilderPage overridables + virtual void Activate() override; + + // OWizardPage overridables + virtual void initializePage() override; + virtual bool commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) override; + virtual bool canAdvance() const override; + + DECL_LINK( OnTableDoubleClicked, weld::TreeView&, bool ); + DECL_LINK( OnTableSelected, weld::TreeView&, void ); + }; + + class OContentFieldSelection final : public OLCPage + { + std::unique_ptr<weld::TreeView> m_xSelectTableField; + std::unique_ptr<weld::Entry> m_xDisplayedField; + std::unique_ptr<weld::Label> m_xInfo; + + public: + explicit OContentFieldSelection(weld::Container* pPage, OListComboWizard* pWizard); + virtual ~OContentFieldSelection() override; + + private: + DECL_LINK( OnFieldSelected, weld::TreeView&, void ); + DECL_LINK( OnTableDoubleClicked, weld::TreeView&, bool ); + + // OWizardPage overridables + virtual void initializePage() override; + virtual bool commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) override; + virtual bool canAdvance() const override; + }; + + class OLinkFieldsPage final : public OLCPage + { + std::unique_ptr<weld::ComboBox> m_xValueListField; + std::unique_ptr<weld::ComboBox> m_xTableField; + + public: + explicit OLinkFieldsPage(weld::Container* pPage, OListComboWizard* pWizard); + virtual ~OLinkFieldsPage() override; + + private: + // BuilderPage overridables + virtual void Activate() override; + + // OWizardPage overridables + virtual void initializePage() override; + virtual bool commitPage( ::vcl::WizardTypes::CommitPageReason _eReason ) override; + virtual bool canAdvance() const override; + + void implCheckFinish(); + + DECL_LINK(OnSelectionModified, weld::ComboBox&, void); + }; + + class OComboDBFieldPage : public ODBFieldPage + { + public: + explicit OComboDBFieldPage(weld::Container* pPage, OControlWizard* pWizard); + + protected: + // BuilderPage overridables + virtual void Activate() override; + + // OWizardPage overridables + virtual bool canAdvance() const override; + + // ODBFieldPage overridables + virtual OUString& getDBFieldSetting() override; + }; + + +} // namespace dbp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/moduledbp.cxx b/extensions/source/dbpilots/moduledbp.cxx new file mode 100644 index 0000000000..0a8f8fd4d2 --- /dev/null +++ b/extensions/source/dbpilots/moduledbp.cxx @@ -0,0 +1,22 @@ +/* -*- 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 <componentmodule.cxx> + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/optiongrouplayouter.cxx b/extensions/source/dbpilots/optiongrouplayouter.cxx new file mode 100644 index 0000000000..c35b0cefc8 --- /dev/null +++ b/extensions/source/dbpilots/optiongrouplayouter.cxx @@ -0,0 +1,204 @@ +/* -*- 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 "optiongrouplayouter.hxx" +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/drawing/ShapeCollection.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/drawing/XShapeGrouper.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> +#include "groupboxwiz.hxx" +#include "dbptools.hxx" +#include <comphelper/diagnose_ex.hxx> + + +namespace dbp +{ + + +#define BUTTON_HEIGHT 300 +#define HEIGHT 450 +#define OFFSET 300 +#define MIN_WIDTH 600 + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::drawing; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::awt; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::text; + using namespace ::com::sun::star::view; + + OOptionGroupLayouter::OOptionGroupLayouter(const Reference< XComponentContext >& _rxContext) + :mxContext(_rxContext) + { + } + + + void OOptionGroupLayouter::doLayout(const OControlWizardContext& _rContext, const OOptionGroupSettings& _rSettings) + { + Reference< XShapes > xPageShapes = _rContext.xDrawPage; + if (!xPageShapes.is()) + { + OSL_FAIL("OOptionGroupLayouter::OOptionGroupLayouter: missing the XShapes interface for the page!"); + return; + } + + Reference< XMultiServiceFactory > xDocFactory(_rContext.xDocumentModel, UNO_QUERY); + if (!xDocFactory.is()) + { + OSL_FAIL("OOptionGroupLayouter::OOptionGroupLayouter: no document service factory!"); + return; + } + + // no. of buttons to create + sal_Int32 nRadioButtons = _rSettings.aLabels.size(); + + // the shape of the groupbox + css::awt::Size aControlShapeSize = _rContext.xObjectShape->getSize(); + // maybe need to adjust the size if the control shapes + sal_Int32 nMinShapeHeight = BUTTON_HEIGHT*(nRadioButtons+1) + BUTTON_HEIGHT + BUTTON_HEIGHT/4; + if (aControlShapeSize.Height < nMinShapeHeight) + aControlShapeSize.Height = nMinShapeHeight; + if (aControlShapeSize.Width < MIN_WIDTH) + aControlShapeSize.Width = MIN_WIDTH; + _rContext.xObjectShape->setSize(aControlShapeSize); + + // if we're working on a writer document, we need to anchor the shape + implAnchorShape(Reference< XPropertySet >(_rContext.xObjectShape, UNO_QUERY)); + + // shape collection (for grouping the shapes) + Reference< XShapes > xButtonCollection( ShapeCollection::create(mxContext) ); + // first member : the shape of the control + xButtonCollection->add(_rContext.xObjectShape); + + sal_Int32 nTempHeight = (aControlShapeSize.Height - BUTTON_HEIGHT/4) / (nRadioButtons + 1); + + css::awt::Point aShapePosition = _rContext.xObjectShape->getPosition(); + + css::awt::Size aButtonSize(aControlShapeSize); + aButtonSize.Width = aControlShapeSize.Width - OFFSET; + aButtonSize.Height = HEIGHT; + css::awt::Point aButtonPosition; + aButtonPosition.X = aShapePosition.X + OFFSET; + + OUString sElementsName("RadioGroup"); + disambiguateName(Reference< XNameAccess >(_rContext.xForm, UNO_QUERY), sElementsName); + + auto aLabelIter = _rSettings.aLabels.cbegin(); + auto aValueIter = _rSettings.aValues.cbegin(); + for (sal_Int32 i=0; i<nRadioButtons; ++i, ++aLabelIter, ++aValueIter) + { + aButtonPosition.Y = aShapePosition.Y + (i+1) * nTempHeight; + + Reference< XPropertySet > xRadioModel( + xDocFactory->createInstance("com.sun.star.form.component.RadioButton"), + UNO_QUERY); + + // the label + xRadioModel->setPropertyValue("Label", Any(*aLabelIter)); + // the value + xRadioModel->setPropertyValue("RefValue", Any(*aValueIter)); + + // default selection + if (_rSettings.sDefaultField == *aLabelIter) + xRadioModel->setPropertyValue("DefaultState", Any(sal_Int16(1))); + + // the connection to the database field + if (!_rSettings.sDBField.isEmpty()) + xRadioModel->setPropertyValue("DataField", Any(_rSettings.sDBField)); + + // the name for the model + xRadioModel->setPropertyValue("Name", Any(sElementsName)); + + // create a shape for the radio button + Reference< XControlShape > xRadioShape( + xDocFactory->createInstance("com.sun.star.drawing.ControlShape"), + UNO_QUERY); + Reference< XPropertySet > xShapeProperties(xRadioShape, UNO_QUERY); + + // if we're working on a writer document, we need to anchor the shape + implAnchorShape(xShapeProperties); + + // position it + xRadioShape->setSize(aButtonSize); + xRadioShape->setPosition(aButtonPosition); + // knitting with the model + xRadioShape->setControl(Reference< XControlModel >(xRadioModel, UNO_QUERY)); + + // the name of the shape + // tdf#117282 com.sun.star.drawing.ControlShape *has* no property + // of type 'Name'. In older versions it was an error that this did + // not throw an UnknownPropertyException. Still, it was never set + // at the Shape/SdrObject and was lost. + // Thus - just do no tset it. It is/stays part of the FormControl + // data, so it will be shown in the FormControl dialogs. It is not + // shown/used in SdrObject::Name dialog (e.g. context menu/Name...) + // if (xShapeProperties.is()) + // xShapeProperties->setPropertyValue("Name", makeAny(sElementsName)); + + // add to the page + xPageShapes->add(xRadioShape); + // add to the collection (for the later grouping) + xButtonCollection->add(xRadioShape); + + // set the GroupBox as "LabelControl" for the RadioButton + // (_after_ having inserted the model into the page!) + xRadioModel->setPropertyValue("LabelControl", Any(_rContext.xObjectModel)); + } + + // group the shapes + try + { + Reference< XShapeGrouper > xGrouper(_rContext.xDrawPage, UNO_QUERY); + if (xGrouper.is()) + { + Reference< XShapeGroup > xGroupedOptions = xGrouper->group(xButtonCollection); + Reference< XSelectionSupplier > xSelector(_rContext.xDocumentModel->getCurrentController(), UNO_QUERY); + if (xSelector.is()) + xSelector->select(Any(xGroupedOptions)); + } + } + catch(Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.dbpilots", + "caught an exception while grouping the shapes!"); + } + } + + + void OOptionGroupLayouter::implAnchorShape(const Reference< XPropertySet >& _rxShapeProps) + { + static constexpr OUString s_sAnchorPropertyName = u"AnchorType"_ustr; + Reference< XPropertySetInfo > xPropertyInfo; + if (_rxShapeProps.is()) + xPropertyInfo = _rxShapeProps->getPropertySetInfo(); + if (xPropertyInfo.is() && xPropertyInfo->hasPropertyByName(s_sAnchorPropertyName)) + _rxShapeProps->setPropertyValue(s_sAnchorPropertyName, Any(TextContentAnchorType_AT_PAGE)); + } + + +} // namespace dbp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/optiongrouplayouter.hxx b/extensions/source/dbpilots/optiongrouplayouter.hxx new file mode 100644 index 0000000000..64a6998c17 --- /dev/null +++ b/extensions/source/dbpilots/optiongrouplayouter.hxx @@ -0,0 +1,58 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + + +namespace dbp +{ + + + struct OControlWizardContext; + struct OOptionGroupSettings; + + class OOptionGroupLayouter final + { + css::uno::Reference< css::uno::XComponentContext > + mxContext; + + public: + explicit OOptionGroupLayouter( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext + ); + + void doLayout( + const OControlWizardContext& _rContext, + const OOptionGroupSettings& _rSettings + ); + + private: + static void implAnchorShape( + const css::uno::Reference< css::beans::XPropertySet >& _rxShapeProps + ); + }; + + +} // namespace dbp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/unoautopilot.hxx b/extensions/source/dbpilots/unoautopilot.hxx new file mode 100644 index 0000000000..ea6e886d2d --- /dev/null +++ b/extensions/source/dbpilots/unoautopilot.hxx @@ -0,0 +1,117 @@ +/* -*- 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 . + */ + +#pragma once + +#include <svtools/genericunodialog.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/proparrhlp.hxx> +#include <componentmodule.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <utility> +#include <vcl/svapp.hxx> + +namespace dbp +{ + typedef ::svt::OGenericUnoDialog OUnoAutoPilot_Base; + template <class TYPE> + class OUnoAutoPilot final + :public OUnoAutoPilot_Base + ,public ::comphelper::OPropertyArrayUsageHelper< OUnoAutoPilot< TYPE > > + { + public: + explicit OUnoAutoPilot(const css::uno::Reference< css::uno::XComponentContext >& _rxORB, + OUString aImplementationName, + const css::uno::Sequence<OUString>& aSupportedServices) + : OUnoAutoPilot_Base(_rxORB), + m_ImplementationName(std::move(aImplementationName)), + m_SupportedServices(aSupportedServices) + { + } + + + // XTypeProvider + virtual css::uno::Sequence<sal_Int8> SAL_CALL getImplementationId( ) override + { + return css::uno::Sequence<sal_Int8>(); + } + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override + { + return m_ImplementationName; + } + + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override + { + return m_SupportedServices; + } + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo> SAL_CALL getPropertySetInfo() override + { + css::uno::Reference< css::beans::XPropertySetInfo > xInfo( createPropertySetInfo( getInfoHelper() ) ); + return xInfo; + } + + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override + { + return *this->getArrayHelper(); + } + + // OPropertyArrayUsageHelper + virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const override + { + css::uno::Sequence< css::beans::Property > aProps; + describeProperties(aProps); + return new ::cppu::OPropertyArrayHelper(aProps); + } + + private: + // OGenericUnoDialog overridables + virtual std::unique_ptr<weld::DialogController> createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) override + { + return std::make_unique<TYPE>(Application::GetFrameWeld(rParent), m_xObjectModel, m_aContext); + } + + virtual void implInitialize(const css::uno::Any& _rValue) override + { + css::beans::PropertyValue aArgument; + if (_rValue >>= aArgument) + if (aArgument.Name == "ObjectModel") + { + aArgument.Value >>= m_xObjectModel; + return; + } + + OUnoAutoPilot_Base::implInitialize(_rValue); + } + + css::uno::Reference< css::beans::XPropertySet > m_xObjectModel; + OUString m_ImplementationName; + css::uno::Sequence<OUString> m_SupportedServices; + + }; + +} // namespace dbp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/wizardcontext.hxx b/extensions/source/dbpilots/wizardcontext.hxx new file mode 100644 index 0000000000..65f4c1410f --- /dev/null +++ b/extensions/source/dbpilots/wizardcontext.hxx @@ -0,0 +1,82 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <map> + +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/sdb/XDatabaseContext.hpp> +#include <com/sun/star/sdbc/XRowSet.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/drawing/XDrawPage.hpp> +#include <com/sun/star/drawing/XControlShape.hpp> +#include <com/sun/star/frame/XModel.hpp> + + +namespace dbp +{ + + struct OControlWizardContext + { + // the global data source context + css::uno::Reference< css::sdb::XDatabaseContext > + xDatasourceContext; + + // the control mode + css::uno::Reference< css::beans::XPropertySet > + xObjectModel; + // the form the control model belongs to + css::uno::Reference< css::beans::XPropertySet > + xForm; + // the form as rowset + css::uno::Reference< css::sdbc::XRowSet > + xRowSet; + + // the model of the document + css::uno::Reference< css::frame::XModel > + xDocumentModel; + // the page where the control mode resides + css::uno::Reference< css::drawing::XDrawPage > + xDrawPage; + // the shape which carries the control + css::uno::Reference< css::drawing::XControlShape > + xObjectShape; + + // the tables or queries of the data source the form is bound to (if any) + css::uno::Reference< css::container::XNameAccess > + xObjectContainer; + // the column types container of the object the form is bound to (table, query or SQL statement) + typedef std::map<OUString, sal_Int32> TNameTypeMap; + TNameTypeMap aTypes; + // the column names of the object the form is bound to (table, query or SQL statement) + css::uno::Sequence< OUString > + aFieldNames; + + bool bEmbedded; + }; + + +} // namespace dbp + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/dbpilots/wizardservices.cxx b/extensions/source/dbpilots/wizardservices.cxx new file mode 100644 index 0000000000..25bcf79427 --- /dev/null +++ b/extensions/source/dbpilots/wizardservices.cxx @@ -0,0 +1,65 @@ +/* -*- 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 "unoautopilot.hxx" +#include "groupboxwiz.hxx" +#include "listcombowizard.hxx" +#include "gridwizard.hxx" + +// the registration methods + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_dbp_OGroupBoxWizard_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire( + new ::dbp::OUnoAutoPilot< ::dbp::OGroupBoxWizard>( + context, + "org.openoffice.comp.dbp.OGroupBoxWizard", + { "com.sun.star.sdb.GroupBoxAutoPilot" } + )); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_dbp_OListComboWizard_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire( + new ::dbp::OUnoAutoPilot< ::dbp::OListComboWizard>( + context, + "org.openoffice.comp.dbp.OListComboWizard", + { "com.sun.star.sdb.ListComboBoxAutoPilot" } + )); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_dbp_OGridWizard_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire( + new ::dbp::OUnoAutoPilot< ::dbp::OGridWizard>( + context, + "org.openoffice.comp.dbp.OGridWizard", + { "com.sun.star.sdb.GridControlAutoPilot" } + )); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/inc/componentmodule.cxx b/extensions/source/inc/componentmodule.cxx new file mode 100644 index 0000000000..e1b29b2ece --- /dev/null +++ b/extensions/source/inc/componentmodule.cxx @@ -0,0 +1,35 @@ +/* -*- 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 "componentmodule.hxx" +#include <unotools/resmgr.hxx> + +namespace compmodule +{ + + OUString ModuleRes(TranslateId pId) + { + return Translate::get(pId, Translate::Create("pcr")); + } + + +} // namespace compmodule + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/inc/componentmodule.hxx b/extensions/source/inc/componentmodule.hxx new file mode 100644 index 0000000000..24b5032cd9 --- /dev/null +++ b/extensions/source/inc/componentmodule.hxx @@ -0,0 +1,34 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <unotools/resmgr.hxx> + +namespace compmodule +{ + + // specialized ResId, using the resource locale provided by the global module + OUString ModuleRes(TranslateId pId); + +} // namespace compmodule + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/logging/consolehandler.cxx b/extensions/source/logging/consolehandler.cxx new file mode 100644 index 0000000000..d1455baa31 --- /dev/null +++ b/extensions/source/logging/consolehandler.cxx @@ -0,0 +1,264 @@ +/* -*- 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 "methodguard.hxx" +#include "loghandler.hxx" + +#include <com/sun/star/logging/XConsoleHandler.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/logging/LogLevel.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/beans/NamedValue.hpp> + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <stdio.h> + +namespace logging +{ + using ::com::sun::star::logging::XConsoleHandler; + using ::com::sun::star::lang::XServiceInfo; + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::logging::XLogFormatter; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::logging::LogRecord; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::lang::IllegalArgumentException; + using ::com::sun::star::beans::NamedValue; + + typedef ::cppu::WeakComponentImplHelper < XConsoleHandler + , XServiceInfo + > ConsoleHandler_Base; + + namespace { + + class ConsoleHandler :public ::cppu::BaseMutex + ,public ConsoleHandler_Base + { + private: + LogHandlerHelper m_aHandlerHelper; + sal_Int32 m_nThreshold; + + public: + ConsoleHandler(const Reference<XComponentContext> &context, + const css::uno::Sequence<css::uno::Any> &arguments); + virtual ~ConsoleHandler() override; + + private: + // XConsoleHandler + virtual ::sal_Int32 SAL_CALL getThreshold() override; + virtual void SAL_CALL setThreshold( ::sal_Int32 _threshold ) override; + + // XLogHandler + virtual OUString SAL_CALL getEncoding() override; + virtual void SAL_CALL setEncoding( const OUString& _encoding ) override; + virtual Reference< XLogFormatter > SAL_CALL getFormatter() override; + virtual void SAL_CALL setFormatter( const Reference< XLogFormatter >& _formatter ) override; + virtual ::sal_Int32 SAL_CALL getLevel() override; + virtual void SAL_CALL setLevel( ::sal_Int32 _level ) override; + virtual void SAL_CALL flush( ) override; + virtual sal_Bool SAL_CALL publish( const LogRecord& Record ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& _rServiceName ) override; + virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // OComponentHelper + virtual void SAL_CALL disposing() override; + + public: + typedef ComponentMethodGuard< ConsoleHandler > MethodGuard; + void enterMethod( MethodGuard::Access ); + void leaveMethod( MethodGuard::Access ); + }; + + } + + ConsoleHandler::ConsoleHandler(const Reference<XComponentContext> &context, + const css::uno::Sequence<css::uno::Any> &arguments) + :ConsoleHandler_Base( m_aMutex ) + ,m_aHandlerHelper( context, m_aMutex, rBHelper ) + ,m_nThreshold( css::logging::LogLevel::SEVERE ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( !arguments.hasElements() ) + { // create() - nothing to init + m_aHandlerHelper.setIsInitialized(); + return; + } + + if ( arguments.getLength() != 1 ) + throw IllegalArgumentException( OUString(), *this, 1 ); + + Sequence< NamedValue > aSettings; + if ( !( arguments[0] >>= aSettings ) ) + throw IllegalArgumentException( OUString(), *this, 1 ); + + // createWithSettings( [in] sequence< css::beans::NamedValue > Settings ) + ::comphelper::NamedValueCollection aTypedSettings( aSettings ); + m_aHandlerHelper.initFromSettings( aTypedSettings ); + + aTypedSettings.get_ensureType( "Threshold", m_nThreshold ); + + m_aHandlerHelper.setIsInitialized(); + } + + ConsoleHandler::~ConsoleHandler() + { + if ( !rBHelper.bDisposed ) + { + acquire(); + dispose(); + } + } + + + void SAL_CALL ConsoleHandler::disposing() + { + m_aHandlerHelper.setFormatter( nullptr ); + } + + + void ConsoleHandler::enterMethod( MethodGuard::Access ) + { + m_aHandlerHelper.enterMethod(); + } + + + void ConsoleHandler::leaveMethod( MethodGuard::Access ) + { + m_aMutex.release(); + } + + + ::sal_Int32 SAL_CALL ConsoleHandler::getThreshold() + { + MethodGuard aGuard( *this ); + return m_nThreshold; + } + + + void SAL_CALL ConsoleHandler::setThreshold( ::sal_Int32 _threshold ) + { + MethodGuard aGuard( *this ); + m_nThreshold = _threshold; + } + + + OUString SAL_CALL ConsoleHandler::getEncoding() + { + MethodGuard aGuard( *this ); + OUString sEncoding; + OSL_VERIFY( m_aHandlerHelper.getEncoding( sEncoding ) ); + return sEncoding; + } + + + void SAL_CALL ConsoleHandler::setEncoding( const OUString& _rEncoding ) + { + MethodGuard aGuard( *this ); + OSL_VERIFY( m_aHandlerHelper.setEncoding( _rEncoding ) ); + } + + + Reference< XLogFormatter > SAL_CALL ConsoleHandler::getFormatter() + { + MethodGuard aGuard( *this ); + return m_aHandlerHelper.getFormatter(); + } + + + void SAL_CALL ConsoleHandler::setFormatter( const Reference< XLogFormatter >& _rxFormatter ) + { + MethodGuard aGuard( *this ); + m_aHandlerHelper.setFormatter( _rxFormatter ); + } + + + ::sal_Int32 SAL_CALL ConsoleHandler::getLevel() + { + MethodGuard aGuard( *this ); + return m_aHandlerHelper.getLevel(); + } + + + void SAL_CALL ConsoleHandler::setLevel( ::sal_Int32 _nLevel ) + { + MethodGuard aGuard( *this ); + m_aHandlerHelper.setLevel( _nLevel ); + } + + + void SAL_CALL ConsoleHandler::flush( ) + { + MethodGuard aGuard( *this ); + fflush( stdout ); + fflush( stderr ); + } + + + sal_Bool SAL_CALL ConsoleHandler::publish( const LogRecord& _rRecord ) + { + MethodGuard aGuard( *this ); + + OString sEntry; + if ( !m_aHandlerHelper.formatForPublishing( _rRecord, sEntry ) ) + return false; + + if ( _rRecord.Level >= m_nThreshold ) + fprintf( stderr, "%s\n", sEntry.getStr() ); + else + fprintf( stdout, "%s\n", sEntry.getStr() ); + + return true; + } + + OUString SAL_CALL ConsoleHandler::getImplementationName() + { + return "com.sun.star.comp.extensions.ConsoleHandler"; + } + + sal_Bool SAL_CALL ConsoleHandler::supportsService( const OUString& _rServiceName ) + { + return cppu::supportsService(this, _rServiceName); + } + + Sequence< OUString > SAL_CALL ConsoleHandler::getSupportedServiceNames() + { + return { "com.sun.star.logging.ConsoleHandler" }; + } + +} // namespace logging + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_extensions_ConsoleHandler( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &arguments) +{ + return cppu::acquire(new logging::ConsoleHandler(context, arguments)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/logging/csvformatter.cxx b/extensions/source/logging/csvformatter.cxx new file mode 100644 index 0000000000..a9ea13f208 --- /dev/null +++ b/extensions/source/logging/csvformatter.cxx @@ -0,0 +1,319 @@ +/* -*- 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 <com/sun/star/logging/XCsvLogFormatter.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <rtl/ustrbuf.hxx> +#include <sal/macros.h> +#include <sal/types.h> + +#include <stdio.h> +#include <string_view> + +namespace logging +{ + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::logging::LogRecord; + + namespace { + + // formats for csv files as defined by RFC4180 + class CsvFormatter : public cppu::WeakImplHelper<css::logging::XCsvLogFormatter, css::lang::XServiceInfo> + { + public: + virtual OUString SAL_CALL formatMultiColumn(const Sequence< OUString>& column_data) override; + + CsvFormatter(); + + private: + // XCsvLogFormatter + virtual sal_Bool SAL_CALL getLogEventNo() override; + virtual sal_Bool SAL_CALL getLogThread() override; + virtual sal_Bool SAL_CALL getLogTimestamp() override; + virtual sal_Bool SAL_CALL getLogSource() override; + virtual Sequence< OUString > SAL_CALL getColumnnames() override; + + virtual void SAL_CALL setLogEventNo( sal_Bool log_event_no ) override; + virtual void SAL_CALL setLogThread( sal_Bool log_thread ) override; + virtual void SAL_CALL setLogTimestamp( sal_Bool log_timestamp ) override; + virtual void SAL_CALL setLogSource( sal_Bool log_source ) override; + virtual void SAL_CALL setColumnnames( const Sequence< OUString>& column_names) override; + + // XLogFormatter + virtual OUString SAL_CALL getHead( ) override; + virtual OUString SAL_CALL format( const LogRecord& Record ) override; + virtual OUString SAL_CALL getTail( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& service_name ) override; + virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + private: + bool m_LogEventNo; + bool m_LogThread; + bool m_LogTimestamp; + bool m_LogSource; + bool m_MultiColumn; + css::uno::Sequence< OUString > m_Columnnames; + }; + + } +} // namespace logging + +// private helpers +namespace +{ + const sal_Unicode quote_char = '"'; + const sal_Unicode comma_char = ','; + constexpr OUString dos_newline = u"\r\n"_ustr; + + bool needsQuoting(std::u16string_view str) + { + return str.find_first_of(u"\",\n\r") != std::u16string_view::npos; + }; + + void appendEncodedString(OUStringBuffer& buf, std::u16string_view str) + { + if(needsQuoting(str)) + { + // each double-quote will get replaced by two double-quotes + buf.append(quote_char); + const sal_Int32 buf_offset = buf.getLength(); + const size_t str_length = str.size(); + buf.append(str); + // special treatment for the last character + if(quote_char==str[str_length-1]) + buf.append(quote_char); + // iterating backwards because the index at which we insert won't be shifted + // when moving that way. + for(size_t i = str_length;; ) + { + --i; + i=str.substr(i).rfind(quote_char); + if(i==std::u16string_view::npos) + break; + buf.insert(buf_offset + i, quote_char); + } + buf.append(quote_char); + } + else + buf.append(str); + }; +} + +namespace logging +{ + CsvFormatter::CsvFormatter() + :m_LogEventNo(true), + m_LogThread(true), + m_LogTimestamp(true), + m_LogSource(false), + m_MultiColumn(false), + m_Columnnames({ "message" }) + { } + + sal_Bool CsvFormatter::getLogEventNo() + { + return m_LogEventNo; + } + + sal_Bool CsvFormatter::getLogThread() + { + return m_LogThread; + } + + sal_Bool CsvFormatter::getLogTimestamp() + { + return m_LogTimestamp; + } + + sal_Bool CsvFormatter::getLogSource() + { + return m_LogSource; + } + + Sequence< OUString > CsvFormatter::getColumnnames() + { + return m_Columnnames; + } + + void CsvFormatter::setLogEventNo(sal_Bool log_event_no) + { + m_LogEventNo = log_event_no; + } + + void CsvFormatter::setLogThread(sal_Bool log_thread) + { + m_LogThread = log_thread; + } + + void CsvFormatter::setLogTimestamp(sal_Bool log_timestamp) + { + m_LogTimestamp = log_timestamp; + } + + void CsvFormatter::setLogSource(sal_Bool log_source) + { + m_LogSource = log_source; + } + + void CsvFormatter::setColumnnames(const Sequence< OUString >& columnnames) + { + m_Columnnames = columnnames; + m_MultiColumn = (m_Columnnames.getLength()>1); + } + + OUString SAL_CALL CsvFormatter::getHead( ) + { + OUStringBuffer buf; + if(m_LogEventNo) + buf.append("event no,"); + if(m_LogThread) + buf.append("thread,"); + if(m_LogTimestamp) + buf.append("timestamp,"); + if(m_LogSource) + buf.append("class,method,"); + sal_Int32 columns = m_Columnnames.getLength(); + for(sal_Int32 i=0; i<columns; i++) + { + buf.append(m_Columnnames[i] + OUStringChar(comma_char)); + } + buf.setLength(buf.getLength()-1); + buf.append(dos_newline); + return buf.makeStringAndClear(); + } + + OUString SAL_CALL CsvFormatter::format( const LogRecord& record ) + { + OUStringBuffer aLogEntry; + + if(m_LogEventNo) + { + aLogEntry.append(record.SequenceNumber + comma_char); + } + + if(m_LogThread) + { + aLogEntry.append(record.ThreadID + OUStringChar(comma_char)); + } + + if(m_LogTimestamp) + { + if ( record.LogTime.Year < -9999 || 9999 < record.LogTime.Year + || record.LogTime.Month < 1 || 12 < record.LogTime.Month + || record.LogTime.Day < 1 || 31 < record.LogTime.Day + || 24 < record.LogTime.Hours + || 60 < record.LogTime.Minutes + || 60 < record.LogTime.Seconds + || 999999999 < record.LogTime.NanoSeconds) + { + throw css::lang::IllegalArgumentException("invalid date", static_cast<cppu::OWeakObject*>(this), 1); + } + + // ISO 8601 + char buffer[ SAL_N_ELEMENTS("-32768-65535-65535T65535:65535:65535.4294967295") ]; + const size_t buffer_size = sizeof( buffer ); + snprintf( buffer, buffer_size, "%04i-%02u-%02uT%02u:%02u:%02u.%09" SAL_PRIuUINT32, + static_cast<int>(record.LogTime.Year), + static_cast<unsigned int>(record.LogTime.Month), + static_cast<unsigned int>(record.LogTime.Day), + static_cast<unsigned int>(record.LogTime.Hours), + static_cast<unsigned int>(record.LogTime.Minutes), + static_cast<unsigned int>(record.LogTime.Seconds), + record.LogTime.NanoSeconds ); + aLogEntry.appendAscii( buffer ); + aLogEntry.append(comma_char); + } + + if(m_LogSource) + { + appendEncodedString(aLogEntry, record.SourceClassName); + aLogEntry.append(comma_char); + + appendEncodedString(aLogEntry, record.SourceMethodName); + aLogEntry.append(comma_char); + } + + // if the CsvFormatter has multiple columns set via setColumnnames(), the + // message of the record is expected to be encoded with formatMultiColumn + // if the CsvFormatter has only one column set, the message is expected not + // to be encoded + if(m_MultiColumn) + aLogEntry.append(record.Message); + else + appendEncodedString(aLogEntry, record.Message); + + aLogEntry.append( dos_newline ); + return aLogEntry.makeStringAndClear(); + } + + OUString SAL_CALL CsvFormatter::getTail( ) + { + return OUString(); + } + + OUString SAL_CALL CsvFormatter::formatMultiColumn(const Sequence< OUString>& column_data) + { + sal_Int32 columns = column_data.getLength(); + OUStringBuffer buf; + for(int i=0; i<columns; i++) + { + appendEncodedString(buf, column_data[i]); + buf.append(comma_char); + } + buf.setLength(buf.getLength()-1); + return buf.makeStringAndClear(); + } + + sal_Bool SAL_CALL CsvFormatter::supportsService( const OUString& service_name ) + { + return cppu::supportsService(this, service_name); + } + + OUString SAL_CALL CsvFormatter::getImplementationName() + { + return "com.sun.star.comp.extensions.CsvFormatter"; + } + + Sequence< OUString > SAL_CALL CsvFormatter::getSupportedServiceNames() + { + return { "com.sun.star.logging.CsvFormatter" }; + } + +} // namespace logging + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_extensions_CsvFormatter( + css::uno::XComponentContext *, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new logging::CsvFormatter()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/logging/filehandler.cxx b/extensions/source/logging/filehandler.cxx new file mode 100644 index 0000000000..8108f7c6ab --- /dev/null +++ b/extensions/source/logging/filehandler.cxx @@ -0,0 +1,359 @@ +/* -*- 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 <sal/log.hxx> + +#include "methodguard.hxx" +#include "loghandler.hxx" + +#include <com/sun/star/logging/XLogHandler.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/util/PathSubstitution.hpp> +#include <com/sun/star/util/XStringSubstitution.hpp> + +#include <comphelper/diagnose_ex.hxx> + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <osl/file.hxx> + +#include <memory> + +namespace logging +{ + using ::com::sun::star::uno::Reference; + using ::com::sun::star::logging::LogRecord; + using ::com::sun::star::logging::XLogFormatter; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::logging::XLogHandler; + using ::com::sun::star::lang::XServiceInfo; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::lang::IllegalArgumentException; + using ::com::sun::star::util::PathSubstitution; + using ::com::sun::star::util::XStringSubstitution; + using ::com::sun::star::beans::NamedValue; + + typedef ::cppu::WeakComponentImplHelper < XLogHandler + , XServiceInfo + > FileHandler_Base; + + namespace { + + class FileHandler :public ::cppu::BaseMutex + ,public FileHandler_Base + { + private: + enum FileValidity + { + /// never attempted to open the file + eUnknown, + /// file is valid + eValid, + /// file is invalid + eInvalid + }; + + Reference<XComponentContext> m_xContext; + LogHandlerHelper m_aHandlerHelper; + OUString m_sFileURL; + std::unique_ptr< ::osl::File > m_pFile; + FileValidity m_eFileValidity; + + public: + FileHandler(const css::uno::Reference<XComponentContext> &context, + const css::uno::Sequence<css::uno::Any> &arguments); + virtual ~FileHandler() override; + + private: + // XLogHandler + virtual OUString SAL_CALL getEncoding() override; + virtual void SAL_CALL setEncoding( const OUString& _encoding ) override; + virtual Reference< XLogFormatter > SAL_CALL getFormatter() override; + virtual void SAL_CALL setFormatter( const Reference< XLogFormatter >& _formatter ) override; + virtual ::sal_Int32 SAL_CALL getLevel() override; + virtual void SAL_CALL setLevel( ::sal_Int32 _level ) override; + virtual void SAL_CALL flush( ) override; + virtual sal_Bool SAL_CALL publish( const LogRecord& Record ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& _rServiceName ) override; + virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // OComponentHelper + virtual void SAL_CALL disposing() override; + + public: + typedef ComponentMethodGuard< FileHandler > MethodGuard; + void enterMethod( MethodGuard::Access ); + void leaveMethod( MethodGuard::Access ); + + private: + /** prepares our output file for writing + */ + bool impl_prepareFile_nothrow(); + + /// writes the given string to our file + void impl_writeString_nothrow( const OString& _rEntry ); + + /** does string substitution on a (usually externally provided) file url + */ + void impl_doStringsubstitution_nothrow( OUString& _inout_rURL ); + }; + + } + + FileHandler::FileHandler(const css::uno::Reference<XComponentContext> &context, + const css::uno::Sequence<css::uno::Any> &arguments) + :FileHandler_Base( m_aMutex ) + ,m_xContext( context ) + ,m_aHandlerHelper( context, m_aMutex, rBHelper ) + ,m_eFileValidity( eUnknown ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( arguments.getLength() != 1 ) + throw IllegalArgumentException( OUString(), *this, 1 ); + + Sequence< NamedValue > aSettings; + if ( arguments[0] >>= m_sFileURL ) + { + // create( [in] string URL ); + impl_doStringsubstitution_nothrow( m_sFileURL ); + } + else if ( arguments[0] >>= aSettings ) + { + // createWithSettings( [in] sequence< css::beans::NamedValue > Settings ) + ::comphelper::NamedValueCollection aTypedSettings( aSettings ); + m_aHandlerHelper.initFromSettings( aTypedSettings ); + + if ( aTypedSettings.get_ensureType( "FileURL", m_sFileURL ) ) + impl_doStringsubstitution_nothrow( m_sFileURL ); + } + else + throw IllegalArgumentException( OUString(), *this, 1 ); + + m_aHandlerHelper.setIsInitialized(); + } + + FileHandler::~FileHandler() + { + if ( !rBHelper.bDisposed ) + { + acquire(); + dispose(); + } + } + + + bool FileHandler::impl_prepareFile_nothrow() + { + if ( m_eFileValidity == eUnknown ) + { + m_pFile.reset( new ::osl::File( m_sFileURL ) ); + // check whether the log file already exists + ::osl::DirectoryItem aFileItem; + if (osl::FileBase::E_None == ::osl::DirectoryItem::get(m_sFileURL, aFileItem)) + { + ::osl::FileStatus aStatus(osl_FileStatus_Mask_Validate); + if (::osl::FileBase::E_None == aFileItem.getFileStatus(aStatus)) + ::osl::File::remove(m_sFileURL); + } + + ::osl::FileBase::RC res = m_pFile->open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ); + m_eFileValidity = res == ::osl::FileBase::E_None + ? eValid + : eInvalid; + #if OSL_DEBUG_LEVEL > 0 + if ( m_eFileValidity == eInvalid ) + { + SAL_WARN( "extensions.logging", "FileHandler::impl_prepareFile_nothrow: could not open the designated log file:" + "\nURL: " << m_sFileURL + << "\nerror code: " << static_cast<sal_Int32>(res) ); + } + #endif + if ( m_eFileValidity == eValid ) + { + OString sHead; + if ( m_aHandlerHelper.getEncodedHead( sHead ) ) + impl_writeString_nothrow( sHead ); + } + } + + return m_eFileValidity == eValid; + } + + + void FileHandler::impl_writeString_nothrow( const OString& _rEntry ) + { + OSL_PRECOND(m_pFile, "FileHandler::impl_writeString_nothrow: no file!"); + + sal_uInt64 nBytesToWrite( _rEntry.getLength() ); + sal_uInt64 nBytesWritten( 0 ); + ::osl::FileBase::RC res = + m_pFile->write( _rEntry.getStr(), nBytesToWrite, nBytesWritten ); + OSL_ENSURE( ( res == ::osl::FileBase::E_None ) && ( nBytesWritten == nBytesToWrite ), + "FileHandler::impl_writeString_nothrow: could not write the log entry!" ); + } + + + void FileHandler::impl_doStringsubstitution_nothrow( OUString& _inout_rURL ) + { + try + { + Reference< XStringSubstitution > xStringSubst(PathSubstitution::create(m_xContext)); + _inout_rURL = xStringSubst->substituteVariables( _inout_rURL, true ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.logging"); + } + } + + + void SAL_CALL FileHandler::disposing() + { + if ( m_eFileValidity == eValid ) + { + OString sTail; + if ( m_aHandlerHelper.getEncodedTail( sTail ) ) + impl_writeString_nothrow( sTail ); + } + + m_pFile.reset(); + m_aHandlerHelper.setFormatter( nullptr ); + } + + + void FileHandler::enterMethod( MethodGuard::Access ) + { + m_aHandlerHelper.enterMethod(); + } + + + void FileHandler::leaveMethod( MethodGuard::Access ) + { + m_aMutex.release(); + } + + + OUString SAL_CALL FileHandler::getEncoding() + { + MethodGuard aGuard( *this ); + OUString sEncoding; + OSL_VERIFY( m_aHandlerHelper.getEncoding( sEncoding ) ); + return sEncoding; + } + + + void SAL_CALL FileHandler::setEncoding( const OUString& _rEncoding ) + { + MethodGuard aGuard( *this ); + OSL_VERIFY( m_aHandlerHelper.setEncoding( _rEncoding ) ); + } + + + Reference< XLogFormatter > SAL_CALL FileHandler::getFormatter() + { + MethodGuard aGuard( *this ); + return m_aHandlerHelper.getFormatter(); + } + + + void SAL_CALL FileHandler::setFormatter( const Reference< XLogFormatter >& _rxFormatter ) + { + MethodGuard aGuard( *this ); + m_aHandlerHelper.setFormatter( _rxFormatter ); + } + + + ::sal_Int32 SAL_CALL FileHandler::getLevel() + { + MethodGuard aGuard( *this ); + return m_aHandlerHelper.getLevel(); + } + + + void SAL_CALL FileHandler::setLevel( ::sal_Int32 _nLevel ) + { + MethodGuard aGuard( *this ); + m_aHandlerHelper.setLevel( _nLevel ); + } + + + void SAL_CALL FileHandler::flush( ) + { + MethodGuard aGuard( *this ); + if (!m_pFile) + { + OSL_PRECOND(false, "FileHandler::flush: no file!"); + return; + } + ::osl::FileBase::RC res = m_pFile->sync(); + OSL_ENSURE(res == ::osl::FileBase::E_None, "FileHandler::flush: Could not sync logfile to filesystem."); + } + + + sal_Bool SAL_CALL FileHandler::publish( const LogRecord& _rRecord ) + { + MethodGuard aGuard( *this ); + + if ( !impl_prepareFile_nothrow() ) + return false; + + OString sEntry; + if ( !m_aHandlerHelper.formatForPublishing( _rRecord, sEntry ) ) + return false; + + impl_writeString_nothrow( sEntry ); + return true; + } + + OUString SAL_CALL FileHandler::getImplementationName() + { + return "com.sun.star.comp.extensions.FileHandler"; + } + + sal_Bool SAL_CALL FileHandler::supportsService( const OUString& _rServiceName ) + { + return cppu::supportsService(this, _rServiceName); + } + + Sequence< OUString > SAL_CALL FileHandler::getSupportedServiceNames() + { + return { "com.sun.star.logging.FileHandler" }; + } + +} // namespace logging + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_extensions_FileHandler( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &arguments) +{ + return cppu::acquire(new logging::FileHandler(context, arguments)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/logging/log.component b/extensions/source/logging/log.component new file mode 100644 index 0000000000..ae221bb576 --- /dev/null +++ b/extensions/source/logging/log.component @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.extensions.ConsoleHandler" + constructor="com_sun_star_comp_extensions_ConsoleHandler"> + <service name="com.sun.star.logging.ConsoleHandler"/> + </implementation> + <implementation name="com.sun.star.comp.extensions.CsvFormatter" + constructor="com_sun_star_comp_extensions_CsvFormatter"> + <service name="com.sun.star.logging.CsvFormatter"/> + </implementation> + <implementation name="com.sun.star.comp.extensions.FileHandler" + constructor="com_sun_star_comp_extensions_FileHandler"> + <service name="com.sun.star.logging.FileHandler"/> + </implementation> + <implementation name="com.sun.star.comp.extensions.LoggerPool" + constructor="com_sun_star_comp_extensions_LoggerPool" + single-instance="true"> + <singleton name="com.sun.star.logging.LoggerPool"/> + </implementation> + <implementation name="com.sun.star.comp.extensions.PlainTextFormatter" + constructor="com_sun_star_comp_extensions_PlainTextFormatter"> + <service name="com.sun.star.logging.PlainTextFormatter"/> + </implementation> + <implementation name="com.sun.star.comp.extensions.SimpleTextFormatter" + constructor="com_sun_star_comp_extensions_SimpleTextFormatter"> + <service name="com.sun.star.logging.SimpleTextFormatter"/> + </implementation> +</component> diff --git a/extensions/source/logging/logger.cxx b/extensions/source/logging/logger.cxx new file mode 100644 index 0000000000..4ae2f79362 --- /dev/null +++ b/extensions/source/logging/logger.cxx @@ -0,0 +1,266 @@ +/* -*- 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 "logrecord.hxx" +#include "loggerconfig.hxx" + +#include <com/sun/star/logging/XLogger.hpp> +#include <com/sun/star/logging/LogLevel.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/logging/XLoggerPool.hpp> + +#include <cppuhelper/basemutex.hxx> +#include <comphelper/interfacecontainer2.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weakref.hxx> +#include <map> +#include <utility> + + +namespace logging +{ + + using ::com::sun::star::logging::XLogger; + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::lang::XServiceInfo; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::WeakReference; + using ::com::sun::star::logging::XLogHandler; + using ::com::sun::star::logging::LogRecord; + + namespace { + + class EventLogger : public cppu::BaseMutex, + public cppu::WeakImplHelper<css::logging::XLogger> + { + private: + comphelper::OInterfaceContainerHelper2 m_aHandlers; + oslInterlockedCount m_nEventNumber; + + // <attributes> + sal_Int32 m_nLogLevel; + OUString m_sName; + // </attributes> + + public: + EventLogger( const Reference< XComponentContext >& _rxContext, OUString _aName ); + + // XLogger + virtual OUString SAL_CALL getName() override; + virtual ::sal_Int32 SAL_CALL getLevel() override; + virtual void SAL_CALL setLevel( ::sal_Int32 _level ) override; + virtual void SAL_CALL addLogHandler( const Reference< XLogHandler >& LogHandler ) override; + virtual void SAL_CALL removeLogHandler( const Reference< XLogHandler >& LogHandler ) override; + virtual sal_Bool SAL_CALL isLoggable( ::sal_Int32 _nLevel ) override; + virtual void SAL_CALL log( ::sal_Int32 Level, const OUString& Message ) override; + virtual void SAL_CALL logp( ::sal_Int32 Level, const OUString& SourceClass, const OUString& SourceMethod, const OUString& Message ) override; + + protected: + virtual ~EventLogger() override; + + private: + /** logs the given log record + */ + void impl_ts_logEvent_nothrow( const LogRecord& _rRecord ); + + /** non-threadsafe impl-version of isLoggable + */ + bool impl_nts_isLoggable_nothrow( ::sal_Int32 _nLevel ); + }; + + /** administrates a pool of XLogger instances, where a logger is keyed by its name, + and subsequent requests for a logger with the same name return the same instance. + */ + class LoggerPool : public cppu::WeakImplHelper<css::logging::XLoggerPool, XServiceInfo> + { + private: + ::osl::Mutex m_aMutex; + Reference<XComponentContext> m_xContext; + std::map< OUString, WeakReference<XLogger> > m_aLoggerMap; + + public: + explicit LoggerPool( const Reference< XComponentContext >& _rxContext ); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& _rServiceName ) override; + virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XLoggerPool + virtual Reference< XLogger > SAL_CALL getNamedLogger( const OUString& Name ) override; + virtual Reference< XLogger > SAL_CALL getDefaultLogger( ) override; + }; + + } + + EventLogger::EventLogger( const Reference< XComponentContext >& _rxContext, OUString _aName ) + :m_aHandlers( m_aMutex ) + ,m_nEventNumber( 0 ) + ,m_nLogLevel( css::logging::LogLevel::OFF ) + ,m_sName(std::move( _aName )) + { + osl_atomic_increment( &m_refCount ); + { + initializeLoggerFromConfiguration( _rxContext, this ); + } + osl_atomic_decrement( &m_refCount ); + } + + EventLogger::~EventLogger() + { + } + + bool EventLogger::impl_nts_isLoggable_nothrow( ::sal_Int32 _nLevel ) + { + if ( _nLevel < m_nLogLevel ) + return false; + + if ( !m_aHandlers.getLength() ) + return false; + + return true; + } + + void EventLogger::impl_ts_logEvent_nothrow( const LogRecord& _rRecord ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( !impl_nts_isLoggable_nothrow( _rRecord.Level ) ) + return; + + m_aHandlers.forEach< XLogHandler >( + [&_rRecord] (Reference<XLogHandler> const& rxListener) { rxListener->publish(_rRecord); } ); + m_aHandlers.forEach< XLogHandler >( + [] (Reference<XLogHandler> const& rxListener) { rxListener->flush(); } ); + } + + OUString SAL_CALL EventLogger::getName() + { + return m_sName; + } + + ::sal_Int32 SAL_CALL EventLogger::getLevel() + { + ::osl::MutexGuard aGuard( m_aMutex ); + return m_nLogLevel; + } + + void SAL_CALL EventLogger::setLevel( ::sal_Int32 _level ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + m_nLogLevel = _level; + } + + void SAL_CALL EventLogger::addLogHandler( const Reference< XLogHandler >& _rxLogHandler ) + { + if ( _rxLogHandler.is() ) + m_aHandlers.addInterface( _rxLogHandler ); + } + + void SAL_CALL EventLogger::removeLogHandler( const Reference< XLogHandler >& _rxLogHandler ) + { + if ( _rxLogHandler.is() ) + m_aHandlers.removeInterface( _rxLogHandler ); + } + + sal_Bool SAL_CALL EventLogger::isLoggable( ::sal_Int32 _nLevel ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + return impl_nts_isLoggable_nothrow( _nLevel ); + } + + void SAL_CALL EventLogger::log( ::sal_Int32 _nLevel, const OUString& _rMessage ) + { + impl_ts_logEvent_nothrow( createLogRecord( + m_sName, + _rMessage, + _nLevel, + osl_atomic_increment( &m_nEventNumber ) + ) ); + } + + void SAL_CALL EventLogger::logp( ::sal_Int32 _nLevel, const OUString& _rSourceClass, const OUString& _rSourceMethod, const OUString& _rMessage ) + { + impl_ts_logEvent_nothrow( createLogRecord( + m_sName, + _rSourceClass, + _rSourceMethod, + _rMessage, + _nLevel, + osl_atomic_increment( &m_nEventNumber ) + ) ); + } + + LoggerPool::LoggerPool( const Reference< XComponentContext >& _rxContext ) + :m_xContext( _rxContext ) + { + } + + OUString SAL_CALL LoggerPool::getImplementationName() + { + return "com.sun.star.comp.extensions.LoggerPool"; + } + + sal_Bool SAL_CALL LoggerPool::supportsService( const OUString& _rServiceName ) + { + return cppu::supportsService(this, _rServiceName); + } + + Sequence< OUString > SAL_CALL LoggerPool::getSupportedServiceNames() + { + return { "com.sun.star.logging.LoggerPool" }; + } + + Reference< XLogger > SAL_CALL LoggerPool::getNamedLogger( const OUString& _rName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + WeakReference< XLogger >& rLogger( m_aLoggerMap[ _rName ] ); + Reference< XLogger > xLogger( rLogger ); + if ( !xLogger.is() ) + { + // never requested before, or already dead + xLogger = new EventLogger( m_xContext, _rName ); + rLogger = xLogger; + } + + return xLogger; + } + + Reference< XLogger > SAL_CALL LoggerPool::getDefaultLogger( ) + { + return getNamedLogger( "org.openoffice.logging.DefaultLogger" ); + } + +} // namespace logging + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_extensions_LoggerPool( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new logging::LoggerPool(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/logging/loggerconfig.cxx b/extensions/source/logging/loggerconfig.cxx new file mode 100644 index 0000000000..90801b484f --- /dev/null +++ b/extensions/source/logging/loggerconfig.cxx @@ -0,0 +1,283 @@ +/* -*- 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 "loggerconfig.hxx" +#include <stdio.h> +#include <string_view> + +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> +#include <com/sun/star/logging/LogLevel.hpp> +#include <com/sun/star/lang/NullPointerException.hpp> +#include <com/sun/star/lang/ServiceNotRegisteredException.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/logging/XLogHandler.hpp> +#include <com/sun/star/logging/XLogFormatter.hpp> + +#include <comphelper/diagnose_ex.hxx> +#include <osl/process.h> + +#include <cppuhelper/component_context.hxx> + + +namespace logging +{ + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::logging::XLogger; + using ::com::sun::star::lang::XMultiServiceFactory; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::uno::Any; + using ::com::sun::star::container::XNameContainer; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::lang::XSingleServiceFactory; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::util::XChangesBatch; + using ::com::sun::star::lang::NullPointerException; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::lang::ServiceNotRegisteredException; + using ::com::sun::star::beans::NamedValue; + using ::com::sun::star::logging::XLogHandler; + using ::com::sun::star::logging::XLogFormatter; + using ::com::sun::star::container::XNameAccess; + using ::com::sun::star::uno::XComponentContext; + + namespace LogLevel = ::com::sun::star::logging::LogLevel; + + namespace + { + + typedef void (*SettingTranslation)( const Reference< XLogger >&, const OUString&, Any& ); + + + void lcl_substituteFileHandlerURLVariables_nothrow( const Reference< XLogger >& _rxLogger, OUString& _inout_rFileURL ) + { + struct Variable + { + std::u16string_view pVariablePattern; + OUString sVariableValue; + }; + + OUString sLoggerName; + try { sLoggerName = _rxLogger->getName(); } + catch( const Exception& ) { DBG_UNHANDLED_EXCEPTION("extensions.logging"); } + + TimeValue aTimeValue; + oslDateTime aDateTime; + OSL_VERIFY( osl_getSystemTime( &aTimeValue ) ); + OSL_VERIFY( osl_getDateTimeFromTimeValue( &aTimeValue, &aDateTime ) ); + + char buffer[ 30 ]; + const size_t buffer_size = sizeof( buffer ); + + snprintf( buffer, buffer_size, "%04i-%02i-%02i", + static_cast<int>(aDateTime.Year), + static_cast<int>(aDateTime.Month), + static_cast<int>(aDateTime.Day) ); + OUString sDate = OUString::createFromAscii( buffer ); + + snprintf( buffer, buffer_size, "%02i-%02i-%02i.%03i", + static_cast<int>(aDateTime.Hours), + static_cast<int>(aDateTime.Minutes), + static_cast<int>(aDateTime.Seconds), + ::sal::static_int_cast< sal_Int16 >( aDateTime.NanoSeconds / 10000000 ) ); + OUString sTime = OUString::createFromAscii( buffer ); + + OUString sDateTime = sDate + "." + sTime; + + oslProcessIdentifier aProcessId = 0; + oslProcessInfo info; + info.Size = sizeof (oslProcessInfo); + if ( osl_getProcessInfo ( nullptr, osl_Process_IDENTIFIER, &info ) == osl_Process_E_None) + aProcessId = info.Ident; + OUString aPID = OUString::number( aProcessId ); + + Variable const aVariables[] = + { + {std::u16string_view(u"$(loggername)"), sLoggerName}, + {std::u16string_view(u"$(date)"), sDate}, + {std::u16string_view(u"$(time)"), sTime}, + {std::u16string_view(u"$(datetime)"), sDateTime}, + {std::u16string_view(u"$(pid)"), aPID} + }; + + for (Variable const & aVariable : aVariables) + { + sal_Int32 nVariableIndex = _inout_rFileURL.indexOf( aVariable.pVariablePattern ); + if (nVariableIndex >= 0) + { + _inout_rFileURL = _inout_rFileURL.replaceAt( nVariableIndex, aVariable.pVariablePattern.size(), aVariable.sVariableValue ); + } + } + } + + + void lcl_transformFileHandlerSettings_nothrow( const Reference< XLogger >& _rxLogger, const OUString& _rSettingName, Any& _inout_rSettingValue ) + { + if ( _rSettingName != "FileURL" ) + // not interested in this setting + return; + + OUString sURL; + OSL_VERIFY( _inout_rSettingValue >>= sURL ); + lcl_substituteFileHandlerURLVariables_nothrow( _rxLogger, sURL ); + _inout_rSettingValue <<= sURL; + } + + + Reference< XInterface > lcl_createInstanceFromSetting_throw( + const Reference<XComponentContext>& _rContext, + const Reference< XLogger >& _rxLogger, + const Reference< XNameAccess >& _rxLoggerSettings, + const char* _pServiceNameAsciiNodeName, + const char* _pServiceSettingsAsciiNodeName, + SettingTranslation _pSettingTranslation = nullptr + ) + { + Reference< XInterface > xInstance; + + // read the settings for the to-be-created service + Reference< XNameAccess > xServiceSettingsNode( _rxLoggerSettings->getByName( + OUString::createFromAscii( _pServiceSettingsAsciiNodeName ) ), UNO_QUERY_THROW ); + + Sequence< OUString > aSettingNames( xServiceSettingsNode->getElementNames() ); + size_t nServiceSettingCount( aSettingNames.getLength() ); + Sequence< NamedValue > aSettings( nServiceSettingCount ); + if ( nServiceSettingCount ) + { + const OUString* pSettingNames = aSettingNames.getConstArray(); + const OUString* pSettingNamesEnd = aSettingNames.getConstArray() + aSettingNames.getLength(); + NamedValue* pSetting = aSettings.getArray(); + + for ( ; + pSettingNames != pSettingNamesEnd; + ++pSettingNames, ++pSetting + ) + { + pSetting->Name = *pSettingNames; + pSetting->Value = xServiceSettingsNode->getByName( *pSettingNames ); + + if ( _pSettingTranslation ) + _pSettingTranslation( _rxLogger, pSetting->Name, pSetting->Value ); + } + } + + OUString sServiceName; + _rxLoggerSettings->getByName( OUString::createFromAscii( _pServiceNameAsciiNodeName ) ) >>= sServiceName; + if ( !sServiceName.isEmpty() ) + { + bool bSuccess = false; + if ( aSettings.hasElements() ) + { + Sequence< Any > aConstructionArgs{ Any(aSettings) }; + xInstance = _rContext->getServiceManager()->createInstanceWithArgumentsAndContext(sServiceName, aConstructionArgs, _rContext); + bSuccess = xInstance.is(); + } + else + { + xInstance = _rContext->getServiceManager()->createInstanceWithContext(sServiceName, _rContext); + bSuccess = xInstance.is(); + } + + if ( !bSuccess ) + throw ServiceNotRegisteredException( sServiceName ); + } + + return xInstance; + } + } + + + void initializeLoggerFromConfiguration( const Reference<XComponentContext>& _rContext, const Reference< XLogger >& _rxLogger ) + { + try + { + if ( !_rxLogger.is() ) + throw NullPointerException(); + + Reference< XMultiServiceFactory > xConfigProvider( + css::configuration::theDefaultProvider::get(_rContext)); + + // write access to the "Settings" node (which includes settings for all loggers) + Sequence<Any> aArguments{ Any(NamedValue( + "nodepath", Any(OUString("/org.openoffice.Office.Logging/Settings")))) }; + Reference< XNameContainer > xAllSettings( xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationUpdateAccess", + aArguments + ), UNO_QUERY_THROW ); + + OUString sLoggerName( _rxLogger->getName() ); + if ( !xAllSettings->hasByName( sLoggerName ) ) + { + // no node yet for this logger. Create default settings. + Reference< XSingleServiceFactory > xNodeFactory( xAllSettings, UNO_QUERY_THROW ); + Reference< XInterface > xLoggerSettings( xNodeFactory->createInstance(), css::uno::UNO_SET_THROW ); + xAllSettings->insertByName( sLoggerName, Any( xLoggerSettings ) ); + Reference< XChangesBatch > xChanges( xAllSettings, UNO_QUERY_THROW ); + xChanges->commitChanges(); + } + + // actually read and forward the settings + Reference< XNameAccess > xLoggerSettings( xAllSettings->getByName( sLoggerName ), UNO_QUERY_THROW ); + + // the log level + sal_Int32 nLogLevel( LogLevel::OFF ); + OSL_VERIFY( xLoggerSettings->getByName("LogLevel") >>= nLogLevel ); + _rxLogger->setLevel( nLogLevel ); + + // the default handler, if any + Reference< XInterface > xUntyped( lcl_createInstanceFromSetting_throw( _rContext, _rxLogger, xLoggerSettings, "DefaultHandler", "HandlerSettings", &lcl_transformFileHandlerSettings_nothrow ) ); + if ( !xUntyped.is() ) + // no handler -> we're done + return; + Reference< XLogHandler > xHandler( xUntyped, UNO_QUERY_THROW ); + _rxLogger->addLogHandler( xHandler ); + + // The newly created handler might have an own (default) level. Ensure that it uses + // the same level as the logger. + xHandler->setLevel( nLogLevel ); + + // the default formatter for the handler + xUntyped = lcl_createInstanceFromSetting_throw( _rContext, _rxLogger, xLoggerSettings, "DefaultFormatter", "FormatterSettings" ); + if ( !xUntyped.is() ) + // no formatter -> we're done + return; + Reference< XLogFormatter > xFormatter( xUntyped, UNO_QUERY_THROW ); + xHandler->setFormatter( xFormatter ); + + // TODO: we could first create the formatter, then the handler. This would allow + // passing the formatter as value in the component context, so the handler would + // not create an own default formatter + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.logging"); + } + } + + +} // namespace logging + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/logging/loggerconfig.hxx b/extensions/source/logging/loggerconfig.hxx new file mode 100644 index 0000000000..b08628cf16 --- /dev/null +++ b/extensions/source/logging/loggerconfig.hxx @@ -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 . + */ + +#pragma once + +#include <com/sun/star/logging/XLogger.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + + +namespace logging +{ + + + /** initializes the given logger from the configuration + + The configuration node /org.openoffice.Office.Logging/Settings/<logger_name> + is examined for this. If it does not yet exist, it will be created. + + The function creates a default handler and a default formatter, as specified in the + configuration. + + This function is currently external to the logger instance. Perhaps it can, on the long + run, be moved to the logger implementation - not sure if it's the best place. + */ + void initializeLoggerFromConfiguration( + const css::uno::Reference<css::uno::XComponentContext>& _rContext, + const css::uno::Reference< css::logging::XLogger >& _rxLogger + ); + + +} // namespace logging + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/logging/loghandler.cxx b/extensions/source/logging/loghandler.cxx new file mode 100644 index 0000000000..a398bd053a --- /dev/null +++ b/extensions/source/logging/loghandler.cxx @@ -0,0 +1,182 @@ +/* -*- 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 "loghandler.hxx" + +#include <com/sun/star/logging/LogLevel.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/logging/PlainTextFormatter.hpp> + +#include <comphelper/diagnose_ex.hxx> +#include <rtl/tencinfo.h> + + +namespace logging +{ + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::logging::LogRecord; + using ::com::sun::star::logging::XLogFormatter; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::lang::IllegalArgumentException; + using ::com::sun::star::lang::DisposedException; + using ::com::sun::star::logging::PlainTextFormatter; + + namespace LogLevel = ::com::sun::star::logging::LogLevel; + + LogHandlerHelper::LogHandlerHelper( const Reference< XComponentContext >& _rxContext, ::osl::Mutex& _rMutex, ::cppu::OBroadcastHelper& _rBHelper ) + :m_eEncoding( RTL_TEXTENCODING_UTF8 ) + ,m_nLevel( LogLevel::SEVERE ) + ,m_xContext( _rxContext ) + ,m_rMutex( _rMutex ) + ,m_rBHelper( _rBHelper ) + ,m_bInitialized( false ) + { + } + + + void LogHandlerHelper::initFromSettings( const ::comphelper::NamedValueCollection& _rSettings ) + { + OUString sEncoding; + if ( _rSettings.get_ensureType( "Encoding", sEncoding ) ) + { + if ( !setEncoding( sEncoding ) ) + throw IllegalArgumentException(); + } + + _rSettings.get_ensureType( "Formatter", m_xFormatter ); + _rSettings.get_ensureType( "Level", m_nLevel ); + } + + + void LogHandlerHelper::enterMethod() + { + m_rMutex.acquire(); + + if ( !m_bInitialized ) + throw DisposedException("component not initialized" ); + + if ( m_rBHelper.bDisposed ) + throw DisposedException("component already disposed" ); + + // fallback settings, in case they weren't passed at construction time + if ( !getFormatter().is() ) + { + try + { + Reference< XLogFormatter > xFormatter( PlainTextFormatter::create( m_xContext ), css::uno::UNO_SET_THROW ); + setFormatter( xFormatter ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.logging"); + } + } + } + + + bool LogHandlerHelper::getEncoding( OUString& _out_rEncoding ) const + { + const char* pMimeCharset = rtl_getMimeCharsetFromTextEncoding( m_eEncoding ); + if ( pMimeCharset ) + { + _out_rEncoding = OUString::createFromAscii( pMimeCharset ); + return true; + } + _out_rEncoding.clear(); + return false; + } + + + bool LogHandlerHelper::setEncoding( std::u16string_view _rEncoding ) + { + OString sAsciiEncoding( OUStringToOString( _rEncoding, RTL_TEXTENCODING_ASCII_US ) ); + rtl_TextEncoding eEncoding = rtl_getTextEncodingFromMimeCharset( sAsciiEncoding.getStr() ); + if ( eEncoding != RTL_TEXTENCODING_DONTKNOW ) + { + m_eEncoding = eEncoding; + return true; + } + return false; + } + + + bool LogHandlerHelper::formatForPublishing( const LogRecord& _rRecord, OString& _out_rEntry ) const + { + if ( _rRecord.Level < getLevel() ) + // not to be published due to low level + return false; + + try + { + Reference< XLogFormatter > xFormatter( getFormatter(), css::uno::UNO_SET_THROW ); + OUString sEntry( xFormatter->format( _rRecord ) ); + _out_rEntry = OUStringToOString( sEntry, getTextEncoding() ); + return true; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.logging"); + } + return false; + } + + + bool LogHandlerHelper::getEncodedHead( OString& _out_rHead ) const + { + try + { + Reference< XLogFormatter > xFormatter( getFormatter(), css::uno::UNO_SET_THROW ); + OUString sHead( xFormatter->getHead() ); + _out_rHead = OUStringToOString( sHead, getTextEncoding() ); + return true; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.logging"); + } + return false; + } + + + bool LogHandlerHelper::getEncodedTail( OString& _out_rTail ) const + { + try + { + Reference< XLogFormatter > xFormatter( getFormatter(), css::uno::UNO_SET_THROW ); + OUString sTail( xFormatter->getTail() ); + _out_rTail = OUStringToOString( sTail, getTextEncoding() ); + return true; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.logging"); + } + return false; + } + + +} // namespace logging + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/logging/loghandler.hxx b/extensions/source/logging/loghandler.hxx new file mode 100644 index 0000000000..02f4fb7737 --- /dev/null +++ b/extensions/source/logging/loghandler.hxx @@ -0,0 +1,142 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <com/sun/star/logging/XLogFormatter.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/logging/LogRecord.hpp> + +#include <comphelper/namedvaluecollection.hxx> +#include <cppuhelper/interfacecontainer.hxx> +#include <rtl/string.hxx> + + +namespace logging +{ + + class LogHandlerHelper + { + private: + // <attributes> + rtl_TextEncoding m_eEncoding; + sal_Int32 m_nLevel; + css::uno::Reference< css::logging::XLogFormatter > + m_xFormatter; + // <//attributes> + + css::uno::Reference< css::uno::XComponentContext > + m_xContext; + ::osl::Mutex& m_rMutex; + ::cppu::OBroadcastHelper& m_rBHelper; + bool m_bInitialized; + + public: + LogHandlerHelper( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext, + ::osl::Mutex& _rMutex, + ::cppu::OBroadcastHelper& _rBHelper + ); + + public: + void setIsInitialized() { m_bInitialized = true; } + + bool getEncoding( OUString& _out_rEncoding ) const; + bool setEncoding( std::u16string_view _rEncoding ); + + rtl_TextEncoding + getTextEncoding() const { return m_eEncoding; } + + const css::uno::Reference< css::logging::XLogFormatter >& + getFormatter() const { return m_xFormatter; } + void + setFormatter( const css::uno::Reference< css::logging::XLogFormatter >& _rxFormatter ) + { + m_xFormatter = _rxFormatter; + } + + sal_Int32 + getLevel() const { return m_nLevel; } + void + setLevel( const sal_Int32 _nLevel ) + { + m_nLevel = _nLevel; + } + + /** prepares implementation of a public accessible method of a log handler + + <code>enterMethod</code> does the following things: + <ul><li>It acquires the mutex given in the constructor.</li> + <li>It checks whether the component is already initialized, and throws an exception if not os.</li> + <li>It checks whether the component is already disposed, and throws an exception if not os.</li> + <li>It creates a default formatter (PlainTextFormatter), if no formatter exists at this time.</li> + </ul> + */ + void enterMethod(); + + /** formats a record for publishing it + + The method first checks whether the records log level is greater or equal our own + log level. If not, <FALSE/> is returned. + + Second, our formatter is used to create a unicode string from the log record. If an error occurs + during this, e.g. if the formatter is <NULL/> or throws an exception during formatting, + <FALSE/> is returned. + + Finally, the unicode string is encoded into a byte string, using our encoding setting. Then, + <TRUE/> is returned. + */ + bool formatForPublishing( const css::logging::LogRecord& _rRecord, OString& _out_rEntry ) const; + + /** retrieves our formatter's heading, encoded with our encoding + + @return <TRUE/> in case of success, <FALSE/> if any error occurred + */ + bool getEncodedHead( OString& _out_rHead ) const; + + /** retrieves our formatter's tail, encoded with our encoding + + @return <TRUE/> in case of success, <FALSE/> if any error occurred + */ + bool getEncodedTail( OString& _out_rTail ) const; + + /** initializes the instance from a collection of named settings + + The recognized named settings are <code>Encoding</code>, <code>Formatter</code>, and <code>Level</code>, + which initialize the respective attributes. + + Settings which are recognized are remove from the given collection. This allows + the caller to determine whether or not the collection contained any unsupported + items, and react appropriately. + + @throws IllegalArgumentException + if one of the values in the collection is of wrong type. + */ + void initFromSettings( const ::comphelper::NamedValueCollection& _rSettings ); + }; + + +} // namespace logging + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/logging/logrecord.cxx b/extensions/source/logging/logrecord.cxx new file mode 100644 index 0000000000..26f59e841a --- /dev/null +++ b/extensions/source/logging/logrecord.cxx @@ -0,0 +1,86 @@ +/* -*- 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 "logrecord.hxx" + +#include <osl/time.h> +#include <osl/thread.h> +#include <osl/thread.hxx> +#include <osl/diagnose.h> + + +namespace logging +{ + + + using ::com::sun::star::logging::LogRecord; + using ::com::sun::star::util::DateTime; + + namespace + { + /** returns a string representation of the current thread + + @todo + We need a way to retrieve the current UNO thread ID as string, + which is issue #i77342# + */ + OUString getCurrentThreadID() + { + oslThreadIdentifier nThreadID( osl::Thread::getCurrentIdentifier() ); + return OUString::number( static_cast<sal_Int64>(nThreadID) ); + } + } + + + LogRecord createLogRecord( const OUString& _rLoggerName, const OUString& _rClassName, + const OUString& _rMethodName, const OUString& _rMessage, + sal_Int32 _nLogLevel, oslInterlockedCount _nEventNumber ) + { + TimeValue aTimeValue; + osl_getSystemTime( &aTimeValue ); + + oslDateTime aDateTime; + OSL_VERIFY( osl_getDateTimeFromTimeValue( &aTimeValue, &aDateTime ) ); + + DateTime aTimeStamp; + aTimeStamp.Year = aDateTime.Year; + aTimeStamp.Month = aDateTime.Month; + aTimeStamp.Day = aDateTime.Day; + aTimeStamp.Hours = aDateTime.Hours; + aTimeStamp.Minutes = aDateTime.Minutes; + aTimeStamp.Seconds = aDateTime.Seconds; + aTimeStamp.NanoSeconds = aDateTime.NanoSeconds; + + return LogRecord( + _rLoggerName, + _rClassName, + _rMethodName, + _rMessage, + aTimeStamp, + _nEventNumber, + getCurrentThreadID(), + _nLogLevel + ); + } + + +} // namespace logging + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/logging/logrecord.hxx b/extensions/source/logging/logrecord.hxx new file mode 100644 index 0000000000..ad6e350cfb --- /dev/null +++ b/extensions/source/logging/logrecord.hxx @@ -0,0 +1,53 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/logging/LogRecord.hpp> + +#include <osl/interlck.h> + + +namespace logging +{ + + css::logging::LogRecord createLogRecord( + const OUString& _rLoggerName, + const OUString& _rClassName, + const OUString& _rMethodName, + const OUString& _rMessage, + sal_Int32 _nLogLevel, + oslInterlockedCount _nEventNumber + ); + + inline css::logging::LogRecord createLogRecord( + const OUString& _rLoggerName, + const OUString& _rMessage, + sal_Int32 _nLogLevel, + oslInterlockedCount _nEventNumber + ) + { + return createLogRecord( _rLoggerName, OUString(), OUString(), _rMessage, _nLogLevel, _nEventNumber ); + } + + +} // namespace logging + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/logging/methodguard.hxx b/extensions/source/logging/methodguard.hxx new file mode 100644 index 0000000000..189462eae6 --- /dev/null +++ b/extensions/source/logging/methodguard.hxx @@ -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 . + */ + +#pragma once + + +namespace logging +{ + template < class COMPONENT > + class ComponentMethodGuard + { + private: + COMPONENT& m_rHandler; + + public: + class Access + { + private: + friend class ComponentMethodGuard; + Access() { } + }; + + public: + explicit ComponentMethodGuard( COMPONENT& _rHandler ) + :m_rHandler( _rHandler ) + { + m_rHandler.enterMethod( Access() ); + } + ~ComponentMethodGuard() + { + m_rHandler.leaveMethod( Access() ); + } + }; + + +} // namespace logging + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/logging/plaintextformatter.cxx b/extensions/source/logging/plaintextformatter.cxx new file mode 100644 index 0000000000..2c534a2a2e --- /dev/null +++ b/extensions/source/logging/plaintextformatter.cxx @@ -0,0 +1,151 @@ +/* -*- 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 <com/sun/star/logging/XLogFormatter.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <rtl/ustrbuf.hxx> +#include <osl/thread.h> + +#include <stdio.h> + +namespace logging +{ + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::logging::LogRecord; + using ::com::sun::star::uno::XInterface; + + namespace { + + class PlainTextFormatter : public cppu::WeakImplHelper<css::logging::XLogFormatter, css::lang::XServiceInfo> + { + public: + PlainTextFormatter(); + + private: + // XLogFormatter + virtual OUString SAL_CALL getHead( ) override; + virtual OUString SAL_CALL format( const LogRecord& Record ) override; + virtual OUString SAL_CALL getTail( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& _rServiceName ) override; + virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + }; + + } + + PlainTextFormatter::PlainTextFormatter() + { + } + + OUString SAL_CALL PlainTextFormatter::getHead( ) + { + return + " event no" // column 1: the event number + " " + "thread " // column 2: the thread ID + " " + "date " // column 3: date + " " + "time " // column 4: time + " " + "(class/method:) message" // column 5: class/method/message + "\n"; + } + + + OUString SAL_CALL PlainTextFormatter::format( const LogRecord& _rRecord ) + { + char buffer[ sizeof("-32768-65535-65535 65535:65535:65535.4294967295") ]; + // reserve enough space for hypothetical max length + const int buffer_size = sizeof( buffer ); + int used = snprintf( buffer, buffer_size, "%10i", static_cast<int>(_rRecord.SequenceNumber) ); + if ( used >= buffer_size || used < 0 ) + buffer[ buffer_size - 1 ] = 0; + + OUStringBuffer aLogEntry; + aLogEntry.appendAscii( buffer ); + aLogEntry.append( " " ); + + OString sThreadID( OUStringToOString( _rRecord.ThreadID, osl_getThreadTextEncoding() ) ); + snprintf( buffer, buffer_size, "%8s", sThreadID.getStr() ); + aLogEntry.appendAscii( buffer ); + aLogEntry.append( " " ); + + snprintf( buffer, buffer_size, "%04" SAL_PRIdINT32 "-%02" SAL_PRIuUINT32 "-%02" SAL_PRIuUINT32 " %02" SAL_PRIuUINT32 ":%02" SAL_PRIuUINT32 ":%02" SAL_PRIuUINT32 ".%09" SAL_PRIuUINT32, + static_cast<sal_Int32>(_rRecord.LogTime.Year), static_cast<sal_uInt32>(_rRecord.LogTime.Month), static_cast<sal_uInt32>(_rRecord.LogTime.Day), + static_cast<sal_uInt32>(_rRecord.LogTime.Hours), static_cast<sal_uInt32>(_rRecord.LogTime.Minutes), static_cast<sal_uInt32>(_rRecord.LogTime.Seconds), _rRecord.LogTime.NanoSeconds ); + aLogEntry.appendAscii( buffer ); + aLogEntry.append( " " ); + + if ( !(_rRecord.SourceClassName.isEmpty() || _rRecord.SourceMethodName.isEmpty()) ) + { + aLogEntry.append( _rRecord.SourceClassName + "::" + + _rRecord.SourceMethodName + ": " ); + } + + aLogEntry.append( _rRecord.Message + "\n" ); + + return aLogEntry.makeStringAndClear(); + } + + + OUString SAL_CALL PlainTextFormatter::getTail( ) + { + // no tail + return OUString(); + } + + sal_Bool SAL_CALL PlainTextFormatter::supportsService( const OUString& _rServiceName ) + { + return cppu::supportsService(this, _rServiceName); + } + + + OUString SAL_CALL PlainTextFormatter::getImplementationName() + { + return "com.sun.star.comp.extensions.PlainTextFormatter"; + } + + Sequence< OUString > SAL_CALL PlainTextFormatter::getSupportedServiceNames() + { + return { "com.sun.star.logging.PlainTextFormatter" }; + } + +} // namespace logging + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_extensions_PlainTextFormatter( + css::uno::XComponentContext *, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new logging::PlainTextFormatter()); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/logging/simpletextformatter.cxx b/extensions/source/logging/simpletextformatter.cxx new file mode 100644 index 0000000000..b54fea5a64 --- /dev/null +++ b/extensions/source/logging/simpletextformatter.cxx @@ -0,0 +1,98 @@ +/* -*- 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 <com/sun/star/logging/XLogFormatter.hpp> +#include <com/sun/star/logging/LogLevel.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> + +namespace logging +{ +using css::logging::LogRecord; +using namespace css::uno; + +namespace +{ +class SimpleTextFormatter + : public cppu::WeakImplHelper<css::logging::XLogFormatter, css::lang::XServiceInfo> +{ +public: + SimpleTextFormatter(); + +private: + // XLogFormatter + virtual OUString SAL_CALL getHead() override; + virtual OUString SAL_CALL format(const LogRecord& Record) override; + virtual OUString SAL_CALL getTail() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString& _rServiceName) override; + virtual Sequence<OUString> SAL_CALL getSupportedServiceNames() override; +}; +} + +SimpleTextFormatter::SimpleTextFormatter() {} + +OUString SAL_CALL SimpleTextFormatter::getHead() { return OUString(); } + +OUString SAL_CALL SimpleTextFormatter::format(const LogRecord& _rRecord) +{ + OUString aLogEntry; + // Highlight warnings + if (_rRecord.Level == css::logging::LogLevel::SEVERE) + aLogEntry = "ERROR: "; + else if (_rRecord.Level == css::logging::LogLevel::WARNING) + aLogEntry = "WARNING: "; + + return aLogEntry + _rRecord.Message + "\n"; +} + +OUString SAL_CALL SimpleTextFormatter::getTail() { return OUString(); } + +sal_Bool SAL_CALL SimpleTextFormatter::supportsService(const OUString& _rServiceName) +{ + return cppu::supportsService(this, _rServiceName); +} + +OUString SAL_CALL SimpleTextFormatter::getImplementationName() +{ + return "com.sun.star.comp.extensions.SimpleTextFormatter"; +} + +Sequence<OUString> SAL_CALL SimpleTextFormatter::getSupportedServiceNames() +{ + return { "com.sun.star.logging.SimpleTextFormatter" }; +} + +} // namespace logging + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_extensions_SimpleTextFormatter(css::uno::XComponentContext*, + css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new logging::SimpleTextFormatter()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/macosx/spotlight/GetMetadataForFile.h b/extensions/source/macosx/spotlight/GetMetadataForFile.h new file mode 100644 index 0000000000..51086827ed --- /dev/null +++ b/extensions/source/macosx/spotlight/GetMetadataForFile.h @@ -0,0 +1,26 @@ +/* -*- 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 . + */ + +#pragma once + +Boolean GetMetadataForFile( + void * thisInterface, CFMutableDictionaryRef attributes, + CFStringRef contentTypeUTI, CFStringRef pathToFile); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/macosx/spotlight/GetMetadataForFile.m b/extensions/source/macosx/spotlight/GetMetadataForFile.m new file mode 100644 index 0000000000..a1dcdbe619 --- /dev/null +++ b/extensions/source/macosx/spotlight/GetMetadataForFile.m @@ -0,0 +1,64 @@ +/* -*- 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 <CoreFoundation/CoreFoundation.h> +#include <CoreServices/CoreServices.h> +#include <Foundation/Foundation.h> + +#include "GetMetadataForFile.h" +#import "OOoSpotlightImporter.h" + +/* ----------------------------------------------------------------------------- + Get metadata attributes from file + + This function's job is to extract useful information your file format supports + and return it as a dictionary + ----------------------------------------------------------------------------- */ + +Boolean GetMetadataForFile(void* thisInterface, + CFMutableDictionaryRef attributes, + CFStringRef contentTypeUTI, + CFStringRef pathToFile) +{ + (void) thisInterface; /* unused */ + /* Pull any available metadata from the file at the specified path */ + /* Return the attribute keys and attribute values in the dict */ + /* Return TRUE if successful, FALSE if there was no data provided */ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + OOoSpotlightImporter *importer = [OOoSpotlightImporter new]; + + Boolean importOK = NO; + @try { + importOK = [importer importDocument:(NSString*)pathToFile + contentType:(NSString*)contentTypeUTI + attributes:(NSMutableDictionary*)attributes]; + } + @catch (NSException *exception) { + NSLog(@"main: Caught %@: %@", [exception name], [exception reason]); + } + + [importer release]; + + [pool release]; + + return importOK; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/macosx/spotlight/OOoContentDataParser.h b/extensions/source/macosx/spotlight/OOoContentDataParser.h new file mode 100644 index 0000000000..dc07a31661 --- /dev/null +++ b/extensions/source/macosx/spotlight/OOoContentDataParser.h @@ -0,0 +1,51 @@ +/* -*- 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 . + */ + +#import <Cocoa/Cocoa.h> + +@interface OOoContentDataParser : NSObject <NSXMLParserDelegate> { + // indicates if we are interested in an element's content + BOOL shouldReadCharacters; + + // the MD importer's values + NSMutableDictionary *mdiValues; + + // all of the text inside a document + NSMutableString *textContent; + + // the current element's content + NSMutableString *runningTextContent; +} + +- (void)parseXML:(NSData*)data intoDictionary:(NSMutableDictionary*)dict; + +// delegates +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict; + +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName; + +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string; + +- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError; + +- (void)parserDidEndDocument:(NSXMLParser *)parser; + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/macosx/spotlight/OOoContentDataParser.m b/extensions/source/macosx/spotlight/OOoContentDataParser.m new file mode 100644 index 0000000000..e1f51e5b90 --- /dev/null +++ b/extensions/source/macosx/spotlight/OOoContentDataParser.m @@ -0,0 +1,144 @@ +/* -*- 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 <objc/objc-runtime.h> + +#import "OOoContentDataParser.h" + +@implementation OOoContentDataParser + +- (id)init +{ + if ((self = [super init]) != nil) { + shouldReadCharacters = NO; + textContent = nil; + runningTextContent = nil; + + return self; + } + + return nil; +} + +- (void)parseXML:(NSData*)data intoDictionary:(NSMutableDictionary*)dict +{ + mdiValues = dict; + + //NSLog(@"data: %@ %d", data, [data length]); + + //init parser settings + shouldReadCharacters = NO; + + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; + + [parser setDelegate:self]; + + [parser setShouldResolveExternalEntities:NO]; + [parser parse]; + + [parser release]; + + //NSLog(@"finished"); +} + +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict +{ + (void) parser; // unused + (void) namespaceURI; // FIXME this should not be ignored but should be used + // instead of text: prefix in the comparison below! + (void) qualifiedName; // unused + (void) attributeDict; // unused + // all text content is stored inside <text:p> elements + if ([elementName isEqualToString:@"text:p"] == YES) { + runningTextContent = [NSMutableString new]; + shouldReadCharacters = YES; + //NSLog(@"start"); + } else { + return; + } + + //NSLog(@"start element %@", elementName); +} + +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName +{ + (void) parser; // unused + (void) elementName; // unused + (void) namespaceURI; // unused + (void) qName; // unused + if (shouldReadCharacters == TRUE) { + if (textContent == nil) { + textContent = [NSMutableString new]; + } else if ([runningTextContent isEqualToString:@""] == NO) { + // separate by whitespace + [textContent appendString:@" "]; + } + //NSLog(@"end"); + + [textContent appendString:[NSString stringWithString:runningTextContent]]; + [runningTextContent release]; + runningTextContent = nil; + } + shouldReadCharacters = NO; +} + +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string +{ + (void) parser; // unused + if (shouldReadCharacters == NO) { + return; + } + //NSLog(string); + + [runningTextContent appendString:string]; + + //NSLog(@"read: %@", string); + +} + +- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError +{ + //NSLog(@"parsing finished with error"); + NSLog(@"An error occurred parsing the document. (Error %li, Description: %@, Line: %li, Column: %li)", (long) [parseError code], + [[parser parserError] localizedDescription], (long) [parser lineNumber], + (long) [parser columnNumber]); + + if (runningTextContent != nil) { + [runningTextContent release]; + runningTextContent = nil; + } + if (textContent != nil) { + [textContent release]; + textContent = nil; + } +} + +- (void)parserDidEndDocument:(NSXMLParser *)parser +{ + (void) parser; // unused + if (textContent != nil && [textContent length] > 0) { + [mdiValues setObject:[NSString stringWithString:textContent] forKey:(NSString*)kMDItemTextContent]; + [textContent release]; + textContent = nil; + } +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/macosx/spotlight/OOoMetaDataParser.h b/extensions/source/macosx/spotlight/OOoMetaDataParser.h new file mode 100644 index 0000000000..85d48b1735 --- /dev/null +++ b/extensions/source/macosx/spotlight/OOoMetaDataParser.h @@ -0,0 +1,46 @@ +/* -*- 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 . + */ + +#import <Cocoa/Cocoa.h> + + +@interface OOoMetaDataParser : NSObject <NSXMLParserDelegate> { + //indicates if content should be read + BOOL shouldReadCharacters; + //indicates if the current element is a custom metadata tag + BOOL isCustom; + + NSMutableDictionary *metaValues; + NSMutableString *textCurrentElement; + NSString *customAttribute; +} + +- (void)parseXML:(NSData*)data intoDictionary:(NSMutableDictionary*)dict; + +//delegates +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict; + +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName; + +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string; + +- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError; +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/macosx/spotlight/OOoMetaDataParser.m b/extensions/source/macosx/spotlight/OOoMetaDataParser.m new file mode 100644 index 0000000000..1e7cac685b --- /dev/null +++ b/extensions/source/macosx/spotlight/OOoMetaDataParser.m @@ -0,0 +1,205 @@ +/* -*- 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 <objc/objc-runtime.h> + +#import "OOoMetaDataParser.h" + +static NSSet *singleValueXMLElements; +static NSSet *multiValueXMLElements; +static NSDictionary *metaXML2MDIKeys; + +@implementation OOoMetaDataParser + ++ (void)initialize +{ + static BOOL isInitialized = NO; + + if (isInitialized == NO) { + //set up the meta elements with only one value + NSMutableSet *temp = [NSMutableSet new]; +//FIXME these should use namespace URIs and not prefixes + [temp addObject:@"dc:title"]; + [temp addObject:@"dc:description"]; + [temp addObject:@"meta:user-defined"]; + singleValueXMLElements = [[NSSet setWithSet:temp] retain]; + + //set up the meta elements that can have more than one value + [temp removeAllObjects]; + [temp addObject:@"dc:subject"]; + [temp addObject:@"meta:keyword"]; + [temp addObject:@"meta:initial-creator"]; + [temp addObject:@"dc:creator"]; + multiValueXMLElements = [[NSSet setWithSet:temp] retain]; + [temp release]; + + //set up the map to store the values with the correct MDI keys + NSMutableDictionary *tempDict = [NSMutableDictionary new]; + [tempDict setObject:(NSString*)kMDItemTitle forKey:@"dc:title"]; + [tempDict setObject:(NSString*)kMDItemDescription forKey:@"dc:description"]; + [tempDict setObject:(NSString*)kMDItemKeywords forKey:@"dc:subject"]; + [tempDict setObject:(NSString*)kMDItemAuthors forKey:@"meta:initial-creator"]; + [tempDict setObject:(NSString*)kMDItemAuthors forKey:@"dc:creator"]; + [tempDict setObject:(NSString*)kMDItemKeywords forKey:@"meta:keyword"]; + [tempDict setObject:@"org_openoffice_opendocument_custominfo1" forKey:@"Info 1"]; + [tempDict setObject:@"org_openoffice_opendocument_custominfo2" forKey:@"Info 2"]; + [tempDict setObject:@"org_openoffice_opendocument_custominfo3" forKey:@"Info 3"]; + [tempDict setObject:@"org_openoffice_opendocument_custominfo4" forKey:@"Info 4"]; + metaXML2MDIKeys = [[NSDictionary dictionaryWithDictionary:tempDict] retain]; + [tempDict release]; + + isInitialized = YES; + } +} + +- (id)init +{ + if ((self = [super init]) != nil) { + shouldReadCharacters = NO; + textCurrentElement = nil; + + return self; + } + + return nil; +} + +- (void)parseXML:(NSData*)data intoDictionary:(NSMutableDictionary*)dict +{ + metaValues = dict; + + //NSLog(@"data: %@ %d", data, [data length]); + + //init parser settings + shouldReadCharacters = NO; + + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; + + [parser setDelegate:self]; + + [parser setShouldResolveExternalEntities:NO]; + [parser parse]; + + [parser release]; + + //NSLog(@"finished parsing meta"); +} + +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict +{ + (void) parser; // unused + (void) namespaceURI; // FIXME this should not be ignored but should be used + // instead of meta: prefix in the comparison below! + (void) qualifiedName; // unused +// NSLog(@"<%@>", elementName); + if ([singleValueXMLElements containsObject:elementName] == YES) { + shouldReadCharacters = YES; + } else if ([multiValueXMLElements containsObject:elementName] == YES) { + shouldReadCharacters = YES; + } else { + //we are not interested in this element + shouldReadCharacters = NO; + return; + } + + if (shouldReadCharacters == YES) { + textCurrentElement = [NSMutableString new]; + isCustom = [elementName isEqualToString:@"meta:user-defined"]; + if (isCustom == YES) { + customAttribute = [[attributeDict objectForKey:@"meta:name"] retain]; + //NSLog(customAttribute); + } + } + + //NSLog(@"start element %@", elementName); +} + +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName +{ + (void) parser; // unused + (void) namespaceURI; // unused + (void) qName; // unused +// NSLog(@"</%@>", elementName); + if (shouldReadCharacters == YES) { + NSString *mdiName = nil; + if (isCustom == YES) { + mdiName = (NSString*)[metaXML2MDIKeys objectForKey:customAttribute]; + } else { + mdiName = (NSString*)[metaXML2MDIKeys objectForKey:elementName]; + } + //NSLog(@"mdiName: %@", mdiName); + + if (mdiName == nil) { + return; + } + + if ([singleValueXMLElements containsObject:elementName] == YES) { + [metaValues setObject:textCurrentElement forKey:mdiName]; + } else { + // must be multi-value + NSMutableArray *arr = [metaValues objectForKey:mdiName]; + if (arr == nil) { + // we have no array yet, create it + arr = [[NSMutableArray new] autorelease]; + // and store it + [metaValues setObject:arr forKey:mdiName]; + } + // only store an element once, no need for duplicates + if ([arr containsObject:textCurrentElement] == NO) { + [arr addObject:textCurrentElement]; + } + } + // cleanup part 1 + [textCurrentElement release]; + if (isCustom == YES) { + [customAttribute release]; + } + } + + //cleanup part 2 + shouldReadCharacters = NO; + isCustom = NO; +} + +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string +{ + (void) parser; // unused +// NSLog(@"%@", string); + if (shouldReadCharacters == NO) { + return; + } + + // this delegate method might be called several times for a single element, + // so we have to collect the received data + [textCurrentElement appendString:string]; + + //NSLog(@"chars read: %@", string); +} + +- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError +{ + //NSLog(@"parsing finished with error"); + NSLog(@"Error %li, Description: %@, Line: %li, Column: %li", (long) [parseError code], + [[parser parserError] localizedDescription], (long) [parser lineNumber], + (long) [parser columnNumber]); +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/macosx/spotlight/OOoSpotlightImporter.h b/extensions/source/macosx/spotlight/OOoSpotlightImporter.h new file mode 100644 index 0000000000..947f8dbf16 --- /dev/null +++ b/extensions/source/macosx/spotlight/OOoSpotlightImporter.h @@ -0,0 +1,37 @@ +/* -*- 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 . + */ + +#import <Cocoa/Cocoa.h> + +@interface OOoSpotlightImporter : NSObject +{ +} + +- (BOOL)importDocument:(NSString*)pathToFile + contentType:(NSString*)contentTypeUTI + attributes:(NSMutableDictionary*)attributes; + +- (NSFileHandle*)openZipFileAtPath:(NSString*)pathToFile; + +- (NSData*)metaDataFileFromZip:(NSFileHandle*)unzipFile; + +- (NSData*)contentDataFileFromZip:(NSFileHandle*)unzipFile; +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/macosx/spotlight/OOoSpotlightImporter.m b/extensions/source/macosx/spotlight/OOoSpotlightImporter.m new file mode 100644 index 0000000000..a144fe2595 --- /dev/null +++ b/extensions/source/macosx/spotlight/OOoSpotlightImporter.m @@ -0,0 +1,487 @@ +/* -*- 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 . + */ + +#import <zlib.h> + +#import "OOoSpotlightImporter.h" +#import "OOoMetaDataParser.h" +#import "OOoContentDataParser.h" + +/* a dictionary to hold the UTIs */ +static NSDictionary *uti2kind; + +typedef struct { + unsigned short min_version; + unsigned short general_flag; + unsigned short compression; + unsigned short lastmod_time; + unsigned short lastmod_date; + unsigned crc32; + unsigned compressed_size; + unsigned uncompressed_size; + unsigned short filename_size; + unsigned short extra_field_size; + NSString *filename; + NSString *extra_field; +} LocalFileHeader; + +typedef struct { + unsigned short creator_version; + unsigned short min_version; + unsigned short general_flag; + unsigned short compression; + unsigned short lastmod_time; + unsigned short lastmod_date; + unsigned crc32; + unsigned compressed_size; + unsigned uncompressed_size; + unsigned short filename_size; + unsigned short extra_field_size; + unsigned short file_comment_size; + unsigned short disk_num; + unsigned short internal_attr; + unsigned external_attr; + unsigned offset; + NSString *filename; + NSString *extra_field; + NSString *file_comment; +} CentralDirectoryEntry; + +typedef struct { + unsigned short disk_num; + unsigned short cdir_disk; + unsigned short disk_entries; + unsigned short cdir_entries; + unsigned cdir_size; + unsigned cdir_offset; + unsigned short comment_size; + NSString *comment; +} CentralDirectoryEnd; + +#define CDIR_ENTRY_SIG (0x02014b50) +#define LOC_FILE_HEADER_SIG (0x04034b50) +#define CDIR_END_SIG (0x06054b50) + +static unsigned char readByte(NSFileHandle *file) +{ + if (file == nil) + return 0; + NSData* tmpBuf = [file readDataOfLength: 1]; + if (tmpBuf == nil) + return 0; + unsigned char *d = (unsigned char*)[tmpBuf bytes]; + if (d == nil) + return 0; + return *d; +} + +static unsigned short readShort(NSFileHandle *file) +{ + unsigned short p0 = (unsigned short)readByte(file); + unsigned short p1 = (unsigned short)readByte(file); + return (unsigned short)(p0|(p1<<8)); +} + +static unsigned readInt(NSFileHandle *file) +{ + unsigned p0 = (unsigned)readByte(file); + unsigned p1 = (unsigned)readByte(file); + unsigned p2 = (unsigned)readByte(file); + unsigned p3 = (unsigned)readByte(file); + return (unsigned)(p0|(p1<<8)|(p2<<16)|(p3<<24)); +} + +static bool readCentralDirectoryEnd(NSFileHandle *file, CentralDirectoryEnd *end) +{ + unsigned signature = readInt(file); + if (signature != CDIR_END_SIG) + return false; + + end->disk_num = readShort(file); + end->cdir_disk = readShort(file); + end->disk_entries = readShort(file); + end->cdir_entries = readShort(file); + end->cdir_size = readInt(file); + end->cdir_offset = readInt(file); + end->comment_size = readShort(file); + NSData *data = [file readDataOfLength: end->comment_size]; + end->comment = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + return true; +} + +static bool readCentralDirectoryEntry(NSFileHandle *file, CentralDirectoryEntry *entry) +{ + unsigned signature = readInt(file); + if (signature != CDIR_ENTRY_SIG) + return false; + + entry->creator_version = readShort(file); + entry->min_version = readShort(file); + entry->general_flag = readShort(file); + entry->compression = readShort(file); + entry->lastmod_time = readShort(file); + entry->lastmod_date = readShort(file); + entry->crc32 = readInt(file); + entry->compressed_size = readInt(file); + entry->uncompressed_size = readInt(file); + entry->filename_size = readShort(file); + entry->extra_field_size = readShort(file); + entry->file_comment_size = readShort(file); + entry->disk_num = readShort(file); + entry->internal_attr = readShort(file); + entry->external_attr = readInt(file); + entry->offset = readInt(file); + NSData *data = [file readDataOfLength: entry->filename_size]; + entry->filename = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + data = [file readDataOfLength: entry->extra_field_size]; + entry->extra_field = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + data = [file readDataOfLength: entry->file_comment_size]; + entry->file_comment = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + return true; +} + +static bool readLocalFileHeader(NSFileHandle *file, LocalFileHeader *header) +{ + unsigned signature = readInt(file); + if (signature != LOC_FILE_HEADER_SIG) + return false; + + header->min_version = readShort(file); + header->general_flag = readShort(file); + header->compression = readShort(file); + header->lastmod_time = readShort(file); + header->lastmod_date = readShort(file); + header->crc32 = readInt(file); + header->compressed_size = readInt(file); + header->uncompressed_size = readInt(file); + header->filename_size = readShort(file); + header->extra_field_size = readShort(file); + NSData *data = [file readDataOfLength: header->filename_size]; + header->filename = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + data = [file readDataOfLength: header->extra_field_size]; + header->extra_field = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + return true; +} + +static bool areHeadersConsistent(const LocalFileHeader *header, const CentralDirectoryEntry *entry) +{ + if (header->min_version != entry->min_version) + return false; + if (header->general_flag != entry->general_flag) + return false; + if (header->compression != entry->compression) + return false; + if (!(header->general_flag & 0x08)) + { + if (header->crc32 != entry->crc32) + return false; + if (header->compressed_size != entry->compressed_size) + return false; + if (header->uncompressed_size != entry->uncompressed_size) + return false; + } + return true; +} + +static bool findCentralDirectoryEnd(NSFileHandle *file) +{ + // Assume the cdir end is in the last 1024 bytes + // Scan backward from end of file for the end signature + + [file seekToEndOfFile]; + unsigned long long fileLength = [file offsetInFile]; + + if (fileLength < 10) + return false; + + [file seekToFileOffset: (fileLength - 4)]; + + unsigned long long limit; + if (fileLength > 1024) + limit = fileLength - 1024; + else + limit = 0; + + unsigned long long offset; + while ((offset = [file offsetInFile]) > limit) + { + unsigned signature = readInt(file); + if (signature == CDIR_END_SIG) + { + // Seek back over the CDIR_END_SIG + [file seekToFileOffset: offset]; + return true; + } + else + { + // Seek one byte back + [file seekToFileOffset: (offset - 1)]; + } + } + return false; +} + +static bool isZipFile(NSFileHandle *file) +{ + if (!findCentralDirectoryEnd(file)) + return false; + CentralDirectoryEnd end; + if (!readCentralDirectoryEnd(file, &end)) + return false; + [file seekToFileOffset: end.cdir_offset]; + CentralDirectoryEntry entry; + if (!readCentralDirectoryEntry(file, &entry)) + return false; + [file seekToFileOffset: entry.offset]; + LocalFileHeader header; + if (!readLocalFileHeader(file, &header)) + return false; + if (!areHeadersConsistent(&header, &entry)) + return false; + return true; +} + +static bool findDataStream(NSFileHandle *file, CentralDirectoryEntry *entry, NSString *name) +{ + [file seekToEndOfFile]; + unsigned long long fileLength = [file offsetInFile]; + if (!findCentralDirectoryEnd(file)) + return false; + CentralDirectoryEnd end; + if (!readCentralDirectoryEnd(file, &end)) + return false; + [file seekToFileOffset: end.cdir_offset]; + do + { + if (!readCentralDirectoryEntry(file, entry)) + return false; + if ([entry->filename compare: name] == NSOrderedSame) + break; + } + while ( [file offsetInFile] < fileLength && [file offsetInFile] < end.cdir_offset + end.cdir_size); + if ([entry->filename compare: name] != NSOrderedSame) + return false; + [file seekToFileOffset: entry->offset]; + LocalFileHeader header; + if (!readLocalFileHeader(file, &header)) + return false; + if (!areHeadersConsistent(&header, entry)) + return false; + return true; +} + +static NSData *getUncompressedData(NSFileHandle *file, NSString *name) +{ + CentralDirectoryEntry entry; + if (!findDataStream(file, &entry, name)) + return nil; + if (!entry.compression) + return [file readDataOfLength: entry.compressed_size]; + else + { + int ret; + z_stream strm; + + /* allocate inflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit2(&strm,-MAX_WBITS); + if (ret != Z_OK) + return nil; + + NSData *compressedData = [file readDataOfLength: entry.compressed_size]; + + strm.avail_in = [compressedData length]; + strm.next_in = (Bytef *)[compressedData bytes]; + + Bytef *uncompressedData = (Bytef *)malloc(entry.uncompressed_size); + if (!uncompressedData) + { + (void)inflateEnd(&strm); + return nil; + } + strm.avail_out = entry.uncompressed_size; + strm.next_out = uncompressedData; + ret = inflate(&strm, Z_FINISH); + switch (ret) + { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: + (void)inflateEnd(&strm); + free(uncompressedData); + return nil; + } + (void)inflateEnd(&strm); + NSData *returnBuffer = [NSData dataWithBytes:(const void *)uncompressedData length:entry.uncompressed_size]; + free(uncompressedData); + return returnBuffer; + } +} + +@implementation OOoSpotlightImporter + +/* initialize is only called once the first time this class is loaded */ ++ (void)initialize +{ + static BOOL isInitialized = NO; + if (isInitialized == NO) { + NSMutableDictionary *temp = [NSMutableDictionary new]; + [temp setObject:@"OpenOffice.org 1.0 Text" forKey:@"org.openoffice.text"]; + [temp setObject:@"OpenDocument Text" forKey:@"org.oasis.opendocument.text"]; + [temp setObject:@"OpenOffice.org 1.0 Spreadsheet" forKey:@"org.openoffice.spreadsheet"]; + [temp setObject:@"OpenDocument Spreadsheet" forKey:@"org.oasis.opendocument.spreadsheet"]; + [temp setObject:@"OpenOffice.org 1.0 Presentation" forKey:@"org.openoffice.presentation"]; + [temp setObject:@"OpenDocument Presentation" forKey:@"org.oasis.opendocument.presentation"]; + [temp setObject:@"OpenOffice.org 1.0 Drawing" forKey:@"org.openoffice.graphics"]; + [temp setObject:@"OpenDocument Drawing" forKey:@"org.oasis.opendocument.graphics"]; + [temp setObject:@"OpenOffice.org 1.0 Master" forKey:@"org.openoffice.text-master"]; + [temp setObject:@"OpenDocument Master" forKey:@"org.oasis.opendocument.text-master"]; + [temp setObject:@"OpenOffice.org 1.0 Formula" forKey:@"org.openoffice.formula"]; + [temp setObject:@"OpenDocument Formula" forKey:@"org.oasis.opendocument.formula"]; + [temp setObject:@"OpenOffice.org 1.0 Text Template" forKey:@"org.openoffice.text-template"]; + [temp setObject:@"OpenDocument Text Template" forKey:@"org.oasis.opendocument.text-template"]; + [temp setObject:@"OpenOffice.org 1.0 Spreadsheet Template" forKey:@"org.openoffice.spreadsheet-template"]; + [temp setObject:@"OpenDocument Spreadsheet Template" forKey:@"org.oasis.opendocument.spreadsheet-template"]; + [temp setObject:@"OpenOffice.org 1.0 Presentation Template" forKey:@"org.openoffice.presentation-template"]; + [temp setObject:@"OpenDocument Presentation Template" forKey:@"org.oasis.opendocument.presentation-template"]; + [temp setObject:@"OpenOffice.org 1.0 Drawing Template" forKey:@"org.openoffice.graphics-template"]; + [temp setObject:@"OpenDocument Drawing Template" forKey:@"org.oasis.opendocument.graphics-template"]; + [temp setObject:@"OpenOffice.org 1.0 Database" forKey:@"org.openoffice.database"]; + [temp setObject:@"OpenDocument Chart" forKey:@"org.oasis.opendocument.chart"]; + + uti2kind = [[NSDictionary dictionaryWithDictionary:temp] retain]; + [temp release]; + + isInitialized = YES; + } +} + +/* importDocument is the real starting point for our plugin */ +- (BOOL)importDocument:(NSString*)pathToFile contentType:(NSString*)contentTypeUTI attributes:(NSMutableDictionary*)attributes +{ + //NSLog(contentTypeUTI); + //NSLog(pathToFile); + + NSString *itemKind = [uti2kind objectForKey:contentTypeUTI]; + if (itemKind != nil) { + [attributes setObject:itemKind forKey:(NSString*)kMDItemKind]; + } + + //first check to see if this is a valid zipped file that contains a file "meta.xml" + NSFileHandle *unzipFile = [self openZipFileAtPath:pathToFile]; + + + if (unzipFile == nil) { + //NSLog(@"zip file not open"); + return NO; + } + + //first get the metadata + NSData *metaData = [self metaDataFileFromZip:unzipFile]; + if (metaData == nil) { + [unzipFile closeFile]; + return YES; + } + + [metaData retain]; + + OOoMetaDataParser *parser = [OOoMetaDataParser new]; + if (parser != nil) { + //parse and extract the data + [parser parseXML:metaData intoDictionary:attributes]; + } + + [metaData release]; + [parser release]; + + //and now get the content + NSData *contentData = [self contentDataFileFromZip:unzipFile]; + if (contentData == nil) { + [unzipFile closeFile]; + return YES; + } + + [contentData retain]; + + OOoContentDataParser *parser2 = [OOoContentDataParser new]; + if (parser2 != nil) { + //parse and extract the data + [parser2 parseXML:contentData intoDictionary:attributes]; + } + + [contentData release]; + [parser2 release]; + + [unzipFile closeFile]; + + return YES; +} + +/* openZipFileAtPath returns the file as a valid data structure or nil otherwise*/ +- (NSFileHandle*)openZipFileAtPath:(NSString*)pathToFile +{ + NSFileHandle* unzipFile = nil; + + if ([pathToFile length] != 0) + { + unzipFile = [NSFileHandle fileHandleForReadingAtPath: pathToFile]; + } + + if (unzipFile == nil) + { + //NSLog(@"Cannot open %s",zipfilename); + return nil; + } + + if (!isZipFile(unzipFile)) + { + [unzipFile closeFile]; + return nil; + } + //NSLog(@"%s opened",zipfilename); + + return unzipFile; +} + +/* metaDataFileFromZip extracts the file meta.xml from the zip file and returns it as an NSData* structure + or nil if the metadata is not present */ +- (NSData*) metaDataFileFromZip:(NSFileHandle*)unzipFile +{ + if (unzipFile == nil) + return nil; + return getUncompressedData(unzipFile, @"meta.xml"); +} + +/* contentDataFileFromZip extracts the file content.xml from the zip file and returns it as an NSData* structure + or nil if the metadata is not present */ +- (NSData*) contentDataFileFromZip:(NSFileHandle*)unzipFile +{ + if (unzipFile == nil) + return nil; + return getUncompressedData(unzipFile, @"content.xml"); +} + + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/macosx/spotlight/SpotlightImporterTester/SpotlightImporterTester.xcodeproj/.gitignore b/extensions/source/macosx/spotlight/SpotlightImporterTester/SpotlightImporterTester.xcodeproj/.gitignore new file mode 100644 index 0000000000..96c4e5542f --- /dev/null +++ b/extensions/source/macosx/spotlight/SpotlightImporterTester/SpotlightImporterTester.xcodeproj/.gitignore @@ -0,0 +1 @@ +project.xcworkspace diff --git a/extensions/source/macosx/spotlight/SpotlightImporterTester/SpotlightImporterTester.xcodeproj/project.pbxproj b/extensions/source/macosx/spotlight/SpotlightImporterTester/SpotlightImporterTester.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..a577eeb1d7 --- /dev/null +++ b/extensions/source/macosx/spotlight/SpotlightImporterTester/SpotlightImporterTester.xcodeproj/project.pbxproj @@ -0,0 +1,308 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + BE9A7AC823590D9500931013 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = BE9A7AC723590D9500931013 /* main.m */; }; + BE9A7AD723590E5D00931013 /* OOoSpotlightImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = BE9A7ACE23590E5D00931013 /* OOoSpotlightImporter.m */; }; + BE9A7AD823590E5D00931013 /* OOoMetaDataParser.m in Sources */ = {isa = PBXBuildFile; fileRef = BE9A7AD223590E5D00931013 /* OOoMetaDataParser.m */; }; + BE9A7AD923590E5D00931013 /* OOoContentDataParser.m in Sources */ = {isa = PBXBuildFile; fileRef = BE9A7AD323590E5D00931013 /* OOoContentDataParser.m */; }; + BE9A7ADA23590E5D00931013 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = BE9A7AD423590E5D00931013 /* main.m */; }; + BE9A7ADB23590E5D00931013 /* GetMetadataForFile.m in Sources */ = {isa = PBXBuildFile; fileRef = BE9A7AD623590E5D00931013 /* GetMetadataForFile.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + BE9A7AC223590D9400931013 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + BE9A7AC423590D9500931013 /* SpotlightImporterTester */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SpotlightImporterTester; sourceTree = BUILT_PRODUCTS_DIR; }; + BE9A7AC723590D9500931013 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; + BE9A7ACE23590E5D00931013 /* OOoSpotlightImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OOoSpotlightImporter.m; path = ../../OOoSpotlightImporter.m; sourceTree = "<group>"; }; + BE9A7ACF23590E5D00931013 /* OOoSpotlightImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OOoSpotlightImporter.h; path = ../../OOoSpotlightImporter.h; sourceTree = "<group>"; }; + BE9A7AD023590E5D00931013 /* GetMetadataForFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GetMetadataForFile.h; path = ../../GetMetadataForFile.h; sourceTree = "<group>"; }; + BE9A7AD123590E5D00931013 /* OOoMetaDataParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OOoMetaDataParser.h; path = ../../OOoMetaDataParser.h; sourceTree = "<group>"; }; + BE9A7AD223590E5D00931013 /* OOoMetaDataParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OOoMetaDataParser.m; path = ../../OOoMetaDataParser.m; sourceTree = "<group>"; }; + BE9A7AD323590E5D00931013 /* OOoContentDataParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OOoContentDataParser.m; path = ../../OOoContentDataParser.m; sourceTree = "<group>"; }; + BE9A7AD423590E5D00931013 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = ../../main.m; sourceTree = "<group>"; }; + BE9A7AD523590E5D00931013 /* OOoContentDataParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OOoContentDataParser.h; path = ../../OOoContentDataParser.h; sourceTree = "<group>"; }; + BE9A7AD623590E5D00931013 /* GetMetadataForFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GetMetadataForFile.m; path = ../../GetMetadataForFile.m; sourceTree = "<group>"; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + BE9A7AC123590D9400931013 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + BE9A7ABB23590D9400931013 = { + isa = PBXGroup; + children = ( + BE9A7AC623590D9500931013 /* SpotlightImporterTester */, + BE9A7AC523590D9500931013 /* Products */, + ); + sourceTree = "<group>"; + }; + BE9A7AC523590D9500931013 /* Products */ = { + isa = PBXGroup; + children = ( + BE9A7AC423590D9500931013 /* SpotlightImporterTester */, + ); + name = Products; + sourceTree = "<group>"; + }; + BE9A7AC623590D9500931013 /* SpotlightImporterTester */ = { + isa = PBXGroup; + children = ( + BE9A7AD023590E5D00931013 /* GetMetadataForFile.h */, + BE9A7AD623590E5D00931013 /* GetMetadataForFile.m */, + BE9A7AD423590E5D00931013 /* main.m */, + BE9A7AD523590E5D00931013 /* OOoContentDataParser.h */, + BE9A7AD323590E5D00931013 /* OOoContentDataParser.m */, + BE9A7AD123590E5D00931013 /* OOoMetaDataParser.h */, + BE9A7AD223590E5D00931013 /* OOoMetaDataParser.m */, + BE9A7ACF23590E5D00931013 /* OOoSpotlightImporter.h */, + BE9A7ACE23590E5D00931013 /* OOoSpotlightImporter.m */, + BE9A7AC723590D9500931013 /* main.m */, + ); + path = SpotlightImporterTester; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + BE9A7AC323590D9400931013 /* SpotlightImporterTester */ = { + isa = PBXNativeTarget; + buildConfigurationList = BE9A7ACB23590D9500931013 /* Build configuration list for PBXNativeTarget "SpotlightImporterTester" */; + buildPhases = ( + BE9A7AC023590D9400931013 /* Sources */, + BE9A7AC123590D9400931013 /* Frameworks */, + BE9A7AC223590D9400931013 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SpotlightImporterTester; + productName = SpotlightImporterTester; + productReference = BE9A7AC423590D9500931013 /* SpotlightImporterTester */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BE9A7ABC23590D9400931013 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1110; + ORGANIZATIONNAME = Collabora; + TargetAttributes = { + BE9A7AC323590D9400931013 = { + CreatedOnToolsVersion = 11.1; + }; + }; + }; + buildConfigurationList = BE9A7ABF23590D9400931013 /* Build configuration list for PBXProject "SpotlightImporterTester" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = BE9A7ABB23590D9400931013; + productRefGroup = BE9A7AC523590D9500931013 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + BE9A7AC323590D9400931013 /* SpotlightImporterTester */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + BE9A7AC023590D9400931013 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BE9A7ADA23590E5D00931013 /* main.m in Sources */, + BE9A7AD923590E5D00931013 /* OOoContentDataParser.m in Sources */, + BE9A7AD823590E5D00931013 /* OOoMetaDataParser.m in Sources */, + BE9A7ADB23590E5D00931013 /* GetMetadataForFile.m in Sources */, + BE9A7AD723590E5D00931013 /* OOoSpotlightImporter.m in Sources */, + BE9A7AC823590D9500931013 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + BE9A7AC923590D9500931013 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + LO_CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + LO_CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + BE9A7ACA23590D9500931013 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + LO_CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + LO_CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + }; + name = Release; + }; + BE9A7ACC23590D9500931013 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_ARC = NO; + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + BE9A7ACD23590D9500931013 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_ARC = NO; + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + BE9A7ABF23590D9400931013 /* Build configuration list for PBXProject "SpotlightImporterTester" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BE9A7AC923590D9500931013 /* Debug */, + BE9A7ACA23590D9500931013 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + BE9A7ACB23590D9500931013 /* Build configuration list for PBXNativeTarget "SpotlightImporterTester" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BE9A7ACC23590D9500931013 /* Debug */, + BE9A7ACD23590D9500931013 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = BE9A7ABC23590D9400931013 /* Project object */; +} diff --git a/extensions/source/macosx/spotlight/SpotlightImporterTester/SpotlightImporterTester.xcodeproj/xcshareddata/xcschemes/SpotlightImporterTester.xcscheme b/extensions/source/macosx/spotlight/SpotlightImporterTester/SpotlightImporterTester.xcodeproj/xcshareddata/xcschemes/SpotlightImporterTester.xcscheme new file mode 100644 index 0000000000..d2aab1a019 --- /dev/null +++ b/extensions/source/macosx/spotlight/SpotlightImporterTester/SpotlightImporterTester.xcodeproj/xcshareddata/xcschemes/SpotlightImporterTester.xcscheme @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1110" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "BE9A7AC323590D9400931013" + BuildableName = "SpotlightImporterTester" + BlueprintName = "SpotlightImporterTester" + ReferencedContainer = "container:SpotlightImporterTester.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES"> + <Testables> + </Testables> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + <BuildableProductRunnable + runnableDebuggingMode = "0"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "BE9A7AC323590D9400931013" + BuildableName = "SpotlightImporterTester" + BlueprintName = "SpotlightImporterTester" + ReferencedContainer = "container:SpotlightImporterTester.xcodeproj"> + </BuildableReference> + </BuildableProductRunnable> + <CommandLineArguments> + <CommandLineArgument + argument = "org.openoffice.database" + isEnabled = "YES"> + </CommandLineArgument> + <CommandLineArgument + argument = "/tmp/sample.odb" + isEnabled = "YES"> + </CommandLineArgument> + </CommandLineArguments> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <BuildableProductRunnable + runnableDebuggingMode = "0"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "BE9A7AC323590D9400931013" + BuildableName = "SpotlightImporterTester" + BlueprintName = "SpotlightImporterTester" + ReferencedContainer = "container:SpotlightImporterTester.xcodeproj"> + </BuildableReference> + </BuildableProductRunnable> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/extensions/source/macosx/spotlight/SpotlightImporterTester/SpotlightImporterTester/main.m b/extensions/source/macosx/spotlight/SpotlightImporterTester/SpotlightImporterTester/main.m new file mode 100644 index 0000000000..f9b6bbb5fb --- /dev/null +++ b/extensions/source/macosx/spotlight/SpotlightImporterTester/SpotlightImporterTester/main.m @@ -0,0 +1,30 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#import <stdio.h> + +#import <CoreFoundation/CoreFoundation.h> +#import <CoreServices/CoreServices.h> +#import <Foundation/Foundation.h> + +#import "GetMetadataForFile.h" + +int main(int argc, const char* argv[]) +{ + @autoreleasepool + { + if (argc != 3) + { + fprintf(stderr, "Usage: %s UTI path\n", argv[0]); + return 1; + } + NSMutableDictionary* attributes = [NSMutableDictionary dictionaryWithCapacity:10]; + NSString* contentTypeUTI = [NSString stringWithUTF8String:argv[1]]; + NSString* pathToFile = [NSString stringWithUTF8String:argv[2]]; + + GetMetadataForFile(NULL, (__bridge CFMutableDictionaryRef)attributes, + (__bridge CFStringRef)contentTypeUTI, (__bridge CFStringRef)pathToFile); + } + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/macosx/spotlight/main.m b/extensions/source/macosx/spotlight/main.m new file mode 100644 index 0000000000..b1a6381a73 --- /dev/null +++ b/extensions/source/macosx/spotlight/main.m @@ -0,0 +1,211 @@ +/* -*- 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 . + */ +// +// main.m +// SpotlightTester +// +// Created by Florian Heckl on 10.07.07. +// +//============================================================================== +// +// DO NOT MODIFY THE CONTENTS OF THIS FILE +// +// This file contains the generic CFPlug-in code necessary for your importer +// To complete your importer implement the function in GetMetadataForFile.c +// +//============================================================================== + +#include <CoreFoundation/CoreFoundation.h> +#include <CoreFoundation/CFPlugInCOM.h> +#include <CoreServices/CoreServices.h> + +#include "GetMetadataForFile.h" + +// constants + + +#define PLUGIN_ID "A3FCC88D-B9A6-4364-8B93-92123C8A2D18" + +// +// Below is the generic glue code for all plug-ins. +// +// You should not have to modify this code aside from changing +// names if you decide to change the names defined in the Info.plist +// + + +// typedefs + +// The layout for an instance of MetaDataImporterPlugIn +typedef struct +{ + MDImporterInterfaceStruct *conduitInterface; + CFUUIDRef factoryID; + UInt32 refCount; +} MetadataImporterPluginType; + +// prototypes +// Forward declaration for the IUnknown implementation. +// + +static MetadataImporterPluginType *AllocMetadataImporterPluginType(CFUUIDRef inFactoryID); +static void DeallocMetadataImporterPluginType(MetadataImporterPluginType *thisInstance); +static HRESULT MetadataImporterQueryInterface(void *thisInstance,REFIID iid,LPVOID *ppv); +static ULONG MetadataImporterPluginAddRef(void *thisInstance); +static ULONG MetadataImporterPluginRelease(void *thisInstance); +// testInterfaceFtbl definition +// The TestInterface function table. +// + +static MDImporterInterfaceStruct testInterfaceFtbl = { + NULL, + MetadataImporterQueryInterface, + MetadataImporterPluginAddRef, + MetadataImporterPluginRelease, + GetMetadataForFile +}; + + +// AllocMetadataImporterPluginType +// Utility function that allocates a new instance. +// You can do some initial setup for the importer here if you wish +// like allocating globals etc... +// +MetadataImporterPluginType *AllocMetadataImporterPluginType(CFUUIDRef inFactoryID) +{ + MetadataImporterPluginType *theNewInstance; + + theNewInstance = (MetadataImporterPluginType *)malloc(sizeof(MetadataImporterPluginType)); + memset(theNewInstance,0,sizeof(MetadataImporterPluginType)); + + /* Point to the function table */ + theNewInstance->conduitInterface = &testInterfaceFtbl; + + /* Retain and keep an open instance refcount for each factory. */ + theNewInstance->factoryID = CFRetain(inFactoryID); + CFPlugInAddInstanceForFactory(inFactoryID); + + /* This function returns the IUnknown interface so set the refCount to one. */ + theNewInstance->refCount = 1; + return theNewInstance; +} + +// DeallocSpotlightTesterMDImporterPluginType +// Utility function that deallocates the instance when +// the refCount goes to zero. +// In the current implementation importer interfaces are never deallocated +// but implement this as this might change in the future +// +void DeallocMetadataImporterPluginType(MetadataImporterPluginType *thisInstance) +{ + CFUUIDRef theFactoryID; + + theFactoryID = thisInstance->factoryID; + free(thisInstance); + if (theFactoryID){ + CFPlugInRemoveInstanceForFactory(theFactoryID); + CFRelease(theFactoryID); + } +} + +// MetadataImporterQueryInterface +// Implementation of the IUnknown QueryInterface function. +// +HRESULT MetadataImporterQueryInterface(void *thisInstance,REFIID iid,LPVOID *ppv) +{ + CFUUIDRef interfaceID; + + interfaceID = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault,iid); + + if (CFEqual(interfaceID,kMDImporterInterfaceID)){ + /* If the Right interface was requested, bump the ref count, + * set the ppv parameter equal to the instance, and + * return good status. + */ + ((MetadataImporterPluginType*)thisInstance)->conduitInterface->AddRef(thisInstance); + *ppv = thisInstance; + CFRelease(interfaceID); + return S_OK; + }else{ + if (CFEqual(interfaceID,IUnknownUUID)){ + /* If the IUnknown interface was requested, same as above. */ + ((MetadataImporterPluginType*)thisInstance )->conduitInterface->AddRef(thisInstance); + *ppv = thisInstance; + CFRelease(interfaceID); + return S_OK; + }else{ + /* Requested interface unknown, bail with error. */ + *ppv = NULL; + CFRelease(interfaceID); + return E_NOINTERFACE; + } + } +} + +// MetadataImporterPluginAddRef +// Implementation of reference counting for this type. Whenever an interface +// is requested, bump the refCount for the instance. NOTE: returning the +// refcount is a convention but is not required so don't rely on it. +// +ULONG MetadataImporterPluginAddRef(void *thisInstance) +{ + ((MetadataImporterPluginType *)thisInstance )->refCount += 1; + return ((MetadataImporterPluginType*) thisInstance)->refCount; +} + +// SampleCMPluginRelease +// When an interface is released, decrement the refCount. +// If the refCount goes to zero, deallocate the instance. +// +ULONG MetadataImporterPluginRelease(void *thisInstance) +{ + ((MetadataImporterPluginType*)thisInstance)->refCount -= 1; + if (((MetadataImporterPluginType*)thisInstance)->refCount == 0){ + DeallocMetadataImporterPluginType((MetadataImporterPluginType*)thisInstance ); + return 0; + }else{ + return ((MetadataImporterPluginType*) thisInstance )->refCount; + } +} + +// SpotlightTesterMDImporterPluginFactory +// Implementation of the factory function for this type. +// +__attribute__ ((visibility("default"))) +void * +MetadataImporterPluginFactory(CFAllocatorRef allocator, CFUUIDRef typeID) +{ + (void) allocator; /* unused */ + MetadataImporterPluginType *result; + CFUUIDRef uuid; + + /* If correct type is being requested, allocate an + * instance of TestType and return the IUnknown interface. + */ + if (CFEqual(typeID,kMDImporterTypeID)){ + uuid = CFUUIDCreateFromString(kCFAllocatorDefault,CFSTR(PLUGIN_ID)); + result = AllocMetadataImporterPluginType(uuid); + CFRelease(uuid); + return result; + } + /* If the requested type is incorrect, return NULL. */ + return NULL; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/macosx/spotlight/mdimporter/Info.plist b/extensions/source/macosx/spotlight/mdimporter/Info.plist new file mode 100644 index 0000000000..5a60493cb4 --- /dev/null +++ b/extensions/source/macosx/spotlight/mdimporter/Info.plist @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<!-- + * 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 . +--> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleDocumentTypes</key> + <array> + <dict> + <key>CFBundleTypeRole</key> + <string>MDImporter</string> + <key>LSItemContentTypes</key> + <array> + <string>org.openoffice.text</string> + <string>org.oasis-open.opendocument.text</string> + <string>org.openoffice.spreadsheet</string> + <string>org.oasis-open.opendocument.spreadsheet</string> + <string>org.openoffice.presentation</string> + <string>org.oasis-open.opendocument.presentation</string> + <string>org.openoffice.graphics</string> + <string>org.oasis-open.opendocument.graphics</string> + <string>org.openoffice.text-master</string> + <string>org.oasis-open.opendocument.text-master</string> + <string>org.openoffice.formula</string> + <string>org.oasis-open.opendocument.formula</string> + <string>org.openoffice.text-template</string> + <string>org.oasis-open.opendocument.text-template</string> + <string>org.openoffice.spreadsheet-template</string> + <string>org.oasis-open.opendocument.spreadsheet-template</string> + <string>org.openoffice.presentation-template</string> + <string>org.oasis-open.opendocument.presentation-template</string> + <string>org.openoffice.graphics-template</string> + <string>org.oasis-open.opendocument.graphics-template</string> + <string>org.oasis-open.opendocument.database</string> + </array> + </dict> + </array> + <key>CFBundleExecutable</key> + <string>OOoSpotlightImporter</string> + <key>CFBundleName</key> + <string>OOoSpotlightImporter</string> + <key>CFBundleIconFile</key> + <string></string> + <key>CFBundleIdentifier</key> + <string>org.libreoffice.mdimporter</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>CFPlugInDynamicRegisterFunction</key> + <string></string> + <key>CFPlugInDynamicRegistration</key> + <string>NO</string> + <key>CFPlugInFactories</key> + <dict> + <key>A3FCC88D-B9A6-4364-8B93-92123C8A2D18</key> + <string>MetadataImporterPluginFactory</string> + </dict> + <key>CFPlugInTypes</key> + <dict> + <key>8B08C4BF-415B-11D8-B3F9-0003936726FC</key> + <array> + <string>A3FCC88D-B9A6-4364-8B93-92123C8A2D18</string> + </array> + </dict> + <key>CFPlugInUnloadFunction</key> + <string></string> + +</dict> +</plist> diff --git a/extensions/source/macosx/spotlight/mdimporter/en.lproj/schema.strings b/extensions/source/macosx/spotlight/mdimporter/en.lproj/schema.strings new file mode 100644 index 0000000000..3559987837 --- /dev/null +++ b/extensions/source/macosx/spotlight/mdimporter/en.lproj/schema.strings @@ -0,0 +1 @@ +ÿþ
\ No newline at end of file diff --git a/extensions/source/macosx/spotlight/mdimporter/schema.xml b/extensions/source/macosx/spotlight/mdimporter/schema.xml new file mode 100644 index 0000000000..f17aa8c6c5 --- /dev/null +++ b/extensions/source/macosx/spotlight/mdimporter/schema.xml @@ -0,0 +1,413 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . +--> +<schema version="1.0" xmlns="http://www.apple.com/metadata" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.apple.com/metadata file:///System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Resources/MetadataSchema.xsd"> + <note> + OpenOffice.org allows the user to enter 4 pieces of custom information in the document's metadata. + These metadata fields are described here for Spotlight's use. + </note> + <attributes> + <attribute name="org_openoffice_opendocument_custominfo1" type="CFString"/> + <attribute name="org_openoffice_opendocument_custominfo2" type="CFString"/> + <attribute name="org_openoffice_opendocument_custominfo3" type="CFString"/> + <attribute name="org_openoffice_opendocument_custominfo4" type="CFString"/> + </attributes> + + <types> + <type name="org.openoffice.text"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.oasis.opendocument.text"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.openoffice.spreadsheet"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.oasis.opendocument.spreadsheet"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.openoffice.presentation"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.oasis.opendocument.presentation"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.openoffice.graphics"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.oasis.opendocument.graphics"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.openoffice.text-master"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.oasis.opendocument.text-master"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.openoffice.formula"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.oasis.opendocument.formula"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.openoffice.text-template"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.oasis.opendocument.text-template"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.openoffice.spreadsheet-template"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.oasis.opendocument.spreadsheet-template"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.openoffice.presentation-template"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.oasis.opendocument.presentation-template"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.openoffice.graphics-template"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.oasis.opendocument.graphics-template"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + + <type name="org.oasis.opendocument.chart"> + <note> + The custom metadata info. + </note> + <allattrs id="attrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </allattrs> + <displayattrs id="dattrs"> + org_openoffice_opendocument_custominfo1 + org_openoffice_opendocument_custominfo2 + org_openoffice_opendocument_custominfo3 + org_openoffice_opendocument_custominfo4 + </displayattrs> + </type> + </types> +</schema> + diff --git a/extensions/source/macosx/spotlight/version.plist b/extensions/source/macosx/spotlight/version.plist new file mode 100644 index 0000000000..d913428417 --- /dev/null +++ b/extensions/source/macosx/spotlight/version.plist @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<!-- + * 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 . +--> +<plist version="1.0"> +<dict> + <key>BuildVersion</key> + <string>266</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>ProjectName</key> + <string>DevToolsWizardTemplates</string> + <key>SourceVersion</key> + <string>3070000</string> +</dict> +</plist> diff --git a/extensions/source/ole/comifaces.hxx b/extensions/source/ole/comifaces.hxx new file mode 100644 index 0000000000..51e955dd60 --- /dev/null +++ b/extensions/source/ole/comifaces.hxx @@ -0,0 +1,62 @@ +/* -*- 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 . + */ +#pragma once + +#include <com/sun/star/uno/XInterface.hpp> + +using namespace com::sun::star::uno; + +MIDL_INTERFACE("e40a2331-3bc1-11d4-8321-005004526ab4") +IJScriptValueObject: public IUnknown +{ + STDMETHOD( Set)( VARIANT type, VARIANT value)= 0; + STDMETHOD( Get)( VARIANT *val)= 0; + STDMETHOD( InitOutParam)()= 0; + STDMETHOD( InitInOutParam)( VARIANT type, VARIANT value)= 0; + STDMETHOD( IsOutParam)( VARIANT_BOOL * flag)= 0; + STDMETHOD( IsInOutParam)( VARIANT_BOOL * flag)= 0; + STDMETHOD( GetValue)( BSTR* type, VARIANT *value)= 0; + +protected: + ~IJScriptValueObject() {} +}; + +MIDL_INTERFACE("7B5C3410-66FA-11d4-832A-005004526AB4") +IUnoObjectWrapper: public IUnknown +{ + STDMETHOD( getWrapperXInterface)( Reference<XInterface>* pInt)=0; + STDMETHOD( getOriginalUnoObject)( Reference<XInterface>* pInt)=0; + STDMETHOD( getOriginalUnoStruct)( Any * pStruct)=0; + +protected: + ~IUnoObjectWrapper() {} +}; + +MIDL_INTERFACE("8BB66591-A544-4de9-822C-57AB57BCED1C") +IUnoTypeWrapper: public IUnknown +{ + STDMETHOD(put_Name)(BSTR val) = 0; + STDMETHOD(get_Name)(BSTR* pVal) = 0; + +protected: + ~IUnoTypeWrapper() {} +}; + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/jscriptclasses.cxx b/extensions/source/ole/jscriptclasses.cxx new file mode 100644 index 0000000000..8fc371c4c2 --- /dev/null +++ b/extensions/source/ole/jscriptclasses.cxx @@ -0,0 +1,312 @@ +/* -*- 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 "jscriptclasses.hxx" + + +// JScriptValue + +JScriptValue::JScriptValue(): m_bOutParam(false), m_bInOutParam(false) +{ +} + +JScriptValue::~JScriptValue() +{ +} + + +// JScriptValue, IDispatch -------------------------------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP JScriptValue::GetTypeInfoCount(UINT* /*pctinfo*/) +{ + return E_NOTIMPL; +} + +// JScriptValue, IDispatch -------------------------------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP JScriptValue::GetTypeInfo( UINT /*iTInfo*/, + LCID /*lcid*/, + ITypeInfo** /*ppTInfo*/) +{ + return E_NOTIMPL; +} + +// JScriptValue, IDispatch -------------------------------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP JScriptValue::GetIDsOfNames( REFIID /*riid*/, + LPOLESTR *rgszNames, + UINT /*cNames*/, + LCID /*lcid*/, + DISPID *rgDispId) +{ + if( !rgDispId) + return E_POINTER; + + + HRESULT ret= S_OK; + CComBSTR name(*rgszNames); + name.ToLower(); + + if( name == CComBSTR( L"set") ) + *rgDispId= 1; + else if( name == CComBSTR( L"get") ) + *rgDispId= 2; + else if( name == CComBSTR( L"initoutparam") ) + *rgDispId= 3; + else if( name == CComBSTR( L"initinoutparam") ) + *rgDispId= 4; + else + ret= DISP_E_UNKNOWNNAME; + + return ret; +} + +// JScriptValue, IDispatch -------------------------------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP JScriptValue::Invoke( DISPID dispIdMember, + REFIID /*riid*/, + LCID /*lcid*/, + WORD wFlags, + DISPPARAMS *pDispParams, + VARIANT *pVarResult, + EXCEPINFO* /*pExcepInfo*/, + UINT* /*puArgErr*/) +{ + if( pDispParams->cNamedArgs) + return DISP_E_NONAMEDARGS; + + + HRESULT ret= S_OK; + switch( dispIdMember) + { + case 0: // DISPID_VALUE + if( wFlags & DISPATCH_PROPERTYGET && pVarResult) + { + if( FAILED( VariantCopy( pVarResult, &m_varValue))) + ret= E_FAIL; + } + else + ret= E_POINTER; + break; + case 1: + if( wFlags & DISPATCH_METHOD) + ret= Set( pDispParams->rgvarg[1], pDispParams->rgvarg[0]); + if( FAILED( ret)) + ret= DISP_E_EXCEPTION; + break; + case 2: + if( wFlags & DISPATCH_METHOD) + ret= Get( pVarResult); + if( FAILED( ret)) + ret= DISP_E_EXCEPTION; + break; + case 3: + if( wFlags & DISPATCH_METHOD) + ret= InitOutParam(); + if( FAILED( ret)) + ret= DISP_E_EXCEPTION; + break; + case 4: + if( wFlags & DISPATCH_METHOD) + ret= InitInOutParam( pDispParams->rgvarg[1], pDispParams->rgvarg[0]); + if( FAILED( ret)) + ret= DISP_E_EXCEPTION; + break; + default: + ret= DISP_E_MEMBERNOTFOUND; + break; + } + + return ret; +} + +// JScriptValue, IScriptOutParam----------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP JScriptValue::Set( VARIANT type, VARIANT value) +{ + Lock(); + m_varValue.Clear(); + HRESULT hr= VariantCopyInd( &m_varValue, &value); + VARIANT var; + VariantInit( &var); + if( SUCCEEDED( hr= VariantChangeType( &var, &type, 0, VT_BSTR))) + m_bstrType= var.bstrVal; + Unlock(); + return hr; +} +// JScriptValue, IScriptOutParam----------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP JScriptValue::Get( VARIANT *val) +{ + Lock(); + if( !val) + return E_POINTER; + HRESULT hr= VariantCopy( val, &m_varValue); + Unlock(); + return hr; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP JScriptValue::InitOutParam() +{ + Lock(); + m_varValue.Clear(); + m_bOutParam= true; + m_bInOutParam= false; + Unlock(); + return S_OK; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP JScriptValue::InitInOutParam( VARIANT type, VARIANT value) +{ + Lock(); + m_bInOutParam= true; + m_bOutParam= false; + Unlock(); + return Set( type, value); +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP JScriptValue::IsOutParam( VARIANT_BOOL * flag) +{ + Lock(); + if( !flag) + return E_POINTER; + *flag= m_bOutParam ? VARIANT_TRUE : VARIANT_FALSE; + Unlock(); + return S_OK; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP JScriptValue::IsInOutParam( VARIANT_BOOL * flag) +{ + Lock(); + if( !flag) + return E_POINTER; + *flag= m_bInOutParam ? VARIANT_TRUE : VARIANT_FALSE; + Unlock(); + return S_OK; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP JScriptValue::GetValue( BSTR* type, VARIANT *value) +{ + Lock(); + if( !type || !value) + return E_POINTER; + HRESULT hr; + if( SUCCEEDED( hr= m_bstrType.CopyTo( type))) + hr= VariantCopy( value, &m_varValue); + Unlock(); + return hr; +} + + +// JScriptOutValue + + +JScriptOutParam::JScriptOutParam() +{ +} + +JScriptOutParam::~JScriptOutParam() +{ +} + + +// JScriptOutParam, IDispatch -------------------------------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP JScriptOutParam::GetTypeInfoCount(UINT* /*pctinfo*/) +{ + return E_NOTIMPL; +} + +// JScriptOutParam, IDispatch -------------------------------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP JScriptOutParam::GetTypeInfo( UINT /*iTInfo*/, + LCID /*lcid*/, + ITypeInfo** /*ppTInfo*/) +{ + return E_NOTIMPL; +} + +// JScriptOutParam, IDispatch -------------------------------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP JScriptOutParam::GetIDsOfNames( REFIID /*riid*/, + LPOLESTR *rgszNames, + UINT /*cNames*/, + LCID /*lcid*/, + DISPID *rgDispId) +{ + if( !rgDispId) + return E_POINTER; + + + HRESULT ret= S_OK; + CComBSTR name(*rgszNames); + name.ToLower(); + + if( name == CComBSTR( L"0") ) + *rgDispId= 1; + else + ret= DISP_E_UNKNOWNNAME; + + return ret; +} + +// JScriptOutParam, IDispatch -------------------------------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP JScriptOutParam::Invoke( DISPID dispIdMember, + REFIID /*riid*/, + LCID /*lcid*/, + WORD wFlags, + DISPPARAMS *pDispParams, + VARIANT *pVarResult, + EXCEPINFO* /*pExcepInfo*/, + UINT* /*puArgErr*/) +{ + HRESULT ret= S_OK; + switch( dispIdMember) + { + case 0: // DISPID_VALUE + if( wFlags & DISPATCH_PROPERTYGET && pVarResult) + { + if( FAILED( VariantCopy( pVarResult, &m_varValue))) + ret= E_FAIL; + } + else if( wFlags & DISPATCH_PROPERTYPUT || wFlags & DISPATCH_PROPERTYPUTREF) + { + m_varValue.Clear(); + if( FAILED( VariantCopyInd( &m_varValue, &pDispParams->rgvarg[0]))) + ret= E_FAIL; + } + else + ret= E_POINTER; + break; + case 1: + if( wFlags & DISPATCH_PROPERTYGET && pVarResult) + { + if( FAILED( VariantCopy( pVarResult, &m_varValue))) + ret= E_FAIL; + } + else if( wFlags & DISPATCH_PROPERTYPUT || wFlags & DISPATCH_PROPERTYPUTREF) + { + m_varValue.Clear(); + if( FAILED( VariantCopyInd( &m_varValue, &pDispParams->rgvarg[0]))) + ret= E_FAIL; + } + else + ret= E_POINTER; + break; + + default: + ret= DISP_E_MEMBERNOTFOUND; + break; + } + + return ret; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/jscriptclasses.hxx b/extensions/source/ole/jscriptclasses.hxx new file mode 100644 index 0000000000..cef993ed04 --- /dev/null +++ b/extensions/source/ole/jscriptclasses.hxx @@ -0,0 +1,147 @@ +/* -*- 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 . + */ + +#pragma once + +#include "wincrap.hxx" + +#include "comifaces.hxx" + + +// Sequences are represented by prepending "[]", e.g. []char, [][]byte, [][][]object, etc. + +// To make a JScriptValue object to an out parameter, call +// "InitOutParam" and to make it a in/out parameter call +// "InitInOutParam" + +// If the object represents an out parameter then the value can after the call +// be retrieved by "Get". + +// From JavaScript the functions Get, Set, InitOutParam and InitInOutParam are +// used, that is they are accessible through IDispatch. The functions are used +// by the bridge. + +class JScriptValue: + public CComObjectRootEx<CComMultiThreadModel>, + public IJScriptValueObject, + public IDispatch +{ +public: + JScriptValue(); + virtual ~JScriptValue(); + + BEGIN_COM_MAP(JScriptValue) + COM_INTERFACE_ENTRY(IDispatch) + COM_INTERFACE_ENTRY(IJScriptValueObject) +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winconsistent-missing-override" +#endif + END_COM_MAP() +#if defined __clang__ +#pragma clang diagnostic pop +#endif + + // IDispatch ------------------------------------------- + STDMETHOD( GetTypeInfoCount)(UINT *pctinfo) override; + + STDMETHOD( GetTypeInfo)( UINT iTInfo, + LCID lcid, + ITypeInfo **ppTInfo) override; + + STDMETHOD( GetIDsOfNames)( REFIID riid, + LPOLESTR *rgszNames, + UINT cNames, + LCID lcid, + DISPID *rgDispId) override; + + STDMETHOD( Invoke)( DISPID dispIdMember, + REFIID riid, + LCID lcid, + WORD wFlags, + DISPPARAMS *pDispParams, + VARIANT *pVarResult, + EXCEPINFO *pExcepInfo, + UINT *puArgErr) override; + // IJScriptOutParam -------------------------------------- + + STDMETHOD( Set)( VARIANT type, VARIANT value) override; + STDMETHOD( Get)( VARIANT *val) override; + STDMETHOD( InitOutParam)() override; + STDMETHOD( InitInOutParam)( VARIANT type, VARIANT value) override; + STDMETHOD( IsOutParam)( VARIANT_BOOL * flag) override; + STDMETHOD( IsInOutParam)( VARIANT_BOOL * flag) override; + STDMETHOD( GetValue)( BSTR* type, VARIANT *value) override; + + + CComVariant m_varValue; + CComBSTR m_bstrType; + bool m_bOutParam: 1; + bool m_bInOutParam: 1; + +}; + +// If a class is implemented in JScript, then its method +class JScriptOutParam: + public CComObjectRootEx<CComMultiThreadModel>, + public IDispatch +{ +public: + JScriptOutParam(); + virtual ~JScriptOutParam(); + + BEGIN_COM_MAP(JScriptOutParam) + COM_INTERFACE_ENTRY(IDispatch) +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winconsistent-missing-override" +#endif + END_COM_MAP() +#if defined __clang__ +#pragma clang diagnostic pop +#endif + + // IDispatch ------------------------------------------- + STDMETHOD( GetTypeInfoCount)(UINT *pctinfo) override; + + STDMETHOD( GetTypeInfo)( UINT iTInfo, + LCID lcid, + ITypeInfo **ppTInfo) override; + + STDMETHOD( GetIDsOfNames)( REFIID riid, + LPOLESTR *rgszNames, + UINT cNames, + LCID lcid, + DISPID *rgDispId) override; + + STDMETHOD( Invoke)( DISPID dispIdMember, + REFIID riid, + LCID lcid, + WORD wFlags, + DISPPARAMS *pDispParams, + VARIANT *pVarResult, + EXCEPINFO *pExcepInfo, + UINT *puArgErr) override; + + +private: + CComVariant m_varValue; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/ole2uno.cxx b/extensions/source/ole/ole2uno.cxx new file mode 100644 index 0000000000..f9eef5125e --- /dev/null +++ b/extensions/source/ole/ole2uno.cxx @@ -0,0 +1,47 @@ +/* -*- 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 <osl/getglobalmutex.hxx> +#include <rtl/instance.hxx> +#include "ole2uno.hxx" + +using namespace osl; + +namespace { + +struct MutexInit +{ + Mutex * operator () () + { + static Mutex aInstance; + return &aInstance; + } +}; + +} + +Mutex * getBridgeMutex() +{ + return rtl_Instance< Mutex, MutexInit, ::osl::MutexGuard, + ::osl::GetGlobalMutex >::create( + MutexInit(), ::osl::GetGlobalMutex()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/ole2uno.hxx b/extensions/source/ole/ole2uno.hxx new file mode 100644 index 0000000000..5fcf2fd96a --- /dev/null +++ b/extensions/source/ole/ole2uno.hxx @@ -0,0 +1,70 @@ +/* -*- 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 . + */ + +#pragma once + +#include "wincrap.hxx" + +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/registry/XRegistryKey.hpp> +#include <com/sun/star/bridge/XBridgeSupplier2.hpp> +#include <com/sun/star/bridge/ModelDependent.hpp> +#include <com/sun/star/reflection/InvocationTargetException.hpp> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/beans/UnknownPropertyException.hpp> +#include <cppuhelper/typeprovider.hxx> +#include <cppuhelper/factory.hxx> +#include <sal/types.h> +#include <typelib/typeclass.h> +#include <osl/diagnose.h> +#include <osl/mutex.hxx> +#include <rtl/process.h> +#include <rtl/uuid.h> + +#define UNO_2_OLE_EXCEPTIONCODE 1001 +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::registry; +using namespace com::sun::star::reflection; +using namespace com::sun::star::beans; +using namespace osl; + +VARTYPE getVarType(const Any& val); +/* creates a Type object for a given type name. + + The function returns false if the name does not represent + a valid type. +*/ +bool getType(BSTR name, Type& type); +void o2u_attachCurrentThread(); + +class BridgeRuntimeError +{ +public: + explicit BridgeRuntimeError(const OUString& sMessage) + : message(sMessage) + { + } + OUString message; +}; + +Mutex* getBridgeMutex(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/oleautobridge.component b/extensions/source/ole/oleautobridge.component new file mode 100644 index 0000000000..09f7621c23 --- /dev/null +++ b/extensions/source/ole/oleautobridge.component @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + prefix="oleautobridge" xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.ole.OleClient"> + <service name="com.sun.star.bridge.OleObjectFactory"/> + <service name="com.sun.star.bridge.oleautomation.Factory"/> + </implementation> + <implementation name="com.sun.star.comp.ole.OleConverter2"> + <service name="com.sun.star.bridge.OleBridgeSupplier2"/> + <service name="com.sun.star.bridge.oleautomation.BridgeSupplier"/> + </implementation> + <implementation name="com.sun.star.comp.ole.OleConverterVar1"> + <service name="com.sun.star.bridge.OleBridgeSupplierVar1"/> + </implementation> + <implementation name="com.sun.star.comp.ole.OleServer"> + <service name="com.sun.star.bridge.OleApplicationRegistration"/> + <service name="com.sun.star.bridge.oleautomation.ApplicationRegistration"/> + </implementation> +</component> diff --git a/extensions/source/ole/oledll.cxx b/extensions/source/ole/oledll.cxx new file mode 100644 index 0000000000..9ed73a3304 --- /dev/null +++ b/extensions/source/ole/oledll.cxx @@ -0,0 +1,69 @@ +/* -*- 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 . + */ + +#define STRICT +#define _WIN32_DCOM + +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wall" +#pragma clang diagnostic ignored "-Wattributes" +#pragma clang diagnostic ignored "-Wdelete-incomplete" +#pragma clang diagnostic ignored "-Wextra" +#pragma clang diagnostic ignored "-Wint-to-pointer-cast" +#pragma clang diagnostic ignored "-Winvalid-noreturn" +#pragma clang diagnostic ignored "-Wmicrosoft" +#pragma clang diagnostic ignored "-Wnon-pod-varargs" +#endif + +#include <atlbase.h> +static CComModule _Module; +#include <atlcom.h> + +#if defined __clang__ +#pragma clang diagnostic pop +#endif + +BEGIN_OBJECT_MAP(ObjectMap) +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#endif +END_OBJECT_MAP() +#if defined __clang__ +#pragma clang diagnostic pop +#endif + +// DLL Entry Point + +extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) +{ + if (dwReason == DLL_PROCESS_ATTACH) + { + _Module.Init(ObjectMap, hInstance); + DisableThreadLibraryCalls(hInstance); + } + else if (dwReason == DLL_PROCESS_DETACH) + { + _Module.Term(); + } + return TRUE; // ok +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/oleobjw.cxx b/extensions/source/ole/oleobjw.cxx new file mode 100644 index 0000000000..85f410c546 --- /dev/null +++ b/extensions/source/ole/oleobjw.cxx @@ -0,0 +1,2513 @@ +/* -*- 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 "ole2uno.hxx" +#include <sal/log.hxx> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <osl/diagnose.h> +#include <osl/doublecheckedlocking.h> +#include <osl/thread.h> + +#include <memory> +#include <string_view> +#include <com/sun/star/script/CannotConvertException.hpp> +#include <com/sun/star/script/FailReason.hpp> +#include <com/sun/star/beans/XMaterialHolder.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/script/XInvocation.hpp> +#include <com/sun/star/bridge/ModelDependent.hpp> + +#include <com/sun/star/bridge/oleautomation/NamedArgument.hpp> +#include <com/sun/star/bridge/oleautomation/PropertyPutArgument.hpp> +#include <cppuhelper/exc_hlp.hxx> + +#include <typelib/typedescription.hxx> +#include <rtl/uuid.h> +#include <rtl/ustring.hxx> + +#include "jscriptclasses.hxx" + +#include "oleobjw.hxx" +#include "unoobjw.hxx" +#include <stdio.h> +using namespace osl; +using namespace cppu; +using namespace com::sun::star::script; +using namespace com::sun::star::lang; +using namespace com::sun::star::bridge; +using namespace com::sun::star::bridge::oleautomation; +using namespace com::sun::star::bridge::ModelDependent; +using namespace ::com::sun::star; + + +#define JSCRIPT_ID_PROPERTY L"_environment" +#define JSCRIPT_ID L"jscript" + +// key: XInterface pointer created by Invocation Adapter Factory +// value: XInterface pointer to the wrapper class. +// Entries to the map are made within +// Any createOleObjectWrapper(IUnknown* pUnknown, const Type& aType); +// Entries are being deleted if the wrapper class's destructor has been +// called. +// Before UNO object is wrapped to COM object this map is checked +// to see if the UNO object is already a wrapper. +std::unordered_map<sal_uIntPtr, sal_uIntPtr> AdapterToWrapperMap; +// key: XInterface of the wrapper object. +// value: XInterface of the Interface created by the Invocation Adapter Factory. +// A COM wrapper is responsible for removing the corresponding entry +// in AdapterToWrapperMap if it is being destroyed. Because the wrapper does not +// know about its adapted interface it uses WrapperToAdapterMap to get the +// adapted interface which is then used to locate the entry in AdapterToWrapperMap. +std::unordered_map<sal_uIntPtr,sal_uIntPtr> WrapperToAdapterMap; + +std::unordered_map<sal_uIntPtr, WeakReference<XInterface> > ComPtrToWrapperMap; + +IUnknownWrapper::IUnknownWrapper( Reference<XMultiServiceFactory> const & xFactory, + sal_uInt8 unoWrapperClass, sal_uInt8 comWrapperClass): + UnoConversionUtilities<IUnknownWrapper>( xFactory, unoWrapperClass, comWrapperClass), + m_pxIdlClass( nullptr), m_eJScript( JScriptUndefined), + m_bComTlbIndexInit(false), m_bHasDfltMethod(false), m_bHasDfltProperty(false) +{ +} + + +IUnknownWrapper::~IUnknownWrapper() +{ + o2u_attachCurrentThread(); + MutexGuard guard(getBridgeMutex()); + XInterface * xIntRoot = static_cast<OWeakObject *>(this); +#if OSL_DEBUG_LEVEL > 0 + acquire(); // make sure we don't delete us twice because of Reference + OSL_ASSERT( Reference<XInterface>( static_cast<XWeak*>(this), UNO_QUERY).get() == xIntRoot ); +#endif + + // remove entries in global maps + auto it= WrapperToAdapterMap.find( reinterpret_cast<sal_uIntPtr>(xIntRoot)); + if( it != WrapperToAdapterMap.end()) + { + sal_uIntPtr adapter= it->second; + + AdapterToWrapperMap.erase( adapter); + WrapperToAdapterMap.erase( it); + } + + auto it_c= ComPtrToWrapperMap.find( reinterpret_cast<sal_uIntPtr>(m_spUnknown.p)); + if(it_c != ComPtrToWrapperMap.end()) + ComPtrToWrapperMap.erase(it_c); +} + +Any IUnknownWrapper::queryInterface(const Type& t) +{ + if (t == cppu::UnoType<XDefaultMethod>::get() && !m_bHasDfltMethod ) + return Any(); + if (t == cppu::UnoType<XDefaultProperty>::get() && !m_bHasDfltProperty ) + return Any(); + if ( ( t == cppu::UnoType<XInvocation>::get() || t == cppu::UnoType<XAutomationInvocation>::get() ) && !m_spDispatch) + return Any(); + // XDirectInvocation seems to be an oracle replacement for XAutomationInvocation, however it is flawed especially wrt. assumptions about whether to invoke a + // Put or Get property, the implementation code has no business guessing that, it's up to the caller to decide that. Worse XDirectInvocation duplicates lots of code. + // XAutomationInvocation provides separate calls for put& get + // properties. Note: Currently the basic runtime doesn't call put properties directly, it should... after all the basic runtime should know whether it is calling a put or get property. + // For the moment for ease of merging we will let the XDirectInvoke and XAuthomationInvocation interfaces stay side by side (and for the moment at least I would prefer the basic + // runtime to call XAutomationInvocation instead of XDirectInvoke + return WeakImplHelper<XBridgeSupplier2, + XInitialization, XAutomationObject, XDefaultProperty, XDefaultMethod, XDirectInvocation, XAutomationInvocation >::queryInterface(t); +} + +Reference<XIntrospectionAccess> SAL_CALL IUnknownWrapper::getIntrospection() +{ + Reference<XIntrospectionAccess> ret; + + return ret; +} + +Any SAL_CALL IUnknownWrapper::invokeGetProperty( const OUString& aPropertyName, const Sequence< Any >& aParams, Sequence< sal_Int16 >& aOutParamIndex, Sequence< Any >& aOutParam ) +{ + Any aResult; + try + { + o2u_attachCurrentThread(); + ITypeInfo * pInfo = getTypeInfo(); + FuncDesc aDescGet(pInfo); + FuncDesc aDescPut(pInfo); + VarDesc aVarDesc(pInfo); + getPropDesc(aPropertyName, & aDescGet, & aDescPut, & aVarDesc); + if ( !aDescGet ) + { + OUString msg("[automation bridge]Property \"" + aPropertyName + + "\" is not supported"); + throw UnknownPropertyException(msg); + } + aResult = invokeWithDispIdComTlb( aDescGet, aPropertyName, aParams, aOutParamIndex, aOutParam ); + } + catch ( const Exception& e ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException("[automation bridge] unexpected exception in " + "IUnknownWrapper::invokeGetProperty ! Message : \n " + + e.Message, + nullptr, anyEx ); + } + return aResult; +} + +Any SAL_CALL IUnknownWrapper::invokePutProperty( const OUString& aPropertyName, const Sequence< Any >& aParams, Sequence< sal_Int16 >& aOutParamIndex, Sequence< Any >& aOutParam ) +{ + Any aResult; + try + { + o2u_attachCurrentThread(); + ITypeInfo * pInfo = getTypeInfo(); + FuncDesc aDescGet(pInfo); + FuncDesc aDescPut(pInfo); + VarDesc aVarDesc(pInfo); + getPropDesc(aPropertyName, & aDescGet, & aDescPut, & aVarDesc); + if ( !aDescPut ) + { + OUString msg("[automation bridge]Property \"" + aPropertyName + + "\" is not supported"); + throw UnknownPropertyException(msg); + } + aResult = invokeWithDispIdComTlb( aDescPut, aPropertyName, aParams, aOutParamIndex, aOutParam ); + } + catch ( const Exception& e ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException("[automation bridge] unexpected exception in " + "IUnknownWrapper::invokePutProperty ! Message : \n" + + e.Message, + nullptr, anyEx ); + } + return aResult; +} + + +Any SAL_CALL IUnknownWrapper::invoke( const OUString& aFunctionName, + const Sequence< Any >& aParams, Sequence< sal_Int16 >& aOutParamIndex, + Sequence< Any >& aOutParam ) +{ + if ( ! m_spDispatch ) + { + throw RuntimeException( + "[automation bridge] The object does not have an IDispatch interface"); + } + + Any ret; + + try + { + o2u_attachCurrentThread(); + + TypeDescription methodDesc; + getMethodInfo(aFunctionName, methodDesc); + if( methodDesc.is()) + { + ret = invokeWithDispIdUnoTlb(aFunctionName, + aParams, + aOutParamIndex, + aOutParam); + } + else + { + ret= invokeWithDispIdComTlb( aFunctionName, + aParams, + aOutParamIndex, + aOutParam); + } + } + catch (const IllegalArgumentException &) + { + throw; + } + catch (const CannotConvertException &) + { + throw; + } + catch (const BridgeRuntimeError & e) + { + throw RuntimeException(e.message); + } + catch (const Exception & e) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException("[automation bridge] unexpected exception in " + "IUnknownWrapper::invoke ! Message : \n" + + e.Message, + nullptr, anyEx ); + + } + catch(...) + { + throw RuntimeException("[automation bridge] unexpected exception in " + "IUnknownWrapper::Invoke !"); + } + return ret; +} + +void SAL_CALL IUnknownWrapper::setValue( const OUString& aPropertyName, + const Any& aValue ) +{ + if ( ! m_spDispatch ) + { + throw RuntimeException( + "[automation bridge] The object does not have an IDispatch interface"); + } + try + { + o2u_attachCurrentThread(); + + ITypeInfo * pInfo = getTypeInfo(); + FuncDesc aDescGet(pInfo); + FuncDesc aDescPut(pInfo); + VarDesc aVarDesc(pInfo); + getPropDesc(aPropertyName, & aDescGet, & aDescPut, & aVarDesc); + //check if there is such a property at all or if it is read only + if ( ! aDescPut && ! aDescGet && ! aVarDesc) + { + OUString msg("[automation bridge]Property \"" + aPropertyName + + "\" is not supported"); + throw UnknownPropertyException(msg); + } + + if ( (! aDescPut && aDescGet) + || (aVarDesc && aVarDesc->wVarFlags == VARFLAG_FREADONLY) ) + { + //read-only + SAL_WARN( "extensions.olebridge", "[automation bridge] Property " << aPropertyName << " is read-only"); + // ignore silently + return; + } + + HRESULT hr= S_OK; + DISPPARAMS dispparams; + CComVariant varArg; + CComVariant varRefArg; + CComVariant varResult; + ExcepInfo excepinfo; + unsigned int uArgErr; + + // converting UNO value to OLE variant + DISPID dispidPut= DISPID_PROPERTYPUT; + dispparams.rgdispidNamedArgs = &dispidPut; + dispparams.cArgs = 1; + dispparams.cNamedArgs = 1; + dispparams.rgvarg = & varArg; + + OSL_ASSERT(aDescPut || aVarDesc); + + VARTYPE vt = 0; + DISPID dispid = 0; + INVOKEKIND invkind = INVOKE_PROPERTYPUT; + //determine the expected type, dispid, invoke kind (DISPATCH_PROPERTYPUT, + //DISPATCH_PROPERTYPUTREF) + if (aDescPut) + { + vt = getElementTypeDesc(& aDescPut->lprgelemdescParam[0].tdesc); + dispid = aDescPut->memid; + invkind = aDescPut->invkind; + } + else + { + vt = getElementTypeDesc( & aVarDesc->elemdescVar.tdesc); + dispid = aVarDesc->memid; + if (vt == VT_UNKNOWN || vt == VT_DISPATCH || + (vt & VT_ARRAY) || (vt & VT_BYREF)) + { + invkind = INVOKE_PROPERTYPUTREF; + } + } + + // convert the uno argument + if (vt & VT_BYREF) + { + anyToVariant( & varRefArg, aValue, ::sal::static_int_cast< VARTYPE, int >( vt ^ VT_BYREF ) ); + varArg.vt = vt; + if( (vt & VT_TYPEMASK) == VT_VARIANT) + varArg.byref = & varRefArg; + else if ((vt & VT_TYPEMASK) == VT_DECIMAL) + varArg.byref = & varRefArg.decVal; + else + varArg.byref = & varRefArg.byref; + } + else + { + anyToVariant(& varArg, aValue, vt); + } + // call to IDispatch + hr = m_spDispatch->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, ::sal::static_int_cast< WORD, INVOKEKIND >( invkind ), + &dispparams, & varResult, & excepinfo, &uArgErr); + + // lookup error code + switch (hr) + { + case S_OK: + break; + case DISP_E_BADPARAMCOUNT: + throw RuntimeException(); + break; + case DISP_E_BADVARTYPE: + throw RuntimeException(); + break; + case DISP_E_EXCEPTION: + throw InvocationTargetException(); + break; + case DISP_E_MEMBERNOTFOUND: + throw UnknownPropertyException(); + break; + case DISP_E_NONAMEDARGS: + throw RuntimeException(); + break; + case DISP_E_OVERFLOW: + throw CannotConvertException("call to OLE object failed", static_cast<XInterface*>( + static_cast<XWeak*>(this)), TypeClass_UNKNOWN, FailReason::OUT_OF_RANGE, uArgErr); + break; + case DISP_E_PARAMNOTFOUND: + throw IllegalArgumentException("call to OLE object failed", static_cast<XInterface*>( + static_cast<XWeak*>(this)), ::sal::static_int_cast< sal_Int16, unsigned int >( uArgErr )) ; + break; + case DISP_E_TYPEMISMATCH: + throw CannotConvertException("call to OLE object failed", static_cast<XInterface*>( + static_cast<XWeak*>(this)), TypeClass_UNKNOWN, FailReason::UNKNOWN, ::sal::static_int_cast< sal_Int16, unsigned int >( uArgErr )); + break; + case DISP_E_UNKNOWNINTERFACE: + throw RuntimeException(); + break; + case DISP_E_UNKNOWNLCID: + throw RuntimeException(); + break; + case DISP_E_PARAMNOTOPTIONAL: + throw CannotConvertException("call to OLE object failed",static_cast<XInterface*>( + static_cast<XWeak*>(this)) , TypeClass_UNKNOWN, FailReason::NO_DEFAULT_AVAILABLE, uArgErr); + break; + default: + throw RuntimeException(); + break; + } + } + catch (const CannotConvertException &) + { + throw; + } + catch (const UnknownPropertyException &) + { + throw; + } + catch (const BridgeRuntimeError& e) + { + throw RuntimeException(e.message); + } + catch (const Exception & e) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException("[automation bridge] unexpected exception in " + "IUnknownWrapper::setValue ! Message : \n" + + e.Message, + nullptr, anyEx ); + + } + catch (...) + { + throw RuntimeException( + "[automation bridge] unexpected exception in " + "IUnknownWrapper::setValue !"); + } +} + +Any SAL_CALL IUnknownWrapper::getValue( const OUString& aPropertyName ) +{ + if ( ! m_spDispatch ) + { + throw RuntimeException( + "[automation bridge] The object does not have an IDispatch interface"); + } + Any ret; + try + { + o2u_attachCurrentThread(); + ITypeInfo * pInfo = getTypeInfo(); + // I was going to implement an XServiceInfo interface to allow the type + // of the automation object to be exposed... but it seems + // from looking at comments in the code that it is possible for + // this object to actually wrap a UNO object ( I guess if automation is + // used from MSO to create Openoffice objects ) Therefore, those objects + // will more than likely already have their own XServiceInfo interface. + // Instead here I chose a name that should be illegal both in COM and + // UNO ( from an IDL point of view ) therefore I think this is a safe + // hack + if ( aPropertyName == "$GetTypeName" ) + { + if ( pInfo && m_sTypeName.getLength() == 0 ) + { + m_sTypeName = "IDispatch"; + CComBSTR sName; + + if ( SUCCEEDED( pInfo->GetDocumentation( -1, &sName, nullptr, nullptr, nullptr ) ) ) + { + OUString sTmp( o3tl::toU(LPCOLESTR(sName))); + if ( sTmp.startsWith("_") ) + sTmp = sTmp.copy(1); + // do we own the memory for pTypeLib, msdn doc is vague + // I'll assume we do + CComPtr< ITypeLib > pTypeLib; + unsigned int index; + if ( SUCCEEDED( pInfo->GetContainingTypeLib( &pTypeLib.p, &index )) ) + { + if ( SUCCEEDED( pTypeLib->GetDocumentation( -1, &sName, nullptr, nullptr, nullptr ) ) ) + { + OUString sLibName( o3tl::toU(LPCOLESTR(sName))); + m_sTypeName = sLibName + "." + sTmp; + + } + } + } + + } + ret <<= m_sTypeName; + return ret; + } + FuncDesc aDescGet(pInfo); + FuncDesc aDescPut(pInfo); + VarDesc aVarDesc(pInfo); + getPropDesc(aPropertyName, & aDescGet, & aDescPut, & aVarDesc); + if ( ! aDescGet && ! aDescPut && ! aVarDesc) + { + //property not found + OUString msg("[automation bridge]Property \"" + aPropertyName + + "\" is not supported"); + throw UnknownPropertyException(msg); + } + // write-only should not be possible + OSL_ASSERT( aDescGet || ! aDescPut); + + HRESULT hr; + DISPPARAMS dispparams = {nullptr, nullptr, 0, 0}; + CComVariant varResult; + ExcepInfo excepinfo; + unsigned int uArgErr; + DISPID dispid; + if (aDescGet) + dispid = aDescGet->memid; + else if (aVarDesc) + dispid = aVarDesc->memid; + else + dispid = aDescPut->memid; + + hr = m_spDispatch->Invoke(dispid, + IID_NULL, + LOCALE_USER_DEFAULT, + DISPATCH_PROPERTYGET, + &dispparams, + &varResult, + &excepinfo, + &uArgErr); + + // converting return value and out parameter back to UNO + if (hr == S_OK) + { + // If the com object implements uno interfaces then we have + // to convert the attribute into the expected type. + TypeDescription attrInfo; + getAttributeInfo(aPropertyName, attrInfo); + if( attrInfo.is() ) + variantToAny( &varResult, ret, Type( attrInfo.get()->pWeakRef)); + else + variantToAny(&varResult, ret); + } + + // lookup error code + switch (hr) + { + case S_OK: + break; + case DISP_E_BADPARAMCOUNT: + case DISP_E_BADVARTYPE: + case DISP_E_EXCEPTION: + throw RuntimeException(OUString(o3tl::toU(excepinfo.bstrDescription))); + break; + case DISP_E_MEMBERNOTFOUND: + throw UnknownPropertyException(OUString(o3tl::toU(excepinfo.bstrDescription))); + break; + default: + throw RuntimeException(OUString(o3tl::toU(excepinfo.bstrDescription))); + break; + } + } + catch ( const UnknownPropertyException& ) + { + throw; + } + catch (const BridgeRuntimeError& e) + { + throw RuntimeException(e.message); + } + catch (const Exception & e) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException("[automation bridge] unexpected exception in " + "IUnknownWrapper::getValue ! Message : \n" + + e.Message, + nullptr, anyEx ); + } + catch (...) + { + throw RuntimeException( + "[automation bridge] unexpected exception in " + "IUnknownWrapper::getValue !"); + } + return ret; +} + +sal_Bool SAL_CALL IUnknownWrapper::hasMethod( const OUString& aName ) +{ + if ( ! m_spDispatch ) + { + throw RuntimeException( + "[automation bridge] The object does not have an IDispatch interface"); + } + bool ret = false; + + try + { + o2u_attachCurrentThread(); + ITypeInfo* pInfo = getTypeInfo(); + FuncDesc aDesc(pInfo); + getFuncDesc(aName, & aDesc); + // Automation properties can have arguments. Those are treated as methods and + //are called through XInvocation::invoke. + if ( ! aDesc) + { + FuncDesc aDescGet(pInfo); + FuncDesc aDescPut(pInfo); + VarDesc aVarDesc(pInfo); + getPropDesc( aName, & aDescGet, & aDescPut, & aVarDesc); + if ((aDescGet && aDescGet->cParams > 0) + || (aDescPut && aDescPut->cParams > 0)) + ret = true; + } + else + ret = true; + } + catch (const BridgeRuntimeError& e) + { + throw RuntimeException(e.message); + } + catch (const Exception & e) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException("[automation bridge] unexpected exception in " + "IUnknownWrapper::hasMethod ! Message : \n" + + e.Message, + nullptr, anyEx ); + } + catch (...) + { + throw RuntimeException("[automation bridge] unexpected exception in " + "IUnknownWrapper::hasMethod !"); + } + return ret; +} + +sal_Bool SAL_CALL IUnknownWrapper::hasProperty( const OUString& aName ) +{ + if ( ! m_spDispatch ) + { + throw RuntimeException("[automation bridge] The object does not have an " + "IDispatch interface"); + } + bool ret = false; + try + { + o2u_attachCurrentThread(); + + ITypeInfo * pInfo = getTypeInfo(); + FuncDesc aDescGet(pInfo); + FuncDesc aDescPut(pInfo); + VarDesc aVarDesc(pInfo); + getPropDesc(aName, & aDescGet, & aDescPut, & aVarDesc); + + // we should probably just check the func kind + // basic has been modified to handle properties ( 'get' ) props at + // least with parameters + // additionally you can call invoke(Get|Set)Property on the bridge + // you can determine if a property has parameter is hasMethod + // returns true for the name + if (aVarDesc + || aDescPut + || aDescGet ) + { + ret = true; + } + } + catch (const BridgeRuntimeError& e) + { + throw RuntimeException(e.message); + } + catch (const Exception & e) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException("[automation bridge] unexpected exception in " + "IUnknownWrapper::hasProperty ! Message : \n" + + e.Message, + nullptr, anyEx ); + + } + catch (...) + { + throw RuntimeException("[automation bridge] unexpected exception in " + "IUnknownWrapper::hasProperty !"); + } + return ret; +} + +Any SAL_CALL IUnknownWrapper::createBridge( const Any& modelDepObject, + const Sequence< sal_Int8 >& /*aProcessId*/, sal_Int16 sourceModelType, + sal_Int16 destModelType ) +{ + Any ret; + o2u_attachCurrentThread(); + + if ( + (sourceModelType == UNO) && + (destModelType == OLE) && + (modelDepObject.getValueTypeClass() == TypeClass_INTERFACE) + ) + { + Reference<XInterface> xInt( *static_cast<XInterface* const *>(modelDepObject.getValue())); + Reference<XInterface> xSelf( static_cast<OWeakObject*>(this)); + + if (xInt == xSelf) + { + VARIANT* pVariant = static_cast<VARIANT*>(CoTaskMemAlloc(sizeof(VARIANT))); + + VariantInit(pVariant); + if (m_bOriginalDispatch) + { + pVariant->vt = VT_DISPATCH; + pVariant->pdispVal = m_spDispatch; + pVariant->pdispVal->AddRef(); + } + else + { + pVariant->vt = VT_UNKNOWN; + pVariant->punkVal = m_spUnknown; + pVariant->punkVal->AddRef(); + } + + ret.setValue(static_cast<void*>(&pVariant), cppu::UnoType<sal_uIntPtr>::get()); + } + } + + return ret; +} +/** @internal + @exception IllegalArgumentException + @exception CannotConvertException + @exception InvocationTargetException + @RuntimeException +*/ +Any IUnknownWrapper::invokeWithDispIdUnoTlb(const OUString& sFunctionName, + const Sequence< Any >& Params, + Sequence< sal_Int16 >& OutParamIndex, + Sequence< Any >& OutParam) +{ + Any ret; + HRESULT hr= S_OK; + + sal_Int32 parameterCount= Params.getLength(); + sal_Int32 outParameterCount= 0; + typelib_InterfaceMethodTypeDescription* pMethod= nullptr; + TypeDescription methodDesc; + getMethodInfo(sFunctionName, methodDesc); + + // We need to know whether the IDispatch is from a JScript object. + // Then out and in/out parameters have to be treated differently than + // with common COM objects. + bool bJScriptObject= isJScriptObject(); + std::unique_ptr<CComVariant[]> sarParams; + std::unique_ptr<CComVariant[]> sarParamsRef; + CComVariant *pVarParams= nullptr; + CComVariant *pVarParamsRef= nullptr; + bool bConvRet= true; + + if( methodDesc.is()) + { + pMethod = reinterpret_cast<typelib_InterfaceMethodTypeDescription*>(methodDesc.get()); + parameterCount = pMethod->nParams; + // Create the Array for the array being passed in DISPPARAMS + // the array also contains the outparameter (but not the values) + if( pMethod->nParams > 0) + { + sarParams.reset(new CComVariant[ parameterCount]); + pVarParams = sarParams.get(); + } + + // Create the Array for the out an in/out parameter. These values + // are referenced by the VT_BYREF VARIANTs in DISPPARAMS. + // We need to find out the number of out and in/out parameter. + for( sal_Int32 i=0; i < parameterCount; i++) + { + if( pMethod->pParams[i].bOut) + outParameterCount++; + } + + if( !bJScriptObject) + { + sarParamsRef.reset(new CComVariant[outParameterCount]); + pVarParamsRef = sarParamsRef.get(); + // build up the parameters for IDispatch::Invoke + sal_Int32 outParamIndex=0; + int i = 0; + try + { + for( i= 0; i < parameterCount; i++) + { + // In parameter + if( pMethod->pParams[i].bIn && ! pMethod->pParams[i].bOut) + { + anyToVariant( &pVarParams[parameterCount - i -1], Params.getConstArray()[i]); + } + // Out parameter + in/out parameter + else if( pMethod->pParams[i].bOut ) + { + CComVariant var; + if(pMethod->pParams[i].bIn) + { + anyToVariant( & var,Params[i]); + pVarParamsRef[outParamIndex] = var; + } + + switch( pMethod->pParams[i].pTypeRef->eTypeClass) + { + case typelib_TypeClass_INTERFACE: + case typelib_TypeClass_STRUCT: + if( ! pMethod->pParams[i].bIn) + { + pVarParamsRef[ outParamIndex].vt= VT_DISPATCH; + pVarParamsRef[ outParamIndex].pdispVal= nullptr; + } + pVarParams[parameterCount - i -1].vt = VT_DISPATCH | VT_BYREF; + pVarParams[parameterCount - i -1].ppdispVal= &pVarParamsRef[outParamIndex].pdispVal; + break; + case typelib_TypeClass_ENUM: + case typelib_TypeClass_LONG: + case typelib_TypeClass_UNSIGNED_LONG: + if( ! pMethod->pParams[i].bIn) + { + pVarParamsRef[ outParamIndex].vt = VT_I4; + pVarParamsRef[ outParamIndex].lVal = 0; + } + pVarParams[parameterCount - i -1].vt = VT_I4 | VT_BYREF; + pVarParams[parameterCount - i -1].plVal= &pVarParamsRef[outParamIndex].lVal; + break; + case typelib_TypeClass_SEQUENCE: + if( ! pMethod->pParams[i].bIn) + { + pVarParamsRef[ outParamIndex].vt = VT_ARRAY| VT_VARIANT; + pVarParamsRef[ outParamIndex].parray= nullptr; + } + pVarParams[parameterCount - i -1].vt = VT_ARRAY| VT_BYREF | VT_VARIANT; + pVarParams[parameterCount - i -1].pparray= &pVarParamsRef[outParamIndex].parray; + break; + case typelib_TypeClass_ANY: + if( ! pMethod->pParams[i].bIn) + { + pVarParamsRef[ outParamIndex].vt = VT_EMPTY; + pVarParamsRef[ outParamIndex].lVal = 0; + } + pVarParams[parameterCount - i -1].vt = VT_VARIANT | VT_BYREF; + pVarParams[parameterCount - i -1].pvarVal = &pVarParamsRef[outParamIndex]; + break; + case typelib_TypeClass_BOOLEAN: + if( ! pMethod->pParams[i].bIn) + { + pVarParamsRef[ outParamIndex].vt = VT_BOOL; + pVarParamsRef[ outParamIndex].boolVal = 0; + } + pVarParams[parameterCount - i -1].vt = VT_BOOL| VT_BYREF; + pVarParams[parameterCount - i -1].pboolVal = + & pVarParamsRef[outParamIndex].boolVal; + break; + + case typelib_TypeClass_STRING: + if( ! pMethod->pParams[i].bIn) + { + pVarParamsRef[ outParamIndex].vt = VT_BSTR; + pVarParamsRef[ outParamIndex].bstrVal= nullptr; + } + pVarParams[parameterCount - i -1].vt = VT_BSTR| VT_BYREF; + pVarParams[parameterCount - i -1].pbstrVal= + & pVarParamsRef[outParamIndex].bstrVal; + break; + + case typelib_TypeClass_FLOAT: + if( ! pMethod->pParams[i].bIn) + { + pVarParamsRef[ outParamIndex].vt = VT_R4; + pVarParamsRef[ outParamIndex].fltVal= 0; + } + pVarParams[parameterCount - i -1].vt = VT_R4| VT_BYREF; + pVarParams[parameterCount - i -1].pfltVal = + & pVarParamsRef[outParamIndex].fltVal; + break; + case typelib_TypeClass_DOUBLE: + if( ! pMethod->pParams[i].bIn) + { + pVarParamsRef[ outParamIndex].vt = VT_R8; + pVarParamsRef[ outParamIndex].dblVal= 0; + } + pVarParams[parameterCount - i -1].vt = VT_R8| VT_BYREF; + pVarParams[parameterCount - i -1].pdblVal= + & pVarParamsRef[outParamIndex].dblVal; + break; + case typelib_TypeClass_BYTE: + if( ! pMethod->pParams[i].bIn) + { + pVarParamsRef[ outParamIndex].vt = VT_UI1; + pVarParamsRef[ outParamIndex].bVal= 0; + } + pVarParams[parameterCount - i -1].vt = VT_UI1| VT_BYREF; + pVarParams[parameterCount - i -1].pbVal= + & pVarParamsRef[outParamIndex].bVal; + break; + case typelib_TypeClass_CHAR: + case typelib_TypeClass_SHORT: + case typelib_TypeClass_UNSIGNED_SHORT: + if( ! pMethod->pParams[i].bIn) + { + pVarParamsRef[ outParamIndex].vt = VT_I2; + pVarParamsRef[ outParamIndex].iVal = 0; + } + pVarParams[parameterCount - i -1].vt = VT_I2| VT_BYREF; + pVarParams[parameterCount - i -1].piVal= + & pVarParamsRef[outParamIndex].iVal; + break; + + default: + if( ! pMethod->pParams[i].bIn) + { + pVarParamsRef[ outParamIndex].vt = VT_EMPTY; + pVarParamsRef[ outParamIndex].lVal = 0; + } + pVarParams[parameterCount - i -1].vt = VT_VARIANT | VT_BYREF; + pVarParams[parameterCount - i -1].pvarVal = + & pVarParamsRef[outParamIndex]; + } + outParamIndex++; + } // end else if + } // end for + } + catch (IllegalArgumentException & e) + { + e.ArgumentPosition = ::sal::static_int_cast< sal_Int16, int >( i ); + throw; + } + catch (CannotConvertException & e) + { + e.ArgumentIndex = i; + throw; + } + } + else // it is a JScriptObject + { + int i = 0; + try + { + for( ; i< parameterCount; i++) + { + // In parameter + if( pMethod->pParams[i].bIn && ! pMethod->pParams[i].bOut) + { + anyToVariant( &pVarParams[parameterCount - i -1], Params.getConstArray()[i]); + } + // Out parameter + in/out parameter + else if( pMethod->pParams[i].bOut ) + { + CComObject<JScriptOutParam>* pParamObject; + if( !SUCCEEDED( CComObject<JScriptOutParam>::CreateInstance( &pParamObject))) + { + throw BridgeRuntimeError( + "[automation bridge]IUnknownWrapper::" + "invokeWithDispIdUnoTlb\n" + "Could not create out parameter at index: " + + OUString::number(static_cast<sal_Int32>(i))); + } + + CComPtr<IUnknown> pUnk(pParamObject->GetUnknown()); + CComQIPtr<IDispatch> pDisp( pUnk); + + pVarParams[ parameterCount - i -1].vt= VT_DISPATCH; + pVarParams[ parameterCount - i -1].pdispVal= pDisp; + pVarParams[ parameterCount - i -1].pdispVal->AddRef(); + // if the param is in/out then put the parameter on index 0 + if( pMethod->pParams[i].bIn ) // in / out + { + CComVariant varParam; + anyToVariant( &varParam, Params.getConstArray()[i]); + CComDispatchDriver dispDriver( pDisp); + if(FAILED( dispDriver.PutPropertyByName( L"0", &varParam))) + throw BridgeRuntimeError( + "[automation bridge]IUnknownWrapper::" + "invokeWithDispIdUnoTlb\n" + "Could not set property \"0\" for the in/out " + "param!"); + + } + } + } + } + catch (IllegalArgumentException & e) + { + e.ArgumentPosition = ::sal::static_int_cast< sal_Int16, int >( i ); + throw; + } + catch (CannotConvertException & e) + { + e.ArgumentIndex = i; + throw; + } + } + } + // No type description Available, that is we have to deal with a COM component, + // that does not implements UNO interfaces ( IDispatch based) + else + { + //We should not run into this block, because invokeWithDispIdComTlb should + //have been called instead. + OSL_ASSERT(false); + } + + + CComVariant varResult; + ExcepInfo excepinfo; + unsigned int uArgErr; + DISPPARAMS dispparams= { pVarParams, nullptr, static_cast<UINT>(parameterCount), 0}; + + // Get the DISPID + FuncDesc aDesc(getTypeInfo()); + getFuncDesc(sFunctionName, & aDesc); + // invoking OLE method + hr = m_spDispatch->Invoke(aDesc->memid, + IID_NULL, + LOCALE_USER_DEFAULT, + DISPATCH_METHOD, + &dispparams, + &varResult, + &excepinfo, + &uArgErr); + + // converting return value and out parameter back to UNO + if (hr == S_OK) + { + if( outParameterCount && pMethod) + { + OutParamIndex.realloc( outParameterCount); + auto pOutParamIndex = OutParamIndex.getArray(); + OutParam.realloc( outParameterCount); + auto pOutParam = OutParam.getArray(); + sal_Int32 outIndex=0; + int i = 0; + try + { + for( ; i < parameterCount; i++) + { + if( pMethod->pParams[i].bOut ) + { + pOutParamIndex[outIndex]= static_cast<sal_Int16>(i); + Any outAny; + if( !bJScriptObject) + { + variantToAny( &pVarParamsRef[outIndex], outAny, + Type(pMethod->pParams[i].pTypeRef), false); + pOutParam[outIndex++]= outAny; + } + else //JScriptObject + { + if( pVarParams[i].vt == VT_DISPATCH) + { + CComDispatchDriver pDisp( pVarParams[i].pdispVal); + if( pDisp) + { + CComVariant varOut; + if( SUCCEEDED( pDisp.GetPropertyByName( L"0", &varOut))) + { + variantToAny( &varOut, outAny, + Type(pMethod->pParams[parameterCount - 1 - i].pTypeRef), false); + pOutParam[outParameterCount - 1 - outIndex++]= outAny; + } + else + bConvRet= false; + } + else + bConvRet= false; + } + else + bConvRet= false; + } + } + if( !bConvRet) break; + } + } + catch(IllegalArgumentException & e) + { + e.ArgumentPosition = ::sal::static_int_cast< sal_Int16, int >( i ); + throw; + } + catch(CannotConvertException & e) + { + e.ArgumentIndex = i; + throw; + } + } + // return value, no type information available + if ( bConvRet) + { + try + { + if( pMethod ) + variantToAny(&varResult, ret, Type( pMethod->pReturnTypeRef), false); + else + variantToAny(&varResult, ret, false); + } + catch (IllegalArgumentException & e) + { + e.Message = + "[automation bridge]IUnknownWrapper::invokeWithDispIdUnoTlb\n" + "Could not convert return value! \n Message: \n" + e.Message; + throw; + } + catch (CannotConvertException & e) + { + e.Message = + "[automation bridge]IUnknownWrapper::invokeWithDispIdUnoTlb\n" + "Could not convert return value! \n Message: \n" + e.Message; + throw; + } + } + } + + if( !bConvRet) // conversion of return or out parameter failed + throw CannotConvertException("Call to COM object failed. Conversion of return or out value failed", + Reference<XInterface>( static_cast<XWeak*>(this), UNO_QUERY ), TypeClass_UNKNOWN, + FailReason::UNKNOWN, 0);// lookup error code + // conversion of return or out parameter failed + switch (hr) + { + case S_OK: + break; + case DISP_E_BADPARAMCOUNT: + throw IllegalArgumentException(); + break; + case DISP_E_BADVARTYPE: + throw RuntimeException(); + break; + case DISP_E_EXCEPTION: + throw InvocationTargetException(); + break; + case DISP_E_MEMBERNOTFOUND: + throw IllegalArgumentException(); + break; + case DISP_E_NONAMEDARGS: + throw IllegalArgumentException(); + break; + case DISP_E_OVERFLOW: + throw CannotConvertException("call to OLE object failed", static_cast<XInterface*>( + static_cast<XWeak*>(this)), TypeClass_UNKNOWN, FailReason::OUT_OF_RANGE, uArgErr); + break; + case DISP_E_PARAMNOTFOUND: + throw IllegalArgumentException("call to OLE object failed", static_cast<XInterface*>( + static_cast<XWeak*>(this)), ::sal::static_int_cast< sal_Int16, unsigned int >( uArgErr )); + break; + case DISP_E_TYPEMISMATCH: + throw CannotConvertException("call to OLE object failed",static_cast<XInterface*>( + static_cast<XWeak*>(this)) , TypeClass_UNKNOWN, FailReason::UNKNOWN, uArgErr); + break; + case DISP_E_UNKNOWNINTERFACE: + throw RuntimeException() ; + break; + case DISP_E_UNKNOWNLCID: + throw RuntimeException() ; + break; + case DISP_E_PARAMNOTOPTIONAL: + throw CannotConvertException("call to OLE object failed", static_cast<XInterface*>( + static_cast<XWeak*>(this)), TypeClass_UNKNOWN, FailReason::NO_DEFAULT_AVAILABLE, uArgErr); + break; + default: + throw RuntimeException(); + break; + } + + return ret; +} + + +// XInitialization +void SAL_CALL IUnknownWrapper::initialize( const Sequence< Any >& aArguments ) +{ + // 1.parameter is IUnknown + // 2.parameter is a boolean which indicates if the COM pointer was an IUnknown or IDispatch + // 3.parameter is a Sequence<Type> + o2u_attachCurrentThread(); + OSL_ASSERT(aArguments.getLength() == 3); + + m_spUnknown= *static_cast<IUnknown* const *>(aArguments[0].getValue()); + m_spUnknown.QueryInterface( & m_spDispatch.p); + + aArguments[1] >>= m_bOriginalDispatch; + aArguments[2] >>= m_seqTypes; + + ITypeInfo* pType = nullptr; + try + { + // a COM object implementation that has no TypeInfo is still a legal COM object; + // such objects can at least be transported through UNO using the bridge + // so we should allow to create wrappers for them as well + pType = getTypeInfo(); + } + catch( const BridgeRuntimeError& ) + {} + catch( const Exception& ) + {} + + if ( pType ) + { + try + { + // Get Default member + CComBSTR defaultMemberName; + if ( SUCCEEDED( pType->GetDocumentation(0, &defaultMemberName, nullptr, nullptr, nullptr ) ) ) + { + OUString usName(o3tl::toU(LPCOLESTR(defaultMemberName))); + FuncDesc aDescGet(pType); + FuncDesc aDescPut(pType); + VarDesc aVarDesc(pType); + // see if this is a property first ( more likely to be a property then a method ) + getPropDesc( usName, & aDescGet, & aDescPut, & aVarDesc); + + if ( !aDescGet && !aDescPut ) + { + getFuncDesc( usName, &aDescGet ); + if ( !aDescGet ) + throw BridgeRuntimeError( "[automation bridge]IUnknownWrapper::initialize() Failed to get Function or Property desc. for " + usName ); + } + // now for some funny heuristics to make basic understand what to do + // a single aDescGet ( that doesn't take any params ) would be + // a read only ( defaultmember ) property e.g. this object + // should implement XDefaultProperty + // a single aDescGet ( that *does* ) take params is basically a + // default method e.g. implement XDefaultMethod + + // a DescPut ( I guess we only really support a default param with '1' param ) as a setValue ( but I guess we can leave it through, the object will fail if we don't get it right anyway ) + if ( aDescPut || ( aDescGet && aDescGet->cParams == 0 ) ) + m_bHasDfltProperty = true; + if ( aDescGet->cParams > 0 ) + m_bHasDfltMethod = true; + if ( m_bHasDfltProperty || m_bHasDfltMethod ) + m_sDefaultMember = usName; + } + } + catch ( const BridgeRuntimeError & e ) + { + throw RuntimeException( e.message ); + } + catch( const Exception& e ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( + "[automation bridge] unexpected exception in IUnknownWrapper::initialize() error message: \n" + e.Message, + nullptr, anyEx ); + } + } +} + + +// XDirectInvocation +uno::Any SAL_CALL IUnknownWrapper::directInvoke( const OUString& aName, const uno::Sequence< uno::Any >& aParams ) +{ + Any aResult; + + if ( !m_spDispatch ) + { + throw RuntimeException( + "[automation bridge] The object does not have an IDispatch interface"); + } + + o2u_attachCurrentThread(); + DISPID dispid; + if ( !getDispid( aName, &dispid ) ) + throw IllegalArgumentException( + "[automation bridge] The object does not have a function or property " + + aName, Reference<XInterface>(), 0); + + CComVariant varResult; + ExcepInfo excepinfo; + unsigned int uArgErr = 0; + INVOKEKIND pInvkinds[2]; + pInvkinds[0] = INVOKE_FUNC; + pInvkinds[1] = aParams.getLength() ? INVOKE_PROPERTYPUT : INVOKE_PROPERTYGET; + HRESULT hInvRes = E_FAIL; + + // try Invoke first, if it does not work, try put/get property + for ( sal_Int32 nStep = 0; FAILED( hInvRes ) && nStep < 2; nStep++ ) + { + DISPPARAMS dispparams = {nullptr, nullptr, 0, 0}; + + std::unique_ptr<DISPID[]> arDispidNamedArgs; + std::unique_ptr<CComVariant[]> ptrArgs; + std::unique_ptr<CComVariant[]> ptrRefArgs; // referenced arguments + CComVariant * arArgs = nullptr; + CComVariant * arRefArgs = nullptr; + + dispparams.cArgs = aParams.getLength(); + + // Determine the number of named arguments + for ( uno::Any const & any : aParams ) + if ( any.getValueType() == cppu::UnoType<NamedArgument>::get() ) + dispparams.cNamedArgs ++; + + // fill the named arguments + if ( dispparams.cNamedArgs > 0 + && ( dispparams.cNamedArgs != 1 || pInvkinds[nStep] != INVOKE_PROPERTYPUT ) ) + { + int nSizeAr = dispparams.cNamedArgs + 1; + if ( pInvkinds[nStep] == INVOKE_PROPERTYPUT ) + nSizeAr = dispparams.cNamedArgs; + + std::unique_ptr<OLECHAR*[]> saNames(new OLECHAR*[nSizeAr]); + OLECHAR ** pNames = saNames.get(); + pNames[0] = const_cast<OLECHAR*>(o3tl::toW(aName.getStr())); + + int cNamedArg = 0; + for ( size_t nInd = 0; nInd < dispparams.cArgs; nInd++ ) + { + if (auto v = o3tl::tryAccess<NamedArgument>(aParams[nInd])) + { + const NamedArgument& arg = *v; + + //We put the parameter names in reverse order into the array, + //so we can use the DISPID array for DISPPARAMS::rgdispidNamedArgs + //The first name in the array is the method name + pNames[nSizeAr - 1 - cNamedArg++] = const_cast<OLECHAR*>(o3tl::toW(arg.Name.getStr())); + } + } + + arDispidNamedArgs.reset( new DISPID[nSizeAr] ); + HRESULT hr = getTypeInfo()->GetIDsOfNames( pNames, nSizeAr, arDispidNamedArgs.get() ); + if ( hr == E_NOTIMPL ) + hr = m_spDispatch->GetIDsOfNames(IID_NULL, pNames, nSizeAr, LOCALE_USER_DEFAULT, arDispidNamedArgs.get() ); + + if ( SUCCEEDED( hr ) ) + { + if ( pInvkinds[nStep] == DISPATCH_PROPERTYPUT ) + { + DISPID* arIDs = arDispidNamedArgs.get(); + arIDs[0] = DISPID_PROPERTYPUT; + dispparams.rgdispidNamedArgs = arIDs; + } + else + { + DISPID* arIDs = arDispidNamedArgs.get(); + dispparams.rgdispidNamedArgs = & arIDs[1]; + } + } + else if (hr == DISP_E_UNKNOWNNAME) + { + throw IllegalArgumentException( + "[automation bridge]One of the named arguments is wrong!", + Reference<XInterface>(), 0); + } + else + { + throw InvocationTargetException( + "[automation bridge] ITypeInfo::GetIDsOfNames returned error " + + OUString::number(static_cast<sal_Int32>(hr), 16), Reference<XInterface>(), Any()); + } + } + + //Convert arguments + ptrArgs.reset(new CComVariant[dispparams.cArgs]); + ptrRefArgs.reset(new CComVariant[dispparams.cArgs]); + arArgs = ptrArgs.get(); + arRefArgs = ptrRefArgs.get(); + + sal_Int32 nInd = 0; + try + { + sal_Int32 revIndex = 0; + for ( nInd = 0; nInd < sal_Int32(dispparams.cArgs); nInd++) + { + revIndex = dispparams.cArgs - nInd - 1; + arRefArgs[revIndex].byref = nullptr; + Any anyArg; + if ( nInd < aParams.getLength() ) + anyArg = aParams.getConstArray()[nInd]; + + // Property Put arguments + if ( anyArg.getValueType() == cppu::UnoType<PropertyPutArgument>::get() ) + { + PropertyPutArgument arg; + anyArg >>= arg; + anyArg = arg.Value; + } + // named argument + if (anyArg.getValueType() == cppu::UnoType<NamedArgument>::get()) + { + NamedArgument aNamedArgument; + anyArg >>= aNamedArgument; + anyArg = aNamedArgument.Value; + } + + if ( nInd < aParams.getLength() && anyArg.getValueTypeClass() != TypeClass_VOID ) + { + anyToVariant( &arArgs[revIndex], anyArg, VT_VARIANT ); + } + else + { + arArgs[revIndex].vt = VT_ERROR; + arArgs[revIndex].scode = DISP_E_PARAMNOTFOUND; + } + } + } + catch (IllegalArgumentException & e) + { + e.ArgumentPosition = ::sal::static_int_cast< sal_Int16, sal_Int32 >( nInd ); + throw; + } + catch (CannotConvertException & e) + { + e.ArgumentIndex = nInd; + throw; + } + + dispparams.rgvarg = arArgs; + // invoking OLE method + hInvRes = m_spDispatch->Invoke( dispid, + IID_NULL, + LOCALE_USER_DEFAULT, + ::sal::static_int_cast< WORD, INVOKEKIND >( pInvkinds[nStep] ), + &dispparams, + &varResult, + &excepinfo, + &uArgErr); + } + + // converting return value and out parameter back to UNO + if ( SUCCEEDED( hInvRes ) ) + variantToAny( &varResult, aResult, false ); + else + { + // map error codes to exceptions + OUString message; + switch ( hInvRes ) + { + case S_OK: + break; + case DISP_E_BADPARAMCOUNT: + throw IllegalArgumentException("[automation bridge] Wrong " + "number of arguments. Object returned DISP_E_BADPARAMCOUNT.", + nullptr, 0); + break; + case DISP_E_BADVARTYPE: + throw RuntimeException("[automation bridge] One or more " + "arguments have the wrong type. Object returned " + "DISP_E_BADVARTYPE.", nullptr); + break; + case DISP_E_EXCEPTION: + message = OUString::Concat("[automation bridge]: ") + + std::u16string_view(o3tl::toU(excepinfo.bstrDescription), + ::SysStringLen(excepinfo.bstrDescription)); + throw InvocationTargetException(message, Reference<XInterface>(), Any()); + break; + case DISP_E_MEMBERNOTFOUND: + message = "[automation bridge]: A function with the name \"" + + aName + "\" is not supported. Object returned " + "DISP_E_MEMBERNOTFOUND."; + throw IllegalArgumentException(message, nullptr, 0); + break; + case DISP_E_NONAMEDARGS: + throw IllegalArgumentException("[automation bridge] Object " + "returned DISP_E_NONAMEDARGS",nullptr, ::sal::static_int_cast< sal_Int16, unsigned int >( uArgErr )); + break; + case DISP_E_OVERFLOW: + throw CannotConvertException("[automation bridge] Call failed.", + static_cast<XInterface*>( + static_cast<XWeak*>(this)), TypeClass_UNKNOWN, FailReason::OUT_OF_RANGE, uArgErr); + break; + case DISP_E_PARAMNOTFOUND: + throw IllegalArgumentException("[automation bridge]Call failed." + "Object returned DISP_E_PARAMNOTFOUND.", + nullptr, ::sal::static_int_cast< sal_Int16, unsigned int >( uArgErr )); + break; + case DISP_E_TYPEMISMATCH: + throw CannotConvertException("[automation bridge] Call failed. " + "Object returned DISP_E_TYPEMISMATCH", + static_cast<XInterface*>( + static_cast<XWeak*>(this)) , TypeClass_UNKNOWN, FailReason::UNKNOWN, uArgErr); + break; + case DISP_E_UNKNOWNINTERFACE: + throw RuntimeException("[automation bridge] Call failed. " + "Object returned DISP_E_UNKNOWNINTERFACE.",nullptr); + break; + case DISP_E_UNKNOWNLCID: + throw RuntimeException("[automation bridge] Call failed. " + "Object returned DISP_E_UNKNOWNLCID.",nullptr); + break; + case DISP_E_PARAMNOTOPTIONAL: + throw CannotConvertException("[automation bridge] Call failed." + "Object returned DISP_E_PARAMNOTOPTIONAL", + static_cast<XInterface*>(static_cast<XWeak*>(this)), + TypeClass_UNKNOWN, FailReason::NO_DEFAULT_AVAILABLE, uArgErr); + break; + default: + throw RuntimeException(); + break; + } + } + + return aResult; +} + +sal_Bool SAL_CALL IUnknownWrapper::hasMember( const OUString& aName ) +{ + if ( ! m_spDispatch ) + { + throw RuntimeException( + "[automation bridge] The object does not have an IDispatch interface"); + } + + o2u_attachCurrentThread(); + DISPID dispid; + return getDispid( aName, &dispid ); +} + + +// UnoConversionUtilities -------------------------------------------------------------------------------- +Reference< XInterface > IUnknownWrapper::createUnoWrapperInstance() +{ + if( m_nUnoWrapperClass == INTERFACE_OLE_WRAPPER_IMPL) + { + Reference<XWeak> xWeak= static_cast<XWeak*>( new InterfaceOleWrapper( + m_smgr, m_nUnoWrapperClass, m_nComWrapperClass)); + return Reference<XInterface>( xWeak, UNO_QUERY); + } + else if( m_nUnoWrapperClass == UNO_OBJECT_WRAPPER_REMOTE_OPT) + { + Reference<XWeak> xWeak= static_cast<XWeak*>( new UnoObjectWrapperRemoteOpt( + m_smgr, m_nUnoWrapperClass, m_nComWrapperClass)); + return Reference<XInterface>( xWeak, UNO_QUERY); + } + else + return Reference<XInterface>(); +} +Reference<XInterface> IUnknownWrapper::createComWrapperInstance() +{ + Reference<XWeak> xWeak= static_cast<XWeak*>( new IUnknownWrapper( + m_smgr, m_nUnoWrapperClass, m_nComWrapperClass)); + return Reference<XInterface>( xWeak, UNO_QUERY); +} + + +void IUnknownWrapper::getMethodInfo(std::u16string_view sName, TypeDescription& methodInfo) +{ + TypeDescription desc= getInterfaceMemberDescOfCurrentCall(sName); + if( desc.is()) + { + typelib_TypeDescription* pMember= desc.get(); + if( pMember->eTypeClass == typelib_TypeClass_INTERFACE_METHOD ) + methodInfo= pMember; + } +} + +void IUnknownWrapper::getAttributeInfo(std::u16string_view sName, TypeDescription& attributeInfo) +{ + TypeDescription desc= getInterfaceMemberDescOfCurrentCall(sName); + if( desc.is()) + { + typelib_TypeDescription* pMember= desc.get(); + if( pMember->eTypeClass == typelib_TypeClass_INTERFACE_ATTRIBUTE ) + { + attributeInfo= reinterpret_cast<typelib_InterfaceAttributeTypeDescription*>(pMember)->pAttributeTypeRef; + } + } +} +TypeDescription IUnknownWrapper::getInterfaceMemberDescOfCurrentCall(std::u16string_view sName) +{ + TypeDescription ret; + + for( auto const & rType : std::as_const(m_seqTypes) ) + { + TypeDescription _curDesc( rType ); + _curDesc.makeComplete(); + typelib_InterfaceTypeDescription * pInterface= reinterpret_cast<typelib_InterfaceTypeDescription*>(_curDesc.get()); + if( pInterface) + { + typelib_InterfaceMemberTypeDescription* pMember= nullptr; + //find the member description of the current call + for( int j=0; j < pInterface->nAllMembers; j++) + { + typelib_TypeDescriptionReference* pTypeRefMember = pInterface->ppAllMembers[j]; + typelib_TypeDescription* pDescMember= nullptr; + TYPELIB_DANGER_GET( &pDescMember, pTypeRefMember); + + typelib_InterfaceMemberTypeDescription* pInterfaceMember= + reinterpret_cast<typelib_InterfaceMemberTypeDescription*>(pDescMember); + if( OUString( pInterfaceMember->pMemberName) == sName) + { + pMember= pInterfaceMember; + break; + } + TYPELIB_DANGER_RELEASE( pDescMember); + } + + if( pMember) + { + ret= &pMember->aBase; + TYPELIB_DANGER_RELEASE( &pMember->aBase); + } + } + if( ret.is()) + break; + } + return ret; +} + +bool IUnknownWrapper::isJScriptObject() +{ + if( m_eJScript == JScriptUndefined) + { + CComDispatchDriver disp( m_spDispatch); + if( disp) + { + CComVariant result; + if( SUCCEEDED( disp.GetPropertyByName( JSCRIPT_ID_PROPERTY, &result))) + { + if(result.vt == VT_BSTR) + { + CComBSTR name( result.bstrVal); + name.ToLower(); + if( name == CComBSTR(JSCRIPT_ID)) + m_eJScript= IsJScript; + } + } + } + if( m_eJScript == JScriptUndefined) + m_eJScript= NoJScript; + } + + return m_eJScript != NoJScript; +} + + +/** @internal + The function ultimately calls IDispatch::Invoke on the wrapped COM object. + The COM object does not implement UNO Interfaces ( via IDispatch). This + is the case when the OleObjectFactory service has been used to create a + component. + @exception IllegalArgumentException + @exception CannotConvertException + @InvocationTargetException + @RuntimeException + @BridgeRuntimeError +*/ +Any IUnknownWrapper::invokeWithDispIdComTlb(const OUString& sFuncName, + const Sequence< Any >& Params, + Sequence< sal_Int16 >& OutParamIndex, + Sequence< Any >& OutParam) +{ + // Get type info for the call. It can be a method call or property put or + // property get operation. + FuncDesc aFuncDesc(getTypeInfo()); + getFuncDescForInvoke(sFuncName, Params, & aFuncDesc); + return invokeWithDispIdComTlb( aFuncDesc, sFuncName, Params, OutParamIndex, OutParam ); +} + +Any IUnknownWrapper::invokeWithDispIdComTlb(FuncDesc& aFuncDesc, + const OUString& sFuncName, + const Sequence< Any >& Params, + Sequence< sal_Int16 >& OutParamIndex, + Sequence< Any >& OutParam) +{ + Any ret; + HRESULT result; + + DISPPARAMS dispparams = {nullptr, nullptr, 0, 0}; + CComVariant varResult; + ExcepInfo excepinfo; + unsigned int uArgErr; + sal_Int32 i = 0; + sal_Int32 nUnoArgs = Params.getLength(); + DISPID idPropertyPut = DISPID_PROPERTYPUT; + std::unique_ptr<DISPID[]> arDispidNamedArgs; + std::unique_ptr<CComVariant[]> ptrArgs; + std::unique_ptr<CComVariant[]> ptrRefArgs; // referenced arguments + CComVariant * arArgs = nullptr; + CComVariant * arRefArgs = nullptr; + sal_Int32 revIndex = 0; + + //Set the array of DISPIDs for named args if it is a property put operation. + //If there are other named arguments another array is set later on. + if (aFuncDesc->invkind == INVOKE_PROPERTYPUT + || aFuncDesc->invkind == INVOKE_PROPERTYPUTREF) + dispparams.rgdispidNamedArgs = & idPropertyPut; + + //Determine the number of named arguments + for (int iParam = 0; iParam < nUnoArgs; iParam ++) + { + const Any & curArg = Params[iParam]; + if (curArg.getValueType() == cppu::UnoType<NamedArgument>::get()) + dispparams.cNamedArgs ++; + } + //In a property put operation a property value is a named argument (DISPID_PROPERTYPUT). + //Therefore the number of named arguments is increased by one. + //Although named, the argument is not named in an actual language, such as Basic, + //therefore it is never a com.sun.star.bridge.oleautomation.NamedArgument + if (aFuncDesc->invkind == DISPATCH_PROPERTYPUT + || aFuncDesc->invkind == DISPATCH_PROPERTYPUTREF) + dispparams.cNamedArgs ++; + + //Determine the number of all arguments and named arguments + if (aFuncDesc->cParamsOpt == -1) + { + //Attribute vararg is set on this method. "Unlimited" number of args + //supported. There can be no optional or defaultvalue on any of the arguments. + dispparams.cArgs = nUnoArgs; + } + else + { + //If there are named arguments, then the dispparams.cArgs + //is the number of supplied args, otherwise it is the expected number. + if (dispparams.cNamedArgs) + dispparams.cArgs = nUnoArgs; + else + dispparams.cArgs = aFuncDesc->cParams; + } + + //check if there are not too many arguments supplied + if (::sal::static_int_cast< sal_uInt32, int >( nUnoArgs ) > dispparams.cArgs) + { + throw IllegalArgumentException( + "[automation bridge] There are too many arguments for this method", + Reference<XInterface>(), static_cast<sal_Int16>(dispparams.cArgs)); + } + + //Set up the array of DISPIDs (DISPPARAMS::rgdispidNamedArgs) + //for the named arguments. + //If there is only one named arg and if it is because of a property put + //operation, then we need not set up the DISPID array. + if (dispparams.cNamedArgs > 0 && + ! (dispparams.cNamedArgs == 1 && + (aFuncDesc->invkind == INVOKE_PROPERTYPUT || + aFuncDesc->invkind == INVOKE_PROPERTYPUTREF))) + { + //set up an array containing the member and parameter names + //which is then used in ITypeInfo::GetIDsOfNames + //First determine the size of the array of names which is passed to + //ITypeInfo::GetIDsOfNames. It must hold the method names + the named + //args. + int nSizeAr = dispparams.cNamedArgs + 1; + if (aFuncDesc->invkind == INVOKE_PROPERTYPUT + || aFuncDesc->invkind == INVOKE_PROPERTYPUTREF) + { + nSizeAr = dispparams.cNamedArgs; //counts the DISID_PROPERTYPUT + } + + std::unique_ptr<OLECHAR*[]> saNames(new OLECHAR*[nSizeAr]); + OLECHAR ** arNames = saNames.get(); + arNames[0] = const_cast<OLECHAR*>(o3tl::toW(sFuncName.getStr())); + + int cNamedArg = 0; + for (size_t iParams = 0; iParams < dispparams.cArgs; iParams ++) + { + const Any & curArg = Params[iParams]; + if (auto v = o3tl::tryAccess<NamedArgument>(curArg)) + { + const NamedArgument& arg = *v; + //We put the parameter names in reverse order into the array, + //so we can use the DISPID array for DISPPARAMS::rgdispidNamedArgs + //The first name in the array is the method name + arNames[nSizeAr - 1 - cNamedArg++] = const_cast<OLECHAR*>(o3tl::toW(arg.Name.getStr())); + } + } + + //Prepare the array of DISPIDs for ITypeInfo::GetIDsOfNames + //it must be big enough to contain the DISPIDs of the member + parameters + arDispidNamedArgs.reset(new DISPID[nSizeAr]); + HRESULT hr = getTypeInfo()->GetIDsOfNames(arNames, nSizeAr, + arDispidNamedArgs.get()); + if ( hr == E_NOTIMPL ) + hr = m_spDispatch->GetIDsOfNames(IID_NULL, arNames, nSizeAr, LOCALE_USER_DEFAULT, arDispidNamedArgs.get() ); + + if (hr == S_OK) + { + // In a "property put" operation, the property value is a named param with the + //special DISPID DISPID_PROPERTYPUT + if (aFuncDesc->invkind == DISPATCH_PROPERTYPUT + || aFuncDesc->invkind == DISPATCH_PROPERTYPUTREF) + { + //Element at index 0 in the DISPID array must be DISPID_PROPERTYPUT + //The first item in the array arDispidNamedArgs is the DISPID for + //the method. We replace it with DISPID_PROPERTYPUT. + DISPID* arIDs = arDispidNamedArgs.get(); + arIDs[0] = DISPID_PROPERTYPUT; + dispparams.rgdispidNamedArgs = arIDs; + } + else + { + //The first item in the array arDispidNamedArgs is the DISPID for + //the method. It must be removed + DISPID* arIDs = arDispidNamedArgs.get(); + dispparams.rgdispidNamedArgs = & arIDs[1]; + } + } + else if (hr == DISP_E_UNKNOWNNAME) + { + throw IllegalArgumentException( + "[automation bridge]One of the named arguments is wrong!", + Reference<XInterface>(), 0); + } + else + { + throw InvocationTargetException( + "[automation bridge] ITypeInfo::GetIDsOfNames returned error " + + OUString::number(static_cast<sal_Int32>(hr), 16), Reference<XInterface>(), Any()); + } + } + + //Convert arguments + ptrArgs.reset(new CComVariant[dispparams.cArgs]); + ptrRefArgs.reset(new CComVariant[dispparams.cArgs]); + arArgs = ptrArgs.get(); + arRefArgs = ptrRefArgs.get(); + try + { + for (i = 0; i < static_cast<sal_Int32>(dispparams.cArgs); i++) + { + revIndex= dispparams.cArgs - i -1; + arRefArgs[revIndex].byref=nullptr; + Any anyArg; + if ( i < nUnoArgs) + anyArg= Params.getConstArray()[i]; + + unsigned short paramFlags = PARAMFLAG_FOPT | PARAMFLAG_FIN; + VARTYPE varType = VT_VARIANT; + if (aFuncDesc->cParamsOpt != -1 || aFuncDesc->cParams != (i + 1)) + { + paramFlags = aFuncDesc->lprgelemdescParam[i].paramdesc.wParamFlags; + varType = getElementTypeDesc(&aFuncDesc->lprgelemdescParam[i].tdesc); + } + + // Make sure that there is a UNO parameter for every + // expected parameter. If there is no UNO parameter where the + // called function expects one, then it must be optional. Otherwise + // it's a UNO programming error. + if (i >= nUnoArgs && !(paramFlags & PARAMFLAG_FOPT)) + { + throw IllegalArgumentException( + ("ole automation bridge: The called function expects an argument at position: " + + OUString::number(i) + " (index starting at 0)."), + Reference<XInterface>(), static_cast<sal_Int16>(i)); + } + + // Property Put arguments + if (anyArg.getValueType() == cppu::UnoType<PropertyPutArgument>::get()) + { + PropertyPutArgument arg; + anyArg >>= arg; + anyArg = arg.Value; + } + // named argument + if (anyArg.getValueType() == cppu::UnoType<NamedArgument>::get()) + { + NamedArgument aNamedArgument; + anyArg >>= aNamedArgument; + anyArg = aNamedArgument.Value; + } + // out param + if (paramFlags & PARAMFLAG_FOUT && + ! (paramFlags & PARAMFLAG_FIN) ) + { + VARTYPE type = ::sal::static_int_cast< VARTYPE, int >( varType ^ VT_BYREF ); + if (i < nUnoArgs) + { + arRefArgs[revIndex].vt= type; + } + else + { + //optional arg + arRefArgs[revIndex].vt = VT_ERROR; + arRefArgs[revIndex].scode = DISP_E_PARAMNOTFOUND; + } + if( type == VT_VARIANT ) + { + arArgs[revIndex].vt= VT_VARIANT | VT_BYREF; + arArgs[revIndex].byref= &arRefArgs[revIndex]; + } + else + { + arArgs[revIndex].vt= varType; + if (type == VT_DECIMAL) + arArgs[revIndex].byref= & arRefArgs[revIndex].decVal; + else + arArgs[revIndex].byref= & arRefArgs[revIndex].byref; + } + } + // in/out + in byref params + else if (varType & VT_BYREF) + { + VARTYPE type = ::sal::static_int_cast< VARTYPE, int >( varType ^ VT_BYREF ); + CComVariant var; + + if (i < nUnoArgs && anyArg.getValueTypeClass() != TypeClass_VOID) + { + anyToVariant( & arRefArgs[revIndex], anyArg, type); + } + else if (paramFlags & PARAMFLAG_FHASDEFAULT) + { + //optional arg with default + VariantCopy( & arRefArgs[revIndex], + & aFuncDesc->lprgelemdescParam[i].paramdesc. + pparamdescex->varDefaultValue); + } + else + { + //optional arg + //e.g: call func(x) in basic : func() ' no arg supplied + OSL_ASSERT(paramFlags & PARAMFLAG_FOPT); + arRefArgs[revIndex].vt = VT_ERROR; + arRefArgs[revIndex].scode = DISP_E_PARAMNOTFOUND; + } + + // Set the converted arguments in the array which will be + // DISPPARAMS::rgvarg + // byref arg VT_XXX |VT_BYREF + arArgs[revIndex].vt = varType; + if (revIndex == 0 && aFuncDesc->invkind == INVOKE_PROPERTYPUT) + { + arArgs[revIndex] = arRefArgs[revIndex]; + } + else if (type == VT_DECIMAL) + { + arArgs[revIndex].byref= & arRefArgs[revIndex].decVal; + } + else if (type == VT_VARIANT) + { + if ( ! (paramFlags & PARAMFLAG_FOUT)) + arArgs[revIndex] = arRefArgs[revIndex]; + else + arArgs[revIndex].byref = & arRefArgs[revIndex]; + } + else + { + arArgs[revIndex].byref = & arRefArgs[revIndex].byref; + arArgs[revIndex].vt = ::sal::static_int_cast< VARTYPE, int >( arRefArgs[revIndex].vt | VT_BYREF ); + } + + } + // in parameter no VT_BYREF except for array, interfaces + else + { // void any stands for optional param + if (i < nUnoArgs && anyArg.getValueTypeClass() != TypeClass_VOID) + { + anyToVariant( & arArgs[revIndex], anyArg, varType); + } + //optional arg but no void any supplied + //Basic: obj.func() ' first parameter left out because it is optional + else if (paramFlags & PARAMFLAG_FHASDEFAULT) + { + //optional arg with default either as direct arg : VT_XXX or + VariantCopy( & arArgs[revIndex], + & aFuncDesc->lprgelemdescParam[i].paramdesc. + pparamdescex->varDefaultValue); + } + else if (paramFlags & PARAMFLAG_FOPT) + { + arArgs[revIndex].vt = VT_ERROR; + arArgs[revIndex].scode = DISP_E_PARAMNOTFOUND; + } + else + { + arArgs[revIndex].vt = VT_EMPTY; + arArgs[revIndex].lVal = 0; + } + } + } + } + catch (IllegalArgumentException & e) + { + e.ArgumentPosition = ::sal::static_int_cast< sal_Int16, sal_Int32 >( i ); + throw; + } + catch (CannotConvertException & e) + { + e.ArgumentIndex = i; + throw; + } + dispparams.rgvarg= arArgs; + // invoking OLE method + result = m_spDispatch->Invoke(aFuncDesc->memid, + IID_NULL, + LOCALE_USER_DEFAULT, + ::sal::static_int_cast< WORD, INVOKEKIND >( aFuncDesc->invkind ), + &dispparams, + &varResult, + &excepinfo, + &uArgErr); + + // converting return value and out parameter back to UNO + if (result == S_OK) + { + + // allocate space for the out param Sequence and indices Sequence + int outParamsCount= 0; // includes in/out parameter + for (int j = 0; j < aFuncDesc->cParams; j++) + { + if (aFuncDesc->lprgelemdescParam[j].paramdesc.wParamFlags & + PARAMFLAG_FOUT) + outParamsCount++; + } + + OutParamIndex.realloc(outParamsCount); + OutParam.realloc(outParamsCount); + // Convert out params + if (outParamsCount) + { + auto pOutParamIndex = OutParamIndex.getArray(); + auto pOutParam = OutParam.getArray(); + int outParamIndex=0; + for (int paramIndex = 0; paramIndex < nUnoArgs; paramIndex ++) + { + //Determine the index within the method signature + int realParamIndex = paramIndex; + int revParamIndex = dispparams.cArgs - paramIndex - 1; + if (Params[paramIndex].getValueType() + == cppu::UnoType<NamedArgument>::get()) + { + //dispparams.rgdispidNamedArgs contains the mapping from index + //of named args list to index of parameter list + realParamIndex = dispparams.rgdispidNamedArgs[revParamIndex]; + } + + // no named arg, always come before named args + if (! (aFuncDesc->lprgelemdescParam[realParamIndex].paramdesc.wParamFlags + & PARAMFLAG_FOUT)) + continue; + Any outAny; + // variantToAny is called with the "reduce range" parameter set to sal_False. + // That causes VT_I4 values not to be converted down to a "lower" type. That + // feature exist for JScript only because it only uses VT_I4 for integer types. + try + { + variantToAny( & arRefArgs[revParamIndex], outAny, false ); + } + catch (IllegalArgumentException & e) + { + e.ArgumentPosition = static_cast<sal_Int16>(paramIndex); + throw; + } + catch (CannotConvertException & e) + { + e.ArgumentIndex = paramIndex; + throw; + } + pOutParam[outParamIndex] = outAny; + pOutParamIndex[outParamIndex] = ::sal::static_int_cast< sal_Int16, int >( paramIndex ); + outParamIndex++; + } + OutParam.realloc(outParamIndex); + OutParamIndex.realloc(outParamIndex); + } + // Return value + variantToAny(&varResult, ret, false); + } + + // map error codes to exceptions + OUString message; + switch (result) + { + case S_OK: + break; + case DISP_E_BADPARAMCOUNT: + throw IllegalArgumentException("[automation bridge] Wrong " + "number of arguments. Object returned DISP_E_BADPARAMCOUNT.", + nullptr, 0); + break; + case DISP_E_BADVARTYPE: + throw RuntimeException("[automation bridge] One or more " + "arguments have the wrong type. Object returned " + "DISP_E_BADVARTYPE.", nullptr); + break; + case DISP_E_EXCEPTION: + message = OUString::Concat("[automation bridge]: ") + + std::u16string_view(o3tl::toU(excepinfo.bstrDescription), + ::SysStringLen(excepinfo.bstrDescription)); + + throw InvocationTargetException(message, Reference<XInterface>(), Any()); + break; + case DISP_E_MEMBERNOTFOUND: + message = "[automation bridge]: A function with the name \"" + + sFuncName + "\" is not supported. Object returned " + "DISP_E_MEMBERNOTFOUND."; + throw IllegalArgumentException(message, nullptr, 0); + break; + case DISP_E_NONAMEDARGS: + throw IllegalArgumentException("[automation bridge] Object " + "returned DISP_E_NONAMEDARGS",nullptr, ::sal::static_int_cast< sal_Int16, unsigned int >( uArgErr )); + break; + case DISP_E_OVERFLOW: + throw CannotConvertException("[automation bridge] Call failed.", + static_cast<XInterface*>( + static_cast<XWeak*>(this)), TypeClass_UNKNOWN, FailReason::OUT_OF_RANGE, uArgErr); + break; + case DISP_E_PARAMNOTFOUND: + throw IllegalArgumentException("[automation bridge]Call failed." + "Object returned DISP_E_PARAMNOTFOUND.", + nullptr, ::sal::static_int_cast< sal_Int16, unsigned int >( uArgErr )); + break; + case DISP_E_TYPEMISMATCH: + throw CannotConvertException("[automation bridge] Call failed. " + "Object returned DISP_E_TYPEMISMATCH", + static_cast<XInterface*>( + static_cast<XWeak*>(this)) , TypeClass_UNKNOWN, FailReason::UNKNOWN, uArgErr); + break; + case DISP_E_UNKNOWNINTERFACE: + throw RuntimeException("[automation bridge] Call failed. " + "Object returned DISP_E_UNKNOWNINTERFACE.",nullptr); + break; + case DISP_E_UNKNOWNLCID: + throw RuntimeException("[automation bridge] Call failed. " + "Object returned DISP_E_UNKNOWNLCID.",nullptr); + break; + case DISP_E_PARAMNOTOPTIONAL: + throw CannotConvertException("[automation bridge] Call failed." + "Object returned DISP_E_PARAMNOTOPTIONAL", + static_cast<XInterface*>(static_cast<XWeak*>(this)), + TypeClass_UNKNOWN, FailReason::NO_DEFAULT_AVAILABLE, uArgErr); + break; + default: + throw RuntimeException(); + break; + } + + return ret; +} + +void IUnknownWrapper::getFuncDescForInvoke(const OUString & sFuncName, + const Sequence<Any> & seqArgs, + FUNCDESC** pFuncDesc) +{ + int nUnoArgs = seqArgs.getLength(); + const Any * arArgs = seqArgs.getConstArray(); + ITypeInfo* pInfo = getTypeInfo(); + + //If the last of the positional arguments is a PropertyPutArgument + //then obtain the type info for the property put operation. + + //The property value is always the last argument, in a positional argument list + //or in a list of named arguments. A PropertyPutArgument is actually a named argument + //hence it must not be put in an extra NamedArgument structure + if (nUnoArgs > 0 && + arArgs[nUnoArgs - 1].getValueType() == cppu::UnoType<PropertyPutArgument>::get()) + { + // DISPATCH_PROPERTYPUT + FuncDesc aDescGet(pInfo); + FuncDesc aDescPut(pInfo); + VarDesc aVarDesc(pInfo); + getPropDesc(sFuncName, & aDescGet, & aDescPut, & aVarDesc); + if ( ! aDescPut) + { + throw IllegalArgumentException( + "[automation bridge] The object does not have a writeable property: " + + sFuncName, Reference<XInterface>(), 0); + } + *pFuncDesc = aDescPut.Detach(); + } + else + { // DISPATCH_METHOD + FuncDesc aFuncDesc(pInfo); + getFuncDesc(sFuncName, & aFuncDesc); + if ( ! aFuncDesc) + { + // Fallback: DISPATCH_PROPERTYGET can mostly be called as + // DISPATCH_METHOD + ITypeInfo * pTypeInfo = getTypeInfo(); + FuncDesc aDescPut(pTypeInfo); + VarDesc aVarDesc(pTypeInfo); + getPropDesc(sFuncName, & aFuncDesc, & aDescPut, & aVarDesc); + if ( ! aFuncDesc ) + { + throw IllegalArgumentException( + "[automation bridge] The object does not have a function" + " or readable property \"" + + sFuncName + "\"", Reference<XInterface>(), 0); + } + } + *pFuncDesc = aFuncDesc.Detach(); + } +} +bool IUnknownWrapper::getDispid(const OUString& sFuncName, DISPID * id) +{ + OSL_ASSERT(m_spDispatch); + LPOLESTR lpsz = const_cast<LPOLESTR> (o3tl::toW(sFuncName.getStr())); + HRESULT hr = m_spDispatch->GetIDsOfNames(IID_NULL, &lpsz, 1, LOCALE_USER_DEFAULT, id); + return hr == S_OK; +} +void IUnknownWrapper::getFuncDesc(const OUString & sFuncName, FUNCDESC ** pFuncDesc) + +{ + OSL_ASSERT( * pFuncDesc == nullptr); + buildComTlbIndex(); + typedef TLBFuncIndexMap::const_iterator cit; + //We assume there is only one entry with the function name. A property + //would have two entries. + cit itIndex= m_mapComFunc.find(sFuncName); + if (itIndex == m_mapComFunc.end()) + { + //try case insensitive with IDispatch::GetIDsOfNames + DISPID id; + if (getDispid(sFuncName, &id)) + { + CComBSTR memberName; + unsigned int pcNames=0; + // get the case sensitive name + if( SUCCEEDED(getTypeInfo()->GetNames( id, & memberName, 1, &pcNames))) + { + //get the associated index and add an entry to the map + //with the name sFuncName which differs in the casing of the letters to + //the actual name as obtained from ITypeInfo + OUString sRealName(o3tl::toU(LPCOLESTR(memberName))); + cit itOrg = m_mapComFunc.find(sRealName); + OSL_ASSERT(itOrg != m_mapComFunc.end()); + // maybe this is a property, if so we need + // to store either both id's ( put/get ) or + // just the get. Storing both is more consistent + std::pair<cit, cit> pItems = m_mapComFunc.equal_range( sRealName ); + for ( ;pItems.first != pItems.second; ++pItems.first ) + m_mapComFunc.insert( TLBFuncIndexMap::value_type ( std::make_pair(sFuncName, pItems.first->second ) )); + itIndex = + m_mapComFunc.find( sFuncName ); + } + } + } + +#if OSL_DEBUG_LEVEL >= 1 + // There must only be one entry if sFuncName represents a function or two + // if it is a property + std::pair<cit, cit> p = m_mapComFunc.equal_range(sFuncName.toAsciiLowerCase()); + int numEntries = 0; + for ( ;p.first != p.second; p.first ++, numEntries ++); + OSL_ASSERT( ! (numEntries > 3) ); +#endif + if( itIndex != m_mapComFunc.end()) + { + ITypeInfo* pType= getTypeInfo(); + FUNCDESC * pDesc = nullptr; + if (!SUCCEEDED(pType->GetFuncDesc(itIndex->second, & pDesc))) + { + throw BridgeRuntimeError("[automation bridge] Could not get " + "FUNCDESC for " + sFuncName); + } + if (pDesc->invkind == INVOKE_FUNC) + { + (*pFuncDesc) = pDesc; + } + else + { + pType->ReleaseFuncDesc(pDesc); + } + } + //else no entry found for sFuncName, pFuncDesc will not be filled in +} + +void IUnknownWrapper::getPropDesc(const OUString & sFuncName, FUNCDESC ** pFuncDescGet, + FUNCDESC** pFuncDescPut, VARDESC** pVarDesc) +{ + OSL_ASSERT( * pFuncDescGet == nullptr && * pFuncDescPut == nullptr); + buildComTlbIndex(); + typedef TLBFuncIndexMap::const_iterator cit; + std::pair<cit, cit> p = m_mapComFunc.equal_range(sFuncName); + if (p.first == m_mapComFunc.end()) + { + //try case insensitive with IDispatch::GetIDsOfNames + DISPID id; + if (getDispid(sFuncName, &id)) + { + CComBSTR memberName; + unsigned int pcNames=0; + // get the case sensitive name + if( SUCCEEDED(getTypeInfo()->GetNames( id, & memberName, 1, &pcNames))) + { + //As opposed to getFuncDesc, we do not add the value because we would + // need to find the get and set description for the property. This would + //mean to iterate over all FUNCDESCs again. + p = m_mapComFunc.equal_range(OUString(o3tl::toU(LPCOLESTR(memberName)))); + } + } + } + + for ( int i = 0 ;p.first != p.second; p.first ++, i ++) + { + // There are a maximum of two entries, property put and property get + OSL_ASSERT( ! (i > 2) ); + ITypeInfo* pType= getTypeInfo(); + FUNCDESC * pFuncDesc = nullptr; + if (SUCCEEDED( pType->GetFuncDesc(p.first->second, & pFuncDesc))) + { + if (pFuncDesc->invkind == INVOKE_PROPERTYGET) + { + (*pFuncDescGet) = pFuncDesc; + } + else if (pFuncDesc->invkind == INVOKE_PROPERTYPUT || + pFuncDesc->invkind == INVOKE_PROPERTYPUTREF) + { + //a property can have 3 entries, put, put ref, get + // If INVOKE_PROPERTYPUTREF or INVOKE_PROPERTYPUT is used + //depends on what is found first. + if ( * pFuncDescPut) + { + //we already have found one + pType->ReleaseFuncDesc(pFuncDesc); + } + else + { + (*pFuncDescPut) = pFuncDesc; + } + } + else + { + pType->ReleaseFuncDesc(pFuncDesc); + } + } + //ITypeInfo::GetFuncDesc may even provide a funcdesc for a VARDESC + // with invkind = INVOKE_FUNC. Since this function should only return + //a value for a real property (XInvokation::hasMethod, ..::hasProperty + //we need to make sure that sFuncName represents a real property. + VARDESC * pVD = nullptr; + if (SUCCEEDED(pType->GetVarDesc(p.first->second, & pVD))) + (*pVarDesc) = pVD; + } + //else no entry for sFuncName, pFuncDesc will not be filled in +} + +VARTYPE IUnknownWrapper::getUserDefinedElementType( ITypeInfo* pTypeInfo, const DWORD nHrefType ) +{ + VARTYPE _type( VT_NULL ); + if ( pTypeInfo ) + { + CComPtr<ITypeInfo> spRefInfo; + pTypeInfo->GetRefTypeInfo( nHrefType, &spRefInfo.p ); + if ( spRefInfo ) + { + TypeAttr attr( spRefInfo ); + spRefInfo->GetTypeAttr( &attr ); + if ( attr->typekind == TKIND_ENUM ) + { + // We use the type of the first enum value. + if ( attr->cVars == 0 ) + { + throw BridgeRuntimeError("[automation bridge] Could not obtain type description"); + } + VarDesc var( spRefInfo ); + spRefInfo->GetVarDesc( 0, &var ); + _type = var->lpvarValue->vt; + } + else if ( attr->typekind == TKIND_INTERFACE ) + { + _type = VT_UNKNOWN; + } + else if ( attr->typekind == TKIND_DISPATCH ) + { + _type = VT_DISPATCH; + } + else if ( attr->typekind == TKIND_ALIAS ) + { + // TKIND_ALIAS is a type that is an alias for another type. So get that alias type. + _type = getUserDefinedElementType( pTypeInfo, attr->tdescAlias.hreftype ); + } + else + { + throw BridgeRuntimeError( "[automation bridge] Unhandled user defined type." ); + } + } + } + return _type; +} + +VARTYPE IUnknownWrapper::getElementTypeDesc(const TYPEDESC *desc) +{ + VARTYPE _type( VT_NULL ); + + if (desc->vt == VT_PTR) + { + _type = getElementTypeDesc(desc->lptdesc); + _type |= VT_BYREF; + } + else if (desc->vt == VT_SAFEARRAY) + { + _type = getElementTypeDesc(desc->lptdesc); + _type |= VT_ARRAY; + } + else if (desc->vt == VT_USERDEFINED) + { + ITypeInfo* thisInfo = getTypeInfo(); //kept by this instance + _type = getUserDefinedElementType( thisInfo, desc->hreftype ); + } + else + { + _type = desc->vt; + } + return _type; +} + +void IUnknownWrapper::buildComTlbIndex() +{ + if ( ! m_bComTlbIndexInit) + { + MutexGuard guard(getBridgeMutex()); + { + if ( ! m_bComTlbIndexInit) + { + OUString sError; + ITypeInfo* pType= getTypeInfo(); + TypeAttr typeAttr(pType); + if( SUCCEEDED( pType->GetTypeAttr( &typeAttr))) + { + for( WORD i= 0; i < typeAttr->cFuncs; i++) + { + FuncDesc funcDesc(pType); + if( SUCCEEDED( pType->GetFuncDesc( i, &funcDesc))) + { + CComBSTR memberName; + unsigned int pcNames=0; + if( SUCCEEDED(pType->GetNames( funcDesc->memid, & memberName, 1, &pcNames))) + { + OUString usName(o3tl::toU(LPCOLESTR(memberName))); + m_mapComFunc.emplace(usName, i); + } + else + { + sError = "[automation bridge] IUnknownWrapper::buildComTlbIndex, " + "ITypeInfo::GetNames failed."; + } + } + else + sError = "[automation bridge] IUnknownWrapper::buildComTlbIndex, " + "ITypeInfo::GetFuncDesc failed."; + } + + //If we create an Object in JScript and a property then it + //has VARDESC instead of FUNCDESC + for (WORD i = 0; i < typeAttr->cVars; i++) + { + VarDesc varDesc(pType); + if (SUCCEEDED(pType->GetVarDesc(i, & varDesc))) + { + CComBSTR memberName; + unsigned int pcNames = 0; + if (SUCCEEDED(pType->GetNames(varDesc->memid, & memberName, 1, &pcNames))) + { + if (varDesc->varkind == VAR_DISPATCH) + { + OUString usName(o3tl::toU(LPCOLESTR(memberName))); + m_mapComFunc.emplace(usName, i); + } + } + else + { + sError = "[automation bridge] IUnknownWrapper::buildComTlbIndex, " + "ITypeInfo::GetNames failed."; + } + } + else + sError = "[automation bridge] IUnknownWrapper::buildComTlbIndex, " + "ITypeInfo::GetVarDesc failed."; + + } + } + else + sError = "[automation bridge] IUnknownWrapper::buildComTlbIndex, " + "ITypeInfo::GetTypeAttr failed."; + + if (sError.getLength()) + { + throw BridgeRuntimeError(sError); + } + + m_bComTlbIndexInit = true; + } + } + } +} + +ITypeInfo* IUnknownWrapper::getTypeInfo() +{ + if( !m_spDispatch) + { + throw BridgeRuntimeError("The object has no IDispatch interface!"); + } + + if( !m_spTypeInfo ) + { + MutexGuard guard(getBridgeMutex()); + if( ! m_spTypeInfo) + { + CComPtr< ITypeInfo > spType; + if( !SUCCEEDED( m_spDispatch->GetTypeInfo( 0, LOCALE_USER_DEFAULT, &spType.p))) + { + throw BridgeRuntimeError("[automation bridge]The dispatch object does not " + "support ITypeInfo!"); + } + + OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); + + //If this is a dual interface then TYPEATTR::typekind is usually TKIND_INTERFACE + //We need to get the type description for TKIND_DISPATCH + TypeAttr typeAttr(spType.p); + if( SUCCEEDED(spType->GetTypeAttr( &typeAttr))) + { + if (typeAttr->typekind == TKIND_INTERFACE && + typeAttr->wTypeFlags & TYPEFLAG_FDUAL) + { + HREFTYPE refDispatch; + if (!SUCCEEDED(spType->GetRefTypeOfImplType(::sal::static_int_cast< UINT, int >( -1 ), &refDispatch))) + { + throw BridgeRuntimeError( + "[automation bridge] Could not obtain type information " + "for dispatch interface." ); + } + CComPtr<ITypeInfo> spTypeDisp; + if (SUCCEEDED(spType->GetRefTypeInfo(refDispatch, & spTypeDisp))) + m_spTypeInfo= spTypeDisp; + } + else if (typeAttr->typekind == TKIND_DISPATCH) + { + m_spTypeInfo= spType; + } + else + { + throw BridgeRuntimeError( + "[automation bridge] Automation object does not " + "provide type information."); + } + } + } + } + return m_spTypeInfo; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/oleobjw.hxx b/extensions/source/ole/oleobjw.hxx new file mode 100644 index 0000000000..d1a1d0ed81 --- /dev/null +++ b/extensions/source/ole/oleobjw.hxx @@ -0,0 +1,244 @@ +/* -*- 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 . + */ + +#pragma once + +#include "ole2uno.hxx" +#include "wincrap.hxx" + +#include <string_view> +#include <unordered_map> +#include <vector> + +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/bridge/oleautomation/XAutomationObject.hpp> +#include <com/sun/star/script/XAutomationInvocation.hpp> +#include <rtl/ustring.hxx> + +#include <com/sun/star/script/XDefaultProperty.hpp> +#include <com/sun/star/script/XDefaultMethod.hpp> +#include <com/sun/star/script/XDirectInvocation.hpp> + +#include <typelib/typedescription.hxx> +#include "unoconversionutilities.hxx" +#include "windata.hxx" +using namespace cppu; +using namespace com::sun::star::lang; +using namespace com::sun::star::bridge; +using namespace com::sun::star::bridge::oleautomation; + +typedef std::unordered_map<OUString, std::pair<DISPID, unsigned short>> DispIdMap; + +typedef std::unordered_multimap<OUString, unsigned int> TLBFuncIndexMap; + +// This class wraps an IDispatch and maps XInvocation calls to IDispatch calls on the wrapped object. +// If m_TypeDescription is set then this class represents a UNO interface implemented in a COM component. +// The interface is not a real interface in terms of an abstract class but is realized through IDispatch. +class IUnknownWrapper : public WeakImplHelper< XBridgeSupplier2, XInitialization, XAutomationObject, XDefaultProperty, XDefaultMethod, XDirectInvocation, XAutomationInvocation >, + + public UnoConversionUtilities<IUnknownWrapper> + +{ +public: + IUnknownWrapper(Reference<XMultiServiceFactory> const &xFactory, + sal_uInt8 unoWrapperClass, sal_uInt8 comWrapperClass); + + ~IUnknownWrapper() override; + + //XInterface + Any SAL_CALL queryInterface(const Type& t) override; + + // XInvokation + virtual Reference< XIntrospectionAccess > SAL_CALL getIntrospection( ) override; + virtual Any SAL_CALL invoke( const OUString& aFunctionName, + const Sequence< Any >& aParams, + Sequence< sal_Int16 >& aOutParamIndex, + Sequence< Any >& aOutParam ) override; + virtual void SAL_CALL setValue( const OUString& aPropertyName, + const Any& aValue ) override; + virtual Any SAL_CALL getValue( const OUString& aPropertyName ) override; + virtual sal_Bool SAL_CALL hasMethod( const OUString& aName ) override; + virtual sal_Bool SAL_CALL hasProperty( const OUString& aName ) override; + + // XBridgeSupplier2 + // This interface is implemented to provide a safe way to obtain the original + // IUnknown or IDispatch within the function anyToVariant. The function asks + // every UNO object for its XBridgeSupplier2 and if it is available uses it to convert + // the object with its own supplier. + virtual Any SAL_CALL createBridge( const Any& modelDepObject, + const Sequence< sal_Int8 >& aProcessId, + sal_Int16 sourceModelType, + sal_Int16 destModelType ) override; + + // XInitialization + virtual void SAL_CALL initialize( const Sequence< Any >& aArguments ) override; + + // XDefaultProperty + virtual OUString SAL_CALL getDefaultPropertyName( ) override { return m_sDefaultMember; } + + // XDefaultMethod + virtual OUString SAL_CALL getDefaultMethodName( ) override { return m_sDefaultMember; } + + virtual css::uno::Any SAL_CALL invokeGetProperty( const OUString& aFunctionName, const css::uno::Sequence< css::uno::Any >& aParams, css::uno::Sequence< ::sal_Int16 >& aOutParamIndex, css::uno::Sequence< css::uno::Any >& aOutParam ) override; + virtual css::uno::Any SAL_CALL invokePutProperty( const OUString& aFunctionName, const css::uno::Sequence< css::uno::Any >& aParams, css::uno::Sequence< ::sal_Int16 >& aOutParamIndex, css::uno::Sequence< css::uno::Any >& aOutParam ) override; + + // XDirectInvocation + virtual css::uno::Any SAL_CALL directInvoke( const OUString& aName, const css::uno::Sequence< css::uno::Any >& aParams ) override; + virtual sal_Bool SAL_CALL hasMember( const OUString& aName ) override; + + + Any invokeWithDispIdComTlb(FuncDesc& aFuncDesc, + const OUString& sFuncName, + const Sequence< Any >& Params, + Sequence< sal_Int16 >& OutParamIndex, + Sequence< Any >& OutParam); + + +protected: + + virtual Any invokeWithDispIdUnoTlb(const OUString& sFunctionName, + const Sequence< Any >& Params, + Sequence<sal_Int16 >& OutParamIndex, + Sequence< Any >& OutParam); + // Is used for OleObjectFactory service + virtual Any invokeWithDispIdComTlb(const OUString& sFuncName, + const Sequence< Any >& Params, + Sequence< sal_Int16 >& OutParamIndex, + Sequence< Any >& OutParam); + + // UnoConversionUtilities ------------------------------------------------------------------------------- + virtual Reference<XInterface> createUnoWrapperInstance() override; + virtual Reference<XInterface> createComWrapperInstance() override; + + /**Obtains a FUNCDESC structure for a function. + Fills the FUNCDESC structure if ITypeInfo provides information for + the function of name sFuncName or pFuncDesc will not be filled in. + May throw a BridgeRuntimeError. + */ + void getFuncDesc(const OUString & sFuncName, FUNCDESC ** pFuncDesc); + /**Obtains a FUNCDESC structures or a VARDESC structure + for a property. pFuncDescPut may also contain + a structure for a "propertyputref" operation. If pFuncDesc contains a + "put ref" or "put" FUNCDESC depends on what was found first in the type + description. + Fills the FUNCDESC structure if ITypeInfo provides information for + the respective property functions or the structures will not be filled in. + May throw a BridgeRuntimeError. + */ + void getPropDesc(const OUString & sFuncName, FUNCDESC ** pFuncDescGet, + FUNCDESC** pFuncDescPut, VARDESC ** pVarDesc); + // These functions are for the case if an object of this class wraps an IDispatch + // object that implements UNO interfaces. In that case the member m_seqTypes + // is set through XInitialization::initialize. + void getMethodInfo(std::u16string_view sName, TypeDescription& methodDescription); + // After return attributInfo contains typelib_InterfaceAttributeTypeDescription::pAttributeTypeRef + void getAttributeInfo(std::u16string_view sName, TypeDescription& attributeInfo); + // used by get MethodInfo + TypeDescription getInterfaceMemberDescOfCurrentCall(std::u16string_view sName); + /** Returns always a valid ITypeInfo interface or throws a BridgeRuntimeError. + The returned interface does not need to be AddRef'ed as long as it is locally + used. The interface is kept in the instance of this class. + */ + ITypeInfo* getTypeInfo(); + + /** Returns the DISPID for a function or property name. If true is returned then + id contains a valid DISPID. + */ + + bool getDispid(const OUString& sFuncName, DISPID * id); + + VARTYPE getUserDefinedElementType( ITypeInfo* pTypeInfo, const DWORD nHrefType ); + + /** Gets the element type in a VARIANT like style. E.g. if desc->lptdesc contains + a VT_PTR than it is replaced by VT_BYREF and VT_SAFEARRAY is replaced by VT_ARRAY + If the TYPEDESC describes an SAFEARRAY then varType is a combination of VT_ARRAY + and the element type. + The argument desc must be obtained from FUNCDESC::lprgelemdescParam[i].tdesc where + FUNCDESC was obtained from the ITypeInfo belonging to wrapped IDispatch. + */ + VARTYPE getElementTypeDesc( const TYPEDESC *desc); + /** Iterates over all functions and put the names and indices into the map + m_mapComFunc of type TLBFuncIndexMap. + Call the function every time before accessing the map. + Throws a BridgeRuntimeError on failure. + */ + void buildComTlbIndex(); + + /** Returns a FUNCDESC structure which contains type information about the + current XInvocation::invoke call. The FUNCDESC either describes a method, + a property put or a property get operation. + It uses the types com.sun.star.bridge.oleautomation.PropertyPutArgument + which can be + contained in the sequence of in-arguments of invoke to determine if the call is + a property put or property get operation. + If no adequate FUNCDESC was found, an IllegalArgumentException is thrown. + Therefore it is safe to assume that the returned FUNCDESC* is not NULL. + + @exception IllegalArgumentException + Thrown if no adequate FUNCDESC could be found. + */ + void getFuncDescForInvoke(const OUString & sFuncName, + const Sequence<Any> & seqArgs, FUNCDESC** pFuncDesc); + + // Finds out whether the wrapped IDispatch is a JScript Object. This is + // done by + // asking for the property "_environment". If it has the value "JScript" + // (case insensitive) then the IDispatch is considered a JScript object. + bool isJScriptObject(); + + + // If UNO interfaces are implemented in JScript objects, VB or C++ COM objects + // and those are passed as parameter to a UNO interface function, then + // the IDispatch* are wrapped by objects of this class. Assuming that the functions + // implemented by the IDispatch object returns another UNO interface then + // it has to be wrapped to this type. But this is only possible if an object of this + // wrapper class knows what type it is represting. The member m_TypeDescription holds this + // information. + // m_TypeDescription is only useful when an object wraps an IDispatch object that implements + // a UNO interface. The value is set during a call to XInitialization::initialize. + Sequence<Type> m_seqTypes; + CComPtr<IUnknown> m_spUnknown; + CComPtr<IDispatch> m_spDispatch; + OUString m_sTypeName; // is "" ( not initialised ), "IDispatch" ( we have no idea ) or "SomeLibrary.SomeTypeName" if we managed to get a type + /** This value is set during XInitialization::initialize. It indicates that the COM interface + was transported as VT_DISPATCH in a VARIANT rather than a VT_UNKNOWN + */ + bool m_bOriginalDispatch; + DispIdMap m_dispIdMap; + Reference<XIdlClass>* m_pxIdlClass; + + + // used by isJScriptObject + enum JScriptDetermination{ JScriptUndefined=0, NoJScript, IsJScript}; + JScriptDetermination m_eJScript; + // The map is filled by buildComTlbIndex + // It maps Uno Function names to an index which is used in ITypeInfo::GetFuncDesc + TLBFuncIndexMap m_mapComFunc; + // used for synchronizing the computation of the content for m_mapComFunc + bool m_bComTlbIndexInit; + // Keeps the ITypeInfo obtained from IDispatch::GetTypeInfo + CComPtr< ITypeInfo > m_spTypeInfo; + OUString m_sDefaultMember; + bool m_bHasDfltMethod; + bool m_bHasDfltProperty; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/olethread.cxx b/extensions/source/ole/olethread.cxx new file mode 100644 index 0000000000..503f8bc096 --- /dev/null +++ b/extensions/source/ole/olethread.cxx @@ -0,0 +1,69 @@ +/* -*- 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 "ole2uno.hxx" + +#include <comphelper/windowserrorstring.hxx> +#include <osl/thread.hxx> +#include <sal/log.hxx> + +void o2u_attachCurrentThread() +{ + static osl::ThreadData oleThreadData; + + if (!bool(reinterpret_cast<sal_IntPtr>(oleThreadData.getData()))) + { + HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + if (!SUCCEEDED(hr)) + { // FIXME: is it a problem that this ends up in STA currently? + assert(RPC_E_CHANGED_MODE == hr); + // Let's find out explicitly what apartment mode we are in. + if (hr == RPC_E_CHANGED_MODE) + SAL_INFO("extensions.olebridge", "CoInitializeEx failed (expectedly): " + << WindowsErrorStringFromHRESULT(hr)); + else + SAL_WARN("extensions.olebridge", + "CoInitializeEx failed: " << WindowsErrorStringFromHRESULT(hr)); + APTTYPE nAptType; + APTTYPEQUALIFIER nAptTypeQualifier; + if (SUCCEEDED(CoGetApartmentType(&nAptType, &nAptTypeQualifier))) + { + SAL_INFO("extensions.olebridge", + " Thread is in a " + << (nAptType == APTTYPE_STA ? OUString("single-threaded") : + (nAptType == APTTYPE_MTA ? OUString("multi-threaded") : + (nAptType == APTTYPE_NA ? OUString("neutral") : + (nAptType == APTTYPE_MAINSTA ? OUString("main single-threaded") : + ("unknown (") + OUString::number(nAptType) + ")")))) + << " apartment" + << (nAptTypeQualifier == APTTYPEQUALIFIER_NONE ? OUString() : + (nAptTypeQualifier == APTTYPEQUALIFIER_IMPLICIT_MTA ? OUString(" (implicit)") : + (nAptTypeQualifier == APTTYPEQUALIFIER_NA_ON_MTA ? OUString(" (on MTA)") : + (nAptTypeQualifier == APTTYPEQUALIFIER_NA_ON_STA ? OUString(" (on STA)") : + (nAptTypeQualifier == APTTYPEQUALIFIER_NA_ON_IMPLICIT_MTA ? OUString(" (on implicit MTA)") : + (nAptTypeQualifier == APTTYPEQUALIFIER_NA_ON_MAINSTA ? OUString(" (on main STA)") : + (" (with unknown qualifier (" + OUString::number(nAptTypeQualifier) + "))"))))))) + << "."); + } + } + oleThreadData.setData(reinterpret_cast<void*>(true)); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/servprov.cxx b/extensions/source/ole/servprov.cxx new file mode 100644 index 0000000000..f5a9323d36 --- /dev/null +++ b/extensions/source/ole/servprov.cxx @@ -0,0 +1,547 @@ +/* -*- 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 <vector> + +#include "ole2uno.hxx" +#include "unoconversionutilities.hxx" +#include "servprov.hxx" +#include "unoobjw.hxx" +#include "oleobjw.hxx" + +#include <com/sun/star/script/CannotConvertException.hpp> +#include <comphelper/automationinvokedzone.hxx> +#include <comphelper/windowsdebugoutput.hxx> +#include <comphelper/windowserrorstring.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <o3tl/any.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <ooo/vba/XHelperInterface.hpp> +#include <sal/log.hxx> + +using namespace cppu; +using namespace osl; +using namespace com::sun::star::lang; +using namespace com::sun::star::uno; +using namespace com::sun::star::bridge; +using namespace com::sun::star::bridge::ModelDependent; + +#include <initguid.h> + +// GUID used since 5.2 ( src569 m) +// {82154420-0FBF-11d4-8313-005004526AB4} +DEFINE_GUID(OID_ServiceManager, 0x82154420, 0xfbf, 0x11d4, 0x83, 0x13, 0x0, 0x50, 0x4, 0x52, 0x6a, 0xb4); + +// FIXME: This GUID is just the above OID_ServiceManager with the +// initial part bumped by one. Is that good enough? +// {82154421-0FBF-11d4-8313-005004526AB4} +DEFINE_GUID(OID_LibreOfficeWriterApplication, 0x82154421, 0xfbf, 0x11d4, 0x83, 0x13, 0x0, 0x50, 0x4, 0x52, 0x6a, 0xb4); + +// For Calc +// {82154425-0FBF-11d4-8313-005004526AB4} +DEFINE_GUID(OID_LibreOfficeCalcApplication, 0x82154425, 0xfbf, 0x11d4, 0x83, 0x13, 0x0, 0x50, 0x4, 0x52, 0x6a, 0xb4); + +OneInstanceOleWrapper::OneInstanceOleWrapper( const Reference<XMultiServiceFactory>& smgr, + std::function<const Reference<XInterface>()> xInstFunction ) + : m_refCount(0) + , m_xInstFunction(xInstFunction) + , m_factoryHandle(0) + , m_smgr(smgr) +{ + Reference<XInterface> xInt = m_smgr->createInstance("com.sun.star.bridge.oleautomation.BridgeSupplier"); + + if (xInt.is()) + { + Any a= xInt->queryInterface( cppu::UnoType<XBridgeSupplier2>::get() ); + a >>= m_bridgeSupplier; + } +} + +OneInstanceOleWrapper::~OneInstanceOleWrapper() +{ +} + +bool OneInstanceOleWrapper::registerClass(GUID const * pGuid) +{ + HRESULT hresult; + + o2u_attachCurrentThread(); + + hresult = CoRegisterClassObject( + *pGuid, + this, + CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, + REGCLS_MULTIPLEUSE, + &m_factoryHandle); + + SAL_INFO("extensions.olebridge", "CoRegisterClassObject(" << *pGuid << "): " << WindowsErrorStringFromHRESULT(hresult)); + + return (hresult == NOERROR); +} + +bool OneInstanceOleWrapper::deregisterClass() +{ + return CoRevokeClassObject(m_factoryHandle) == NOERROR; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP OneInstanceOleWrapper::QueryInterface(REFIID riid, void ** ppv) +{ + if(IsEqualIID(riid, IID_IUnknown)) + { + AddRef(); + *ppv = static_cast<IUnknown*>(static_cast<IClassFactory*>(this)); + return NOERROR; + } + else if (IsEqualIID(riid, IID_IClassFactory)) + { + AddRef(); + *ppv = static_cast<IClassFactory*>(this); + return NOERROR; + } + + *ppv = nullptr; + return ResultFromScode(E_NOINTERFACE); +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP_(ULONG) OneInstanceOleWrapper::AddRef() +{ + return osl_atomic_increment( &m_refCount); +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP_(ULONG) OneInstanceOleWrapper::Release() +{ + MutexGuard oGuard( Mutex::getGlobalMutex()); + ULONG refCount = --m_refCount; + if ( m_refCount == 0) + { + delete this; + } + + return refCount; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP OneInstanceOleWrapper::CreateInstance(IUnknown*, + REFIID riid, void** ppv) +{ + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + SAL_INFO("extensions.olebridge", "OneInstanceOleWrapper::CreateInstance(" << riid << ")"); + + HRESULT ret = ResultFromScode(E_UNEXPECTED); + + const Reference<XInterface>& xInst = m_xInstFunction(); + if (xInst.is()) + { + Any usrAny(&xInst, cppu::UnoType<decltype(xInst)>::get()); + sal_uInt8 arId[16]; + rtl_getGlobalProcessId( arId); + Any oleAny = m_bridgeSupplier->createBridge(usrAny, + Sequence<sal_Int8>( reinterpret_cast<sal_Int8*>(arId), 16), + UNO, + OLE); + + + if (auto v = o3tl::tryAccess<sal_uIntPtr>(oleAny)) + { + VARIANT* pVariant = reinterpret_cast<VARIANT*>(*v); + + if ((pVariant->vt == VT_UNKNOWN) || (pVariant->vt == VT_DISPATCH)) + { + SAL_INFO("extensions.olebridge", "OneInstanceOleWrapper::Createbridge: punkVal=" << pVariant->punkVal); + ret = pVariant->punkVal->QueryInterface(riid, ppv); + } + + VariantClear(pVariant); + CoTaskMemFree(pVariant); + } + } + + return ret; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP OneInstanceOleWrapper::LockServer(BOOL /*fLock*/) +{ + return NOERROR; +} + +OleConverter::OleConverter( const Reference<XMultiServiceFactory> &smgr): + UnoConversionUtilities<OleConverter>( smgr) + +{ +} + +// The XMultiServiceFactory is later set by XInitialization +OleConverter::OleConverter( const Reference<XMultiServiceFactory>& smgr, sal_uInt8 unoWrapperClass, sal_uInt8 comWrapperClass ): + UnoConversionUtilities<OleConverter>( smgr, unoWrapperClass, comWrapperClass ) + +{ +} + +OleConverter::~OleConverter() +{ +} + +// XBridgeSupplier -------------------------------------------------------------- +Any SAL_CALL OleConverter::createBridge(const Any& modelDepObject, + const Sequence< sal_Int8 >& ProcessId, + sal_Int16 sourceModelType, + sal_Int16 destModelType) +{ + Any ret; + sal_uInt8 arId[16]; + rtl_getGlobalProcessId( arId ); + + Sequence< sal_Int8 > seqProcessId( reinterpret_cast<sal_Int8*>(arId), 16); + + if ( seqProcessId == ProcessId) + { + if (sourceModelType == UNO) + { + if (destModelType == UNO) + { + // same model -> copy value only + ret = modelDepObject; + } + else if (destModelType == OLE) + { + // convert UNO any into variant + VARIANT* pVariant = static_cast<VARIANT*>(CoTaskMemAlloc(sizeof(VARIANT))); + VariantInit( pVariant); + try + { + anyToVariant( pVariant, modelDepObject); + } + catch(...) + { + CoTaskMemFree(pVariant); + throw IllegalArgumentException(); + } + ret.setValue(static_cast<void*>(&pVariant), cppu::UnoType<sal_uIntPtr>::get()); + } + else + throw IllegalArgumentException(); + } + else if (sourceModelType == OLE) + { + auto v = o3tl::tryAccess<sal_uIntPtr>(modelDepObject); + if (!v) + { + throw IllegalArgumentException(); + } + else if (destModelType == OLE) + { + // same model -> copy value only + VARIANT* pVariant = static_cast<VARIANT*>(CoTaskMemAlloc(sizeof(VARIANT))); + + if (NOERROR != VariantCopy(pVariant, reinterpret_cast<VARIANT*>(*v))) + { + CoTaskMemFree(pVariant); + throw(IllegalArgumentException()); + } + else + { + ret.setValue(static_cast<void*>(&pVariant), cppu::UnoType<sal_uIntPtr>::get()); + } + } + else if (destModelType == UNO) + { + // convert variant into UNO any + VARIANT* pVariant = reinterpret_cast<VARIANT*>(*v); + try + { + variantToAny(pVariant, ret); + } + catch (const CannotConvertException & e) + { + throw IllegalArgumentException( + e.Message, nullptr, -1); + } + } + else + throw IllegalArgumentException(); + + } + else + throw IllegalArgumentException(); + } + + return ret; +} + +OUString OleConverter::getImplementationName() +{ + return m_nUnoWrapperClass == INTERFACE_OLE_WRAPPER_IMPL + ? OUString("com.sun.star.comp.ole.OleConverter2") + : OUString("com.sun.star.comp.ole.OleConverterVar1"); +} + +sal_Bool OleConverter::supportsService(OUString const & ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence<OUString> OleConverter::getSupportedServiceNames() +{ + if (m_nUnoWrapperClass == INTERFACE_OLE_WRAPPER_IMPL) + { + return css::uno::Sequence<OUString>{ + "com.sun.star.bridge.OleBridgeSupplier2", + "com.sun.star.bridge.oleautomation.BridgeSupplier"}; + } + return {"com.sun.star.bridge.OleBridgeSupplierVar1"}; +} + +// XInitialize ------------------------------------------------------------------------------ +// the first argument is an XMultiServiceFactory if at all +void SAL_CALL OleConverter::initialize( const Sequence< Any >& aArguments ) +{ + if( aArguments.getLength() == 1 && aArguments[0].getValueTypeClass() == TypeClass_INTERFACE) + { + Reference < XInterface > xInt; + aArguments[0] >>= xInt; + Reference <XMultiServiceFactory> xMulti( xInt, UNO_QUERY); + m_smgrRemote= xMulti; + } +} + +// UnoConversionUtilities ------------------------------------------------------------------- +Reference< XInterface > OleConverter::createUnoWrapperInstance() +{ + if( m_nUnoWrapperClass == INTERFACE_OLE_WRAPPER_IMPL) + { + Reference<XWeak> xWeak= static_cast<XWeak*>( new InterfaceOleWrapper( + m_smgr, m_nUnoWrapperClass, m_nComWrapperClass)); + return Reference<XInterface>( xWeak, UNO_QUERY); + } + else if( m_nUnoWrapperClass == UNO_OBJECT_WRAPPER_REMOTE_OPT) + { + Reference<XWeak> xWeak= static_cast<XWeak*>( new UnoObjectWrapperRemoteOpt( + m_smgr, m_nUnoWrapperClass, m_nComWrapperClass)); + return Reference<XInterface>( xWeak, UNO_QUERY); + } + else + return Reference<XInterface>(); +} + +Reference< XInterface > OleConverter::createComWrapperInstance() +{ + Reference<XWeak> xWeak= static_cast<XWeak*>( new IUnknownWrapper( + m_smgr, m_nUnoWrapperClass, m_nComWrapperClass)); + return Reference<XInterface>( xWeak, UNO_QUERY); +} + +OleClient::OleClient( const Reference<XMultiServiceFactory>& smgr): + UnoConversionUtilities<OleClient>( smgr) +{ + Reference<XInterface> xInt;// = m_smgr->createInstance(L"com.sun.star.bridge.OleBridgeSupplier2"); + + if (xInt.is()) + { + Any a= xInt->queryInterface(cppu::UnoType<XBridgeSupplier2>::get() ); + a >>= m_bridgeSupplier; + } +} + +OleClient::~OleClient() +{ +} + +Sequence< OUString > SAL_CALL OleClient::getAvailableServiceNames() +{ + Sequence< OUString > ret; + + return ret; +} + +OUString OleClient::getImplementationName() +{ + return "com.sun.star.comp.ole.OleClient"; +} + +sal_Bool OleClient::supportsService(OUString const & ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence<OUString> OleClient::getSupportedServiceNames() +{ + return css::uno::Sequence<OUString>{ + "com.sun.star.bridge.OleObjectFactory", + "com.sun.star.bridge.oleautomation.Factory"}; +} + +Reference<XInterface> SAL_CALL OleClient::createInstance(const OUString& ServiceSpecifier) +{ + Reference<XInterface> ret; + HRESULT result; + IUnknown* pUnknown = nullptr; + CLSID classId; + + o2u_attachCurrentThread(); + + result = CLSIDFromProgID( + o3tl::toW(ServiceSpecifier.getStr()), //Pointer to the ProgID + &classId); //Pointer to the CLSID + + + if (result == NOERROR) + { + result = CoCreateInstance( + classId, //Class identifier (CLSID) of the object + nullptr, //Pointer to whether object is or isn't part of an aggregate + CLSCTX_SERVER, //Context for running executable code + IID_IUnknown, //Reference to the identifier of the interface + reinterpret_cast<void**>(&pUnknown)); //Address of output variable that receives + // the interface pointer requested in riid + } + + if (pUnknown != nullptr) + { + Any any; + CComVariant variant; + + V_VT(&variant) = VT_UNKNOWN; + V_UNKNOWN(&variant) = pUnknown; + // AddRef for Variant + pUnknown->AddRef(); + + // When the object is wrapped, then its refcount is increased + variantToAny(&variant, any); + if (any.getValueTypeClass() == TypeClass_INTERFACE) + { + any >>= ret; + } + pUnknown->Release(); // CoCreateInstance + } + + return ret; +} + +Reference<XInterface> SAL_CALL OleClient::createInstanceWithArguments(const OUString& ServiceSpecifier, const Sequence< Any >& /*Arguments*/) +{ + return createInstance( ServiceSpecifier); +} + +// UnoConversionUtilities ----------------------------------------------------------------------------- +Reference< XInterface > OleClient::createUnoWrapperInstance() +{ + if( m_nUnoWrapperClass == INTERFACE_OLE_WRAPPER_IMPL) + { + Reference<XWeak> xWeak= static_cast<XWeak*>( new InterfaceOleWrapper( + m_smgr, m_nUnoWrapperClass, m_nComWrapperClass)); + return Reference<XInterface>( xWeak, UNO_QUERY); + } + else if( m_nUnoWrapperClass == UNO_OBJECT_WRAPPER_REMOTE_OPT) + { + Reference<XWeak> xWeak= static_cast<XWeak*>( new UnoObjectWrapperRemoteOpt( + m_smgr, m_nUnoWrapperClass, m_nComWrapperClass)); + return Reference<XInterface>( xWeak, UNO_QUERY); + } + else + return Reference< XInterface>(); +} +// UnoConversionUtilities ----------------------------------------------------------------------------- +Reference< XInterface > OleClient::createComWrapperInstance( ) +{ + Reference<XWeak> xWeak= static_cast<XWeak*>( new IUnknownWrapper( + m_smgr, m_nUnoWrapperClass, m_nComWrapperClass)); + return Reference<XInterface>( xWeak, UNO_QUERY); +} + +OleServer::OleServer( const Reference<XMultiServiceFactory>& smgr): + m_smgr( smgr) +{ + Reference<XInterface> xInt = m_smgr->createInstance("com.sun.star.bridge.oleautomation.BridgeSupplier"); + + if (xInt.is()) + { + Any a= xInt->queryInterface( cppu::UnoType<XBridgeSupplier2>::get() ); + a >>= m_bridgeSupplier; + } + + (void) provideInstance( [&] + { + return m_smgr; + }, + &OID_ServiceManager ); + + (void) provideInstance( [&] + { + // We want just one SwVbaGlobals for all Automation clients + static const Reference<XInterface> xWordGlobals = m_smgr->createInstance("ooo.vba.word.Globals"); + const Reference<ooo::vba::XHelperInterface> xHelperInterface(xWordGlobals, UNO_QUERY); + Any aApplication = xHelperInterface->Application(); + Reference<XInterface> xApplication; + aApplication >>= xApplication; + return xApplication; + }, + &OID_LibreOfficeWriterApplication ); + + (void) provideInstance( [&] + { + // Ditto for sc + static const Reference<XInterface> xCalcGlobals = m_smgr->createInstance("ooo.vba.excel.Globals"); + const Reference<ooo::vba::XHelperInterface> xHelperInterface(xCalcGlobals, UNO_QUERY); + Any aApplication = xHelperInterface->Application(); + Reference<XInterface> xApplication; + aApplication >>= xApplication; + return xApplication; + }, + &OID_LibreOfficeCalcApplication ); +} + +OleServer::~OleServer() +{ + for (auto const& elem : m_wrapperList) + { + elem->deregisterClass(); + elem->Release(); + } + m_wrapperList.clear(); +} + +OUString OleServer::getImplementationName() +{ + return "com.sun.star.comp.ole.OleServer"; +} + +sal_Bool OleServer::supportsService(OUString const & ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence<OUString> OleServer::getSupportedServiceNames() +{ + return css::uno::Sequence<OUString>{ + "com.sun.star.bridge.OleApplicationRegistration", + "com.sun.star.bridge.oleautomation.ApplicationRegistration"}; +} + +bool OleServer::provideInstance(std::function<const Reference<XInterface>()> xInstFunction, GUID const * guid) +{ + OneInstanceOleWrapper* pWrapper = new OneInstanceOleWrapper( m_smgr, xInstFunction ); + + pWrapper->AddRef(); + m_wrapperList.push_back(pWrapper); + + return pWrapper->registerClass(guid); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/servprov.hxx b/extensions/source/ole/servprov.hxx new file mode 100644 index 0000000000..8871f28cf7 --- /dev/null +++ b/extensions/source/ole/servprov.hxx @@ -0,0 +1,184 @@ +/* -*- 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 . + */ + +#pragma once + +#include <functional> + +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <cppuhelper/implbase.hxx> + +#include "ole2uno.hxx" +#include "unoconversionutilities.hxx" + +using namespace com::sun::star::bridge; +using namespace cppu; + +/// @throws Exception +Reference< XInterface> ConverterProvider_CreateInstance2( const Reference<XMultiServiceFactory> & xSMgr); +/// @throws Exception +Reference< XInterface> ConverterProvider_CreateInstanceVar1( const Reference<XMultiServiceFactory> & xSMgr); +/// @throws Exception +Reference<XInterface> OleClient_CreateInstance( const Reference<XMultiServiceFactory> & xSMgr); +/// @throws Exception +Reference<XInterface> OleServer_CreateInstance( const Reference<XMultiServiceFactory> & xSMgr); + +/***************************************************************************** + + OneInstanceOleWrapper + + Provides a single UNO object as OLE object. + + Acts as a COM class factory. When IClassFactory::CreateInstance is being called + then it maps the XInstance member it to a COM object. + +*****************************************************************************/ + +class OneInstanceOleWrapper : public IClassFactory +{ +public: + + OneInstanceOleWrapper( const Reference<XMultiServiceFactory>& smgr, + std::function<const Reference<XInterface>()> xInstFunction ); + virtual ~OneInstanceOleWrapper(); + + bool registerClass(GUID const * pGuid); + bool deregisterClass(); + + /* IUnknown methods */ + STDMETHOD(QueryInterface)(REFIID riid, void ** ppvObj) override; + STDMETHOD_(ULONG, AddRef)() override; + STDMETHOD_(ULONG, Release)() override; + + /* IClassFactory methods */ + STDMETHOD(CreateInstance)(IUnknown* punkOuter, REFIID riid, void** ppv) override; + STDMETHOD(LockServer)(BOOL fLock) override; + +protected: + oslInterlockedCount m_refCount; + std::function<const Reference<XInterface>()> m_xInstFunction; + DWORD m_factoryHandle; + Reference<XBridgeSupplier2> m_bridgeSupplier; + Reference<XMultiServiceFactory> m_smgr; +}; + +// Implementation of the UNO service com.sun.star.bridge.OleBridgeSupplier2. + +// This class realizes the service com.sun.star.bridge.OleBridgeSupplier2 and +// com.sun.star.bridge.OleBridgeSupplierVar1. The class implements XBridgeSupplier2 which +// interface does not need a Machine Id in its createBridge function anymore, +// If a UNO interface is to be converted then the member m_nUnoWrapperClass determines +// what wrapper class is to be used. There are currently InterfaceOleWrapper and +// UnoObjectWrapperRemoteOpt. The first is used for the OleBridgeSupplier2 and the +// latter for OleBridgeSupplierVar1. +// The m_nComWrapperClass specifies the class which is used as wrapper for COM interfaces. +// Currently there is only one class available (IUnknownWrapper). +class OleConverter : public WeakImplHelper<XBridgeSupplier2, XInitialization, css::lang::XServiceInfo>, + public UnoConversionUtilities<OleConverter> +{ +public: + explicit OleConverter( const Reference<XMultiServiceFactory>& smgr); + OleConverter( const Reference<XMultiServiceFactory>& smgr, sal_uInt8 unoWrapperClass, sal_uInt8 comWrapperClass ); + virtual ~OleConverter() override; + + // XBridgeSupplier2 --------------------------------------------------- + + Any SAL_CALL createBridge(const Any& modelDepObject, + const Sequence<sal_Int8>& ProcessId, + sal_Int16 sourceModelType, + sal_Int16 destModelType) override; + + // XInitialization + void SAL_CALL initialize( const Sequence< Any >& aArguments ) override; + + OUString SAL_CALL getImplementationName() override; + + sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override; + + css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + // UnoConversionUtilities + Reference< XInterface > createUnoWrapperInstance() override; + Reference< XInterface > createComWrapperInstance() override; +protected: + +}; + +// Implementation of the UNO service com.sun.star.bridge.OleObjectFactory. + +class OleClient : public WeakImplHelper<XMultiServiceFactory, css::lang::XServiceInfo>, + public UnoConversionUtilities<OleClient> +{ +public: + explicit OleClient( const Reference<XMultiServiceFactory>& smgr); + ~OleClient() override; + + // XMultiServiceFactory + Reference<XInterface> SAL_CALL createInstance(const OUString& ServiceSpecifier) override; + Reference<XInterface> SAL_CALL createInstanceWithArguments(const OUString& ServiceSpecifier, const Sequence< Any >& Arguments) override; + Sequence< OUString > SAL_CALL getAvailableServiceNames() override; + + OUString SAL_CALL getImplementationName() override; + + sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override; + + css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + // UnoConversionUtilities + Reference< XInterface > createUnoWrapperInstance() override; + Reference< XInterface > createComWrapperInstance() override; + +protected: + Reference<XBridgeSupplier2> m_bridgeSupplier; +}; + +/***************************************************************************** + + OleServer + + Implementation of the UNO service com.sun.star.bridge.OleApplicationRegistration. + Register the calling application as OLE automation server for + standard OLE object. The objects will be registered while instantiating + this implementation and deregistered, if this implementation is destroyed. + +*****************************************************************************/ + +class OleServer : public cppu::WeakImplHelper<css::lang::XServiceInfo> +{ +public: + explicit OleServer( const Reference<XMultiServiceFactory> &smgr); + ~OleServer() override; + + OUString SAL_CALL getImplementationName() override; + + sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override; + + css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + +protected: + bool provideInstance(std::function<const Reference<XInterface>()> xInstFunction, GUID const * guid); + + std::list< OneInstanceOleWrapper* > m_wrapperList; + Reference< XBridgeSupplier2 > m_bridgeSupplier; + + Reference<XMultiServiceFactory> m_smgr; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/servreg.cxx b/extensions/source/ole/servreg.cxx new file mode 100644 index 0000000000..66928137b4 --- /dev/null +++ b/extensions/source/ole/servreg.cxx @@ -0,0 +1,94 @@ +/* -*- 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 <osl/time.h> +#include "ole2uno.hxx" +#include "servprov.hxx" +#include <rtl/ustring.hxx> +#include <cppuhelper/factory.hxx> + +using namespace cppu; + +Reference<XInterface> ConverterProvider_CreateInstance2( const Reference<XMultiServiceFactory> & xSMgr) +{ + Reference<XInterface> xService = *new OleConverter( xSMgr); + return xService; +} + +Reference<XInterface> ConverterProvider_CreateInstanceVar1( const Reference<XMultiServiceFactory> & xSMgr) +{ + Reference<XInterface> xService = *new OleConverter( xSMgr, UNO_OBJECT_WRAPPER_REMOTE_OPT, IUNKNOWN_WRAPPER_IMPL); + return xService; +} + +Reference<XInterface> OleClient_CreateInstance( const Reference<XMultiServiceFactory> & xSMgr) +{ + Reference<XInterface> xService = *new OleClient( xSMgr); + return xService; +} + +Reference<XInterface> OleServer_CreateInstance( const Reference<XMultiServiceFactory> & xSMgr) +{ + Reference<XInterface > xService = *new OleServer(xSMgr); + return xService; +} + +extern "C" SAL_DLLPUBLIC_EXPORT void * oleautobridge_component_getFactory( + const char * pImplName, void * pServiceManager, void * /*pRegistryKey*/ ) +{ + void * pRet = nullptr; + + OUString aImplName( OUString::createFromAscii( pImplName ) ); + Reference< XSingleServiceFactory > xFactory; + Sequence<OUString> seqServiceNames; + if (pServiceManager && aImplName == "com.sun.star.comp.ole.OleConverter2") + { + xFactory= createSingleFactory( static_cast< XMultiServiceFactory*>(pServiceManager), + aImplName, + ConverterProvider_CreateInstance2, seqServiceNames ); + } + else if (pServiceManager && aImplName == "com.sun.star.comp.ole.OleConverterVar1") + { + xFactory= createSingleFactory( static_cast<XMultiServiceFactory*>(pServiceManager), + aImplName, + ConverterProvider_CreateInstanceVar1, seqServiceNames ); + } + else if(pServiceManager && aImplName == "com.sun.star.comp.ole.OleClient") + { + xFactory= createSingleFactory( static_cast< XMultiServiceFactory*>(pServiceManager), + aImplName, + OleClient_CreateInstance, seqServiceNames ); + } + else if(pServiceManager && aImplName == "com.sun.star.comp.ole.OleServer") + { + xFactory= createOneInstanceFactory( static_cast< XMultiServiceFactory*>(pServiceManager), + aImplName, + OleServer_CreateInstance, seqServiceNames ); + } + + if (xFactory.is()) + { + xFactory->acquire(); + pRet = xFactory.get(); + } + + return pRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/unoconversionutilities.hxx b/extensions/source/ole/unoconversionutilities.hxx new file mode 100644 index 0000000000..a73a714abe --- /dev/null +++ b/extensions/source/ole/unoconversionutilities.hxx @@ -0,0 +1,2363 @@ +/* -*- 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 . + */ +#pragma once + +#include <memory> +#include <com/sun/star/script/CannotConvertException.hpp> +#include <com/sun/star/script/XInvocationAdapterFactory.hpp> +#include <com/sun/star/script/XInvocationAdapterFactory2.hpp> +#include <com/sun/star/script/XTypeConverter.hpp> +#include <com/sun/star/script/FailReason.hpp> +#include <com/sun/star/bridge/ModelDependent.hpp> +#include <com/sun/star/bridge/XBridgeSupplier2.hpp> +#include <com/sun/star/bridge/oleautomation/Date.hpp> +#include <com/sun/star/bridge/oleautomation/Currency.hpp> +#include <com/sun/star/bridge/oleautomation/SCode.hpp> +#include <com/sun/star/bridge/oleautomation/Decimal.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <typelib/typedescription.hxx> +#include <o3tl/any.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include "ole2uno.hxx" +#include <cppuhelper/weakref.hxx> + +#include "unotypewrapper.hxx" +#include <unordered_map> + +// for some reason DECIMAL_NEG (wtypes.h) which contains BYTE is not resolved. +typedef unsigned char BYTE; +// classes for wrapping uno objects +#define INTERFACE_OLE_WRAPPER_IMPL 1 +#define UNO_OBJECT_WRAPPER_REMOTE_OPT 2 + +#define INVOCATION_SERVICE "com.sun.star.script.Invocation" + + +// classes for wrapping ole objects +#define IUNKNOWN_WRAPPER_IMPL 1 + +#define INTERFACE_ADAPTER_FACTORY "com.sun.star.script.InvocationAdapterFactory" +// COM or JScript objects implementing UNO interfaces have to implement this property +#define SUPPORTED_INTERFACES_PROP L"_implementedInterfaces" +// Second property without leading underscore for use in VB +#define SUPPORTED_INTERFACES_PROP2 L"Bridge_ImplementedInterfaces" + +using namespace com::sun::star::script; +using namespace com::sun::star::beans; +using namespace com::sun::star::uno; +using namespace com::sun::star::bridge::oleautomation; + +extern std::unordered_map<sal_uIntPtr, sal_uIntPtr> AdapterToWrapperMap; +extern std::unordered_map<sal_uIntPtr, sal_uIntPtr> WrapperToAdapterMap; + +//Maps IUnknown pointers to a weak reference of the respective wrapper class (e.g. +// IUnknownWrapperImpl. It is the responsibility of the wrapper to remove the entry when +// it is being destroyed. +// Used to ensure that an Automation object is always mapped to the same UNO objects. +extern std::unordered_map<sal_uIntPtr, WeakReference<XInterface> > ComPtrToWrapperMap; + +// Maps XInterface pointers to a weak reference of its wrapper class (i.e. +// InterfaceOleWrapper). It is the responsibility of the wrapper to remove the entry when +// it is being destroyed. It is used to ensure the identity of objects. That is, a UNO interface +// is mapped to IDispatch which is kept alive in the COM environment. If the same +// UNO interface is mapped again to COM then the IDispach of the first mapped instance +// must be returned. +extern std::unordered_map<sal_uIntPtr, WeakReference<XInterface> > UnoObjToWrapperMap; + +// This function tries to the change the type of a value (contained in the Any) +// to the smallest possible that can hold the value. This is actually done only +// for types of VT_I4 (see o2u_variantToAny). The reason is the following: +// JavaScript passes integer values always as VT_I4. If there is a parameter or +// property of type any then the bridge converts the any's content according +// to "o2u_variantToAny". Because the VARTYPE is VT_I4 the value would be converted +// to TypeClass_LONG. Say the method XPropertySet::setPropertyValue( string name, any value) +// would be called on an object and the property actually is of TypeClass_SHORT. +// After conversion of the VARIANT parameter the Any would contain type +// TypeClass_LONG. Because the corereflection does not cast from long to short +// the "setPropertValue" would fail as the value has not the right type. + +// The corereflection does convert small integer types to bigger types. +// Therefore we can reduce the type if possible and avoid the above mentioned +// problem. + +// The function is not used when elements are to be converted for Sequences. + +inline void reduceRange( Any& any) +{ + OSL_ASSERT( any.getValueTypeClass() == TypeClass_LONG); + + sal_Int32 value= *o3tl::doAccess<sal_Int32>(any); + if( value <= 0x7f && value >= -0x80) + {// -128 bis 127 + sal_Int8 charVal= static_cast<sal_Int8>( value); + any.setValue( &charVal, cppu::UnoType<sal_Int8>::get()); + } + else if( value <= 0x7fff && value >= -0x8000) + {// -32768 bis 32767 + sal_Int16 shortVal= static_cast<sal_Int16>( value); + any.setValue( &shortVal, cppu::UnoType<sal_Int16>::get()); + } +} + +// createUnoObjectWrapper gets a wrapper instance by calling createUnoWrapperInstance + // and initializes it via XInitialization. The wrapper object is required to implement + // XBridgeSupplier so that it can convert itself to IDispatch. + // class T: Deriving class ( must implement XInterface ) +/** All methods are allowed to throw at least a BridgeRuntimeError. + */ +template< class > +class UnoConversionUtilities +{ +public: + explicit UnoConversionUtilities( const Reference<XMultiServiceFactory> & smgr): + m_nUnoWrapperClass( INTERFACE_OLE_WRAPPER_IMPL), + m_nComWrapperClass( IUNKNOWN_WRAPPER_IMPL), + m_smgr( smgr) + {} + + UnoConversionUtilities( const Reference<XMultiServiceFactory> & xFactory, sal_uInt8 unoWrapperClass, sal_uInt8 comWrapperClass ) + : m_nUnoWrapperClass(unoWrapperClass), + m_nComWrapperClass(comWrapperClass), m_smgr(xFactory) + {} + + virtual ~UnoConversionUtilities() {} + /** converts only into oleautomation types, that is there is no VT_I1, VT_UI2, VT_UI4 + a sal_Unicode character is converted into a BSTR. + @exception com.sun.star.lang.IllegalArgumentException + If the any was inappropriate for conversion. + @exception com.sun.star.script.CannotConvertException + The any contains a type class for which no conversion is provided. + */ + void anyToVariant(VARIANT* pVariant, const Any& rAny); + void anyToVariant(VARIANT* pVariant, const Any& rAny, VARTYPE type); + + /** @exception com.sun.star.lang.IllegalArgumentException + If rSeq does not contain a sequence then the exception is thrown. + */ + SAFEARRAY* createUnoSequenceWrapper(const Any& rSeq); + /** @exception com.sun.star.lang.IllegalArgumentException + If rSeq does not contain a sequence or elemtype has no proper value + then the exception is thrown. + */ + SAFEARRAY* createUnoSequenceWrapper(const Any& rSeq, VARTYPE elemtype); + /** + @exception com.sun.star.lang.IllegalArgumentException + If rObj does not contain a struct or interface + */ + void createUnoObjectWrapper(const Any & rObj, VARIANT * pVar); + /** @exception CannotConvertException + Thrown if the VARIANT contains a type that cannot be coerced in the expected Any. + ArgumentIndex is 0. + @IllegalArgumentException + Thrown if the VARIANT is inappropriate for conversion. ArgumentPosition is -1, + */ + void variantToAny(const VARIANT* pVariant, Any& rAny, bool bReduceValueRange = true); + /** This method converts variants arguments in calls from COM -> UNO. Only then + the expected UNO type is known. + @exception CannotConvertException + Thrown if the VARIANT contains a type that cannot be coerced in the expected Any. + ArgumentIndex is 0. + @IllegalArgumentException + Thrown if the VARIANT is inappropriate for conversion. ArgumentPosition is -1, + */ + void variantToAny( const VARIANTARG* pArg, Any& rAny, const Type& ptype, bool bReduceValueRange = true); + + /** + @exception IllegalArgumentException + -if pVar does not contain VT_UNKNOWN or VT_DISPATCH or + pVar is used for a particular UNO type which is not supported by pVar + */ + Any createOleObjectWrapper(VARIANT* pVar, const Type& aType= Type()); + + /* + Return true means var contained a ValueObject, and it was successfully converted. + The result is in any. It an error occurred a BridgeRuntimeError will be thrown. + */ + bool convertValueObject( const VARIANTARG *var, Any& any); + void dispatchExObject2Sequence( const VARIANTARG* pvar, Any& anySeq, const Type& type); + + Sequence<Any> createOleArrayWrapperOfDim(SAFEARRAY* pArray, unsigned int dimCount, unsigned int actDim, LONG* index, + VARTYPE type, const Type& unotype); + Sequence<Any> createOleArrayWrapper(SAFEARRAY* pArray, VARTYPE type, const Type& unotype= Type()); + + + VARTYPE mapTypeClassToVartype( TypeClass type); + Reference< XSingleServiceFactory > getInvocationFactory(const Any& anyObject); + + + virtual Reference< XInterface > createUnoWrapperInstance()=0; + virtual Reference< XInterface > createComWrapperInstance()=0; + + static bool isJScriptArray(const VARIANT* pvar); + + Sequence<Type> getImplementedInterfaces(IUnknown* pUnk); + +protected: + Reference<XInterface> createAdapter(const Sequence<Type>& types, const Reference<XInterface>& receiver); + + // helper function for Sequence conversion + void getElementCountAndTypeOfSequence( const Any& rSeq, sal_Int32 dim, Sequence< sal_Int32 >& seqElementCounts, TypeDescription& typeDesc); + // helper function for Sequence conversion + static bool incrementMultidimensionalIndex(sal_Int32 dimensions, const sal_Int32 * parDimensionLength, + sal_Int32 * parMultidimensionalIndex); + // helper function for Sequence conversion + static size_t getOleElementSize( VARTYPE type); + + static Type getElementTypeOfSequence( const Type& seqType); + + //Provides a typeconverter + Reference<XTypeConverter> getTypeConverter(); + + // This member determines what class is used to convert a UNO object + // or struct to a COM object. It is passed along to the anyToVariant + // function in the createBridge function implementation + const sal_uInt8 m_nUnoWrapperClass; + const sal_uInt8 m_nComWrapperClass; + + // The servicemanager is either a local smgr or remote when the service + // com.sun.star.bridge.OleBridgeSupplierVar1 is used. This service can be + // created by createInstanceWithArguments where one can supply a service + // manager that is to be used. + // Local service manager as supplied by the loader when the creator function + // of the service is being called. + Reference<XMultiServiceFactory> m_smgr; + // An explicitly supplied service manager when the service + // com.sun.star.bridge.OleBridgeSupplierVar1 is used. That can be a remote + // manager. + Reference<XMultiServiceFactory> m_smgrRemote; + Reference<XSingleServiceFactory> m_xInvocationFactoryLocal; + Reference<XSingleServiceFactory> m_xInvocationFactoryRemote; + +private: + // Holds the type converter which is used for sequence conversion etc. + // Use the getTypeConverter function to obtain the interface. + Reference<XTypeConverter> m_typeConverter; + + +}; + +// ask the object for XBridgeSupplier2 and on success bridges +// the uno object to IUnknown or IDispatch. +// return true the UNO object supports +template < class T > +bool convertSelfToCom( T& unoInterface, VARIANT * pVar) +{ + bool ret = false; + Reference< XInterface > xInt( unoInterface, UNO_QUERY); + if( xInt.is()) + { + Reference< css::bridge::XBridgeSupplier2 > xSupplier( xInt, UNO_QUERY); + if( xSupplier.is()) + { + sal_Int8 arId[16]; + rtl_getGlobalProcessId( reinterpret_cast<sal_uInt8*>(arId)); + Sequence<sal_Int8> seqId( arId, 16); + Any anySource; + anySource <<= xInt; + Any anyDisp = xSupplier->createBridge( + anySource, seqId, css::bridge::ModelDependent::UNO, + css::bridge::ModelDependent::OLE); + + // due to global-process-id check this must be in-process pointer + if (auto v = o3tl::tryAccess<sal_uIntPtr>(anyDisp)) + { + VARIANT* pvariant= reinterpret_cast<VARIANT*>(*v); + HRESULT hr; + if (FAILED(hr = VariantCopy(pVar, pvariant))) + throw BridgeRuntimeError( + "[automation bridge] convertSelfToCom\n" + "VariantCopy failed! Error: " + + OUString::number(hr)); + VariantClear( pvariant); + CoTaskMemFree( pvariant); + ret = true; + } + } + } + return ret; +} + + +// Gets the invocation factory depending on the Type in the Any. +// The factory can be created by a local or remote multi service factory. +// In case there is a remote multi service factory available there are +// some services or types for which the local factory is used. The exceptions +// are: all structs. +// Param anyObject - contains the object ( interface, struct) for what we need an invocation object. + +template<class T> +Reference< XSingleServiceFactory > UnoConversionUtilities<T>::getInvocationFactory(const Any& anyObject) +{ + Reference< XSingleServiceFactory > retVal; + MutexGuard guard( getBridgeMutex()); + if( anyObject.getValueTypeClass() != TypeClass_STRUCT && + m_smgrRemote.is() ) + { + if( ! m_xInvocationFactoryRemote.is() ) + m_xInvocationFactoryRemote.set(m_smgrRemote->createInstance( INVOCATION_SERVICE), UNO_QUERY); + retVal= m_xInvocationFactoryRemote; + } + else + { + if( ! m_xInvocationFactoryLocal.is() ) + m_xInvocationFactoryLocal.set(m_smgr->createInstance(INVOCATION_SERVICE ), UNO_QUERY); + retVal= m_xInvocationFactoryLocal; + } + return retVal; +} + +template<class T> +void UnoConversionUtilities<T>::variantToAny( const VARIANTARG* pArg, Any& rAny, const Type& ptype, bool bReduceValueRange /* = sal_True */) +{ + try + { + HRESULT hr; + bool bFail = false; + bool bCannotConvert = false; + CComVariant var; + + // There is no need to support indirect values, since they're not supported by UNO + if( FAILED(hr= VariantCopyInd( &var, pArg))) // remove VT_BYREF + throw BridgeRuntimeError( + "[automation bridge] UnoConversionUtilities<T>::variantToAny \n" + "VariantCopyInd failed for reason : " + OUString::number(hr)); + bool bHandled = convertValueObject( & var, rAny); + if( bHandled) + OSL_ENSURE( rAny.getValueType() == ptype, "type in Value Object must match the type parameter"); + + if( ! bHandled) + { + // convert into a variant type that is the equivalent to the type + // the sequence expects. Thus variantToAny produces the correct type + // E.g. An Array object contains VT_I4 and the sequence expects shorts + // than the vartype must be changed. The reason is, you can't specify the + // type in JavaScript and the script engine determines the type being used. + switch( ptype.getTypeClass()) + { + case TypeClass_CHAR: // could be: new Array( 12, 'w', "w") + if( var.vt == VT_BSTR) + { + if(SUCCEEDED( hr= VariantChangeType( &var, &var, 0, VT_BSTR))) + rAny.setValue( V_BSTR( &var), ptype); + else if (hr == DISP_E_TYPEMISMATCH) + bCannotConvert = true; + else + bFail = true; + } + else + { + if(SUCCEEDED(hr = VariantChangeType( & var, &var, 0, VT_I2))) + rAny.setValue(& var.iVal, ptype); + else if (hr == DISP_E_TYPEMISMATCH) + bCannotConvert = true; + else + bFail = true; + } + break; + case TypeClass_INTERFACE: // could also be an IUnknown + case TypeClass_STRUCT: + { + rAny = createOleObjectWrapper( & var, ptype); + break; + } + case TypeClass_ENUM: + if(SUCCEEDED(hr = VariantChangeType( & var, &var, 0, VT_I4))) + rAny.setValue(& var.lVal, ptype); + else if (hr == DISP_E_TYPEMISMATCH) + bCannotConvert = true; + else + bFail = true; + break; + case TypeClass_SEQUENCE: + // There are different ways of receiving a sequence: + // 1: JScript, VARTYPE: VT_DISPATCH + // 2. VBScript simple arraysVT_VARIANT|VT_BYREF the referenced VARIANT contains + // a VT_ARRAY| <type> + // 3. VBScript multi dimensional arrays: VT_ARRAY|VT_BYREF + if( pArg->vt == VT_DISPATCH) + { + dispatchExObject2Sequence( pArg, rAny, ptype); + } + else + { + if ((var.vt & VT_ARRAY) != 0) + { + VARTYPE oleType = ::sal::static_int_cast< VARTYPE, int >( var.vt ^ VT_ARRAY ); + Sequence<Any> unoSeq = createOleArrayWrapper( var.parray, oleType, ptype); + Reference<XTypeConverter> conv = getTypeConverter(); + if (conv.is()) + { + try + { + Any anySeq(unoSeq); + Any convAny = conv->convertTo(anySeq, ptype); + rAny = convAny; + } + catch (const IllegalArgumentException& e) + { + throw BridgeRuntimeError( + "[automation bridge]com.sun.star.lang.IllegalArgumentException " + "in UnoConversionUtilities<T>::variantToAny! Message: " + + e.Message); + } + catch (const CannotConvertException& e) + { + throw BridgeRuntimeError( + "[automation bridge]com.sun.star.script.CannotConvertException " + "in UnoConversionUtilities<T>::variantToAny! Message: " + + e.Message); + } + } + } + } + break; + case TypeClass_VOID: + rAny.setValue(nullptr,Type()); + break; + case TypeClass_ANY: // Any + // There could be a JScript Array that needs special handling + // If an Any is expected and this Any must contain a Sequence + // then we cannot figure out what element type is required. + // Therefore we convert to Sequence< Any > + if( pArg->vt == VT_DISPATCH && isJScriptArray( pArg)) + { + dispatchExObject2Sequence( pArg, rAny, + cppu::UnoType<Sequence<Any>>::get()); + } + else if (pArg->vt == VT_DECIMAL) + { + //Decimal maps to hyper in calls from COM -> UNO + // It does not matter if we create a sal_uInt64 or sal_Int64, + // because the UNO object is called through invocation which + //will do a type conversion if necessary + if (var.decVal.sign == 0) + { + // positive value + variantToAny( & var, rAny, cppu::UnoType<sal_uInt64>::get(), + bReduceValueRange); + } + else + { + //negative value + variantToAny( & var, rAny, cppu::UnoType<sal_Int64>::get(), + bReduceValueRange); + } + } + else + { + variantToAny( & var, rAny); + } + break; + case TypeClass_BOOLEAN: // VARIANT could be VARIANT_BOOL or other + if(SUCCEEDED(hr = VariantChangeType( & var, &var, 0, VT_BOOL))) + variantToAny( & var, rAny); + else if (hr == DISP_E_TYPEMISMATCH) + bCannotConvert = true; + else + bFail = true; + break; + case TypeClass_STRING: // UString + if(var.vt == VT_NULL) + var = CComBSTR(""); + if(SUCCEEDED(hr = VariantChangeType( & var, &var, 0, VT_BSTR))) + variantToAny( & var, rAny); + else if (hr == DISP_E_TYPEMISMATCH) + bCannotConvert = true; + else + bFail = true; + break; + case TypeClass_FLOAT: // float + if(SUCCEEDED(hr = VariantChangeType( & var, &var, 0, VT_R4))) + variantToAny( & var, rAny); + else if (hr == DISP_E_TYPEMISMATCH) + bCannotConvert = true; + else + bFail = true; + break; + case TypeClass_DOUBLE: // double + if(SUCCEEDED(hr = VariantChangeType( & var, &var, 0, VT_R8))) + variantToAny(& var, rAny); + else if (hr == DISP_E_TYPEMISMATCH) + bCannotConvert = true; + else + bFail = true; + break; + case TypeClass_BYTE: // BYTE + if(SUCCEEDED(hr = VariantChangeType( & var, &var, 0, VT_I1))) + variantToAny( & var, rAny); + else if (hr == DISP_E_TYPEMISMATCH) + bCannotConvert = true; + else + bFail = true; + break; + case TypeClass_SHORT: // INT16 + if(SUCCEEDED(hr = VariantChangeType( & var, &var, 0, VT_I2))) + variantToAny( & var, rAny); + else if (hr == DISP_E_TYPEMISMATCH) + bCannotConvert = true; + else + bFail = true; + break; + case TypeClass_LONG: + if(SUCCEEDED(hr = VariantChangeType(& var, &var, 0, VT_I4))) + variantToAny( & var, rAny, bReduceValueRange); + else if (hr == DISP_E_TYPEMISMATCH) + bCannotConvert = true; + else + bFail = true; + break; + case TypeClass_HYPER: + if(SUCCEEDED(hr = VariantChangeType(& var, &var, 0, VT_DECIMAL))) + { + if (var.decVal.Lo64 > SAL_CONST_UINT64(0x8000000000000000) + || var.decVal.Hi32 > 0 + || var.decVal.scale > 0) + { + bFail = true; + break; + } + sal_Int64 value = var.decVal.Lo64; + if (var.decVal.sign == DECIMAL_NEG) + value |= SAL_CONST_UINT64(0x8000000000000000); + rAny <<= value; + } + else if (hr == DISP_E_TYPEMISMATCH) + bCannotConvert = true; + else + bFail = true; + break; + case TypeClass_UNSIGNED_SHORT: // UINT16 + if(SUCCEEDED(hr = VariantChangeType( & var, &var, 0, VT_UI2))) + variantToAny( & var, rAny); + else if (hr == DISP_E_TYPEMISMATCH) + bCannotConvert = true; + else + bFail = true; + break; + case TypeClass_UNSIGNED_LONG: + if(SUCCEEDED(hr = VariantChangeType( & var, &var, 0, VT_UI4))) + variantToAny( & var, rAny, bReduceValueRange); + else if (hr == DISP_E_TYPEMISMATCH) + bCannotConvert = true; + else + bFail = true; + break; + case TypeClass_UNSIGNED_HYPER: + if(SUCCEEDED(hr = VariantChangeType(& var, &var, 0, VT_DECIMAL))) + { + if (var.decVal.Hi32 > 0 || var.decVal.scale > 0) + { + bFail = true; + break; + } + rAny <<= var.decVal.Lo64; + } + else if (hr == DISP_E_TYPEMISMATCH) + bCannotConvert = true; + else + bFail = true; + break; + case TypeClass_TYPE: + if(SUCCEEDED(hr = VariantChangeType(& var, &var, 0, VT_UNKNOWN))) + variantToAny( & var, rAny); + else if (hr == DISP_E_TYPEMISMATCH) + bCannotConvert = true; + else + bFail = true; + break; + default: + bCannotConvert = true; + break; + } + } + if (bCannotConvert) + throw CannotConvertException( + "[automation bridge]UnoConversionUtilities<T>::variantToAny \n" + "Cannot convert the value of vartype :\"" + + OUString::number(static_cast<sal_Int32>(var.vt)) + + "\" to the expected UNO type of type class: " + + OUString::number(static_cast<sal_Int32>(ptype.getTypeClass())), + nullptr, TypeClass_UNKNOWN, FailReason::TYPE_NOT_SUPPORTED,0); + + if (bFail) + throw IllegalArgumentException( + "[automation bridge]UnoConversionUtilities<T>:variantToAny\n" + "The provided VARIANT of type\" " + OUString::number(static_cast<sal_Int32>(var.vt)) + + "\" is unappropriate for conversion!", Reference<XInterface>(), -1); + } + catch (const CannotConvertException &) + { + throw; + } + catch (const IllegalArgumentException &) + { + throw; + } + catch (const BridgeRuntimeError &) + { + throw; + } + catch (const Exception & e) + { + throw BridgeRuntimeError("[automation bridge] unexpected exception in " + "UnoConversionUtilities<T>::variantToAny ! Message : \n" + + e.Message); + } + catch(...) + { + throw BridgeRuntimeError( + "[automation bridge] unexpected exception in " + "UnoConversionUtilities<T>::variantToAny !"); + } +} + +// The function only converts Sequences to SAFEARRAYS with elements of the type +// specified by the parameter type. Everything else is forwarded to +// anyToVariant(VARIANT* pVariant, const Any& rAny) +// Param type must not be VT_BYREF +template<class T> +void UnoConversionUtilities<T>::anyToVariant(VARIANT* pVariant, const Any& rAny, VARTYPE type) +{ + try + { + HRESULT hr= S_OK; + + OSL_ASSERT( (type & VT_BYREF) == 0); + if (type & VT_ARRAY) + { + type ^= VT_ARRAY; + SAFEARRAY* ar= createUnoSequenceWrapper( rAny, type); + if( ar) + { + VariantClear( pVariant); + pVariant->vt= ::sal::static_int_cast< VARTYPE, int >( VT_ARRAY | type ); + pVariant->byref= ar; + } + } + else if(type == VT_VARIANT) + { + anyToVariant(pVariant, rAny); + } + else + { + CComVariant var; + anyToVariant( &var, rAny); + if(FAILED(hr = VariantChangeType(&var, &var, 0, type))) + { + if (hr == DISP_E_TYPEMISMATCH) + throw CannotConvertException( + "[automation bridge]UnoConversionUtilities<T>::anyToVariant \n" + "Cannot convert the value of type :\"" + + rAny.getValueTypeName() + + "\" to the expected Automation type of VARTYPE: " + + OUString::number(static_cast<sal_Int32>(type)), + nullptr, TypeClass_UNKNOWN, FailReason::TYPE_NOT_SUPPORTED,0); + + throw BridgeRuntimeError( + "[automation bridge]UnoConversionUtilities<T>::anyToVariant \n" + "Conversion of any with " + + rAny.getValueType().getTypeName() + + " to VARIANT with type: " + OUString::number(static_cast<sal_Int32>(type)) + + " failed! Error code: " + OUString::number(hr)); + + } + if(FAILED(hr = VariantCopy(pVariant, &var))) + { + throw BridgeRuntimeError( + "[automation bridge]UnoConversionUtilities<T>::anyToVariant \n" + "VariantCopy failed for reason: " + OUString::number(hr)); + } + } + } + catch (const IllegalArgumentException &) + { + throw; + } + catch (const CannotConvertException &) + { + throw; + } + catch (const BridgeRuntimeError&) + { + throw; + } + catch(const Exception & e) + { + throw BridgeRuntimeError( + "[automation bridge]UnoConversionUtilities<T>::anyToVariant \n" + "Unexpected exception occurred. Message: " + e.Message); + } + catch(...) + { + throw BridgeRuntimeError( + "[automation bridge]UnoConversionUtilities<T>::anyToVariant \n" + "Unexpected exception occurred."); + } +} + +template<class T> +void UnoConversionUtilities<T>::anyToVariant(VARIANT* pVariant, const Any& rAny) +{ + try + { + bool bIllegal = false; + switch (rAny.getValueTypeClass()) + { + case TypeClass_INTERFACE: + { + Reference<XInterface> xInt; + if (rAny >>= xInt) + { + createUnoObjectWrapper(rAny, pVariant); + } + else + { + bIllegal = true; + } + break; + } + case TypeClass_STRUCT: + { + if (rAny.getValueType() == cppu::UnoType<Date>::get() ) + { + Date d; + if (rAny >>= d) + { + pVariant->vt = VT_DATE; + pVariant->date = d.Value; + } + else + { + bIllegal = true; + } + } + else if(rAny.getValueType() == cppu::UnoType<Decimal>::get()) + { + Decimal d; + if (rAny >>= d) + { + pVariant->vt = VT_DECIMAL; + pVariant->decVal.scale = d.Scale; + pVariant->decVal.sign = d.Sign; + pVariant->decVal.Lo32 = d.LowValue; + pVariant->decVal.Mid32 = d.MiddleValue; + pVariant->decVal.Hi32 = d.HighValue; + } + else + { + bIllegal = true; + } + } + else if (rAny.getValueType() == cppu::UnoType<Currency>::get()) + { + Currency c; + if (rAny >>= c) + { + pVariant->vt = VT_CY; + pVariant->cyVal.int64 = c.Value; + } + else + { + bIllegal = true; + } + } + else if(rAny.getValueType() == cppu::UnoType<SCode>::get()) + { + SCode s; + if (rAny >>= s) + { + pVariant->vt = VT_ERROR; + pVariant->scode = s.Value; + } + else + { + bIllegal = true; + } + } + else + { + createUnoObjectWrapper(rAny, pVariant); + } + break; + } + case TypeClass_SEQUENCE: // sequence ??? SafeArray descriptor + { + SAFEARRAY* pArray = createUnoSequenceWrapper(rAny); + if (pArray) + { + V_VT(pVariant) = VT_ARRAY | VT_VARIANT; + V_ARRAY(pVariant) = pArray; + } + else + { + bIllegal = true; + } + break; + } + case TypeClass_VOID: + { + HRESULT hr = S_OK; + if (FAILED(hr = VariantClear(pVariant))) + { + throw BridgeRuntimeError( + "[automation bridge]UnoConversionUtilities<T>::anyToVariant\n" + "VariantClear failed with error:" + OUString::number(hr)); + } + break; + } + case TypeClass_BOOLEAN: + { + bool value; + if (rAny >>= value) + { + pVariant->vt = VT_BOOL; + pVariant->boolVal = value ? VARIANT_TRUE: VARIANT_FALSE; + } + else + { + bIllegal = true; + } + break; + } + case TypeClass_CHAR: + { + // Because VT_UI2 does not conform to oleautomation we convert into VT_I2 instead + sal_uInt16 value = *o3tl::forceAccess<sal_Unicode>(rAny); + pVariant->vt = VT_I2; + pVariant->iVal = value; + break; + } + case TypeClass_STRING: + { + OUString value; + if (rAny >>= value) + { + pVariant->vt = VT_BSTR; + pVariant->bstrVal = SysAllocString(o3tl::toW(value.getStr())); + } + else + { + bIllegal = true; + } + break; + } + case TypeClass_FLOAT: + { + float value; + if (rAny >>= value) + { + pVariant->vt = VT_R4; + pVariant->fltVal = value; + } + else + { + bIllegal = true; + } + break; + } + case TypeClass_DOUBLE: + { + double value; + if (rAny >>= value) + { + pVariant->vt = VT_R8; + pVariant->dblVal = value; + } + else + { + bIllegal = true; + } + break; + } + case TypeClass_BYTE: + { + // ole automation does not know a signed char but only unsigned char + sal_Int8 value; + if (rAny >>= value) + { + pVariant->vt = VT_UI1; + pVariant->bVal = value; + } + else + { + bIllegal = true; + } + break; + } + case TypeClass_SHORT: // INT16 + case TypeClass_UNSIGNED_SHORT: // UINT16 + { + sal_Int16 value; + if (rAny >>= value) + { + pVariant->vt = VT_I2; + pVariant->iVal = value; + } + else + { + bIllegal = true; + } + break; + } + case TypeClass_ENUM: + { + sal_Int32 value = *static_cast<sal_Int32 const *>(rAny.getValue()); + pVariant->vt = VT_I4; + pVariant->lVal= value; + break; + } + case TypeClass_LONG: + case TypeClass_UNSIGNED_LONG: + { + sal_Int32 value; + if (rAny >>= value) + { + pVariant->vt = VT_I4; + pVariant->lVal= value; + } + else + { + bIllegal = true; + } + break; + } + case TypeClass_HYPER: + { + + pVariant->vt = VT_DECIMAL; + pVariant->decVal.scale = 0; + pVariant->decVal.sign = 0; + pVariant->decVal.Hi32 = 0; + + sal_Int64 value; + rAny >>= value; + + if (value & SAL_CONST_UINT64(0x8000000000000000)) + pVariant->decVal.sign = DECIMAL_NEG; + + pVariant->decVal.Lo64 = value; + break; + } + case TypeClass_UNSIGNED_HYPER: + { + pVariant->vt = VT_DECIMAL; + pVariant->decVal.scale = 0; + pVariant->decVal.sign = 0; + pVariant->decVal.Hi32 = 0; + + sal_uInt64 value; + rAny >>= value; + pVariant->decVal.Lo64 = value; + break; + } + case TypeClass_TYPE: + { + Type type; + rAny >>= type; + CComVariant var; + if (!createUnoTypeWrapper(type.getTypeName(), & var)) + throw BridgeRuntimeError( + "[automation bridge] UnoConversionUtilities<T>::anyToVariant \n" + "Error during conversion of UNO type to Automation object!"); + + if (FAILED(VariantCopy(pVariant, &var))) + throw BridgeRuntimeError( + "[automation bridge] UnoConversionUtilities<T>::anyToVariant \n" + "Unexpected error!"); + break; + } + default: + //TypeClass_SERVICE: + //TypeClass_EXCEPTION: + //When an InvocationTargetException is thrown when calling XInvocation::invoke + //on a UNO object, then the target exception is directly used to create a + //EXEPINFO structure + //TypeClass_TYPEDEF + //TypeClass_ANY: + //TypeClass_UNKNOWN: + //TypeClass_MODULE: + throw CannotConvertException( + "[automation bridge]UnoConversionUtilities<T>::anyToVariant\n" + "There is no conversion for this UNO type to an Automation type." + "The destination type class is the type class of the UNO " + "argument which was to be converted.", + Reference<XInterface>(), rAny.getValueTypeClass(), + FailReason::TYPE_NOT_SUPPORTED, 0); + + break; + } + if (bIllegal) + { + throw IllegalArgumentException( + "[automation bridge]UnoConversionUtilities<T>::anyToVariant\n" + "The provided any of type\" " + rAny.getValueType().getTypeName() + + "\" is unappropriate for conversion!", Reference<XInterface>(), -1); + + } + } + catch (const CannotConvertException &) + { + throw; + } + catch (const IllegalArgumentException &) + { + throw; + } + catch(const BridgeRuntimeError&) + { + throw; + } + catch(const Exception & e) + { + throw BridgeRuntimeError( + "[automation bridge]UnoConversionUtilities<T>::anyToVariant \n" + "Unexpected exception occurred. Message: " + e.Message); + } + catch(...) + { + throw BridgeRuntimeError( + "[automation bridge]UnoConversionUtilities<T>::anyToVariant \n" + "Unexpected exception occurred. " ); + } +} + +// Creates an SAFEARRAY of the specified element and if necessary +// creates a SAFEARRAY with multiple dimensions. +// Used by sal_Bool anyToVariant(VARIANT* pVariant, const Any& rAny, VARTYPE type); +template<class T> +SAFEARRAY* UnoConversionUtilities<T>::createUnoSequenceWrapper(const Any& rSeq, VARTYPE elemtype) +{ + if (rSeq.getValueTypeClass() != TypeClass_SEQUENCE) + throw IllegalArgumentException( + "[automation bridge]UnoConversionUtilities<T>::createUnoSequenceWrapper \n" + "The any does not contain a sequence!", nullptr, 0); + if (elemtype == VT_NULL || elemtype == VT_EMPTY) + throw IllegalArgumentException( + "[automation bridge]UnoConversionUtilities<T>::createUnoSequenceWrapper \n" + "No element type supplied!",nullptr, -1); + SAFEARRAY* pArray= nullptr; + // Get the dimensions. This is done by examining the type name string + // The count of brackets determines the dimensions. + OUString sTypeName= rSeq.getValueType().getTypeName(); + sal_Int32 dims=0; + for(sal_Int32 lastIndex=0;(lastIndex= sTypeName.indexOf( L'[', lastIndex)) != -1; lastIndex++,dims++); + + //get the maximum number of elements per dimensions and the typedescription of the elements + Sequence<sal_Int32> seqElementCounts( dims); + TypeDescription elementTypeDesc; + getElementCountAndTypeOfSequence( rSeq, 1, seqElementCounts, elementTypeDesc ); + + if( elementTypeDesc.is() ) + { + // set up the SAFEARRAY + std::unique_ptr<SAFEARRAYBOUND[]> sarSafeArrayBound(new SAFEARRAYBOUND[dims]); + SAFEARRAYBOUND* prgsabound= sarSafeArrayBound.get(); + for( sal_Int32 i=0; i < dims; i++) + { + //prgsabound[0] is the right most dimension + prgsabound[dims - i - 1].lLbound = 0; + prgsabound[dims - i - 1].cElements = seqElementCounts[i]; + } + + typelib_TypeDescription* rawTypeDesc= elementTypeDesc.get(); + sal_Int32 elementSize= rawTypeDesc->nSize; + size_t oleElementSize= getOleElementSize( elemtype); + // SafeArrayCreate clears the memory for the data itself. + pArray = SafeArrayCreate(elemtype, dims, prgsabound); + + // convert the Sequence's elements and populate the SAFEARRAY + if( pArray) + { + // Iterate over every Sequence that contains the actual elements + void* pSAData; + if( SUCCEEDED( SafeArrayAccessData( pArray, &pSAData))) + { + const sal_Int32* parElementCount= seqElementCounts.getConstArray(); + uno_Sequence * pMultiSeq= *static_cast<uno_Sequence* const*>(rSeq.getValue()); + sal_Int32 dimsSeq= dims - 1; + + // arDimSeqIndices contains the current index of a block of data. + // E.g. Sequence<Sequence<sal_Int32>> , the index would refer to Sequence<sal_Int32> + // In this case arDimSeqIndices would have the size 1. That is the elements are not counted + // but the Sequences that contain those elements. + // The indices are 0 based + std::unique_ptr<sal_Int32[]> sarDimsSeqIndices; + sal_Int32* arDimsSeqIndices= nullptr; + if( dimsSeq > 0) + { + sarDimsSeqIndices.reset(new sal_Int32[dimsSeq]); + arDimsSeqIndices = sarDimsSeqIndices.get(); + memset( arDimsSeqIndices, 0, sizeof( sal_Int32 ) * dimsSeq); + } + + char* psaCurrentData= static_cast<char*>(pSAData); + + do + { + // Get the Sequence at the current index , see arDimsSeqIndices + uno_Sequence * pCurrentSeq= pMultiSeq; + sal_Int32 curDim=1; // 1 based + bool skipSeq= false; + while( curDim <= dimsSeq ) + { + // get the Sequence at the index if valid + if( pCurrentSeq->nElements > arDimsSeqIndices[ curDim - 1] ) // don't point to Nirvana + { + // size of Sequence is 4 + sal_Int32 offset= arDimsSeqIndices[ curDim - 1] * 4; + pCurrentSeq= *reinterpret_cast<uno_Sequence**>(&pCurrentSeq->elements[ offset]); + curDim++; + } + else + { + // There is no Sequence at this index, so skip this index + skipSeq= true; + break; + } + } + + if( skipSeq) + continue; + + // Calculate the current position within the datablock of the SAFEARRAY + // for the next Sequence. + sal_Int32 memOffset= 0; + sal_Int32 dimWeight= parElementCount[ dims - 1]; // size of the rightmost dimension + for(sal_Int32 idims=0; idims < dimsSeq; idims++ ) + { + memOffset+= arDimsSeqIndices[dimsSeq - 1 - idims] * dimWeight; + // now determine the weight of the dimension to the left of the current. + if( dims - 2 - idims >=0) + dimWeight*= parElementCount[dims - 2 - idims]; + } + psaCurrentData= static_cast<char*>(pSAData) + memOffset * oleElementSize; + // convert the Sequence and put the elements into the Safearray + for( sal_Int32 i= 0; i < pCurrentSeq->nElements; i++) + { + Any unoElement( pCurrentSeq->elements + i * elementSize, rawTypeDesc ); + // The any is being converted into a VARIANT which value is then copied + // to the SAFEARRAY's data block. When copying one has to follow the rules for + // copying certain types, as are VT_DISPATCH, VT_UNKNOWN, VT_VARIANT, VT_BSTR. + // To increase performance, we just do a memcpy of VARIANT::byref. This is possible + // because anyToVariant has already followed the copying rules. To make this + // work there must not be a VariantClear. + // One Exception is VARIANT because I don't know how VariantCopy works. + + VARIANT var; + VariantInit( &var); + anyToVariant( &var, unoElement); + if( elemtype == VT_VARIANT ) + { + VariantCopy( reinterpret_cast<VARIANT*>(psaCurrentData), &var); + VariantClear( &var); + } + else + memcpy( psaCurrentData, &var.byref, oleElementSize); + + psaCurrentData+= oleElementSize; + } + } + while( incrementMultidimensionalIndex( dimsSeq, parElementCount, arDimsSeqIndices)); + + SafeArrayUnaccessData( pArray); + } + } + } + return pArray; +} + +// Increments a multi dimensional index. +// Returns true as long as the index has been successfully incremented, false otherwise. +// False is also returned if an overflow of the most significant dimension occurs. E.g. +// assume an array with the dimensions (2,2), then the lowest index is (0,0) and the highest +// index is (1,1). If the function is being called with the index (1,1) then the overflow would +// occur, with the result (0,0) and a sal_False as return value. +// Param dimensions - number of dimensions +// Param parDimensionsLength - The array contains the size of each dimension, that is the +// size of the array equals the parameter dimensions. +// The rightmost dimensions is the least significant one +// ( parDimensionsLengths[ dimensions -1 ] ). +// Param parMultiDimensionalIndex - The array contains the index. Each dimension index is +// 0 based. +template<class T> +bool UnoConversionUtilities<T>::incrementMultidimensionalIndex(sal_Int32 dimensions, + const sal_Int32 * parDimensionLengths, + sal_Int32 * parMultidimensionalIndex) +{ + if( dimensions < 1) + return false; + + bool ret= true; + bool carry= true; // to get into the while loop + + sal_Int32 currentDimension= dimensions; //most significant is 1 + while( carry) + { + parMultidimensionalIndex[ currentDimension - 1]++; + // if carryover, set index to 0 and handle carry on a level above + if( parMultidimensionalIndex[ currentDimension - 1] > (parDimensionLengths[ currentDimension - 1] - 1)) + parMultidimensionalIndex[ currentDimension - 1]= 0; + else + carry= false; + + currentDimension --; + // if dimensions drops below 1 and carry is set than then all indices are 0 again + // this is signalled by returning sal_False + if( currentDimension < 1 && carry) + { + carry= false; + ret= false; + } + } + return ret; +} + +// Determines the size of a certain OLE type. The function takes +// only those types into account which are oleautomation types and +// can have a value ( unless VT_NULL, VT_EMPTY, VT_ARRAY, VT_BYREF). +// Currently used in createUnoSequenceWrapper to calculate addresses +// for data within a SAFEARRAY. +template<class T> +size_t UnoConversionUtilities<T>::getOleElementSize( VARTYPE type) +{ + size_t size; + switch( type) + { + case VT_BOOL: size= sizeof( VARIANT_BOOL);break; + case VT_UI1: size= sizeof( unsigned char);break; + case VT_R8: size= sizeof( double);break; + case VT_R4: size= sizeof( float);break; + case VT_I2: size= sizeof( short);break; + case VT_I4: size= sizeof( long);break; + case VT_BSTR: size= sizeof( BSTR); break; + case VT_ERROR: size= sizeof( SCODE); break; + case VT_DISPATCH: + case VT_UNKNOWN: size= sizeof( IUnknown*); break; + case VT_VARIANT: size= sizeof( VARIANT);break; + default: size= 0; + } + return size; +} + +//If a Sequence is being converted into a SAFEARRAY then we possibly have +// to create a SAFEARRAY with multiple dimensions. This is the case when a +// Sequence contains Sequences ( Sequence< Sequence < XXX > > ). The leftmost +// Sequence in the declaration is assumed to represent dimension 1. Because +// all Sequence elements of a Sequence can have different length, we have to +// determine the maximum length which is then the length of the respective +// dimension. +// getElementCountAndTypeOfSequence determines the length of each dimension and calls itself recursively +// in the process. +// param rSeq - an Any that has to contain a Sequence +// param dim - the dimension for which the number of elements is being determined, +// must be one. +// param seqElementCounts - contains the maximum number of elements for each +// dimension. Index 0 contains the number of dimension one. +// After return the Sequence contains the maximum number of +// elements for each dimension. +// The length of the Sequence must equal the number of dimensions. +// param typeClass - TypeClass of the element type that is no Sequence, e.g. +// Sequence< Sequence <Sequence <sal_Int32> > > - type is sal_Int32) +template<class T> +void UnoConversionUtilities<T>::getElementCountAndTypeOfSequence( const Any& rSeq, sal_Int32 dim, + Sequence< sal_Int32 >& seqElementCounts, TypeDescription& typeDesc) +{ + sal_Int32 dimCount= (*static_cast<uno_Sequence* const *>(rSeq.getValue()))->nElements; + if( dimCount > seqElementCounts[ dim-1]) + seqElementCounts.getArray()[ dim-1]= dimCount; + + // we need the element type to construct the any that is + // passed into getElementCountAndTypeOfSequence again + typelib_TypeDescription* pSeqDesc= nullptr; + rSeq.getValueTypeDescription( &pSeqDesc); + typelib_TypeDescriptionReference* pElementDescRef= reinterpret_cast<typelib_IndirectTypeDescription*>(pSeqDesc)->pType; + + // if the elements are Sequences then do recursion + if( dim < seqElementCounts.getLength() ) + { + uno_Sequence* pSeq = *static_cast<uno_Sequence* const*>(rSeq.getValue()); + uno_Sequence** arSequences= reinterpret_cast<uno_Sequence**>(pSeq->elements); + for( sal_Int32 i=0; i < dimCount; i++) + { + uno_Sequence* arElement= arSequences[ i]; + getElementCountAndTypeOfSequence( Any( &arElement, pElementDescRef), dim + 1 , seqElementCounts, typeDesc); + } + } + else + { + // determine the element type ( e.g. Sequence< Sequence <Sequence <sal_Int32> > > - type is sal_Int32) + typeDesc= pElementDescRef; + } + typelib_typedescription_release( pSeqDesc); +} + + +template<class T> +SAFEARRAY* UnoConversionUtilities<T>::createUnoSequenceWrapper(const Any& rSeq) +{ + SAFEARRAY* pArray = nullptr; + sal_uInt32 n = 0; + + if( rSeq.getValueTypeClass() != TypeClass_SEQUENCE ) + throw IllegalArgumentException( + "[automation bridge]UnoConversionUtilities<T>::createUnoSequenceWrapper\n" + "The UNO argument is not a sequence", nullptr, -1); + + uno_Sequence * punoSeq= *static_cast<uno_Sequence* const *>(rSeq.getValue()); + + typelib_TypeDescriptionReference* pSeqTypeRef= rSeq.getValueTypeRef(); + typelib_TypeDescription* pSeqType= nullptr; + TYPELIB_DANGER_GET( &pSeqType, pSeqTypeRef); + typelib_IndirectTypeDescription * pSeqIndDec= reinterpret_cast<typelib_IndirectTypeDescription*>(pSeqType); + + + typelib_TypeDescriptionReference * pSeqElementTypeRef= pSeqIndDec->pType; + TYPELIB_DANGER_RELEASE( pSeqType); + + typelib_TypeDescription* pSeqElementDesc= nullptr; + TYPELIB_DANGER_GET( &pSeqElementDesc, pSeqElementTypeRef); + sal_Int32 nElementSize= pSeqElementDesc->nSize; + n= punoSeq->nElements; + + SAFEARRAYBOUND rgsabound[1]; + rgsabound[0].lLbound = 0; + rgsabound[0].cElements = n; + VARIANT oleElement; + LONG safeI[1]; + + pArray = SafeArrayCreate(VT_VARIANT, 1, rgsabound); + + Any unoElement; + char * pSeqData= punoSeq->elements; + + for (sal_uInt32 i = 0; i < n; i++) + { + unoElement.setValue( pSeqData + i * nElementSize, pSeqElementDesc); + VariantInit(&oleElement); + + anyToVariant(&oleElement, unoElement); + + safeI[0] = i; + SafeArrayPutElement(pArray, safeI, &oleElement); + + VariantClear(&oleElement); + } + TYPELIB_DANGER_RELEASE( pSeqElementDesc); + + return pArray; +} + +/* The argument rObj can contain +- UNO struct +- UNO interface +- UNO interface created by this bridge (adapter factory) +- UNO interface created by this bridge ( COM Wrapper) + +pVar must be initialized. +*/ +template<class T> +void UnoConversionUtilities<T>::createUnoObjectWrapper(const Any & rObj, VARIANT * pVar) +{ + MutexGuard guard(getBridgeMutex()); + + Reference<XInterface> xInt; + + TypeClass tc = rObj.getValueTypeClass(); + if (tc != TypeClass_INTERFACE && tc != TypeClass_STRUCT) + throw IllegalArgumentException( + "[automation bridge]UnoConversionUtilities<T>::createUnoObjectWrapper \n" + "Cannot create an Automation interface for a UNO type which is not " + "a struct or interface!", nullptr, -1); + + if (rObj.getValueTypeClass() == TypeClass_INTERFACE) + { + if (! (rObj >>= xInt)) + throw IllegalArgumentException( + "[automation bridge] UnoConversionUtilities<T>::createUnoObjectWrapper\n " + "Could not create wrapper object for UNO object!", nullptr, -1); + //If XInterface is NULL, which is a valid value, then simply return NULL. + if ( ! xInt.is()) + { + pVar->vt = VT_UNKNOWN; + pVar->punkVal = nullptr; + return; + } + //make sure we have the main XInterface which is used with a map + xInt.set(xInt, UNO_QUERY); + //If there is already a wrapper for the UNO object then use it + + Reference<XInterface> xIntWrapper; + // Does a UNO wrapper exist already ? + auto it_uno = UnoObjToWrapperMap.find( reinterpret_cast<sal_uIntPtr>(xInt.get())); + if(it_uno != UnoObjToWrapperMap.end()) + { + xIntWrapper = it_uno->second; + if (xIntWrapper.is()) + { + convertSelfToCom(xIntWrapper, pVar); + return; + } + } + // Is the object a COM wrapper ( either XInvocation, or Adapter object) + // or does it supply an IDispatch by its own ? + else + { + Reference<XInterface> xIntComWrapper = xInt; + + // Adapter? then get the COM wrapper to which the adapter delegates its calls + auto it = AdapterToWrapperMap.find( reinterpret_cast<sal_uIntPtr>(xInt.get())); + if( it != AdapterToWrapperMap.end() ) + xIntComWrapper= reinterpret_cast<XInterface*>(it->second); + + if (convertSelfToCom(xIntComWrapper, pVar)) + return; + } + } + // If we have no UNO wrapper nor the IDispatch yet then we have to create + // a wrapper. For that we need an XInvocation. + + // create an XInvocation using the invocation service + Reference<XInvocation> xInv; + Reference<XSingleServiceFactory> xInvFactory= getInvocationFactory(rObj); + if (xInvFactory.is()) + { + Sequence<Any> params(2); + params.getArray()[0] = rObj; + params.getArray()[1] <<= OUString("FromOLE"); + Reference<XInterface> xInt2 = xInvFactory->createInstanceWithArguments(params); + xInv.set(xInt2, UNO_QUERY); + } + + if (xInv.is()) + { + Reference<XInterface> xNewWrapper = createUnoWrapperInstance(); + Reference<css::lang::XInitialization> xInitWrapper(xNewWrapper, UNO_QUERY); + if (xInitWrapper.is()) + { + VARTYPE vartype= getVarType( rObj); + + if (xInt.is()) + { + Any params[3]; + params[0] <<= xInv; + params[1] <<= xInt; + params[2] <<= vartype; + xInitWrapper->initialize( Sequence<Any>(params, 3)); + } + else + { + Any params[2]; + params[0] <<= xInv; + params[1] <<= vartype; + xInitWrapper->initialize( Sequence<Any>(params, 2)); + } + + // put the newly created object into a map. If the same object will + // be mapped again and there is already a wrapper then the old wrapper + // will be used. + if(xInt.is()) // only interfaces + UnoObjToWrapperMap[reinterpret_cast<sal_uIntPtr>(xInt.get())]= xNewWrapper; + convertSelfToCom(xNewWrapper, pVar); + return; + } + } +} + +template<class T> +void UnoConversionUtilities<T>::variantToAny( const VARIANT* pVariant, Any& rAny, + bool bReduceValueRange /* = sal_True */) +{ + HRESULT hr = S_OK; + try + { + CComVariant var; + + // There is no need to support indirect values, since they're not supported by UNO + if( FAILED(hr= VariantCopyInd( &var, pVariant))) // remove VT_BYREF + throw BridgeRuntimeError( + "[automation bridge] UnoConversionUtilities<T>::variantToAny \n" + "VariantCopyInd failed for reason : " + OUString::number(hr)); + + if ( ! convertValueObject( & var, rAny)) + { + if ((var.vt & VT_ARRAY) > 0) + { + VARTYPE oleTypeFlags = ::sal::static_int_cast< VARTYPE, int >( var.vt ^ VT_ARRAY ); + + Sequence<Any> unoSeq = createOleArrayWrapper(var.parray, oleTypeFlags); + rAny.setValue( &unoSeq, cppu::UnoType<decltype(unoSeq)>::get()); + } + else + { + switch (var.vt) + { + case VT_EMPTY: + rAny.setValue(nullptr, Type()); + break; + case VT_NULL: + rAny.setValue(nullptr, Type()); + break; + case VT_I2: + rAny.setValue( & var.iVal, cppu::UnoType<sal_Int16>::get()); + break; + case VT_I4: + rAny.setValue( & var.lVal, cppu::UnoType<sal_Int32>::get()); + // necessary for use in JavaScript ( see "reduceRange") + if( bReduceValueRange) + reduceRange(rAny); + break; + case VT_R4: + rAny.setValue( & var.fltVal, cppu::UnoType<float>::get()); + break; + case VT_R8: + rAny.setValue(& var.dblVal, cppu::UnoType<double>::get()); + break; + case VT_CY: + { + Currency cy(var.cyVal.int64); + rAny <<= cy; + break; + } + case VT_DATE: + { + Date d(var.date); + rAny <<= d; + break; + } + case VT_BSTR: + { + OUString b(o3tl::toU(var.bstrVal)); + rAny.setValue( &b, cppu::UnoType<decltype(b)>::get()); + break; + } + case VT_UNKNOWN: + case VT_DISPATCH: + { + //check if it is a UNO type + CComQIPtr<IUnoTypeWrapper> spType(static_cast<IUnknown*>(var.byref)); + if (spType) + { + CComBSTR sName; + if (FAILED(spType->get_Name(&sName))) + throw BridgeRuntimeError( + "[automation bridge]UnoConversionUtilities<T>::variantToAny \n" + "Failed to get the type name from a UnoTypeWrapper!"); + Type type; + if (!getType(sName, type)) + { + throw CannotConvertException( + OUString::Concat("[automation bridge]UnoConversionUtilities<T>::variantToAny \n" + "A UNO type with the name: ") + o3tl::toU(LPCOLESTR(sName)) + + "does not exist!", + nullptr, TypeClass_UNKNOWN, FailReason::TYPE_NOT_SUPPORTED,0); + } + rAny <<= type; + } + else + { + rAny = createOleObjectWrapper( & var); + } + break; + } + case VT_ERROR: + { + SCode scode(var.scode); + rAny <<= scode; + break; + } + case VT_BOOL: + { + rAny <<= (var.boolVal == VARIANT_TRUE); + break; + } + case VT_I1: + rAny.setValue( & var.cVal, cppu::UnoType<sal_Int8>::get()); + break; + case VT_UI1: // there is no unsigned char in UNO + rAny <<= sal_Int8(var.bVal); + break; + case VT_UI2: + rAny.setValue( & var.uiVal, cppu::UnoType<cppu::UnoUnsignedShortType>::get() ); + break; + case VT_UI4: + rAny.setValue( & var.ulVal, cppu::UnoType<sal_uInt32>::get()); + break; + case VT_INT: + rAny.setValue( & var.intVal, cppu::UnoType<sal_Int32>::get()); + break; + case VT_UINT: + rAny.setValue( & var.uintVal, cppu::UnoType<sal_uInt32>::get()); + break; + case VT_VOID: + rAny.setValue( nullptr, Type()); + break; + case VT_DECIMAL: + { + Decimal dec; + dec.Scale = var.decVal.scale; + dec.Sign = var.decVal.sign; + dec.LowValue = var.decVal.Lo32; + dec.MiddleValue = var.decVal.Mid32; + dec.HighValue = var.decVal.Hi32; + rAny <<= dec; + break; + } + + default: + break; + } + } + } + } + catch (const IllegalArgumentException &) + { + throw; + } + catch (const CannotConvertException &) + { + throw; + } + catch (const BridgeRuntimeError &) + { + throw; + } + catch (const Exception & e) + { + throw BridgeRuntimeError("[automation bridge] unexpected exception in " + "UnoConversionUtilities<T>::variantToAny ! Message : \n" + + e.Message); + } + catch(...) + { + throw BridgeRuntimeError( + "[automation bridge] unexpected exception in " + "UnoConversionUtilities<T>::variantToAny !"); + } + +} +// The function converts an IUnknown* into a UNO interface or struct. The +// IUnknown pointer can constitute different kind of objects: +// 1. a wrapper of a UNO struct (the wrapper was created by this bridge) +// 2. a wrapper of a UNO interface (created by this bridge) +// 3. a dispatch object that implements UNO interfaces +// 4. a dispatch object. + +// If the parameter "aType" has a value then the COM object ( pUnknown) is supposed to +// implement the interface described by "aType". Moreover it ( pUnknown) can implement +// several other +// UNO interfaces in which case it has to support the SUPPORTED_INTERFACES_PROP (see +// #define) property. That property contains all names of interfaces. +// "pUnknown" is wrapped by a COM wrapper object that implements XInvocation, e.g. +// IUnknownWrapper. Additionally an object of type "aType" is created by help +// of the INTERFACE_ADAPTER_FACTORY (see #define) service. The implementation of +// "aType" calls on the COM wrapper's XInvocation::invoke. If the COM object supports +// more than one UNO interfaces, as can be determined by the property +// SUPPORTED_INTERFACES_PROP, then the INTERFACE_ADAPTER_FACTORY creates an object that +// implements all these interfaces. +// This is only done if "pUnknown" is not already a UNO wrapper, +// that is it is actually NOT a UNO object that was converted to a COM object. If it is an +// UNO wrapper than the original UNO object is being extracted, queried for "aType" (if +// it is no struct) and returned. +template<class T> +Any UnoConversionUtilities<T>::createOleObjectWrapper(VARIANT* pVar, const Type& aType) +{ + //To allow passing "Nothing" in VS 2008 we need to accept VT_EMPTY + if (pVar->vt != VT_UNKNOWN && pVar->vt != VT_DISPATCH && pVar->vt != VT_EMPTY) + throw IllegalArgumentException( + "[automation bridge]UnoConversionUtilities<T>::createOleObjectWrapper \n" + "The VARIANT does not contain an object type! ", nullptr, -1); + + MutexGuard guard( getBridgeMutex()); + + CComPtr<IUnknown> spUnknown; + CComPtr<IDispatch> spDispatch; + + if (pVar->vt == VT_UNKNOWN) + { + spUnknown = pVar->punkVal; + if (spUnknown) + spUnknown.QueryInterface( & spDispatch.p); + } + else if (pVar->vt == VT_DISPATCH && pVar->pdispVal != nullptr) + { + CComPtr<IDispatch> spDispatch2(pVar->pdispVal); + if (spDispatch2) + spDispatch2.QueryInterface( & spUnknown.p); + } + + static Type VOID_TYPE; + Any ret; + //If no Type is provided and pVar contains IUnknown then we return a XInterface. + //If pVar contains an IDispatch then we return a XInvocation. + Type desiredType = aType; + + if (aType == VOID_TYPE) + { + switch (pVar->vt) + { + case VT_EMPTY: + case VT_UNKNOWN: + desiredType = cppu::UnoType<XInterface>::get(); + break; + case VT_DISPATCH: + desiredType = cppu::UnoType<XInvocation>::get(); + break; + default: + desiredType = aType; + } + } + + // COM pointer are NULL, no wrapper required + if (spUnknown == nullptr) + { + Reference<XInterface> xInt; + if( aType.getTypeClass() == TypeClass_INTERFACE) + ret.setValue( &xInt, aType); + else if( aType.getTypeClass() == TypeClass_STRUCT) + ret.setValue( nullptr, aType); + else + ret <<= xInt; + return ret; + } + + + // Check if "spUnknown" is a UNO wrapper, that is a UNO object that has been + // passed to COM. Then it supports IUnoObjectWrapper + // and we extract the original UNO object. + CComQIPtr<IUnoObjectWrapper> spUno( spUnknown); + if( spUno) + { // it is a wrapper + Reference<XInterface> xInt; + if( SUCCEEDED( spUno->getOriginalUnoObject( &xInt))) + { + ret <<= xInt; + } + else + { + Any any; + if( SUCCEEDED( spUno->getOriginalUnoStruct(&any))) + ret= any; + } + return ret; + } + + // "spUnknown" is a real COM object. + // Before we create a new wrapper object we check if there is an existing wrapper + // There can be two kinds of wrappers, those who wrap dispatch - UNO objects, and those who + // wrap ordinary dispatch objects. The dispatch-UNO objects usually are adapted to represent + // particular UNO interfaces. + Reference<XInterface> xIntWrapper; + auto cit_currWrapper= ComPtrToWrapperMap.find( reinterpret_cast<sal_uIntPtr>(spUnknown.p)); + if(cit_currWrapper != ComPtrToWrapperMap.end()) + xIntWrapper = cit_currWrapper->second; + if (xIntWrapper.is()) + { + //Try to find an adapter for the wrapper + //find the proper Adapter. The pointer in the WrapperToAdapterMap are valid as long as + //we get a pointer to the wrapper from ComPtrToWrapperMap, because the Adapter hold references + //to the wrapper. + auto it = WrapperToAdapterMap.find(reinterpret_cast<sal_uIntPtr>(xIntWrapper.get())); + if (it == WrapperToAdapterMap.end()) + { + // No adapter available. + //The COM component could be a UNO object. Then we need to provide + // a proxy that implements all interfaces + Sequence<Type> seqTypes= getImplementedInterfaces(spUnknown); + Reference<XInterface> xIntAdapter; + if (seqTypes.getLength() > 0) + { + //It is a COM UNO object + xIntAdapter = createAdapter(seqTypes, xIntWrapper); + } + else + { + // Some ordinary COM object + xIntAdapter = xIntWrapper; + } + // return the wrapper directly, return XInterface or XInvocation + ret = xIntWrapper->queryInterface(desiredType); + if ( ! ret.hasValue()) + throw IllegalArgumentException( + "[automation bridge]UnoConversionUtilities<T>::createOleObjectWrapper \n" + "The COM object is not suitable for the UNO type: " + + desiredType.getTypeName(), nullptr, -1); + } + else + { + //There is an adapter available + Reference<XInterface> xIntAdapter(reinterpret_cast<XInterface*>(it->second)); + ret = xIntAdapter->queryInterface( desiredType); + if ( ! ret.hasValue()) + throw IllegalArgumentException( + "[automation bridge]UnoConversionUtilities<T>::createOleObjectWrapper \n" + "The COM object is not suitable for the UNO type: " + + desiredType.getTypeName(), nullptr, -1); + } + + return ret; + } + // No existing wrapper. Therefore create a new proxy. + // If the object implements UNO interfaces then get the types. + Sequence<Type> seqTypes = getImplementedInterfaces(spUnknown); + if (seqTypes.getLength() == 0 && + aType != VOID_TYPE && aType != cppu::UnoType<XInvocation>::get()) + { + seqTypes = Sequence<Type>( & aType, 1); + } + + //There is no existing wrapper, therefore we create one for the real COM object + Reference<XInterface> xIntNewProxy= createComWrapperInstance(); + if ( ! xIntNewProxy.is()) + throw BridgeRuntimeError( + "[automation bridge]UnoConversionUtilities<T>::createOleObjectWrapper \n" + "Could not create proxy object for COM object!"); + + // initialize the COM wrapper + Reference<XInitialization> xInit( xIntNewProxy, UNO_QUERY); + OSL_ASSERT( xInit.is()); + + Any params[3]; + params[0] <<= reinterpret_cast<sal_uIntPtr>(spUnknown.p); + params[1] <<= (pVar->vt == VT_DISPATCH); + params[2] <<= seqTypes; + + xInit->initialize( Sequence<Any>( params, 3)); + ComPtrToWrapperMap[reinterpret_cast<sal_uInt64>(spUnknown.p)] = xIntNewProxy; + + // we have a wrapper object + //The wrapper implements already XInvocation and XInterface. If + //param aType is void then the object is supposed to have XInvocation. + if (aType == cppu::UnoType<XInvocation>::get()|| + (aType == VOID_TYPE && seqTypes.getLength() == 0 )) + { + ret = xIntNewProxy->queryInterface(desiredType); + } + else + { + Reference<XInterface> xIntAdapter = + createAdapter(seqTypes, xIntNewProxy); + ret = xIntAdapter->queryInterface(desiredType); + } + return ret; +} +template<class T> +Reference<XInterface> UnoConversionUtilities<T>::createAdapter(const Sequence<Type>& seqTypes, + const Reference<XInterface>& receiver) +{ + Reference< XInterface> xIntAdapterFac; + xIntAdapterFac= m_smgr->createInstance(INTERFACE_ADAPTER_FACTORY); + // We create an adapter object that does not only implement the required type but also + // all types that the COM object pretends to implement. A COM object must therefore + // support the property "_implementedInterfaces". + Reference<XInterface> xIntAdapted; + Reference<XInvocation> xInv(receiver, UNO_QUERY); + Reference<XInvocationAdapterFactory2> xAdapterFac( xIntAdapterFac, UNO_QUERY); + if( xAdapterFac.is()) + xIntAdapted= xAdapterFac->createAdapter( xInv, seqTypes); + + if( !xIntAdapted.is()) + { + throw BridgeRuntimeError( + "[automation bridge]UnoConversionUtilities<T>::createOleObjectWrapper \n" + "Could not create a proxy for COM object! Creation of adapter failed."); + } + + // Put the pointer to the wrapper object and the interface pointer of the adapted interface + // in a global map. Thus we can determine in a call to createUnoObjectWrapper whether the UNO + // object is a wrapped COM object. In that case we extract the original COM object rather than + // creating a wrapper around the UNO object. + typedef std::unordered_map<sal_uInt64,sal_uInt64>::value_type VALUE; + AdapterToWrapperMap.insert( VALUE( reinterpret_cast<sal_uInt64>(xIntAdapted.get()), reinterpret_cast<sal_uInt64>(receiver.get()))); + WrapperToAdapterMap.insert( VALUE( reinterpret_cast<sal_uInt64>(receiver.get()), reinterpret_cast<sal_uInt64>(xIntAdapted.get()))); + + return xIntAdapted; +} +// "convertValueObject" converts a JScriptValue object contained in "var" into +// an any. The type contained in the any is stipulated by a "type value" thas +// was set within the JScript script on the value object ( see JScriptValue). +template<class T> +bool UnoConversionUtilities<T>::convertValueObject( const VARIANTARG *var, Any& any) +{ + bool ret = false; + try + { + bool bFail = false; + HRESULT hr= S_OK; + CComVariant varDisp; + + if(SUCCEEDED(hr = varDisp.ChangeType( VT_DISPATCH, var))) + { + CComPtr <IJScriptValueObject> spValue; + VARIANT_BOOL varBool; + CComBSTR bstrType; + CComVariant varValue; + CComPtr<IDispatch> spDisp( varDisp.pdispVal); + if(spDisp) + { + if(SUCCEEDED( spDisp->QueryInterface( __uuidof( IJScriptValueObject), + reinterpret_cast<void**> (&spValue)))) + { + ret = true; // is a ValueObject + //If it is an out - param then it does not need to be converted. In/out and + // in params does so. + if (SUCCEEDED(hr= spValue->IsOutParam( &varBool))) + { + // if varBool == true then no conversion needed because out param + if (varBool == VARIANT_FALSE) + { + if(SUCCEEDED(hr = spValue->GetValue( & bstrType, & varValue))) + { + Type type; + if (getType(bstrType, type)) + variantToAny( & varValue, any, type); + else + bFail = true; + } + else + bFail = true; + } + } + else + bFail = true; + } + } + } + else if( hr != DISP_E_TYPEMISMATCH && hr != E_NOINTERFACE) + bFail = true; + + if (bFail) + throw BridgeRuntimeError( + "[automation bridge] Conversion of ValueObject failed "); + } + catch (const BridgeRuntimeError &) + { + throw; + } + catch (const Exception & e) + { + throw BridgeRuntimeError("[automation bridge] unexpected exception in " + "UnoConversionUtilities<T>::convertValueObject ! Message : \n" + + e.Message); + } + catch(...) + { + throw BridgeRuntimeError( + "[automation bridge] unexpected exception in " + "UnoConversionUtilities<T>::convertValueObject !"); + } + return ret; +} + +template<class T> +void UnoConversionUtilities<T>::dispatchExObject2Sequence( const VARIANTARG* pvar, Any& anySeq, const Type& type) +{ + try + { + if( pvar->vt != VT_DISPATCH) + throw BridgeRuntimeError("[automation bridge] UnoConversionUtilities<T>::dispatchExObject2Sequence \n" + "Conversion of dispatch object to Sequence failed!"); + IDispatchEx* pdispEx; + HRESULT hr; + if( FAILED( hr= pvar->pdispVal->QueryInterface( IID_IDispatchEx, + reinterpret_cast<void**>( &pdispEx)))) + throw BridgeRuntimeError("[automation bridge] UnoConversionUtilities<T>::dispatchExObject2Sequence \n" + "Conversion of dispatch object to Sequence failed!"); + + DISPID dispid; + DISPPARAMS param= {nullptr,nullptr,0,0}; + CComVariant result; + + OLECHAR const * sLength= L"length"; + + // Get the length of the array. Can also be obtained through GetNextDispID. The + // method only returns DISPIDs of the array data. Their names are like "0", "1" etc. + if( FAILED( hr= pdispEx->GetIDsOfNames(IID_NULL, const_cast<OLECHAR **>(&sLength), 1, LOCALE_USER_DEFAULT, &dispid))) + throw BridgeRuntimeError("[automation bridge] UnoConversionUtilities<T>::dispatchExObject2Sequence \n" + "Conversion of dispatch object to Sequence failed!"); + if( FAILED( hr= pdispEx->InvokeEx(dispid, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, + ¶m, &result, nullptr, nullptr))) + throw BridgeRuntimeError("[automation bridge] UnoConversionUtilities<T>::dispatchExObject2Sequence \n" + "Conversion of dispatch object to Sequence failed!"); + if( FAILED( VariantChangeType( &result, &result, 0, VT_I4))) + throw BridgeRuntimeError("[automation bridge] UnoConversionUtilities<T>::dispatchExObject2Sequence \n" + "Conversion of dispatch object to Sequence failed!"); + LONG length= result.lVal; + + result.Clear(); + + // get a few basic facts about the sequence, and reallocate: + // create the Sequences + // get the size of the elements + typelib_TypeDescription *pDesc= nullptr; + type.getDescription( &pDesc); + + typelib_IndirectTypeDescription *pSeqDesc= reinterpret_cast<typelib_IndirectTypeDescription*>(pDesc); + typelib_TypeDescriptionReference *pSeqElemDescRef= pSeqDesc->pType; // type of the Sequence' elements + Type elemType( pSeqElemDescRef); + _typelib_TypeDescription* pSeqElemDesc=nullptr; + TYPELIB_DANGER_GET( &pSeqElemDesc, pSeqElemDescRef); + sal_uInt32 nelementSize= pSeqElemDesc->nSize; + TYPELIB_DANGER_RELEASE( pSeqElemDesc); + + uno_Sequence *p_uno_Seq; + uno_sequence_construct( &p_uno_Seq, pDesc, nullptr, length, cpp_acquire); + + typelib_TypeClass typeElement= pSeqDesc->pType->eTypeClass; + char *pArray= p_uno_Seq->elements; + + // Get All properties in the object, convert their values to the expected type and + // put them into the passed in sequence + for( sal_Int32 i= 0; i< length; i++) + { + OUString ousIndex=OUString::number( i); + OLECHAR* sindex = const_cast<OLECHAR *>(o3tl::toW(ousIndex.getStr())); + + if( FAILED( hr= pdispEx->GetIDsOfNames(IID_NULL, &sindex , 1, LOCALE_USER_DEFAULT, &dispid))) + { + throw BridgeRuntimeError("[automation bridge] UnoConversionUtilities<T>::dispatchExObject2Sequence \n" + "Conversion of dispatch object to Sequence failed!"); + } + if( FAILED( hr= pdispEx->InvokeEx(dispid, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, + ¶m, &result, nullptr, nullptr))) + { + throw BridgeRuntimeError("[automation bridge] UnoConversionUtilities<T>::dispatchExObject2Sequence \n" + "Conversion of dispatch object to Sequence failed!"); + } + + // If the result is VT_DISPATCH than the Sequence's element type could be Sequence + // Look that up in the CoreReflection to make clear. + // That requires a recursiv conversion + Any any; + // Destination address within the out-Sequence "anySeq" where to copy the next converted element + void* pDest= pArray + (i * nelementSize); + + if( result.vt & VT_DISPATCH && typeElement == typelib_TypeClass_SEQUENCE) + { + variantToAny( &result, any, elemType, false); + // copy the converted VARIANT, that is a Sequence to the Sequence + uno_Sequence * p_unoSeq= *static_cast<uno_Sequence* const *>(any.getValue()); + // just copy the pointer of the uno_Sequence + // nelementSize should be 4 !!!! + memcpy( pDest, &p_unoSeq, nelementSize); + osl_atomic_increment( &p_unoSeq->nRefCount); + } + else // Element type is no Sequence -> do one conversion + { + variantToAny( &result, any, elemType, false); + if( typeElement == typelib_TypeClass_ANY) + { + // copy the converted VARIANT to the Sequence + uno_type_assignData( pDest, pSeqElemDescRef , &any, pSeqElemDescRef,cpp_queryInterface, + cpp_acquire, cpp_release); + } + else + { + // type after conversion must be the element type of the sequence + OSL_ENSURE(any.getValueTypeClass() == css::uno::TypeClass(typeElement), "wrong conversion"); + uno_type_assignData( pDest, pSeqElemDescRef,const_cast<void*>( any.getValue()), any.getValueTypeRef(), + cpp_queryInterface, cpp_acquire, cpp_release); + } + } + } // else + result.Clear(); + anySeq.setValue( &p_uno_Seq, pDesc); + uno_destructData( &p_uno_Seq, pDesc, cpp_release); + typelib_typedescription_release( pDesc); + } + catch (const BridgeRuntimeError &) + { + throw; + } + catch (const Exception & e) + { + throw BridgeRuntimeError("[automation bridge] unexpected exception in " + "UnoConversionUtilities<T>::convertValueObject ! Message : \n" + + e.Message); + } + catch(...) + { + throw BridgeRuntimeError( + "[automation bridge] unexpected exception in " + "UnoConversionUtilities<T>::convertValueObject !"); + } +} + +/* The argument unotype is the type that is expected by the currently called UNO function. + For example: []long, [][]long. If the function calls itself recursively then the element type + is passed on. For example a two dimensional SAFEARRAY of type VT_I4 is to be converted. Then + unotype has to be either void or [][]long. When the function calls itself recursively then + it passes the element type which is []long. +*/ +template<class T> +Sequence<Any> UnoConversionUtilities<T>::createOleArrayWrapperOfDim(SAFEARRAY* pArray, + unsigned int dimCount, unsigned int actDim, LONG* index, VARTYPE type, const Type& unotype) +{ + LONG lBound; + LONG uBound; + LONG nCountElements; + + SafeArrayGetLBound(pArray, actDim, &lBound); + SafeArrayGetUBound(pArray, actDim, &uBound); + nCountElements= uBound - lBound +1; + + Sequence<Any> anySeq(nCountElements); + Any* pUnoArray = anySeq.getArray(); + + for (index[actDim - 1] = lBound; index[actDim - 1] <= uBound; index[actDim - 1]++) + { + if (actDim > 1 ) + { + Sequence<Any> element = createOleArrayWrapperOfDim(pArray, dimCount, + actDim - 1, index, type, getElementTypeOfSequence(unotype)); + + pUnoArray[index[actDim - 1] - lBound].setValue(&element, cppu::UnoType<decltype(element)>::get()); + } + else + { + VARIANT variant; + + VariantInit(&variant); + + V_VT(&variant) = type; + + switch (type) + { + case VT_I2: + SafeArrayGetElement(pArray, index, &V_I2(&variant)); + break; + case VT_I4: + SafeArrayGetElement(pArray, index, &V_I4(&variant)); + break; + case VT_R4: + SafeArrayGetElement(pArray, index, &V_R4(&variant)); + break; + case VT_R8: + SafeArrayGetElement(pArray, index, &V_R8(&variant)); + break; + case VT_CY: + SafeArrayGetElement(pArray, index, &V_CY(&variant)); + break; + case VT_DATE: + SafeArrayGetElement(pArray, index, &V_DATE(&variant)); + break; + case VT_BSTR: + SafeArrayGetElement(pArray, index, &V_BSTR(&variant)); + break; + case VT_DISPATCH: + SafeArrayGetElement(pArray, index, &V_DISPATCH(&variant)); + break; + case VT_ERROR: + SafeArrayGetElement(pArray, index, &V_ERROR(&variant)); + break; + case VT_BOOL: + SafeArrayGetElement(pArray, index, &V_BOOL(&variant)); + break; + case VT_VARIANT: + SafeArrayGetElement(pArray, index, &variant); + break; + case VT_UNKNOWN: + SafeArrayGetElement(pArray, index, &V_UNKNOWN(&variant)); + break; + case VT_I1: + SafeArrayGetElement(pArray, index, &V_I1(&variant)); + break; + case VT_UI1: + SafeArrayGetElement(pArray, index, &V_UI1(&variant)); + break; + case VT_UI2: + SafeArrayGetElement(pArray, index, &V_UI2(&variant)); + break; + case VT_UI4: + SafeArrayGetElement(pArray, index, &V_UI4(&variant)); + break; + default: + break; + } + + if( unotype.getTypeClass() == TypeClass_VOID) + // the function was called without specifying the destination type + variantToAny(&variant, pUnoArray[index[actDim - 1] - lBound], false); + else + variantToAny(&variant, pUnoArray[index[actDim - 1] - lBound], + getElementTypeOfSequence(unotype), false); + + VariantClear(&variant); + } + } + return anySeq; +} + +template<class T> +Type UnoConversionUtilities<T>::getElementTypeOfSequence( const Type& seqType) +{ + Type retValue; + if( seqType.getTypeClass() != TypeClass_VOID) + { + OSL_ASSERT( seqType.getTypeClass() == TypeClass_SEQUENCE); + typelib_TypeDescription* pDescSeq= nullptr; + seqType.getDescription(& pDescSeq); + retValue = Type(reinterpret_cast<typelib_IndirectTypeDescription *>(pDescSeq)->pType); + typelib_typedescription_release(pDescSeq); + } + return retValue; +} +template<class T> +Sequence<Any> UnoConversionUtilities<T>::createOleArrayWrapper(SAFEARRAY* pArray, VARTYPE type, const Type& unoType) +{ + sal_uInt32 dim = SafeArrayGetDim(pArray); + + Sequence<Any> ret; + + if (dim > 0) + { + std::unique_ptr<LONG[]> sarIndex(new LONG[dim]); + LONG * index = sarIndex.get(); + + for (unsigned int i = 0; i < dim; i++) + { + index[i] = 0; + } + + ret = createOleArrayWrapperOfDim(pArray, dim, dim, index, type, unoType); + } + + return ret; +} + +// If a VARIANT has the type VT_DISPATCH it can either be a JScript Array +// or some other object. This function finds out if it is such an array or +// not. Currently there's no way to make sure it's an array +// so we assume that when the object has a property "0" then it is an Array. +// A JScript has property like "0", "1", "2" etc. which represent the +// value at the corresponding index of the array +template<class T> +bool UnoConversionUtilities<T>::isJScriptArray(const VARIANT* rvar) +{ + OSL_ENSURE( rvar->vt == VT_DISPATCH, "param is not a VT_DISPATCH"); + HRESULT hr; + OLECHAR const * sindex= L"0"; + DISPID id; + if ( rvar->vt == VT_DISPATCH && rvar->pdispVal ) + { + hr= rvar->pdispVal->GetIDsOfNames( + IID_NULL, const_cast<OLECHAR **>(&sindex), 1, LOCALE_USER_DEFAULT, + &id); + + if( SUCCEEDED ( hr) ) + return true; + } + + return false; +} + +template<class T> +VARTYPE UnoConversionUtilities<T>::mapTypeClassToVartype( TypeClass type) +{ + VARTYPE ret; + switch( type) + { + case TypeClass_INTERFACE: ret= VT_DISPATCH; + break; + case TypeClass_STRUCT: ret= VT_DISPATCH; + break; + case TypeClass_ENUM: ret= VT_I4; + break; + case TypeClass_SEQUENCE: ret= VT_ARRAY; + break; + case TypeClass_ANY: ret= VT_VARIANT; + break; + case TypeClass_BOOLEAN: ret= VT_BOOL; + break; + case TypeClass_CHAR: ret= VT_I2; + break; + case TypeClass_STRING: ret= VT_BSTR; + break; + case TypeClass_FLOAT: ret= VT_R4; + break; + case TypeClass_DOUBLE: ret= VT_R8; + break; + case TypeClass_BYTE: ret= VT_UI1; + break; + case TypeClass_SHORT: ret= VT_I2; + break; + case TypeClass_LONG: ret= VT_I4; + break; + case TypeClass_UNSIGNED_SHORT: ret= VT_UI2; + break; + case TypeClass_UNSIGNED_LONG: ret= VT_UI4; + break; + default: + ret= VT_EMPTY; + } + return ret; +} + +template<class T> +Sequence<Type> UnoConversionUtilities<T>::getImplementedInterfaces(IUnknown* pUnk) +{ + Sequence<Type> seqTypes; + CComDispatchDriver disp( pUnk); + if( disp) + { + CComVariant var; + HRESULT hr= S_OK; + // There are two different property names possible. + if( FAILED( hr= disp.GetPropertyByName( SUPPORTED_INTERFACES_PROP, &var))) + { + hr= disp.GetPropertyByName( SUPPORTED_INTERFACES_PROP2, &var); + } + if (SUCCEEDED( hr)) + { + // we expect an array( SafeArray or IDispatch) of Strings. + Any anyNames; + variantToAny( &var, anyNames, cppu::UnoType<Sequence<Any>>::get()); + Sequence<Any> seqAny; + if( anyNames >>= seqAny) + { + seqTypes.realloc( seqAny.getLength()); + auto pseqTypes = seqTypes.getArray(); + for( sal_Int32 i=0; i < seqAny.getLength(); i++) + { + OUString typeName; + seqAny[i] >>= typeName; + pseqTypes[i]= Type( TypeClass_INTERFACE, typeName); + } + } + } + } + return seqTypes; +} +template<class T> +Reference<XTypeConverter> UnoConversionUtilities<T>::getTypeConverter() +{ + if ( ! m_typeConverter.is()) + { + MutexGuard guard(getBridgeMutex()); + if ( ! m_typeConverter.is()) + { + Reference<XInterface> xIntConverter = + m_smgr->createInstance("com.sun.star.script.Converter"); + if (xIntConverter.is()) + m_typeConverter.set(xIntConverter, UNO_QUERY); + } + } + return m_typeConverter; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/unoobjw.cxx b/extensions/source/ole/unoobjw.cxx new file mode 100644 index 0000000000..915ecd92f8 --- /dev/null +++ b/extensions/source/ole/unoobjw.cxx @@ -0,0 +1,3436 @@ +/* -*- 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/. + * + * 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 . + */ + +// Documentation pointers for recent work: +// +// https://www.codeproject.com/Articles/9014/Understanding-COM-Event-Handling +// https://blogs.msdn.microsoft.com/ericlippert/2005/02/15/why-does-wscript-connectobject-not-always-work/ + +#include "ole2uno.hxx" + +#include <stdio.h> +#include <list> +#include <sstream> +#include <unordered_map> +#include <vector> + +#if defined _MSC_VER && defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wall" +#pragma clang diagnostic ignored "-Wattributes" +#pragma clang diagnostic ignored "-Wdelete-incomplete" +#pragma clang diagnostic ignored "-Wdynamic-class-memaccess" +#pragma clang diagnostic ignored "-Wextra" +#pragma clang diagnostic ignored "-Wint-to-pointer-cast" +#pragma clang diagnostic ignored "-Winvalid-noreturn" +#pragma clang diagnostic ignored "-Wmicrosoft" +#pragma clang diagnostic ignored "-Wnon-pod-varargs" +#pragma clang diagnostic ignored "-Wnonportable-include-path" +#pragma clang diagnostic ignored "-Wsequence-point" +#pragma clang diagnostic ignored "-Wtypename-missing" +#endif +#include <atlbase.h> +#include <atlcom.h> +#if defined _MSC_VER && defined __clang__ +#pragma clang diagnostic pop +#endif +#include <comdef.h> + +#include <osl/diagnose.h> +#include <salhelper/simplereferenceobject.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <com/sun/star/beans/MethodConcept.hpp> +#include <com/sun/star/beans/PropertyConcept.hpp> +#include <com/sun/star/lang/NoSuchMethodException.hpp> +#include <com/sun/star/script/CannotConvertException.hpp> +#include <com/sun/star/script/FailReason.hpp> +#include <com/sun/star/reflection/theCoreReflection.hpp> +#include <com/sun/star/reflection/ParamInfo.hpp> +#include <com/sun/star/beans/XExactName.hpp> +#include <com/sun/star/container/NoSuchElementException.hpp> +#include <com/sun/star/container/XEnumeration.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> + +#include <com/sun/star/beans/XMaterialHolder.hpp> +#include <com/sun/star/script/XInvocation2.hpp> +#include <com/sun/star/script/MemberType.hpp> +#include <com/sun/star/reflection/XIdlReflection.hpp> +#include <ooo/vba/XCollection.hpp> +#include <ooo/vba/XConnectable.hpp> +#include <ooo/vba/XConnectionPoint.hpp> +#include <ooo/vba/XSink.hpp> +#include <ooo/vba/msforms/XCheckBox.hpp> +#include <osl/interlck.h> +#include <com/sun/star/uno/genfunc.h> +#include <comphelper/automationinvokedzone.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/profilezone.hxx> +#include <comphelper/windowsdebugoutput.hxx> +#include <comphelper/windowserrorstring.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <o3tl/safeint.hxx> + +#include "comifaces.hxx" +#include "jscriptclasses.hxx" +#include "unotypewrapper.hxx" +#include "oleobjw.hxx" +#include "unoobjw.hxx" +#include "servprov.hxx" + +using namespace osl; +using namespace cppu; +using namespace com::sun::star::uno; +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::bridge::ModelDependent; +using namespace com::sun::star::reflection; + +std::unordered_map<sal_uIntPtr, WeakReference<XInterface> > UnoObjToWrapperMap; +static bool writeBackOutParameter(VARIANTARG* pDest, VARIANT* pSource); +static bool writeBackOutParameter2( VARIANTARG* pDest, VARIANT* pSource); +static HRESULT mapCannotConvertException(const CannotConvertException &e, unsigned int * puArgErr); + +/* Does not throw any exceptions. + Param pInfo can be NULL. + */ +static void writeExcepinfo(EXCEPINFO * pInfo, const OUString& message) +{ + if (pInfo != nullptr) + { + pInfo->wCode = UNO_2_OLE_EXCEPTIONCODE; + pInfo->bstrSource = SysAllocString(L"[automation bridge] "); + pInfo->bstrDescription = SysAllocString(o3tl::toW(message.getStr())); + } +} + +InterfaceOleWrapper::InterfaceOleWrapper( Reference<XMultiServiceFactory> const & xFactory, + sal_uInt8 unoWrapperClass, sal_uInt8 comWrapperClass): + UnoConversionUtilities<InterfaceOleWrapper>( xFactory, unoWrapperClass, comWrapperClass), + m_defaultValueType( 0) +{ +} + +InterfaceOleWrapper::~InterfaceOleWrapper() +{ + MutexGuard guard(getBridgeMutex()); + // remove entries in global map + auto it = UnoObjToWrapperMap.find( reinterpret_cast<sal_uIntPtr>(m_xOrigin.get())); + if(it != UnoObjToWrapperMap.end()) + UnoObjToWrapperMap.erase(it); +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP InterfaceOleWrapper::QueryInterface(REFIID riid, void ** ppv) +{ + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + SAL_INFO("extensions.olebridge", this << "@InterfaceOleWrapper::QueryInterface(" << riid << ")"); + + HRESULT ret= S_OK; + + if( !ppv) + return E_POINTER; + + if(IsEqualIID(riid, IID_IUnknown)) + { + AddRef(); + *ppv = static_cast<IUnknown*>(static_cast<IDispatch*>(this)); + SAL_INFO("extensions.olebridge", " " << *ppv); + } + else if (IsEqualIID(riid, IID_IDispatch)) + { + AddRef(); + *ppv = static_cast<IDispatch*>(this); + SAL_INFO("extensions.olebridge", " " << *ppv); + } + else if (IsEqualIID(riid, IID_IProvideClassInfo)) + { + Reference<ooo::vba::XConnectable> xConnectable(m_xOrigin, UNO_QUERY); + if (!xConnectable.is()) + return E_NOINTERFACE; + AddRef(); + *ppv = static_cast<IProvideClassInfo*>(this); + SAL_INFO("extensions.olebridge", " " << *ppv); + } + else if (IsEqualIID(riid, IID_IConnectionPointContainer)) + { + Reference<ooo::vba::XConnectable> xConnectable(m_xOrigin, UNO_QUERY); + if (!xConnectable.is()) + return E_NOINTERFACE; + AddRef(); + *ppv = static_cast<IConnectionPointContainer*>(this); + SAL_INFO("extensions.olebridge", " " << *ppv); + } + else if( IsEqualIID( riid, __uuidof( IUnoObjectWrapper))) + { + AddRef(); + *ppv= static_cast<IUnoObjectWrapper*>(this); + SAL_INFO("extensions.olebridge", " " << *ppv); + } + else + ret= E_NOINTERFACE; + return ret; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP_(ULONG) InterfaceOleWrapper::AddRef() +{ + acquire(); + // does not need to guard because one should not rely on the return value of + // AddRef anyway + return m_refCount; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP_(ULONG) InterfaceOleWrapper::Release() +{ + ULONG n= m_refCount; + release(); + return n - 1; +} + +// IUnoObjectWrapper -------------------------------------------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP InterfaceOleWrapper::getWrapperXInterface( Reference<XInterface>* pXInt) +{ + pXInt->set( static_cast<XWeak*>( this), UNO_QUERY); + return pXInt->is() ? S_OK : E_FAIL; +} +COM_DECLSPEC_NOTHROW STDMETHODIMP InterfaceOleWrapper::getOriginalUnoObject( Reference<XInterface>* pXInt) +{ + *pXInt= m_xOrigin; + return m_xOrigin.is() ? S_OK : E_FAIL; +} +COM_DECLSPEC_NOTHROW STDMETHODIMP InterfaceOleWrapper::getOriginalUnoStruct( Any * pStruct) +{ + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + HRESULT ret= E_FAIL; + if( !m_xOrigin.is()) + { + Reference<XMaterialHolder> xMatHolder( m_xInvocation, UNO_QUERY); + if( xMatHolder.is()) + { + Any any = xMatHolder->getMaterial(); + if( any.getValueTypeClass() == TypeClass_STRUCT) + { + *pStruct= any; + ret= S_OK; + } + } + } + return ret; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP InterfaceOleWrapper::GetTypeInfoCount( UINT *pctinfo ) +{ + SAL_INFO("extensions.olebridge", this << "@InterfaceOleWrapper::GetTypeInfoCount"); + + if (!pctinfo) + return E_POINTER; + + *pctinfo = 1; + + return S_OK; +} + +namespace { + +class CXTypeInfo : public ITypeInfo, + public CComObjectRoot +{ +public: + enum class Kind { COCLASS, MAIN, OUTGOING }; + +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#endif + BEGIN_COM_MAP(CXTypeInfo) +#if defined __clang__ +#pragma clang diagnostic pop +#endif + COM_INTERFACE_ENTRY(ITypeInfo) +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winconsistent-missing-override" +#pragma clang diagnostic ignored "-Wunused-function" +#endif + END_COM_MAP() +#if defined __clang__ +#pragma clang diagnostic pop +#endif + + DECLARE_NOT_AGGREGATABLE(CXTypeInfo) + + virtual ~CXTypeInfo() {} + + void InitForCoclass(Reference<XInterface> xOrigin, + const OUString& sImplementationName, + const IID& rIID, + Reference<XMultiServiceFactory> xMSF); + void InitForClassItself(Reference<XInterface> xOrigin, + const OUString& sImplementationName, + const IID& rIID, + Reference<XMultiServiceFactory> xMSF); + void InitForOutgoing(Reference<XInterface> xOrigin, + const OUString& sInterfaceName, + const IID& rIID, + Reference<XMultiServiceFactory> xMSF, + Type aType); + virtual HRESULT STDMETHODCALLTYPE GetTypeAttr(TYPEATTR **ppTypeAttr) override; + virtual HRESULT STDMETHODCALLTYPE GetTypeComp(ITypeComp **ppTComp) override; + virtual HRESULT STDMETHODCALLTYPE GetFuncDesc(UINT index, + FUNCDESC **ppFuncDesc) override; + virtual HRESULT STDMETHODCALLTYPE GetVarDesc(UINT index, + VARDESC **ppVarDesc) override; + virtual HRESULT STDMETHODCALLTYPE GetNames(MEMBERID memid, + BSTR *rgBstrNames, + UINT cMaxNames, + UINT *pcNames) override; + virtual HRESULT STDMETHODCALLTYPE GetRefTypeOfImplType(UINT index, + HREFTYPE *pRefType) override; + virtual HRESULT STDMETHODCALLTYPE GetImplTypeFlags(UINT index, + INT *pImplTypeFlags) override; + virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(LPOLESTR *rgszNames, + UINT cNames, + MEMBERID *pMemId) override; + virtual HRESULT STDMETHODCALLTYPE Invoke(PVOID pvInstance, + MEMBERID memid, + WORD wFlags, + DISPPARAMS *pDispParams, + VARIANT *pVarResult, + EXCEPINFO *pExcepInfo, + UINT *puArgErr) override; + virtual HRESULT STDMETHODCALLTYPE GetDocumentation(MEMBERID memid, + BSTR *pBstrName, + BSTR *pBstrDocString, + DWORD *pdwHelpContext, + BSTR *pBstrHelpFile) override; + virtual HRESULT STDMETHODCALLTYPE GetDllEntry(MEMBERID memid, + INVOKEKIND invKind, + BSTR *pBstrDllName, + BSTR *pBstrName, + WORD *pwOrdinal) override; + virtual HRESULT STDMETHODCALLTYPE GetRefTypeInfo(HREFTYPE hRefType, + ITypeInfo **ppTInfo) override; + virtual HRESULT STDMETHODCALLTYPE AddressOfMember(MEMBERID memid, + INVOKEKIND invKind, + PVOID *ppv) override; + virtual HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown *pUnkOuter, + REFIID riid, + PVOID *ppvObj) override; + virtual HRESULT STDMETHODCALLTYPE GetMops(MEMBERID memid, + BSTR *pBstrMops) override; + virtual HRESULT STDMETHODCALLTYPE GetContainingTypeLib(ITypeLib **ppTLib, + UINT *pIndex) override; + virtual void STDMETHODCALLTYPE ReleaseTypeAttr(TYPEATTR *pTypeAttr) override; + virtual void STDMETHODCALLTYPE ReleaseFuncDesc(FUNCDESC *pFuncDesc) override; + virtual void STDMETHODCALLTYPE ReleaseVarDesc(VARDESC *pVarDesc) override; + +private: + Kind meKind; + Reference<XInterface> mxOrigin; + OUString msImplementationName; + OUString msInterfaceName; + IID maIID; + Reference<XMultiServiceFactory> mxMSF; + Type maType; +}; + +class CXTypeLib : public ITypeLib, + public CComObjectRoot +{ +public: +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#endif + BEGIN_COM_MAP(CXTypeLib) +#if defined __clang__ +#pragma clang diagnostic pop +#endif + COM_INTERFACE_ENTRY(ITypeLib) +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winconsistent-missing-override" +#pragma clang diagnostic ignored "-Wunused-function" +#endif + END_COM_MAP() +#if defined __clang__ +#pragma clang diagnostic pop +#endif + + DECLARE_NOT_AGGREGATABLE(CXTypeLib) + + virtual ~CXTypeLib() {} + + void Init(Reference<XInterface> xOrigin, + const OUString& sImplementationName, + Reference<XMultiServiceFactory> xMSF) + { + SAL_INFO("extensions.olebridge", this << "@CXTypeLib::Init for " << sImplementationName); + mxOrigin = xOrigin; + msImplementationName = sImplementationName; + mxMSF = xMSF; + } + + virtual UINT STDMETHODCALLTYPE GetTypeInfoCount() override + { + SAL_WARN("extensions.olebridge", this << "@CXTypeLib::GetTypeInfoCount"); + return 1; + } + + virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT, + ITypeInfo **) override + { + SAL_WARN("extensions.olebridge", this << "@CXTypeLib::GetTypeInfo: E_NOTIMPL"); + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE GetTypeInfoType(UINT, + TYPEKIND *) override + { + SAL_WARN("extensions.olebridge", this << "@CXTypeLib::GetTypeInfoType: E_NOTIMPL"); + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE GetTypeInfoOfGuid(REFGUID guid, + ITypeInfo **ppTInfo) override + { + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + SAL_INFO("extensions.olebridge", this << "@CXTypeLib::GetTypeInfoOfGuid(" << guid << ")"); + if (!ppTInfo) + return E_POINTER; + + Reference<ooo::vba::XConnectable> xConnectable(mxOrigin, UNO_QUERY); + if (!xConnectable.is()) + return TYPE_E_ELEMENTNOTFOUND; + + IID aIID; + if (SUCCEEDED(IIDFromString(reinterpret_cast<LPOLESTR>(xConnectable->getIID().pData->buffer), &aIID))) + { + if (IsEqualIID(guid, aIID)) + { + HRESULT ret; + + CComObject<CXTypeInfo>* pTypeInfo; + + ret = CComObject<CXTypeInfo>::CreateInstance(&pTypeInfo); + if (FAILED(ret)) + return ret; + + pTypeInfo->AddRef(); + + pTypeInfo->InitForCoclass(mxOrigin, msImplementationName, aIID, mxMSF); + + *ppTInfo = pTypeInfo; + + return S_OK; + } + } + +#if 0 + ooo::vba::TypeAndIID aTypeAndIID = xConnectable->GetConnectionPoint(); + + IID aIID; + if (SUCCEEDED(IIDFromString((LPOLESTR)aTypeAndIID.IID.pData->buffer, &aIID))) + { + HRESULT ret; + + CComObject<CXTypeInfo>* pTypeInfo; + + ret = CComObject<CXTypeInfo>::CreateInstance(&pTypeInfo); + if (FAILED(ret)) + return ret; + + pTypeInfo->AddRef(); + + pTypeInfo->InitForOutgoing(mxOrigin, msImplementationName, aIID, mxMSF); + + *ppTInfo = pTypeInfo; + + return S_OK; + } +#else + SAL_WARN("extensions.olebridge", "Not implemented: GetTypeInfoOfGuid(" << guid << ")"); +#endif + + return TYPE_E_ELEMENTNOTFOUND; + + } + + virtual HRESULT STDMETHODCALLTYPE GetLibAttr(TLIBATTR **) override + { + SAL_WARN("extensions.olebridge", this << "@CXTypeLib::GetLibAttr: E_NOTIMPL"); + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE GetTypeComp(ITypeComp **) override + { + SAL_WARN("extensions.olebridge", this << "@CXTypeLib::GetTypeComp: E_NOTIMPL"); + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE GetDocumentation(INT, + BSTR *, + BSTR *, + DWORD *, + BSTR *) override + { + SAL_WARN("extensions.olebridge", this << "@CXTypeLib::GetDocumentation: E_NOTIMPL"); + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE IsName(LPOLESTR, + ULONG, + BOOL *) override + { + SAL_WARN("extensions.olebridge", this << "@CXTypeLib:IsName: E_NOTIMPL"); + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE FindName(LPOLESTR, + ULONG, + ITypeInfo **, + MEMBERID *, + USHORT *) override + { + SAL_WARN("extensions.olebridge", this << "@CXTypeLib::FindName: E_NOTIMPL"); + return E_NOTIMPL; + } + + virtual void STDMETHODCALLTYPE ReleaseTLibAttr(TLIBATTR *) override + { + SAL_WARN("extensions.olebridge", this << "@CXTypeLib::ReleaseTLibAttr: E_NOTIMPL"); + } + +private: + Reference<XInterface> mxOrigin; + OUString msImplementationName; + Reference<XMultiServiceFactory> mxMSF; +}; + +} + +void CXTypeInfo::InitForCoclass(Reference<XInterface> xOrigin, + const OUString& sImplementationName, + const IID& rIID, + Reference<XMultiServiceFactory> xMSF) +{ + SAL_INFO("extensions.olebridge", this << "@CXTypeInfo::InitForCoclass(" << sImplementationName << "," << rIID << ")"); + meKind = Kind::COCLASS; + mxOrigin = xOrigin; + msImplementationName = sImplementationName; + maIID = rIID; + mxMSF = xMSF; +} + +void CXTypeInfo::InitForClassItself(Reference<XInterface> xOrigin, + const OUString& sImplementationName, + const IID& rIID, + Reference<XMultiServiceFactory> xMSF) +{ + SAL_INFO("extensions.olebridge", this << "@CXTypeInfo::InitForClassItself(" << sImplementationName << "," << rIID << ")"); + meKind = Kind::MAIN; + mxOrigin = xOrigin; + msImplementationName = sImplementationName; + maIID = rIID; + mxMSF = xMSF; +} + +void CXTypeInfo::InitForOutgoing(Reference<XInterface> xOrigin, + const OUString& sInterfaceName, + const IID& rIID, + Reference<XMultiServiceFactory> xMSF, + Type aType) +{ + SAL_INFO("extensions.olebridge", this << "@CXTypeInfo::InitForOutgoing(" << sInterfaceName << "," << rIID << ")"); + meKind = Kind::OUTGOING; + mxOrigin = xOrigin; + msInterfaceName = sInterfaceName; + maIID = rIID; + mxMSF = xMSF; + maType = aType; +} + +HRESULT STDMETHODCALLTYPE CXTypeInfo::GetTypeAttr(TYPEATTR **ppTypeAttr) +{ + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + SAL_INFO("extensions.olebridge", this << "@CXTypeInfo::GetTypeAttr"); + + if (!ppTypeAttr) + return E_POINTER; + + assert(!IsEqualIID(maIID, IID_NULL)); + + TYPEATTR *pTypeAttr = new TYPEATTR; + memset(pTypeAttr, 0, sizeof(*pTypeAttr)); + + pTypeAttr->guid = maIID; + + if (meKind == Kind::COCLASS) + { + pTypeAttr->typekind = TKIND_COCLASS; + pTypeAttr->cFuncs = 0; + pTypeAttr->cVars = 0; + pTypeAttr->cImplTypes = 3; + pTypeAttr->cbSizeVft = 0; + pTypeAttr->cbAlignment = 8; + pTypeAttr->wTypeFlags = TYPEFLAG_FCANCREATE; + } + else if (meKind == Kind::MAIN) + { + pTypeAttr->typekind = TKIND_DISPATCH; + pTypeAttr->cFuncs = 10; // FIXME, dummy + pTypeAttr->cVars = 0; + pTypeAttr->cImplTypes = 1; + // FIXME: I think this is always supposed to be as if just for the seven methods in + // IDIspatch? + pTypeAttr->cbSizeVft = 7 * sizeof(void*); + pTypeAttr->cbAlignment = 8; + pTypeAttr->wTypeFlags = TYPEFLAG_FHIDDEN|TYPEFLAG_FDISPATCHABLE; + } + else if (meKind == Kind::OUTGOING) + { + pTypeAttr->typekind = TKIND_DISPATCH; + + Reference<XIdlReflection> xRefl = theCoreReflection::get(comphelper::getComponentContext(mxMSF)); + assert(xRefl.is()); + + Reference<XIdlClass> xClass = xRefl->forName(maType.getTypeName()); + assert(xClass.is()); + + auto aMethods = xClass->getMethods(); + assert(xClass->getTypeClass() == TypeClass_INTERFACE && + aMethods.getLength() > 0); + + // Drop the three XInterface methods, add the three corresponding IUnknown ones plus the + // four IDispatch ones on top of that. + pTypeAttr->cFuncs = aMethods.getLength() - 3 + 3 + 4; + pTypeAttr->cVars = 0; + pTypeAttr->cImplTypes = 1; + // FIXME: I think this, too, is always supposed to be as if just for the seven methods in + // IDIspatch? + pTypeAttr->cbSizeVft = 7 * sizeof(void*); + pTypeAttr->cbAlignment = 8; + pTypeAttr->wTypeFlags = TYPEFLAG_FHIDDEN|TYPEFLAG_FNONEXTENSIBLE|TYPEFLAG_FDISPATCHABLE; + } + else + assert(false); + + pTypeAttr->lcid = LOCALE_USER_DEFAULT; + pTypeAttr->memidConstructor = MEMBERID_NIL; + pTypeAttr->memidDestructor = MEMBERID_NIL; + // FIXME: Is this correct, just the vtable pointer, right? + pTypeAttr->cbSizeInstance = sizeof(void*); + pTypeAttr->wMajorVerNum = 0; + pTypeAttr->wMinorVerNum = 0; + pTypeAttr->idldescType.wIDLFlags = IDLFLAG_NONE; + + SAL_INFO("extensions.olebridge", this << "@CXTypeInfo::GetTypeAttr: " << pTypeAttr); + + *ppTypeAttr = pTypeAttr; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE CXTypeInfo::GetTypeComp(ITypeComp **) +{ + SAL_WARN("extensions.olebridge", this << "@CXTypeInfo::GetTypeComp: E_NOTIMPL"); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE CXTypeInfo::GetFuncDesc(UINT index, + FUNCDESC **ppFuncDesc) +{ + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + if (!ppFuncDesc) + return E_POINTER; + + if (meKind != Kind::OUTGOING) + return E_NOTIMPL; + + if (index <= 6) + { + *ppFuncDesc = new FUNCDESC; + (*ppFuncDesc)->memid = 0x60000000 + index; + (*ppFuncDesc)->lprgscode = nullptr; + (*ppFuncDesc)->lprgelemdescParam = nullptr; + (*ppFuncDesc)->funckind = FUNC_DISPATCH; + (*ppFuncDesc)->invkind = INVOKE_FUNC; + (*ppFuncDesc)->callconv = CC_STDCALL; + switch (index) + { + case 0: // QueryInterface + (*ppFuncDesc)->cParams = 2; + (*ppFuncDesc)->elemdescFunc.tdesc.lptdesc = nullptr; + (*ppFuncDesc)->elemdescFunc.tdesc.vt = VT_VOID; + break; + case 1: // AddRef + (*ppFuncDesc)->cParams = 0; + (*ppFuncDesc)->elemdescFunc.tdesc.lptdesc = nullptr; + (*ppFuncDesc)->elemdescFunc.tdesc.vt = VT_UI4; + break; + case 2: // Release + (*ppFuncDesc)->cParams = 1; + (*ppFuncDesc)->elemdescFunc.tdesc.lptdesc = nullptr; + (*ppFuncDesc)->elemdescFunc.tdesc.vt = VT_UI4; + break; + case 3: // GetTypeInfoCount + (*ppFuncDesc)->cParams = 1; + (*ppFuncDesc)->elemdescFunc.tdesc.lptdesc = nullptr; + (*ppFuncDesc)->elemdescFunc.tdesc.vt = VT_VOID; + break; + case 4: // GetTypeInfo + (*ppFuncDesc)->cParams = 3; + (*ppFuncDesc)->elemdescFunc.tdesc.lptdesc = nullptr; + (*ppFuncDesc)->elemdescFunc.tdesc.vt = VT_VOID; + break; + case 5: // GetIDsOfNames + (*ppFuncDesc)->cParams = 5; + (*ppFuncDesc)->elemdescFunc.tdesc.lptdesc = nullptr; + (*ppFuncDesc)->elemdescFunc.tdesc.vt = VT_VOID; + break; + case 6: // Invoke + (*ppFuncDesc)->cParams = 8; + (*ppFuncDesc)->elemdescFunc.tdesc.lptdesc = nullptr; + (*ppFuncDesc)->elemdescFunc.tdesc.vt = VT_VOID; + break; + } + (*ppFuncDesc)->cParamsOpt = 0; + (*ppFuncDesc)->oVft = index * sizeof(void*); + (*ppFuncDesc)->cScodes = 0; + (*ppFuncDesc)->wFuncFlags = FUNCFLAG_FRESTRICTED; + + SAL_INFO("extensions.olebridge", this << "@CXTypeInfo::GetFuncDesc(" << index << "): S_OK: " << *ppFuncDesc); + + return S_OK; + } + + Reference<XIdlReflection> xRefl = theCoreReflection::get(comphelper::getComponentContext(mxMSF)); + assert(xRefl.is()); + + Reference<XIdlClass> xClass = xRefl->forName(maType.getTypeName()); + assert(xClass.is()); + + auto aMethods = xClass->getMethods(); + assert(xClass->getTypeClass() == TypeClass_INTERFACE && + aMethods.getLength() > 0); + + if (index > o3tl::make_unsigned(aMethods.getLength() - 3 + 3 + 4)) + return E_INVALIDARG; + + *ppFuncDesc = new FUNCDESC; + + (*ppFuncDesc)->memid = index - 6; + (*ppFuncDesc)->lprgscode = nullptr; + (*ppFuncDesc)->lprgelemdescParam = nullptr; + (*ppFuncDesc)->funckind = FUNC_DISPATCH; + (*ppFuncDesc)->invkind = INVOKE_FUNC; + (*ppFuncDesc)->callconv = CC_STDCALL; + (*ppFuncDesc)->cParams = aMethods[index - 4]->getParameterInfos().getLength(); + (*ppFuncDesc)->cParamsOpt = 0; + (*ppFuncDesc)->oVft = index * sizeof(void*); + (*ppFuncDesc)->cScodes = 0; + (*ppFuncDesc)->elemdescFunc.tdesc.lptdesc = nullptr; // ??? + (*ppFuncDesc)->elemdescFunc.tdesc.vt = VT_VOID; // ??? + (*ppFuncDesc)->wFuncFlags = 0; + + SAL_INFO("extensions.olebridge", this << "@CXTypeInfo::GetFuncDesc(" << index << "): S_OK: " << *ppFuncDesc); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE CXTypeInfo::GetVarDesc(UINT, + VARDESC **) +{ + SAL_WARN("extensions.olebridge", this << "@CXTypeInfo::GetVarDesc: E_NOTIMPL"); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE CXTypeInfo::GetNames(MEMBERID memid, + BSTR *rgBstrNames, + UINT cMaxNames, + UINT *pcNames) +{ + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + SAL_INFO("extensions.olebridge", this << "@CXTypeInfo::GetNames(" << memid << ")"); + assert(meKind != Kind::COCLASS); + + if (!rgBstrNames) + return E_POINTER; + + if (!pcNames) + return E_POINTER; + + if (memid < 1) + return E_INVALIDARG; + + if (cMaxNames < 1) + return E_INVALIDARG; + + if (meKind == Kind::MAIN) + { + SAL_WARN("extensions.olebridge", "GetNames() for MAIN not implemented"); + return E_NOTIMPL; + } + + Reference<XIdlReflection> xRefl = theCoreReflection::get(comphelper::getComponentContext(mxMSF)); + assert(xRefl.is()); + + Reference<XIdlClass> xClass = xRefl->forName(maType.getTypeName()); + assert(xClass.is()); + + auto aMethods = xClass->getMethods(); + assert(xClass->getTypeClass() == TypeClass_INTERFACE && + aMethods.getLength() > 0); + + // Subtract the three XInterface methods. Memid for the first following method is 1. + if (memid > aMethods.getLength() - 3) + return E_INVALIDARG; + + SAL_INFO("extensions.olebridge", "..." << this << "@CXTypeInfo::GetNames(" << memid << "): " << aMethods[memid + 2]->getName()); + rgBstrNames[0] = SysAllocString(reinterpret_cast<LPOLESTR>(aMethods[memid + 2]->getName().pData->buffer)); + *pcNames = 1; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE CXTypeInfo::GetRefTypeOfImplType(UINT index, + HREFTYPE *pRefType) +{ + SAL_INFO("extensions.olebridge", this << "@CXTypeInfo::GetRefTypeOfImplType(" << index << ")"); + + if (!pRefType) + return E_POINTER; + + assert(index == 0 || index == 1); + + *pRefType = 1000+index; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE CXTypeInfo::GetImplTypeFlags(UINT index, + INT *pImplTypeFlags) +{ + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + SAL_INFO("extensions.olebridge", this << "@CXTypeInfo::GetImplTypeFlags(" << index << ")"); + + if (!pImplTypeFlags) + return E_POINTER; + + assert(meKind == Kind::COCLASS); + assert(index == 0 || index == 1); + + if (index == 0) + *pImplTypeFlags = IMPLTYPEFLAG_FDEFAULT; + else if (index == 1) + *pImplTypeFlags = IMPLTYPEFLAG_FDEFAULT|IMPLTYPEFLAG_FSOURCE; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE CXTypeInfo::GetIDsOfNames(LPOLESTR *, + UINT, + MEMBERID *) +{ + SAL_WARN("extensions.olebridge", this << "@CXTypeInfo::GetIDsOfNames: E_NOTIMPL"); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE CXTypeInfo::Invoke(PVOID, + MEMBERID, + WORD, + DISPPARAMS *, + VARIANT *, + EXCEPINFO *, + UINT *) +{ + SAL_WARN("extensions.olebridge", this << "@CXTypeInfo::Invoke: E_NOTIMPL"); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE CXTypeInfo::GetDocumentation(MEMBERID memid, + BSTR *pBstrName, + BSTR *pBstrDocString, + DWORD *pdwHelpContext, + BSTR *pBstrHelpFile) +{ + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + SAL_INFO("extensions.olebridge", this << "@CXTypeInfo::GetDocumentation(" << memid << ")"); + + if (pBstrName) + { + if (memid == MEMBERID_NIL) + { + *pBstrName = SysAllocString(o3tl::toW(msImplementationName.getStr())); + } + else if (memid == DISPID_VALUE) + { + // MEMBERIDs are the same as DISPIDs, apparently? + *pBstrName = SysAllocString(L"Value"); + } + else + { + // FIXME: Shouldn't we be able to know the names of the members of UNO interfaces? + *pBstrName = SysAllocString(o3tl::toW(OUString("UnknownNameOfMember#" + OUString::number(memid)).getStr())); + } + } + if (pBstrDocString) + *pBstrDocString = SysAllocString(L""); + if (pdwHelpContext) + *pdwHelpContext = 0; + if (pBstrHelpFile) + *pBstrHelpFile = nullptr; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE CXTypeInfo::GetDllEntry(MEMBERID, + INVOKEKIND, + BSTR *, + BSTR *, + WORD *) +{ + SAL_WARN("extensions.olebridge", this << "@CXTypeInfo::GetDllEntry: E_NOTIMPL"); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE CXTypeInfo::GetRefTypeInfo(HREFTYPE hRefType, + ITypeInfo **ppTInfo) +{ + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + SAL_INFO("extensions.olebridge", this << "@CXTypeInfo::GetRefTypeInfo(" << hRefType << ")"); + + if (!ppTInfo) + return E_POINTER; + + // FIXME: Is it correct to assume that the only interfaces on which GetRefTypeInfo() would be + // called are those that implement ooo::vba::XConnectable? + + Reference<ooo::vba::XConnectable> xConnectable(mxOrigin, UNO_QUERY); + if (!xConnectable.is()) + return E_NOTIMPL; + + ooo::vba::TypeAndIID aTypeAndIID = xConnectable->GetConnectionPoint(); + + IID aIID; + if (!SUCCEEDED(IIDFromString(reinterpret_cast<LPOLESTR>(aTypeAndIID.IID.pData->buffer), &aIID))) + return E_NOTIMPL; + + HRESULT ret; + + CComObject<CXTypeInfo>* pTypeInfo; + + ret = CComObject<CXTypeInfo>::CreateInstance(&pTypeInfo); + if (FAILED(ret)) + return ret; + + pTypeInfo->AddRef(); + + pTypeInfo->InitForOutgoing(mxOrigin, aTypeAndIID.Type.getTypeName(), aIID, mxMSF, aTypeAndIID.Type); + + *ppTInfo = pTypeInfo; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE CXTypeInfo::AddressOfMember(MEMBERID, + INVOKEKIND, + PVOID *) +{ + SAL_WARN("extensions.olebridge", this << "@CXTypeInfo::AddressOfMember: E_NOTIMPL"); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE CXTypeInfo::CreateInstance(IUnknown *, + REFIID, + PVOID *) +{ + SAL_WARN("extensions.olebridge", this << "@CXTypeInfo::CreateInstance: E_NOTIMPL"); + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE CXTypeInfo::GetMops(MEMBERID, + BSTR *) +{ + SAL_WARN("extensions.olebridge", this << "@CXTypeInfo::GetMops: E_NOTIMPL"); + return E_NOTIMPL; +} + +// This is not actually called any more by my vbscript test after I added the IProvideClassInfo +// thing... so all the CXTypeLib stuff is dead code at the moment. + +HRESULT STDMETHODCALLTYPE CXTypeInfo::GetContainingTypeLib(ITypeLib **ppTLib, + UINT *pIndex) +{ + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + SAL_INFO("extensions.olebridge", this << "@CXTypeInfo::GetContainingTypeLib"); + + if (!ppTLib || !pIndex) + return E_POINTER; + + HRESULT ret; + + CComObject<CXTypeLib>* pTypeLib; + + ret = CComObject<CXTypeLib>::CreateInstance(&pTypeLib); + if (FAILED(ret)) + return ret; + + pTypeLib->AddRef(); + + pTypeLib->Init(mxOrigin, msImplementationName, mxMSF); + + *ppTLib = pTypeLib; + + return S_OK; +} + +void STDMETHODCALLTYPE CXTypeInfo::ReleaseTypeAttr(TYPEATTR *pTypeAttr) +{ + SAL_INFO("extensions.olebridge", this << "@CXTypeInfo::ReleaseTypeAttr(" << pTypeAttr << ")"); + + delete pTypeAttr; +} + +void STDMETHODCALLTYPE CXTypeInfo::ReleaseFuncDesc(FUNCDESC *pFuncDesc) +{ + SAL_WARN("extensions.olebridge", this << "@CXTypeInfo::ReleaseFuncDesc(" << pFuncDesc << ")"); + + delete pFuncDesc; +} + +void STDMETHODCALLTYPE CXTypeInfo::ReleaseVarDesc(VARDESC *) +{ + SAL_WARN("extensions.olebridge", this << "@CXTypeInfo::ReleaseVarDesc: E_NOTIMPL"); +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP InterfaceOleWrapper::GetTypeInfo(UINT iTInfo, LCID, ITypeInfo ** ppTInfo) +{ + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + SAL_INFO("extensions.olebridge", this << "@InterfaceOleWrapper::GetTypeInfo(" << iTInfo << ")"); + + if (!ppTInfo) + return E_POINTER; + + if (iTInfo != 0) + return E_NOTIMPL; + + // FIXME: This is surely incorrect. Why is being able to handle GetTypeInfo() here coupled to + // being a source for outgoing events, i.e. implementing XConnectable? What would break if we + // would use XInterfaceWithIID and its getIID instead? + + Reference<ooo::vba::XConnectable> xConnectable(m_xOrigin, UNO_QUERY); + if (!xConnectable.is()) + return E_NOTIMPL; + + OUString sIID = xConnectable->GetIIDForClassItselfNotCoclass(); + IID aIID; + if (!SUCCEEDED(IIDFromString(reinterpret_cast<LPOLESTR>(sIID.pData->buffer), &aIID))) + return E_NOTIMPL; + + HRESULT ret; + + CComObject<CXTypeInfo>* pTypeInfo; + + ret = CComObject<CXTypeInfo>::CreateInstance(&pTypeInfo); + if (FAILED(ret)) + return ret; + + pTypeInfo->AddRef(); + + pTypeInfo->InitForClassItself(m_xOrigin, m_sImplementationName, aIID, m_smgr); + + *ppTInfo = pTypeInfo; + + return S_OK; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP InterfaceOleWrapper::GetIDsOfNames(REFIID /*riid*/, + LPOLESTR * rgszNames, + UINT cNames, + LCID /*lcid*/, + DISPID * rgdispid ) +{ + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + if( ! rgdispid) + return E_POINTER; + + SAL_INFO("extensions.olebridge", this << "@InterfaceOleWrapper::GetIDsOfNames:"); + for (unsigned int i = 0; i < cNames; ++i) + { + // Initialise returned rgdispid values. + rgdispid[i] = DISPID_UNKNOWN; + + SAL_INFO("extensions.olebridge", " " << OUString(o3tl::toU(rgszNames[i]))); + } + + HRESULT ret = DISP_E_UNKNOWNNAME; + try + { + MutexGuard guard( getBridgeMutex()); + + // FIXME: Handle the cNames > 1 case? Note that the rest of the names mean the names of *arguments*. + + if( ! _wcsicmp( *rgszNames, JSCRIPT_VALUE_FUNC) || + ! _wcsicmp( *rgszNames, BRIDGE_VALUE_FUNC)) + { + *rgdispid= DISPID_JSCRIPT_VALUE_FUNC; + return S_OK; + } + else if( ! _wcsicmp( *rgszNames, GET_STRUCT_FUNC) || + ! _wcsicmp( *rgszNames, BRIDGE_GET_STRUCT_FUNC)) + { + *rgdispid= DISPID_GET_STRUCT_FUNC; + return S_OK; + } + else if( ! _wcsicmp( *rgszNames, BRIDGE_CREATE_TYPE_FUNC)) + { + *rgdispid= DISPID_CREATE_TYPE_FUNC; + return S_OK; + } + + if (m_xInvocation.is() && (cNames > 0)) + { + OUString name(o3tl::toU(rgszNames[0])); + NameToIdMap::iterator iter = m_nameToDispIdMap.find(name); + + bool bIsMethod = false; + + OUString exactName = name; + + if (iter == m_nameToDispIdMap.end()) + { + if (m_xExactName.is()) + { + exactName = m_xExactName->getExactName(name); + if (exactName.isEmpty()) + exactName = name; + } + + MemberInfo d(0, exactName); + + if (m_xInvocation->hasProperty(exactName)) + { + d.flags |= DISPATCH_PROPERTYGET; + d.flags |= DISPATCH_PROPERTYPUT; + d.flags |= DISPATCH_PROPERTYPUTREF; + } + + if (m_xInvocation->hasMethod(exactName)) + { + d.flags |= DISPATCH_METHOD; + bIsMethod = true; + } + + if (d.flags != 0) + { + m_MemberInfos.push_back(d); + iter = m_nameToDispIdMap.emplace(exactName, static_cast<DISPID>(m_MemberInfos.size())).first; + + if (exactName != name) + { + iter = m_nameToDispIdMap.emplace(name, static_cast<DISPID>(m_MemberInfos.size())).first; + } + } + } + + if (iter == m_nameToDispIdMap.end()) + { + ret = DISP_E_UNKNOWNNAME; + SAL_INFO("extensions.olebridge", " " << name << ": UNKNOWN"); + } + else + { + rgdispid[0] = (*iter).second; + SAL_INFO("extensions.olebridge", " " << name << ": " << rgdispid[0]); + + if (bIsMethod && cNames > 1) + { + Reference<XIdlMethod> xIdlMethod; + Reference<XIntrospectionAccess> xIntrospectionAccess = m_xInvocation->getIntrospection(); + try + { + if (xIntrospectionAccess.is()) + xIdlMethod = xIntrospectionAccess->getMethod(exactName, MethodConcept::ALL); + } + catch (const NoSuchMethodException&) + { + } + if (xIdlMethod.is()) + { + auto aParamInfos = xIdlMethod->getParameterInfos(); + for (unsigned int i = 1; i < cNames; ++i) + { + bool bFound = false; + for (int j = 0; j < aParamInfos.getLength(); ++j) + { + if (aParamInfos[j].aName.equalsIgnoreAsciiCase(o3tl::toU(rgszNames[i]))) + { + rgdispid[i] = j; + bFound = true; + SAL_INFO("extensions.olebridge", " " << OUString(o3tl::toU(rgszNames[i])) << ": " << rgdispid[i]); + break; + } + } + if (!bFound) + SAL_INFO("extensions.olebridge", " " << OUString(o3tl::toU(rgszNames[i])) << ": NOT FOUND"); + } + } + } + + // Return value should be S_OK only if *all* the names were found. + unsigned int i; + for (i = 0; i < cNames; ++i) + if (rgdispid[i] == DISPID_UNKNOWN) + break; + if (i == cNames) + ret = S_OK; + } + } + } + catch(const BridgeRuntimeError&) + { + OSL_ASSERT(false); + } + catch(const Exception&) + { + OSL_ASSERT(false); + } + catch(...) + { + OSL_ASSERT(false); + } + + return ret; +} + +// Note: What the comments here say about JScript possibly holds for Automation clients in general, +// like VBScript ones, too. Or not. Hard to say. What is the relevance of JScript nowadays anyway, +// and can LO really be used from JScript code on web pages any longer? + +// "convertDispparamsArgs" converts VARIANTS to their respecting Any counterparts +// The parameters "id", "wFlags" and "pdispparams" equal those as used in +// IDispatch::Invoke. The function handles special JavaScript +// cases where a VARIANT of type VT_DISPATCH is ambiguous and could represent +// an object, array ( JavaScript Array object), out parameter and in/out ( JavaScript Array object) +// parameter (JavaScript Array object) +// Because all those VT_DISPATCH objects need a different conversion +// we have to find out what the object is supposed to be. The function does this +// by either using type information or by help of a specialized ValueObject object. + +// A. Type Information + +// With the help of type information the kind of parameter can be exactly determined +// and an appropriate conversion can be chosen. A problem arises if a method expects +// an Any. Then the type info does not tell what the type of the value, that is kept +// by the any, should be. In this situation the decision whether the param is a +// sequence or an object is made upon the fact if the object has a property "0" +// ( see function "isJScriptArray"). Since this is unsafe it is recommended to use +// the JScript value objects within a JScript script on such an occasion. + +// B. JavaScript Value Object ( class JScriptValue ) + +// A JScriptValue (ValueObject) object is a COM object in that it implements IDispatch and the +// IJScriptValue object interface. Such objects are provided by all UNO wrapper +// objects used within a JScript script. To obtain an instance one has to call +// "_GetValueObject() or Bridge_GetValueObject()" on a UNO wrapper object (class InterfaceOleWrapper). +// A value object is appropriately initialized within the script and passed as +// parameter to a UNO object method or property. The convertDispparamsArgs function +// can easily find out that a param is such an object by querying for the +// IJScriptValue interface. By this interface one the type and kind ( out, in/out) +// can be determined and the right conversion can be applied. +// Using ValueObjects we spare us the effort of acquiring and examining type information +// in order to figure out what the an IDispatch parameter is meant for. + +// Normal JScript object parameter can be mixed with JScriptValue object. If an +// VARIANT contains a VT_DISPATCH that is no JScriptValue than the type information +// is used to find out about the required type. +void InterfaceOleWrapper::convertDispparamsArgs(DISPID id, + unsigned short /*wFlags*/, DISPPARAMS* pdispparams, Sequence<Any>& rSeq) +{ + // Parameters come in in reverse order in pdispparams. There might be less parameters than + // expected. In that case, assume they are "optional" (but can't be marked as such in UNO IDL), + // and fill in the rest with empty Anys. There might also be more than expected. In that case, + // assume the oovbaapi UNO IDL hasn't kept up with added optional parameters in MSO, and just + // ignore the extra ones, as long as they are empty. + + // An example: incoming parameters: <12, 13, "foo/bar.tem"> + // + // Expected parameters: (string filename, int something, int somethingElse, Any whatever, Any + // whateverElse) + // + // Here the existing incoming parameters are placed in reverse order in the first three outgoing + // parameters, and the rest of the outgoing parameters are kept as empty Anys. + // + // Another example: incoming parameters: <EMPTY, TRUE> + // + // Expected parameters: (bool flag) + // + // Here the TRUE is passed as the sole outgoing parameter, and the incoming EMPTY is ignored. + // + // Still an example: incoming parameters: <"foo.doc", TRUE> + // + // Expected parameters: (bool flag) + // + // This throws an error as the incoming string parameter presumably should do something important, + // but there is no corresponding outgoing parameter. + + HRESULT hr = S_OK; + const int countIncomingArgs = pdispparams->cArgs; + + //Get type information for the current call + InvocationInfo info; + if( ! getInvocationInfoForCall( id, info)) + throw BridgeRuntimeError( + "[automation bridge]InterfaceOleWrapper::convertDispparamsArgs \n" + "Could not obtain type information for current call."); + + // Size rSeq according to the number of expected parameters. + const int expectedArgs = info.aParamTypes.getLength() + (info.eMemberType == MemberType_PROPERTY ? 1 : 0); + rSeq.realloc( expectedArgs ); + Any* pParams = rSeq.getArray(); + + Any anyParam; + + int outgoingArgIndex = 0; + + // Go through incoming parameters in reverse order, i.e. in the order as declared in IDL + for (int i = std::max(countIncomingArgs, expectedArgs) - 1; i >= 0; i--) + { + // Ignore too many parameters if they are VT_EMPTY anyway + if ( outgoingArgIndex >= expectedArgs && pdispparams->rgvarg[i].vt == VT_EMPTY ) + continue; + + // But otherwise too many parameters is an error + if ( outgoingArgIndex >= expectedArgs ) + throw BridgeRuntimeError( "[automation bridge] Too many parameters" ); + + if (info.eMemberType == MemberType_METHOD && + info.aParamModes[ outgoingArgIndex ] == ParamMode_OUT) + { + outgoingArgIndex++; + continue; + } + + if (i < countIncomingArgs) + { + // A missing (and hopefully optional) arg (in the middle of the argument list) is passed + // as an empty Any. + if (pdispparams->rgvarg[i].vt == VT_ERROR && pdispparams->rgvarg[i].scode == DISP_E_PARAMNOTFOUND) + { + Any aEmpty; + pParams[ outgoingArgIndex ] = aEmpty; + outgoingArgIndex++; + continue; + } + + if(convertValueObject( & pdispparams->rgvarg[i], anyParam)) + { //a param is a ValueObject and could be converted + pParams[ outgoingArgIndex ] = anyParam; + outgoingArgIndex++; + continue; + } + } + else + { + // A missing arg. Let's hope it is de facto optional (there is no way in UNO IDL to mark + // a parameter as optional). The corresponding slot in pParams is already a void Any. + // Here we don't increase outgoingArgIndex! + continue; + } + + // If the param is an out, in/out parameter in + // JScript (Array object, with value at index 0) then we + // extract Array[0] and put the value into varParam. At the end of the loop varParam + // is converted if it contains a value otherwise the VARIANT from + // DISPPARAMS is converted. + CComVariant varParam; + + // Check for JScript out and in/out paramsobjects (VT_DISPATCH). + // To find them out we use typeinformation of the function being called. + + // No idea how this stuff, originally written for JScript, works for other Automation + // clients. + + if( pdispparams->rgvarg[i].vt == VT_DISPATCH ) + { + if( info.eMemberType == MemberType_METHOD && info.aParamModes[ outgoingArgIndex ] == ParamMode_INOUT) + { + // INOUT-param + // Index ( property) "0" contains the actual IN-param. The object is a JScript + // Array object. + // Get the IN-param at index "0" + IDispatch* pdisp= pdispparams->rgvarg[i].pdispVal; + + OLECHAR const * sindex= L"0"; + DISPID id2; + DISPPARAMS noParams= {nullptr,nullptr,0,0}; + if(SUCCEEDED( hr= pdisp->GetIDsOfNames( IID_NULL, const_cast<OLECHAR **>(&sindex), 1, LOCALE_USER_DEFAULT, &id2))) + hr= pdisp->Invoke( id2, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, + & noParams, & varParam, nullptr, nullptr); + if( FAILED( hr)) + { + throw BridgeRuntimeError( + "[automation bridge] Could not determine " + "if the object has a member \"0\". Error: " + + OUString::number(hr)); + } + } + } + + if( varParam.vt == VT_EMPTY) // then it was no in/out parameter + varParam= pdispparams->rgvarg[i]; + + if(info.eMemberType == MemberType_METHOD) + variantToAny( & varParam, anyParam, + info.aParamTypes[ outgoingArgIndex ]); + else if(info.eMemberType == MemberType_PROPERTY) + variantToAny( & varParam, anyParam, info.aType); + else + OSL_ASSERT(false); + + if (outgoingArgIndex < expectedArgs) + pParams[ outgoingArgIndex ]= anyParam; + outgoingArgIndex++; + }// end for / iterating over all parameters +} + +bool InterfaceOleWrapper::getInvocationInfoForCall( DISPID id, InvocationInfo& info) +{ + bool bTypesAvailable= false; + + if( !m_xInvocation.is() )return false; + Reference<XInvocation2> inv2( m_xInvocation, UNO_QUERY); + if( inv2.is()) + { + // We need the name of the property or method to get its type information. + // The name can be identified through the param "id" + // that is kept as value in the map m_nameToDispIdMap. + // Problem: the Windows JScript engine sometimes changes small letters to capital + // letters as happens in xidlclass_obj.createObject( var) // in JScript. + // IDispatch::GetIdsOfNames is then called with "CreateObject" !!! + // m_nameToDispIdMap can contain several names for one DISPID but only one is + // the exact one. If there's no m_xExactName and therefore no exact name then + // there's only one entry in the map. + OUString sMemberName; + + auto ci1 = std::find_if(m_nameToDispIdMap.cbegin(), m_nameToDispIdMap.cend(), + [&id](const NameToIdMap::value_type& nameToDispId) { return nameToDispId.second == id; }); // item is a pair<OUString, DISPID> + if (ci1 != m_nameToDispIdMap.cend()) + sMemberName= (*ci1).first; + // Get information for the current call ( property or method). + // There could be similar names which only differ in the cases + // of letters. First we assume that the name which was passed into + // GetIDsOfNames is correct. If we won't get information with that + // name then we have the invocation service use the XExactName interface. + bool validInfo= true; + InvocationInfo invInfo; + try{ + invInfo= inv2->getInfoForName( sMemberName, false); + } + catch(const IllegalArgumentException&) + { + validInfo= false; + } + + if( ! validInfo) + { + invInfo= inv2->getInfoForName( sMemberName, true); + } + if( invInfo.aName.pData) + { + bTypesAvailable= true; + info= invInfo; + } + } + return bTypesAvailable; +} + +// XBridgeSupplier2 --------------------------------------------------- +// only bridges itself ( this instance of InterfaceOleWrapper)from UNO to IDispatch +// If sourceModelType is UNO than any UNO interface implemented by InterfaceOleWrapper +// can bridged to IDispatch ( if destModelType == OLE). The IDispatch is +// implemented by this class. +Any SAL_CALL InterfaceOleWrapper::createBridge(const Any& modelDepObject, + const Sequence<sal_Int8>& /*ProcessId*/, + sal_Int16 sourceModelType, + sal_Int16 destModelType) +{ + + Any retAny; + if( sourceModelType == UNO && destModelType == OLE && + modelDepObject.getValueTypeClass() == TypeClass_INTERFACE ) + { + Reference<XInterface> xInt; + if( modelDepObject >>= xInt ) + { + if( xInt == Reference<XInterface>( static_cast<XWeak*>( this), UNO_QUERY)) + { + VARIANT *pVar= static_cast<VARIANT*>(CoTaskMemAlloc( sizeof( VARIANT))); + if( pVar) + { + pVar->vt= VT_DISPATCH; + pVar->pdispVal= this; + AddRef(); + + retAny<<= reinterpret_cast< sal_uIntPtr >( pVar); + } + } + } + } + + return retAny; +} + +// XInitialization -------------------------------------------------- +void SAL_CALL InterfaceOleWrapper::initialize( const Sequence< Any >& aArguments ) +{ + switch( aArguments.getLength() ) + { + case 2: // the object wraps a UNO struct + aArguments[0] >>= m_xInvocation; + aArguments[1] >>= m_defaultValueType; + break; + case 3: // the object wraps a UNO interface + aArguments[0] >>= m_xInvocation; + aArguments[1] >>= m_xOrigin; + aArguments[2] >>= m_defaultValueType; + + Reference<XServiceInfo> xServiceInfo(m_xOrigin, UNO_QUERY); + if (xServiceInfo.is()) + m_sImplementationName = xServiceInfo->getImplementationName(); + + SAL_INFO("extensions.olebridge", this << "@InterfaceOleWrapper::initialize for " + << (m_sImplementationName.isEmpty()?"an unknown implementation":m_sImplementationName)); + break; + } + + m_xExactName.set( m_xInvocation, UNO_QUERY); +} + +Reference< XInterface > InterfaceOleWrapper::createUnoWrapperInstance() +{ + Reference<XWeak> xWeak= static_cast<XWeak*>( new InterfaceOleWrapper( + m_smgr, m_nUnoWrapperClass, m_nComWrapperClass)); + return Reference<XInterface>( xWeak, UNO_QUERY); +} + +Reference<XInterface> InterfaceOleWrapper::createComWrapperInstance() +{ + Reference<XWeak> xWeak= static_cast<XWeak*>( new IUnknownWrapper( + m_smgr, m_nUnoWrapperClass, m_nComWrapperClass)); + return Reference<XInterface>( xWeak, UNO_QUERY); +} + +// "getType" is used in convertValueObject to map the string denoting the type +// to an actual Type object. +bool getType( const BSTR name, Type & type) +{ + bool ret = false; + typelib_TypeDescription * pDesc= nullptr; + OUString str(o3tl::toU(name)); + typelib_typedescription_getByName( &pDesc, str.pData ); + if( pDesc) + { + type = Type( pDesc->pWeakRef ); + typelib_typedescription_release( pDesc); + ret = true; + } + return ret; +} + +static bool writeBackOutParameter2( VARIANTARG* pDest, VARIANT* pSource) +{ + bool ret = false; + HRESULT hr; + + // Handle JScriptValue objects and JScript out params ( Array object ) + CComVariant varDest( *pDest); + + if( SUCCEEDED( varDest.ChangeType(VT_DISPATCH))) + { + CComPtr<IDispatch> spDispDest(varDest.pdispVal); + + // special Handling for a JScriptValue object + CComQIPtr<IJScriptValueObject> spValueDest(spDispDest); + if (spValueDest) + { + VARIANT_BOOL varBool= VARIANT_FALSE; + if ((SUCCEEDED(hr = spValueDest->IsOutParam(&varBool)) + && varBool == VARIANT_TRUE) + || (SUCCEEDED(hr = spValueDest->IsInOutParam(&varBool)) + && varBool == VARIANT_TRUE)) + { + if( SUCCEEDED( spValueDest->Set( CComVariant(), *pSource))) + ret= true; + } + } + else if (pDest->vt == VT_DISPATCH)// VT_DISPATCH -> JScript out param + { + // We use IDispatchEx because its GetDispID function causes the creation + // of a property if it does not exist already. This is convenient for + // out parameters in JScript. Then the user must not specify property "0" + // explicitly + CComQIPtr<IDispatchEx> spDispEx( spDispDest); + if( spDispEx) + { + CComBSTR nullProp(L"0"); + DISPID dwDispID; + if( SUCCEEDED( spDispEx->GetDispID( nullProp, fdexNameEnsure, &dwDispID))) + { + DISPPARAMS dispparams = {nullptr, nullptr, 1, 1}; + dispparams.rgvarg = pSource; + DISPID dispidPut = DISPID_PROPERTYPUT; + dispparams.rgdispidNamedArgs = &dispidPut; + + if (pSource->vt == VT_UNKNOWN || pSource->vt == VT_DISPATCH || + (pSource->vt & VT_ARRAY) || (pSource->vt & VT_BYREF)) + hr = spDispEx->InvokeEx(dwDispID, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUTREF, + &dispparams, nullptr, nullptr, nullptr); + else + hr= spDispEx->InvokeEx(dwDispID, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, + &dispparams, nullptr, nullptr, nullptr); + if( SUCCEEDED(hr)) + ret= true; + } + } + } + else + ret= writeBackOutParameter( pDest, pSource); + } + else // The param can't be a JScript out-parameter ( an Array object), it could be a VBScript + { // param. The function checks itself for correct VBScript params + ret= writeBackOutParameter( pDest, pSource); + } + return ret; +} + +// VisualBasic Script passes arguments as VT_VARIANT | VT_BYREF be it in or out parameter. +// Thus we are in charge of freeing an eventual value contained by the inner VARIANT +// Please note: VariantCopy doesn't free a VT_BYREF value +// The out parameters are expected to have always a valid type +static bool writeBackOutParameter(VARIANTARG* pDest, VARIANT* pSource) +{ + HRESULT hr; + bool ret = false; + // Out parameter must be VT_BYREF + if ((V_VT(pDest) & VT_BYREF) != 0 ) + { + VARTYPE oleTypeFlags = V_VT(pSource); + + // if caller accept VARIANT as out parameter, any value must be converted + if (V_VT(pDest) == (VT_VARIANT | VT_BYREF)) + { + // When the user provides a VARIANT rather than a concrete type + // we just copy the source to the out, in/out parameter + // VT_DISPATCH, VT_UNKNOWN, VT_ARRAY, VT_BSTR in the VARIANT that + // is contained in pDest are released by VariantCopy + VariantCopy(V_VARIANTREF(pDest), pSource); + ret = true; + } + else + { + // variantarg and variant must have same type + if ((V_VT(pDest) & oleTypeFlags) == oleTypeFlags) + { + if ((oleTypeFlags & VT_ARRAY) != 0) + { + // In / Out Param + if( *V_ARRAYREF(pDest) != nullptr) + hr= SafeArrayCopyData( V_ARRAY(pSource), *V_ARRAYREF(pDest)); + else + // Out Param + hr= SafeArrayCopy(V_ARRAY(pSource), V_ARRAYREF(pDest)); + if( SUCCEEDED( hr)) + ret = true; + } + else + { + // copy base type + switch (V_VT(pSource)) + { + case VT_I2: + { + *V_I2REF(pDest) = V_I2(pSource); + ret = true; + break; + } + case VT_I4: + *V_I4REF(pDest) = V_I4(pSource); + ret = true; + break; + case VT_R4: + *V_R4REF(pDest) = V_R4(pSource); + ret = true; + break; + case VT_R8: + *V_R8REF(pDest) = V_R8(pSource); + ret = true; + break; + case VT_CY: + *V_CYREF(pDest) = V_CY(pSource); + ret = true; + break; + case VT_DATE: + *V_DATEREF(pDest) = V_DATE(pSource); + ret = true; + break; + case VT_BSTR: + SysFreeString( *pDest->pbstrVal); + + *V_BSTRREF(pDest) = SysAllocString(V_BSTR(pSource)); + ret = true; + break; + case VT_DISPATCH: + if (*V_DISPATCHREF(pDest) != nullptr) + (*V_DISPATCHREF(pDest))->Release(); + + *V_DISPATCHREF(pDest) = V_DISPATCH(pSource); + + if (*V_DISPATCHREF(pDest) != nullptr) + (*V_DISPATCHREF(pDest))->AddRef(); + + ret = true; + break; + case VT_ERROR: + *V_ERRORREF(pDest) = V_ERROR(pSource); + ret = true; + break; + case VT_BOOL: + *V_BOOLREF(pDest) = V_BOOL(pSource); + ret = true; + break; + case VT_UNKNOWN: + if (*V_UNKNOWNREF(pDest) != nullptr) + (*V_UNKNOWNREF(pDest))->Release(); + + *V_UNKNOWNREF(pDest) = V_UNKNOWN(pSource); + + if (*V_UNKNOWNREF(pDest) != nullptr) + (*V_UNKNOWNREF(pDest))->AddRef(); + + ret = true; + break; + case VT_I1: + *V_I1REF(pDest) = V_I1(pSource); + ret = true; + break; + case VT_UI1: + *V_UI1REF(pDest) = V_UI1(pSource); + ret = true; + break; + case VT_UI2: + *V_UI2REF(pDest) = V_UI2(pSource); + ret = true; + break; + case VT_UI4: + *V_UI4REF(pDest) = V_UI4(pSource); + ret = true; + break; + case VT_INT: + *V_INTREF(pDest) = V_INT(pSource); + ret = true; + break; + case VT_UINT: + *V_UINTREF(pDest) = V_UINT(pSource); + ret = true; + break; + case VT_DECIMAL: + memcpy(pDest->pdecVal, pSource, sizeof(DECIMAL)); + ret = true; + break; + default: + break; + } + } + } + else + { + // Handling of special cases + // Destination and source types are different + if( pDest->vt == (VT_BSTR | VT_BYREF) + && pSource->vt == VT_I2) + { + // When the user provides a String as out our in/out parameter + // and the type is char (TypeClass_CHAR) then we convert to a BSTR + // instead of VT_I2 as is done otherwise + OLECHAR buff[]= {0,0}; + buff[0]= pSource->iVal; + + SysFreeString( *pDest->pbstrVal); + *pDest->pbstrVal= SysAllocString( buff); + ret = true; + } + } + } + } + return ret; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP InterfaceOleWrapper::Invoke(DISPID dispidMember, + REFIID /*riid*/, + LCID /*lcid*/, + WORD wFlags, + DISPPARAMS * pdispparams, + VARIANT * pvarResult, + EXCEPINFO * pexcepinfo, + UINT * puArgErr ) +{ + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + OUString sParams; +#if defined SAL_LOG_INFO + sParams += "["; + for (unsigned int i = 0; i < pdispparams->cArgs; ++i) + { + if (i > 0) + sParams += ","; + std::stringstream aStringStream; + aStringStream << pdispparams->rgvarg[i]; + sParams += OUString::createFromAscii(aStringStream.str()); + } + sParams += "]"; +#endif + SAL_INFO("extensions.olebridge", this << "@InterfaceOleWrapper::Invoke(" << dispidMember << "," << sParams << ")"); + + comphelper::ProfileZone aZone("COM Bridge"); + HRESULT ret = S_OK; + + try + { + bool bHandled= false; + ret= InvokeGeneral( dispidMember, wFlags, pdispparams, pvarResult, pexcepinfo, + puArgErr, bHandled); + if( bHandled) + return ret; + + if ((dispidMember > 0) && (o3tl::make_unsigned(dispidMember) <= m_MemberInfos.size()) && m_xInvocation.is()) + { + MemberInfo d = m_MemberInfos[dispidMember - 1]; + DWORD flags = wFlags & d.flags; + + if (flags != 0) + { + if ((flags & DISPATCH_METHOD) != 0) + { + std::unique_ptr<DISPPARAMS> pNewDispParams; + std::vector<VARIANTARG> vNewArgs; + + if (pdispparams->cNamedArgs > 0) + { + // Convert named arguments to positional ones. + + // An example: + // + // Function declaration (in pseudo-code): + // int foo(int A, int B, optional int C, optional int D, optional int E, optional int F, optional int G) + // + // Corresponding parameter numbers (DISPIDs): + // 0 1 2 3 4 5 6 + // + // Actual call: + // foo(10, 20, E:=50, D:=40, F:=60) + // + // That is, A and B are passed positionally, D, E, and F as named arguments, + // and the optional C and G parameters are left out. + // + // Incoming DISPPARAMS: + // cArgs=5, cNamedArgs=3 + // rgvarg: [60, 40, 50, 20, 10] + // rgdispidNamedArgs: [5, 3, 4] + // + // We calculate nLowestNamedArgDispid = 3 and nHighestNamedArgDispid = 5. + // + // Result of conversion, no named args: + // cArgs=6, cNamedArgs=0 + // rgvarg: [60, 50, 40, DISP_E_PARAMNOTFOUND, 20, 10] + + // First find the lowest and highest DISPID of the named arguments. + DISPID nLowestNamedArgDispid = 1000000; + DISPID nHighestNamedArgDispid = -1; + for (unsigned int i = 0; i < pdispparams->cNamedArgs; ++i) + { + if (pdispparams->rgdispidNamedArgs[i] < nLowestNamedArgDispid) + nLowestNamedArgDispid = pdispparams->rgdispidNamedArgs[i]; + if (pdispparams->rgdispidNamedArgs[i] > nHighestNamedArgDispid) + nHighestNamedArgDispid = pdispparams->rgdispidNamedArgs[i]; + } + + // Make sure named arguments don't overlap with positional ones. The lowest + // DISPID of the named arguments should be >= the number of positional + // arguments. + if (nLowestNamedArgDispid < static_cast<DISPID>(pdispparams->cArgs - pdispparams->cNamedArgs)) + return DISP_E_NONAMEDARGS; + + // Do the actual conversion. + pNewDispParams.reset(new DISPPARAMS); + vNewArgs.resize(nHighestNamedArgDispid + 1); + pNewDispParams->rgvarg = vNewArgs.data(); + pNewDispParams->rgdispidNamedArgs = nullptr; + pNewDispParams->cArgs = nHighestNamedArgDispid + 1; + pNewDispParams->cNamedArgs = 0; + + // Initialise all parameter slots as missing + for (int i = 0; i < nHighestNamedArgDispid; ++i) + { + pNewDispParams->rgvarg[i].vt = VT_ERROR; + pNewDispParams->rgvarg[i].scode = DISP_E_PARAMNOTFOUND; + } + + // Then set the value of those actually present. + for (unsigned int i = 0; i < pdispparams->cNamedArgs; ++i) + pNewDispParams->rgvarg[nHighestNamedArgDispid - pdispparams->rgdispidNamedArgs[i]] = pdispparams->rgvarg[i]; + + const int nFirstUnnamedArg = pdispparams->cNamedArgs + (nLowestNamedArgDispid-(pdispparams->cArgs - pdispparams->cNamedArgs)); + + for (unsigned int i = pdispparams->cNamedArgs; i < pdispparams->cArgs; ++i) + pNewDispParams->rgvarg[nFirstUnnamedArg + (i-pdispparams->cNamedArgs)] = pdispparams->rgvarg[i]; + + pdispparams = pNewDispParams.get(); + } + + Sequence<Any> params; + + convertDispparamsArgs(dispidMember, wFlags, pdispparams , params ); + + ret= doInvoke(pdispparams, pvarResult, + pexcepinfo, puArgErr, d.name, params); + } + else if ((flags & DISPATCH_PROPERTYGET) != 0) + { + ret= doGetProperty( pdispparams, pvarResult, + pexcepinfo, d.name); + } + else if ((flags & DISPATCH_PROPERTYPUT) != 0 || (flags & DISPATCH_PROPERTYPUTREF) != 0) + { + if (pdispparams->cArgs != 1) + ret = DISP_E_BADPARAMCOUNT; + else + { + Sequence<Any> params; + convertDispparamsArgs(dispidMember, wFlags, pdispparams, params ); + if(params.getLength() > 0) + ret= doSetProperty( pdispparams, pvarResult, pexcepinfo, puArgErr, d.name, params); + else + ret = DISP_E_BADVARTYPE; + } + } + } + else + ret= DISP_E_MEMBERNOTFOUND; + } + else + ret = DISP_E_MEMBERNOTFOUND; + } + catch(const BridgeRuntimeError& e) + { + writeExcepinfo(pexcepinfo, e.message); + ret = DISP_E_EXCEPTION; + } + catch(const Exception& e) + { + OUString message= "InterfaceOleWrapper::Invoke : \n" + + e.Message; + writeExcepinfo(pexcepinfo, message); + ret = DISP_E_EXCEPTION; + } + catch(...) + { + writeExcepinfo(pexcepinfo, "InterfaceOleWrapper::Invoke : \nUnexpected exception"); + ret = DISP_E_EXCEPTION; + } + + return ret; +} + +HRESULT InterfaceOleWrapper::doInvoke( DISPPARAMS * pdispparams, VARIANT * pvarResult, + EXCEPINFO * pexcepinfo, unsigned int * puArgErr, OUString& name, Sequence<Any>& params) +{ + + + HRESULT ret= S_OK; + try + { + Sequence<sal_Int16> outIndex; + Sequence<Any> outParams; + Any returnValue; + + if (pdispparams->cNamedArgs > 0) + return DISP_E_NONAMEDARGS; + + // invoke method and take care of exceptions + returnValue = m_xInvocation->invoke(name, + params, + outIndex, + outParams); + + // try to write back out parameter + if (outIndex.getLength() > 0) + { + const sal_Int16* pOutIndex = outIndex.getConstArray(); + const Any* pOutParams = outParams.getConstArray(); + + for (sal_Int32 i = 0; i < outIndex.getLength(); i++) + { + CComVariant variant; + // Currently a Sequence is converted to an SafeArray of VARIANTs. + anyToVariant( &variant, pOutParams[i]); + + // out parameter need special handling if they are VT_DISPATCH + // and used in JScript + int outindex= pOutIndex[i]; + writeBackOutParameter2(&(pdispparams->rgvarg[pdispparams->cArgs - 1 - outindex]), + &variant ); + } + } + + // write back return value + if (pvarResult != nullptr) + anyToVariant(pvarResult, returnValue); + } + catch(const IllegalArgumentException & e) //XInvocation::invoke + { + writeExcepinfo(pexcepinfo, e.Message); + ret = DISP_E_TYPEMISMATCH; + } + catch(const CannotConvertException & e) //XInvocation::invoke + { + writeExcepinfo(pexcepinfo, e.Message); + ret = mapCannotConvertException( e, puArgErr); + } + catch(const InvocationTargetException & e) //XInvocation::invoke + { + const Any& org = e.TargetException; + Exception excTarget; + org >>= excTarget; + OUString message= + org.getValueType().getTypeName() + ": " + excTarget.Message; + writeExcepinfo(pexcepinfo, message); + ret = DISP_E_EXCEPTION; + } + catch(const NoSuchMethodException & e) //XInvocation::invoke + { + writeExcepinfo(pexcepinfo, e.Message); + ret = DISP_E_MEMBERNOTFOUND; + } + catch(const BridgeRuntimeError & e) + { + writeExcepinfo(pexcepinfo, e.message); + ret = DISP_E_EXCEPTION; + } + catch(const Exception & e) + { + OUString message= "InterfaceOleWrapper::doInvoke : \n" + + e.Message; + writeExcepinfo(pexcepinfo, message); + ret = DISP_E_EXCEPTION; + } + catch( ... ) + { + writeExcepinfo(pexcepinfo, "InterfaceOleWrapper::doInvoke : \nUnexpected exception"); + ret = DISP_E_EXCEPTION; + } + return ret; +} + +HRESULT InterfaceOleWrapper::doGetProperty( DISPPARAMS * /*pdispparams*/, VARIANT * pvarResult, + EXCEPINFO * pexcepinfo, OUString& name) +{ + HRESULT ret= S_OK; + + try + { + Any returnValue = m_xInvocation->getValue( name); + // write back return value + if (pvarResult) + anyToVariant(pvarResult, returnValue); + } + catch(const UnknownPropertyException& e) //XInvocation::getValue + { + writeExcepinfo(pexcepinfo, e.Message); + ret = DISP_E_MEMBERNOTFOUND; + } + catch(const BridgeRuntimeError& e) + { + writeExcepinfo(pexcepinfo, e.message); + ret = DISP_E_EXCEPTION; + } + catch(const Exception& e) + { + OUString message= "InterfaceOleWrapper::doGetProperty : \n" + + e.Message; + writeExcepinfo(pexcepinfo, message); + } + catch( ... ) + { + writeExcepinfo(pexcepinfo, "InterfaceOleWrapper::doInvoke : \nUnexpected exception"); + ret = DISP_E_EXCEPTION; + } + return ret; +} + +HRESULT InterfaceOleWrapper::doSetProperty( DISPPARAMS * /*pdispparams*/, VARIANT * /*pvarResult*/, + EXCEPINFO * pexcepinfo, unsigned int * puArgErr, OUString& name, Sequence<Any> const & params) +{ + HRESULT ret= S_OK; + + try + { + m_xInvocation->setValue( name, params.getConstArray()[0]); + } + catch(const UnknownPropertyException &) + { + ret = DISP_E_MEMBERNOTFOUND; + } + catch(const CannotConvertException &e) + { + ret= mapCannotConvertException( e, puArgErr); + } + catch(const InvocationTargetException &e) + { + if (pexcepinfo != nullptr) + { + Any org = e.TargetException; + + pexcepinfo->wCode = UNO_2_OLE_EXCEPTIONCODE; + pexcepinfo->bstrSource = SysAllocString(L"any ONE component"); + pexcepinfo->bstrDescription = SysAllocString( + o3tl::toW(org.getValueType().getTypeName().getStr())); + } + ret = DISP_E_EXCEPTION; + } + catch( ... ) + { + ret= DISP_E_EXCEPTION; + } + return ret; +} + +namespace { + +class CXEnumVariant : public IEnumVARIANT, + public CComObjectRoot +{ +public: + CXEnumVariant() + : mnIndex(1) // ooo::vba::XCollection index starts at one + { + } + + virtual ~CXEnumVariant() + { + } + +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#endif + BEGIN_COM_MAP(CXEnumVariant) +#if defined __clang__ +#pragma clang diagnostic pop +#endif + COM_INTERFACE_ENTRY(IEnumVARIANT) +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winconsistent-missing-override" +#pragma clang diagnostic ignored "-Wunused-function" +#endif + END_COM_MAP() +#if defined __clang__ +#pragma clang diagnostic pop +#endif + + DECLARE_NOT_AGGREGATABLE(CXEnumVariant) + + // Creates and initializes the enumerator + void Init(InterfaceOleWrapper* pInterfaceOleWrapper, + const Reference<ooo::vba::XCollection > xCollection) + { + mpInterfaceOleWrapper = pInterfaceOleWrapper; + mxCollection = xCollection; + } + + // IEnumVARIANT + virtual HRESULT STDMETHODCALLTYPE Clone(IEnumVARIANT **) override + { + SAL_INFO("extensions.olebridge", this << "@CXEnumVariant::Clone: E_NOTIMPL"); + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE Next(ULONG const celt, + VARIANT *rgVar, + ULONG *pCeltFetched) override + { + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + if (pCeltFetched) + *pCeltFetched = 0; + + if (celt == 0) + { + SAL_INFO("extensions.olebridge", this << "@CXEnumVariant::Next(" << celt << "): E_INVALIDARG"); + return E_INVALIDARG; + } + + if (rgVar == nullptr || (celt != 1 && pCeltFetched == nullptr)) + { + SAL_INFO("extensions.olebridge", this << "@CXEnumVariant::Next(" << celt << "): E_FAIL"); + return E_FAIL; + } + + for (ULONG i = 0; i < celt; i++) + VariantInit(&rgVar[i]); + + ULONG nLeft = celt; + ULONG nReturned = 0; + while (nLeft > 0) + { + if (mnIndex > mxCollection->getCount()) + { + SAL_INFO("extensions.olebridge", this << "@CXEnumVariant::Next(" << celt << "): got " << nReturned << ": S_FALSE"); + return S_FALSE; + } + Any aIndex; + aIndex <<= mnIndex; + Any aElement = mxCollection->Item(aIndex, Any()); + mpInterfaceOleWrapper->anyToVariant(rgVar, aElement); + // rgVar->pdispVal->AddRef(); ?? + if (pCeltFetched) + (*pCeltFetched)++; + rgVar++; + nReturned++; + mnIndex++; + nLeft--; + } + SAL_INFO("extensions.olebridge", this << "@CXEnumVariant::Next(" << celt << "): S_OK"); + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE Reset() override + { + SAL_INFO("extensions.olebridge", this << "@CXEnumVariant::Reset: S_OK"); + mnIndex = 1; + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE STDMETHODCALLTYPE Skip(ULONG const celt) override + { + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + ULONG nLeft = celt; + ULONG nSkipped = 0; + while (nLeft > 0) + { + if (mnIndex > mxCollection->getCount()) + { + SAL_INFO("extensions.olebridge", this << "@CXEnumVariant::Skip(" << celt << "): skipped " << nSkipped << ": S_FALSE"); + return S_FALSE; + } + mnIndex++; + nLeft--; + } + SAL_INFO("extensions.olebridge", this << "@CXEnumVariant::Skip(" << celt << "): S_OK"); + return S_OK; + } + +private: + InterfaceOleWrapper* mpInterfaceOleWrapper; + Reference<ooo::vba::XCollection> mxCollection; + sal_Int32 mnIndex; +}; + +class Sink : public cppu::WeakImplHelper<ooo::vba::XSink> +{ +public: + Sink(IUnknown* pUnkSink, + Reference<XMultiServiceFactory> xMSF, + ooo::vba::TypeAndIID aTypeAndIID, + InterfaceOleWrapper* pInterfaceOleWrapper); + + // XSink + void SAL_CALL Call( const OUString& Method, Sequence< Any >& Arguments ) override; + +private: + IUnknown* mpUnkSink; + Reference<XMultiServiceFactory> mxMSF; + ooo::vba::TypeAndIID maTypeAndIID; + InterfaceOleWrapper* mpInterfaceOleWrapper; +}; + +} + +Sink::Sink(IUnknown* pUnkSink, + Reference<XMultiServiceFactory> xMSF, + ooo::vba::TypeAndIID aTypeAndIID, + InterfaceOleWrapper* pInterfaceOleWrapper) : + mpUnkSink(pUnkSink), + mxMSF(xMSF), + maTypeAndIID(aTypeAndIID), + mpInterfaceOleWrapper(pInterfaceOleWrapper) +{ + mpUnkSink->AddRef(); +} + +void SAL_CALL +Sink::Call( const OUString& Method, Sequence< Any >& Arguments ) +{ + SAL_INFO("extensions.olebridge", "Sink::Call(" << Method << ", " << Arguments.getLength() << " arguments)"); + + IDispatch* pDispatch; + HRESULT nResult = mpUnkSink->QueryInterface(IID_IDispatch, reinterpret_cast<void **>(&pDispatch)); + if (!SUCCEEDED(nResult)) + { + SAL_WARN("extensions.olebridge", "Sink::Call: Not IDispatch: " << WindowsErrorStringFromHRESULT(nResult)); + return; + } + + Reference<XIdlReflection> xRefl = theCoreReflection::get(comphelper::getComponentContext(mxMSF)); + assert(xRefl.is()); + + Reference<XIdlClass> xClass = xRefl->forName(maTypeAndIID.Type.getTypeName()); + assert(xClass.is()); + + auto aMethods = xClass->getMethods(); + assert(xClass->getTypeClass() == TypeClass_INTERFACE && + aMethods.getLength() > 0); + + int nMemId = 1; + auto ArgumentsRange = asNonConstRange(Arguments); + // Skip the three XInterface methods + for (int i = 3; i < aMethods.getLength(); i++) + { + if (aMethods[i]->getName() == Method) + { + // FIXME: Handle mismatch in type of actual argument and parameter of the method. + + // FIXME: Handle mismatch in number of arguments passed and actual number of parameters + // of the method. + + auto aParamInfos = aMethods[i]->getParameterInfos(); + + assert(Arguments.getLength() == aParamInfos.getLength()); + + DISPPARAMS aDispParams; + aDispParams.rgdispidNamedArgs = nullptr; + aDispParams.cArgs = Arguments.getLength(); + aDispParams.cNamedArgs = 0; + aDispParams.rgvarg = new VARIANT[aDispParams.cArgs]; + for (unsigned j = 0; j < aDispParams.cArgs; j++) + { + VariantInit(aDispParams.rgvarg+j); + // Note: Reverse order of arguments in Arguments and aDispParams.rgvarg! + const unsigned nIncomingArgIndex = aDispParams.cArgs - j - 1; + mpInterfaceOleWrapper->anyToVariant(aDispParams.rgvarg+j, Arguments[nIncomingArgIndex]); + + // Handle OUT and INOUT arguments. For instance, the second ('Cancel') parameter to + // DocumentBeforeClose() should be a VT_BYREF|VT_BOOL parameter. Need to handle that + // here. + + if (aParamInfos[nIncomingArgIndex].aMode == ParamMode_OUT || + aParamInfos[nIncomingArgIndex].aMode == ParamMode_INOUT) + { + switch (aDispParams.rgvarg[j].vt) + { + case VT_I2: + aDispParams.rgvarg[j].byref = new SHORT(aDispParams.rgvarg[j].iVal); + aDispParams.rgvarg[j].vt |= VT_BYREF; + break; + case VT_I4: + aDispParams.rgvarg[j].byref = new LONG(aDispParams.rgvarg[j].lVal); + aDispParams.rgvarg[j].vt |= VT_BYREF; + break; + case VT_BSTR: + aDispParams.rgvarg[j].byref = new BSTR(aDispParams.rgvarg[j].bstrVal); + aDispParams.rgvarg[j].vt |= VT_BYREF; + break; + case VT_BOOL: + aDispParams.rgvarg[j].byref = new VARIANT_BOOL(aDispParams.rgvarg[j].boolVal); + aDispParams.rgvarg[j].vt |= VT_BYREF; + break; + default: + assert(false && "Not handled yet"); + break; + } + } + } + + VARIANT aVarResult; + VariantInit(&aVarResult); + UINT uArgErr; + + // In the case of a VBScript client, which uses "late binding", calling Invoke on the + // sink it provides will cause a callback to our CXTypeInfo::GetNames for the given + // member id, and in that we will tell it the name of the corresponding method, and the + // client will know what event handler to invoke based on that name. + // + // As the outgoing interfaces used (ooo::vba::word::XApplicationOutgoing and others) are + // totally not stable and not published in any way, there can be no client that would + // have done "compile-time binding" and where the sink would actually be an object with + // a vtbl corresponding to the outgoing interface. Late binding clients that work like + // VBScript is all we support. + SAL_INFO("extensions.olebridge", "Sink::Call(" << Method << "): Calling Invoke(" << nMemId << ")"); + + nResult = pDispatch->Invoke(nMemId, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &aDispParams, &aVarResult, nullptr, &uArgErr); + SAL_INFO("extensions.olebridge", "Sink::Call(" << Method << "): Invoke() returned"); + + SAL_WARN_IF(!SUCCEEDED(nResult), "extensions.olebridge", "Call to " << Method << " failed: " << WindowsErrorStringFromHRESULT(nResult)); + + // Undo VT_BYREF magic done above. Copy out parameters back to the Anys in Arguments + for (unsigned j = 0; j < aDispParams.cArgs; j++) + { + const unsigned nIncomingArgIndex = aDispParams.cArgs - j - 1; + if (aParamInfos[nIncomingArgIndex].aMode == ParamMode_OUT || + aParamInfos[nIncomingArgIndex].aMode == ParamMode_INOUT) + { + switch (aDispParams.rgvarg[j].vt) + { + case VT_BYREF|VT_I2: + { + SHORT *pI = static_cast<SHORT*>(aDispParams.rgvarg[j].byref); + ArgumentsRange[nIncomingArgIndex] <<= static_cast<sal_Int16>(*pI); + delete pI; + } + break; + case VT_BYREF|VT_I4: + { + LONG *pL = static_cast<LONG*>(aDispParams.rgvarg[j].byref); + ArgumentsRange[nIncomingArgIndex] <<= static_cast<sal_Int32>(*pL); + delete pL; + } + break; + case VT_BYREF|VT_BSTR: + { + BSTR *pBstr = static_cast<BSTR*>(aDispParams.rgvarg[j].byref); + ArgumentsRange[nIncomingArgIndex] <<= OUString(o3tl::toU(*pBstr)); + // Undo SysAllocString() done in anyToVariant() + SysFreeString(*pBstr); + delete pBstr; + } + break; + case VT_BYREF|VT_BOOL: + { + VARIANT_BOOL *pBool = static_cast<VARIANT_BOOL*>(aDispParams.rgvarg[j].byref); + ArgumentsRange[nIncomingArgIndex] <<= (*pBool != VARIANT_FALSE); + delete pBool; + } + break; + default: + assert(false && "Not handled yet"); + break; + } + } + else + { + switch (aDispParams.rgvarg[j].vt) + { + case VT_BSTR: + // Undo SysAllocString() done in anyToVariant() + SysFreeString(aDispParams.rgvarg[j].bstrVal); + break; + } + } + } + + delete[] aDispParams.rgvarg; + return; + } + nMemId++; + } + SAL_WARN("extensions.olebridge", "Sink::Call: Unknown method '" << Method << "'"); +} + +namespace { + +class CXEnumConnections : public IEnumConnections, + public CComObjectRoot +{ +public: + CXEnumConnections() + { + } + + virtual ~CXEnumConnections() + { + } + +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#endif + BEGIN_COM_MAP(CXEnumConnections) +#if defined __clang__ +#pragma clang diagnostic pop +#endif + COM_INTERFACE_ENTRY(IEnumConnections) +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winconsistent-missing-override" +#pragma clang diagnostic ignored "-Wunused-function" +#endif + END_COM_MAP() +#if defined __clang__ +#pragma clang diagnostic pop +#endif + + DECLARE_NOT_AGGREGATABLE(CXEnumConnections) + + void Init(std::vector<IUnknown*>& rUnknowns, std::vector<DWORD>& rCookies) + { + SAL_INFO("extensions.olebridge", this << "@CXEnumConnections::Init"); + SAL_WARN_IF(rUnknowns.size() != rCookies.size(), "extensions.olebridge", "Vectors of different size"); + mvUnknowns = rUnknowns; + mvCookies = rCookies; + mnIndex = 0; + } + + virtual HRESULT STDMETHODCALLTYPE Next(ULONG cConnections, + LPCONNECTDATA rgcd, + ULONG *pcFetched) override + { + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + if (!rgcd) + { + SAL_INFO("extensions.olebridge", this << "@CXEnumConnections::Next(" << cConnections << "): E_POINTER"); + return E_POINTER; + } + + if (pcFetched && cConnections != 1) + { + SAL_INFO("extensions.olebridge", this << "@CXEnumConnections::Next(" << cConnections << "): E_INVALIDARG"); + return E_INVALIDARG; + } + + ULONG nFetched = 0; + while (nFetched < cConnections && mnIndex < mvUnknowns.size()) + { + rgcd[nFetched].pUnk = mvUnknowns[mnIndex]; + rgcd[nFetched].pUnk->AddRef(); + rgcd[nFetched].dwCookie = mvCookies[mnIndex]; + ++nFetched; + ++mnIndex; + } + if (nFetched != cConnections) + { + SAL_INFO("extensions.olebridge", this << "@CXEnumConnections::Next(" << cConnections << "): S_FALSE"); + if (pcFetched) + *pcFetched = nFetched; + return S_FALSE; + } + SAL_INFO("extensions.olebridge", this << "@CXEnumConnections::Next(" << cConnections << "): S_OK"); + if (pcFetched) + *pcFetched = nFetched; + + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE Skip(ULONG cConnections) override + { + SAL_INFO("extensions.olebridge", this << "@CXEnumConnections::Skip(" << cConnections << "): E_NOTIMPL"); + + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE Reset() override + { + SAL_INFO("extensions.olebridge", this << "@CXEnumConnections::Reset: E_NOTIMPL"); + + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE Clone(IEnumConnections** /* ppEnum */) override + { + SAL_INFO("extensions.olebridge", this << "@CXEnumConnections::Clone: E_NOTIMPL"); + + return E_NOTIMPL; + } + +private: + std::vector<IUnknown*> mvUnknowns; + std::vector<DWORD> mvCookies; + ULONG mnIndex; +}; + +class CXConnectionPoint : public IConnectionPoint, + public CComObjectRoot +{ +public: +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#endif + BEGIN_COM_MAP(CXConnectionPoint) +#if defined __clang__ +#pragma clang diagnostic pop +#endif + COM_INTERFACE_ENTRY(IConnectionPoint) +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winconsistent-missing-override" +#pragma clang diagnostic ignored "-Wunused-function" +#endif + END_COM_MAP() +#if defined __clang__ +#pragma clang diagnostic pop +#endif + + DECLARE_NOT_AGGREGATABLE(CXConnectionPoint) + + virtual ~CXConnectionPoint() {} + + void Init(InterfaceOleWrapper* pInterfaceOleWrapper, + Reference<ooo::vba::XConnectionPoint>& xCP, + Reference<XMultiServiceFactory>& xMSF, + ooo::vba::TypeAndIID aTypeAndIID) + { + SAL_INFO("extensions.olebridge", this << "@CXConnectionPoint::Init for " << pInterfaceOleWrapper->getImplementationName()); + + IUnknown *pUnknown; + if (SUCCEEDED(QueryInterface(IID_IUnknown, reinterpret_cast<void **>(&pUnknown)))) + { + // In case QI for IUnknown returns a different pointer, but nah, it doesn't + SAL_INFO("extensions.olebridge", " (IUnknown@" << pUnknown << ")"); + } + + mpInterfaceOleWrapper = pInterfaceOleWrapper; + mxCP = xCP; + mxMSF = xMSF; + maTypeAndIID = aTypeAndIID; + } + + virtual HRESULT STDMETHODCALLTYPE GetConnectionInterface(IID *pIID) override + { + SAL_WARN("extensions.olebridge", this << "@CXConnectionPoint::GetConnectionInterface(" << *pIID << "): E_NOTIMPL"); + + // FIXME: Needed? + + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE GetConnectionPointContainer(IConnectionPointContainer **) override + { + SAL_WARN("extensions.olebridge", this << "@CXConnectionPoint::GetConnectionInterface: E_NOTIMPL"); + + // FIXME: Needed? + + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE Advise(IUnknown *pUnkSink, + DWORD *pdwCookie) override + { + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + SAL_INFO("extensions.olebridge", this << "@CXConnectionPoint::Advise(" << pUnkSink << ")"); + + if (!pdwCookie) + return E_POINTER; + + Reference<ooo::vba::XSink> xSink(new Sink(pUnkSink, mxMSF, maTypeAndIID, mpInterfaceOleWrapper)); + + mvISinks.push_back(pUnkSink); + *pdwCookie = mvISinks.size(); + + mvCookies.push_back(mxCP->Advise(xSink)); + + mvXSinks.push_back(xSink); + + SAL_INFO("extensions.olebridge", " *pdwCookie: " << *pdwCookie); + + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE Unadvise(DWORD dwCookie) override + { + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + SAL_INFO("extensions.olebridge", this << "@CXConnectionPoint::Unadvise(" << dwCookie << ")"); + + if (dwCookie == 0 || dwCookie > mvISinks.size()) + return E_POINTER; + + mvISinks[dwCookie-1] = nullptr; + + mxCP->Unadvise(mvCookies[dwCookie-1]); + + mvXSinks[dwCookie-1] = Reference<ooo::vba::XSink>(); + + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE EnumConnections(IEnumConnections **ppEnum) override + { + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + HRESULT nResult; + + SAL_INFO("extensions.olebridge", this << "@CXConnectionPoint::EnumConnections..."); + + if (!ppEnum) + { + SAL_INFO("extensions.olebridge", "..." << this << "@CXConnectionPoint::EnumConnections: E_POINTER"); + return E_POINTER; + } + + CComObject<CXEnumConnections>* pEnumConnections; + + nResult = CComObject<CXEnumConnections>::CreateInstance(&pEnumConnections); + if (FAILED(nResult)) + { + SAL_INFO("extensions.olebridge", "..." << this << "@CXConnectionPoint::EnumConnections: " << WindowsErrorStringFromHRESULT(nResult)); + return nResult; + } + + pEnumConnections->AddRef(); + + pEnumConnections->Init(mvISinks, mvCookies); + *ppEnum = pEnumConnections; + + SAL_INFO("extensions.olebridge", "..." << this << "@CXConnectionPoint::EnumConnections: S_OK"); + + return S_OK; + } + + InterfaceOleWrapper* mpInterfaceOleWrapper; + std::vector<IUnknown*> mvISinks; + std::vector<Reference<ooo::vba::XSink>> mvXSinks; + std::vector<DWORD> mvCookies; + Reference<XMultiServiceFactory> mxMSF; + Reference<ooo::vba::XConnectionPoint> mxCP; + ooo::vba::TypeAndIID maTypeAndIID; +}; + +} + +HRESULT InterfaceOleWrapper::InvokeGeneral( DISPID dispidMember, unsigned short wFlags, + DISPPARAMS * pdispparams, VARIANT * pvarResult, EXCEPINFO * pexcepinfo, + unsigned int * /*puArgErr*/, bool& bHandled) +{ + HRESULT ret= S_OK; + try + { +// DISPID_VALUE | The DEFAULT Value is required in JScript when the situation +// is that we put an object into an Array object ( out parameter). We have to return +// IDispatch otherwise the object cannot be accessed from the Script. + if( dispidMember == DISPID_VALUE && (wFlags & DISPATCH_PROPERTYGET) != 0 + && m_defaultValueType != VT_EMPTY && pvarResult != nullptr) + { + // Special case hack: If it is a ScVbaCheckBox, return the boolean value + Reference<ooo::vba::msforms::XCheckBox> xCheckBox(m_xOrigin, UNO_QUERY); + if (xCheckBox.is()) + { + bHandled = true; + Any aValue = xCheckBox->getValue(); + anyToVariant(pvarResult, aValue); + return S_OK; + } + + bHandled= true; + if( m_defaultValueType == VT_DISPATCH) + { + pvarResult->vt= VT_DISPATCH; + pvarResult->pdispVal= this; + AddRef(); + ret= S_OK; + } + } + + // function: _GetValueObject + else if( dispidMember == DISPID_JSCRIPT_VALUE_FUNC) + { + bHandled= true; + if( !pvarResult) + return E_POINTER; + CComObject< JScriptValue>* pValue; + if( SUCCEEDED( CComObject<JScriptValue>::CreateInstance( &pValue))) + { + pValue->AddRef(); + pvarResult->vt= VT_DISPATCH; + pvarResult->pdispVal= CComQIPtr<IDispatch>(pValue->GetUnknown()); + ret= S_OK; + } + else + ret= DISP_E_EXCEPTION; + } + else if( dispidMember == DISPID_GET_STRUCT_FUNC) + { + bHandled= true; + bool bStruct= false; + + + Reference<XIdlReflection> xRefl = theCoreReflection::get(comphelper::getComponentContext(m_smgr)); + // the first parameter is in DISPPARAMS rgvargs contains the name of the struct. + CComVariant arg; + if( pdispparams->cArgs == 1 && SUCCEEDED( arg.ChangeType( VT_BSTR, &pdispparams->rgvarg[0])) ) + { + Reference<XIdlClass> classStruct= xRefl->forName(OUString(o3tl::toU(arg.bstrVal))); + if( classStruct.is()) + { + Any anyStruct; + classStruct->createObject( anyStruct); + CComVariant var; + anyToVariant( &var, anyStruct ); + + if( var.vt == VT_DISPATCH) + { + VariantCopy( pvarResult, & var); + bStruct= true; + } + } + } + ret= bStruct ? S_OK : DISP_E_EXCEPTION; + } + else if (dispidMember == DISPID_CREATE_TYPE_FUNC) + { + bHandled= true; + if( !pvarResult) + return E_POINTER; + // the first parameter is in DISPPARAMS rgvargs contains the name of the struct. + CComVariant arg; + if( pdispparams->cArgs != 1) + return DISP_E_BADPARAMCOUNT; + if (FAILED( arg.ChangeType( VT_BSTR, &pdispparams->rgvarg[0]))) + return DISP_E_BADVARTYPE; + + //check if the provided name represents a valid type + Type type; + if (!getType(arg.bstrVal, type)) + { + writeExcepinfo(pexcepinfo, OUString::Concat("[automation bridge] A UNO type with the name ") + + o3tl::toU(arg.bstrVal) + " does not exist!"); + return DISP_E_EXCEPTION; + } + + if (!createUnoTypeWrapper(arg.bstrVal, pvarResult)) + { + writeExcepinfo(pexcepinfo, "[automation bridge] InterfaceOleWrapper::InvokeGeneral\n" + "Could not initialize UnoTypeWrapper object!"); + return DISP_E_EXCEPTION; + } + } + else if (dispidMember == DISPID_NEWENUM) + { + bHandled = true; + if( !pvarResult) + return E_POINTER; + + Reference< ooo::vba::XCollection> xCollection(m_xOrigin, UNO_QUERY); + if (!xCollection.is()) + return DISP_E_MEMBERNOTFOUND; + + CComObject<CXEnumVariant>* pEnumVar; + + ret = CComObject<CXEnumVariant>::CreateInstance(&pEnumVar); + if (FAILED(ret)) + return ret; + + pEnumVar->AddRef(); + + pEnumVar->Init(this, xCollection); + + pvarResult->vt = VT_UNKNOWN; + pvarResult->punkVal = nullptr; + + ret = pEnumVar->QueryInterface(IID_IUnknown, reinterpret_cast<void**>(&pvarResult->punkVal)); + if (FAILED(ret)) + { + pEnumVar->Release(); + return ret; + } + } + } + catch(const BridgeRuntimeError & e) + { + writeExcepinfo(pexcepinfo, e.message); + ret = DISP_E_EXCEPTION; + } + catch(const Exception & e) + { + OUString message= "InterfaceOleWrapper::InvokeGeneral : \n" + + e.Message; + writeExcepinfo(pexcepinfo, message); + ret = DISP_E_EXCEPTION; + } + catch( ... ) + { + writeExcepinfo(pexcepinfo, "InterfaceOleWrapper::InvokeGeneral : \nUnexpected exception"); + ret = DISP_E_EXCEPTION; + } + return ret; +} + +STDMETHODIMP InterfaceOleWrapper::GetDispID(BSTR /*bstrName*/, DWORD /*grfdex*/, DISPID __RPC_FAR* /*pid*/) +{ + return ResultFromScode(E_NOTIMPL); +} + +STDMETHODIMP InterfaceOleWrapper::InvokeEx( + /* [in] */ DISPID /*id*/, + /* [in] */ LCID /*lcid*/, + /* [in] */ WORD /*wFlags*/, + /* [in] */ DISPPARAMS __RPC_FAR* /*pdp*/, + /* [out] */ VARIANT __RPC_FAR* /*pvarRes*/, + /* [out] */ EXCEPINFO __RPC_FAR* /*pei*/, + /* [unique][in] */ IServiceProvider __RPC_FAR* /*pspCaller*/) +{ + return ResultFromScode(E_NOTIMPL); +} + +STDMETHODIMP InterfaceOleWrapper::DeleteMemberByName( + /* [in] */ BSTR /*bstr*/, + /* [in] */ DWORD /*grfdex*/) +{ + return ResultFromScode(E_NOTIMPL); +} + +STDMETHODIMP InterfaceOleWrapper::DeleteMemberByDispID(DISPID /*id*/) +{ + return ResultFromScode(E_NOTIMPL); +} + +STDMETHODIMP InterfaceOleWrapper::GetMemberProperties( + /* [in] */ DISPID /*id*/, + /* [in] */ DWORD /*grfdexFetch*/, + /* [out] */ DWORD __RPC_FAR* /*pgrfdex*/) +{ + return ResultFromScode(E_NOTIMPL); +} + +STDMETHODIMP InterfaceOleWrapper::GetMemberName( + /* [in] */ DISPID /*id*/, + /* [out] */ BSTR __RPC_FAR* /*pbstrName*/) +{ + return ResultFromScode(E_NOTIMPL); +} + +STDMETHODIMP InterfaceOleWrapper::GetNextDispID( + /* [in] */ DWORD /*grfdex*/, + /* [in] */ DISPID /*id*/, + /* [out] */ DISPID __RPC_FAR* /*pid*/) +{ + return ResultFromScode(E_NOTIMPL); +} + +STDMETHODIMP InterfaceOleWrapper::GetNameSpaceParent( + /* [out] */ IUnknown __RPC_FAR *__RPC_FAR* /*ppunk*/) +{ + return ResultFromScode(E_NOTIMPL); +} + +// IProvideClassInfo +HRESULT STDMETHODCALLTYPE InterfaceOleWrapper::GetClassInfo ( + /* [out] */ ITypeInfo **ppTI) +{ + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + SAL_INFO("extensions.olebridge", this << "@InterfaceOleWrapper::GetClassInfo"); + + if (!ppTI) + return E_POINTER; + + Reference<ooo::vba::XInterfaceWithIID> xIID(m_xOrigin, UNO_QUERY); + if (!xIID.is()) + return E_NOTIMPL; + + OUString sIID = xIID->getIID(); + IID aIID; + if (!SUCCEEDED(IIDFromString(reinterpret_cast<LPOLESTR>(sIID.pData->buffer), &aIID))) + return E_NOTIMPL; + + HRESULT ret; + + CComObject<CXTypeInfo>* pTypeInfo; + + ret = CComObject<CXTypeInfo>::CreateInstance(&pTypeInfo); + if (FAILED(ret)) + return ret; + + pTypeInfo->AddRef(); + + pTypeInfo->InitForCoclass(m_xOrigin, m_sImplementationName, aIID, m_smgr); + + *ppTI = pTypeInfo; + + return S_OK; +} + +// IConnectionPointContainer +HRESULT STDMETHODCALLTYPE InterfaceOleWrapper::EnumConnectionPoints( + /* [out] */ IEnumConnectionPoints **) +{ + SAL_INFO("extensions.olebridge", this << "@InterfaceOleWrapper::EnumConnectionPoints"); + return ResultFromScode(E_NOTIMPL); +} + +HRESULT STDMETHODCALLTYPE InterfaceOleWrapper::FindConnectionPoint( + /* [in] */ REFIID riid, + /* [out] */ IConnectionPoint **ppCP) +{ + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + SAL_INFO("extensions.olebridge", this << "@InterfaceOleWrapper::FindConnectionPoint(" << riid << ")"); + + if (!ppCP) + return E_POINTER; + + Reference<ooo::vba::XConnectable> xConnectable(m_xOrigin, UNO_QUERY); + + // We checked already + assert(xConnectable.is()); + if (!xConnectable.is()) + return E_NOTIMPL; + + ooo::vba::TypeAndIID aTypeAndIID = xConnectable->GetConnectionPoint(); + + IID aIID; + if (!SUCCEEDED(IIDFromString(reinterpret_cast<LPOLESTR>(aTypeAndIID.IID.pData->buffer), &aIID))) + return E_INVALIDARG; + + if (!IsEqualIID(riid, aIID)) + return E_INVALIDARG; + + Reference<ooo::vba::XConnectionPoint> xCP = xConnectable->FindConnectionPoint(); + if (!xCP.is()) + return E_INVALIDARG; + + HRESULT ret; + + CComObject<CXConnectionPoint>* pConnectionPoint; + + ret = CComObject<CXConnectionPoint>::CreateInstance(&pConnectionPoint); + if (FAILED(ret)) + return ret; + + pConnectionPoint->AddRef(); + + pConnectionPoint->Init(this, xCP, m_smgr, aTypeAndIID); + + *ppCP = pConnectionPoint; + + return S_OK; +} + +// UnoObjectWrapperRemoteOpt --------------------------------------------------- + +UnoObjectWrapperRemoteOpt::UnoObjectWrapperRemoteOpt( Reference<XMultiServiceFactory> const & aFactory, + sal_uInt8 unoWrapperClass, sal_uInt8 comWrapperClass): +InterfaceOleWrapper( aFactory, unoWrapperClass, comWrapperClass), +m_currentId(1) + +{ +} +UnoObjectWrapperRemoteOpt::~UnoObjectWrapperRemoteOpt() +{ +} + +// UnoConversionUtilities +Reference< XInterface > UnoObjectWrapperRemoteOpt::createUnoWrapperInstance() +{ + Reference<XWeak> xWeak= static_cast<XWeak*>( new UnoObjectWrapperRemoteOpt( + m_smgr, m_nUnoWrapperClass, m_nComWrapperClass)); + return Reference<XInterface>( xWeak, UNO_QUERY); +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP UnoObjectWrapperRemoteOpt::GetIDsOfNames ( REFIID /*riid*/, LPOLESTR * rgszNames, UINT cNames, + LCID /*lcid*/, DISPID * rgdispid ) +{ + MutexGuard guard( getBridgeMutex()); + + if( ! rgdispid) + return E_POINTER; + HRESULT ret = E_UNEXPECTED; + + // _GetValueObject + if( ! wcscmp( *rgszNames, JSCRIPT_VALUE_FUNC)) + { + *rgdispid= DISPID_JSCRIPT_VALUE_FUNC; + return S_OK; + } + else if( ! wcscmp( *rgszNames, GET_STRUCT_FUNC)) + { + *rgdispid= DISPID_GET_STRUCT_FUNC; + return S_OK; + } + + if (m_xInvocation.is() && (cNames > 0)) + { + OUString name(o3tl::toU(rgszNames[0])); + // has this name been determined as "bad" + BadNameMap::iterator badIter= m_badNameMap.find( name); + if( badIter == m_badNameMap.end() ) + { + // name has not been bad before( member exists + typedef NameToIdMap::iterator ITnames; + std::pair< ITnames, bool > pair_id= m_nameToDispIdMap.emplace(name, m_currentId++); + // new ID inserted ? + if( pair_id.second ) + {// yes, now create MemberInfo and ad to IdToMemberInfoMap + MemberInfo d(0, name); + m_idToMemberInfoMap.emplace(m_currentId - 1, d); + } + + *rgdispid = pair_id.first->second; + ret = S_OK; + } + else + ret= DISP_E_UNKNOWNNAME; + } + return ret; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP UnoObjectWrapperRemoteOpt::Invoke ( DISPID dispidMember, REFIID /*riid*/, LCID /*lcid*/, WORD wFlags, + DISPPARAMS * pdispparams, VARIANT * pvarResult, EXCEPINFO * pexcepinfo, + UINT * puArgErr ) +{ + comphelper::Automation::AutomationInvokedZone aAutomationActive; + + HRESULT ret = S_OK; + try + { + bool bHandled= false; + ret= InvokeGeneral( dispidMember, wFlags, pdispparams, pvarResult, pexcepinfo, + puArgErr, bHandled); + if( bHandled) + return ret; + + if ( dispidMember > 0 && m_xInvocation.is()) + { + + IdToMemberInfoMap::iterator it_MemberInfo= m_idToMemberInfoMap.find( dispidMember); + if( it_MemberInfo != m_idToMemberInfoMap.end() ) + { + MemberInfo& info= it_MemberInfo->second; + + Sequence<Any> params; // holds converted any s + if( ! info.flags ) + { // DISPID called for the first time + if( wFlags == DISPATCH_METHOD ) + { + convertDispparamsArgs(dispidMember, wFlags, pdispparams, params ); + + if( FAILED( ret= doInvoke( pdispparams, pvarResult, + pexcepinfo, puArgErr, info.name, params)) + && ret == DISP_E_MEMBERNOTFOUND) + { + // try to get the exact name + OUString exactName; + if (m_xExactName.is()) + { + exactName = m_xExactName->getExactName( info.name); + // invoke again + if( !exactName.isEmpty() ) + { + if( SUCCEEDED( ret= doInvoke( pdispparams, pvarResult, + pexcepinfo, puArgErr, exactName, params))) + info.name= exactName; + } + } + } + if( SUCCEEDED( ret ) ) + info.flags= DISPATCH_METHOD; + } + else if( wFlags == DISPATCH_PROPERTYPUT || wFlags == DISPATCH_PROPERTYPUTREF) + { + convertDispparamsArgs(dispidMember, wFlags, pdispparams, params ); + if( FAILED( ret= doSetProperty( pdispparams, pvarResult, + pexcepinfo, puArgErr, info.name, params)) + && ret == DISP_E_MEMBERNOTFOUND) + { + // try to get the exact name + OUString exactName; + if (m_xExactName.is()) + { + exactName = m_xExactName->getExactName( info.name); + // invoke again + if( !exactName.isEmpty() ) + { + if( SUCCEEDED( ret= doSetProperty( pdispparams, pvarResult, + pexcepinfo, puArgErr, exactName, params))) + info.name= exactName; + } + } + } + if( SUCCEEDED( ret ) ) + info.flags= DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYGET; + } + else if( wFlags == DISPATCH_PROPERTYGET) + { + if( FAILED( ret= doGetProperty( pdispparams, pvarResult, + pexcepinfo, info.name)) + && ret == DISP_E_MEMBERNOTFOUND) + { + // try to get the exact name + OUString exactName; + if (m_xExactName.is()) + { + exactName = m_xExactName->getExactName( info.name); + // invoke again + if( !exactName.isEmpty() ) + { + if( SUCCEEDED( ret= doGetProperty( pdispparams, pvarResult, + pexcepinfo, exactName))) + info.name= exactName; + } + } + } + if( SUCCEEDED( ret ) ) + info.flags= DISPATCH_PROPERTYGET | DISPATCH_PROPERTYPUT; + } + else if( wFlags & DISPATCH_METHOD && + (wFlags & DISPATCH_PROPERTYPUT || wFlags & DISPATCH_PROPERTYPUTREF)) + { + + OUString exactName; + // convert params for DISPATCH_METHOD or DISPATCH_PROPERTYPUT + convertDispparamsArgs(dispidMember, wFlags, pdispparams, params ); + // try first as method + if( FAILED( ret= doInvoke( pdispparams, pvarResult, + pexcepinfo, puArgErr, info.name, params)) + && ret == DISP_E_MEMBERNOTFOUND) + { + // try to get the exact name + if (m_xExactName.is()) + { + exactName = m_xExactName->getExactName( info.name); + // invoke again + if( !exactName.isEmpty() ) + { + if( SUCCEEDED( ret= doInvoke( pdispparams, pvarResult, + pexcepinfo, puArgErr, exactName, params))) + info.name= exactName; + } + } + } + if( SUCCEEDED( ret ) ) + info.flags= DISPATCH_METHOD; + + // try as property + if( FAILED( ret) && pdispparams->cArgs == 1) + { + if( FAILED( ret= doSetProperty( pdispparams, pvarResult, + pexcepinfo, puArgErr, info.name, params)) + && ret == DISP_E_MEMBERNOTFOUND) + { + // try to get the exact name + if( !exactName.isEmpty() ) + { + if( SUCCEEDED( ret= doSetProperty( pdispparams, pvarResult, + pexcepinfo, puArgErr, exactName, params))) + info.name= exactName; + } + } + if( SUCCEEDED( ret ) ) + info.flags= DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYGET; + } + } + else if( wFlags & DISPATCH_METHOD && wFlags & DISPATCH_PROPERTYGET) + { + OUString exactName; + convertDispparamsArgs(dispidMember, wFlags, pdispparams, params ); + + if( FAILED( ret= doInvoke( pdispparams, pvarResult, + pexcepinfo, puArgErr, info.name, params)) + && ret == DISP_E_MEMBERNOTFOUND) + { + // try to get the exact name + if (m_xExactName.is()) + { + exactName = m_xExactName->getExactName( info.name); + // invoke again + if( !exactName.isEmpty() ) + { + if( SUCCEEDED( ret= doInvoke( pdispparams, pvarResult, + pexcepinfo, puArgErr, exactName, params))) + info.name= exactName; + } + } + } + if( SUCCEEDED( ret ) ) + info.flags= DISPATCH_METHOD; + + // try as property + if( FAILED( ret) && pdispparams->cArgs == 1) + { + if( FAILED( ret= doGetProperty( pdispparams, pvarResult, + pexcepinfo, info.name)) + && ret == DISP_E_MEMBERNOTFOUND) + { + if( !exactName.isEmpty() ) + { + if( SUCCEEDED( ret= doSetProperty( pdispparams, pvarResult, + pexcepinfo, puArgErr, exactName, params))) + info.name= exactName; + } + } + if( SUCCEEDED( ret ) ) + info.flags= DISPATCH_PROPERTYGET; + } + } + + // update information about this member + if( ret == DISP_E_MEMBERNOTFOUND) + { + // Remember the name as not existing + // and remove the MemberInfo + m_badNameMap[info.name]= false; + m_idToMemberInfoMap.erase( it_MemberInfo); + } + } // if( ! info.flags ) + else // IdToMemberInfoMap contains a MemberInfo + { + if( wFlags & DISPATCH_METHOD && info.flags == DISPATCH_METHOD) + { + convertDispparamsArgs(dispidMember, wFlags, pdispparams, params ); + ret= doInvoke( pdispparams, pvarResult, + pexcepinfo, puArgErr, info.name, params); + } + else if( (wFlags & DISPATCH_PROPERTYPUT || wFlags & DISPATCH_PROPERTYPUTREF ) && + info.flags & DISPATCH_PROPERTYPUT) + { + convertDispparamsArgs(dispidMember, wFlags, pdispparams, params ); + ret= doSetProperty( pdispparams, pvarResult, + pexcepinfo, puArgErr, info.name, params); + } + else if( (wFlags & DISPATCH_PROPERTYGET) && ( info.flags & DISPATCH_PROPERTYGET)) + { + ret= doGetProperty( pdispparams, pvarResult, + pexcepinfo, info.name); + } + else + { + ret= DISP_E_MEMBERNOTFOUND; + } + } + }// if( it_MemberInfo != m_idToMemberInfoMap.end() ) + else + ret= DISP_E_MEMBERNOTFOUND; + } + } + catch(const BridgeRuntimeError& e) + { + writeExcepinfo(pexcepinfo, e.message); + ret = DISP_E_EXCEPTION; + } + catch(const Exception& e) + { + OUString message= "UnoObjectWrapperRemoteOpt::Invoke : \n" + + e.Message; + writeExcepinfo(pexcepinfo, message); + ret = DISP_E_EXCEPTION; + } + catch(...) + { + writeExcepinfo(pexcepinfo, "UnoObjectWrapperRemoteOpt::Invoke : \nUnexpected exception"); + ret = DISP_E_EXCEPTION; + } + + return ret; +} + +HRESULT UnoObjectWrapperRemoteOpt::methodInvoke( DISPID /*dispidMember*/, DISPPARAMS * /*pdispparams*/, VARIANT * /*pvarResult*/, + EXCEPINFO * /*pexcepinfo*/, unsigned int * /*puArgErr*/, Sequence<Any> const &) +{ + return S_OK; +} + +// The returned HRESULT is only appropriate for IDispatch::Invoke +static HRESULT mapCannotConvertException(const CannotConvertException &e, unsigned int * puArgErr) +{ + HRESULT ret; + bool bWriteIndex= true; + + switch ( e.Reason) + { + case FailReason::OUT_OF_RANGE: + ret = DISP_E_OVERFLOW; + break; + case FailReason::IS_NOT_NUMBER: + ret = DISP_E_TYPEMISMATCH; + break; + case FailReason::IS_NOT_ENUM: + ret = DISP_E_TYPEMISMATCH; + break; + case FailReason::IS_NOT_BOOL: + ret = DISP_E_TYPEMISMATCH; + break; + case FailReason::NO_SUCH_INTERFACE: + ret = DISP_E_TYPEMISMATCH; + break; + case FailReason::SOURCE_IS_NO_DERIVED_TYPE: + ret = DISP_E_TYPEMISMATCH; + break; + case FailReason::TYPE_NOT_SUPPORTED: + ret = DISP_E_TYPEMISMATCH; + break; + case FailReason::INVALID: + ret = DISP_E_TYPEMISMATCH; + break; + case FailReason::NO_DEFAULT_AVAILABLE: + ret = DISP_E_BADPARAMCOUNT; + break; + case FailReason::UNKNOWN: + ret = E_UNEXPECTED; + break; + default: + ret = E_UNEXPECTED; + bWriteIndex= false; + break; + } + + if( bWriteIndex && puArgErr != nullptr) + *puArgErr = e.ArgumentIndex; + return ret; +} + +// The function maps the TypeClass of the any to VARTYPE: If +// the Any contains STRUCT or INTERFACE then the return value +// is VT_DISPATCH. The function is used from o2u_createUnoObjectWrapper +// and the result is put into the constructor of the uno - wrapper +// object. If a client asks the object for DISPID_VALUE and this +// function returned VT_DISPATCH then the IDispatch of the same +// object is being returned. +// See InterfaceOleWrapper::Invoke, InterfaceOleWrapper::m_defaultValueType +VARTYPE getVarType( const Any& value) +{ + VARTYPE ret= VT_EMPTY; + + switch ( value.getValueTypeClass()) + { + case TypeClass_STRUCT: ret= VT_DISPATCH; break; + case TypeClass_INTERFACE: ret= VT_DISPATCH; break; + default: break; + } + return ret; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/unoobjw.hxx b/extensions/source/ole/unoobjw.hxx new file mode 100644 index 0000000000..845724d819 --- /dev/null +++ b/extensions/source/ole/unoobjw.hxx @@ -0,0 +1,268 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/bridge/XBridgeSupplier2.hpp> +#include <com/sun/star/beans/XExactName.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/script/InvocationInfo.hpp> +#include <salhelper/simplereferenceobject.hxx> +#include <cppuhelper/implbase.hxx> + +#include "comifaces.hxx" +#include "ole2uno.hxx" +#include "unoconversionutilities.hxx" + +#define JSCRIPT_VALUE_FUNC L"_GetValueObject" +#define GET_STRUCT_FUNC L"_GetStruct" +#define BRIDGE_VALUE_FUNC L"Bridge_GetValueObject" +#define BRIDGE_GET_STRUCT_FUNC L"Bridge_GetStruct" +#define BRIDGE_CREATE_TYPE_FUNC L"Bridge_CreateType" + +#define DISPID_JSCRIPT_VALUE_FUNC -10l +#define DISPID_GET_STRUCT_FUNC -102 +#define DISPID_CREATE_TYPE_FUNC -103 + +using namespace cppu; +using namespace com::sun::star::bridge; +using namespace com::sun::star::script; + +struct MemberInfo +{ + MemberInfo() : flags(0), name() {} + MemberInfo(WORD f, const OUString& n) : flags(f), name(n) {} + + WORD flags; + OUString name; +}; + +typedef std::unordered_map +< + OUString, + DISPID +> NameToIdMap; + +typedef std::unordered_map +< + OUString, + bool +> BadNameMap; + +typedef std::unordered_map +< + DISPID, + MemberInfo +> IdToMemberInfoMap; + +// An InterfaceOleWrapper object can wrap either a UNO struct or a UNO +// interface as a COM IDispatchEx and IUnoObjectWrapper. + +class InterfaceOleWrapper : public WeakImplHelper<XBridgeSupplier2, XInitialization>, + public IDispatchEx, + public IProvideClassInfo, + public IConnectionPointContainer, + public UnoConversionUtilities<InterfaceOleWrapper>, + public IUnoObjectWrapper +{ +public: + InterfaceOleWrapper(Reference<XMultiServiceFactory> const & xFactory, sal_uInt8 unoWrapperClass, sal_uInt8 comWrapperClass); + ~InterfaceOleWrapper() override; + + // IUnknown + STDMETHOD(QueryInterface)(REFIID riid, void ** ppvObj) override; + STDMETHOD_(ULONG, AddRef)() override; + STDMETHOD_(ULONG, Release)() override; + + // IDispatch + STDMETHOD( GetTypeInfoCount )( UINT * pctinfo ) override; + STDMETHOD( GetTypeInfo )( UINT itinfo, LCID lcid, ITypeInfo ** pptinfo ) override; + STDMETHOD( GetIDsOfNames )( REFIID riid, LPOLESTR * rgszNames, UINT cNames, + LCID lcid, DISPID * rgdispid ) override; + STDMETHOD( Invoke )( DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, + DISPPARAMS * pdispparams, VARIANT * pvarResult, EXCEPINFO * pexcepinfo, + UINT * puArgErr ) override; + + // IDispatchEx + virtual HRESULT STDMETHODCALLTYPE GetDispID( + /* [in] */ BSTR bstrName, + /* [in] */ DWORD grfdex, + /* [out] */ DISPID __RPC_FAR *pid) override; + + virtual /* [local] */ HRESULT STDMETHODCALLTYPE InvokeEx( + /* [in] */ DISPID id, + /* [in] */ LCID lcid, + /* [in] */ WORD wFlags, + /* [in] */ DISPPARAMS __RPC_FAR *pdp, + /* [out] */ VARIANT __RPC_FAR *pvarRes, + /* [out] */ EXCEPINFO __RPC_FAR *pei, + /* [unique][in] */ IServiceProvider __RPC_FAR *pspCaller) override; + + virtual HRESULT STDMETHODCALLTYPE DeleteMemberByName( + /* [in] */ BSTR bstr, + /* [in] */ DWORD grfdex) override; + + virtual HRESULT STDMETHODCALLTYPE DeleteMemberByDispID( + /* [in] */ DISPID id) override; + + virtual HRESULT STDMETHODCALLTYPE GetMemberProperties( + /* [in] */ DISPID id, + /* [in] */ DWORD grfdexFetch, + /* [out] */ DWORD __RPC_FAR *pgrfdex) override; + + virtual HRESULT STDMETHODCALLTYPE GetMemberName( + /* [in] */ DISPID id, + /* [out] */ BSTR __RPC_FAR *pbstrName) override; + + virtual HRESULT STDMETHODCALLTYPE GetNextDispID( + /* [in] */ DWORD grfdex, + /* [in] */ DISPID id, + /* [out] */ DISPID __RPC_FAR *pid) override; + + virtual HRESULT STDMETHODCALLTYPE GetNameSpaceParent( + /* [out] */ IUnknown __RPC_FAR *__RPC_FAR *ppunk) override; + + // IProvideClassInfo + virtual HRESULT STDMETHODCALLTYPE GetClassInfo( + /* [out] */ ITypeInfo **ppTI) override; + + // IConnectionPointContainer + virtual HRESULT STDMETHODCALLTYPE EnumConnectionPoints( + /* [out] */ IEnumConnectionPoints **ppEnum) override; + virtual HRESULT STDMETHODCALLTYPE FindConnectionPoint( + /* [in] */ REFIID riid, + /* [out] */ IConnectionPoint **ppCP) override; + + // XBridgeSupplier2 + virtual Any SAL_CALL createBridge(const Any& modelDepObject, + const Sequence<sal_Int8>& ProcessId, + sal_Int16 sourceModelType, + sal_Int16 destModelType) override; + + // XInitialization + virtual void SAL_CALL initialize( const Sequence< Any >& aArguments ) override; + + // IUnoObjectWrapper + STDMETHOD( getWrapperXInterface)( Reference<XInterface>* pXInt) override; + STDMETHOD( getOriginalUnoObject)( Reference<XInterface>* pXInt) override; + STDMETHOD( getOriginalUnoStruct)( Any * pStruct) override; + + // UnoConversionUtility + virtual Reference< XInterface > createUnoWrapperInstance() override; + virtual Reference< XInterface > createComWrapperInstance() override; + + const OUString& getImplementationName() const + { + return m_sImplementationName; + } + +protected: + virtual HRESULT doInvoke( DISPPARAMS * pdispparams, VARIANT * pvarResult, + EXCEPINFO * pexcepinfo, unsigned int * puArgErr, OUString & name, Sequence<Any>& params); + + virtual HRESULT doGetProperty( DISPPARAMS * pdispparams, VARIANT * pvarResult, + EXCEPINFO * pexcepinfo, OUString & name ); + + virtual HRESULT doSetProperty( DISPPARAMS * pdispparams, VARIANT * pvarResult, + EXCEPINFO * pexcepinfo, unsigned int * puArgErr, OUString & name, Sequence<Any> const & params); + + virtual HRESULT InvokeGeneral( DISPID dispidMember, unsigned short wFlags, + DISPPARAMS * pdispparams, VARIANT * pvarResult, EXCEPINFO * pexcepinfo, + unsigned int * puArgErr, bool& bHandled); + + void convertDispparamsArgs( DISPID id, unsigned short wFlags, DISPPARAMS* pdispparams, + Sequence<Any>& rSeq); + + bool getInvocationInfoForCall(DISPID id, InvocationInfo& info); + + Reference<XInvocation> m_xInvocation; + Reference<XExactName> m_xExactName; + Reference<XInterface> m_xOrigin; + NameToIdMap m_nameToDispIdMap; + std::vector<MemberInfo> m_MemberInfos; + // This member is used to determine the default value + // denoted by DISPID_VALUE (0). For proper results in JavaScript + // we have to return the default value when we write an object + // as out parameter. That is, we get a JScript Array as parameter + // and put a wrapped object on index null. The array object tries + // to detect the default value. The wrapped object must then return + // its own IDispatch* otherwise we cannot access it within the script. + // see InterfaceOleWrapper::Invoke + VARTYPE m_defaultValueType; + + // The name of the implementation. Can be empty if unknown. + OUString m_sImplementationName; +}; + +/***************************************************************************** + + UnoObjectWrapperRemoteOpt = Uno Object Wrapper Remote Optimized + + This is the UNO wrapper used in the service com.sun.star.bridge.OleBridgeSupplierVar1. + Key features: + DISPIDs are passed out blindly. That is in GetIDsOfNames is no name checking carried out. + Only if Invoke fails the name is being checked. Moreover Invoke tries to figure out + if a call is made to a property or method if the flags are DISPATCH_METHOD | DISPATCH_PROPERTYPUT. + If something has been found out about a property or member than it is saved + in a MemberInfo structure hold by an IdToMemberInfoMap stl map. + +*****************************************************************************/ +class UnoObjectWrapperRemoteOpt: public InterfaceOleWrapper +{ +public: + UnoObjectWrapperRemoteOpt( Reference<XMultiServiceFactory> const & aFactory, sal_uInt8 unoWrapperClass, sal_uInt8 comWrapperClass); + ~UnoObjectWrapperRemoteOpt() override; + + STDMETHOD( GetIDsOfNames )( REFIID riid, LPOLESTR * rgszNames, UINT cNames, + LCID lcid, DISPID * rgdispid ) override; + STDMETHOD( Invoke )( DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, + DISPPARAMS * pdispparams, VARIANT * pvarResult, EXCEPINFO * pexcepinfo, + UINT * puArgErr ) override; + + // UnoConversionUtility + // If UNO interfaces are converted in methods of this class then + // they are always wrapped with instances of this class + virtual Reference< XInterface > createUnoWrapperInstance() override; + +protected: + + static HRESULT methodInvoke( DISPID dispidMember, DISPPARAMS * pdispparams, VARIANT * pvarResult, + EXCEPINFO * pexcepinfo, unsigned int * puArgErr, Sequence<Any> const & params); + // In GetIDsOfNames are blindly passed out, that is without verifying + // the name. If two names are passed in during different calls to + // GetIDsOfNames and the names differ only in their cases then different + // id's are passed out ( e.g. "doSomethingMethod" or "dosomethingmethod"). + // In Invoke the DISPID is remapped to the name passed to GetIDsOfNames + // and the name is used as parameter for XInvocation::invoke. If invoke + // fails because of a wrong name, then m_xExactName ( XExactName) is used + // to verify the name. The correct name is then inserted to m_MemberInfos + // ( vector<MemberInfo> ). During the next call to Invoke the right name + // is used. . + + + BadNameMap m_badNameMap; + + IdToMemberInfoMap m_idToMemberInfoMap; + + DISPID m_currentId; + + +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/unotypewrapper.cxx b/extensions/source/ole/unotypewrapper.cxx new file mode 100644 index 0000000000..2824e8ef7b --- /dev/null +++ b/extensions/source/ole/unotypewrapper.cxx @@ -0,0 +1,160 @@ +/* -*- 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 "unotypewrapper.hxx" +#include <rtl/ustring.hxx> +#include <osl/diagnose.h> +#include <o3tl/char16_t2wchar_t.hxx> + +bool createUnoTypeWrapper(BSTR sTypeName, VARIANT * pVar) +{ + bool ret = false; + OSL_ASSERT(sTypeName && pVar); + CComObject< UnoTypeWrapper>* pObj; + VariantClear(pVar); + if( SUCCEEDED( CComObject<UnoTypeWrapper>::CreateInstance( &pObj))) + { + pObj->AddRef(); + pVar->vt= VT_DISPATCH; + pVar->pdispVal= CComQIPtr<IDispatch>(pObj->GetUnknown()); + //now set the value, e.i. the name of the type + CComQIPtr<IUnoTypeWrapper> spType(pVar->pdispVal); + OSL_ASSERT(spType); + if (SUCCEEDED(spType->put_Name(sTypeName))) + { + ret = true; + } + } + return ret; +} + + +bool createUnoTypeWrapper(const OUString& sTypeName, VARIANT * pVar) +{ + CComBSTR bstr(o3tl::toW(sTypeName.getStr())); + return createUnoTypeWrapper(bstr, pVar); +} + +UnoTypeWrapper::UnoTypeWrapper() +{ +} + +UnoTypeWrapper::~UnoTypeWrapper() +{ +} + + +// UnoTypeWrapper, IDispatch -------------------------------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP UnoTypeWrapper::GetTypeInfoCount(UINT* /*pctinfo*/) +{ + return E_NOTIMPL; +} + +// UnoTypeWrapper, IDispatch -------------------------------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP UnoTypeWrapper::GetTypeInfo( UINT /*iTInfo*/, + LCID /*lcid*/, + ITypeInfo** /*ppTInfo*/) +{ + return E_NOTIMPL; +} + +// UnoTypeWrapper, IDispatch -------------------------------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP UnoTypeWrapper::GetIDsOfNames( REFIID /*riid*/, + LPOLESTR *rgszNames, + UINT /*cNames*/, + LCID /*lcid*/, + DISPID *rgDispId) +{ + if( !rgDispId) + return E_POINTER; + + HRESULT ret= S_OK; + CComBSTR name(*rgszNames); + name.ToLower(); + + if( name == CComBSTR( L"name") ) + *rgDispId= DISPID_VALUE; + else + ret= DISP_E_UNKNOWNNAME; + + return ret; +} + +// UnoTypeWrapper, IDispatch -------------------------------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP UnoTypeWrapper::Invoke( DISPID dispIdMember, + REFIID /*riid*/, + LCID /*lcid*/, + WORD wFlags, + DISPPARAMS *pDispParams, + VARIANT *pVarResult, + EXCEPINFO* /*pExcepInfo*/, + UINT* /*puArgErr*/) +{ + if (pDispParams == nullptr) + return DISP_E_EXCEPTION; + + if( pDispParams->cNamedArgs) + return DISP_E_NONAMEDARGS; + + + HRESULT ret= S_OK; + switch( dispIdMember) + { + case DISPID_VALUE: // DISPID_VALUE + if (wFlags & DISPATCH_PROPERTYGET) + { + if (pVarResult == nullptr) + { + ret = E_POINTER; + break; + } + get_Name( & pVarResult->bstrVal); + pVarResult->vt = VT_BSTR; + } + break; + default: + ret= DISP_E_MEMBERNOTFOUND; + break; + } + + return ret; +} + +// IUnoTypeWrapper----------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP UnoTypeWrapper::put_Name(BSTR val) +{ + Lock(); + m_sName = val; + Unlock(); + return S_OK; +} + +// (UnoTypeWrapper----------------------- +COM_DECLSPEC_NOTHROW STDMETHODIMP UnoTypeWrapper::get_Name(BSTR *pVal) +{ + Lock(); + if( !pVal) + return E_POINTER; + *pVal = m_sName.Copy(); + Unlock(); + return S_OK; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/unotypewrapper.hxx b/extensions/source/ole/unotypewrapper.hxx new file mode 100644 index 0000000000..a7515448c9 --- /dev/null +++ b/extensions/source/ole/unotypewrapper.hxx @@ -0,0 +1,81 @@ +/* -*- 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 . + */ +#pragma once + +#include "wincrap.hxx" + +#include "comifaces.hxx" + +/* creates a UnoTypWrapper and sets the Name property to the value + specified by sTypeName. + Returns true if the object could be created and initialized. + */ +bool createUnoTypeWrapper(BSTR sTypeName, VARIANT * pVariant); +bool createUnoTypeWrapper(const OUString& sTypeName, VARIANT * pVar); + +class UnoTypeWrapper: + public CComObjectRootEx<CComMultiThreadModel>, + public IUnoTypeWrapper, + public IDispatch +{ +public: + UnoTypeWrapper(); + virtual ~UnoTypeWrapper(); + + BEGIN_COM_MAP(UnoTypeWrapper) + COM_INTERFACE_ENTRY(IDispatch) + COM_INTERFACE_ENTRY(IUnoTypeWrapper) +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winconsistent-missing-override" +#endif + END_COM_MAP() +#if defined __clang__ +#pragma clang diagnostic pop +#endif + + // IDispatch ------------------------------------------- + STDMETHOD( GetTypeInfoCount)(UINT *pctinfo) override; + + STDMETHOD( GetTypeInfo)( UINT iTInfo, + LCID lcid, + ITypeInfo **ppTInfo) override; + + STDMETHOD( GetIDsOfNames)( REFIID riid, + LPOLESTR *rgszNames, + UINT cNames, + LCID lcid, + DISPID *rgDispId) override; + + STDMETHOD( Invoke)( DISPID dispIdMember, + REFIID riid, + LCID lcid, + WORD wFlags, + DISPPARAMS *pDispParams, + VARIANT *pVarResult, + EXCEPINFO *pExcepInfo, + UINT *puArgErr) override; + // IUnoTypeWrapper -------------------------------------- + STDMETHOD(put_Name)(BSTR val) override; + STDMETHOD(get_Name)(BSTR* pVal) override; + + CComBSTR m_sName; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/wincrap.hxx b/extensions/source/ole/wincrap.hxx new file mode 100644 index 0000000000..3c8417cf92 --- /dev/null +++ b/extensions/source/ole/wincrap.hxx @@ -0,0 +1,63 @@ +/* -*- 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 . + */ + +#pragma once + +/* wrap all includes that need to be wrapped by prewin.h/postwin.h here */ + +#define STRICT + +#define _WIN32_DCOM +#if OSL_DEBUG_LEVEL > 0 +//#define _ATL_DEBUG_INTERFACES +#endif + +#include <dispex.h> + +#include <prewin.h> +#include <list> + +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wall" +#pragma clang diagnostic ignored "-Wattributes" +#pragma clang diagnostic ignored "-Wdelete-incomplete" +#pragma clang diagnostic ignored "-Wextra" +#pragma clang diagnostic ignored "-Wint-to-pointer-cast" +#pragma clang diagnostic ignored "-Winvalid-noreturn" +#pragma clang diagnostic ignored "-Wmicrosoft" +#pragma clang diagnostic ignored "-Wnon-pod-varargs" +#endif + +// from oleobjw.hxx +#include <atlbase.h> +// from jscriptclasses.hxx +extern CComModule _Module; +#include <atlcom.h> + +#if defined __clang__ +#pragma clang diagnostic pop +#endif + +// from unoobjw.cxx +#include <olectl.h> + +#include <postwin.h> + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/ole/windata.hxx b/extensions/source/ole/windata.hxx new file mode 100644 index 0000000000..d9c4cb3143 --- /dev/null +++ b/extensions/source/ole/windata.hxx @@ -0,0 +1,195 @@ +/* -*- 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 . + */ +#pragma once + +#include <oleidl.h> + +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wall" +#pragma clang diagnostic ignored "-Wextra" +#pragma clang diagnostic ignored "-Wignored-attributes" +#pragma clang diagnostic ignored "-Wint-to-pointer-cast" +#pragma clang diagnostic ignored "-Winvalid-noreturn" +#pragma clang diagnostic ignored "-Wmicrosoft" +#pragma clang diagnostic ignored "-Wnon-pod-varargs" +#endif + +#include <atlbase.h> + +#if defined __clang__ +#pragma clang diagnostic pop +#endif + +#include <osl/diagnose.h> + +//Wrapper for VARDESC +class VarDesc +{ + VARDESC* operator = (const VarDesc*); + VarDesc(const VarDesc&); +// Construction +public: + CComPtr< ITypeInfo > m_pTypeInfo; + VARDESC* m_pVarDesc; + + explicit VarDesc(ITypeInfo* pTypeInfo) : + m_pTypeInfo(pTypeInfo), + m_pVarDesc(nullptr) + { + OSL_ASSERT(pTypeInfo); + } + ~VarDesc() + { + if (m_pVarDesc != nullptr) + { + m_pTypeInfo->ReleaseVarDesc(m_pVarDesc); + } + } + + VARDESC* operator->() + { + return m_pVarDesc; + } + + VARDESC** operator&() + { + return &m_pVarDesc; + } + + operator VARDESC* () + { + return m_pVarDesc; + } +}; + +//Wrapper for FUNCDESC structure +class FuncDesc +{ + FUNCDESC* operator = (const FuncDesc &); + FuncDesc(const FuncDesc&); + CComPtr<ITypeInfo> m_pTypeInfo; + FUNCDESC * m_pFuncDesc; + +public: + + explicit FuncDesc(ITypeInfo * pTypeInfo) : + m_pTypeInfo(pTypeInfo), + m_pFuncDesc(nullptr) + { + OSL_ASSERT(pTypeInfo); + } + ~FuncDesc() + { + ReleaseFUNCDESC(); + } + + FUNCDESC* operator -> () + { + return m_pFuncDesc; + } + + FUNCDESC** operator & () + { + return & m_pFuncDesc; + } + + operator FUNCDESC* () + { + return m_pFuncDesc; + } + + FUNCDESC* operator = (FUNCDESC* pDesc) + { + ReleaseFUNCDESC(); + m_pFuncDesc = pDesc; + return m_pFuncDesc; + } + FUNCDESC* Detach() + { + FUNCDESC* pDesc = m_pFuncDesc; + m_pFuncDesc = nullptr; + return pDesc; + } + + void ReleaseFUNCDESC() + { + if (m_pFuncDesc != nullptr) + { + m_pTypeInfo->ReleaseFuncDesc(m_pFuncDesc); + } + m_pFuncDesc = nullptr; + } +}; +//Wrapper for EXCEPINFO structure +class ExcepInfo : public EXCEPINFO +{ + EXCEPINFO* operator = (const ExcepInfo& ); + ExcepInfo(const ExcepInfo &); +public: + ExcepInfo() + : EXCEPINFO{} + { + } + ~ExcepInfo() + { + if (bstrSource != nullptr) + ::SysFreeString(bstrSource); + if (bstrDescription != nullptr) + ::SysFreeString(bstrDescription); + if (bstrHelpFile != nullptr) + ::SysFreeString(bstrHelpFile); + } +}; + +//Wrapper for TYPEATTR +class TypeAttr +{ + TYPEATTR* operator = (const TypeAttr &); + TypeAttr(const TypeAttr &); +public: + CComPtr< ITypeInfo > m_pTypeInfo; + TYPEATTR* m_pTypeAttr; + + explicit TypeAttr(ITypeInfo* pTypeInfo) : + m_pTypeInfo( pTypeInfo ), + m_pTypeAttr( nullptr ) + { + OSL_ASSERT(pTypeInfo); + } + ~TypeAttr() noexcept + { + if (m_pTypeAttr != nullptr) + { + m_pTypeInfo->ReleaseTypeAttr(m_pTypeAttr); + } + } + + TYPEATTR** operator&() noexcept + { + return &m_pTypeAttr; + } + + TYPEATTR* operator->() noexcept + { + return m_pTypeAttr; + } +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/MasterDetailLinkDialog.cxx b/extensions/source/propctrlr/MasterDetailLinkDialog.cxx new file mode 100644 index 0000000000..5f38856fc9 --- /dev/null +++ b/extensions/source/propctrlr/MasterDetailLinkDialog.cxx @@ -0,0 +1,133 @@ +/* -*- 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/log.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <vcl/svapp.hxx> +#include "MasterDetailLinkDialog.hxx" +#include "formlinkdialog.hxx" + +namespace pcr +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + + MasterDetailLinkDialog::MasterDetailLinkDialog(const Reference< XComponentContext >& _rxContext ) + :OGenericUnoDialog( _rxContext ) + { + } + + Sequence<sal_Int8> SAL_CALL MasterDetailLinkDialog::getImplementationId( ) + { + return css::uno::Sequence<sal_Int8>(); + } + + + OUString SAL_CALL MasterDetailLinkDialog::getImplementationName() + { + return "org.openoffice.comp.form.ui.MasterDetailLinkDialog"; + } + + + css::uno::Sequence<OUString> SAL_CALL MasterDetailLinkDialog::getSupportedServiceNames() + { + return { "com.sun.star.form.MasterDetailLinkDialog" }; + } + + + Reference<XPropertySetInfo> SAL_CALL MasterDetailLinkDialog::getPropertySetInfo() + { + Reference<XPropertySetInfo> xInfo( createPropertySetInfo( getInfoHelper() ) ); + return xInfo; + } + + + ::cppu::IPropertyArrayHelper& MasterDetailLinkDialog::getInfoHelper() + { + return *getArrayHelper(); + } + + + ::cppu::IPropertyArrayHelper* MasterDetailLinkDialog::createArrayHelper( ) const + { + Sequence< Property > aProps; + describeProperties(aProps); + return new ::cppu::OPropertyArrayHelper(aProps); + } + + std::unique_ptr<weld::DialogController> MasterDetailLinkDialog::createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) + { + return std::make_unique<FormLinkDialog>(Application::GetFrameWeld(rParent), m_xDetail, + m_xMaster, m_aContext, m_sExplanation, + m_sDetailLabel, m_sMasterLabel); + } + + void MasterDetailLinkDialog::implInitialize(const Any& _rValue) + { + PropertyValue aProperty; + if (_rValue >>= aProperty) + { + if (aProperty.Name == "Detail") + { + if ( ! (aProperty.Value >>= m_xDetail) ) + SAL_WARN("extensions.propctrlr", "implInitialize: unable to get property Detail"); + return; + } + else if (aProperty.Name == "Master") + { + if ( ! (aProperty.Value >>= m_xMaster) ) + SAL_WARN("extensions.propctrlr", "implInitialize: unable to get property Master"); + return; + } + else if (aProperty.Name == "Explanation") + { + if ( ! (aProperty.Value >>= m_sExplanation) ) + SAL_WARN("extensions.propctrlr", "implInitialize: unable to get property Explanation"); + return; + } + else if (aProperty.Name == "DetailLabel") + { + if ( ! (aProperty.Value >>= m_sDetailLabel) ) + SAL_WARN("extensions.propctrlr", "implInitialize: unable to get property DetailLabel"); + return; + } + else if (aProperty.Name == "MasterLabel") + { + if ( ! (aProperty.Value >>= m_sMasterLabel) ) + SAL_WARN("extensions.propctrlr", "implInitialize: unable to get property MasterLabel"); + return; + } + } + MasterDetailLinkDialog_DBase::implInitialize(_rValue); + } + + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_MasterDetailLinkDialog_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::MasterDetailLinkDialog(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/MasterDetailLinkDialog.hxx b/extensions/source/propctrlr/MasterDetailLinkDialog.hxx new file mode 100644 index 0000000000..97911436a1 --- /dev/null +++ b/extensions/source/propctrlr/MasterDetailLinkDialog.hxx @@ -0,0 +1,67 @@ +/* -*- 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 . + */ +#pragma once + +#include <comphelper/proparrhlp.hxx> +#include <svtools/genericunodialog.hxx> + +namespace pcr +{ + + + class MasterDetailLinkDialog; + typedef ::svt::OGenericUnoDialog MasterDetailLinkDialog_DBase; + typedef ::comphelper::OPropertyArrayUsageHelper< MasterDetailLinkDialog > MasterDetailLinkDialog_PBase; + + class MasterDetailLinkDialog : public MasterDetailLinkDialog_DBase + ,public MasterDetailLinkDialog_PBase + { + public: + explicit MasterDetailLinkDialog(const css::uno::Reference< css::uno::XComponentContext >& _rxContext); + + private: + // XTypeProvider + virtual css::uno::Sequence<sal_Int8> SAL_CALL getImplementationId( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo> SAL_CALL getPropertySetInfo() override; + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; + + // OPropertyArrayUsageHelper + virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const override; + + // OGenericUnoDialog overridables + virtual std::unique_ptr<weld::DialogController> createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) override; + virtual void implInitialize(const css::uno::Any& _rValue) override; + + css::uno::Reference< css::beans::XPropertySet> m_xDetail; + css::uno::Reference< css::beans::XPropertySet> m_xMaster; + OUString m_sExplanation; + OUString m_sDetailLabel; + OUString m_sMasterLabel; + }; + + +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/browserline.cxx b/extensions/source/propctrlr/browserline.cxx new file mode 100644 index 0000000000..f4619000dc --- /dev/null +++ b/extensions/source/propctrlr/browserline.cxx @@ -0,0 +1,404 @@ +/* -*- 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 "browserline.hxx" + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/inspection/PropertyLineElement.hpp> +#include <com/sun/star/graphic/GraphicProvider.hpp> +#include <com/sun/star/graphic/XGraphicProvider.hpp> + +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/string.hxx> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <utility> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/weldutils.hxx> + +namespace pcr +{ + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::inspection::XPropertyControl; + using ::com::sun::star::inspection::XPropertyControlContext; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::graphic::GraphicProvider; + using ::com::sun::star::graphic::XGraphicProvider; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::graphic::XGraphic; + + namespace PropertyLineElement = ::com::sun::star::inspection::PropertyLineElement; + + OBrowserLine::OBrowserLine(OUString aEntryName, weld::Container* pParent, weld::SizeGroup* pLabelGroup, + weld::Container* pInitialControlParent) + : m_sEntryName(std::move(aEntryName)) + , m_xBuilder(Application::CreateBuilder(pParent, "modules/spropctrlr/ui/browserline.ui")) + , m_xContainer(m_xBuilder->weld_container("BrowserLine")) + , m_xFtTitle(m_xBuilder->weld_label("label")) + , m_xBrowseButton(m_xBuilder->weld_button("browse")) + , m_xAdditionalBrowseButton(m_xBuilder->weld_button("morebrowse")) + , m_pInitialControlParent(pInitialControlParent) // controls start with this as their parent and need to be moved into m_xContainer + , m_pParent(pParent) + , m_pControlWindow( nullptr ) + , m_pBrowseButton(nullptr) + , m_pAdditionalBrowseButton( nullptr ) + , m_pClickListener( nullptr ) + , m_nNameWidth(0) + , m_nEnableFlags( 0xFFFF ) + , m_bIndentTitle( false ) + , m_bReadOnly( false ) + { + pLabelGroup->add_widget(m_xFtTitle.get()); + } + + OBrowserLine::~OBrowserLine() + { + implHideBrowseButton(true); + implHideBrowseButton(false); + m_pParent->move(m_xContainer.get(), nullptr); + } + + void OBrowserLine::IndentTitle( bool _bIndent ) + { + if ( m_bIndentTitle != _bIndent ) + { + m_bIndentTitle = _bIndent; + } + } + + void OBrowserLine::SetComponentHelpIds(const OUString& rHelpId) + { + if (m_pControlWindow) + m_pControlWindow->set_help_id(rHelpId); + + if ( m_pBrowseButton ) + { + m_pBrowseButton->set_help_id(rHelpId); + + if ( m_pAdditionalBrowseButton ) + { + m_pAdditionalBrowseButton->set_help_id(rHelpId); + } + } + } + + void OBrowserLine::setControl( const Reference< XPropertyControl >& rxControl ) + { + m_xControl = rxControl; + auto xWindow = m_xControl->getControlWindow(); + if (weld::TransportAsXWindow* pTunnel = dynamic_cast<weld::TransportAsXWindow*>(xWindow.get())) + m_pControlWindow = pTunnel->getWidget(); + else + m_pControlWindow = nullptr; + DBG_ASSERT( m_pControlWindow, "OBrowserLine::setControl: setting NULL controls/windows is not allowed!" ); + + if ( m_pControlWindow ) + { + m_pInitialControlParent->move(m_pControlWindow, m_xContainer.get()); + m_pControlWindow->set_grid_left_attach(1); + m_xFtTitle->set_mnemonic_widget(m_pControlWindow); + m_pControlWindow->show(); + } + } + + bool OBrowserLine::GrabFocus() + { + bool bRes=false; + + if (m_pControlWindow && m_pControlWindow->get_sensitive()) + { + m_pControlWindow->grab_focus(); + bRes = true; + } + else if ( m_pAdditionalBrowseButton && m_pAdditionalBrowseButton->get_sensitive() ) + { + m_pAdditionalBrowseButton->grab_focus(); + bRes = true; + } + else if ( m_pBrowseButton && m_pBrowseButton->get_sensitive() ) + { + m_pBrowseButton->grab_focus(); + bRes = true; + } + return bRes; + } + + void OBrowserLine::Show(bool bFlag) + { + m_xFtTitle->set_visible(bFlag); + if (m_pControlWindow) + m_pControlWindow->set_visible( bFlag ); + if ( m_pBrowseButton ) + m_pBrowseButton->set_visible( bFlag ); + if ( m_pAdditionalBrowseButton ) + m_pAdditionalBrowseButton->set_visible( bFlag ); + } + + void OBrowserLine::Hide() + { + Show(false); + } + + void OBrowserLine::SetTitle(const OUString& rNewTitle ) + { + if ( GetTitle() == rNewTitle ) + return; + m_xFtTitle->set_label( rNewTitle ); + if (m_pControlWindow) + m_pControlWindow->set_accessible_name(rNewTitle); + if ( m_pBrowseButton ) + m_pBrowseButton->set_accessible_name( rNewTitle ); + FullFillTitleString(); + } + + void OBrowserLine::FullFillTitleString() + { + OUStringBuffer aText(m_xFtTitle->get_label()); + + int n10DotsWidth = m_xFtTitle->get_pixel_size("..........").Width(); + int nTextWidth = m_xFtTitle->get_pixel_size(OUString::unacquired(aText)).Width(); + int nDiff = m_nNameWidth - nTextWidth; + int nExtraChars = (nDiff * 10) / n10DotsWidth; + for (int i = 0; i < nExtraChars; ++i) + aText.append("."); + + // for Issue 69452 + if (AllSettings::GetLayoutRTL()) + { + sal_Unicode const cRTL_mark = 0x200F; + aText.append( cRTL_mark ); + } + + m_xFtTitle->set_label(aText.makeStringAndClear()); + } + + OUString OBrowserLine::GetTitle() const + { + OUString sDisplayName = m_xFtTitle->get_label(); + + // for Issue 69452 + if (AllSettings::GetLayoutRTL()) + { + sal_Unicode const cRTL_mark = 0x200F; + sDisplayName = comphelper::string::stripEnd(sDisplayName, cRTL_mark); + } + + sDisplayName = comphelper::string::stripEnd(sDisplayName, '.'); + + return sDisplayName; + } + + void OBrowserLine::SetReadOnly( bool _bReadOnly ) + { + if ( m_bReadOnly != _bReadOnly ) + { + m_bReadOnly = _bReadOnly; + implUpdateEnabledDisabled(); + } + } + + namespace + { + void implSetBitIfAffected(sal_uInt16& nEnabledBits, sal_Int16 _nAffectedMask, sal_Int16 _nTestBit, bool _bSet) + { + if ( _nAffectedMask & _nTestBit ) + { + if ( _bSet ) + nEnabledBits |= _nTestBit; + else + nEnabledBits &= ~_nTestBit; + } + } + + void implEnable(weld::Widget* pWindow, bool bEnable) + { + // tdf#138131 get_sensitive comparison as bodge for + // vcl's recursive Enable behavior + if (pWindow && pWindow->get_sensitive() != bEnable) + pWindow->set_sensitive(bEnable); + } + + void implEnable(weld::Widget* pWindow, sal_uInt16 nEnabledBits, sal_uInt16 nMatchBits) + { + bool bEnable = ((nEnabledBits & nMatchBits) == nMatchBits); + implEnable(pWindow, bEnable); + } + } + + void OBrowserLine::implUpdateEnabledDisabled() + { + implEnable( m_xFtTitle.get(), m_nEnableFlags, PropertyLineElement::CompleteLine ); + if ( m_pControlWindow ) + implEnable( m_pControlWindow, m_nEnableFlags, PropertyLineElement::CompleteLine | PropertyLineElement::InputControl ); + + if ( m_bReadOnly ) + { + implEnable( m_pBrowseButton, false ); + implEnable( m_pAdditionalBrowseButton, false ); + } + else + { + implEnable( m_pBrowseButton, m_nEnableFlags, PropertyLineElement::CompleteLine | PropertyLineElement::PrimaryButton ); + implEnable( m_pAdditionalBrowseButton, m_nEnableFlags, PropertyLineElement::CompleteLine | PropertyLineElement::SecondaryButton ); + } + } + + void OBrowserLine::EnablePropertyLine( bool _bEnable ) + { + implSetBitIfAffected( m_nEnableFlags, PropertyLineElement::CompleteLine, PropertyLineElement::CompleteLine, _bEnable ); + implUpdateEnabledDisabled(); + } + + + void OBrowserLine::EnablePropertyControls( sal_Int16 _nControls, bool _bEnable ) + { + implSetBitIfAffected( m_nEnableFlags, _nControls, PropertyLineElement::InputControl, _bEnable ); + implSetBitIfAffected( m_nEnableFlags, _nControls, PropertyLineElement::PrimaryButton, _bEnable ); + implSetBitIfAffected( m_nEnableFlags, _nControls, PropertyLineElement::SecondaryButton, _bEnable ); + implUpdateEnabledDisabled(); + } + + weld::Button& OBrowserLine::impl_ensureButton(bool bPrimary) + { + weld::Button* pButton; + if (bPrimary) + pButton = m_pBrowseButton; + else + pButton = m_pAdditionalBrowseButton; + + if (!pButton ) + { + if (bPrimary) + pButton = m_pBrowseButton = m_xBrowseButton.get(); + else + pButton = m_pAdditionalBrowseButton = m_xAdditionalBrowseButton.get(); + pButton->connect_focus_in(LINK(this, OBrowserLine, OnButtonFocus)); + pButton->connect_clicked(LINK(this, OBrowserLine, OnButtonClicked)); + } + + pButton->show(); + + return *pButton; + } + + void OBrowserLine::ShowBrowseButton( const OUString& rImageURL, bool bPrimary ) + { + weld::Button& rButton( impl_ensureButton( bPrimary ) ); + + OSL_PRECOND( !rImageURL.isEmpty(), "OBrowserLine::ShowBrowseButton: use the other version if you don't have an image!" ); + Reference<XGraphic> xGraphic; + try + { + Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + Reference< XGraphicProvider > xGraphicProvider( GraphicProvider::create(xContext) ); + + Sequence aMediaProperties{ comphelper::makePropertyValue("URL", rImageURL) }; + + xGraphic = Reference<XGraphic>(xGraphicProvider->queryGraphic(aMediaProperties), css::uno::UNO_SET_THROW); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + + rButton.set_image(xGraphic); + } + + void OBrowserLine::ShowBrowseButton(const css::uno::Reference<css::graphic::XGraphic>& rGraphic, bool bPrimary) + { + weld::Button& rButton( impl_ensureButton( bPrimary ) ); + rButton.set_image(rGraphic); + } + + void OBrowserLine::ShowBrowseButton( bool bPrimary ) + { + impl_ensureButton(bPrimary); + } + + void OBrowserLine::implHideBrowseButton(bool bPrimary) + { + if (bPrimary) + { + if (m_pBrowseButton) + { + m_pBrowseButton->hide(); + m_pBrowseButton->connect_focus_in(Link<weld::Widget&, void>()); + m_pBrowseButton = nullptr; + } + } + else + { + if (m_pAdditionalBrowseButton) + { + m_pAdditionalBrowseButton->hide(); + m_pAdditionalBrowseButton->connect_focus_in(Link<weld::Widget&, void>()); + m_pAdditionalBrowseButton = nullptr; + } + } + } + + void OBrowserLine::HideBrowseButton(bool bPrimary) + { + implHideBrowseButton(bPrimary); + } + + void OBrowserLine::SetTitleWidth(sal_uInt16 nWidth) + { + int nMinDotsWidth = m_xFtTitle->get_pixel_size("...").Width(); + if (m_nNameWidth != nWidth + nMinDotsWidth) + m_nNameWidth = nWidth + nMinDotsWidth; + FullFillTitleString(); + } + + void OBrowserLine::SetClickListener( IButtonClickListener* _pListener ) + { + m_pClickListener = _pListener; + } + + IMPL_LINK(OBrowserLine, OnButtonClicked, weld::Button&, rButton, void) + { + if ( m_pClickListener ) + m_pClickListener->buttonClicked(this, &rButton == m_pBrowseButton); + } + + IMPL_LINK_NOARG( OBrowserLine, OnButtonFocus, weld::Widget&, void ) + { + if ( m_xControl.is() ) + { + try + { + Reference< XPropertyControlContext > xContext( m_xControl->getControlContext(), css::uno::UNO_SET_THROW ); + xContext->focusGained( m_xControl ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + } + +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/browserline.hxx b/extensions/source/propctrlr/browserline.hxx new file mode 100644 index 0000000000..dfde2969f4 --- /dev/null +++ b/extensions/source/propctrlr/browserline.hxx @@ -0,0 +1,127 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/inspection/XPropertyControl.hpp> +#include <vcl/weld.hxx> + +namespace com::sun::star::inspection::PropertyLineElement +{ + const sal_Int16 CompleteLine = 0x4000; +} + + +namespace pcr +{ + + + class OBrowserLine; + + + class IButtonClickListener + { + public: + virtual void buttonClicked( OBrowserLine* pLine, bool bPrimary ) = 0; + + protected: + ~IButtonClickListener() {} + }; + + + class OBrowserLine + { + private: + OUString m_sEntryName; + std::unique_ptr<weld::Builder> m_xBuilder; + std::unique_ptr<weld::Container> m_xContainer; + std::unique_ptr<weld::Label> m_xFtTitle; + std::unique_ptr<weld::Button> m_xBrowseButton; + std::unique_ptr<weld::Button> m_xAdditionalBrowseButton; + css::uno::Reference< css::inspection::XPropertyControl > + m_xControl; + weld::Container* m_pInitialControlParent; + weld::Container* m_pParent; + weld::Widget* m_pControlWindow; + weld::Button* m_pBrowseButton; + weld::Button* m_pAdditionalBrowseButton; + IButtonClickListener* m_pClickListener; + sal_uInt16 m_nNameWidth; + sal_uInt16 m_nEnableFlags; + bool m_bIndentTitle; + bool m_bReadOnly; + + public: + OBrowserLine(OUString aEntryName, weld::Container* pParent, weld::SizeGroup* pLabelGroup, + weld::Container* pInitialControlParent); + ~OBrowserLine(); + + void setControl( const css::uno::Reference< css::inspection::XPropertyControl >& rxControl ); + const css::uno::Reference< css::inspection::XPropertyControl >& getControl() const + { + return m_xControl; + } + weld::Widget* getControlWindow() const + { + return m_pControlWindow; + } + + const OUString& GetEntryName() const { return m_sEntryName; } + + void SetComponentHelpIds(const OUString& rHelpId); + + void SetTitle(const OUString& rString ); + void FullFillTitleString(); + OUString GetTitle() const; + void SetTitleWidth(sal_uInt16); + + int GetRowHeight() const { return m_xContainer->get_preferred_size().Height(); } + void Show(bool bFlag=true); + void Hide(); + + bool GrabFocus(); + void ShowBrowseButton( const OUString& rImageURL, bool bPrimary ); + void ShowBrowseButton( const css::uno::Reference<css::graphic::XGraphic>& rGraphic, bool bPrimary ); + void ShowBrowseButton( bool bPrimary ); + void HideBrowseButton( bool bPrimary ); + + void EnablePropertyControls( sal_Int16 nControls, bool bEnable ); + void EnablePropertyLine( bool bEnable ); + + void SetReadOnly( bool bReadOnly ); + + void SetClickListener( IButtonClickListener* pListener ); + + void IndentTitle( bool bIndent ); + + private: + DECL_LINK( OnButtonClicked, weld::Button&, void ); + DECL_LINK( OnButtonFocus, weld::Widget&, void ); + + void implHideBrowseButton(bool bPrimary); + void implUpdateEnabledDisabled(); + + weld::Button& impl_ensureButton(bool bPrimary); + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/browserlistbox.cxx b/extensions/source/propctrlr/browserlistbox.cxx new file mode 100644 index 0000000000..b48fc7fa22 --- /dev/null +++ b/extensions/source/propctrlr/browserlistbox.cxx @@ -0,0 +1,817 @@ +/* -*- 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 "browserlistbox.hxx" +#include "pcrcommon.hxx" +#include "proplinelistener.hxx" +#include "propcontrolobserver.hxx" +#include "linedescriptor.hxx" +#include "inspectorhelpwindow.hxx" + +#include <sal/log.hxx> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/inspection/PropertyControlType.hpp> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <comphelper/asyncnotification.hxx> +#include <cppuhelper/implbase.hxx> +#include <vcl/svapp.hxx> +#include <osl/mutex.hxx> + + +namespace pcr +{ + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::inspection::XPropertyControlContext; + using ::com::sun::star::uno::Reference; + using ::com::sun::star::inspection::XPropertyControl; + using ::com::sun::star::lang::DisposedException; + using ::com::sun::star::lang::XComponent; + using ::com::sun::star::uno::UNO_QUERY; + + namespace PropertyControlType = ::com::sun::star::inspection::PropertyControlType; + + namespace { + + enum ControlEventType + { + FOCUS_GAINED, + VALUE_CHANGED, + ACTIVATE_NEXT + }; + + struct ControlEvent : public ::comphelper::AnyEvent + { + Reference< XPropertyControl > xControl; + ControlEventType eType; + + ControlEvent( const Reference< XPropertyControl >& _rxControl, ControlEventType _eType ) + :xControl( _rxControl ) + ,eType( _eType ) + { + } + }; + + class SharedNotifier + { + private: + static ::osl::Mutex& getMutex(); + static ::rtl::Reference< ::comphelper::AsyncEventNotifier > s_pNotifier; + + public: + SharedNotifier(const SharedNotifier&) = delete; + SharedNotifier& operator=(const SharedNotifier&) = delete; + static const ::rtl::Reference< ::comphelper::AsyncEventNotifier >& + getNotifier(); + }; + + } + + ::rtl::Reference< ::comphelper::AsyncEventNotifier > SharedNotifier::s_pNotifier; + + + ::osl::Mutex& SharedNotifier::getMutex() + { + static ::osl::Mutex s_aMutex; + return s_aMutex; + } + + + const ::rtl::Reference< ::comphelper::AsyncEventNotifier >& SharedNotifier::getNotifier() + { + ::osl::MutexGuard aGuard( getMutex() ); + if ( !s_pNotifier.is() ) + { + s_pNotifier.set( + new ::comphelper::AsyncEventNotifier("browserlistbox")); + s_pNotifier->launch(); + //TODO: a protocol is missing how to join with the launched + // thread before exit(3), to ensure the thread is no longer + // relying on any infrastructure while that infrastructure is + // being shut down in atexit handlers + } + return s_pNotifier; + } + + + /** implementation for of <type scope="css::inspection">XPropertyControlContext</type> + which forwards all events to a non-UNO version of this interface + */ + typedef ::cppu::WeakImplHelper< XPropertyControlContext > PropertyControlContext_Impl_Base; + class PropertyControlContext_Impl :public PropertyControlContext_Impl_Base + ,public ::comphelper::IEventProcessor + { + public: + enum NotificationMode + { + eSynchronously, + eAsynchronously + }; + + private: + OBrowserListBox* m_pContext; + NotificationMode m_eMode; + + public: + /** creates an instance + @param _rContextImpl + the instance to delegate events to + */ + explicit PropertyControlContext_Impl( OBrowserListBox& _rContextImpl ); + + /** disposes the context. + + When you call this method, all subsequent callbacks to the + <type scope="css::inspection">XPropertyControlContext</type> methods + will throw a <type scope="css::lang">DisposedException</type>. + */ + void dispose(); + + /** sets the notification mode, so that notifications received from the controls are + forwarded to our OBrowserListBox either synchronously or asynchronously + @param _eMode + the new notification mode + */ + void setNotificationMode( NotificationMode _eMode ); + + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + + protected: + virtual ~PropertyControlContext_Impl() override; + + // XPropertyControlObserver + virtual void SAL_CALL focusGained( const Reference< XPropertyControl >& Control ) override; + virtual void SAL_CALL valueChanged( const Reference< XPropertyControl >& Control ) override; + // XPropertyControlContext + virtual void SAL_CALL activateNextControl( const Reference< XPropertyControl >& CurrentControl ) override; + + // IEventProcessor + virtual void processEvent( const ::comphelper::AnyEvent& _rEvent ) override; + + private: + /** processes the given event, i.e. notifies it to our OBrowserListBox + @param _rEvent + the event no notify + @precond + our mutex (well, the SolarMutex) is locked + */ + void impl_processEvent_throw( const ::comphelper::AnyEvent& _rEvent ); + + /** checks whether the instance is already disposed + */ + bool impl_isDisposed_nothrow() const { return m_pContext == nullptr; } + + /** notifies the given event originating from the given control + @throws DisposedException + @param _rxControl + @param _eType + */ + void impl_notify_throw( const Reference< XPropertyControl >& _rxControl, ControlEventType _eType ); + }; + + PropertyControlContext_Impl::PropertyControlContext_Impl( OBrowserListBox& _rContextImpl ) + : m_pContext( &_rContextImpl ) + , m_eMode( eAsynchronously ) + { + } + + PropertyControlContext_Impl::~PropertyControlContext_Impl() + { + if ( !impl_isDisposed_nothrow() ) + dispose(); + } + + void PropertyControlContext_Impl::dispose() + { + SolarMutexGuard aGuard; + if ( impl_isDisposed_nothrow() ) + return; + + SharedNotifier::getNotifier()->removeEventsForProcessor( this ); + m_pContext = nullptr; + } + + void PropertyControlContext_Impl::setNotificationMode( NotificationMode _eMode ) + { + SolarMutexGuard aGuard; + m_eMode = _eMode; + } + + void PropertyControlContext_Impl::impl_notify_throw( const Reference< XPropertyControl >& _rxControl, ControlEventType _eType ) + { + ::comphelper::AnyEventRef pEvent; + + { + SolarMutexGuard aGuard; + if ( impl_isDisposed_nothrow() ) + throw DisposedException( OUString(), *this ); + pEvent = new ControlEvent( _rxControl, _eType ); + + if ( m_eMode == eSynchronously ) + { + impl_processEvent_throw( *pEvent ); + return; + } + } + + SharedNotifier::getNotifier()->addEvent( pEvent, this ); + } + + void SAL_CALL PropertyControlContext_Impl::focusGained( const Reference< XPropertyControl >& Control ) + { + impl_notify_throw( Control, FOCUS_GAINED ); + } + + void SAL_CALL PropertyControlContext_Impl::valueChanged( const Reference< XPropertyControl >& Control ) + { + impl_notify_throw( Control, VALUE_CHANGED ); + } + + void SAL_CALL PropertyControlContext_Impl::activateNextControl( const Reference< XPropertyControl >& CurrentControl ) + { + impl_notify_throw( CurrentControl, ACTIVATE_NEXT ); + } + + void SAL_CALL PropertyControlContext_Impl::acquire() noexcept + { + PropertyControlContext_Impl_Base::acquire(); + } + + void SAL_CALL PropertyControlContext_Impl::release() noexcept + { + PropertyControlContext_Impl_Base::release(); + } + + void PropertyControlContext_Impl::processEvent( const ::comphelper::AnyEvent& _rEvent ) + { + SolarMutexGuard aGuard; + if ( impl_isDisposed_nothrow() ) + return; + + try + { + impl_processEvent_throw( _rEvent ); + } + catch( const Exception& ) + { + // can't handle otherwise, since our caller (the notification thread) does not allow + // for exceptions (it could itself abort only) + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + void PropertyControlContext_Impl::impl_processEvent_throw( const ::comphelper::AnyEvent& _rEvent ) + { + const ControlEvent& rControlEvent = static_cast< const ControlEvent& >( _rEvent ); + switch ( rControlEvent.eType ) + { + case FOCUS_GAINED: + m_pContext->focusGained( rControlEvent.xControl ); + break; + case VALUE_CHANGED: + m_pContext->valueChanged( rControlEvent.xControl ); + break; + case ACTIVATE_NEXT: + m_pContext->activateNextControl( rControlEvent.xControl ); + break; + } + } + + OBrowserListBox::OBrowserListBox(weld::Builder& rBuilder, weld::Container* pContainer) + : m_xScrolledWindow(rBuilder.weld_scrolled_window("scrolledwindow")) + , m_xLinesPlayground(rBuilder.weld_container("playground")) + , m_xSizeGroup(rBuilder.create_size_group()) + , m_xHelpWindow(new InspectorHelpWindow(rBuilder)) + , m_pInitialControlParent(pContainer) + , m_pLineListener(nullptr) + , m_pControlObserver( nullptr ) + , m_nTheNameSize(0) + , m_nRowHeight(0) + , m_pControlContextImpl( new PropertyControlContext_Impl( *this ) ) + { + m_xScrolledWindow->set_size_request(-1, m_xScrolledWindow->get_text_height() * 20); + } + + OBrowserListBox::~OBrowserListBox() + { + OSL_ENSURE( !IsModified(), "OBrowserListBox::~OBrowserListBox: still modified - should have been committed before!" ); + + // doing the commit here, while we, as well as our owner, as well as some other components, + // are already "half dead" (means within their dtor) is potentially dangerous. + // By definition, CommitModified has to be called (if necessary) before destruction + m_pControlContextImpl->dispose(); + m_pControlContextImpl.clear(); + + Clear(); + } + + bool OBrowserListBox::IsModified() const + { + bool bModified = false; + + if (m_xScrolledWindow->get_visible() && m_xActiveControl.is()) + bModified = m_xActiveControl->isModified(); + + return bModified; + } + + void OBrowserListBox::CommitModified( ) + { + if ( !(IsModified() && m_xActiveControl.is()) ) + return; + + // for the time of this commit, notify all events synchronously + // #i63814# + m_pControlContextImpl->setNotificationMode( PropertyControlContext_Impl::eSynchronously ); + try + { + m_xActiveControl->notifyModifiedValue(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + m_pControlContextImpl->setNotificationMode( PropertyControlContext_Impl::eAsynchronously ); + } + + void OBrowserListBox::SetListener( IPropertyLineListener* _pListener ) + { + m_pLineListener = _pListener; + } + + void OBrowserListBox::SetObserver( IPropertyControlObserver* _pObserver ) + { + m_pControlObserver = _pObserver; + } + + void OBrowserListBox::EnableHelpSection( bool _bEnable ) + { + m_xHelpWindow->Show( _bEnable ); + } + + bool OBrowserListBox::HasHelpSection() const + { + return m_xHelpWindow->IsVisible(); + } + + void OBrowserListBox::SetHelpText( const OUString& _rHelpText ) + { + OSL_ENSURE( HasHelpSection(), "OBrowserListBox::SetHelpText: help section not visible!" ); + m_xHelpWindow->SetText( _rHelpText ); + } + + void OBrowserListBox::UpdatePlayGround() + { + for (auto& line : m_aLines) + line.pLine->SetTitleWidth(m_nTheNameSize); + } + + void OBrowserListBox::SetPropertyValue(const OUString& _rEntryName, const Any& _rValue, bool _bUnknownValue ) + { + ListBoxLines::iterator line = std::find_if(m_aLines.begin(), m_aLines.end(), + [&_rEntryName](const ListBoxLine& rLine) { return rLine.aName == _rEntryName; }); + + if ( line != m_aLines.end() ) + { + if ( _bUnknownValue ) + { + Reference< XPropertyControl > xControl( line->pLine->getControl() ); + OSL_ENSURE( xControl.is(), "OBrowserListBox::SetPropertyValue: illegal control!" ); + if ( xControl.is() ) + xControl->setValue( Any() ); + } + else + impl_setControlAsPropertyValue( *line, _rValue ); + } + } + + sal_uInt16 OBrowserListBox::GetPropertyPos( std::u16string_view _rEntryName ) const + { + sal_uInt16 nPos = 0; + for (auto const& line : m_aLines) + { + if ( line.aName == _rEntryName ) + { + return nPos; + } + ++nPos; + } + + return EDITOR_LIST_ENTRY_NOTFOUND; + } + + bool OBrowserListBox::impl_getBrowserLineForName( const OUString& _rEntryName, BrowserLinePointer& _out_rpLine ) const + { + ListBoxLines::const_iterator line = std::find_if(m_aLines.begin(), m_aLines.end(), + [&_rEntryName](const ListBoxLine& rLine) { return rLine.aName == _rEntryName; }); + + if ( line != m_aLines.end() ) + _out_rpLine = line->pLine; + else + _out_rpLine.reset(); + return bool(_out_rpLine); + } + + void OBrowserListBox::EnablePropertyControls( const OUString& _rEntryName, sal_Int16 _nControls, bool _bEnable ) + { + BrowserLinePointer pLine; + if ( impl_getBrowserLineForName( _rEntryName, pLine ) ) + pLine->EnablePropertyControls( _nControls, _bEnable ); + } + + void OBrowserListBox::EnablePropertyLine( const OUString& _rEntryName, bool _bEnable ) + { + BrowserLinePointer pLine; + if ( impl_getBrowserLineForName( _rEntryName, pLine ) ) + pLine->EnablePropertyLine( _bEnable ); + } + + Reference< XPropertyControl > OBrowserListBox::GetPropertyControl( const OUString& _rEntryName ) + { + BrowserLinePointer pLine; + if ( impl_getBrowserLineForName( _rEntryName, pLine ) ) + return pLine->getControl(); + return nullptr; + } + + void OBrowserListBox::InsertEntry(const OLineDescriptor& rPropertyData, sal_uInt16 _nPos) + { + // create a new line + BrowserLinePointer pBrowserLine = std::make_shared<OBrowserLine>(rPropertyData.sName, m_xLinesPlayground.get(), + m_xSizeGroup.get(), m_pInitialControlParent); + + // check that the name is unique + for (auto const& line : m_aLines) + { + if (line.aName == rPropertyData.sName) + { + // already have another line for this name! + assert(false); + } + } + + ListBoxLine aNewLine( rPropertyData.sName, pBrowserLine, rPropertyData.xPropertyHandler ); + ListBoxLines::size_type nInsertPos = _nPos; + if ( _nPos >= m_aLines.size() ) + { + nInsertPos = m_aLines.size(); + m_aLines.push_back( aNewLine ); + } + else + m_aLines.insert( m_aLines.begin() + _nPos, aNewLine ); + + pBrowserLine->SetTitleWidth(m_nTheNameSize); + + // initialize the entry + ChangeEntry(rPropertyData, nInsertPos); + + m_nRowHeight = std::max(m_nRowHeight, pBrowserLine->GetRowHeight() + 6); // 6 is spacing of the "playground" in browserpage.ui + m_xScrolledWindow->vadjustment_set_step_increment(m_nRowHeight); + } + + void OBrowserListBox::ShowEntry(sal_uInt16 nPos) + { + if (nPos == 0) + { + // special case the simple entry 0 situation + m_xScrolledWindow->vadjustment_set_value(0); + return; + } + + if (nPos >= m_aLines.size()) + return; + + unsigned const nWinHeight = m_xScrolledWindow->vadjustment_get_page_size(); + + auto nThumbPos = m_xScrolledWindow->vadjustment_get_value(); + int const nWinTop = nThumbPos; + int const nWinBottom = nWinTop + nWinHeight; + + auto nCtrlPosY = nPos * m_nRowHeight; + + int const nSelectedItemTop = nCtrlPosY; + int const nSelectedItemBottom = nCtrlPosY + m_nRowHeight; + bool const shouldScrollDown = nSelectedItemBottom >= nWinBottom; + bool const shouldScrollUp = nSelectedItemTop <= nWinTop; + bool const isNeedToScroll = shouldScrollDown || shouldScrollUp; + + if (!isNeedToScroll) + return; + + if (shouldScrollDown) + { + int nOffset = nSelectedItemBottom - nWinBottom; + nThumbPos += nOffset; + } + else + { + int nOffset = nWinTop - nSelectedItemTop; + nThumbPos -= nOffset; + if(nThumbPos < 0) + nThumbPos = 0; + } + m_xScrolledWindow->vadjustment_set_value(nThumbPos); + } + + void OBrowserListBox::buttonClicked( OBrowserLine* _pLine, bool _bPrimary ) + { + DBG_ASSERT( _pLine, "OBrowserListBox::buttonClicked: invalid browser line!" ); + if ( _pLine && m_pLineListener ) + { + m_pLineListener->Clicked( _pLine->GetEntryName(), _bPrimary ); + } + } + + void OBrowserListBox::impl_setControlAsPropertyValue( const ListBoxLine& _rLine, const Any& _rPropertyValue ) + { + Reference< XPropertyControl > xControl( _rLine.pLine->getControl() ); + try + { + if ( _rPropertyValue.getValueType().equals( _rLine.pLine->getControl()->getValueType() ) ) + { + xControl->setValue( _rPropertyValue ); + } + else + { + SAL_WARN_IF( !_rLine.xHandler.is(), "extensions.propctrlr", + "OBrowserListBox::impl_setControlAsPropertyValue: no handler -> no conversion (property: '" + << _rLine.pLine->GetEntryName() << "')!" ); + if ( _rLine.xHandler.is() ) + { + Any aControlValue = _rLine.xHandler->convertToControlValue( + _rLine.pLine->GetEntryName(), _rPropertyValue, xControl->getValueType() ); + xControl->setValue( aControlValue ); + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + Any OBrowserListBox::impl_getControlAsPropertyValue( const ListBoxLine& _rLine ) + { + Reference< XPropertyControl > xControl( _rLine.pLine->getControl() ); + Any aPropertyValue; + try + { + SAL_WARN_IF( !_rLine.xHandler.is(), "extensions.propctrlr", + "OBrowserListBox::impl_getControlAsPropertyValue: no handler -> no conversion (property: '" + << _rLine.pLine->GetEntryName() << "')!" ); + if ( _rLine.xHandler.is() ) + aPropertyValue = _rLine.xHandler->convertToPropertyValue( _rLine.pLine->GetEntryName(), xControl->getValue() ); + else + aPropertyValue = xControl->getValue(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + return aPropertyValue; + } + + sal_uInt16 OBrowserListBox::impl_getControlPos( const Reference< XPropertyControl >& _rxControl ) const + { + sal_uInt16 nPos = 0; + for (auto const& search : m_aLines) + { + if ( search.pLine->getControl().get() == _rxControl.get() ) + return nPos; + ++nPos; + } + OSL_FAIL( "OBrowserListBox::impl_getControlPos: invalid control - not part of any of our lines!" ); + return sal_uInt16(-1); + } + + + void OBrowserListBox::focusGained( const Reference< XPropertyControl >& _rxControl ) + { + DBG_TESTSOLARMUTEX(); + + DBG_ASSERT( _rxControl.is(), "OBrowserListBox::focusGained: invalid event source!" ); + if ( !_rxControl.is() ) + return; + + if ( m_pControlObserver ) + m_pControlObserver->focusGained( _rxControl ); + + m_xActiveControl = _rxControl; + ShowEntry( impl_getControlPos( m_xActiveControl ) ); + } + + + void OBrowserListBox::valueChanged( const Reference< XPropertyControl >& _rxControl ) + { + DBG_TESTSOLARMUTEX(); + + DBG_ASSERT( _rxControl.is(), "OBrowserListBox::valueChanged: invalid event source!" ); + if ( !_rxControl.is() ) + return; + + if ( m_pControlObserver ) + m_pControlObserver->valueChanged( _rxControl ); + + if ( m_pLineListener ) + { + const ListBoxLine& rLine = m_aLines[ impl_getControlPos( _rxControl ) ]; + m_pLineListener->Commit( + rLine.pLine->GetEntryName(), + impl_getControlAsPropertyValue( rLine ) + ); + } + } + + + void OBrowserListBox::activateNextControl( const Reference< XPropertyControl >& _rxCurrentControl ) + { + DBG_TESTSOLARMUTEX(); + + sal_uInt16 nLine = impl_getControlPos( _rxCurrentControl ); + + // cycle forwards, 'til we've the next control which can grab the focus + ++nLine; + while ( static_cast< size_t >( nLine ) < m_aLines.size() ) + { + if ( m_aLines[nLine].pLine->GrabFocus() ) + break; + ++nLine; + } + + // wrap around? + if ( ( static_cast< size_t >( nLine ) >= m_aLines.size() ) && ( !m_aLines.empty() ) ) + m_aLines[0].pLine->GrabFocus(); + } + + + namespace + { + + void lcl_implDisposeControl_nothrow( const Reference< XPropertyControl >& _rxControl ) + { + if ( !_rxControl.is() ) + return; + try + { + _rxControl->setControlContext( nullptr ); + Reference< XComponent > xControlComponent( _rxControl, UNO_QUERY ); + if ( xControlComponent.is() ) + xControlComponent->dispose(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + } + + void OBrowserListBox::Clear() + { + for (auto const& line : m_aLines) + { + // hide the line + line.pLine->Hide(); + // reset the listener + lcl_implDisposeControl_nothrow( line.pLine->getControl() ); + } + + clearContainer( m_aLines ); + } + + bool OBrowserListBox::RemoveEntry( const OUString& _rName ) + { + ListBoxLines::iterator it = std::find_if(m_aLines.begin(), m_aLines.end(), + [&_rName](const ListBoxLine& rLine) { return rLine.aName == _rName; }); + + if ( it == m_aLines.end() ) + return false; + + m_aLines.erase( it ); + + return true; + } + + void OBrowserListBox::ChangeEntry( const OLineDescriptor& rPropertyData, ListBoxLines::size_type nPos ) + { + OSL_PRECOND( rPropertyData.Control.is(), "OBrowserListBox::ChangeEntry: invalid control!" ); + if ( !rPropertyData.Control.is() ) + return; + + if ( nPos == EDITOR_LIST_REPLACE_EXISTING ) + nPos = GetPropertyPos( rPropertyData.sName ); + + if ( nPos >= m_aLines.size() ) + return; + + // the current line and control + ListBoxLine& rLine = m_aLines[nPos]; + + // the old control and some data about it + Reference< XPropertyControl > xControl = rLine.pLine->getControl(); + + // clean up the old control + lcl_implDisposeControl_nothrow( xControl ); + + // set the new control at the line + rLine.pLine->setControl( rPropertyData.Control ); + xControl = rLine.pLine->getControl(); + + if ( xControl.is() ) + xControl->setControlContext( m_pControlContextImpl ); + + // the initial property value + if ( rPropertyData.bUnknownValue ) + xControl->setValue( Any() ); + else + impl_setControlAsPropertyValue( rLine, rPropertyData.aValue ); + + rLine.pLine->SetTitle(rPropertyData.DisplayName); + rLine.xHandler = rPropertyData.xPropertyHandler; + + if ( rPropertyData.HasPrimaryButton ) + { + if ( !rPropertyData.PrimaryButtonImageURL.isEmpty() ) + rLine.pLine->ShowBrowseButton( rPropertyData.PrimaryButtonImageURL, true ); + else if ( rPropertyData.PrimaryButtonImage.is() ) + rLine.pLine->ShowBrowseButton( rPropertyData.PrimaryButtonImage, true ); + else + rLine.pLine->ShowBrowseButton( true ); + + if ( rPropertyData.HasSecondaryButton ) + { + if ( !rPropertyData.SecondaryButtonImageURL.isEmpty() ) + rLine.pLine->ShowBrowseButton( rPropertyData.SecondaryButtonImageURL, false ); + else if ( rPropertyData.SecondaryButtonImage.is() ) + rLine.pLine->ShowBrowseButton( rPropertyData.SecondaryButtonImage, false ); + else + rLine.pLine->ShowBrowseButton( false ); + } + else + rLine.pLine->HideBrowseButton( false ); + + rLine.pLine->SetClickListener( this ); + } + else + { + rLine.pLine->HideBrowseButton( true ); + rLine.pLine->HideBrowseButton( false ); + } + + DBG_ASSERT( ( rPropertyData.IndentLevel == 0 ) || ( rPropertyData.IndentLevel == 1 ), + "OBrowserListBox::ChangeEntry: unsupported indent level!" ); + rLine.pLine->IndentTitle( rPropertyData.IndentLevel > 0 ); + + rLine.pLine->SetComponentHelpIds( + HelpIdUrl::getHelpId( rPropertyData.HelpURL ) + ); + + if ( rPropertyData.bReadOnly ) + { + rLine.pLine->SetReadOnly( true ); + + // user controls (i.e. the ones not provided by the usual + // XPropertyControlFactory) have no chance to know that they should be read-only, + // since XPropertyHandler::describePropertyLine does not transport this + // information. + // So, we manually switch this to read-only. + if ( xControl.is() && ( xControl->getControlType() == PropertyControlType::Unknown ) ) + { + weld::Widget* pWindow = rLine.pLine->getControlWindow(); + weld::Entry* pControlWindowAsEdit = dynamic_cast<weld::Entry*>(pWindow); + if (pControlWindowAsEdit) + pControlWindowAsEdit->set_editable(false); + else + pWindow->set_sensitive(false); + } + } + + sal_uInt16 nTextWidth = m_xLinesPlayground->get_pixel_size(rPropertyData.DisplayName).Width(); + if (m_nTheNameSize< nTextWidth) + { + m_nTheNameSize = nTextWidth; + UpdatePlayGround(); + } + } +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/browserlistbox.hxx b/extensions/source/propctrlr/browserlistbox.hxx new file mode 100644 index 0000000000..fc1f355412 --- /dev/null +++ b/extensions/source/propctrlr/browserlistbox.hxx @@ -0,0 +1,164 @@ +/* -*- 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 . + */ + +#pragma once + +#include "browserline.hxx" + +#include <com/sun/star/inspection/XPropertyControl.hpp> +#include <com/sun/star/inspection/XPropertyHandler.hpp> +#include <utility> +#include <vcl/weld.hxx> +#include <rtl/ref.hxx> + +#include <memory> +#include <vector> + +#define EDITOR_LIST_REPLACE_EXISTING \ + std::numeric_limits<ListBoxLines::size_type>::max() + +namespace pcr +{ + + + class IPropertyLineListener; + class IPropertyControlObserver; + struct OLineDescriptor; + class InspectorHelpWindow; + class PropertyControlContext_Impl; + + + // administrative structures for OBrowserListBox + + typedef std::shared_ptr< OBrowserLine > BrowserLinePointer; + struct ListBoxLine + { + OUString aName; + BrowserLinePointer pLine; + css::uno::Reference< css::inspection::XPropertyHandler > + xHandler; + + ListBoxLine( OUString _aName, BrowserLinePointer _pLine, css::uno::Reference< css::inspection::XPropertyHandler > _xHandler ) + : aName(std::move( _aName )), + pLine(std::move( _pLine )), + xHandler(std::move( _xHandler )) + { + } + }; + typedef std::vector< ListBoxLine > ListBoxLines; + + + class OBrowserListBox final : public IButtonClickListener + { + std::unique_ptr<weld::ScrolledWindow> m_xScrolledWindow; + std::unique_ptr<weld::Container> m_xLinesPlayground; + std::unique_ptr<weld::SizeGroup> m_xSizeGroup; + std::unique_ptr<InspectorHelpWindow> m_xHelpWindow; + weld::Container* m_pInitialControlParent; + ListBoxLines m_aLines; + IPropertyLineListener* m_pLineListener; + IPropertyControlObserver* m_pControlObserver; + css::uno::Reference< css::inspection::XPropertyControl > + m_xActiveControl; + sal_uInt16 m_nTheNameSize; + int m_nRowHeight; + ::rtl::Reference< PropertyControlContext_Impl > + m_pControlContextImpl; + + void UpdatePlayGround(); + void ShowEntry(sal_uInt16 nPos); + + public: + explicit OBrowserListBox(weld::Builder& rBuilder, weld::Container* pContainer); + ~OBrowserListBox(); + + void SetListener( IPropertyLineListener* _pListener ); + void SetObserver( IPropertyControlObserver* _pObserver ); + + void EnableHelpSection( bool _bEnable ); + bool HasHelpSection() const; + void SetHelpText( const OUString& _rHelpText ); + + void Clear(); + + void InsertEntry( const OLineDescriptor&, sal_uInt16 nPos ); + bool RemoveEntry( const OUString& _rName ); + void ChangeEntry( const OLineDescriptor&, ListBoxLines::size_type nPos ); + + void SetPropertyValue( const OUString& rEntryName, const css::uno::Any& rValue, bool _bUnknownValue ); + sal_uInt16 GetPropertyPos( std::u16string_view rEntryName ) const; + css::uno::Reference< css::inspection::XPropertyControl > + GetPropertyControl( const OUString& rEntryName ); + void EnablePropertyControls( const OUString& _rEntryName, sal_Int16 _nControls, bool _bEnable ); + void EnablePropertyLine( const OUString& _rEntryName, bool _bEnable ); + + bool IsModified( ) const; + void CommitModified( ); + + /// @throws css::uno::RuntimeException + void focusGained( const css::uno::Reference< css::inspection::XPropertyControl >& Control ); + /// @throws css::uno::RuntimeException + void valueChanged( const css::uno::Reference< css::inspection::XPropertyControl >& Control ); + /// @throws css::uno::RuntimeException + void activateNextControl( const css::uno::Reference< css::inspection::XPropertyControl >& CurrentControl ); + + private: + // IButtonClickListener + void buttonClicked( OBrowserLine* _pLine, bool _bPrimary ) override; + + /** retrieves the index of a given control in our line list + @param _rxControl + The control to lookup. Must denote a control of one of the lines in ->m_aLines + */ + sal_uInt16 impl_getControlPos( const css::uno::Reference< css::inspection::XPropertyControl >& _rxControl ) const; + + /** sets the given property value at the given control, after converting it as necessary + @param _rLine + The line whose at which the value is to be set. + @param _rPropertyValue + the property value to set. If it's not compatible with the control value, + it will be converted, using <member>XPropertyHandler::convertToControlValue</member> + */ + static void impl_setControlAsPropertyValue( const ListBoxLine& _rLine, const css::uno::Any& _rPropertyValue ); + + /** retrieves the value for the given control, as a property value, after converting it as necessary + @param _rLine + The line whose at which the value is to be set. + */ + static css::uno::Any + impl_getControlAsPropertyValue( const ListBoxLine& _rLine ); + + /** retrieves the ->BrowserLinePointer for a given entry name + @param _rEntryName + the name whose line is to be looked up + @param _out_rpLine + contains, upon return, the found browser line, if any + @return + <TRUE/> if and only if a non-<NULL/> line for the given entry name could be + found. + */ + bool impl_getBrowserLineForName( const OUString& _rEntryName, BrowserLinePointer& _out_rpLine ) const; + }; + + +} // namespace pcr + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/browserpage.cxx b/extensions/source/propctrlr/browserpage.cxx new file mode 100644 index 0000000000..5502418bd0 --- /dev/null +++ b/extensions/source/propctrlr/browserpage.cxx @@ -0,0 +1,41 @@ +/* -*- 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 <vcl/svapp.hxx> +#include "browserpage.hxx" + +namespace pcr +{ + OBrowserPage::OBrowserPage(weld::Container* pParent, weld::Container* pInitialControlContainer) + : m_pParent(pParent) + , m_xBuilder(Application::CreateBuilder(pParent, "modules/spropctrlr/ui/browserpage.ui")) + , m_xContainer(m_xBuilder->weld_container("BrowserPage")) + , m_xListBox(new OBrowserListBox(*m_xBuilder, pInitialControlContainer)) + { + } + + OBrowserPage::~OBrowserPage() + { + if (m_pParent) + detach(); + assert(!m_pParent); + } +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/browserpage.hxx b/extensions/source/propctrlr/browserpage.hxx new file mode 100644 index 0000000000..5f9220a796 --- /dev/null +++ b/extensions/source/propctrlr/browserpage.hxx @@ -0,0 +1,61 @@ +/* -*- 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 . + */ + +#pragma once + +#include <o3tl/deleter.hxx> +#include "browserlistbox.hxx" + +namespace pcr +{ + class OBrowserPage + { + private: + weld::Container* m_pParent; + std::unique_ptr<weld::Builder> m_xBuilder; + std::unique_ptr<weld::Container> m_xContainer; + std::unique_ptr<OBrowserListBox, o3tl::default_delete<OBrowserListBox>> m_xListBox; + + public: + // TODO inherit from BuilderPage + explicit OBrowserPage(weld::Container* pParent, weld::Container* pContainer); + ~OBrowserPage(); + + void SetHelpId(const OUString& rHelpId) { m_xContainer->set_help_id(rHelpId); } + + OBrowserListBox& getListBox() { return *m_xListBox; } + const OBrowserListBox& getListBox() const { return *m_xListBox; } + + void detach() + { + assert(m_pParent && "already attached"); + m_pParent->move(m_xContainer.get(), nullptr); + m_pParent = nullptr; + } + + void reattach(weld::Container* pNewParent) + { + assert(!m_pParent && "already attached"); + m_pParent = pNewParent; + m_pParent->move(m_xContainer.get(), pNewParent); + } + }; +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/browserview.cxx b/extensions/source/propctrlr/browserview.cxx new file mode 100644 index 0000000000..3be48abe41 --- /dev/null +++ b/extensions/source/propctrlr/browserview.cxx @@ -0,0 +1,64 @@ +/* -*- 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 "browserview.hxx" +#include "propertyeditor.hxx" +#include <helpids.h> +#include <memory> + +namespace pcr +{ + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + + OPropertyBrowserView::OPropertyBrowserView(const css::uno::Reference<css::uno::XComponentContext>& rContext, weld::Builder& rBuilder) + : m_xPropBox(new OPropertyEditor(rContext, rBuilder)) + , m_nActivePage(0) + { + m_xPropBox->SetHelpId(HID_FM_PROPDLG_TABCTR); + m_xPropBox->setPageActivationHandler(LINK(this, OPropertyBrowserView, OnPageActivation)); + } + + IMPL_LINK(OPropertyBrowserView, OnPageActivation, const OUString&, rNewPage, void) + { + m_nActivePage = rNewPage.toUInt32(); + m_aPageActivationHandler.Call(nullptr); + } + + OPropertyBrowserView::~OPropertyBrowserView() + { + sal_uInt16 nTmpPage = m_xPropBox->GetCurPage(); + if (nTmpPage) + m_nActivePage = nTmpPage; + } + + void OPropertyBrowserView::activatePage(sal_uInt16 _nPage) + { + m_nActivePage = _nPage; + getPropertyBox().SetPage(m_nActivePage); + } + + css::awt::Size OPropertyBrowserView::getMinimumSize() const + { + ::Size aSize = m_xPropBox->get_preferred_size(); + return css::awt::Size(aSize.Width(), aSize.Height()); + } +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/browserview.hxx b/extensions/source/propctrlr/browserview.hxx new file mode 100644 index 0000000000..d640398137 --- /dev/null +++ b/extensions/source/propctrlr/browserview.hxx @@ -0,0 +1,56 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <o3tl/deleter.hxx> +#include <vcl/weld.hxx> + +namespace pcr +{ + class OPropertyEditor; + class OPropertyBrowserView final + { + std::unique_ptr<OPropertyEditor, o3tl::default_delete<OPropertyEditor>> m_xPropBox; + sal_uInt16 m_nActivePage; + Link<LinkParamNone*,void> m_aPageActivationHandler; + + public: + explicit OPropertyBrowserView(const css::uno::Reference<css::uno::XComponentContext>& rContext, weld::Builder& rBuilder); + ~OPropertyBrowserView(); + + OPropertyEditor& getPropertyBox() { return *m_xPropBox; } + + // page handling + sal_uInt16 getActivePage() const { return m_nActivePage; } + void activatePage(sal_uInt16 _nPage); + + void setPageActivationHandler(const Link<LinkParamNone*,void>& _rHdl) { m_aPageActivationHandler = _rHdl; } + + css::awt::Size getMinimumSize() const; + + private: + DECL_LINK(OnPageActivation, const OUString&, void); + }; + +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/buttonnavigationhandler.cxx b/extensions/source/propctrlr/buttonnavigationhandler.cxx new file mode 100644 index 0000000000..618d9db46b --- /dev/null +++ b/extensions/source/propctrlr/buttonnavigationhandler.cxx @@ -0,0 +1,268 @@ +/* -*- 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 "buttonnavigationhandler.hxx" +#include "formstrings.hxx" +#include "formmetadata.hxx" +#include "pushbuttonnavigation.hxx" + +#include <com/sun/star/form/inspection/FormComponentPropertyHandler.hpp> + +namespace pcr +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::script; + using namespace ::com::sun::star::frame; + using namespace ::com::sun::star::inspection; + + ButtonNavigationHandler::ButtonNavigationHandler( const Reference< XComponentContext >& _rxContext ) + :PropertyHandlerComponent( _rxContext ) + { + + m_xSlaveHandler = css::form::inspection::FormComponentPropertyHandler::create( m_xContext ); + } + + + ButtonNavigationHandler::~ButtonNavigationHandler( ) + { + } + + + OUString ButtonNavigationHandler::getImplementationName( ) + { + return "com.sun.star.comp.extensions.ButtonNavigationHandler"; + } + + + Sequence< OUString > ButtonNavigationHandler::getSupportedServiceNames( ) + { + return { "com.sun.star.form.inspection.ButtonNavigationHandler" }; + } + + + void SAL_CALL ButtonNavigationHandler::inspect( const Reference< XInterface >& _rxIntrospectee ) + { + PropertyHandlerComponent::inspect( _rxIntrospectee ); + m_xSlaveHandler->inspect( _rxIntrospectee ); + } + + + PropertyState SAL_CALL ButtonNavigationHandler::getPropertyState( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + PropertyState eState = PropertyState_DIRECT_VALUE; + switch ( nPropId ) + { + case PROPERTY_ID_BUTTONTYPE: + { + PushButtonNavigation aHelper( m_xComponent ); + eState = aHelper.getCurrentButtonTypeState(); + } + break; + case PROPERTY_ID_TARGET_URL: + { + PushButtonNavigation aHelper( m_xComponent ); + eState = aHelper.getCurrentTargetURLState(); + } + break; + + default: + OSL_FAIL( "ButtonNavigationHandler::getPropertyState: cannot handle this property!" ); + break; + } + + return eState; + } + + + Any SAL_CALL ButtonNavigationHandler::getPropertyValue( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + Any aReturn; + switch ( nPropId ) + { + case PROPERTY_ID_BUTTONTYPE: + { + PushButtonNavigation aHelper( m_xComponent ); + aReturn = aHelper.getCurrentButtonType(); + } + break; + + case PROPERTY_ID_TARGET_URL: + { + PushButtonNavigation aHelper( m_xComponent ); + aReturn = aHelper.getCurrentTargetURL(); + } + break; + + default: + OSL_FAIL( "ButtonNavigationHandler::getPropertyValue: cannot handle this property!" ); + break; + } + + return aReturn; + } + + + void SAL_CALL ButtonNavigationHandler::setPropertyValue( const OUString& _rPropertyName, const Any& _rValue ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + switch ( nPropId ) + { + case PROPERTY_ID_BUTTONTYPE: + { + PushButtonNavigation aHelper( m_xComponent ); + aHelper.setCurrentButtonType( _rValue ); + } + break; + + case PROPERTY_ID_TARGET_URL: + { + PushButtonNavigation aHelper( m_xComponent ); + aHelper.setCurrentTargetURL( _rValue ); + } + break; + + default: + OSL_FAIL( "ButtonNavigationHandler::setPropertyValue: cannot handle this id!" ); + } + } + + + bool ButtonNavigationHandler::isNavigationCapableButton( const Reference< XPropertySet >& _rxComponent ) + { + Reference< XPropertySetInfo > xPSI; + if ( _rxComponent.is() ) + xPSI = _rxComponent->getPropertySetInfo(); + + return xPSI.is() + && xPSI->hasPropertyByName( PROPERTY_TARGET_URL ) + && xPSI->hasPropertyByName( PROPERTY_BUTTONTYPE ); + } + + + Sequence< Property > ButtonNavigationHandler::doDescribeSupportedProperties() const + { + std::vector< Property > aProperties; + + if ( isNavigationCapableButton( m_xComponent ) ) + { + addStringPropertyDescription( aProperties, PROPERTY_TARGET_URL ); + implAddPropertyDescription( aProperties, PROPERTY_BUTTONTYPE, ::cppu::UnoType<sal_Int32>::get() ); + } + + if ( aProperties.empty() ) + return Sequence< Property >(); + return comphelper::containerToSequence(aProperties); + } + + + Sequence< OUString > SAL_CALL ButtonNavigationHandler::getActuatingProperties( ) + { + Sequence< OUString > aActuating{ PROPERTY_BUTTONTYPE, PROPERTY_TARGET_URL }; + return aActuating; + } + + + InteractiveSelectionResult SAL_CALL ButtonNavigationHandler::onInteractivePropertySelection( const OUString& _rPropertyName, sal_Bool _bPrimary, Any& _rData, const Reference< XObjectInspectorUI >& _rxInspectorUI ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + InteractiveSelectionResult eReturn( InteractiveSelectionResult_Cancelled ); + + switch ( nPropId ) + { + case PROPERTY_ID_TARGET_URL: + eReturn = m_xSlaveHandler->onInteractivePropertySelection( _rPropertyName, _bPrimary, _rData, _rxInspectorUI ); + break; + default: + eReturn = PropertyHandlerComponent::onInteractivePropertySelection( _rPropertyName, _bPrimary, _rData, _rxInspectorUI ); + break; + } + + return eReturn; + } + + + void SAL_CALL ButtonNavigationHandler::actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const Any& /*_rNewValue*/, const Any& /*_rOldValue*/, const Reference< XObjectInspectorUI >& _rxInspectorUI, sal_Bool /*_bFirstTimeInit*/ ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwRuntime( _rActuatingPropertyName ) ); + switch ( nPropId ) + { + case PROPERTY_ID_BUTTONTYPE: + { + PushButtonNavigation aHelper( m_xComponent ); + _rxInspectorUI->enablePropertyUI( PROPERTY_TARGET_URL, aHelper.currentButtonTypeIsOpenURL() ); + } + break; + + case PROPERTY_ID_TARGET_URL: + { + PushButtonNavigation aHelper( m_xComponent ); + _rxInspectorUI->enablePropertyUI( PROPERTY_TARGET_FRAME, aHelper.hasNonEmptyCurrentTargetURL() ); + } + break; + + default: + OSL_FAIL( "ButtonNavigationHandler::actuatingPropertyChanged: cannot handle this id!" ); + } + } + + + LineDescriptor SAL_CALL ButtonNavigationHandler::describePropertyLine( const OUString& _rPropertyName, const Reference< XPropertyControlFactory >& _rxControlFactory ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + LineDescriptor aReturn; + + switch ( nPropId ) + { + case PROPERTY_ID_TARGET_URL: + aReturn = m_xSlaveHandler->describePropertyLine( _rPropertyName, _rxControlFactory ); + break; + default: + aReturn = PropertyHandlerComponent::describePropertyLine( _rPropertyName, _rxControlFactory ); + break; + } + + return aReturn; + } + + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_ButtonNavigationHandler_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::ButtonNavigationHandler(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/buttonnavigationhandler.hxx b/extensions/source/propctrlr/buttonnavigationhandler.hxx new file mode 100644 index 0000000000..c5e01e1df2 --- /dev/null +++ b/extensions/source/propctrlr/buttonnavigationhandler.hxx @@ -0,0 +1,71 @@ +/* -*- 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 . + */ + +#pragma once + +#include "propertyhandler.hxx" + + +namespace pcr +{ + + /** a property handler for any virtual string properties + */ + class ButtonNavigationHandler : public PropertyHandlerComponent + { + private: + css::uno::Reference< css::inspection::XPropertyHandler > + m_xSlaveHandler; + + public: + explicit ButtonNavigationHandler( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext + ); + + protected: + virtual ~ButtonNavigationHandler() override; + + static bool isNavigationCapableButton( const css::uno::Reference< css::beans::XPropertySet >& _rxComponent ); + + protected: + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames () override; + + // XPropertyHandler overriables + virtual void SAL_CALL inspect( const css::uno::Reference< css::uno::XInterface >& _rxIntrospectee ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& _rPropertyName ) override; + virtual void SAL_CALL setPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rValue ) override; + virtual css::beans::PropertyState SAL_CALL getPropertyState( const OUString& _rPropertyName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getActuatingProperties( ) override; + virtual css::inspection::InteractiveSelectionResult + SAL_CALL onInteractivePropertySelection( const OUString& _rPropertyName, sal_Bool _bPrimary, css::uno::Any& _rData, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI ) override; + virtual void SAL_CALL actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const css::uno::Any& _rNewValue, const css::uno::Any& _rOldValue, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI, sal_Bool _bFirstTimeInit ) override; + virtual css::inspection::LineDescriptor SAL_CALL describePropertyLine( const OUString& _rPropertyName, const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory ) override; + + // PropertyHandler overridables + virtual css::uno::Sequence< css::beans::Property > + doDescribeSupportedProperties() const override; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/cellbindinghandler.cxx b/extensions/source/propctrlr/cellbindinghandler.cxx new file mode 100644 index 0000000000..bb5bc9a319 --- /dev/null +++ b/extensions/source/propctrlr/cellbindinghandler.cxx @@ -0,0 +1,487 @@ +/* -*- 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 "cellbindinghandler.hxx" +#include "formstrings.hxx" +#include "formmetadata.hxx" +#include "cellbindinghelper.hxx" + +#include <com/sun/star/form/binding/XValueBinding.hpp> +#include <com/sun/star/lang/NullPointerException.hpp> +#include <com/sun/star/table/CellAddress.hpp> +#include <com/sun/star/inspection/XObjectInspectorUI.hpp> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> + + +namespace pcr +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::table; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::script; + using namespace ::com::sun::star::frame; + using namespace ::com::sun::star::inspection; + using namespace ::com::sun::star::form::binding; + using namespace ::comphelper; + + CellBindingPropertyHandler::CellBindingPropertyHandler( const Reference< XComponentContext >& _rxContext ) + :PropertyHandlerComponent( _rxContext ) + ,m_pCellExchangeConverter( new DefaultEnumRepresentation( *m_pInfoService, ::cppu::UnoType<sal_Int16>::get(), PROPERTY_ID_CELL_EXCHANGE_TYPE ) ) + { + } + + + OUString CellBindingPropertyHandler::getImplementationName( ) + { + return "com.sun.star.comp.extensions.CellBindingPropertyHandler"; + } + + + Sequence< OUString > CellBindingPropertyHandler::getSupportedServiceNames( ) + { + return { "com.sun.star.form.inspection.CellBindingPropertyHandler" }; + } + + + void CellBindingPropertyHandler::onNewComponent() + { + PropertyHandlerComponent::onNewComponent(); + + Reference< XModel > xDocument( impl_getContextDocument_nothrow() ); + DBG_ASSERT( xDocument.is(), "CellBindingPropertyHandler::onNewComponent: no document!" ); + if ( CellBindingHelper::isSpreadsheetDocument( xDocument ) ) + m_pHelper.reset( new CellBindingHelper( m_xComponent, xDocument ) ); + } + + + CellBindingPropertyHandler::~CellBindingPropertyHandler( ) + { + } + + + Sequence< OUString > SAL_CALL CellBindingPropertyHandler::getActuatingProperties( ) + { + Sequence< OUString > aInterestingProperties{ PROPERTY_LIST_CELL_RANGE, + PROPERTY_BOUND_CELL, + PROPERTY_CONTROLSOURCE }; + return aInterestingProperties; + } + + + void SAL_CALL CellBindingPropertyHandler::actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const Any& _rNewValue, const Any& /*_rOldValue*/, const Reference< XObjectInspectorUI >& _rxInspectorUI, sal_Bool _bFirstTimeInit ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nActuatingPropId( impl_getPropertyId_throwRuntime( _rActuatingPropertyName ) ); + OSL_PRECOND(m_pHelper, + "CellBindingPropertyHandler::actuatingPropertyChanged: inconsistency!"); + // if we survived impl_getPropertyId_throwRuntime, we should have a helper, since no helper implies no properties + + OSL_PRECOND( _rxInspectorUI.is(), "FormComponentPropertyHandler::actuatingPropertyChanged: no access to the UI!" ); + if ( !_rxInspectorUI.is() ) + throw NullPointerException(); + + std::vector< PropertyId > aDependentProperties; + + switch ( nActuatingPropId ) + { + // ----- BoundCell ----- + case PROPERTY_ID_BOUND_CELL: + { + // the SQL-data-binding related properties need to be enabled if and only if + // there is *no* valid cell binding + Reference< XValueBinding > xBinding; + _rNewValue >>= xBinding; + + if ( impl_isSupportedProperty_nothrow( PROPERTY_ID_CELL_EXCHANGE_TYPE ) ) + _rxInspectorUI->enablePropertyUI( PROPERTY_CELL_EXCHANGE_TYPE, xBinding.is() ); + if ( impl_componentHasProperty_throw( PROPERTY_CONTROLSOURCE ) ) + _rxInspectorUI->enablePropertyUI( PROPERTY_CONTROLSOURCE, !xBinding.is() ); + + if ( impl_isSupportedProperty_nothrow( PROPERTY_ID_FILTERPROPOSAL ) ) + _rxInspectorUI->enablePropertyUI( PROPERTY_FILTERPROPOSAL, !xBinding.is() ); + if ( impl_isSupportedProperty_nothrow( PROPERTY_ID_EMPTY_IS_NULL ) ) + _rxInspectorUI->enablePropertyUI( PROPERTY_EMPTY_IS_NULL, !xBinding.is() ); + + aDependentProperties.push_back( PROPERTY_ID_BOUNDCOLUMN ); + + if ( !xBinding.is() && m_pHelper->getCurrentBinding().is() ) + { + // ensure that the "transfer selection as" property is reset. Since we can't remember + // it at the object itself, but derive it from the binding only, we have to normalize + // it now that there *is* no binding anymore. + setPropertyValue( PROPERTY_CELL_EXCHANGE_TYPE, Any( sal_Int16(0) ) ); + } + } + break; + + // ----- CellRange ----- + case PROPERTY_ID_LIST_CELL_RANGE: + { + // the list source related properties need to be enabled if and only if + // there is *no* valid external list source for the control + Reference< XListEntrySource > xSource; + _rNewValue >>= xSource; + + _rxInspectorUI->enablePropertyUI( PROPERTY_STRINGITEMLIST, !xSource.is() ); + _rxInspectorUI->enablePropertyUI( PROPERTY_LISTSOURCE, !xSource.is() ); + _rxInspectorUI->enablePropertyUI( PROPERTY_LISTSOURCETYPE, !xSource.is() ); + + aDependentProperties.push_back( PROPERTY_ID_BOUNDCOLUMN ); + + // also reset the list entries if the cell range is reset + // #i28319# + if ( !_bFirstTimeInit ) + { + try + { + if ( !xSource.is() ) + { + setPropertyValue( PROPERTY_STRINGITEMLIST, Any( Sequence< OUString >() ) ); + setPropertyValue( PROPERTY_TYPEDITEMLIST, Any( Sequence< Any >() ) ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( + "extensions.propctrlr", + "ListCellRange: caught an exception while resetting the string items!"); + } + } + } + break; // case PROPERTY_ID_LIST_CELL_RANGE + + // ----- DataField ----- + case PROPERTY_ID_CONTROLSOURCE: + { + OUString sControlSource; + _rNewValue >>= sControlSource; + if ( impl_isSupportedProperty_nothrow( PROPERTY_ID_BOUND_CELL ) ) + _rxInspectorUI->enablePropertyUI( PROPERTY_BOUND_CELL, sControlSource.isEmpty() ); + } + break; // case PROPERTY_ID_CONTROLSOURCE + + default: + OSL_FAIL( "CellBindingPropertyHandler::actuatingPropertyChanged: did not register for this property!" ); + } + + for (auto const& dependentProperty : aDependentProperties) + { + impl_updateDependentProperty_nothrow( dependentProperty, _rxInspectorUI ); + } + } + + + void CellBindingPropertyHandler::impl_updateDependentProperty_nothrow( PropertyId _nPropId, const Reference< XObjectInspectorUI >& _rxInspectorUI ) const + { + try + { + switch ( _nPropId ) + { + // ----- BoundColumn ----- + case PROPERTY_ID_BOUNDCOLUMN: + { + CellBindingPropertyHandler* pNonConstThis = const_cast< CellBindingPropertyHandler* >( this ); + Reference< XValueBinding > xBinding( pNonConstThis->getPropertyValue( PROPERTY_BOUND_CELL ), UNO_QUERY ); + Reference< XListEntrySource > xListSource( pNonConstThis->getPropertyValue( PROPERTY_LIST_CELL_RANGE ), UNO_QUERY ); + + if ( impl_isSupportedProperty_nothrow( PROPERTY_ID_BOUNDCOLUMN ) ) + _rxInspectorUI->enablePropertyUI( PROPERTY_BOUNDCOLUMN, !xBinding.is() && !xListSource.is() ); + } + break; // case PROPERTY_ID_BOUNDCOLUMN + + } // switch + + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "CellBindingPropertyHandler::impl_updateDependentProperty_nothrow" ); + } + } + + + Any SAL_CALL CellBindingPropertyHandler::getPropertyValue( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + OSL_ENSURE(m_pHelper, "CellBindingPropertyHandler::getPropertyValue: inconsistency!"); + // if we survived impl_getPropertyId_throwUnknownProperty, we should have a helper, since no helper implies no properties + + Any aReturn; + switch ( nPropId ) + { + case PROPERTY_ID_BOUND_CELL: + { + Reference< XValueBinding > xBinding( m_pHelper->getCurrentBinding() ); + if ( !CellBindingHelper::isCellBinding( xBinding ) ) + xBinding.clear(); + + aReturn <<= xBinding; + } + break; + + case PROPERTY_ID_LIST_CELL_RANGE: + { + Reference< XListEntrySource > xSource( m_pHelper->getCurrentListSource() ); + if ( !CellBindingHelper::isCellRangeListSource( xSource ) ) + xSource.clear(); + + aReturn <<= xSource; + } + break; + + case PROPERTY_ID_CELL_EXCHANGE_TYPE: + { + Reference< XValueBinding > xBinding( m_pHelper->getCurrentBinding() ); + aReturn <<= static_cast<sal_Int16>( CellBindingHelper::isCellIntegerBinding( xBinding ) ? 1 : 0 ); + } + break; + + default: + OSL_FAIL( "CellBindingPropertyHandler::getPropertyValue: cannot handle this!" ); + break; + } + return aReturn; + } + + + void SAL_CALL CellBindingPropertyHandler::setPropertyValue( const OUString& _rPropertyName, const Any& _rValue ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + OSL_ENSURE(m_pHelper, "CellBindingPropertyHandler::setPropertyValue: inconsistency!"); + // if we survived impl_getPropertyId_throwUnknownProperty, we should have a helper, since no helper implies no properties + + try + { + Any aOldValue = getPropertyValue( _rPropertyName ); + + switch ( nPropId ) + { + case PROPERTY_ID_BOUND_CELL: + { + Reference< XValueBinding > xBinding; + _rValue >>= xBinding; + m_pHelper->setBinding( xBinding ); + } + break; + + case PROPERTY_ID_LIST_CELL_RANGE: + { + Reference< XListEntrySource > xSource; + _rValue >>= xSource; + m_pHelper->setListSource( xSource ); + } + break; + + case PROPERTY_ID_CELL_EXCHANGE_TYPE: + { + sal_Int16 nExchangeType = 0; + OSL_VERIFY( _rValue >>= nExchangeType ); + + Reference< XValueBinding > xBinding = m_pHelper->getCurrentBinding( ); + if ( xBinding.is() ) + { + bool bNeedIntegerBinding = ( nExchangeType == 1 ); + if ( bNeedIntegerBinding != CellBindingHelper::isCellIntegerBinding( xBinding ) ) + { + CellAddress aAddress; + if ( m_pHelper->getAddressFromCellBinding( xBinding, aAddress ) ) + { + xBinding = m_pHelper->createCellBindingFromAddress( aAddress, bNeedIntegerBinding ); + m_pHelper->setBinding( xBinding ); + } + } + } + } + break; + + default: + OSL_FAIL( "CellBindingPropertyHandler::setPropertyValue: cannot handle this!" ); + break; + } + + impl_setContextDocumentModified_nothrow(); + + Any aNewValue( getPropertyValue( _rPropertyName ) ); + firePropertyChange( _rPropertyName, nPropId, aOldValue, aNewValue ); + // TODO/UNOize: can't we make this a part of the base class, for all those "virtual" + // properties? Base class'es |setPropertyValue| could call some |doSetPropertyValue|, + // and handle the listener notification itself + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "CellBindingPropertyHandler::setPropertyValue" ); + } + } + + + Any SAL_CALL CellBindingPropertyHandler::convertToPropertyValue( const OUString& _rPropertyName, const Any& _rControlValue ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + Any aPropertyValue; + + OSL_ENSURE( + m_pHelper, + "CellBindingPropertyHandler::convertToPropertyValue: we have no SupportedProperties!"); + if (!m_pHelper) + return aPropertyValue; + + PropertyId nPropId( m_pInfoService->getPropertyId( _rPropertyName ) ); + + OUString sControlValue; + OSL_VERIFY( _rControlValue >>= sControlValue ); + switch( nPropId ) + { + case PROPERTY_ID_LIST_CELL_RANGE: + aPropertyValue <<= m_pHelper->createCellListSourceFromStringAddress( sControlValue ); + break; + + case PROPERTY_ID_BOUND_CELL: + { + // if we have the possibility of an integer binding, then we must preserve + // this property's value (e.g. if the current binding is an integer binding, then + // the newly created one must be, too) + bool bIntegerBinding = false; + if ( m_pHelper->isCellIntegerBindingAllowed() ) + { + sal_Int16 nCurrentBindingType = 0; + getPropertyValue( PROPERTY_CELL_EXCHANGE_TYPE ) >>= nCurrentBindingType; + bIntegerBinding = ( nCurrentBindingType != 0 ); + } + aPropertyValue <<= m_pHelper->createCellBindingFromStringAddress( sControlValue, bIntegerBinding ); + } + break; + + case PROPERTY_ID_CELL_EXCHANGE_TYPE: + m_pCellExchangeConverter->getValueFromDescription( sControlValue, aPropertyValue ); + break; + + default: + OSL_FAIL( "CellBindingPropertyHandler::convertToPropertyValue: cannot handle this!" ); + break; + } + + return aPropertyValue; + } + + + Any SAL_CALL CellBindingPropertyHandler::convertToControlValue( const OUString& _rPropertyName, + const Any& _rPropertyValue, const Type& /*_rControlValueType*/ ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + Any aControlValue; + + OSL_ENSURE( + m_pHelper, + "CellBindingPropertyHandler::convertToControlValue: we have no SupportedProperties!"); + if (!m_pHelper) + return aControlValue; + + PropertyId nPropId( m_pInfoService->getPropertyId( _rPropertyName ) ); + + switch ( nPropId ) + { + case PROPERTY_ID_BOUND_CELL: + { + Reference< XValueBinding > xBinding; + bool bSuccess = _rPropertyValue >>= xBinding; + OSL_ENSURE( bSuccess, "CellBindingPropertyHandler::convertToControlValue: invalid value (1)!" ); + + // the only value binding we support so far is linking to spreadsheet cells + aControlValue <<= m_pHelper->getStringAddressFromCellBinding( xBinding ); + } + break; + + case PROPERTY_ID_LIST_CELL_RANGE: + { + Reference< XListEntrySource > xSource; + bool bSuccess = _rPropertyValue >>= xSource; + OSL_ENSURE( bSuccess, "CellBindingPropertyHandler::convertToControlValue: invalid value (2)!" ); + + // the only value binding we support so far is linking to spreadsheet cells + aControlValue <<= m_pHelper->getStringAddressFromCellListSource( xSource ); + } + break; + + case PROPERTY_ID_CELL_EXCHANGE_TYPE: + aControlValue <<= m_pCellExchangeConverter->getDescriptionForValue( _rPropertyValue ); + break; + + default: + OSL_FAIL( "CellBindingPropertyHandler::convertToControlValue: cannot handle this!" ); + break; + } + + return aControlValue; + } + + + Sequence< Property > CellBindingPropertyHandler::doDescribeSupportedProperties() const + { + std::vector< Property > aProperties; + + bool bAllowCellLinking = m_pHelper && m_pHelper->isCellBindingAllowed(); + bool bAllowCellIntLinking = m_pHelper && m_pHelper->isCellIntegerBindingAllowed(); + bool bAllowListCellRange = m_pHelper && m_pHelper->isListCellRangeAllowed(); + if ( bAllowCellLinking || bAllowListCellRange || bAllowCellIntLinking ) + { + sal_Int32 nPos = ( bAllowCellLinking ? 1 : 0 ) + + ( bAllowListCellRange ? 1 : 0 ) + + ( bAllowCellIntLinking ? 1 : 0 ); + aProperties.resize( nPos ); + + if ( bAllowCellLinking ) + { + aProperties[ --nPos ] = Property( PROPERTY_BOUND_CELL, PROPERTY_ID_BOUND_CELL, + ::cppu::UnoType<OUString>::get(), 0 ); + } + if ( bAllowCellIntLinking ) + { + aProperties[ --nPos ] = Property( PROPERTY_CELL_EXCHANGE_TYPE, PROPERTY_ID_CELL_EXCHANGE_TYPE, + ::cppu::UnoType<sal_Int16>::get(), 0 ); + } + if ( bAllowListCellRange ) + { + aProperties[ --nPos ] = Property( PROPERTY_LIST_CELL_RANGE, PROPERTY_ID_LIST_CELL_RANGE, + ::cppu::UnoType<OUString>::get(), 0 ); + } + } + + if ( aProperties.empty() ) + return Sequence< Property >(); + return comphelper::containerToSequence(aProperties); + } + + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_CellBindingPropertyHandler_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::CellBindingPropertyHandler(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/cellbindinghandler.hxx b/extensions/source/propctrlr/cellbindinghandler.hxx new file mode 100644 index 0000000000..4e81b0624b --- /dev/null +++ b/extensions/source/propctrlr/cellbindinghandler.hxx @@ -0,0 +1,92 @@ +/* -*- 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 . + */ + +#pragma once + +#include "propertyhandler.hxx" + +#include <rtl/ref.hxx> + +#include <memory> + + +namespace pcr +{ + + + class CellBindingHelper; + class IPropertyEnumRepresentation; + + class CellBindingPropertyHandler : public PropertyHandlerComponent + { + private: + std::unique_ptr< CellBindingHelper > m_pHelper; + ::rtl::Reference< IPropertyEnumRepresentation > m_pCellExchangeConverter; + + public: + explicit CellBindingPropertyHandler( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext + ); + + protected: + virtual ~CellBindingPropertyHandler() override; + + protected: + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames () override; + + // XPropertyHandler overriables + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& _rPropertyName ) override; + virtual void SAL_CALL setPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rValue ) override; + virtual css::uno::Any SAL_CALL convertToPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rControlValue ) override; + virtual css::uno::Any SAL_CALL convertToControlValue( const OUString& _rPropertyName, const css::uno::Any& _rPropertyValue, const css::uno::Type& _rControlValueType ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getActuatingProperties( ) override; + virtual void SAL_CALL actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const css::uno::Any& _rNewValue, const css::uno::Any& _rOldValue, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI, sal_Bool _bFirstTimeInit ) override; + + // PropertyHandler overridables + virtual css::uno::Sequence< css::beans::Property > + doDescribeSupportedProperties() const override; + virtual void onNewComponent() override; + + private: + /** updates a property (UI) whose state depends on more than one other property + + ->actuatingPropertyChanged is called for certain properties in whose changes + we expressed interes (->getActuatingProperty). Now such a property change can + result in simple UI updates, for instance another property being enabled or disabled. + + However, it can also result in a more complex change: The current (UI) state might + depend on the value of more than one other property. Those dependent properties (their + UI, more precisely) are updated in this method. + + @param _nPropid + the ->PropertyId of the dependent property whose UI state is to be updated + + @param _rxInspectorUI + provides access to the property browser UI. Must not be <NULL/>. + */ + void impl_updateDependentProperty_nothrow( PropertyId _nPropId, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI ) const; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/cellbindinghelper.cxx b/extensions/source/propctrlr/cellbindinghelper.cxx new file mode 100644 index 0000000000..a9ab9ce745 --- /dev/null +++ b/extensions/source/propctrlr/cellbindinghelper.cxx @@ -0,0 +1,541 @@ +/* -*- 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 "cellbindinghelper.hxx" +#include <com/sun/star/form/binding/XBindableValue.hpp> +#include <com/sun/star/form/binding/XListEntrySink.hpp> +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/form/XGridColumnFactory.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/form/XFormsSupplier.hpp> +#include <com/sun/star/form/XForm.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/sheet/XSpreadsheet.hpp> +#include <unotools/transliterationwrapper.hxx> +#include <osl/diagnose.h> +#include <comphelper/diagnose_ex.hxx> +#include "formstrings.hxx" + +#include <algorithm> +#include <utility> + + +namespace pcr +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::frame; + using namespace ::com::sun::star::sheet; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::drawing; + using namespace ::com::sun::star::table; + using namespace ::com::sun::star::form; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::i18n; + using namespace ::com::sun::star::form::binding; + + namespace + { + + struct StringCompare + { + private: + OUString m_sReference; + + public: + explicit StringCompare( OUString _aReference ) : m_sReference(std::move( _aReference )) { } + + bool operator()( std::u16string_view _rCompare ) + { + return ( _rCompare == m_sReference ); + } + }; + } + + + CellBindingHelper::CellBindingHelper( const Reference< XPropertySet >& _rxControlModel, const Reference< XModel >& _rxContextDocument ) + :m_xControlModel( _rxControlModel ) + { + OSL_ENSURE( m_xControlModel.is(), "CellBindingHelper::CellBindingHelper: invalid control model!" ); + + m_xDocument.set(_rxContextDocument, css::uno::UNO_QUERY); + OSL_ENSURE( m_xDocument.is(), "CellBindingHelper::CellBindingHelper: This is no spreadsheet document!" ); + + OSL_ENSURE( isSpreadsheetDocumentWhichSupplies( SERVICE_ADDRESS_CONVERSION ), + "CellBindingHelper::CellBindingHelper: the document cannot convert address representations!" ); + } + + + bool CellBindingHelper::isSpreadsheetDocument( const Reference< XModel >& _rxContextDocument ) + { + return Reference< XSpreadsheetDocument >::query( _rxContextDocument ).is(); + } + + + sal_Int16 CellBindingHelper::getControlSheetIndex( Reference< XSpreadsheet >& _out_rxSheet ) const + { + sal_Int16 nSheetIndex = -1; + // every sheet has a draw page, and every draw page has a forms collection. + // Our control, OTOH, belongs to a forms collection. Match these... + try + { + // for determining the draw page, we need the forms collection which + // the object belongs to. This is the first object up the hierarchy which is + // *no* XForm (and, well, no XGridColumnFactory) + Reference< XChild > xCheck( m_xControlModel, UNO_QUERY ); + Reference< XForm > xParentAsForm; if ( xCheck.is() ) xParentAsForm.set(xCheck->getParent(), css::uno::UNO_QUERY); + Reference< XGridColumnFactory > xParentAsGrid; if ( xCheck.is() ) xParentAsGrid.set(xCheck->getParent(), css::uno::UNO_QUERY); + + while ( ( xParentAsForm.is() || xParentAsGrid.is() ) && xCheck.is() ) + { + xCheck.set(xCheck->getParent(), css::uno::UNO_QUERY); + xParentAsForm.set(xCheck.is() ? xCheck->getParent() : Reference< XForm >(), css::uno::UNO_QUERY); + xParentAsGrid.set(xCheck.is() ? xCheck->getParent() : Reference< XGridColumnFactory >(), css::uno::UNO_QUERY); + } + Reference< XInterface > xFormsCollection( xCheck.is() ? xCheck->getParent() : Reference< XInterface >() ); + + // now iterate through the sheets + Reference< XIndexAccess > xSheets( m_xDocument->getSheets(), UNO_QUERY ); + if ( xSheets.is() && xFormsCollection.is() ) + { + for ( sal_Int32 i = 0; i < xSheets->getCount(); ++i ) + { + Reference< XDrawPageSupplier > xSuppPage( xSheets->getByIndex( i ), UNO_QUERY_THROW ); + Reference< XFormsSupplier > xSuppForms( xSuppPage->getDrawPage(), UNO_QUERY_THROW ); + + if ( xSuppForms->getForms() == xFormsCollection ) + { // found it + nSheetIndex = static_cast<sal_Int16>(i); + _out_rxSheet.set( xSuppPage, UNO_QUERY_THROW ); + break; + } + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + + return nSheetIndex; + } + + + bool CellBindingHelper::convertStringAddress( const OUString& _rAddressDescription, CellAddress& /* [out] */ _rAddress ) const + { + Any aAddress; + return doConvertAddressRepresentations( + PROPERTY_UI_REPRESENTATION, + Any( _rAddressDescription ), + PROPERTY_ADDRESS, + aAddress, + false + ) + && ( aAddress >>= _rAddress ); + } + + + bool CellBindingHelper::doConvertAddressRepresentations( const OUString& _rInputProperty, const Any& _rInputValue, + const OUString& _rOutputProperty, Any& _rOutputValue, bool _bIsRange ) const + { + bool bSuccess = false; + + Reference< XPropertySet > xConverter( + createDocumentDependentInstance( + _bIsRange ? SERVICE_RANGEADDRESS_CONVERSION : SERVICE_ADDRESS_CONVERSION, + OUString(), + Any() + ), + UNO_QUERY + ); + OSL_ENSURE( xConverter.is(), "CellBindingHelper::doConvertAddressRepresentations: could not get a converter service!" ); + if ( xConverter.is() ) + { + try + { + Reference< XSpreadsheet > xSheet; + xConverter->setPropertyValue( PROPERTY_REFERENCE_SHEET, Any( static_cast<sal_Int32>(getControlSheetIndex( xSheet )) ) ); + xConverter->setPropertyValue( _rInputProperty, _rInputValue ); + _rOutputValue = xConverter->getPropertyValue( _rOutputProperty ); + bSuccess = true; + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "CellBindingHelper::doConvertAddressRepresentations" ); + } + } + + return bSuccess; + } + + + bool CellBindingHelper::convertStringAddress( const OUString& _rAddressDescription, + CellRangeAddress& /* [out] */ _rAddress ) const + { + Any aAddress; + return doConvertAddressRepresentations( + PROPERTY_UI_REPRESENTATION, + Any( _rAddressDescription ), + PROPERTY_ADDRESS, + aAddress, + true + ) + && ( aAddress >>= _rAddress ); + } + + + Reference< XValueBinding > CellBindingHelper::createCellBindingFromAddress( const CellAddress& _rAddress, bool _bSupportIntegerExchange ) const + { + Reference< XValueBinding > xBinding( createDocumentDependentInstance( + _bSupportIntegerExchange ? SERVICE_SHEET_CELL_INT_BINDING : SERVICE_SHEET_CELL_BINDING, + PROPERTY_BOUND_CELL, + Any( _rAddress ) + ), UNO_QUERY ); + + return xBinding; + } + + + Reference< XValueBinding > CellBindingHelper::createCellBindingFromStringAddress( const OUString& _rAddress, bool _bSupportIntegerExchange ) const + { + Reference< XValueBinding > xBinding; + if ( !m_xDocument.is() ) + // very bad ... + return xBinding; + + // get the UNO representation of the address + CellAddress aAddress; + if ( _rAddress.isEmpty() || !convertStringAddress( _rAddress, aAddress ) ) + return xBinding; + + return createCellBindingFromAddress( aAddress, _bSupportIntegerExchange ); + } + + + Reference< XListEntrySource > CellBindingHelper::createCellListSourceFromStringAddress( const OUString& _rAddress ) const + { + Reference< XListEntrySource > xSource; + + CellRangeAddress aRangeAddress; + if ( _rAddress.isEmpty() || !convertStringAddress( _rAddress, aRangeAddress ) ) + return xSource; + + // create a range object for this address + xSource.set(createDocumentDependentInstance( + SERVICE_SHEET_CELLRANGE_LISTSOURCE, + PROPERTY_LIST_CELL_RANGE, + Any( aRangeAddress ) + ), css::uno::UNO_QUERY); + + return xSource; + } + + + Reference< XInterface > CellBindingHelper::createDocumentDependentInstance( const OUString& _rService, const OUString& _rArgumentName, + const Any& _rArgumentValue ) const + { + Reference< XInterface > xReturn; + + Reference< XMultiServiceFactory > xDocumentFactory( m_xDocument, UNO_QUERY ); + OSL_ENSURE( xDocumentFactory.is(), "CellBindingHelper::createDocumentDependentInstance: no document service factory!" ); + if ( xDocumentFactory.is() ) + { + try + { + if ( !_rArgumentName.isEmpty() ) + { + Sequence aArgs{ Any(NamedValue(_rArgumentName, _rArgumentValue)) }; + xReturn = xDocumentFactory->createInstanceWithArguments( _rService, aArgs ); + } + else + { + xReturn = xDocumentFactory->createInstance( _rService ); + } + } + catch ( const Exception& ) + { + OSL_FAIL( "CellBindingHelper::createDocumentDependentInstance: could not create the binding at the document!" ); + } + } + return xReturn; + } + + + bool CellBindingHelper::getAddressFromCellBinding( + const Reference< XValueBinding >& _rxBinding, CellAddress& _rAddress ) const + { + OSL_PRECOND( !_rxBinding.is() || isCellBinding( _rxBinding ), "CellBindingHelper::getAddressFromCellBinding: this is no cell binding!" ); + + bool bReturn = false; + if ( !m_xDocument.is() ) + // very bad ... + return bReturn; + + try + { + Reference< XPropertySet > xBindingProps( _rxBinding, UNO_QUERY ); + OSL_ENSURE( xBindingProps.is() || !_rxBinding.is(), "CellBindingHelper::getAddressFromCellBinding: no property set for the binding!" ); + if ( xBindingProps.is() ) + { + bReturn = ( xBindingProps->getPropertyValue( PROPERTY_BOUND_CELL ) >>= _rAddress ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "CellBindingHelper::getAddressFromCellBinding" ); + } + + return bReturn; + } + + + OUString CellBindingHelper::getStringAddressFromCellBinding( const Reference< XValueBinding >& _rxBinding ) const + { + CellAddress aAddress; + OUString sAddress; + if ( getAddressFromCellBinding( _rxBinding, aAddress ) ) + { + Any aStringAddress; + doConvertAddressRepresentations( PROPERTY_ADDRESS, Any( aAddress ), + PROPERTY_UI_REPRESENTATION, aStringAddress, false ); + + aStringAddress >>= sAddress; + } + + return sAddress; + } + + + OUString CellBindingHelper::getStringAddressFromCellListSource( const Reference< XListEntrySource >& _rxSource ) const + { + OSL_PRECOND( !_rxSource.is() || isCellRangeListSource( _rxSource ), "CellBindingHelper::getStringAddressFromCellListSource: this is no cell list source!" ); + + OUString sAddress; + if ( !m_xDocument.is() ) + // very bad ... + return sAddress; + + try + { + Reference< XPropertySet > xSourceProps( _rxSource, UNO_QUERY ); + OSL_ENSURE( xSourceProps.is() || !_rxSource.is(), "CellBindingHelper::getStringAddressFromCellListSource: no property set for the list source!" ); + if ( xSourceProps.is() ) + { + CellRangeAddress aRangeAddress; + xSourceProps->getPropertyValue( PROPERTY_LIST_CELL_RANGE ) >>= aRangeAddress; + + Any aStringAddress; + doConvertAddressRepresentations( PROPERTY_ADDRESS, Any( aRangeAddress ), + PROPERTY_UI_REPRESENTATION, aStringAddress, true ); + aStringAddress >>= sAddress; + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "CellBindingHelper::getStringAddressFromCellListSource" ); + } + + return sAddress; + } + + + bool CellBindingHelper::isSpreadsheetDocumentWhichSupplies( const OUString& _rService ) const + { + bool bYesItIs = false; + + Reference< XServiceInfo > xSI( m_xDocument, UNO_QUERY ); + if ( xSI.is() && xSI->supportsService( SERVICE_SPREADSHEET_DOCUMENT ) ) + { + Reference< XMultiServiceFactory > xDocumentFactory( m_xDocument, UNO_QUERY ); + OSL_ENSURE( xDocumentFactory.is(), "CellBindingHelper::isSpreadsheetDocumentWhichSupplies: spreadsheet document, but no factory?" ); + + if ( xDocumentFactory.is() ) + { + const Sequence<OUString> aAvailableServices = xDocumentFactory->getAvailableServiceNames( ); + + bYesItIs = std::any_of( + aAvailableServices.begin(), + aAvailableServices.end(), + StringCompare( _rService ) + ); + } + } + + return bYesItIs; + } + + + bool CellBindingHelper::isListCellRangeAllowed( ) const + { + bool bAllow( false ); + + Reference< XListEntrySink > xSink( m_xControlModel, UNO_QUERY ); + if ( xSink.is() ) + { + bAllow = isSpreadsheetDocumentWhichSupplies( SERVICE_SHEET_CELLRANGE_LISTSOURCE ); + } + + return bAllow; + } + + + bool CellBindingHelper::isCellIntegerBindingAllowed( ) const + { + bool bAllow( true ); + + // first, we only offer this for controls which allow bindings in general + Reference< XBindableValue > xBindable( m_xControlModel, UNO_QUERY ); + if ( !xBindable.is() ) + bAllow = false; + + // then, we must live in a spreadsheet document which can provide the special + // service needed for exchanging integer values + if ( bAllow ) + bAllow = isSpreadsheetDocumentWhichSupplies( SERVICE_SHEET_CELL_INT_BINDING ); + + // then, we only offer this for list boxes + if ( bAllow ) + { + try + { + sal_Int16 nClassId = FormComponentType::CONTROL; + m_xControlModel->getPropertyValue( PROPERTY_CLASSID ) >>= nClassId; + if ( FormComponentType::LISTBOX != nClassId ) + bAllow = false; + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "CellBindingHelper::isCellIntegerBindingAllowed" ); + // are there really control models which survive isCellBindingAllowed, but don't have a ClassId + // property? + bAllow = false; + } + } + + return bAllow; + } + + + bool CellBindingHelper::isCellBindingAllowed( ) const + { + bool bAllow( false ); + + Reference< XBindableValue > xBindable( m_xControlModel, UNO_QUERY ); + if ( xBindable.is() ) + { + // the control can potentially be bound to an external value + // Does it live within a Calc document, and is able to supply CellBindings? + bAllow = isSpreadsheetDocumentWhichSupplies( SERVICE_SHEET_CELL_BINDING ); + } + + // disallow for some types + // TODO: shouldn't the XBindableValue supply a list of supported types, and we can distinguish + // using this list? The current behavior below is somewhat hackish... + if ( bAllow ) + { + try + { + sal_Int16 nClassId = FormComponentType::CONTROL; + m_xControlModel->getPropertyValue( PROPERTY_CLASSID ) >>= nClassId; + if ( ( FormComponentType::DATEFIELD == nClassId ) || ( FormComponentType::TIMEFIELD == nClassId ) ) + bAllow = false; + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "CellBindingHelper::isCellBindingAllowed" ); + bAllow = false; + } + } + return bAllow; + } + + + bool CellBindingHelper::isCellBinding( const Reference< XValueBinding >& _rxBinding ) + { + return doesComponentSupport( _rxBinding, SERVICE_SHEET_CELL_BINDING ); + } + + + bool CellBindingHelper::isCellIntegerBinding( const Reference< XValueBinding >& _rxBinding ) + { + return doesComponentSupport( _rxBinding, SERVICE_SHEET_CELL_INT_BINDING ); + } + + + bool CellBindingHelper::isCellRangeListSource( const Reference< XListEntrySource >& _rxSource ) + { + return doesComponentSupport( _rxSource, SERVICE_SHEET_CELLRANGE_LISTSOURCE ); + } + + + bool CellBindingHelper::doesComponentSupport( const Reference< XInterface >& _rxComponent, const OUString& _rService ) + { + Reference< XServiceInfo > xSI( _rxComponent, UNO_QUERY ); + bool bDoes = xSI.is() && xSI->supportsService( _rService ); + return bDoes; + } + + + Reference< XValueBinding > CellBindingHelper::getCurrentBinding( ) const + { + Reference< XValueBinding > xBinding; + Reference< XBindableValue > xBindable( m_xControlModel, UNO_QUERY ); + if ( xBindable.is() ) + xBinding = xBindable->getValueBinding(); + return xBinding; + } + + + Reference< XListEntrySource > CellBindingHelper::getCurrentListSource( ) const + { + Reference< XListEntrySource > xSource; + Reference< XListEntrySink > xSink( m_xControlModel, UNO_QUERY ); + if ( xSink.is() ) + xSource = xSink->getListEntrySource(); + return xSource; + } + + + void CellBindingHelper::setBinding( const Reference< XValueBinding >& _rxBinding ) + { + Reference< XBindableValue > xBindable( m_xControlModel, UNO_QUERY ); + OSL_PRECOND( xBindable.is(), "CellBindingHelper::setBinding: the object is not bindable!" ); + if ( xBindable.is() ) + xBindable->setValueBinding( _rxBinding ); + } + + + void CellBindingHelper::setListSource( const Reference< XListEntrySource >& _rxSource ) + { + Reference< XListEntrySink > xSink( m_xControlModel, UNO_QUERY ); + OSL_PRECOND( xSink.is(), "CellBindingHelper::setListSource: the object is no list entry sink!" ); + if ( xSink.is() ) + xSink->setListEntrySource( _rxSource ); + } + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/cellbindinghelper.hxx b/extensions/source/propctrlr/cellbindinghelper.hxx new file mode 100644 index 0000000000..a2c85cd65b --- /dev/null +++ b/extensions/source/propctrlr/cellbindinghelper.hxx @@ -0,0 +1,272 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/sheet/XSpreadsheetDocument.hpp> +#include <com/sun/star/table/CellAddress.hpp> +#include <com/sun/star/table/CellRangeAddress.hpp> +#include <com/sun/star/form/binding/XValueBinding.hpp> +#include <com/sun/star/form/binding/XListEntrySource.hpp> +#include <com/sun/star/sheet/XSpreadsheet.hpp> + + +namespace pcr +{ + + /** encapsulates functionality related to binding a form control to a spreadsheet cell + */ + class CellBindingHelper final + { + css::uno::Reference< css::beans::XPropertySet > + m_xControlModel; // the model we work for + css::uno::Reference< css::sheet::XSpreadsheetDocument > + m_xDocument; // the document where the model lives + + public: + /** ctor + @param _rxControlModel + the control model which is or will be bound + */ + CellBindingHelper( + const css::uno::Reference< css::beans::XPropertySet >& _rxControlModel, + const css::uno::Reference< css::frame::XModel >& _rxContextDocument + ); + + /** determines whether the given model is a spreadsheet document model + + <p>If this method returns <FALSE/>, you cannot instantiate a CellBindingHelper with + the document, since then no of its functionality will be available.</p> + */ + static bool isSpreadsheetDocument( + const css::uno::Reference< css::frame::XModel >& _rxContextDocument + ); + + /** gets a cell binding for the given address + @precond + isCellBindingAllowed returns <TRUE/> + */ + css::uno::Reference< css::form::binding::XValueBinding > + createCellBindingFromStringAddress( + const OUString& _rAddress, + bool _bSupportIntegerExchange + ) const; + + /** creates a cell binding (supporting integer exchange, if requested) for + the given address object + */ + css::uno::Reference< css::form::binding::XValueBinding > + createCellBindingFromAddress( + const css::table::CellAddress& _rAddress, + bool _bSupportIntegerExchange + ) const; + + /** gets a cell range list source binding for the given address + */ + css::uno::Reference< css::form::binding::XListEntrySource > + createCellListSourceFromStringAddress( const OUString& _rAddress ) const; + + /** creates a string representation for the given value binding's address + + <p>If the sheet of the bound cell is the same as the sheet which our control belongs + to, then the sheet name is omitted in the resulting string representation.</p> + + @precond + The binding is a valid cell binding, or <NULL/> + @see isCellBinding + */ + OUString getStringAddressFromCellBinding( + const css::uno::Reference< css::form::binding::XValueBinding >& _rxBinding + ) const; + + /** creates an address object for the given value binding's address + + @precond + The binding is a valid cell binding, or <NULL/> + @return + <FALSE/> if and only if an error occurred and no valid address could be obtained + @see isCellBinding + */ + bool getAddressFromCellBinding( + const css::uno::Reference< css::form::binding::XValueBinding >& _rxBinding, + css::table::CellAddress& _rAddress + ) const; + + /** creates a string representation for the given list source's range address + + <p>If the sheet of the cell range which acts as list source is the same as the + sheet which our control belongs to, then the sheet name is omitted in the + resulting string representation.</p> + + @precond + The object is a valid cell range list source, or <NULL/> + @see isCellRangeListSource + */ + OUString getStringAddressFromCellListSource( + const css::uno::Reference< css::form::binding::XListEntrySource >& _rxSource + ) const; + + /** returns the current binding of our control model, if any. + */ + css::uno::Reference< css::form::binding::XValueBinding > + getCurrentBinding( ) const; + + /** returns the current external list source of the control model, if any + */ + css::uno::Reference< css::form::binding::XListEntrySource > + getCurrentListSource( ) const; + + /** sets a new binding for our control model + @precond + the control model is bindable (which is implied by <member>isCellBindingAllowed</member> + returning <TRUE/>) + */ + void setBinding( + const css::uno::Reference< css::form::binding::XValueBinding >& _rxBinding + ); + + /** sets a list source for our control model + @precond + the control model is a list sink (which is implied by <member>isListCellRangeAllowed</member> + returning <TRUE/>) + */ + void setListSource( + const css::uno::Reference< css::form::binding::XListEntrySource >& _rxSource + ); + + /** checks whether it's possible to bind the control model to a spreadsheet cell + */ + bool isCellBindingAllowed( ) const; + + /** checks whether it's possible to bind the control model to a spreadsheet cell, + with exchanging integer values + */ + bool isCellIntegerBindingAllowed( ) const; + + /** checks whether it's possible to bind the control model to range of spreadsheet cells + supplying the list entries + */ + bool isListCellRangeAllowed( ) const; + + /** checks whether a given binding is a spreadsheet cell binding + */ + static bool isCellBinding( + const css::uno::Reference< css::form::binding::XValueBinding >& _rxBinding + ); + + /** checks whether a given binding is a spreadsheet cell binding, exchanging + integer values + */ + static bool isCellIntegerBinding( + const css::uno::Reference< css::form::binding::XValueBinding >& _rxBinding + ); + + /** checks whether a given list source is a spreadsheet cell list source + */ + static bool isCellRangeListSource( + const css::uno::Reference< css::form::binding::XListEntrySource >& _rxSource + ); + + /** retrieves the index of the sheet which our control belongs to + @return the index of the sheet which our control belongs to or -1, if an error occurred + */ + sal_Int16 getControlSheetIndex( + css::uno::Reference< css::sheet::XSpreadsheet >& _out_rxSheet + ) const; + + private: + /** creates an address object from a string representation of a cell address + */ + bool convertStringAddress( + const OUString& _rAddressDescription, + css::table::CellAddress& /* [out] */ _rAddress + ) const; + + /** creates an address range object from a string representation of a cell range address + */ + bool convertStringAddress( + const OUString& _rAddressDescription, + css::table::CellRangeAddress& /* [out] */ _rAddress + ) const; + + /** determines if our document is a spreadsheet document, *and* can supply + the given service + */ + bool isSpreadsheetDocumentWhichSupplies( const OUString& _rService ) const; + + /** checks whether a given component supports a given service + */ + static bool doesComponentSupport( + const css::uno::Reference< css::uno::XInterface >& _rxComponent, + const OUString& _rService + ); + + /** uses the document (it's factory interface, respectively) to create a component instance + @param _rService + the service name + @param _rArgumentName + the name of the single argument to pass during creation. May be empty, in this case + no arguments are passed + @param _rArgumentValue + the value of the instantiation argument. Not evaluated if <arg>_rArgumentName</arg> + is empty. + */ + css::uno::Reference< css::uno::XInterface > + createDocumentDependentInstance( + const OUString& _rService, + const OUString& _rArgumentName, + const css::uno::Any& _rArgumentValue + ) const; + + /** converts an address representation into another one + + @param _rInputProperty + the input property name for the conversion service + @param _rInputValue + the input property value for the conversion service + @param _rOutputProperty + the output property name for the conversion service + @param _rOutputValue + the output property value for the conversion service + @param _bIsRange + if <TRUE/>, the RangeAddressConversion service will be used, else + the AddressConversion service + + @return + <TRUE/> if any only if the conversion was successful + + @see css::table::CellAddressConversion + @see css::table::CellRangeAddressConversion + */ + bool doConvertAddressRepresentations( + const OUString& _rInputProperty, + const css::uno::Any& _rInputValue, + const OUString& _rOutputProperty, + css::uno::Any& _rOutputValue, + bool _bIsRange + ) const; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/commoncontrol.cxx b/extensions/source/propctrlr/commoncontrol.cxx new file mode 100644 index 0000000000..4f44f3e568 --- /dev/null +++ b/extensions/source/propctrlr/commoncontrol.cxx @@ -0,0 +1,134 @@ +/* -*- 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 "commoncontrol.hxx" +#include <comphelper/diagnose_ex.hxx> + + +namespace pcr +{ + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::inspection::XPropertyControlContext; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::inspection::XPropertyControl; + + CommonBehaviourControlHelper::CommonBehaviourControlHelper( sal_Int16 _nControlType, XPropertyControl& _rAntiImpl ) + :m_nControlType( _nControlType ) + ,m_rAntiImpl( _rAntiImpl ) + ,m_bModified( false ) + { + } + + + CommonBehaviourControlHelper::~CommonBehaviourControlHelper() + { + } + + void CommonBehaviourControlHelper::setControlContext( const Reference< XPropertyControlContext >& _controlcontext ) + { + m_xContext = _controlcontext; + } + + void CommonBehaviourControlHelper::notifyModifiedValue( ) + { + if ( isModified() && m_xContext.is() ) + { + try + { + m_xContext->valueChanged( &m_rAntiImpl ); + m_bModified = false; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + } + + void CommonBehaviourControlHelper::editChanged() + { + setModified(); + } + + IMPL_LINK_NOARG( CommonBehaviourControlHelper, EditModifiedHdl, weld::Entry&, void ) + { + editChanged(); + } + + IMPL_LINK_NOARG( CommonBehaviourControlHelper, ModifiedHdl, weld::ComboBox&, void ) + { + setModified(); + // notify as soon as the Data source is changed, don't wait until we lose focus + // because the Content dropdown cannot be populated after it is popped up + // and going from Data source direct to Content may give focus-lost to + // Content after the popup attempt is made + notifyModifiedValue(); + } + + IMPL_LINK_NOARG( CommonBehaviourControlHelper, MetricModifiedHdl, weld::MetricSpinButton&, void ) + { + setModified(); + } + + IMPL_LINK_NOARG( CommonBehaviourControlHelper, FormattedModifiedHdl, weld::FormattedSpinButton&, void ) + { + setModified(); + } + + IMPL_LINK_NOARG( CommonBehaviourControlHelper, TimeModifiedHdl, weld::FormattedSpinButton&, void ) + { + setModified(); + } + + IMPL_LINK_NOARG( CommonBehaviourControlHelper, DateModifiedHdl, SvtCalendarBox&, void ) + { + setModified(); + } + + IMPL_LINK_NOARG( CommonBehaviourControlHelper, ColorModifiedHdl, ColorListBox&, void ) + { + setModified(); + } + + IMPL_LINK_NOARG( CommonBehaviourControlHelper, GetFocusHdl, weld::Widget&, void ) + { + try + { + if ( m_xContext.is() ) + m_xContext->focusGained( &m_rAntiImpl ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + IMPL_LINK_NOARG( CommonBehaviourControlHelper, LoseFocusHdl, weld::Widget&, void ) + { + // TODO/UNOize: should this be outside the default control's implementations? If somebody + // has an own control implementation, which does *not* do this - would this be allowed? + // If not, then we must move this logic out of here. + notifyModifiedValue(); + } + +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/commoncontrol.hxx b/extensions/source/propctrlr/commoncontrol.hxx new file mode 100644 index 0000000000..746f2f56f6 --- /dev/null +++ b/extensions/source/propctrlr/commoncontrol.hxx @@ -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 . + */ + +#pragma once + +#include <com/sun/star/inspection/XPropertyControl.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> +#include <tools/link.hxx> +#include <vcl/weld.hxx> +#include <vcl/weldutils.hxx> + +class NotifyEvent; +class ColorListBox; +class SvtCalendarBox; + +namespace pcr +{ + + //= CommonBehaviourControlHelper + + /** A helper class for implementing the <type scope="css::inspection">XPropertyControl</type> + or one of its derived interfaces. + + This class is used as a base class the CommonBehaviourControl template. + */ + class CommonBehaviourControlHelper + { + private: + sal_Int16 m_nControlType; + css::uno::Reference< css::inspection::XPropertyControlContext > + m_xContext; + css::inspection::XPropertyControl& + m_rAntiImpl; + bool m_bModified; + + public: + /** creates the instance + @param nControlType + the type of the control - one of the <type scope="css::inspection">PropertyControlType</type> + constants + @param pAntiImpl + Reference to the instance as whose "impl-class" we act i.e. the CommonBehaviourControl<> template, + which is why we hold it without acquiring it/ + */ + CommonBehaviourControlHelper( + sal_Int16 nControlType, + css::inspection::XPropertyControl& rAntiImpl); + + virtual ~CommonBehaviourControlHelper(); + + virtual void setModified() { m_bModified = true; } + + virtual void editChanged(); + + // XPropertyControl + /// @throws css::uno::RuntimeException + ::sal_Int16 getControlType() const { return m_nControlType; } + /// @throws css::uno::RuntimeException + const css::uno::Reference< css::inspection::XPropertyControlContext >& getControlContext() const { return m_xContext; } + /// @throws css::uno::RuntimeException + void setControlContext( const css::uno::Reference< css::inspection::XPropertyControlContext >& controlcontext ); + /// @throws css::uno::RuntimeException + bool isModified( ) const { return m_bModified; } + /// @throws css::uno::RuntimeException + void notifyModifiedValue( ); + + virtual weld::Widget* getWidget() = 0; + + /// may be used by derived classes, they forward the event to the PropCtrListener + DECL_LINK( ModifiedHdl, weld::ComboBox&, void ); + DECL_LINK( ColorModifiedHdl, ColorListBox&, void ); + DECL_LINK( EditModifiedHdl, weld::Entry&, void ); + DECL_LINK( MetricModifiedHdl, weld::MetricSpinButton&, void ); + DECL_LINK( FormattedModifiedHdl, weld::FormattedSpinButton&, void ); + DECL_LINK( TimeModifiedHdl, weld::FormattedSpinButton&, void ); + DECL_LINK( DateModifiedHdl, SvtCalendarBox&, void ); + DECL_LINK( GetFocusHdl, weld::Widget&, void ); + DECL_LINK( LoseFocusHdl, weld::Widget&, void ); + }; + + + //= CommonBehaviourControl + + /** implements a base class for <type scope="css::inspection">XPropertyControl</type> + implementations + + @param TControlInterface + an interface class which is derived from (or identical to) <type scope="css::inspection">XPropertyControl</type> + @param TControlWindow + a class which is derived from weld::Widget + */ + template < class TControlInterface, class TControlWindow > + class CommonBehaviourControl :public ::cppu::BaseMutex + ,public ::cppu::WeakComponentImplHelper< TControlInterface > + ,public CommonBehaviourControlHelper + { + protected: + typedef ::cppu::WeakComponentImplHelper< TControlInterface > ComponentBaseClass; + + inline CommonBehaviourControl(sal_Int16 nControlType, + std::unique_ptr<weld::Builder> xBuilder, + std::unique_ptr<TControlWindow> xWidget, + bool bReadOnly); + + virtual ~CommonBehaviourControl() override + { + clear_widgetry(); + } + + // XPropertyControl - delegated to ->m_aImplControl + virtual ::sal_Int16 SAL_CALL getControlType() override + { return CommonBehaviourControlHelper::getControlType(); } + virtual css::uno::Reference< css::inspection::XPropertyControlContext > SAL_CALL getControlContext() override + { return CommonBehaviourControlHelper::getControlContext(); } + virtual void SAL_CALL setControlContext( const css::uno::Reference< css::inspection::XPropertyControlContext >& controlcontext ) override + { CommonBehaviourControlHelper::setControlContext( controlcontext ); } + virtual css::uno::Reference< css::awt::XWindow > SAL_CALL getControlWindow() override + { return new weld::TransportAsXWindow(getWidget()); } + virtual sal_Bool SAL_CALL isModified( ) override + { return CommonBehaviourControlHelper::isModified(); } + virtual void SAL_CALL notifyModifiedValue( ) override + { CommonBehaviourControlHelper::notifyModifiedValue(); } + + void clear_widgetry() + { + if (!m_xControlWindow) + return; + weld::Widget* pWidget = getWidget(); + std::unique_ptr<weld::Container> xParent(pWidget->weld_parent()); + xParent->move(pWidget, nullptr); + m_xControlWindow.reset(); + m_xBuilder.reset(); + } + + // XComponent + virtual void SAL_CALL disposing() override + { + clear_widgetry(); + } + + TControlWindow* getTypedControlWindow() + { return m_xControlWindow.get(); } + const TControlWindow* getTypedControlWindow() const + { return m_xControlWindow.get(); } + + virtual void SetModifyHandler() + { + m_xControlWindow->connect_focus_in( LINK( this, CommonBehaviourControlHelper, GetFocusHdl ) ); + m_xControlWindow->connect_focus_out( LINK( this, CommonBehaviourControlHelper, LoseFocusHdl ) ); + } + + /** checks whether the instance is already disposed + @throws DisposedException + if the instance is already disposed + */ + inline void impl_checkDisposed_throw(); + protected: + std::unique_ptr<weld::Builder> m_xBuilder; + private: + std::unique_ptr<TControlWindow> m_xControlWindow; + }; + + //= CommonBehaviourControl - implementation + template< class TControlInterface, class TControlWindow > + inline CommonBehaviourControl< TControlInterface, TControlWindow >::CommonBehaviourControl(sal_Int16 nControlType, + std::unique_ptr<weld::Builder> xBuilder, + std::unique_ptr<TControlWindow> xWidget, + bool bReadOnly) + : ComponentBaseClass( m_aMutex ) + , CommonBehaviourControlHelper( nControlType, *this ) + , m_xBuilder(std::move(xBuilder)) + , m_xControlWindow(std::move(xWidget)) + { + if (bReadOnly) + { + // disable widget by default, entries will override to enable the widget but set it non-editable + m_xControlWindow->set_sensitive(false); + } + } + + template< class TControlInterface, class TControlWindow > + inline void CommonBehaviourControl< TControlInterface, TControlWindow >::impl_checkDisposed_throw() + { + if ( ComponentBaseClass::rBHelper.bDisposed ) + throw css::lang::DisposedException( OUString(), *this ); + } + +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/composeduiupdate.cxx b/extensions/source/propctrlr/composeduiupdate.cxx new file mode 100644 index 0000000000..247dd4ab3e --- /dev/null +++ b/extensions/source/propctrlr/composeduiupdate.cxx @@ -0,0 +1,786 @@ +/* -*- 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 "composeduiupdate.hxx" +#include "pcrcommon.hxx" + +#include <com/sun/star/inspection/XObjectInspectorUI.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/NullPointerException.hpp> +#include <com/sun/star/inspection/PropertyLineElement.hpp> +#include <osl/diagnose.h> +#include <osl/mutex.hxx> +#include <rtl/ref.hxx> +#include <cppuhelper/implbase.hxx> + +#include <algorithm> +#include <map> +#include <set> + + +namespace pcr +{ + + + using ::com::sun::star::lang::DisposedException; + using ::com::sun::star::lang::NullPointerException; + using ::com::sun::star::inspection::XPropertyHandler; + using ::com::sun::star::uno::Reference; + using ::com::sun::star::inspection::XObjectInspectorUI; + using ::com::sun::star::inspection::XPropertyControl; + using ::com::sun::star::inspection::XPropertyControlObserver; + + namespace PropertyLineElement = ::com::sun::star::inspection::PropertyLineElement; + + namespace + { + struct HandlerLess + { + bool operator()( const Reference< XPropertyHandler >& lhs, const Reference< XPropertyHandler >& rhs) const + { + return lhs.get() < rhs.get(); + } + }; + + + typedef std::set< OUString > StringBag; + typedef std::map< sal_Int16, StringBag > MapIntToStringBag; + } + + + // callbacks for CachedInspectorUI + + typedef void (ComposedPropertyUIUpdate::*FNotifySingleUIChange)(); + + typedef ::cppu::WeakImplHelper < css::inspection::XObjectInspectorUI + > CachedInspectorUI_Base; + + namespace { + + struct CachedInspectorUI : public CachedInspectorUI_Base + { + private: + ::osl::Mutex m_aMutex; + bool m_bDisposed; + ComposedPropertyUIUpdate& + m_rMaster; + FNotifySingleUIChange m_pUIChangeNotification; + + // enablePropertyUI cache + StringBag aEnabledProperties; + StringBag aDisabledProperties; + + // show/hidePropertyUI cache + StringBag aShownProperties; + StringBag aHiddenProperties; + + // rebuildPropertyUI cache + StringBag aRebuiltProperties; + + // showCategory cache + StringBag aShownCategories; + StringBag aHiddenCategories; + + // enablePropertyUIElements cache + MapIntToStringBag aEnabledElements; + MapIntToStringBag aDisabledElements; + + public: + typedef StringBag& (CachedInspectorUI::*FGetStringBag)(); + + // enablePropertyUI cache + StringBag& getEnabledProperties() { return aEnabledProperties; } + StringBag& getDisabledProperties() { return aDisabledProperties; } + + // show/hidePropertyUI cache + StringBag& getShownProperties() { return aShownProperties; } + StringBag& getHiddenProperties() { return aHiddenProperties; } + + // rebuildPropertyUI cache + StringBag& getRebuiltProperties() { return aRebuiltProperties; } + + // showCategory cache + StringBag& getShownCategories() { return aShownCategories; } + StringBag& getHiddenCategories() { return aHiddenCategories; } + + // enablePropertyUIElements + StringBag& getEnabledInputControls() { return aEnabledElements[ PropertyLineElement::InputControl ]; } + StringBag& getDisabledInputControls() { return aDisabledElements[ PropertyLineElement::InputControl ]; } + StringBag& getEnabledPrimaryButtons() { return aEnabledElements[ PropertyLineElement::PrimaryButton ]; } + StringBag& getDisabledPrimaryButtons() { return aDisabledElements[ PropertyLineElement::PrimaryButton ]; } + StringBag& getEnabledSecondaryButtons() { return aEnabledElements[ PropertyLineElement::SecondaryButton ]; } + StringBag& getDisabledSecondaryButtons() { return aDisabledElements[ PropertyLineElement::SecondaryButton ]; } + + public: + CachedInspectorUI( ComposedPropertyUIUpdate& _rMaster, FNotifySingleUIChange _pUIChangeNotification ); + CachedInspectorUI(const CachedInspectorUI&) = delete; + CachedInspectorUI& operator=(const CachedInspectorUI&) = delete; + + /// disposes the instance + void dispose(); + + // XObjectInspectorUI overridables + virtual void SAL_CALL enablePropertyUI( const OUString& _rPropertyName, sal_Bool _bEnable ) override; + virtual void SAL_CALL enablePropertyUIElements( const OUString& _rPropertyName, ::sal_Int16 _nElements, sal_Bool _bEnable ) override; + virtual void SAL_CALL rebuildPropertyUI( const OUString& _rPropertyName ) override; + virtual void SAL_CALL showPropertyUI( const OUString& _rPropertyName ) override; + virtual void SAL_CALL hidePropertyUI( const OUString& _rPropertyName ) override; + virtual void SAL_CALL showCategory( const OUString& _rCategory, sal_Bool _bShow ) override; + virtual Reference< XPropertyControl > SAL_CALL getPropertyControl( const OUString& _rPropertyName ) override; + virtual void SAL_CALL registerControlObserver( const Reference< XPropertyControlObserver >& Observer ) override; + virtual void SAL_CALL revokeControlObserver( const Reference< XPropertyControlObserver >& Observer ) override; + virtual void SAL_CALL setHelpSectionText( const OUString& HelpText ) override; + + protected: + virtual ~CachedInspectorUI() override; + + /// throws an exception if the component is already disposed + void checkDisposed() const; + + private: + void impl_markElementEnabledOrDisabled( const OUString& _rPropertyName, sal_Int16 _nElementIdOrZero, bool _bEnable ); + + /** calls <member>m_pUIChangeNotification</member> at <member>m_rMaster</member> + */ + void impl_notifySingleUIChange() const; + + private: + class MethodGuard; + friend class MethodGuard; + class MethodGuard : public ::osl::MutexGuard + { + public: + explicit MethodGuard( CachedInspectorUI& rInstance ) + : ::osl::MutexGuard( rInstance.m_aMutex ) + { + rInstance.checkDisposed(); + } + }; + }; + + } + + CachedInspectorUI::CachedInspectorUI( ComposedPropertyUIUpdate& _rMaster, FNotifySingleUIChange _pUIChangeNotification ) + :m_bDisposed( false ) + ,m_rMaster( _rMaster ) + ,m_pUIChangeNotification( _pUIChangeNotification ) + { + } + + + CachedInspectorUI::~CachedInspectorUI() + { + } + + + void CachedInspectorUI::dispose() + { + ::osl::MutexGuard aGuard( m_aMutex ); + m_bDisposed = true; + + clearContainer( aEnabledProperties ); + clearContainer( aDisabledProperties ); + clearContainer( aRebuiltProperties ); + clearContainer( aShownProperties ); + clearContainer( aHiddenProperties ); + clearContainer( aShownCategories ); + clearContainer( aHiddenCategories ); + clearContainer( aEnabledElements ); + clearContainer( aDisabledElements ); + } + + + void CachedInspectorUI::checkDisposed() const + { + if (m_bDisposed) + throw DisposedException(); + } + + + namespace + { + void lcl_markStringKeyPositiveOrNegative( const OUString& _rKeyName, StringBag& _rPositives, StringBag& _rNegatives, bool _bMarkPositive ) + { + if ( _bMarkPositive ) + { + _rPositives.insert( _rKeyName ); + // if the same key has been remember as in the "negative" list before, clear this information, since it's overruled + _rNegatives.erase( _rKeyName ); + } + else + _rNegatives.insert( _rKeyName ); + } + } + + + void CachedInspectorUI::enablePropertyUI( const OUString& _rPropertyName, sal_Bool _bEnable ) + { + MethodGuard aGuard( *this ); + if ( !m_rMaster.shouldContinuePropertyHandling( _rPropertyName ) ) + return; + + lcl_markStringKeyPositiveOrNegative( _rPropertyName, aEnabledProperties, aDisabledProperties, _bEnable ); + impl_notifySingleUIChange(); + } + + + void CachedInspectorUI::impl_markElementEnabledOrDisabled( const OUString& _rPropertyName, sal_Int16 _nElementIdOrZero, bool _bEnable ) + { + if ( _nElementIdOrZero == 0 ) + return; + + lcl_markStringKeyPositiveOrNegative( + _rPropertyName, + aEnabledElements[ _nElementIdOrZero ], + aDisabledElements[ _nElementIdOrZero ], + _bEnable + ); + } + + + void CachedInspectorUI::impl_notifySingleUIChange() const + { + (m_rMaster.*m_pUIChangeNotification)(); + } + + + void CachedInspectorUI::enablePropertyUIElements( const OUString& _rPropertyName, sal_Int16 _nElements, sal_Bool _bEnable ) + { + MethodGuard aGuard( *this ); + if ( !m_rMaster.shouldContinuePropertyHandling( _rPropertyName ) ) + return; + + impl_markElementEnabledOrDisabled( _rPropertyName, _nElements & PropertyLineElement::InputControl, _bEnable ); + impl_markElementEnabledOrDisabled( _rPropertyName, _nElements & PropertyLineElement::PrimaryButton, _bEnable ); + impl_markElementEnabledOrDisabled( _rPropertyName, _nElements & PropertyLineElement::SecondaryButton, _bEnable ); + + impl_notifySingleUIChange(); + } + + + void CachedInspectorUI::rebuildPropertyUI( const OUString& _rPropertyName ) + { + MethodGuard aGuard( *this ); + if ( !m_rMaster.shouldContinuePropertyHandling( _rPropertyName ) ) + return; + + aRebuiltProperties.insert( _rPropertyName ); + + impl_notifySingleUIChange(); + } + + + void CachedInspectorUI::showPropertyUI( const OUString& _rPropertyName ) + { + MethodGuard aGuard( *this ); + if ( !m_rMaster.shouldContinuePropertyHandling( _rPropertyName ) ) + return; + + aShownProperties.insert( _rPropertyName ); + // if the same category has been hidden before, clear this information, since it's overruled + aHiddenProperties.erase( _rPropertyName ); + + impl_notifySingleUIChange(); + } + + + void CachedInspectorUI::hidePropertyUI( const OUString& _rPropertyName ) + { + MethodGuard aGuard( *this ); + if ( !m_rMaster.shouldContinuePropertyHandling( _rPropertyName ) ) + return; + + aHiddenProperties.insert( _rPropertyName ); + impl_notifySingleUIChange(); + } + + + void CachedInspectorUI::showCategory( const OUString& _rCategory, sal_Bool _bShow ) + { + MethodGuard aGuard( *this ); + + lcl_markStringKeyPositiveOrNegative( _rCategory, aShownCategories, aHiddenCategories, _bShow ); + impl_notifySingleUIChange(); + } + + + Reference< XPropertyControl > SAL_CALL CachedInspectorUI::getPropertyControl( const OUString& _rPropertyName ) + { + MethodGuard aGuard( *this ); + if ( !m_rMaster.shouldContinuePropertyHandling( _rPropertyName ) ) + return Reference< XPropertyControl >(); + + return m_rMaster.getDelegatorUI()->getPropertyControl( _rPropertyName ); + } + + + void SAL_CALL CachedInspectorUI::registerControlObserver( const Reference< XPropertyControlObserver >& Observer ) + { + OSL_FAIL( "CachedInspectorUI::registerControlObserver: not expected to be called!" ); + // CachedInspectorUI is used as context for the controls, and we don't expect them to + // register listeners themself + m_rMaster.getDelegatorUI()->registerControlObserver( Observer ); + } + + + void SAL_CALL CachedInspectorUI::revokeControlObserver( const Reference< XPropertyControlObserver >& Observer ) + { + OSL_FAIL( "CachedInspectorUI::revokeControlObserver: not expected to be called!" ); + // CachedInspectorUI is used as context for the controls, and we don't expect them to + // register listeners themself + m_rMaster.getDelegatorUI()->revokeControlObserver( Observer ); + } + + + void SAL_CALL CachedInspectorUI::setHelpSectionText( const OUString& HelpText ) + { + m_rMaster.getDelegatorUI()->setHelpSectionText( HelpText ); + } + + + // HandlerMap + + typedef std::map < Reference< XPropertyHandler > + , ::rtl::Reference< CachedInspectorUI > + , HandlerLess + > ImplMapHandlerToUI; + struct MapHandlerToUI + { + ImplMapHandlerToUI aHandlers; + }; + + ComposedPropertyUIUpdate::ComposedPropertyUIUpdate( const Reference< XObjectInspectorUI >& _rxDelegatorUI, + IPropertyExistenceCheck* _pPropertyCheck ) + :m_pCollectedUIs( new MapHandlerToUI ) + ,m_xDelegatorUI( _rxDelegatorUI ) + ,m_nSuspendCounter( 0 ) + ,m_pPropertyCheck( _pPropertyCheck ) + { + if ( !m_xDelegatorUI.is() ) + throw NullPointerException(); + } + + + ComposedPropertyUIUpdate::~ComposedPropertyUIUpdate( ) + { + } + + + Reference< XObjectInspectorUI > ComposedPropertyUIUpdate::getUIForPropertyHandler( const Reference< XPropertyHandler >& _rxHandler ) + { + impl_checkDisposed(); + + ::rtl::Reference< CachedInspectorUI >& rUI = m_pCollectedUIs->aHandlers[ _rxHandler ]; + if ( !rUI.is() ) + rUI = new CachedInspectorUI( *this, &ComposedPropertyUIUpdate::callback_inspectorUIChanged_throw ); + return rUI; + } + + + namespace + { + + // an STL-compatible structure which collects strings from a CachedInspectorUI instances + struct StringBagCollector + { + private: + StringBag& m_rBag; + CachedInspectorUI::FGetStringBag m_pGetter; + + public: + StringBagCollector( StringBag& _rBag, CachedInspectorUI::FGetStringBag _pGetter ) :m_rBag( _rBag ), m_pGetter( _pGetter ) { } + + void operator()( const ImplMapHandlerToUI::value_type& _rUI ) + { + StringBag& rBag( ((_rUI.second.get())->*m_pGetter)() ); + m_rBag.insert( rBag.begin(), rBag.end() ); + } + + static void collectAll( StringBag& _rAll, const ImplMapHandlerToUI& _rMap, CachedInspectorUI::FGetStringBag _pGetter ) + { + std::for_each( _rMap.begin(), _rMap.end(), StringBagCollector( _rAll, _pGetter ) ); + } + }; + + + // an STL-compatible structure which cleans a certain string bag in a CachedInspectorUI instances + struct StringBagClearer + { + private: + CachedInspectorUI::FGetStringBag m_pGetter; + + public: + explicit StringBagClearer( CachedInspectorUI::FGetStringBag _pGetter ) :m_pGetter( _pGetter ) { } + + void operator()( const ImplMapHandlerToUI::value_type& _rUI ) + { + clearContainer( ((_rUI.second.get())->*m_pGetter)() ); + } + + static void clearAll( const ImplMapHandlerToUI& _rMap, CachedInspectorUI::FGetStringBag _pGetter ) + { + std::for_each( _rMap.begin(), _rMap.end(), StringBagClearer( _pGetter ) ); + } + }; + + // a typedef for a ->XObjectInspectorUI member function taking a string + typedef void ( SAL_CALL XObjectInspectorUI::*FPropertyUISetter )( const OUString& ); + + + // an STL-compatible struct which calls a certain member method (taking a string) at a + // given ->XObjectInspectorUI instance + struct PropertyUIOperator + { + private: + Reference< XObjectInspectorUI > m_xUpdater; + FPropertyUISetter m_pSetter; + + public: + PropertyUIOperator( const Reference< XObjectInspectorUI >& _rxInspectorUI, FPropertyUISetter _pSetter ) + :m_xUpdater( _rxInspectorUI ) + ,m_pSetter( _pSetter ) + { + } + + void operator()( const OUString& _rPropertyName ) + { + ((m_xUpdater.get())->*m_pSetter)( _rPropertyName ); + } + + static void forEach( const StringBag& _rProperties, const Reference< XObjectInspectorUI >& _rxDelegatorUI, FPropertyUISetter _pSetter ) + { + std::for_each( _rProperties.begin(), _rProperties.end(), PropertyUIOperator( _rxDelegatorUI, _pSetter ) ); + } + }; + + + // an interface which encapsulates access to a single aspect of the ->XObjectInspectorUI, + // where this aspect is given by a string key, and has a boolean value. + class IStringKeyBooleanUIUpdate + { + public: + virtual void updateUIForKey( const OUString& _rKey, bool _bFlag ) const = 0; + + virtual ~IStringKeyBooleanUIUpdate() { } + }; + + + // FPropertyUIFlagSetter + + /** an implementation of the ->IStringKeyBooleanUIUpdate interface which, + for a fixed ->XObjectInspectorUI instance and a fixed UI element (->PropertyLineElement), + updates this element for a given property with a given boolean flag + (->XObjectInspectorUI::enablePropertyUIElements) + */ + class EnablePropertyUIElement : public IStringKeyBooleanUIUpdate + { + private: + Reference< XObjectInspectorUI > m_xUIUpdate; + sal_Int16 m_nElement; + + public: + EnablePropertyUIElement( const Reference< XObjectInspectorUI >& _rxUIUpdate, sal_Int16 _nElement ) + :m_xUIUpdate( _rxUIUpdate ) + ,m_nElement( _nElement ) + { + } + // IStringKeyBooleanUIUpdate + virtual void updateUIForKey( const OUString& _rKey, bool _bFlag ) const override; + }; + + + void EnablePropertyUIElement::updateUIForKey( const OUString& _rKey, bool _bFlag ) const + { + m_xUIUpdate->enablePropertyUIElements( _rKey, m_nElement, _bFlag ); + } + + + // a ->XObjectInspectorUI method taking a string and a boolean + typedef void ( SAL_CALL XObjectInspectorUI::*FPropertyUIFlagSetter )( const OUString&, sal_Bool ); + + + // an implementation of the ->IStringKeyBooleanUIUpdate interface which calls + // an arbitrary ->XObjectInspectorUI method taking a string and a boolean flag + class DefaultStringKeyBooleanUIUpdate : public IStringKeyBooleanUIUpdate + { + private: + Reference< XObjectInspectorUI > m_xUIUpdate; + FPropertyUIFlagSetter m_pSetter; + + public: + DefaultStringKeyBooleanUIUpdate( const Reference< XObjectInspectorUI >& _rxUIUpdate, FPropertyUIFlagSetter _pSetter ); + // IStringKeyBooleanUIUpdate + virtual void updateUIForKey( const OUString& _rKey, bool _bFlag ) const override; + }; + + + DefaultStringKeyBooleanUIUpdate::DefaultStringKeyBooleanUIUpdate( const Reference< XObjectInspectorUI >& _rxUIUpdate, FPropertyUIFlagSetter _pSetter ) + :m_xUIUpdate( _rxUIUpdate ) + ,m_pSetter( _pSetter ) + { + } + + + void DefaultStringKeyBooleanUIUpdate::updateUIForKey( const OUString& _rKey, bool _bFlag ) const + { + ((m_xUIUpdate.get())->*m_pSetter)( _rKey, _bFlag ); + } + + + // an STL-compatible structure which applies a ->IStringKeyBooleanUIUpdate::updateUIForKey + // operation with a fixed boolean value, for a given string value + struct BooleanUIAspectUpdate + { + private: + const IStringKeyBooleanUIUpdate& m_rUpdater; + bool m_bFlag; + + public: + BooleanUIAspectUpdate( const IStringKeyBooleanUIUpdate& _rUpdater, bool _bFlag ) + :m_rUpdater( _rUpdater ) + ,m_bFlag( _bFlag ) + { + } + + void operator()( const OUString& _rPropertyName ) + { + m_rUpdater.updateUIForKey( _rPropertyName, m_bFlag ); + } + + static void forEach( const StringBag& _rProperties, const IStringKeyBooleanUIUpdate& _rUpdater, bool _bFlag ) + { + std::for_each( _rProperties.begin(), _rProperties.end(), BooleanUIAspectUpdate( _rUpdater, _bFlag ) ); + } + }; + + + // BooleanUIAspectUpdate + + // an STL-compatible structure subtracting a given string from a fixed ->StringBag + struct StringBagComplement + { + private: + StringBag& m_rMinuend; + + public: + explicit StringBagComplement( StringBag& _rMinuend ) :m_rMinuend( _rMinuend ) { } + + void operator()( const OUString& _rPropertyToSubtract ) + { + m_rMinuend.erase( _rPropertyToSubtract ); + } + + static void subtract( StringBag& _rMinuend, const StringBag& _rSubtrahend ) + { + std::for_each( _rSubtrahend.begin(), _rSubtrahend.end(), StringBagComplement( _rMinuend ) ); + } + }; + + + // BooleanUIAspectUpdate + + void lcl_fireUIStateFlag( + const IStringKeyBooleanUIUpdate& _rUIUpdate, + const ImplMapHandlerToUI& _rHandlerUIs, + CachedInspectorUI::FGetStringBag _pGetPositives, + CachedInspectorUI::FGetStringBag _pGetNegatives + ) + { + // all strings which are in the "positive" list of one handler + StringBag aAllPositives; + StringBagCollector::collectAll( aAllPositives, _rHandlerUIs, _pGetPositives ); + + // all strings which are in the "negative" list of one handler + StringBag aAllNegatives; + StringBagCollector::collectAll( aAllNegatives, _rHandlerUIs, _pGetNegatives ); + + // propagate the "negative" flags to the delegator UI + BooleanUIAspectUpdate::forEach( aAllNegatives, _rUIUpdate, false ); + + // propagate the "positive" flags to the delegator UI, for all elements where _no_ + // "negative" flag exists + StringBagComplement::subtract( aAllPositives, aAllNegatives ); + BooleanUIAspectUpdate::forEach( aAllPositives, _rUIUpdate, true ); + + // the "positive" request can be cleared no, only negative requests + // (such as "disable a property" or "hide a category") need to be preserved for the next round + StringBagClearer::clearAll( _rHandlerUIs, _pGetPositives ); + } + } + + + void ComposedPropertyUIUpdate::impl_fireEnablePropertyUI_throw() + { + lcl_fireUIStateFlag( + DefaultStringKeyBooleanUIUpdate( m_xDelegatorUI, &XObjectInspectorUI::enablePropertyUI ), + m_pCollectedUIs->aHandlers, + &CachedInspectorUI::getEnabledProperties, + &CachedInspectorUI::getDisabledProperties + ); + } + + + void ComposedPropertyUIUpdate::impl_fireRebuildPropertyUI_throw() + { + // collect all properties for which a rebuild request has been made + StringBag aAllRebuilt; + StringBagCollector::collectAll( aAllRebuilt, m_pCollectedUIs->aHandlers, &CachedInspectorUI::getRebuiltProperties ); + + // rebuild all those properties + PropertyUIOperator::forEach( aAllRebuilt, m_xDelegatorUI, &XObjectInspectorUI::rebuildPropertyUI ); + + // clear the "properties to rebuild" at all handlers, since the request has been fulfilled now. + StringBagClearer::clearAll( m_pCollectedUIs->aHandlers, &CachedInspectorUI::getRebuiltProperties ); + } + + + void ComposedPropertyUIUpdate::impl_fireShowHidePropertyUI_throw() + { + // all properties which have been shown by at least one handler + StringBag aAllShown; + StringBagCollector::collectAll( aAllShown, m_pCollectedUIs->aHandlers, &CachedInspectorUI::getShownProperties ); + // all properties which have been hidden by at least one handler + StringBag aAllHidden; + StringBagCollector::collectAll( aAllHidden, m_pCollectedUIs->aHandlers, &CachedInspectorUI::getHiddenProperties ); + + // hide properties as necessary + PropertyUIOperator::forEach( aAllHidden, m_xDelegatorUI, &XObjectInspectorUI::hidePropertyUI ); + + // for those properties which are hidden, ignore all "show" requests which other handlers might have had + StringBagComplement::subtract( aAllShown, aAllHidden ); + + // show properties + PropertyUIOperator::forEach( aAllShown, m_xDelegatorUI, &XObjectInspectorUI::showPropertyUI ); + } + + + void ComposedPropertyUIUpdate::impl_fireShowCategory_throw() + { + lcl_fireUIStateFlag( + DefaultStringKeyBooleanUIUpdate( m_xDelegatorUI, &XObjectInspectorUI::showCategory ), + m_pCollectedUIs->aHandlers, + &CachedInspectorUI::getShownCategories, + &CachedInspectorUI::getHiddenCategories + ); + } + + + void ComposedPropertyUIUpdate::impl_fireEnablePropertyUIElements_throw() + { + lcl_fireUIStateFlag( + EnablePropertyUIElement( m_xDelegatorUI, PropertyLineElement::InputControl ), + m_pCollectedUIs->aHandlers, + &CachedInspectorUI::getEnabledInputControls, + &CachedInspectorUI::getDisabledInputControls + ); + + lcl_fireUIStateFlag( + EnablePropertyUIElement( m_xDelegatorUI, PropertyLineElement::PrimaryButton ), + m_pCollectedUIs->aHandlers, + &CachedInspectorUI::getEnabledPrimaryButtons, + &CachedInspectorUI::getDisabledPrimaryButtons + ); + + lcl_fireUIStateFlag( + EnablePropertyUIElement( m_xDelegatorUI, PropertyLineElement::SecondaryButton ), + m_pCollectedUIs->aHandlers, + &CachedInspectorUI::getEnabledSecondaryButtons, + &CachedInspectorUI::getDisabledSecondaryButtons + ); + } + + + void ComposedPropertyUIUpdate::impl_fireAll_throw() + { + OSL_PRECOND( !impl_isDisposed(), "ComposedPropertyUIUpdate::impl_fireAll_throw: already disposed, this will crash!" ); + + impl_fireEnablePropertyUI_throw(); + impl_fireShowHidePropertyUI_throw(); + impl_fireRebuildPropertyUI_throw(); + impl_fireShowCategory_throw(); + impl_fireEnablePropertyUIElements_throw(); + } + + + void ComposedPropertyUIUpdate::suspendAutoFire() + { + impl_checkDisposed(); + osl_atomic_increment( &m_nSuspendCounter ); + } + + + void ComposedPropertyUIUpdate::resumeAutoFire() + { + impl_checkDisposed(); + if ( 0 == osl_atomic_decrement( &m_nSuspendCounter ) ) + impl_fireAll_throw(); + } + + + void ComposedPropertyUIUpdate::impl_checkDisposed() const + { + if ( impl_isDisposed() ) + throw DisposedException(); + } + + + void ComposedPropertyUIUpdate::callback_inspectorUIChanged_throw() + { + if ( 0 == m_nSuspendCounter ) + impl_fireAll_throw(); + } + + + Reference< XObjectInspectorUI > const & ComposedPropertyUIUpdate::getDelegatorUI() const + { + impl_checkDisposed(); + return m_xDelegatorUI; + } + + + void ComposedPropertyUIUpdate::dispose() + { + if ( impl_isDisposed() ) + return; + + OSL_ENSURE( m_nSuspendCounter == 0, "ComposedPropertyUIUpdate::dispose: still suspended, the changes will be lost!" ); + + for (auto const& singleUI : m_pCollectedUIs->aHandlers) + { + singleUI.second->dispose(); + } + m_pCollectedUIs.reset(); + m_xDelegatorUI.clear(); + } + + + bool ComposedPropertyUIUpdate::shouldContinuePropertyHandling( const OUString& _rName ) const + { + if ( !m_pPropertyCheck ) + return true; + if ( m_pPropertyCheck->hasPropertyByName( _rName ) ) + return true; + return false; + } + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/composeduiupdate.hxx b/extensions/source/propctrlr/composeduiupdate.hxx new file mode 100644 index 0000000000..5e356af1f9 --- /dev/null +++ b/extensions/source/propctrlr/composeduiupdate.hxx @@ -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 . + */ + +#pragma once + +#include <com/sun/star/inspection/XObjectInspectorUI.hpp> +#include <com/sun/star/inspection/XPropertyHandler.hpp> + +#include <memory> + + +namespace pcr +{ + + struct MapHandlerToUI; + + /** callback for a ComposedPropertyUIUpdate checking a given property for existence + */ + class SAL_NO_VTABLE IPropertyExistenceCheck + { + public: + /// @throws css::uno::RuntimeException + virtual bool hasPropertyByName( const OUString& _rName ) = 0; + + protected: + ~IPropertyExistenceCheck() {} + }; + + /** helper class composing requests to a ->XObjectInspectorUI interface, coming + from multiple sources + + Usually, a handler tells the browser UI to enable to disable, or show or hide, certain + elements. Now when multiple handlers do this, their instructions must be combined: + If one handler disables a certain element, but others enable it, it must in the + result still be disabled. Similar for showing/hiding elements. + + ->ComposedPropertyUIUpdate implements this combination. It does so by providing a dedicated + ->XObjectInspectorUI instance for every participating handler, and remembering the UI + state on a per-handler basis. Upon request (->fire), the combined UI state is + forwarded to another ->XObjectInspectorUI instance, the so-called delegator UI. + */ + class ComposedPropertyUIUpdate + { + private: + std::unique_ptr< MapHandlerToUI > m_pCollectedUIs; + css::uno::Reference< css::inspection::XObjectInspectorUI > + m_xDelegatorUI; + oslInterlockedCount m_nSuspendCounter; + IPropertyExistenceCheck* m_pPropertyCheck; + + public: + /** constructs a ->ComposedPropertyUIUpdate instance + @param _rxDelegatorUI + a ->XObjectInspectorUI instance to which composed UI requests should be forwarded. Must + not be <NULL/>. + @param _pPropertyCheck + an instance checking properties for existence. If this is not <NULL/>, it will be invoked + whenever one of the ->XObjectInspectorUI methods is called, to check the passed property + name.<br/> + Beware of lifetime issues. The instance pointed to by <arg>_pPropertyCheck</arg> must + live at least as long as the ->ComposedPropertyUIUpdate instance you're going to create. + @throws css::lang::NullPointerException + if ->_rxDelegatorUI is <NULL/> + */ + ComposedPropertyUIUpdate( + const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxDelegatorUI, + IPropertyExistenceCheck* _pPropertyCheck ); + ~ComposedPropertyUIUpdate(); + + /** returns the delegator UI + @throw css::lang::DisposedException + */ + css::uno::Reference< css::inspection::XObjectInspectorUI > const & getDelegatorUI() const; + + /** returns a ->XObjectInspectorUI instance belonging to a given property handler + + In every call to an ->XPropertyHandler method which requires a ->XObjectInspectorUI, + the same UI instance should be used. The instance here will cache all requests passed + to it, and ->ComposedPropertyUIUpdate::fire will use the combination of all + cached UI states of all handlers to update the delegator UI. + */ + css::uno::Reference< css::inspection::XObjectInspectorUI > + getUIForPropertyHandler( const css::uno::Reference< css::inspection::XPropertyHandler >& _rxHandler ); + + /** Suspends automatic firing of UI changes + + normally, as soon as any of the property handlers does a request for an + arbitrary UI change, the set of collected UI changes is evaluated, and the combined + UI state is fired to the delegator UI. + + You can disable this automatic firing by calling ->suspendAutoFire. As longs as auto + firing is suspended, only explicit ->fire calls trigger the notification to the + delegator UI. + + Note that calls to ->suspendAutoFire are cumulative, that is, if you make multiple calls + they must be accompanied by an equal number of calls to ->resumeAutoFire, to enable + auto-firing again. + + @seealso resumeAutoFire + */ + void suspendAutoFire(); + + /** Suspends automatic firing of UI changes + + @seealso suspendAutoFire + */ + void resumeAutoFire(); + + /** disposes the instance, so it becomes non-functional. + + All cached handlers and cached ->XObjectInspectorUI instances will be released, + the latter will also be disposed, so that if anybody still holds a reference to them + and tries to operate them will get a DisposedException. + */ + void dispose(); + + /** invokes m_pPropertyCheck to check whether a given property should be handled + */ + bool shouldContinuePropertyHandling( const OUString& _rName ) const; + + private: + /// determines whether the instance is already disposed + bool impl_isDisposed() const { return !m_pCollectedUIs; } + + /// throws an exception if the component is already disposed + void impl_checkDisposed() const; + + /** fires the collected UI changes to our delegator UI + + All operations for any elements are forwarded: + <ul><li>If an element has been hidden at least once, it's also hidden at the delegator UI.</li> + <li>If an element has been shown at least once, and never been hidden, it's also + shown at the delegator UI.</li> + <li>If an element has never been shown or hidden, it's also not touched at the delegator UI.</li> + <li>The same holds if you replace "hidden" in the last three items with "disabled", + and "shown" with "enabled".</li> + <li>If an element should have been rebuilt (->XObjectInspectorUI::rebuiltPropertyUI) + at least once, it's rebuilt at the delegator UI, too.<br/> + After that, the request to rebuild the UI for this property is cleared, so subsequent + calls to ->fire will not trigger a new rebuilt request. + </ul> + + @precond + instance is not disposed + */ + void impl_fireAll_throw(); + + /// fires the combination of ->XObjectInspectorUI::enablePropertyUI calls + void impl_fireEnablePropertyUI_throw(); + + /// fires the combination of ->XObjectInspectorUI::enablePropertyUIElements calls + void impl_fireEnablePropertyUIElements_throw(); + + /// fires the combination of ->XObjectInspectorUI::rebuildPropertyUI calls + void impl_fireRebuildPropertyUI_throw(); + + /// fires the combination of ->XObjectInspectorUI::showPropertyUI and ->XObjectInspectorUI::hidePropertyUI calls + void impl_fireShowHidePropertyUI_throw(); + + /// fires the combination of ->XObjectInspectorUI::showCategory calls + void impl_fireShowCategory_throw(); + + /** callback for when a single property handler requested any change in the inspector UI + */ + void callback_inspectorUIChanged_throw(); + + private: + ComposedPropertyUIUpdate( const ComposedPropertyUIUpdate& ) = delete; + ComposedPropertyUIUpdate& operator=( const ComposedPropertyUIUpdate& ) = delete; + }; + + class ComposedUIAutoFireGuard + { + private: + ComposedPropertyUIUpdate& m_rUIUpdate; + public: + explicit ComposedUIAutoFireGuard( ComposedPropertyUIUpdate& _rUIUpdate ) + :m_rUIUpdate( _rUIUpdate ) + { + m_rUIUpdate.suspendAutoFire(); + } + ~ComposedUIAutoFireGuard() COVERITY_NOEXCEPT_FALSE + { + m_rUIUpdate.resumeAutoFire(); + } + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/controlfontdialog.cxx b/extensions/source/propctrlr/controlfontdialog.cxx new file mode 100644 index 0000000000..9c3eddf4d1 --- /dev/null +++ b/extensions/source/propctrlr/controlfontdialog.cxx @@ -0,0 +1,148 @@ +/* -*- 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/beans/PropertyAttribute.hpp> +#include "controlfontdialog.hxx" + +#include <comphelper/propertyvalue.hxx> +#include <vcl/svapp.hxx> +#include "fontdialog.hxx" +#include "formstrings.hxx" +#include "pcrcommon.hxx" + +namespace pcr +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + + OControlFontDialog::OControlFontDialog(const Reference< XComponentContext >& _rxContext ) + :OGenericUnoDialog( _rxContext ) + ,m_pItemPoolDefaults(nullptr) + { + registerProperty(PROPERTY_INTROSPECTEDOBJECT, static_cast<sal_Int32>(OwnPropertyId::INTROSPECTEDOBJECT), + PropertyAttribute::BOUND | PropertyAttribute::TRANSIENT, + &m_xControlModel, cppu::UnoType<decltype(m_xControlModel)>::get()); + } + + + OControlFontDialog::~OControlFontDialog() + { + if (m_xDialog) + { + ::osl::MutexGuard aGuard(m_aMutex); + if (m_xDialog) + { + destroyDialog(); + ControlCharacterDialog::destroyItemSet(m_pFontItems, m_pItemPool, m_pItemPoolDefaults); + } + } + } + + + Sequence<sal_Int8> SAL_CALL OControlFontDialog::getImplementationId( ) + { + return css::uno::Sequence<sal_Int8>(); + } + + + OUString SAL_CALL OControlFontDialog::getImplementationName() + { + return "org.openoffice.comp.form.ui.OControlFontDialog"; + } + + + css::uno::Sequence<OUString> SAL_CALL OControlFontDialog::getSupportedServiceNames() + { + return { "com.sun.star.form.ControlFontDialog" }; + } + + void OControlFontDialog::initialize( const Sequence< Any >& aArguments ) + { + Reference<XPropertySet> xGridModel; + if (aArguments.getLength() == 1 && (aArguments[0] >>= xGridModel)) + { + Sequence aNewArguments{ Any(comphelper::makePropertyValue("IntrospectedObject", + xGridModel)) }; + OControlFontDialog_DBase::initialize(aNewArguments); + } + else + OControlFontDialog_DBase::initialize(aArguments); + } + + + Reference<XPropertySetInfo> SAL_CALL OControlFontDialog::getPropertySetInfo() + { + Reference<XPropertySetInfo> xInfo( createPropertySetInfo( getInfoHelper() ) ); + return xInfo; + } + + + ::cppu::IPropertyArrayHelper& OControlFontDialog::getInfoHelper() + { + return *getArrayHelper(); + } + + + ::cppu::IPropertyArrayHelper* OControlFontDialog::createArrayHelper( ) const + { + Sequence< Property > aProps; + describeProperties(aProps); + return new ::cppu::OPropertyArrayHelper(aProps); + } + + std::unique_ptr<weld::DialogController> OControlFontDialog::createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) + { + ControlCharacterDialog::createItemSet(m_pFontItems, m_pItemPool, m_pItemPoolDefaults); + + OSL_ENSURE(m_xControlModel.is(), "OControlFontDialog::createDialog: no introspectee set!"); + if (m_xControlModel.is()) + ControlCharacterDialog::translatePropertiesToItems(m_xControlModel, m_pFontItems.get()); + // TODO: we need a mechanism to prevent that somebody creates us, sets an introspectee, executes us, + // sets a new introspectee and re-executes us. In this case, the dialog returned here (upon the first + // execute) will be re-used upon the second execute, and thus it won't be initialized correctly. + + return std::make_unique<ControlCharacterDialog>(Application::GetFrameWeld(rParent), *m_pFontItems); + } + + void OControlFontDialog::executedDialog(sal_Int16 _nExecutionResult) + { + OSL_ENSURE(m_xDialog, "OControlFontDialog::executedDialog: no dialog anymore?!!"); + if (m_xDialog && (RET_OK == _nExecutionResult) && m_xControlModel.is()) + { + const SfxItemSet* pOutput = static_cast<ControlCharacterDialog*>(m_xDialog.get())->GetOutputItemSet(); + if (pOutput) + ControlCharacterDialog::translateItemsToProperties( *pOutput, m_xControlModel ); + } + } + + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_OControlFontDialog_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::OControlFontDialog(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/controlfontdialog.hxx b/extensions/source/propctrlr/controlfontdialog.hxx new file mode 100644 index 0000000000..8dc5201027 --- /dev/null +++ b/extensions/source/propctrlr/controlfontdialog.hxx @@ -0,0 +1,82 @@ +/* -*- 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 . + */ + +#pragma once + +#include <comphelper/proparrhlp.hxx> +#include <svtools/genericunodialog.hxx> + +class SfxItemSet; +class SfxItemPool; +class SfxPoolItem; + +namespace pcr +{ + + class OControlFontDialog; + typedef ::svt::OGenericUnoDialog OControlFontDialog_DBase; + typedef ::comphelper::OPropertyArrayUsageHelper< OControlFontDialog > OControlFontDialog_PBase; + + class OControlFontDialog + :public OControlFontDialog_DBase + ,public OControlFontDialog_PBase + { + protected: + // <properties> + css::uno::Reference< css::beans::XPropertySet > + m_xControlModel; + // </properties> + + std::unique_ptr<SfxItemSet> m_pFontItems; // item set for the dialog + rtl::Reference<SfxItemPool> m_pItemPool; // item pool for the item set for the dialog + std::vector<SfxPoolItem*>* + m_pItemPoolDefaults; // pool defaults + + public: + explicit OControlFontDialog(const css::uno::Reference< css::uno::XComponentContext >& _rxContext); + virtual ~OControlFontDialog() override; + + // XTypeProvider + virtual css::uno::Sequence<sal_Int8> SAL_CALL getImplementationId( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo> SAL_CALL getPropertySetInfo() override; + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; + + // OPropertyArrayUsageHelper + virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const override; + + protected: + // OGenericUnoDialog overridables + virtual std::unique_ptr<weld::DialogController> createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) override; + virtual void executedDialog(sal_Int16 _nExecutionResult) override; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/controltype.hxx b/extensions/source/propctrlr/controltype.hxx new file mode 100644 index 0000000000..89a400a769 --- /dev/null +++ b/extensions/source/propctrlr/controltype.hxx @@ -0,0 +1,35 @@ +/* -*- 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 . + */ +#pragma once + +#include <sal/types.h> + + +namespace pcr::ControlType +{ + const sal_Int16 FIXEDLINE = sal_Int16(100); + const sal_Int16 FORMATTEDFIELD = sal_Int16(101); + const sal_Int16 PROGRESSBAR = sal_Int16(102); + + // need only those which are not already covered as FormComponentType + +} // namespace pcr::ControlType + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/defaultforminspection.cxx b/extensions/source/propctrlr/defaultforminspection.cxx new file mode 100644 index 0000000000..3fa7fa5658 --- /dev/null +++ b/extensions/source/propctrlr/defaultforminspection.cxx @@ -0,0 +1,214 @@ +/* -*- 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 "defaultforminspection.hxx" +#include "pcrcommon.hxx" +#include <helpids.h> +#include <strings.hrc> +#include "modulepcr.hxx" +#include "formmetadata.hxx" + +#include <com/sun/star/ucb/AlreadyInitializedException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <sal/macros.h> + + +namespace pcr +{ + + + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::inspection::PropertyCategoryDescriptor; + using ::com::sun::star::ucb::AlreadyInitializedException; + using ::com::sun::star::lang::IllegalArgumentException; + + DefaultFormComponentInspectorModel::DefaultFormComponentInspectorModel( bool _bUseFormFormComponentHandlers ) + :m_bUseFormComponentHandlers( _bUseFormFormComponentHandlers ) + ,m_bConstructed( false ) + ,m_pInfoService( new OPropertyInfoService ) + { + } + + + DefaultFormComponentInspectorModel::~DefaultFormComponentInspectorModel() + { + } + + + OUString SAL_CALL DefaultFormComponentInspectorModel::getImplementationName( ) + { + return "org.openoffice.comp.extensions.DefaultFormComponentInspectorModel"; + } + + + Sequence< OUString > SAL_CALL DefaultFormComponentInspectorModel::getSupportedServiceNames( ) + { + return { "com.sun.star.form.inspection.DefaultFormComponentInspectorModel" }; + } + + + Sequence< Any > SAL_CALL DefaultFormComponentInspectorModel::getHandlerFactories() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // service names for all our handlers + static struct + { + const char* serviceName; + bool isFormOnly; + } const aFactories[] = { + + // a generic handler for form component properties (must precede the ButtonNavigationHandler) + { "com.sun.star.form.inspection.FormComponentPropertyHandler", false }, + + // generic virtual edit properties + { "com.sun.star.form.inspection.EditPropertyHandler", false }, + + // a handler which virtualizes the ButtonType property, to provide additional types like + // "move to next record" + { "com.sun.star.form.inspection.ButtonNavigationHandler", false }, + + // a handler for script events bound to form components or dialog elements + { "com.sun.star.form.inspection.EventHandler", false }, + + // a handler which introduces virtual properties for binding controls to spreadsheet cells + { "com.sun.star.form.inspection.CellBindingPropertyHandler", false }, + + // properties related to binding to an XForms DOM node + { "com.sun.star.form.inspection.XMLFormsPropertyHandler", true }, + + // properties related to the XSD data against which a control content is validated + { "com.sun.star.form.inspection.XSDValidationPropertyHandler", true }, + + // a handler which cares for XForms submissions + { "com.sun.star.form.inspection.SubmissionPropertyHandler", true }, + + // a handler which cares for geometry properties of form controls + { "com.sun.star.form.inspection.FormGeometryHandler", true } + }; + + sal_Int32 nFactories = SAL_N_ELEMENTS( aFactories ); + Sequence< Any > aReturn( nFactories ); + Any* pReturn = aReturn.getArray(); + for ( sal_Int32 i = 0; i < nFactories; ++i ) + { + if ( aFactories[i].isFormOnly && !m_bUseFormComponentHandlers ) + continue; + *pReturn++ <<= OUString::createFromAscii( aFactories[i].serviceName ); + } + aReturn.realloc( pReturn - aReturn.getArray() ); + + return aReturn; + } + + + Sequence< PropertyCategoryDescriptor > SAL_CALL DefaultFormComponentInspectorModel::describeCategories( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + static struct + { + const char* programmaticName; + TranslateId uiNameResId; + OUString helpId; + } constexpr aCategories[] = { + { "General", RID_STR_PROPPAGE_DEFAULT, HID_FM_PROPDLG_TAB_GENERAL }, + { "Data", RID_STR_PROPPAGE_DATA, HID_FM_PROPDLG_TAB_DATA }, + { "Events", RID_STR_EVENTS, HID_FM_PROPDLG_TAB_EVT } + }; + + sal_Int32 nCategories = SAL_N_ELEMENTS( aCategories ); + Sequence< PropertyCategoryDescriptor > aReturn( nCategories ); + PropertyCategoryDescriptor* pReturn = aReturn.getArray(); + for ( sal_Int32 i=0; i<nCategories; ++i, ++pReturn ) + { + pReturn->ProgrammaticName = OUString::createFromAscii( aCategories[i].programmaticName ); + pReturn->UIName = PcrRes( aCategories[i].uiNameResId ); + pReturn->HelpURL = HelpIdUrl::getHelpURL( aCategories[i].helpId ); + } + + return aReturn; + } + + + ::sal_Int32 SAL_CALL DefaultFormComponentInspectorModel::getPropertyOrderIndex( const OUString& _rPropertyName ) + { + sal_Int32 nPropertyId( m_pInfoService->getPropertyId( _rPropertyName ) ); + if ( nPropertyId == -1 ) + { + if ( _rPropertyName.indexOf( ';' ) != -1 ) + // it's an event. Just give it an arbitrary number - events will be on a separate + // page, and by definition, if two properties have the same OrderIndex, then + // they will be ordered as they appear in the handler's getSupportedProperties. + return 1000; + return 0; + } + return m_pInfoService->getPropertyPos( nPropertyId ); + } + + + void SAL_CALL DefaultFormComponentInspectorModel::initialize( const Sequence< Any >& _arguments ) + { + if ( m_bConstructed ) + throw AlreadyInitializedException(); + + StlSyntaxSequence< Any > arguments( _arguments ); + if ( arguments.empty() ) + { // constructor: "createDefault()" + m_bConstructed = true; + return; + } + + if ( arguments.size() == 2 ) + { // constructor: "createWithHelpSection( long, long )" + sal_Int32 nMinHelpTextLines( 0 ), nMaxHelpTextLines( 0 ); + if ( !( arguments[0] >>= nMinHelpTextLines ) || !( arguments[1] >>= nMaxHelpTextLines ) ) + throw IllegalArgumentException( OUString(), *this, 0 ); + createWithHelpSection( nMinHelpTextLines, nMaxHelpTextLines ); + return; + } + + throw IllegalArgumentException( OUString(), *this, 0 ); + } + + + void DefaultFormComponentInspectorModel::createWithHelpSection( sal_Int32 _nMinHelpTextLines, sal_Int32 _nMaxHelpTextLines ) + { + if ( ( _nMinHelpTextLines <= 0 ) || ( _nMaxHelpTextLines <= 0 ) || ( _nMinHelpTextLines > _nMaxHelpTextLines ) ) + throw IllegalArgumentException( OUString(), *this, 0 ); + + enableHelpSectionProperties( _nMinHelpTextLines, _nMaxHelpTextLines ); + m_bConstructed = true; + } + + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_DefaultFormComponentInspectorModel_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::DefaultFormComponentInspectorModel(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/defaultforminspection.hxx b/extensions/source/propctrlr/defaultforminspection.hxx new file mode 100644 index 0000000000..e118e9da67 --- /dev/null +++ b/extensions/source/propctrlr/defaultforminspection.hxx @@ -0,0 +1,66 @@ +/* -*- 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 . + */ +#pragma once + +#include "inspectormodelbase.hxx" + +#include <memory> + + +namespace pcr +{ + + + class OPropertyInfoService; + + class DefaultFormComponentInspectorModel final : public ImplInspectorModel + { + bool m_bUseFormComponentHandlers; + bool m_bConstructed; + + /// access to property meta data + std::unique_ptr< OPropertyInfoService > m_pInfoService; + + virtual ~DefaultFormComponentInspectorModel() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // XObjectInspectorModel + virtual css::uno::Sequence< css::uno::Any > SAL_CALL getHandlerFactories() override; + virtual css::uno::Sequence< css::inspection::PropertyCategoryDescriptor > SAL_CALL describeCategories( ) override; + virtual ::sal_Int32 SAL_CALL getPropertyOrderIndex( const OUString& PropertyName ) override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + public: + explicit DefaultFormComponentInspectorModel( bool _bUseFormFormComponentHandlers = true ); + + private: + // Service constructors + void createWithHelpSection( sal_Int32 _nMinHelpTextLines, sal_Int32 _nMaxHelpTextLines ); + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/defaulthelpprovider.cxx b/extensions/source/propctrlr/defaulthelpprovider.cxx new file mode 100644 index 0000000000..96c99af67e --- /dev/null +++ b/extensions/source/propctrlr/defaulthelpprovider.cxx @@ -0,0 +1,183 @@ +/* -*- 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 "defaulthelpprovider.hxx" +#include "pcrcommon.hxx" + +#include <com/sun/star/ucb/AlreadyInitializedException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/window.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <cppuhelper/supportsservice.hxx> + + +namespace pcr +{ + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::inspection::XPropertyControl; + using ::com::sun::star::uno::RuntimeException; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::inspection::XObjectInspectorUI; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::ucb::AlreadyInitializedException; + using ::com::sun::star::lang::IllegalArgumentException; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::awt::XWindow; + + DefaultHelpProvider::DefaultHelpProvider() + :m_bConstructed( false ) + { + } + + + DefaultHelpProvider::~DefaultHelpProvider() + { + } + + + Sequence< OUString > SAL_CALL DefaultHelpProvider::getSupportedServiceNames() + { + return { "com.sun.star.inspection.DefaultHelpProvider" }; + } + + OUString SAL_CALL DefaultHelpProvider::getImplementationName() + { + return "org.openoffice.comp.extensions.DefaultHelpProvider"; + } + + sal_Bool SAL_CALL DefaultHelpProvider::supportsService(const OUString& aServiceName) + { + return cppu::supportsService(this, aServiceName); + } + + + void SAL_CALL DefaultHelpProvider::focusGained( const Reference< XPropertyControl >& Control ) + { + if ( !m_xInspectorUI.is() ) + throw RuntimeException( OUString(), *this ); + + try + { + m_xInspectorUI->setHelpSectionText( impl_getHelpText_nothrow( Control ) ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + + void SAL_CALL DefaultHelpProvider::valueChanged( const Reference< XPropertyControl >& ) + { + // not interested in + } + + + void SAL_CALL DefaultHelpProvider::initialize( const Sequence< Any >& _arguments ) + { + if ( m_bConstructed ) + throw AlreadyInitializedException(); + + StlSyntaxSequence< Any > arguments( _arguments ); + if ( arguments.size() == 1 ) + { // constructor: "create( XObjectInspectorUI )" + Reference< XObjectInspectorUI > xUI( arguments[0], UNO_QUERY ); + create( xUI ); + return; + } + + throw IllegalArgumentException( OUString(), *this, 0 ); + } + + + void DefaultHelpProvider::create( const Reference< XObjectInspectorUI >& _rxUI ) + { + if ( !_rxUI.is() ) + throw IllegalArgumentException( OUString(), *this, 1 ); + + try + { + m_xInspectorUI = _rxUI; + m_xInspectorUI->registerControlObserver( this ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + + m_bConstructed = true; + } + + + vcl::Window* DefaultHelpProvider::impl_getVclControlWindow_nothrow( const Reference< XPropertyControl >& _rxControl ) + { + vcl::Window* pControlWindow = nullptr; + OSL_PRECOND( _rxControl.is(), "DefaultHelpProvider::impl_getVclControlWindow_nothrow: illegal control!" ); + if ( !_rxControl.is() ) + return pControlWindow; + + try + { + Reference< XWindow > xControlWindow( _rxControl->getControlWindow(), css::uno::UNO_SET_THROW ); + pControlWindow = VCLUnoHelper::GetWindow( xControlWindow ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + + return pControlWindow; + } + + + OUString DefaultHelpProvider::impl_getHelpText_nothrow( const Reference< XPropertyControl >& _rxControl ) + { + OUString sHelpText; + OSL_PRECOND( _rxControl.is(), "DefaultHelpProvider::impl_getHelpText_nothrow: illegal control!" ); + if ( !_rxControl.is() ) + return sHelpText; + + vcl::Window* pControlWindow( impl_getVclControlWindow_nothrow( _rxControl ) ); + OSL_ENSURE( pControlWindow, "DefaultHelpProvider::impl_getHelpText_nothrow: could not determine the VCL window!" ); + if ( !pControlWindow ) + return sHelpText; + + sHelpText = pControlWindow->GetHelpText(); + return sHelpText; + } + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_DefaultHelpProvider_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::DefaultHelpProvider()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/defaulthelpprovider.hxx b/extensions/source/propctrlr/defaulthelpprovider.hxx new file mode 100644 index 0000000000..f647937dda --- /dev/null +++ b/extensions/source/propctrlr/defaulthelpprovider.hxx @@ -0,0 +1,78 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/inspection/XPropertyControlObserver.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/inspection/XObjectInspectorUI.hpp> + +#include <cppuhelper/implbase.hxx> + +namespace vcl { class Window; } + + +namespace pcr +{ + + + //= DefaultHelpProvider + + typedef ::cppu::WeakImplHelper < css::inspection::XPropertyControlObserver + , css::lang::XInitialization + , css::lang::XServiceInfo + > DefaultHelpProvider_Base; + class DefaultHelpProvider final : public DefaultHelpProvider_Base + { + private: + bool m_bConstructed; + css::uno::Reference< css::inspection::XObjectInspectorUI > + m_xInspectorUI; + + public: + DefaultHelpProvider(); + + private: + virtual ~DefaultHelpProvider() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames () override; + + // XPropertyControlObserver + virtual void SAL_CALL focusGained( const css::uno::Reference< css::inspection::XPropertyControl >& Control ) override; + virtual void SAL_CALL valueChanged( const css::uno::Reference< css::inspection::XPropertyControl >& Control ) override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // Service constructors + void create( const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxUI ); + + static vcl::Window* impl_getVclControlWindow_nothrow( const css::uno::Reference< css::inspection::XPropertyControl >& _rxControl ); + static OUString impl_getHelpText_nothrow( const css::uno::Reference< css::inspection::XPropertyControl >& _rxControl ); + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/editpropertyhandler.cxx b/extensions/source/propctrlr/editpropertyhandler.cxx new file mode 100644 index 0000000000..f063489e64 --- /dev/null +++ b/extensions/source/propctrlr/editpropertyhandler.cxx @@ -0,0 +1,314 @@ +/* -*- 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 "editpropertyhandler.hxx" +#include "formstrings.hxx" +#include "formmetadata.hxx" + +#include <com/sun/star/inspection/XObjectInspectorUI.hpp> +#include <com/sun/star/lang/NullPointerException.hpp> +#include <comphelper/diagnose_ex.hxx> + +namespace +{ + enum class TextType + { + SINGLELINE = 0, + MULTILINE = 1, + RICHTEXT = 2 + }; +}; +namespace pcr +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::script; + using namespace ::com::sun::star::frame; + using namespace ::com::sun::star::inspection; + + + //= EditPropertyHandler + + + EditPropertyHandler::EditPropertyHandler( const Reference< XComponentContext >& _rxContext ) + :PropertyHandlerComponent( _rxContext ) + { + } + + + EditPropertyHandler::~EditPropertyHandler( ) + { + } + + + OUString EditPropertyHandler::getImplementationName( ) + { + return "com.sun.star.comp.extensions.EditPropertyHandler"; + } + + + Sequence< OUString > EditPropertyHandler::getSupportedServiceNames( ) + { + return { "com.sun.star.form.inspection.EditPropertyHandler" }; + } + + + Any SAL_CALL EditPropertyHandler::getPropertyValue( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + Any aReturn; + try + { + switch ( nPropId ) + { + case PROPERTY_ID_SHOW_SCROLLBARS: + { + bool bHasVScroll = false; + m_xComponent->getPropertyValue( PROPERTY_VSCROLL ) >>= bHasVScroll; + bool bHasHScroll = false; + m_xComponent->getPropertyValue( PROPERTY_HSCROLL ) >>= bHasHScroll; + + aReturn <<= static_cast<sal_Int32>( ( bHasVScroll ? 2 : 0 ) + ( bHasHScroll ? 1 : 0 ) ); + } + break; + + case PROPERTY_ID_TEXTTYPE: + { + TextType nTextType = TextType::SINGLELINE; + bool bRichText = false; + OSL_VERIFY( m_xComponent->getPropertyValue( PROPERTY_RICHTEXT ) >>= bRichText ); + if ( bRichText ) + nTextType = TextType::RICHTEXT; + else + { + bool bMultiLine = false; + OSL_VERIFY( m_xComponent->getPropertyValue( PROPERTY_MULTILINE ) >>= bMultiLine ); + if ( bMultiLine ) + nTextType = TextType::MULTILINE; + else + nTextType = TextType::SINGLELINE; + } + aReturn <<= static_cast<sal_Int32>(nTextType); + } + break; + + + default: + OSL_FAIL( "EditPropertyHandler::getPropertyValue: cannot handle this property!" ); + break; + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EditPropertyHandler::getPropertyValue" ); + } + + return aReturn; + } + + + void SAL_CALL EditPropertyHandler::setPropertyValue( const OUString& _rPropertyName, const Any& _rValue ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + try + { + switch ( nPropId ) + { + case PROPERTY_ID_SHOW_SCROLLBARS: + { + sal_Int32 nScrollbars = 0; + _rValue >>= nScrollbars; + + bool bHasVScroll = 0 != ( nScrollbars & 2 ); + bool bHasHScroll = 0 != ( nScrollbars & 1 ); + + m_xComponent->setPropertyValue( PROPERTY_VSCROLL, Any( bHasVScroll ) ); + m_xComponent->setPropertyValue( PROPERTY_HSCROLL, Any( bHasHScroll ) ); + } + break; + + case PROPERTY_ID_TEXTTYPE: + { + bool bMultiLine = false; + bool bRichText = false; + sal_Int32 nTextType = static_cast<sal_Int32>(TextType::SINGLELINE); + OSL_VERIFY( _rValue >>= nTextType ); + switch ( static_cast<TextType>(nTextType) ) + { + case TextType::SINGLELINE: bMultiLine = bRichText = false; break; + case TextType::MULTILINE: bMultiLine = true; bRichText = false; break; + case TextType::RICHTEXT: bMultiLine = true; bRichText = true; break; + default: + OSL_FAIL( "EditPropertyHandler::setPropertyValue: invalid text type!" ); + } + + m_xComponent->setPropertyValue( PROPERTY_MULTILINE, Any( bMultiLine ) ); + m_xComponent->setPropertyValue( PROPERTY_RICHTEXT, Any( bRichText ) ); + } + break; + + default: + OSL_FAIL( "EditPropertyHandler::setPropertyValue: cannot handle this id!" ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EditPropertyHandler::setPropertyValue" ); + } + } + + + bool EditPropertyHandler::implHaveBothScrollBarProperties() const + { + // have a "Scrollbars" property if the object supports both "HScroll" and "VScroll" + Reference< XPropertySetInfo > xPSI; + if ( m_xComponent.is() ) + xPSI = m_xComponent->getPropertySetInfo(); + + return xPSI.is() + && xPSI->hasPropertyByName( PROPERTY_HSCROLL ) + && xPSI->hasPropertyByName( PROPERTY_VSCROLL ); + } + + + bool EditPropertyHandler::implHaveTextTypeProperty() const + { + // have a "Scrollbars" property if the object supports both "HScroll" and "VScroll" + Reference< XPropertySetInfo > xPSI; + if ( m_xComponent.is() ) + xPSI = m_xComponent->getPropertySetInfo(); + + return xPSI.is() + && xPSI->hasPropertyByName( PROPERTY_RICHTEXT ) + && xPSI->hasPropertyByName( PROPERTY_MULTILINE ); + } + + + Sequence< Property > EditPropertyHandler::doDescribeSupportedProperties() const + { + std::vector< Property > aProperties; + + if ( implHaveBothScrollBarProperties() ) + addInt32PropertyDescription( aProperties, PROPERTY_SHOW_SCROLLBARS ); + + if ( implHaveTextTypeProperty() ) + addInt32PropertyDescription( aProperties, PROPERTY_TEXTTYPE ); + + if ( aProperties.empty() ) + return Sequence< Property >(); + return comphelper::containerToSequence(aProperties); + } + + + Sequence< OUString > SAL_CALL EditPropertyHandler::getSupersededProperties( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + std::vector< OUString > aSuperseded; + if ( implHaveBothScrollBarProperties() ) + { + aSuperseded.push_back( PROPERTY_HSCROLL ); + aSuperseded.push_back( PROPERTY_VSCROLL ); + } + if ( implHaveTextTypeProperty() ) + { + aSuperseded.push_back( PROPERTY_RICHTEXT ); + aSuperseded.push_back( PROPERTY_MULTILINE ); + } + if ( aSuperseded.empty() ) + return Sequence< OUString >(); + return comphelper::containerToSequence(aSuperseded); + } + + + Sequence< OUString > SAL_CALL EditPropertyHandler::getActuatingProperties( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + std::vector< OUString > aInterestingActuatingProps; + if ( implHaveTextTypeProperty() ) + aInterestingActuatingProps.push_back( PROPERTY_TEXTTYPE ); + aInterestingActuatingProps.push_back( PROPERTY_MULTILINE ); + return comphelper::containerToSequence(aInterestingActuatingProps); + } + + + void SAL_CALL EditPropertyHandler::actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const Any& _rNewValue, const Any& /*_rOldValue*/, const Reference< XObjectInspectorUI >& _rxInspectorUI, sal_Bool ) + { + if ( !_rxInspectorUI.is() ) + throw NullPointerException(); + + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nActuatingPropId( impl_getPropertyId_throwRuntime( _rActuatingPropertyName ) ); + switch ( nActuatingPropId ) + { + case PROPERTY_ID_TEXTTYPE: + { + sal_Int32 nTextTypeInt = static_cast<sal_Int32>(TextType::SINGLELINE); + getPropertyValue( PROPERTY_TEXTTYPE ) >>= nTextTypeInt; + + TextType nTextType = static_cast<TextType>(nTextTypeInt); + + if ( impl_isSupportedProperty_nothrow( PROPERTY_ID_WORDBREAK ) ) + _rxInspectorUI->enablePropertyUI( PROPERTY_WORDBREAK, nTextType == TextType::RICHTEXT ); + _rxInspectorUI->enablePropertyUI( PROPERTY_MAXTEXTLEN, nTextType != TextType::RICHTEXT ); + _rxInspectorUI->enablePropertyUI( PROPERTY_ECHO_CHAR, nTextType == TextType::SINGLELINE ); + _rxInspectorUI->enablePropertyUI( PROPERTY_FONT, nTextType != TextType::RICHTEXT ); + _rxInspectorUI->enablePropertyUI( PROPERTY_ALIGN, nTextType != TextType::RICHTEXT ); + _rxInspectorUI->enablePropertyUI( PROPERTY_DEFAULT_TEXT, nTextType != TextType::RICHTEXT ); + _rxInspectorUI->enablePropertyUI( PROPERTY_SHOW_SCROLLBARS, nTextType != TextType::SINGLELINE ); + _rxInspectorUI->enablePropertyUI( PROPERTY_LINEEND_FORMAT, nTextType != TextType::SINGLELINE ); + _rxInspectorUI->enablePropertyUI( PROPERTY_VERTICAL_ALIGN, nTextType == TextType::SINGLELINE ); + + _rxInspectorUI->showCategory( "Data", nTextType != TextType::RICHTEXT ); + } + break; + + case PROPERTY_ID_MULTILINE: + { + bool bIsMultiline = false; + _rNewValue >>= bIsMultiline; + + _rxInspectorUI->enablePropertyUI( PROPERTY_SHOW_SCROLLBARS, bIsMultiline ); + _rxInspectorUI->enablePropertyUI( PROPERTY_ECHO_CHAR, !bIsMultiline ); + _rxInspectorUI->enablePropertyUI( PROPERTY_LINEEND_FORMAT, bIsMultiline ); + } + break; + + default: + OSL_FAIL( "EditPropertyHandler::actuatingPropertyChanged: cannot handle this id!" ); + } + } + + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_EditPropertyHandler_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::EditPropertyHandler(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/editpropertyhandler.hxx b/extensions/source/propctrlr/editpropertyhandler.hxx new file mode 100644 index 0000000000..33ad8df983 --- /dev/null +++ b/extensions/source/propctrlr/editpropertyhandler.hxx @@ -0,0 +1,68 @@ +/* -*- 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 . + */ + +#pragma once + +#include "propertyhandler.hxx" + + +namespace pcr +{ + + + //= EditPropertyHandler + + class EditPropertyHandler; + /** a property handler for any virtual string properties + */ + class EditPropertyHandler : public PropertyHandlerComponent + { + public: + explicit EditPropertyHandler( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext + ); + + protected: + virtual ~EditPropertyHandler() override; + + protected: + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames () override; + + // XPropertyHandler overriables + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& _rPropertyName ) override; + virtual void SAL_CALL setPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rValue ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupersededProperties( ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getActuatingProperties( ) override; + virtual void SAL_CALL actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const css::uno::Any& _rNewValue, const css::uno::Any& _rOldValue, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI, sal_Bool ) override; + + // PropertyHandler overridables + virtual css::uno::Sequence< css::beans::Property > + doDescribeSupportedProperties() const override; + private: + bool implHaveBothScrollBarProperties() const; + bool implHaveTextTypeProperty() const; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/eformshelper.cxx b/extensions/source/propctrlr/eformshelper.cxx new file mode 100644 index 0000000000..40aceed5df --- /dev/null +++ b/extensions/source/propctrlr/eformshelper.cxx @@ -0,0 +1,762 @@ +/* -*- 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 <string_view> + +#include "eformshelper.hxx" +#include "formstrings.hxx" +#include <strings.hrc> +#include "modulepcr.hxx" +#include "propeventtranslation.hxx" +#include "formbrowsertools.hxx" + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/xforms/XFormsUIHelper1.hpp> +#include <com/sun/star/xsd/DataTypeClass.hpp> +#include <com/sun/star/form/binding/XListEntrySink.hpp> +#include <comphelper/diagnose_ex.hxx> + +#include <algorithm> +#include <o3tl/functional.hxx> + +namespace pcr +{ + + + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::form::binding; + using namespace ::com::sun::star::xsd; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::form; + + + //= file-local helpers + + namespace + { + + OUString composeModelElementUIName( std::u16string_view _rModelName, std::u16string_view _rElementName ) + { + OUString a = OUString::Concat("[") + + _rModelName + "] " + + _rElementName; + return a; + } + } + + + //= EFormsHelper + + + EFormsHelper::EFormsHelper( ::osl::Mutex& _rMutex, const Reference< XPropertySet >& _rxControlModel, const Reference< frame::XModel >& _rxContextDocument ) + :m_xControlModel( _rxControlModel ) + ,m_aPropertyListeners( _rMutex ) + { + OSL_ENSURE( _rxControlModel.is(), "EFormsHelper::EFormsHelper: invalid control model!" ); + m_xBindableControl.set(_rxControlModel, css::uno::UNO_QUERY); + + m_xDocument.set(_rxContextDocument, css::uno::UNO_QUERY); + OSL_ENSURE( m_xDocument.is(), "EFormsHelper::EFormsHelper: invalid document!" ); + + } + + + bool EFormsHelper::isEForm( const Reference< frame::XModel >& _rxContextDocument ) + { + try + { + Reference< xforms::XFormsSupplier > xDocument( _rxContextDocument, UNO_QUERY ); + if ( !xDocument.is() ) + return false; + + return xDocument->getXForms().is(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::isEForm" ); + } + return false; + } + + + bool EFormsHelper::canBindToDataType( sal_Int32 _nDataType ) const + { + if ( !m_xBindableControl.is() ) + // cannot bind at all + return false; + + // some types cannot be bound, independent from the control type + if ( ( DataTypeClass::hexBinary == _nDataType ) + || ( DataTypeClass::base64Binary == _nDataType ) + || ( DataTypeClass::QName == _nDataType ) + || ( DataTypeClass::NOTATION == _nDataType ) + ) + return false; + + bool bCan = false; + try + { + // classify the control model + sal_Int16 nControlType = FormComponentType::CONTROL; + OSL_VERIFY( m_xControlModel->getPropertyValue( PROPERTY_CLASSID ) >>= nControlType ); + + // some lists + sal_Int16 const nNumericCompatibleTypes[] = { DataTypeClass::DECIMAL, DataTypeClass::FLOAT, DataTypeClass::DOUBLE, 0 }; + sal_Int16 const nDateCompatibleTypes[] = { DataTypeClass::DATE, 0 }; + sal_Int16 const nTimeCompatibleTypes[] = { DataTypeClass::TIME, 0 }; + sal_Int16 const nCheckboxCompatibleTypes[] = { DataTypeClass::BOOLEAN, DataTypeClass::STRING, DataTypeClass::anyURI, 0 }; + sal_Int16 const nRadiobuttonCompatibleTypes[] = { DataTypeClass::STRING, DataTypeClass::anyURI, 0 }; + sal_Int16 const nFormattedCompatibleTypes[] = { DataTypeClass::DECIMAL, DataTypeClass::FLOAT, DataTypeClass::DOUBLE, DataTypeClass::DATETIME, DataTypeClass::DATE, DataTypeClass::TIME, 0 }; + + sal_Int16 const * pCompatibleTypes = nullptr; + switch ( nControlType ) + { + case FormComponentType::SPINBUTTON: + case FormComponentType::NUMERICFIELD: + pCompatibleTypes = nNumericCompatibleTypes; + break; + case FormComponentType::DATEFIELD: + pCompatibleTypes = nDateCompatibleTypes; + break; + case FormComponentType::TIMEFIELD: + pCompatibleTypes = nTimeCompatibleTypes; + break; + case FormComponentType::CHECKBOX: + pCompatibleTypes = nCheckboxCompatibleTypes; + break; + case FormComponentType::RADIOBUTTON: + pCompatibleTypes = nRadiobuttonCompatibleTypes; + break; + + case FormComponentType::TEXTFIELD: + { + // both the normal text field, and the formatted field, claim to be a TEXTFIELD + // need to distinguish by service name + Reference< XServiceInfo > xSI( m_xControlModel, UNO_QUERY ); + OSL_ENSURE( xSI.is(), "EFormsHelper::canBindToDataType: a control model which has no service info?" ); + if ( xSI.is() ) + { + if ( xSI->supportsService( SERVICE_COMPONENT_FORMATTEDFIELD ) ) + { + pCompatibleTypes = nFormattedCompatibleTypes; + break; + } + } + [[fallthrough]]; + } + case FormComponentType::LISTBOX: + case FormComponentType::COMBOBOX: + // edit fields and list/combo boxes can be bound to anything + bCan = true; + } + + if ( !bCan && pCompatibleTypes ) + { + if ( _nDataType == -1 ) + { + // the control can be bound to at least one type, and exactly this is being asked for + bCan = true; + } + else + { + while ( *pCompatibleTypes && !bCan ) + bCan = ( *pCompatibleTypes++ == _nDataType ); + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::canBindToDataType" ); + } + + return bCan; + } + + + bool EFormsHelper::isListEntrySink() const + { + bool bIs = false; + try + { + Reference< XListEntrySink > xAsSink( m_xControlModel, UNO_QUERY ); + bIs = xAsSink.is(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::isListEntrySink" ); + } + return bIs; + } + + + void EFormsHelper::impl_switchBindingListening_throw( bool _bDoListening, const Reference< XPropertyChangeListener >& _rxListener ) + { + Reference< XPropertySet > xBindingProps; + if ( m_xBindableControl.is() ) + xBindingProps.set(m_xBindableControl->getValueBinding(), css::uno::UNO_QUERY); + if ( !xBindingProps.is() ) + return; + + if ( _bDoListening ) + { + xBindingProps->addPropertyChangeListener( OUString(), _rxListener ); + } + else + { + xBindingProps->removePropertyChangeListener( OUString(), _rxListener ); + } + } + + + void EFormsHelper::registerBindingListener( const Reference< XPropertyChangeListener >& _rxBindingListener ) + { + if ( !_rxBindingListener.is() ) + return; + impl_toggleBindingPropertyListening_throw( true, _rxBindingListener ); + } + + + void EFormsHelper::impl_toggleBindingPropertyListening_throw( bool _bDoListen, const Reference< XPropertyChangeListener >& _rxConcreteListenerOrNull ) + { + if ( !_bDoListen ) + { + ::comphelper::OInterfaceIteratorHelper3 aListenerIterator(m_aPropertyListeners); + while ( aListenerIterator.hasMoreElements() ) + { + PropertyEventTranslation* pTranslator = dynamic_cast< PropertyEventTranslation* >( aListenerIterator.next().get() ); + OSL_ENSURE( pTranslator, "EFormsHelper::impl_toggleBindingPropertyListening_throw: invalid listener element in my container!" ); + if ( !pTranslator ) + continue; + + Reference< XPropertyChangeListener > xEventSourceTranslator( pTranslator ); + if ( _rxConcreteListenerOrNull.is() ) + { + if ( pTranslator->getDelegator() == _rxConcreteListenerOrNull ) + { + impl_switchBindingListening_throw( false, xEventSourceTranslator ); + m_aPropertyListeners.removeInterface( xEventSourceTranslator ); + break; + } + } + else + { + impl_switchBindingListening_throw( false, xEventSourceTranslator ); + } + } + } + else + { + if ( _rxConcreteListenerOrNull.is() ) + { + Reference< XPropertyChangeListener > xEventSourceTranslator( new PropertyEventTranslation( _rxConcreteListenerOrNull, m_xBindableControl ) ); + m_aPropertyListeners.addInterface( xEventSourceTranslator ); + impl_switchBindingListening_throw( true, xEventSourceTranslator ); + } + else + { + ::comphelper::OInterfaceIteratorHelper3 aListenerIterator(m_aPropertyListeners); + while ( aListenerIterator.hasMoreElements() ) + impl_switchBindingListening_throw( true, aListenerIterator.next() ); + } + } + } + + + void EFormsHelper::revokeBindingListener( const Reference< XPropertyChangeListener >& _rxBindingListener ) + { + impl_toggleBindingPropertyListening_throw( false, _rxBindingListener ); + } + + + void EFormsHelper::getFormModelNames( std::vector< OUString >& /* [out] */ _rModelNames ) const + { + if ( !m_xDocument.is() ) + return; + + try + { + _rModelNames.resize( 0 ); + + Reference< XNameContainer > xForms( m_xDocument->getXForms() ); + OSL_ENSURE( xForms.is(), "EFormsHelper::getFormModelNames: invalid forms container!" ); + if ( xForms.is() ) + { + const Sequence< OUString > aModelNames = xForms->getElementNames(); + _rModelNames.resize( aModelNames.getLength() ); + std::copy( aModelNames.begin(), aModelNames.end(), _rModelNames.begin() ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::getFormModelNames" ); + } + } + + + void EFormsHelper::getBindingNames( const OUString& _rModelName, std::vector< OUString >& /* [out] */ _rBindingNames ) const + { + _rBindingNames.resize( 0 ); + try + { + Reference< xforms::XModel > xModel( getFormModelByName( _rModelName ) ); + if ( xModel.is() ) + { + Reference< XNameAccess > xBindings( xModel->getBindings(), UNO_QUERY ); + OSL_ENSURE( xBindings.is(), "EFormsHelper::getBindingNames: invalid bindings container obtained from the model!" ); + if ( xBindings.is() ) + { + const Sequence< OUString > aNames = xBindings->getElementNames(); + _rBindingNames.resize( aNames.getLength() ); + std::copy( aNames.begin(), aNames.end(), _rBindingNames.begin() ); + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::getBindingNames" ); + } + } + + + Reference< xforms::XModel > EFormsHelper::getFormModelByName( const OUString& _rModelName ) const + { + Reference< xforms::XModel > xReturn; + try + { + Reference< XNameContainer > xForms( m_xDocument->getXForms() ); + OSL_ENSURE( xForms.is(), "EFormsHelper::getFormModelByName: invalid forms container!" ); + if ( xForms.is() ) + OSL_VERIFY( xForms->getByName( _rModelName ) >>= xReturn ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::getFormModelByName" ); + } + return xReturn; + } + + + Reference< xforms::XModel > EFormsHelper::getCurrentFormModel() const + { + Reference< xforms::XModel > xModel; + try + { + Reference< XPropertySet > xBinding( getCurrentBinding() ); + if ( xBinding.is() ) + { + OSL_VERIFY( xBinding->getPropertyValue( PROPERTY_MODEL ) >>= xModel ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::getCurrentFormModel" ); + } + return xModel; + } + + + OUString EFormsHelper::getCurrentFormModelName() const + { + OUString sModelName; + try + { + Reference< xforms::XModel > xFormsModel( getCurrentFormModel() ); + if ( xFormsModel.is() ) + sModelName = xFormsModel->getID(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::getCurrentFormModel" ); + } + return sModelName; + } + + + Reference< XPropertySet > EFormsHelper::getCurrentBinding() const + { + Reference< XPropertySet > xBinding; + + try + { + if ( m_xBindableControl.is() ) + xBinding.set(m_xBindableControl->getValueBinding(), css::uno::UNO_QUERY); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::getCurrentBinding" ); + } + + return xBinding; + } + + + OUString EFormsHelper::getCurrentBindingName() const + { + OUString sBindingName; + try + { + Reference< XPropertySet > xBinding( getCurrentBinding() ); + if ( xBinding.is() ) + xBinding->getPropertyValue( PROPERTY_BINDING_ID ) >>= sBindingName; + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::getCurrentBindingName" ); + } + return sBindingName; + } + + + Reference< XListEntrySource > EFormsHelper::getCurrentListSourceBinding() const + { + Reference< XListEntrySource > xReturn; + try + { + Reference< XListEntrySink > xAsSink( m_xControlModel, UNO_QUERY ); + OSL_ENSURE( xAsSink.is(), "EFormsHelper::getCurrentListSourceBinding: you should have used isListEntrySink before!" ); + if ( xAsSink.is() ) + xReturn = xAsSink->getListEntrySource(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::getCurrentListSourceBinding" ); + } + return xReturn; + } + + + void EFormsHelper::setListSourceBinding( const Reference< XListEntrySource >& _rxListSource ) + { + try + { + Reference< XListEntrySink > xAsSink( m_xControlModel, UNO_QUERY ); + OSL_ENSURE( xAsSink.is(), "EFormsHelper::setListSourceBinding: you should have used isListEntrySink before!" ); + if ( xAsSink.is() ) + xAsSink->setListEntrySource( _rxListSource ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::setListSourceBinding" ); + } + } + + + void EFormsHelper::setBinding( const Reference< css::beans::XPropertySet >& _rxBinding ) + { + if ( !m_xBindableControl.is() ) + return; + + try + { + Reference< XPropertySet > xOldBinding( m_xBindableControl->getValueBinding(), UNO_QUERY ); + + Reference< XValueBinding > xBinding( _rxBinding, UNO_QUERY ); + OSL_ENSURE( xBinding.is() || !_rxBinding.is(), "EFormsHelper::setBinding: invalid binding!" ); + + impl_toggleBindingPropertyListening_throw( false, nullptr ); + m_xBindableControl->setValueBinding( xBinding ); + impl_toggleBindingPropertyListening_throw( true, nullptr ); + + std::set< OUString > aSet; + firePropertyChanges( xOldBinding, _rxBinding, aSet ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::setBinding" ); + } + } + + + Reference< XPropertySet > EFormsHelper::getOrCreateBindingForModel( const OUString& _rTargetModel, const OUString& _rBindingName ) const + { + OSL_ENSURE( !_rBindingName.isEmpty(), "EFormsHelper::getOrCreateBindingForModel: invalid binding name!" ); + return implGetOrCreateBinding( _rTargetModel, _rBindingName ); + } + + + Reference< XPropertySet > EFormsHelper::implGetOrCreateBinding( const OUString& _rTargetModel, const OUString& _rBindingName ) const + { + OSL_ENSURE( !( _rTargetModel.isEmpty() && !_rBindingName.isEmpty() ), "EFormsHelper::implGetOrCreateBinding: no model, but a binding name?" ); + + Reference< XPropertySet > xBinding; + try + { + OUString sTargetModel( _rTargetModel ); + // determine the model which the binding should belong to + if ( sTargetModel.isEmpty() ) + { + std::vector< OUString > aModelNames; + getFormModelNames( aModelNames ); + if ( !aModelNames.empty() ) + sTargetModel = *aModelNames.begin(); + OSL_ENSURE( !sTargetModel.isEmpty(), "EFormsHelper::implGetOrCreateBinding: unable to obtain a default model!" ); + } + Reference< xforms::XModel > xModel( getFormModelByName( sTargetModel ) ); + Reference< XNameAccess > xBindingNames( xModel.is() ? xModel->getBindings() : Reference< XSet >(), UNO_QUERY ); + if ( xBindingNames.is() ) + { + // get or create the binding instance + if ( !_rBindingName.isEmpty() ) + { + if ( xBindingNames->hasByName( _rBindingName ) ) + OSL_VERIFY( xBindingNames->getByName( _rBindingName ) >>= xBinding ); + else + { + xBinding = xModel->createBinding( ); + if ( xBinding.is() ) + { + xBinding->setPropertyValue( PROPERTY_BINDING_ID, Any( _rBindingName ) ); + xModel->getBindings()->insert( Any( xBinding ) ); + } + } + } + else + { + xBinding = xModel->createBinding( ); + if ( xBinding.is() ) + { + // find a nice name for it + OUString sBaseName(PcrRes(RID_STR_BINDING_NAME) + " "); + OUString sNewName; + sal_Int32 nNumber = 1; + do + { + sNewName = sBaseName + OUString::number( nNumber++ ); + } + while ( xBindingNames->hasByName( sNewName ) ); + Reference< XNamed > xName( xBinding, UNO_QUERY_THROW ); + xName->setName( sNewName ); + // and insert into the model + xModel->getBindings()->insert( Any( xBinding ) ); + } + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + + return xBinding; + } + + + namespace + { + + struct PropertyBagInserter + { + private: + PropertyBag& m_rProperties; + + public: + explicit PropertyBagInserter( PropertyBag& rProperties ) : m_rProperties( rProperties ) { } + + void operator()( const Property& _rProp ) + { + m_rProperties.insert( _rProp ); + } + }; + + + Reference< XPropertySetInfo > collectPropertiesGetInfo( const Reference< XPropertySet >& _rxProps, PropertyBag& _rBag ) + { + Reference< XPropertySetInfo > xInfo; + if ( _rxProps.is() ) + xInfo = _rxProps->getPropertySetInfo(); + if ( xInfo.is() ) + { + const Sequence< Property > aProperties = xInfo->getProperties(); + std::for_each( aProperties.begin(), aProperties.end(), + PropertyBagInserter( _rBag ) + ); + } + return xInfo; + } + } + + + OUString EFormsHelper::getModelElementUIName( const EFormsHelper::ModelElementType _eType, const Reference< XPropertySet >& _rxElement ) + { + OUString sUIName; + try + { + // determine the model which the element belongs to + Reference< xforms::XFormsUIHelper1 > xHelper; + if ( _rxElement.is() ) + _rxElement->getPropertyValue( PROPERTY_MODEL ) >>= xHelper; + OSL_ENSURE( xHelper.is(), "EFormsHelper::getModelElementUIName: invalid element or model!" ); + if ( xHelper.is() ) + { + OUString sElementName = ( _eType == Submission ) ? xHelper->getSubmissionName( _rxElement, true ) : xHelper->getBindingName( _rxElement, true ); + Reference< xforms::XModel > xModel( xHelper, UNO_QUERY_THROW ); + sUIName = composeModelElementUIName( xModel->getID(), sElementName ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::getModelElementUIName" ); + } + + return sUIName; + } + + + Reference< XPropertySet > EFormsHelper::getModelElementFromUIName( const EFormsHelper::ModelElementType _eType, const OUString& _rUIName ) const + { + const MapStringToPropertySet& rMapUINameToElement( ( _eType == Submission ) ? m_aSubmissionUINames : m_aBindingUINames ); + MapStringToPropertySet::const_iterator pos = rMapUINameToElement.find( _rUIName ); + OSL_ENSURE( pos != rMapUINameToElement.end(), "EFormsHelper::getModelElementFromUIName: didn't find it!" ); + + return ( pos != rMapUINameToElement.end() ) ? pos->second : Reference< XPropertySet >(); + } + + + void EFormsHelper::getAllElementUINames( const ModelElementType _eType, std::vector< OUString >& /* [out] */ _rElementNames, bool _bPrepentEmptyEntry ) + { + MapStringToPropertySet& rMapUINameToElement( ( _eType == Submission ) ? m_aSubmissionUINames : m_aBindingUINames ); + rMapUINameToElement.clear(); + _rElementNames.resize( 0 ); + + if ( _bPrepentEmptyEntry ) + rMapUINameToElement[ OUString() ].clear(); + + try + { + // obtain the model names + std::vector< OUString > aModels; + getFormModelNames( aModels ); + _rElementNames.reserve( aModels.size() * 2 ); // heuristics + + // for every model, obtain the element + for (auto const& modelName : aModels) + { + Reference< xforms::XModel > xModel = getFormModelByName(modelName); + OSL_ENSURE( xModel.is(), "EFormsHelper::getAllElementUINames: inconsistency in the models!" ); + Reference< xforms::XFormsUIHelper1 > xHelper( xModel, UNO_QUERY ); + + Reference< XIndexAccess > xElements; + if ( xModel.is() ) + xElements.set(( _eType == Submission ) ? xModel->getSubmissions() : xModel->getBindings(), css::uno::UNO_QUERY); + if ( !xElements.is() ) + break; + + sal_Int32 nElementCount = xElements->getCount(); + for ( sal_Int32 i = 0; i < nElementCount; ++i ) + { + Reference< XPropertySet > xElement( xElements->getByIndex( i ), UNO_QUERY ); + OSL_ENSURE( xElement.is(), "EFormsHelper::getAllElementUINames: empty element!" ); + if ( !xElement.is() ) + continue; +#if OSL_DEBUG_LEVEL > 0 + { + Reference< xforms::XModel > xElementsModel; + xElement->getPropertyValue( PROPERTY_MODEL ) >>= xElementsModel; + OSL_ENSURE( xElementsModel == xModel, "EFormsHelper::getAllElementUINames: inconsistency in the model-element relationship!" ); + if ( xElementsModel != xModel ) + xElement->setPropertyValue( PROPERTY_MODEL, Any( xModel ) ); + } +#endif + OUString sElementName = ( _eType == Submission ) ? xHelper->getSubmissionName( xElement, true ) : xHelper->getBindingName( xElement, true ); + OUString sUIName = composeModelElementUIName( modelName, sElementName ); + + OSL_ENSURE( rMapUINameToElement.find( sUIName ) == rMapUINameToElement.end(), "EFormsHelper::getAllElementUINames: duplicate name!" ); + rMapUINameToElement.emplace( sUIName, xElement ); + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::getAllElementUINames" ); + } + + _rElementNames.resize( rMapUINameToElement.size() ); + std::transform( rMapUINameToElement.begin(), rMapUINameToElement.end(), _rElementNames.begin(), + ::o3tl::select1st< MapStringToPropertySet::value_type >() ); + } + + + void EFormsHelper::firePropertyChange( const OUString& _rName, const Any& _rOldValue, const Any& _rNewValue ) const + { + if ( m_aPropertyListeners.getLength() == 0 ) + return; + + if ( _rOldValue == _rNewValue ) + return; + + try + { + PropertyChangeEvent aEvent; + + aEvent.Source = m_xBindableControl.get(); + aEvent.PropertyName = _rName; + aEvent.OldValue = _rOldValue; + aEvent.NewValue = _rNewValue; + + const_cast< EFormsHelper* >( this )->m_aPropertyListeners.notifyEach( &XPropertyChangeListener::propertyChange, aEvent ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::firePropertyChange" ); + } + } + + + void EFormsHelper::firePropertyChanges( const Reference< XPropertySet >& _rxOldProps, const Reference< XPropertySet >& _rxNewProps, std::set< OUString >& _rFilter ) const + { + if ( m_aPropertyListeners.getLength() == 0 ) + return; + + try + { + PropertyBag aProperties; + Reference< XPropertySetInfo > xOldInfo = collectPropertiesGetInfo( _rxOldProps, aProperties ); + Reference< XPropertySetInfo > xNewInfo = collectPropertiesGetInfo( _rxNewProps, aProperties ); + + for (auto const& property : aProperties) + { + if ( _rFilter.find( property.Name ) != _rFilter.end() ) + continue; + + Any aOldValue( nullptr, property.Type ); + if ( xOldInfo.is() && xOldInfo->hasPropertyByName( property.Name ) ) + aOldValue = _rxOldProps->getPropertyValue( property.Name ); + + Any aNewValue( nullptr, property.Type ); + if ( xNewInfo.is() && xNewInfo->hasPropertyByName( property.Name ) ) + aNewValue = _rxNewProps->getPropertyValue( property.Name ); + + firePropertyChange( property.Name, aOldValue, aNewValue ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsHelper::firePropertyChanges" ); + } + } + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/eformshelper.hxx b/extensions/source/propctrlr/eformshelper.hxx new file mode 100644 index 0000000000..e88cb7491d --- /dev/null +++ b/extensions/source/propctrlr/eformshelper.hxx @@ -0,0 +1,255 @@ +/* -*- 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 . + */ + +#pragma once + +#include "pcrcommon.hxx" + +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/xforms/XModel.hpp> +#include <com/sun/star/xforms/XFormsSupplier.hpp> +#include <com/sun/star/form/binding/XBindableValue.hpp> +#include <com/sun/star/form/binding/XListEntrySource.hpp> +#include <osl/mutex.hxx> +#include <rtl/ustring.hxx> + +#include <vector> +#include <set> +#include <map> + + +namespace pcr +{ + + + typedef std::map< OUString, css::uno::Reference< css::beans::XPropertySet >, std::less< OUString > > + MapStringToPropertySet; + + + //= EFormsHelper + + class EFormsHelper + { + protected: + css::uno::Reference< css::beans::XPropertySet > + m_xControlModel; + css::uno::Reference< css::form::binding::XBindableValue > + m_xBindableControl; + css::uno::Reference< css::xforms::XFormsSupplier > + m_xDocument; + PropertyChangeListeners + m_aPropertyListeners; + MapStringToPropertySet + m_aSubmissionUINames; // only filled upon request + MapStringToPropertySet + m_aBindingUINames; // only filled upon request + + public: + EFormsHelper( + ::osl::Mutex& _rMutex, + const css::uno::Reference< css::beans::XPropertySet >& _rxControlModel, + const css::uno::Reference< css::frame::XModel >& _rxContextDocument + ); + + /** determines whether the given document is an eForm + + If this method returns <FALSE/>, you cannot instantiate an EFormsHelper with + this document, since none of its functionality will be available then. + */ + static bool + isEForm( + const css::uno::Reference< css::frame::XModel >& _rxContextDocument + ); + + /** registers a listener to be notified when any aspect of the binding changes. + + The listener will be registered at the current binding of the control model. If the binding + changes (see <method>setBinding</method>), the listener will be revoked from the old binding, + registered at the new binding, and for all properties which differ between both bindings, + the listener will be notified. + @see revokeBindingListener + */ + void registerBindingListener( + const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxBindingListener + ); + + /** revokes the binding listener which has previously been registered + @see registerBindingListener + */ + void revokeBindingListener( + const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxBindingListener + ); + + /** checks whether it's possible to bind the control model to a given XSD data type + + @param _nDataType + the data type which should be bound. If this is -1, <TRUE/> is returned if the control model + can be bound to <em>any</em> data type. + */ + bool canBindToDataType( sal_Int32 _nDataType = -1 ) const; + + /** checks whether the control model can be bound to any XSD data type + */ + bool canBindToAnyDataType() const { return canBindToDataType(); } + + /** checks whether the control model is a source for list entries, as supplied by XML data bindings + */ + bool isListEntrySink() const; + + /** retrieves the names of all XForms models in the document the control lives in + */ + void getFormModelNames( std::vector< OUString >& /* [out] */ _rModelNames ) const; + + /** retrieves the names of all bindings for a given model + @see getFormModelNames + */ + void getBindingNames( const OUString& _rModelName, std::vector< OUString >& /* [out] */ _rBindingNames ) const; + + /// retrieves the XForms model (within the control model's document) with the given name + css::uno::Reference< css::xforms::XModel > + getFormModelByName( const OUString& _rModelName ) const; + + /** retrieves the model which the active binding of the control model belongs to + */ + css::uno::Reference< css::xforms::XModel > + getCurrentFormModel() const; + + /** retrieves the name of the model which the active binding of the control model belongs to + */ + OUString + getCurrentFormModelName() const; + + /** retrieves the binding instance which is currently attached to the control model + */ + css::uno::Reference< css::beans::XPropertySet > + getCurrentBinding() const; + + /** retrieves the name of the binding instance which is currently attached to the control model + */ + OUString + getCurrentBindingName() const; + + /** sets a new binding at the control model + */ + void setBinding( const css::uno::Reference< css::beans::XPropertySet >& _rxBinding ); + + /** retrieves the binding instance which is currently used as list source for the control model + @see isListEntrySink + */ + css::uno::Reference< css::form::binding::XListEntrySource > + getCurrentListSourceBinding() const; + + /** sets a new list source at the control model + @see isListEntrySink + */ + void setListSourceBinding( const css::uno::Reference< css::form::binding::XListEntrySource >& _rxListSource ); + + /** retrieves a given binding for a given model, or creates a new one + + @param _rTargetModel + the name of the model to create a binding for. Must not be empty + @param _rBindingName + the name of the binding to retrieve. If the model denoted by <arg>_rTargetModel</arg> does not + have a binding with this name, a new binding is created and returned. + */ + css::uno::Reference< css::beans::XPropertySet > + getOrCreateBindingForModel( const OUString& _rTargetModel, const OUString& _rBindingName ) const; + + /** types of sub-elements of a model + */ + enum ModelElementType + { + Submission, + Binding + }; + + /** retrieves the name of a model's sub-element, as to be shown in the UI + @see getModelElementFromUIName + @see getAllElementUINames + */ + static OUString + getModelElementUIName( + const ModelElementType _eType, + const css::uno::Reference< css::beans::XPropertySet >& _rxElement + ); + + /** retrieves the submission object for an UI name + + Note that <member>getAllElementUINames</member> must have been called before, for the given element type + + @see getModelElementUIName + @see getAllElementUINames + */ + css::uno::Reference< css::beans::XPropertySet > + getModelElementFromUIName( + const ModelElementType _eType, + const OUString& _rUIName + ) const; + + /** retrieves the UI names of all elements of all models in our document + @param _eType + the type of elements for which the names should be retrieved + @param _rElementNames + the array of element names + @see getModelElementUIName + @see getModelElementFromUIName + */ + void getAllElementUINames( + const ModelElementType _eType, + std::vector< OUString >& /* [out] */ _rElementNames, + bool _bPrepentEmptyEntry + ); + + protected: + void firePropertyChanges( + const css::uno::Reference< css::beans::XPropertySet >& _rxOldProps, + const css::uno::Reference< css::beans::XPropertySet >& _rxNewProps, + std::set< OUString >& _rFilter + ) const; + + /** fires a change in a single property, if the property value changed, and if we have a listener + interested in property changes + */ + void firePropertyChange( + const OUString& _rName, + const css::uno::Any& _rOldValue, + const css::uno::Any& _rNewValue + ) const; + + private: + void impl_switchBindingListening_throw( bool _bDoListening, const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ); + + /// implementation for both <member>getOrCreateBindingForModel</member> + css::uno::Reference< css::beans::XPropertySet > + implGetOrCreateBinding( const OUString& _rTargetModel, const OUString& _rBindingName ) const; + + void + impl_toggleBindingPropertyListening_throw( bool _bDoListen, const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxConcreteListenerOrNull ); + + private: + EFormsHelper( const EFormsHelper& ) = delete; + EFormsHelper& operator=( const EFormsHelper& ) = delete; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/eformspropertyhandler.cxx b/extensions/source/propctrlr/eformspropertyhandler.cxx new file mode 100644 index 0000000000..cb206897f5 --- /dev/null +++ b/extensions/source/propctrlr/eformspropertyhandler.cxx @@ -0,0 +1,598 @@ +/* -*- 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 "eformspropertyhandler.hxx" +#include "formstrings.hxx" +#include "formmetadata.hxx" +#include <propctrlr.h> +#include "eformshelper.hxx" +#include "handlerhelper.hxx" + +#include <com/sun/star/lang/NullPointerException.hpp> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/inspection/PropertyControlType.hpp> +#include <com/sun/star/inspection/XObjectInspectorUI.hpp> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <sal/log.hxx> + + +namespace pcr +{ + + + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::xforms; + using namespace ::com::sun::star::script; + using namespace ::com::sun::star::ui::dialogs; + using namespace ::com::sun::star::form::binding; + using namespace ::com::sun::star::inspection; + + + //= EFormsPropertyHandler + + + EFormsPropertyHandler::EFormsPropertyHandler( const Reference< XComponentContext >& _rxContext ) + :PropertyHandlerComponent( _rxContext ) + ,m_bSimulatingModelChange( false ) + { + } + + + EFormsPropertyHandler::~EFormsPropertyHandler( ) + { + } + + + OUString EFormsPropertyHandler::getImplementationName( ) + { + return "com.sun.star.comp.extensions.EFormsPropertyHandler"; + } + + + Sequence< OUString > EFormsPropertyHandler::getSupportedServiceNames( ) + { + return { "com.sun.star.form.inspection.XMLFormsPropertyHandler" }; + } + + + OUString EFormsPropertyHandler::getModelNamePropertyValue() const + { + OUString sModelName = m_pHelper->getCurrentFormModelName(); + if ( sModelName.isEmpty() ) + sModelName = m_sBindingLessModelName; + return sModelName; + } + + + Any SAL_CALL EFormsPropertyHandler::getPropertyValue( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + OSL_ENSURE( + m_pHelper, + "EFormsPropertyHandler::getPropertyValue: we don't have any SupportedProperties!"); + // if we survived impl_getPropertyId_throwUnknownProperty, we should have a helper, since no helper implies no properties + + Any aReturn; + try + { + switch ( nPropId ) + { + case PROPERTY_ID_LIST_BINDING: + aReturn <<= m_pHelper->getCurrentListSourceBinding(); + break; + + case PROPERTY_ID_XML_DATA_MODEL: + aReturn <<= getModelNamePropertyValue(); + break; + + case PROPERTY_ID_BINDING_NAME: + aReturn <<= m_pHelper->getCurrentBindingName(); + break; + + case PROPERTY_ID_BIND_EXPRESSION: + case PROPERTY_ID_XSD_CONSTRAINT: + case PROPERTY_ID_XSD_CALCULATION: + case PROPERTY_ID_XSD_REQUIRED: + case PROPERTY_ID_XSD_RELEVANT: + case PROPERTY_ID_XSD_READONLY: + { + Reference< XPropertySet > xBindingProps( m_pHelper->getCurrentBinding() ); + if ( xBindingProps.is() ) + { + aReturn = xBindingProps->getPropertyValue( _rPropertyName ); + DBG_ASSERT( aReturn.getValueType().equals( ::cppu::UnoType<OUString>::get() ), + "EFormsPropertyHandler::getPropertyValue: invalid BindingExpression value type!" ); + } + else + aReturn <<= OUString(); + } + break; + + default: + OSL_FAIL( "EFormsPropertyHandler::getPropertyValue: cannot handle this property!" ); + break; + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsPropertyHandler::getPropertyValue: caught exception!" + "(have been asked for the \"" <<_rPropertyName << "\" property.)"); + } + return aReturn; + } + + + void SAL_CALL EFormsPropertyHandler::setPropertyValue( const OUString& _rPropertyName, const Any& _rValue ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + OSL_ENSURE( + m_pHelper, + "EFormsPropertyHandler::setPropertyValue: we don't have any SupportedProperties!"); + // if we survived impl_getPropertyId_throwUnknownProperty, we should have a helper, since no helper implies no properties + + try + { + Any aOldValue = getPropertyValue( _rPropertyName ); + + switch ( nPropId ) + { + case PROPERTY_ID_LIST_BINDING: + { + Reference< XListEntrySource > xSource; + OSL_VERIFY( _rValue >>= xSource ); + m_pHelper->setListSourceBinding( xSource ); + } + break; + + case PROPERTY_ID_XML_DATA_MODEL: + { + OSL_VERIFY( _rValue >>= m_sBindingLessModelName ); + + // if the model changed, reset the binding to NULL + if ( m_pHelper->getCurrentFormModelName() != m_sBindingLessModelName ) + { + OUString sOldBindingName = m_pHelper->getCurrentBindingName(); + m_pHelper->setBinding( nullptr ); + firePropertyChange( PROPERTY_BINDING_NAME, PROPERTY_ID_BINDING_NAME, + Any( sOldBindingName ), Any( OUString() ) ); + } + } + break; + + case PROPERTY_ID_BINDING_NAME: + { + OUString sNewBindingName; + OSL_VERIFY( _rValue >>= sNewBindingName ); + + bool bPreviouslyEmptyModel = !m_pHelper->getCurrentFormModel().is(); + + Reference< XPropertySet > xNewBinding; + if ( !sNewBindingName.isEmpty() ) + // obtain the binding with this name, for the current model + xNewBinding = m_pHelper->getOrCreateBindingForModel( getModelNamePropertyValue(), sNewBindingName ); + + m_pHelper->setBinding( xNewBinding ); + + if ( bPreviouslyEmptyModel ) + { // simulate a property change for the model property + // This is because we "simulate" the Model property by remembering the + // value ourself. Other instances might, however, not know this value, + // but prefer to retrieve it somewhere else - e.g. from the EFormsHelper + + // The really correct solution would be if *all* property handlers + // obtain a "current property value" for *all* properties from a central + // instance. Then, handler A could ask it for the value of property + // X, and this request would be re-routed to handler B, which ultimately + // knows the current value. + // However, there's no such mechanism in place currently. + m_bSimulatingModelChange = true; + firePropertyChange( PROPERTY_XML_DATA_MODEL, PROPERTY_ID_XML_DATA_MODEL, + Any( OUString() ), Any( getModelNamePropertyValue() ) ); + m_bSimulatingModelChange = false; + } + } + break; + + case PROPERTY_ID_BIND_EXPRESSION: + { + Reference< XPropertySet > xBinding( m_pHelper->getCurrentBinding() ); + OSL_ENSURE( xBinding.is(), "You should not reach this without an active binding!" ); + if ( xBinding.is() ) + xBinding->setPropertyValue( PROPERTY_BIND_EXPRESSION, _rValue ); + } + break; + + case PROPERTY_ID_XSD_REQUIRED: + case PROPERTY_ID_XSD_RELEVANT: + case PROPERTY_ID_XSD_READONLY: + case PROPERTY_ID_XSD_CONSTRAINT: + case PROPERTY_ID_XSD_CALCULATION: + { + Reference< XPropertySet > xBindingProps( m_pHelper->getCurrentBinding() ); + DBG_ASSERT( xBindingProps.is(), "EFormsPropertyHandler::setPropertyValue: how can I set a property if there's no binding?" ); + if ( xBindingProps.is() ) + { + DBG_ASSERT( _rValue.getValueType().equals( ::cppu::UnoType<OUString>::get() ), + "EFormsPropertyHandler::setPropertyValue: invalid value type!" ); + xBindingProps->setPropertyValue( _rPropertyName, _rValue ); + } + } + break; + + default: + OSL_FAIL( "EFormsPropertyHandler::setPropertyValue: cannot handle this property!" ); + break; + } + + impl_setContextDocumentModified_nothrow(); + + Any aNewValue( getPropertyValue( _rPropertyName ) ); + firePropertyChange( _rPropertyName, nPropId, aOldValue, aNewValue ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsPropertyHandler::setPropertyValue" ); + } + } + + + void EFormsPropertyHandler::onNewComponent() + { + PropertyHandlerComponent::onNewComponent(); + + Reference< frame::XModel > xDocument( impl_getContextDocument_nothrow() ); + DBG_ASSERT( xDocument.is(), "EFormsPropertyHandler::onNewComponent: no document!" ); + if ( EFormsHelper::isEForm( xDocument ) ) + m_pHelper.reset( new EFormsHelper( m_aMutex, m_xComponent, xDocument ) ); + else + m_pHelper.reset(); + } + + + Sequence< Property > EFormsPropertyHandler::doDescribeSupportedProperties() const + { + std::vector< Property > aProperties; + + if (m_pHelper) + { + if ( m_pHelper->canBindToAnyDataType() ) + { + aProperties.reserve(9); + addStringPropertyDescription( aProperties, PROPERTY_XML_DATA_MODEL ); + addStringPropertyDescription( aProperties, PROPERTY_BINDING_NAME ); + addStringPropertyDescription( aProperties, PROPERTY_BIND_EXPRESSION ); + addStringPropertyDescription( aProperties, PROPERTY_XSD_REQUIRED ); + addStringPropertyDescription( aProperties, PROPERTY_XSD_RELEVANT ); + addStringPropertyDescription( aProperties, PROPERTY_XSD_READONLY ); + addStringPropertyDescription( aProperties, PROPERTY_XSD_CONSTRAINT ); + addStringPropertyDescription( aProperties, PROPERTY_XSD_CALCULATION ); + } + if ( m_pHelper->isListEntrySink() ) + { + implAddPropertyDescription( aProperties, PROPERTY_LIST_BINDING, + cppu::UnoType<XListEntrySource>::get() ); + } + } + + if ( aProperties.empty() ) + return Sequence< Property >(); + return comphelper::containerToSequence(aProperties); + } + + + Any SAL_CALL EFormsPropertyHandler::convertToPropertyValue( const OUString& _rPropertyName, const Any& _rControlValue ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + Any aReturn; + + OSL_ENSURE( + m_pHelper, + "EFormsPropertyHandler::convertToPropertyValue: we have no SupportedProperties!"); + if (!m_pHelper) + return aReturn; + + PropertyId nPropId( m_pInfoService->getPropertyId( _rPropertyName ) ); + + OUString sControlValue; + switch ( nPropId ) + { + case PROPERTY_ID_LIST_BINDING: + { + OSL_VERIFY( _rControlValue >>= sControlValue ); + Reference< XListEntrySource > xListSource( m_pHelper->getModelElementFromUIName( EFormsHelper::Binding, sControlValue ), UNO_QUERY ); + OSL_ENSURE( xListSource.is() || !m_pHelper->getModelElementFromUIName( EFormsHelper::Binding, sControlValue ).is(), + "EFormsPropertyHandler::convertToPropertyValue: there's a binding which is no ListEntrySource!" ); + aReturn <<= xListSource; + } + break; + + default: + aReturn = PropertyHandlerComponent::convertToPropertyValue( _rPropertyName, _rControlValue ); + break; + } + + return aReturn; + } + + + Any SAL_CALL EFormsPropertyHandler::convertToControlValue( const OUString& _rPropertyName, const Any& _rPropertyValue, const Type& _rControlValueType ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + Any aReturn; + + OSL_ENSURE(m_pHelper, + "EFormsPropertyHandler::convertToControlValue: we have no SupportedProperties!"); + if (!m_pHelper) + return aReturn; + + PropertyId nPropId( m_pInfoService->getPropertyId( _rPropertyName ) ); + + OSL_ENSURE( _rControlValueType.getTypeClass() == TypeClass_STRING, + "EFormsPropertyHandler::convertToControlValue: all our controls should use strings for value exchange!" ); + + switch ( nPropId ) + { + case PROPERTY_ID_LIST_BINDING: + { + Reference< XPropertySet > xListSourceBinding( _rPropertyValue, UNO_QUERY ); + if ( xListSourceBinding.is() ) + aReturn <<= EFormsHelper::getModelElementUIName( EFormsHelper::Binding, xListSourceBinding ); + } + break; + + default: + aReturn = PropertyHandlerComponent::convertToControlValue( _rPropertyName, _rPropertyValue, _rControlValueType ); + break; + } + + return aReturn; + } + + + Sequence< OUString > SAL_CALL EFormsPropertyHandler::getActuatingProperties( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if (!m_pHelper) + return Sequence< OUString >(); + + std::vector< OUString > aInterestedInActuations( 2 ); + aInterestedInActuations[ 0 ] = PROPERTY_XML_DATA_MODEL; + aInterestedInActuations[ 1 ] = PROPERTY_BINDING_NAME; + return comphelper::containerToSequence(aInterestedInActuations); + } + + + Sequence< OUString > SAL_CALL EFormsPropertyHandler::getSupersededProperties( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if (!m_pHelper) + return Sequence< OUString >(); + + Sequence<OUString> aReturn { PROPERTY_INPUT_REQUIRED }; + return aReturn; + } + + LineDescriptor SAL_CALL EFormsPropertyHandler::describePropertyLine( const OUString& _rPropertyName, + const Reference< XPropertyControlFactory >& _rxControlFactory ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !_rxControlFactory.is() ) + throw NullPointerException(); + if (!m_pHelper) + throw RuntimeException(); + + LineDescriptor aDescriptor; + sal_Int16 nControlType = PropertyControlType::TextField; + std::vector< OUString > aListEntries; + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + switch ( nPropId ) + { + case PROPERTY_ID_LIST_BINDING: + nControlType = PropertyControlType::ListBox; + m_pHelper->getAllElementUINames(EFormsHelper::Binding, aListEntries, true); + break; + + case PROPERTY_ID_XML_DATA_MODEL: + nControlType = PropertyControlType::ListBox; + m_pHelper->getFormModelNames( aListEntries ); + break; + + case PROPERTY_ID_BINDING_NAME: + { + nControlType = PropertyControlType::ComboBox; + OUString sCurrentModel( getModelNamePropertyValue() ); + if ( !sCurrentModel.isEmpty() ) + m_pHelper->getBindingNames( sCurrentModel, aListEntries ); + } + break; + + case PROPERTY_ID_BIND_EXPRESSION: aDescriptor.PrimaryButtonId = UID_PROP_DLG_BIND_EXPRESSION; break; + case PROPERTY_ID_XSD_REQUIRED: aDescriptor.PrimaryButtonId = UID_PROP_DLG_XSD_REQUIRED; break; + case PROPERTY_ID_XSD_RELEVANT: aDescriptor.PrimaryButtonId = UID_PROP_DLG_XSD_RELEVANT; break; + case PROPERTY_ID_XSD_READONLY: aDescriptor.PrimaryButtonId = UID_PROP_DLG_XSD_READONLY; break; + case PROPERTY_ID_XSD_CONSTRAINT: aDescriptor.PrimaryButtonId = UID_PROP_DLG_XSD_CONSTRAINT; break; + case PROPERTY_ID_XSD_CALCULATION: aDescriptor.PrimaryButtonId = UID_PROP_DLG_XSD_CALCULATION; break; + + default: + OSL_FAIL( "EFormsPropertyHandler::describePropertyLine: cannot handle this property!" ); + break; + } + + switch ( nControlType ) + { + case PropertyControlType::ListBox: + aDescriptor.Control = PropertyHandlerHelper::createListBoxControl( _rxControlFactory, std::move(aListEntries), false, true ); + break; + case PropertyControlType::ComboBox: + aDescriptor.Control = PropertyHandlerHelper::createComboBoxControl( _rxControlFactory, std::move(aListEntries), true ); + break; + default: + aDescriptor.Control = _rxControlFactory->createPropertyControl( nControlType, false ); + break; + } + + aDescriptor.DisplayName = m_pInfoService->getPropertyTranslation( nPropId ); + aDescriptor.Category = "Data"; + aDescriptor.HelpURL = HelpIdUrl::getHelpURL( m_pInfoService->getPropertyHelpId( nPropId ) ); + return aDescriptor; + } + + InteractiveSelectionResult SAL_CALL EFormsPropertyHandler::onInteractivePropertySelection( const OUString& _rPropertyName, sal_Bool /*_bPrimary*/, Any& _rData, const Reference< XObjectInspectorUI >& _rxInspectorUI ) + { + if ( !_rxInspectorUI.is() ) + throw NullPointerException(); + + ::osl::MutexGuard aGuard( m_aMutex ); + OSL_ENSURE(m_pHelper, "EFormsPropertyHandler::onInteractivePropertySelection: we do not " + "have any SupportedProperties!"); + if (!m_pHelper) + return InteractiveSelectionResult_Cancelled; + + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + OSL_ENSURE( ( PROPERTY_ID_BINDING_NAME == nPropId ) + || ( PROPERTY_ID_BIND_EXPRESSION == nPropId ) + || ( PROPERTY_ID_XSD_REQUIRED == nPropId ) + || ( PROPERTY_ID_XSD_RELEVANT == nPropId ) + || ( PROPERTY_ID_XSD_READONLY == nPropId ) + || ( PROPERTY_ID_XSD_CONSTRAINT == nPropId ) + || ( PROPERTY_ID_XSD_CALCULATION == nPropId ), "EFormsPropertyHandler::onInteractivePropertySelection: unexpected!" ); + + try + { + Reference< XExecutableDialog > xDialog; + xDialog.set( m_xContext->getServiceManager()->createInstanceWithContext( "com.sun.star.xforms.ui.dialogs.AddCondition", m_xContext ), UNO_QUERY ); + Reference< XPropertySet > xDialogProps( xDialog, UNO_QUERY_THROW ); + + // the model for the dialog to work with + Reference< xforms::XModel > xModel( m_pHelper->getCurrentFormModel() ); + // the binding for the dialog to work with + Reference< XPropertySet > xBinding( m_pHelper->getCurrentBinding() ); + // the aspect of the binding which the dialog should modify + const OUString& sFacetName( _rPropertyName ); + + OSL_ENSURE( xModel.is() && xBinding.is() && !sFacetName.isEmpty(), + "EFormsPropertyHandler::onInteractivePropertySelection: something is missing for the dialog initialization!" ); + if ( !xModel.is() || !xBinding.is() || sFacetName.isEmpty() ) + return InteractiveSelectionResult_Cancelled; + + xDialogProps->setPropertyValue("FormModel", Any( xModel ) ); + xDialogProps->setPropertyValue("Binding", Any( xBinding ) ); + xDialogProps->setPropertyValue("FacetName", Any( sFacetName ) ); + + if ( !xDialog->execute() ) + // cancelled + return InteractiveSelectionResult_Cancelled; + + _rData = xDialogProps->getPropertyValue("ConditionValue"); + return InteractiveSelectionResult_ObtainedValue; + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EFormsPropertyHandler::onInteractivePropertySelection" ); + } + + // something went wrong here ...(but has been asserted already) + return InteractiveSelectionResult_Cancelled; + } + + + void SAL_CALL EFormsPropertyHandler::addPropertyChangeListener( const Reference< XPropertyChangeListener >& _rxListener ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyHandlerComponent::addPropertyChangeListener( _rxListener ); + if (m_pHelper) + m_pHelper->registerBindingListener( _rxListener ); + } + + + void SAL_CALL EFormsPropertyHandler::removePropertyChangeListener( const Reference< XPropertyChangeListener >& _rxListener ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if (m_pHelper) + m_pHelper->revokeBindingListener( _rxListener ); + PropertyHandlerComponent::removePropertyChangeListener( _rxListener ); + } + + + void SAL_CALL EFormsPropertyHandler::actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const Any& _rNewValue, const Any& /*_rOldValue*/, const Reference< XObjectInspectorUI >& _rxInspectorUI, sal_Bool ) + { + if ( !_rxInspectorUI.is() ) + throw NullPointerException(); + + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nActuatingPropId( impl_getPropertyId_throwRuntime( _rActuatingPropertyName ) ); + OSL_PRECOND(m_pHelper, "EFormsPropertyHandler::actuatingPropertyChanged: inconsistency!"); + // if we survived impl_getPropertyId_throwRuntime, we should have a helper, since no helper implies no properties + + DBG_ASSERT( _rxInspectorUI.is(), "EFormsPropertyHandler::actuatingPropertyChanged: invalid callback!" ); + if ( !_rxInspectorUI.is() ) + return; + + switch ( nActuatingPropId ) + { + case PROPERTY_ID_XML_DATA_MODEL: + { + if ( m_bSimulatingModelChange ) + break; + OUString sDataModelName; + OSL_VERIFY( _rNewValue >>= sDataModelName ); + bool bBoundToSomeModel = !sDataModelName.isEmpty(); + _rxInspectorUI->rebuildPropertyUI( PROPERTY_BINDING_NAME ); + _rxInspectorUI->enablePropertyUI( PROPERTY_BINDING_NAME, bBoundToSomeModel ); + [[fallthrough]]; + } + + case PROPERTY_ID_BINDING_NAME: + { + bool bHaveABinding = !m_pHelper->getCurrentBindingName().isEmpty(); + _rxInspectorUI->enablePropertyUI( PROPERTY_BIND_EXPRESSION, bHaveABinding ); + _rxInspectorUI->enablePropertyUI( PROPERTY_XSD_REQUIRED, bHaveABinding ); + _rxInspectorUI->enablePropertyUI( PROPERTY_XSD_RELEVANT, bHaveABinding ); + _rxInspectorUI->enablePropertyUI( PROPERTY_XSD_READONLY, bHaveABinding ); + _rxInspectorUI->enablePropertyUI( PROPERTY_XSD_CONSTRAINT, bHaveABinding ); + _rxInspectorUI->enablePropertyUI( PROPERTY_XSD_CALCULATION, bHaveABinding ); + _rxInspectorUI->enablePropertyUI( PROPERTY_XSD_DATA_TYPE, bHaveABinding ); + } + break; + + default: + OSL_FAIL( "EFormsPropertyHandler::actuatingPropertyChanged: cannot handle this property!" ); + break; + } + } + + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_EFormsPropertyHandler_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::EFormsPropertyHandler(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/eformspropertyhandler.hxx b/extensions/source/propctrlr/eformspropertyhandler.hxx new file mode 100644 index 0000000000..8945c24f49 --- /dev/null +++ b/extensions/source/propctrlr/eformspropertyhandler.hxx @@ -0,0 +1,95 @@ +/* -*- 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 . + */ + +#pragma once + +#include "propertyhandler.hxx" + +#include <memory> + + +namespace pcr +{ + + + class EFormsHelper; + + //= EFormsPropertyHandler + + class EFormsPropertyHandler; + class EFormsPropertyHandler : public PropertyHandlerComponent + { + private: + std::unique_ptr< EFormsHelper > m_pHelper; + /** current value of the Model property, if there is no binding, yet + */ + OUString m_sBindingLessModelName; + /** are we currently simulating a propertyChange event of the Model property? + */ + bool m_bSimulatingModelChange; + + public: + explicit EFormsPropertyHandler( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext + ); + + protected: + virtual ~EFormsPropertyHandler() override; + + protected: + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames () override; + + // XPropertyHandler overriables + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& _rPropertyName ) override; + virtual void SAL_CALL setPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rValue ) override; + virtual css::uno::Sequence< OUString > + SAL_CALL getActuatingProperties( ) override; + virtual css::uno::Sequence< OUString > + SAL_CALL getSupersededProperties( ) override; + virtual css::inspection::LineDescriptor + SAL_CALL describePropertyLine( const OUString& _rPropertyName, const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory ) override; + virtual css::inspection::InteractiveSelectionResult + SAL_CALL onInteractivePropertySelection( const OUString& _rPropertyName, sal_Bool _bPrimary, css::uno::Any& _rData, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI ) override; + virtual void SAL_CALL actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const css::uno::Any& _rNewValue, const css::uno::Any& _rOldValue, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI, sal_Bool ) override; + virtual css::uno::Any SAL_CALL convertToPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rControlValue ) override; + virtual css::uno::Any SAL_CALL convertToControlValue( const OUString& _rPropertyName, const css::uno::Any& _rPropertyValue, const css::uno::Type& _rControlValueType ) override; + virtual void SAL_CALL addPropertyChangeListener( const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ) override; + + // PropertyHandler overridables + virtual css::uno::Sequence< css::beans::Property > + doDescribeSupportedProperties() const override; + virtual void onNewComponent() override; + + protected: + /** returns the value of the PROPERTY_XML_DATA_MODEL property. + + An extra method is necessary here, which respects both the value set at our helper, + and <member>m_sBindingLessModelName</member> + */ + OUString getModelNamePropertyValue() const; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/enumrepresentation.hxx b/extensions/source/propctrlr/enumrepresentation.hxx new file mode 100644 index 0000000000..b3ba04c9a1 --- /dev/null +++ b/extensions/source/propctrlr/enumrepresentation.hxx @@ -0,0 +1,62 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/Any.hxx> +#include <rtl/ustring.hxx> +#include <salhelper/simplereferenceobject.hxx> + +#include <vector> + + +namespace pcr +{ + + + //= IPropertyEnumRepresentation + + class SAL_NO_VTABLE IPropertyEnumRepresentation : public salhelper::SimpleReferenceObject + { + public: + /** retrieves all descriptions of all possible values of the enumeration property + */ + virtual std::vector< OUString > getDescriptions( + ) const = 0; + + /** converts a given description into a property value + */ + virtual void getValueFromDescription( + const OUString& _rDescription, + css::uno::Any& _out_rValue + ) const = 0; + + /** converts a given property value into a description + */ + virtual OUString getDescriptionForValue( + const css::uno::Any& _rEnumValue + ) const = 0; + + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/eventhandler.cxx b/extensions/source/propctrlr/eventhandler.cxx new file mode 100644 index 0000000000..7e4ca0747c --- /dev/null +++ b/extensions/source/propctrlr/eventhandler.cxx @@ -0,0 +1,1106 @@ +/* -*- 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 "eventhandler.hxx" +#include <helpids.h> +#include <propctrlr.h> +#include "formbrowsertools.hxx" +#include <strings.hrc> +#include "formstrings.hxx" +#include "handlerhelper.hxx" +#include "modulepcr.hxx" +#include "pcrcommon.hxx" +#include "propertycontrolextender.hxx" + +#include <com/sun/star/awt/XTabControllerModel.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/UnknownPropertyException.hpp> +#include <com/sun/star/beans/theIntrospection.hpp> +#include <com/sun/star/beans/XIntrospectionAccess.hpp> +#include <com/sun/star/container/NoSuchElementException.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/container/XNameReplace.hpp> +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/form/XForm.hpp> +#include <com/sun/star/form/runtime/FormController.hpp> +#include <com/sun/star/inspection/PropertyControlType.hpp> +#include <com/sun/star/lang/NullPointerException.hpp> +#include <com/sun/star/script/XEventAttacherManager.hpp> +#include <com/sun/star/script/XScriptEventsSupplier.hpp> +#include <com/sun/star/uri/UriReferenceFactory.hpp> +#include <com/sun/star/uri/XVndSunStarScriptUrlReference.hpp> + +#include <comphelper/namedvaluecollection.hxx> +#include <comphelper/evtmethodhelper.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/types.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustrbuf.hxx> +#include <svx/svxdlg.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <map> +#include <algorithm> +#include <iterator> +#include <string_view> +#include <utility> + +namespace pcr +{ + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::TypeClass_STRING; + using ::com::sun::star::uno::Type; + using ::com::sun::star::beans::theIntrospection; + using ::com::sun::star::beans::XPropertyChangeListener; + using ::com::sun::star::beans::Property; + using ::com::sun::star::beans::PropertyState; + using ::com::sun::star::beans::PropertyState_DIRECT_VALUE; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::script::ScriptEventDescriptor; + using ::com::sun::star::script::XScriptEventsSupplier; + using ::com::sun::star::lang::NullPointerException; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::container::XChild; + using ::com::sun::star::container::XIndexAccess; + using ::com::sun::star::script::XEventAttacherManager; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::beans::XIntrospection; + using ::com::sun::star::beans::XIntrospectionAccess; + using ::com::sun::star::container::XNameContainer; + using ::com::sun::star::awt::XTabControllerModel; + using ::com::sun::star::form::XForm; + using ::com::sun::star::form::runtime::FormController; + using ::com::sun::star::form::runtime::XFormController; + using ::com::sun::star::beans::UnknownPropertyException; + using ::com::sun::star::container::NoSuchElementException; + using ::com::sun::star::beans::XPropertySetInfo; + using ::com::sun::star::container::XNameReplace; + using ::com::sun::star::beans::PropertyValue; + using ::com::sun::star::inspection::LineDescriptor; + using ::com::sun::star::inspection::XPropertyControlFactory; + using ::com::sun::star::inspection::InteractiveSelectionResult; + using ::com::sun::star::inspection::InteractiveSelectionResult_Cancelled; + using ::com::sun::star::inspection::InteractiveSelectionResult_Success; + using ::com::sun::star::inspection::XObjectInspectorUI; + using ::com::sun::star::beans::PropertyChangeEvent; + using ::com::sun::star::frame::XFrame; + using ::com::sun::star::frame::XModel; + using ::com::sun::star::frame::XController; + using ::com::sun::star::uno::UNO_SET_THROW; + using com::sun::star::uri::UriReferenceFactory; + using com::sun::star::uri::XUriReferenceFactory; + using com::sun::star::uri::XVndSunStarScriptUrlReference; + + namespace PropertyControlType = css::inspection::PropertyControlType; + namespace PropertyAttribute = css::beans::PropertyAttribute; + namespace FormComponentType = css::form::FormComponentType; + + EventDescription::EventDescription( EventId _nId, std::u16string_view listenerClassName, + std::u16string_view listenerMethodName, TranslateId pDisplayNameResId, OUString _sHelpId, OString _sUniqueBrowseId ) + :sDisplayName(PcrRes( pDisplayNameResId )) + ,sListenerClassName( listenerClassName ) + ,sListenerMethodName( listenerMethodName ) + ,sHelpId(std::move( _sHelpId )) + ,sUniqueBrowseId(std::move( _sUniqueBrowseId )) + ,nId( _nId ) + { + } + + namespace + { + #define DESCRIBE_EVENT( map, listener, method, id_postfix ) \ + map.emplace( \ + u"" method##_ustr, \ + EventDescription( ++nEventId, u"com.sun.star." listener, u"" method, RID_STR_EVT_##id_postfix, HID_EVT_##id_postfix, UID_BRWEVT_##id_postfix ) ) + + bool lcl_getEventDescriptionForMethod( const OUString& _rMethodName, EventDescription& _out_rDescription ) + { + static EventMap s_aKnownEvents = []() { + EventMap aMap; + sal_Int32 nEventId = 0; + + DESCRIBE_EVENT(aMap, "form.XApproveActionListener", "approveAction", APPROVEACTIONPERFORMED); + DESCRIBE_EVENT(aMap, "awt.XActionListener", "actionPerformed", ACTIONPERFORMED); + DESCRIBE_EVENT(aMap, "form.XChangeListener", "changed", CHANGED); + DESCRIBE_EVENT(aMap, "awt.XTextListener", "textChanged", TEXTCHANGED); + DESCRIBE_EVENT(aMap, "awt.XItemListener", "itemStateChanged", ITEMSTATECHANGED); + DESCRIBE_EVENT(aMap, "awt.XFocusListener", "focusGained", FOCUSGAINED); + DESCRIBE_EVENT(aMap, "awt.XFocusListener", "focusLost", FOCUSLOST); + DESCRIBE_EVENT(aMap, "awt.XKeyListener", "keyPressed", KEYTYPED); + DESCRIBE_EVENT(aMap, "awt.XKeyListener", "keyReleased", KEYUP); + DESCRIBE_EVENT(aMap, "awt.XMouseListener", "mouseEntered", MOUSEENTERED); + DESCRIBE_EVENT(aMap, "awt.XMouseMotionListener", "mouseDragged", MOUSEDRAGGED); + DESCRIBE_EVENT(aMap, "awt.XMouseMotionListener", "mouseMoved", MOUSEMOVED); + DESCRIBE_EVENT(aMap, "awt.XMouseListener", "mousePressed", MOUSEPRESSED); + DESCRIBE_EVENT(aMap, "awt.XMouseListener", "mouseReleased", MOUSERELEASED); + DESCRIBE_EVENT(aMap, "awt.XMouseListener", "mouseExited", MOUSEEXITED); + DESCRIBE_EVENT(aMap, "form.XResetListener", "approveReset", APPROVERESETTED); + DESCRIBE_EVENT(aMap, "form.XResetListener", "resetted", RESETTED); + DESCRIBE_EVENT(aMap, "form.XSubmitListener", "approveSubmit", SUBMITTED); + DESCRIBE_EVENT(aMap, "form.XUpdateListener", "approveUpdate", BEFOREUPDATE); + DESCRIBE_EVENT(aMap, "form.XUpdateListener", "updated", AFTERUPDATE); + DESCRIBE_EVENT(aMap, "form.XLoadListener", "loaded", LOADED); + DESCRIBE_EVENT(aMap, "form.XLoadListener", "reloading", RELOADING); + DESCRIBE_EVENT(aMap, "form.XLoadListener", "reloaded", RELOADED); + DESCRIBE_EVENT(aMap, "form.XLoadListener", "unloading", UNLOADING); + DESCRIBE_EVENT(aMap, "form.XLoadListener", "unloaded", UNLOADED); + DESCRIBE_EVENT(aMap, "form.XConfirmDeleteListener", "confirmDelete", CONFIRMDELETE); + DESCRIBE_EVENT(aMap, "sdb.XRowSetApproveListener", "approveRowChange", APPROVEROWCHANGE); + DESCRIBE_EVENT(aMap, "sdbc.XRowSetListener", "rowChanged", ROWCHANGE); + DESCRIBE_EVENT(aMap, "sdb.XRowSetApproveListener", "approveCursorMove", POSITIONING); + DESCRIBE_EVENT(aMap, "sdbc.XRowSetListener", "cursorMoved", POSITIONED); + DESCRIBE_EVENT(aMap, "form.XDatabaseParameterListener", "approveParameter", APPROVEPARAMETER); + DESCRIBE_EVENT(aMap, "sdb.XSQLErrorListener", "errorOccured", ERROROCCURRED); + DESCRIBE_EVENT(aMap, "awt.XAdjustmentListener", "adjustmentValueChanged", ADJUSTMENTVALUECHANGED); + + return aMap; + }(); + + EventMap::const_iterator pos = s_aKnownEvents.find( _rMethodName ); + if ( pos == s_aKnownEvents.end() ) + return false; + + _out_rDescription = pos->second; + return true; + } + + OUString lcl_getEventPropertyName( std::u16string_view _rListenerClassName, std::u16string_view _rMethodName ) + { + return _rListenerClassName + OUStringChar(';') + _rMethodName; + } + + ScriptEventDescriptor lcl_getAssignedScriptEvent( const EventDescription& _rEvent, const std::vector< ScriptEventDescriptor >& _rAllAssignedMacros ) + { + ScriptEventDescriptor aScriptEvent; + // for the case there is actually no event assigned, initialize at least ListenerType and MethodName, + // so this ScriptEventDescriptor properly describes the given event + aScriptEvent.ListenerType = _rEvent.sListenerClassName; + aScriptEvent.EventMethod = _rEvent.sListenerMethodName; + + for ( const ScriptEventDescriptor& rSED : _rAllAssignedMacros ) + { + if ( rSED.ListenerType != _rEvent.sListenerClassName + || rSED.EventMethod != _rEvent.sListenerMethodName + ) + continue; + + if ( rSED.ScriptCode.isEmpty() + || rSED.ScriptType.isEmpty() + ) + { + OSL_FAIL( "lcl_getAssignedScriptEvent: me thinks this should not happen!" ); + continue; + } + + aScriptEvent = rSED; + + if ( aScriptEvent.ScriptType != "StarBasic" ) + continue; + + // this is an old-style macro specification: + // [document|application]:Library.Module.Function + // we need to translate this to the new-style macro specification + // vnd.sun.star.script:Library.Module.Function?language=Basic&location=[document|application] + + sal_Int32 nPrefixLen = aScriptEvent.ScriptCode.indexOf( ':' ); + OSL_ENSURE( nPrefixLen > 0, "lcl_getAssignedScriptEvent: illegal location!" ); + std::u16string_view sLocation = aScriptEvent.ScriptCode.subView( 0, nPrefixLen ); + std::u16string_view sMacroPath = aScriptEvent.ScriptCode.subView( nPrefixLen + 1 ); + + aScriptEvent.ScriptCode = + OUString::Concat("vnd.sun.star.script:") + + sMacroPath + + "?language=Basic&location=" + + sLocation; + + // also, this new-style spec requires the script code to be "Script" instead of "StarBasic" + aScriptEvent.ScriptType = "Script"; + } + return aScriptEvent; + } + + OUString lcl_getQualifiedKnownListenerName( const ScriptEventDescriptor& _rFormComponentEventDescriptor ) + { + EventDescription aKnownEvent; + if ( lcl_getEventDescriptionForMethod( _rFormComponentEventDescriptor.EventMethod, aKnownEvent ) ) + return aKnownEvent.sListenerClassName; + OSL_FAIL( "lcl_getQualifiedKnownListenerName: unknown method name!" ); + // somebody assigned an script to a form component event which we don't know + // Speaking strictly, this is not really an error - it is possible to do + // this programmatically -, but it should rarely happen, since it's not possible + // via UI + return _rFormComponentEventDescriptor.ListenerType; + } + + typedef std::set< Type, TypeLessByName > TypeBag; + + void lcl_addListenerTypesFor_throw( const Reference< XInterface >& _rxComponent, + const Reference< XIntrospection >& _rxIntrospection, TypeBag& _out_rTypes ) + { + if ( !_rxComponent.is() ) + return; + OSL_PRECOND( _rxIntrospection.is(), "lcl_addListenerTypesFor_throw: this will crash!" ); + + Reference< XIntrospectionAccess > xIntrospectionAccess( + _rxIntrospection->inspect( Any( _rxComponent ) ), UNO_SET_THROW ); + + const Sequence< Type > aListeners( xIntrospectionAccess->getSupportedListeners() ); + + std::copy( aListeners.begin(), aListeners.end(), + std::insert_iterator< TypeBag >( _out_rTypes, _out_rTypes.begin() ) ); + } + } + + typedef ::cppu::WeakImplHelper < css::container::XNameReplace + > EventHolder_Base; + + namespace { + + /* A UNO component holding assigned event descriptions, for use with a SvxMacroAssignDlg */ + class EventHolder : public EventHolder_Base + { + private: + typedef std::unordered_map< OUString, ScriptEventDescriptor > EventMap; + typedef std::map< EventId, OUString > EventMapIndexAccess; + + EventMap m_aEventNameAccess; + EventMapIndexAccess m_aEventIndexAccess; + + public: + EventHolder( ); + + void addEvent( EventId _nId, const OUString& _rEventName, const ScriptEventDescriptor& _rScriptEvent ); + + /** effectively the same as getByName, but instead of converting the ScriptEventDescriptor to the weird + format used by the macro assignment dialog, it is returned directly + */ + ScriptEventDescriptor getNormalizedDescriptorByName( const OUString& _rEventName ) const; + + // XNameReplace + virtual void SAL_CALL replaceByName( const OUString& _rName, const Any& aElement ) override; + virtual Any SAL_CALL getByName( const OUString& _rName ) override; + virtual Sequence< OUString > SAL_CALL getElementNames( ) override; + virtual sal_Bool SAL_CALL hasByName( const OUString& _rName ) override; + virtual Type SAL_CALL getElementType( ) override; + virtual sal_Bool SAL_CALL hasElements( ) override; + + protected: + virtual ~EventHolder( ) override; + + private: + ScriptEventDescriptor const & impl_getDescriptor_throw( const OUString& _rEventName ) const; + }; + + } + + EventHolder::EventHolder() + { + } + + EventHolder::~EventHolder() + { + m_aEventNameAccess.clear(); + m_aEventIndexAccess.clear(); + } + + void EventHolder::addEvent( EventId _nId, const OUString& _rEventName, const ScriptEventDescriptor& _rScriptEvent ) + { + std::pair< EventMap::iterator, bool > insertionResult = + m_aEventNameAccess.emplace( _rEventName, _rScriptEvent ); + OSL_ENSURE( insertionResult.second, "EventHolder::addEvent: there already was a MacroURL for this event!" ); + m_aEventIndexAccess[ _nId ] = _rEventName; + } + + ScriptEventDescriptor EventHolder::getNormalizedDescriptorByName( const OUString& _rEventName ) const + { + return impl_getDescriptor_throw( _rEventName ); + } + + ScriptEventDescriptor const & EventHolder::impl_getDescriptor_throw( const OUString& _rEventName ) const + { + EventMap::const_iterator pos = m_aEventNameAccess.find( _rEventName ); + if ( pos == m_aEventNameAccess.end() ) + throw NoSuchElementException( OUString(), *const_cast< EventHolder* >( this ) ); + return pos->second; + } + + void SAL_CALL EventHolder::replaceByName( const OUString& _rName, const Any& _rElement ) + { + EventMap::iterator pos = m_aEventNameAccess.find( _rName ); + if ( pos == m_aEventNameAccess.end() ) + throw NoSuchElementException( OUString(), *this ); + + Sequence< PropertyValue > aScriptDescriptor; + OSL_VERIFY( _rElement >>= aScriptDescriptor ); + + ::comphelper::NamedValueCollection aExtractor( aScriptDescriptor ); + + pos->second.ScriptType = aExtractor.getOrDefault( "EventType", OUString() ); + pos->second.ScriptCode = aExtractor.getOrDefault( "Script", OUString() ); + } + + Any SAL_CALL EventHolder::getByName( const OUString& _rName ) + { + ScriptEventDescriptor aDescriptor( impl_getDescriptor_throw( _rName ) ); + + Sequence< PropertyValue > aScriptDescriptor{ + comphelper::makePropertyValue("EventType", aDescriptor.ScriptType), + comphelper::makePropertyValue("Script", aDescriptor.ScriptCode) + }; + + return Any( aScriptDescriptor ); + } + + Sequence< OUString > SAL_CALL EventHolder::getElementNames( ) + { + Sequence< OUString > aReturn( m_aEventIndexAccess.size() ); + OUString* pReturn = aReturn.getArray(); + + // SvxMacroAssignDlg has a weird API: It expects a XNameReplace, means a container whose + // main access method is by name. In its UI, it shows the possible events in exactly the + // order in which XNameAccess::getElementNames returns them. + // However, SvxMacroAssignDlg *also* takes an index for the initial selection, which is + // relative to the sequence returned by XNameAccess::getElementNames. + // This is IMO weird, since it mixes index access with name access, which decreases efficiency + // of the implementation. + // Well, it means we're forced to return the events in getElementNames in exactly the same as they + // appear in the property browser UI. + for (auto const& elem : m_aEventIndexAccess) + { + *pReturn = elem.second; + ++pReturn; + } + return aReturn; + } + + sal_Bool SAL_CALL EventHolder::hasByName( const OUString& _rName ) + { + EventMap::const_iterator pos = m_aEventNameAccess.find( _rName ); + return pos != m_aEventNameAccess.end(); + } + + Type SAL_CALL EventHolder::getElementType( ) + { + return cppu::UnoType<Sequence< PropertyValue >>::get(); + } + + sal_Bool SAL_CALL EventHolder::hasElements( ) + { + return !m_aEventNameAccess.empty(); + } + + + EventHandler::EventHandler( const Reference< XComponentContext >& _rxContext ) + :EventHandler_Base( m_aMutex ) + ,m_xContext( _rxContext ) + ,m_aPropertyListeners( m_aMutex ) + ,m_bEventsMapInitialized( false ) + ,m_bIsDialogElement( false ) + ,m_nGridColumnType( -1 ) + { + } + + EventHandler::~EventHandler() + { + } + + OUString SAL_CALL EventHandler::getImplementationName( ) + { + return "com.sun.star.comp.extensions.EventHandler"; + } + + sal_Bool SAL_CALL EventHandler::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService(this, ServiceName); + } + + Sequence< OUString > SAL_CALL EventHandler::getSupportedServiceNames( ) + { + return { "com.sun.star.form.inspection.EventHandler" }; + } + + void SAL_CALL EventHandler::inspect( const Reference< XInterface >& _rxIntrospectee ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( !_rxIntrospectee.is() ) + throw NullPointerException(); + + m_xComponent.set( _rxIntrospectee, UNO_QUERY_THROW ); + + m_bEventsMapInitialized = false; + EventMap().swap(m_aEvents); + + m_bIsDialogElement = false; + m_nGridColumnType = -1; + try + { + Reference< XPropertySetInfo > xPSI( m_xComponent->getPropertySetInfo() ); + m_bIsDialogElement = xPSI.is() + && xPSI->hasPropertyByName( PROPERTY_WIDTH ) + && xPSI->hasPropertyByName( PROPERTY_HEIGHT ) + && xPSI->hasPropertyByName( PROPERTY_POSITIONX ) + && xPSI->hasPropertyByName( PROPERTY_POSITIONY ); + + Reference< XChild > xAsChild( _rxIntrospectee, UNO_QUERY ); + if ( xAsChild.is() && !Reference< XForm >( _rxIntrospectee, UNO_QUERY ).is() ) + { + if ( FormComponentType::GRIDCONTROL == classifyComponent( xAsChild->getParent() ) ) + { + m_nGridColumnType = classifyComponent( _rxIntrospectee ); + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + Any SAL_CALL EventHandler::getPropertyValue( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + const EventDescription& rEvent = impl_getEventForName_throw( _rPropertyName ); + + std::vector< ScriptEventDescriptor > aEvents; + impl_getComponentScriptEvents_nothrow( aEvents ); + + ScriptEventDescriptor aPropertyValue; + for ( const ScriptEventDescriptor& rSCD : aEvents ) + { + if ( rEvent.sListenerClassName == rSCD.ListenerType + && rEvent.sListenerMethodName == rSCD.EventMethod + ) + { + aPropertyValue = rSCD; + break; + } + } + + return Any( aPropertyValue ); + } + + void SAL_CALL EventHandler::setPropertyValue( const OUString& _rPropertyName, const Any& _rValue ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + const EventDescription& rEvent = impl_getEventForName_throw( _rPropertyName ); + + ScriptEventDescriptor aNewScriptEvent; + OSL_VERIFY( _rValue >>= aNewScriptEvent ); + + ScriptEventDescriptor aOldScriptEvent; + OSL_VERIFY( getPropertyValue( _rPropertyName ) >>= aOldScriptEvent ); + if ( aOldScriptEvent == aNewScriptEvent ) + return; + + if ( m_bIsDialogElement ) + impl_setDialogElementScriptEvent_nothrow( aNewScriptEvent ); + else + impl_setFormComponentScriptEvent_nothrow( aNewScriptEvent ); + + PropertyHandlerHelper::setContextDocumentModified( m_xContext ); + + PropertyChangeEvent aEvent; + aEvent.Source = m_xComponent; + aEvent.PropertyHandle = rEvent.nId; + aEvent.PropertyName = _rPropertyName; + aEvent.OldValue <<= aOldScriptEvent; + aEvent.NewValue <<= aNewScriptEvent; + m_aPropertyListeners.notifyEach( &XPropertyChangeListener::propertyChange, aEvent ); + } + + Any SAL_CALL EventHandler::convertToPropertyValue( const OUString& _rPropertyName, const Any& _rControlValue ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + OUString sNewScriptCode; + OSL_VERIFY( _rControlValue >>= sNewScriptCode ); + + std::vector< ScriptEventDescriptor > aAllAssignedEvents; + impl_getComponentScriptEvents_nothrow( aAllAssignedEvents ); + + const EventDescription& rEvent = impl_getEventForName_throw( _rPropertyName ); + ScriptEventDescriptor aAssignedScript = lcl_getAssignedScriptEvent( rEvent, aAllAssignedEvents ); + + OSL_ENSURE( sNewScriptCode.isEmpty(), "EventHandler::convertToPropertyValue: cannot convert a non-empty display name!" ); + // Usually, there is no possibility for the user to change the content of an event binding directly in the + // input field, this instead is done with the macro assignment dialog. + // The only exception is the user pressing "DEL" while the control has the focus, in this case, we reset the + // control content to an empty string. So this is the only scenario where this method is allowed to be called. + + // Strictly, we would be able to convert the display value to a property value, + // using the "name (location, language)" format we used in convertToControlValue. However, + // there is no need for this code... + + aAssignedScript.ScriptCode = sNewScriptCode; + return Any( aAssignedScript ); + } + + Any SAL_CALL EventHandler::convertToControlValue( const OUString& /*_rPropertyName*/, const Any& _rPropertyValue, const Type& _rControlValueType ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + ScriptEventDescriptor aScriptEvent; + OSL_VERIFY( _rPropertyValue >>= aScriptEvent ); + + OSL_ENSURE( _rControlValueType.getTypeClass() == TypeClass_STRING, + "EventHandler::convertToControlValue: unexpected ControlValue type class!" ); + + OUString sScript( aScriptEvent.ScriptCode ); + if ( !sScript.isEmpty() ) + { + // format is: "name (location, language)" + try + { + // parse + Reference< XUriReferenceFactory > xUriRefFac = UriReferenceFactory::create( m_xContext ); + Reference< XVndSunStarScriptUrlReference > xScriptUri( xUriRefFac->parse( sScript ), UNO_QUERY_THROW ); + + OUStringBuffer aComposeBuffer; + + // name + aComposeBuffer.append( xScriptUri->getName() ); + + // location + const OUString sLocation = xScriptUri->getParameter( "location" ); + const OUString sLanguage = xScriptUri->getParameter( "language" ); + + if ( !(sLocation.isEmpty() && sLanguage.isEmpty()) ) + { + aComposeBuffer.append( " (" ); + + // location + OSL_ENSURE( !sLocation.isEmpty(), "EventHandler::convertToControlValue: unexpected: no location!" ); + if ( !sLocation.isEmpty() ) + { + aComposeBuffer.append( sLocation + ", " ); + } + + // language + if ( !sLanguage.isEmpty() ) + { + aComposeBuffer.append( sLanguage ); + } + + aComposeBuffer.append( ')' ); + } + + sScript = aComposeBuffer.makeStringAndClear(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + return Any( sScript ); + } + + PropertyState SAL_CALL EventHandler::getPropertyState( const OUString& /*_rPropertyName*/ ) + { + return PropertyState_DIRECT_VALUE; + } + + void SAL_CALL EventHandler::addPropertyChangeListener( const Reference< XPropertyChangeListener >& _rxListener ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !_rxListener.is() ) + throw NullPointerException(); + m_aPropertyListeners.addInterface( _rxListener ); + } + + void SAL_CALL EventHandler::removePropertyChangeListener( const Reference< XPropertyChangeListener >& _rxListener ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + m_aPropertyListeners.removeInterface( _rxListener ); + } + + Sequence< Property > SAL_CALL EventHandler::getSupportedProperties() + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !m_bEventsMapInitialized ) + { + m_bEventsMapInitialized = true; + try + { + std::vector< Type > aListeners; + impl_getComponentListenerTypes_nothrow( aListeners ); + + OUString sListenerClassName; + + // loop through all listeners and all methods, and see which we can present at the UI + for ( const Type& rListener : aListeners ) + { + // the programmatic name of the listener, to be used as "property" name + sListenerClassName = rListener.getTypeName(); + OSL_ENSURE( !sListenerClassName.isEmpty(), "EventHandler::getSupportedProperties: strange - no listener name ..." ); + if ( sListenerClassName.isEmpty() ) + continue; + + // loop through all methods + const Sequence<OUString> aEventMethods = comphelper::getEventMethodsForType( rListener ); + for (const OUString& rMethod : aEventMethods) + { + EventDescription aEvent; + if ( !lcl_getEventDescriptionForMethod( rMethod, aEvent ) ) + continue; + + if ( !impl_filterMethod_nothrow( aEvent ) ) + continue; + + m_aEvents.emplace( + lcl_getEventPropertyName( sListenerClassName, rMethod ), aEvent ); + } + } + + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + // sort them by ID - this is the relative ordering in the UI + std::map< EventId, Property > aOrderedProperties; + for (auto const& event : m_aEvents) + { + aOrderedProperties[ event.second.nId ] = Property( + event.first, event.second.nId, + ::cppu::UnoType<OUString>::get(), + PropertyAttribute::BOUND ); + } + + return comphelper::mapValuesToSequence( aOrderedProperties ); + } + + Sequence< OUString > SAL_CALL EventHandler::getSupersededProperties( ) + { + // none + return Sequence< OUString >( ); + } + + Sequence< OUString > SAL_CALL EventHandler::getActuatingProperties( ) + { + // none + return Sequence< OUString >( ); + } + + LineDescriptor SAL_CALL EventHandler::describePropertyLine( const OUString& _rPropertyName, + const Reference< XPropertyControlFactory >& _rxControlFactory ) + { + if ( !_rxControlFactory.is() ) + throw NullPointerException(); + + ::osl::MutexGuard aGuard( m_aMutex ); + + LineDescriptor aDescriptor; + + aDescriptor.Control = _rxControlFactory->createPropertyControl( PropertyControlType::TextField, true ); + new PropertyControlExtender( aDescriptor.Control ); + + const EventDescription& rEvent = impl_getEventForName_throw( _rPropertyName ); + aDescriptor.DisplayName = rEvent.sDisplayName; + aDescriptor.HelpURL = HelpIdUrl::getHelpURL( rEvent.sHelpId ); + aDescriptor.PrimaryButtonId = OStringToOUString(rEvent.sUniqueBrowseId, RTL_TEXTENCODING_UTF8); + aDescriptor.HasPrimaryButton = true; + aDescriptor.Category = "Events"; + return aDescriptor; + } + + sal_Bool SAL_CALL EventHandler::isComposable( const OUString& /*_rPropertyName*/ ) + { + return false; + } + + InteractiveSelectionResult SAL_CALL EventHandler::onInteractivePropertySelection( const OUString& _rPropertyName, sal_Bool /*_bPrimary*/, Any& /*_rData*/, const Reference< XObjectInspectorUI >& _rxInspectorUI ) + { + if ( !_rxInspectorUI.is() ) + throw NullPointerException(); + + ::osl::MutexGuard aGuard( m_aMutex ); + const EventDescription& rForEvent = impl_getEventForName_throw( _rPropertyName ); + + std::vector< ScriptEventDescriptor > aAllAssignedEvents; + impl_getComponentScriptEvents_nothrow( aAllAssignedEvents ); + + // SvxMacroAssignDlg-compatible structure holding all event/assignments + ::rtl::Reference< EventHolder > pEventHolder( new EventHolder ); + + for (auto const& event : m_aEvents) + { + // the script which is assigned to the current event (if any) + ScriptEventDescriptor aAssignedScript = lcl_getAssignedScriptEvent( event.second, aAllAssignedEvents ); + pEventHolder->addEvent( event.second.nId, event.second.sListenerMethodName, aAssignedScript ); + } + + // the initial selection in the dialog + const Sequence< OUString > aNames( pEventHolder->getElementNames() ); + const OUString* pChosenEvent = std::find( aNames.begin(), aNames.end(), rForEvent.sListenerMethodName ); + sal_uInt16 nInitialSelection = static_cast<sal_uInt16>( pChosenEvent - aNames.begin() ); + + // the dialog + SvxAbstractDialogFactory* pFactory = SvxAbstractDialogFactory::Create(); + + ScopedVclPtr<VclAbstractDialog> pDialog( pFactory->CreateSvxMacroAssignDlg( + PropertyHandlerHelper::getDialogParentFrame( m_xContext ), + impl_getContextFrame_nothrow(), + m_bIsDialogElement, + pEventHolder, + nInitialSelection + ) ); + + if ( !pDialog ) + return InteractiveSelectionResult_Cancelled; + + // DF definite problem here + // OK & Cancel seem to be both returning 0 + if ( pDialog->Execute() == RET_CANCEL ) + return InteractiveSelectionResult_Cancelled; + + try + { + for (auto const& event : m_aEvents) + { + ScriptEventDescriptor aScriptDescriptor( pEventHolder->getNormalizedDescriptorByName( event.second.sListenerMethodName ) ); + + // set the new "property value" + setPropertyValue( + lcl_getEventPropertyName( event.second.sListenerClassName, event.second.sListenerMethodName ), + Any( aScriptDescriptor ) + ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + + return InteractiveSelectionResult_Success; + } + + void SAL_CALL EventHandler::actuatingPropertyChanged( const OUString& /*_rActuatingPropertyName*/, const Any& /*_rNewValue*/, const Any& /*_rOldValue*/, const Reference< XObjectInspectorUI >& /*_rxInspectorUI*/, sal_Bool /*_bFirstTimeInit*/ ) + { + OSL_FAIL( "EventHandler::actuatingPropertyChanged: no actuating properties -> no callback (well, this is how it *should* be!)" ); + } + + IMPLEMENT_FORWARD_XCOMPONENT( EventHandler, EventHandler_Base ) + + void SAL_CALL EventHandler::disposing() + { + EventMap().swap(m_aEvents); + m_xComponent.clear(); + } + + sal_Bool SAL_CALL EventHandler::suspend( sal_Bool /*_bSuspend*/ ) + { + return true; + } + + Reference< XFrame > EventHandler::impl_getContextFrame_nothrow() const + { + Reference< XFrame > xContextFrame; + + try + { + Reference< XModel > xContextDocument( PropertyHandlerHelper::getContextDocument(m_xContext), UNO_QUERY_THROW ); + Reference< XController > xController( xContextDocument->getCurrentController(), UNO_SET_THROW ); + xContextFrame.set( xController->getFrame(), UNO_SET_THROW ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + + return xContextFrame; + } + + sal_Int32 EventHandler::impl_getComponentIndexInParent_throw() const + { + Reference< XChild > xChild( m_xComponent, UNO_QUERY_THROW ); + Reference< XIndexAccess > xParentAsIndexAccess( xChild->getParent(), UNO_QUERY_THROW ); + + // get the index of the inspected object within its parent container + sal_Int32 nElements = xParentAsIndexAccess->getCount(); + for ( sal_Int32 i=0; i<nElements; ++i ) + { + Reference< XInterface > xElement( xParentAsIndexAccess->getByIndex( i ), UNO_QUERY_THROW ); + if ( xElement == m_xComponent ) + return i; + } + throw NoSuchElementException(); + } + + void EventHandler::impl_getFormComponentScriptEvents_nothrow( std::vector < ScriptEventDescriptor >& _out_rEvents ) const + { + _out_rEvents.clear(); + try + { + Reference< XChild > xChild( m_xComponent, UNO_QUERY_THROW ); + Reference< XEventAttacherManager > xEventManager( xChild->getParent(), UNO_QUERY_THROW ); + comphelper::sequenceToContainer(_out_rEvents, xEventManager->getScriptEvents( impl_getComponentIndexInParent_throw() )); + + // the form component script API has unqualified listener names, but for normalization + // purpose, we want fully qualified ones + for ( ScriptEventDescriptor& rSED : _out_rEvents) + { + rSED.ListenerType = lcl_getQualifiedKnownListenerName( rSED ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + void EventHandler::impl_getComponentListenerTypes_nothrow( std::vector< Type >& _out_rTypes ) const + { + _out_rTypes.clear(); + try + { + // we use a set to avoid duplicates + TypeBag aListeners; + + Reference< XIntrospection > xIntrospection = theIntrospection::get( m_xContext ); + + // --- model listeners + lcl_addListenerTypesFor_throw( + m_xComponent, xIntrospection, aListeners ); + + // --- "secondary component" (usually: "control" listeners) + { + Reference< XInterface > xSecondaryComponent( impl_getSecondaryComponentForEventInspection_throw() ); + lcl_addListenerTypesFor_throw( xSecondaryComponent, xIntrospection, aListeners ); + ::comphelper::disposeComponent( xSecondaryComponent ); + } + + // now that they're disambiguated, copy these types into our member + _out_rTypes.insert( _out_rTypes.end(), aListeners.begin(), aListeners.end() ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + void EventHandler::impl_getDialogElementScriptEvents_nothrow( std::vector < ScriptEventDescriptor >& _out_rEvents ) const + { + _out_rEvents.clear(); + try + { + Reference< XScriptEventsSupplier > xEventsSupplier( m_xComponent, UNO_QUERY_THROW ); + Reference< XNameContainer > xEvents( xEventsSupplier->getEvents(), UNO_SET_THROW ); + Sequence< OUString > aEventNames( xEvents->getElementNames() ); + + sal_Int32 nEventCount = aEventNames.getLength(); + _out_rEvents.resize( nEventCount ); + + for( sal_Int32 i = 0; i < nEventCount; ++i ) + OSL_VERIFY( xEvents->getByName( aEventNames[i] ) >>= _out_rEvents[i] ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + Reference< XInterface > EventHandler::impl_getSecondaryComponentForEventInspection_throw( ) const + { + Reference< XInterface > xReturn; + + // if it's a form, create a form controller for the additional events + Reference< XForm > xComponentAsForm( m_xComponent, UNO_QUERY ); + if ( xComponentAsForm.is() ) + { + Reference< XTabControllerModel > xComponentAsTCModel( m_xComponent, UNO_QUERY_THROW ); + Reference< XFormController > xController = FormController::create( m_xContext ); + xController->setModel( xComponentAsTCModel ); + + xReturn = xController; + } + else + { + OUString sControlService; + OSL_VERIFY( m_xComponent->getPropertyValue( PROPERTY_DEFAULTCONTROL ) >>= sControlService ); + + xReturn = m_xContext->getServiceManager()->createInstanceWithContext( sControlService, m_xContext ); + } + return xReturn; + } + + const EventDescription& EventHandler::impl_getEventForName_throw( const OUString& _rPropertyName ) const + { + EventMap::const_iterator pos = m_aEvents.find( _rPropertyName ); + if ( pos == m_aEvents.end() ) + throw UnknownPropertyException(_rPropertyName); + return pos->second; + } + + namespace + { + bool lcl_endsWith( std::u16string_view _rText, std::u16string_view _rCheck ) + { + size_t nTextLen = _rText.size(); + size_t nCheckLen = _rCheck.size(); + if ( nCheckLen > nTextLen ) + return false; + + return _rText.find( _rCheck ) == ( nTextLen - nCheckLen ); + } + } + + void EventHandler::impl_setFormComponentScriptEvent_nothrow( const ScriptEventDescriptor& _rScriptEvent ) + { + try + { + OUString sScriptCode( _rScriptEvent.ScriptCode ); + OUString sScriptType( _rScriptEvent.ScriptType ); + bool bResetScript = sScriptCode.isEmpty(); + + sal_Int32 nObjectIndex = impl_getComponentIndexInParent_throw(); + Reference< XChild > xChild( m_xComponent, UNO_QUERY_THROW ); + Reference< XEventAttacherManager > xEventManager( xChild->getParent(), UNO_QUERY_THROW ); + std::vector< ScriptEventDescriptor > aEvents; + comphelper::sequenceToContainer( aEvents, xEventManager->getScriptEvents( nObjectIndex ) ); + + // is there already a registered script for this event? + sal_Int32 eventCount = aEvents.size(), event = 0; + for ( event = 0; event < eventCount; ++event ) + { + ScriptEventDescriptor* pEvent = &aEvents[event]; + if ( ( pEvent->EventMethod == _rScriptEvent.EventMethod ) + && ( lcl_endsWith( _rScriptEvent.ListenerType, pEvent->ListenerType ) ) + // (strange enough, the events we get from getScriptEvents are not fully qualified) + ) + { + // yes + if ( !bResetScript ) + { + // set to something non-empty -> overwrite + pEvent->ScriptCode = sScriptCode; + pEvent->ScriptType = sScriptType; + } + else + { + // set to empty -> remove from vector + aEvents.erase(aEvents.begin() + event ); + --eventCount; + } + break; + } + } + if ( ( event >= eventCount ) && !bResetScript ) + { + // no, did not find it -> append + aEvents.push_back( _rScriptEvent ); + } + + xEventManager->revokeScriptEvents( nObjectIndex ); + xEventManager->registerScriptEvents( nObjectIndex, comphelper::containerToSequence(aEvents) ); + + PropertyHandlerHelper::setContextDocumentModified( m_xContext ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + void EventHandler::impl_setDialogElementScriptEvent_nothrow( const ScriptEventDescriptor& _rScriptEvent ) + { + try + { + OUString sScriptCode( _rScriptEvent.ScriptCode ); + bool bResetScript = sScriptCode.isEmpty(); + + Reference< XScriptEventsSupplier > xEventsSupplier( m_xComponent, UNO_QUERY_THROW ); + Reference< XNameContainer > xEvents( xEventsSupplier->getEvents(), UNO_SET_THROW ); + + OUString sCompleteName = + _rScriptEvent.ListenerType + + "::" + + _rScriptEvent.EventMethod; + + bool bExists = xEvents->hasByName( sCompleteName ); + + if ( bResetScript ) + { + if ( bExists ) + xEvents->removeByName( sCompleteName ); + } + else + { + Any aNewValue; aNewValue <<= _rScriptEvent; + + if ( bExists ) + xEvents->replaceByName( sCompleteName, aNewValue ); + else + xEvents->insertByName( sCompleteName, aNewValue ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + bool EventHandler::impl_filterMethod_nothrow( const EventDescription& _rEvent ) const + { + // some (control-triggered) events do not make sense for certain grid control columns. However, + // our mechanism to retrieve control-triggered events does not know about this, so we do some + // late filtering here. + switch ( m_nGridColumnType ) + { + case FormComponentType::COMBOBOX: + if ( UID_BRWEVT_ACTIONPERFORMED == _rEvent.sUniqueBrowseId ) + return false; + break; + case FormComponentType::LISTBOX: + if ( ( UID_BRWEVT_CHANGED == _rEvent.sUniqueBrowseId ) + || ( UID_BRWEVT_ACTIONPERFORMED == _rEvent.sUniqueBrowseId ) + ) + return false; + break; + } + + return true; + } + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_EventHandler_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::EventHandler(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/eventhandler.hxx b/extensions/source/propctrlr/eventhandler.hxx new file mode 100644 index 0000000000..4fa5c85d2e --- /dev/null +++ b/extensions/source/propctrlr/eventhandler.hxx @@ -0,0 +1,240 @@ +/* -*- 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 . + */ + +#pragma once + +#include "pcrcommon.hxx" + +#include <com/sun/star/script/ScriptEventDescriptor.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/inspection/XPropertyHandler.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/compbase.hxx> +#include <unotools/resmgr.hxx> + +#include <unordered_map> + +namespace pcr +{ + + + //= EventDescription + + typedef sal_Int32 EventId; + struct EventDescription + { + public: + OUString sDisplayName; + OUString sListenerClassName; + OUString sListenerMethodName; + OUString sHelpId; + OString sUniqueBrowseId; + EventId nId; + + EventDescription() + :nId( 0 ) + { + } + + EventDescription( + EventId _nId, + std::u16string_view listenerClassName, + std::u16string_view listenerMethodName, + TranslateId pDisplayNameResId, + OUString _sHelpId, + OString _sUniqueBrowseId ); + }; + + typedef std::unordered_map< OUString, EventDescription > EventMap; + + + //= EventHandler + + typedef ::cppu::WeakComponentImplHelper < css::inspection::XPropertyHandler + , css::lang::XServiceInfo + > EventHandler_Base; + class EventHandler final : public EventHandler_Base + { + private: + mutable ::osl::Mutex m_aMutex; + + /// the context in which the instance was created + css::uno::Reference< css::uno::XComponentContext > m_xContext; + /// the properties of the object we're handling + css::uno::Reference< css::beans::XPropertySet > m_xComponent; + /// our XPropertyChangeListener(s) + PropertyChangeListeners m_aPropertyListeners; + /// cache of the events we found at our introspectee + EventMap m_aEvents; + /// has m_aEvents been initialized? + bool m_bEventsMapInitialized; + /// is our introspectee a dialog element? + bool m_bIsDialogElement; + // TODO: move different handling into different derived classes? + /// (FormComponent) type of the grid column being inspected, or -1 if we're not inspecting a grid column + sal_Int16 m_nGridColumnType; + + public: + explicit EventHandler( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext + ); + + virtual ~EventHandler() override; + + private: + // XPropertyHandler overridables + virtual void SAL_CALL inspect( const css::uno::Reference< css::uno::XInterface >& _rxIntrospectee ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& _rPropertyName ) override; + virtual void SAL_CALL setPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rValue ) override; + virtual css::uno::Any SAL_CALL convertToPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rControlValue ) override; + virtual css::uno::Any SAL_CALL convertToControlValue( const OUString& _rPropertyName, const css::uno::Any& _rPropertyValue, const css::uno::Type& _rControlValueType ) override; + virtual css::beans::PropertyState SAL_CALL getPropertyState( const OUString& _rPropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ) override; + virtual css::uno::Sequence< css::beans::Property > SAL_CALL getSupportedProperties() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupersededProperties( ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getActuatingProperties( ) override; + virtual css::inspection::LineDescriptor SAL_CALL describePropertyLine( const OUString& _rPropertyName, const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory ) override; + virtual sal_Bool SAL_CALL isComposable( const OUString& _rPropertyName ) override; + virtual css::inspection::InteractiveSelectionResult SAL_CALL onInteractivePropertySelection( const OUString& _rPropertyName, sal_Bool _bPrimary, css::uno::Any& _rData, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI ) override; + virtual void SAL_CALL actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const css::uno::Any& _rNewValue, const css::uno::Any& _rOldValue, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI, sal_Bool _bFirstTimeInit ) override; + virtual sal_Bool SAL_CALL suspend( sal_Bool _bSuspend ) override; + + // XComponent + DECLARE_XCOMPONENT() + virtual void SAL_CALL disposing() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + /** returns the script events associated with our introspectee + @param _out_rEvents + Takes, upon successful return, the events currently associated with the introspectee + @precond + Our introspectee is a form component + */ + void impl_getFormComponentScriptEvents_nothrow( + std::vector< css::script::ScriptEventDescriptor >& _out_rEvents + ) const; + + /** returns the script events associated with our introspectee + @param _out_rEvents + Takes, upon successful return, the events currently associated with the introspectee + @precond + Our introspectee is a dialog element + */ + void impl_getDialogElementScriptEvents_nothrow( + std::vector< css::script::ScriptEventDescriptor >& _out_rEvents + ) const; + + /** returns the script events associated with our introspectee + @param _out_rEvents + Takes, the events currently associated with the introspectee + */ + void impl_getComponentScriptEvents_nothrow( + std::vector< css::script::ScriptEventDescriptor >& _out_rEvents + ) const + { + if ( m_bIsDialogElement ) + impl_getDialogElementScriptEvents_nothrow( _out_rEvents ); + else + impl_getFormComponentScriptEvents_nothrow( _out_rEvents ); + } + + /** returns the types of the listeners which can be registered at our introspectee + @param _out_rTypes + Takes, upon successful return, the types of possible listeners at the introspectee + */ + void impl_getComponentListenerTypes_nothrow( + std::vector< css::uno::Type >& _out_rTypes + ) const; + + /** returns a secondary component to be used for event inspection + + In the UI, we want to mix events for the control model with events for the control. + Since our introspectee is a model, this method creates a control for it (if possible). + + @return + the secondary component whose events should be mixed with the introspectee's events + The caller takes the ownership of the component (if not <NULL/>). + + @throws + if an unexpected error occurs during creation of the secondary component. + A <NULL/> component to be returned is not unexpected, but allowed + + @precond + ->m_xComponent is not <NULL/> + */ + css::uno::Reference< css::uno::XInterface > + impl_getSecondaryComponentForEventInspection_throw( ) const; + + /** returns the event description for the given (programmatic) property name + @param _rPropertyName + the name whose event description should be looked up + @return + the event description for the property name + @throws css::beans::UnknownPropertyException + if our introspectee does not have an event with the given logical name (see ->getSupportedProperties) + */ + const EventDescription& + impl_getEventForName_throw( const OUString& _rPropertyName ) const; + + /** returns the index of our component within its parent, if this parent can be + obtained (XChild::getParent) and supports an ->XIndexAccess interface + */ + sal_Int32 impl_getComponentIndexInParent_throw() const; + + /** sets a given script event as event handler at a form component + + @param _rScriptEvent + the script event to set + */ + void impl_setFormComponentScriptEvent_nothrow( const css::script::ScriptEventDescriptor& _rScriptEvent ); + + /** sets a given script event as event handler at a dialog component + + @param _rScriptEvent + the script event to set + */ + void impl_setDialogElementScriptEvent_nothrow( const css::script::ScriptEventDescriptor& _rScriptEvent ); + + /** returns the frame associated with our context document + */ + css::uno::Reference< css::frame::XFrame > + impl_getContextFrame_nothrow() const; + + /** approves or denies a certain method to be included in the UI + @return + <TRUE/> if and only if the given method is allowed. + */ + bool impl_filterMethod_nothrow( const EventDescription& _rEvent ) const; + + EventHandler( const EventHandler& ) = delete; + EventHandler& operator=( const EventHandler& ) = delete; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/fontdialog.cxx b/extensions/source/propctrlr/fontdialog.cxx new file mode 100644 index 0000000000..59757421a0 --- /dev/null +++ b/extensions/source/propctrlr/fontdialog.cxx @@ -0,0 +1,600 @@ +/* -*- 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 "fontdialog.hxx" +#include <vcl/outdev.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/unohelp.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <comphelper/types.hxx> +#include <comphelper/extract.hxx> +#include <com/sun/star/awt/FontDescriptor.hpp> +#include <com/sun/star/awt/FontWeight.hpp> +#include <com/sun/star/awt/FontSlant.hpp> +#include <com/sun/star/awt/FontUnderline.hpp> +#include <com/sun/star/awt/FontStrikeout.hpp> +#include "formstrings.hxx" +#include <editeng/charreliefitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/flstitem.hxx> +#include <svtools/ctrltool.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <svx/svxids.hrc> +#include <svx/svxdlg.hxx> +#include <svx/dialogs.hrc> +#include <svx/flagsdef.hxx> + + +namespace pcr +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::beans; + + + //= OFontPropertyExtractor + + namespace FontItemIds + { + constexpr sal_uInt16 CFID_FONT = 1; + constexpr sal_uInt16 CFID_HEIGHT = 2; + constexpr sal_uInt16 CFID_WEIGHT = 3; + constexpr sal_uInt16 CFID_POSTURE = 4; + constexpr sal_uInt16 CFID_LANGUAGE = 5; + constexpr sal_uInt16 CFID_UNDERLINE = 6; + constexpr sal_uInt16 CFID_STRIKEOUT = 7; + constexpr TypedWhichId<SvxWordLineModeItem> CFID_WORDLINEMODE(8); + constexpr sal_uInt16 CFID_CHARCOLOR = 9; + constexpr sal_uInt16 CFID_RELIEF = 10; + constexpr TypedWhichId<SvxEmphasisMarkItem> CFID_EMPHASIS(11); + + constexpr sal_uInt16 CFID_CJK_FONT = 12; + constexpr sal_uInt16 CFID_CJK_HEIGHT = 13; + constexpr sal_uInt16 CFID_CJK_WEIGHT = 14; + constexpr sal_uInt16 CFID_CJK_POSTURE = 15; + constexpr sal_uInt16 CFID_CJK_LANGUAGE = 16; + constexpr sal_uInt16 CFID_CASEMAP = 17; + constexpr TypedWhichId<SvxContourItem> CFID_CONTOUR(18); + constexpr TypedWhichId<SvxShadowedItem> CFID_SHADOWED(19); + + constexpr sal_uInt16 CFID_FONTLIST = 20; + + constexpr sal_uInt16 CFID_FIRST_ITEM_ID = CFID_FONT; + constexpr sal_uInt16 CFID_LAST_ITEM_ID = CFID_FONTLIST; + } + + namespace { + + class OFontPropertyExtractor + { + protected: + css::uno::Reference< css::beans::XPropertySet > + m_xPropValueAccess; + css::uno::Reference< css::beans::XPropertyState > + m_xPropStateAccess; + + public: + explicit OFontPropertyExtractor( const css::uno::Reference< css::beans::XPropertySet >& + _rxProps ); + + public: + bool getCheckFontProperty(const OUString& _rPropName, css::uno::Any& _rValue); + OUString getStringFontProperty(const OUString& _rPropName, const OUString& _rDefault); + sal_Int16 getInt16FontProperty(const OUString& _rPropName, const sal_Int16 _nDefault); + sal_Int32 getInt32FontProperty(const OUString& _rPropName, const sal_Int32 _nDefault); + float getFloatFontProperty(const OUString& _rPropName, const float _nDefault); + + void invalidateItem( + const OUString& _rPropName, + sal_uInt16 _nItemId, + SfxItemSet& _rSet, + bool _bForceInvalidation = false); + }; + + } + + OFontPropertyExtractor::OFontPropertyExtractor(const Reference< XPropertySet >& _rxProps) + :m_xPropValueAccess(_rxProps) + ,m_xPropStateAccess(_rxProps, UNO_QUERY) + { + OSL_ENSURE(m_xPropValueAccess.is(), "OFontPropertyExtractor::OFontPropertyExtractor: invalid property set!"); + } + + + bool OFontPropertyExtractor::getCheckFontProperty(const OUString& _rPropName, Any& _rValue) + { + _rValue = m_xPropValueAccess->getPropertyValue(_rPropName); + if (m_xPropStateAccess.is()) + return PropertyState_DEFAULT_VALUE == m_xPropStateAccess->getPropertyState(_rPropName); + + return false; + } + + + OUString OFontPropertyExtractor::getStringFontProperty(const OUString& _rPropName, const OUString& _rDefault) + { + Any aValue; + if (getCheckFontProperty(_rPropName, aValue)) + return _rDefault; + + return ::comphelper::getString(aValue); + } + + + sal_Int16 OFontPropertyExtractor::getInt16FontProperty(const OUString& _rPropName, const sal_Int16 _nDefault) + { + Any aValue; + if (getCheckFontProperty(_rPropName, aValue)) + return _nDefault; + + sal_Int32 nValue(_nDefault); + ::cppu::enum2int(nValue, aValue); + return static_cast<sal_Int16>(nValue); + } + + + sal_Int32 OFontPropertyExtractor::getInt32FontProperty(const OUString& _rPropName, const sal_Int32 _nDefault) + { + Any aValue; + if (getCheckFontProperty(_rPropName, aValue)) + return _nDefault; + + sal_Int32 nValue(_nDefault); + ::cppu::enum2int(nValue, aValue); + return nValue; + } + + + float OFontPropertyExtractor::getFloatFontProperty(const OUString& _rPropName, const float _nDefault) + { + Any aValue; + if (getCheckFontProperty(_rPropName, aValue)) + return _nDefault; + + return ::comphelper::getFloat(aValue); + } + + + void OFontPropertyExtractor::invalidateItem(const OUString& _rPropName, sal_uInt16 _nItemId, SfxItemSet& _rSet, bool _bForceInvalidation) + { + if ( _bForceInvalidation + || ( m_xPropStateAccess.is() + && (PropertyState_AMBIGUOUS_VALUE == m_xPropStateAccess->getPropertyState(_rPropName)) + ) + ) + _rSet.InvalidateItem(_nItemId); + } + + //= ControlCharacterDialog + ControlCharacterDialog::ControlCharacterDialog(weld::Window* pParent, const SfxItemSet& _rCoreSet) + : SfxTabDialogController(pParent, "modules/spropctrlr/ui/controlfontdialog.ui", "ControlFontDialog", &_rCoreSet) + { + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + AddTabPage("font", pFact->GetTabPageCreatorFunc(RID_SVXPAGE_CHAR_NAME), nullptr ); + AddTabPage("fonteffects", pFact->GetTabPageCreatorFunc(RID_SVXPAGE_CHAR_EFFECTS), nullptr ); + } + + ControlCharacterDialog::~ControlCharacterDialog() + { + } + + void ControlCharacterDialog::translatePropertiesToItems(const Reference< XPropertySet >& _rxModel, SfxItemSet* _pSet) + { + OSL_ENSURE(_pSet && _rxModel.is(), "ControlCharacterDialog::translatePropertiesToItems: invalid arguments!"); + if (!_pSet || !_rxModel.is()) + return; + + try + { + OFontPropertyExtractor aPropExtractor(_rxModel); + + // some items, which may be in default state, have to be filled with non-void information + vcl::Font aDefaultVCLFont = Application::GetDefaultDevice()->GetSettings().GetStyleSettings().GetAppFont(); + css::awt::FontDescriptor aDefaultFont = VCLUnoHelper::CreateFontDescriptor(aDefaultVCLFont); + + // get the current properties + OUString aFontName = aPropExtractor.getStringFontProperty(PROPERTY_FONT_NAME, aDefaultFont.Name); + OUString aFontStyleName = aPropExtractor.getStringFontProperty(PROPERTY_FONT_STYLENAME, aDefaultFont.StyleName); + sal_Int16 nFontFamily = aPropExtractor.getInt16FontProperty(PROPERTY_FONT_FAMILY, aDefaultFont.Family); + sal_Int16 nFontCharset = aPropExtractor.getInt16FontProperty(PROPERTY_FONT_CHARSET, aDefaultFont.CharSet); + float nFontHeight = aPropExtractor.getFloatFontProperty(PROPERTY_FONT_HEIGHT, static_cast<float>(aDefaultFont.Height)); + float nFontWeight = aPropExtractor.getFloatFontProperty(PROPERTY_FONT_WEIGHT, aDefaultFont.Weight); + css::awt::FontSlant nFontSlant = static_cast<css::awt::FontSlant>(aPropExtractor.getInt16FontProperty(PROPERTY_FONT_SLANT, static_cast<sal_Int16>(aDefaultFont.Slant))); + sal_Int16 nFontLineStyle = aPropExtractor.getInt16FontProperty(PROPERTY_FONT_UNDERLINE, aDefaultFont.Underline); + sal_Int16 nFontStrikeout = aPropExtractor.getInt16FontProperty(PROPERTY_FONT_STRIKEOUT, aDefaultFont.Strikeout); + + sal_Int32 nTextLineColor = aPropExtractor.getInt32FontProperty(PROPERTY_TEXTLINECOLOR, sal_uInt32(COL_AUTO)); + sal_Int16 nFontRelief = aPropExtractor.getInt16FontProperty(PROPERTY_FONT_RELIEF, static_cast<sal_Int16>(aDefaultVCLFont.GetRelief())); + sal_Int16 nFontEmphasisMark = aPropExtractor.getInt16FontProperty(PROPERTY_FONT_EMPHASIS_MARK, static_cast<sal_uInt16>(aDefaultVCLFont.GetEmphasisMark())); + + Any aValue; + bool bWordLineMode = aPropExtractor.getCheckFontProperty(PROPERTY_WORDLINEMODE, aValue) ? aDefaultFont.WordLineMode : ::cppu::any2bool(aValue); + sal_Int32 nColor32 = aPropExtractor.getInt32FontProperty(PROPERTY_TEXTCOLOR, 0); + + // build SfxItems with the values + SvxFontItem aFontItem(static_cast<FontFamily>(nFontFamily), aFontName, aFontStyleName, PITCH_DONTKNOW, nFontCharset, FontItemIds::CFID_FONT); + + nFontHeight = static_cast<float>(o3tl::convert(nFontHeight, o3tl::Length::pt, o3tl::Length::twip)); + + SvxFontHeightItem aSvxFontHeightItem(static_cast<sal_uInt32>(nFontHeight),100,FontItemIds::CFID_HEIGHT); + + FontWeight eWeight=vcl::unohelper::ConvertFontWeight(nFontWeight); + FontItalic eItalic=vcl::unohelper::ConvertFontSlant(nFontSlant); + FontLineStyle eUnderline=static_cast<FontLineStyle>(nFontLineStyle); + FontStrikeout eStrikeout=static_cast<FontStrikeout>(nFontStrikeout); + + SvxWeightItem aWeightItem(eWeight,FontItemIds::CFID_WEIGHT); + SvxPostureItem aPostureItem(eItalic,FontItemIds::CFID_POSTURE); + + SvxCrossedOutItem aCrossedOutItem(eStrikeout,FontItemIds::CFID_STRIKEOUT); + SvxWordLineModeItem aWordLineModeItem(bWordLineMode, FontItemIds::CFID_WORDLINEMODE); + + SvxUnderlineItem aUnderlineItem(eUnderline,FontItemIds::CFID_UNDERLINE); + aUnderlineItem.SetColor(Color(ColorTransparency, nTextLineColor)); + + SvxColorItem aSvxColorItem(Color(ColorTransparency, nColor32),FontItemIds::CFID_CHARCOLOR); + SvxLanguageItem aLanguageItem(Application::GetSettings().GetUILanguageTag().getLanguageType(), FontItemIds::CFID_LANGUAGE); + + // the 2 CJK props + SvxCharReliefItem aFontReliefItem(static_cast<FontRelief>(nFontRelief), FontItemIds::CFID_RELIEF); + SvxEmphasisMarkItem aEmphasisMarkitem(static_cast<FontEmphasisMark>(nFontEmphasisMark), FontItemIds::CFID_EMPHASIS); + + _pSet->Put(aFontItem); + _pSet->Put(aSvxFontHeightItem); + _pSet->Put(aWeightItem); + _pSet->Put(aPostureItem); + _pSet->Put(aLanguageItem); + _pSet->Put(aUnderlineItem); + _pSet->Put(aCrossedOutItem); + _pSet->Put(aWordLineModeItem); + _pSet->Put(aSvxColorItem); + _pSet->Put(aFontReliefItem); + _pSet->Put(aEmphasisMarkitem); + + aPropExtractor.invalidateItem(PROPERTY_FONT_NAME, FontItemIds::CFID_FONT, *_pSet); + aPropExtractor.invalidateItem(PROPERTY_FONT_HEIGHT, FontItemIds::CFID_HEIGHT, *_pSet); + aPropExtractor.invalidateItem(PROPERTY_FONT_WEIGHT, FontItemIds::CFID_WEIGHT, *_pSet, css::awt::FontWeight::DONTKNOW == nFontWeight); + aPropExtractor.invalidateItem(PROPERTY_FONT_SLANT, FontItemIds::CFID_POSTURE, *_pSet, css::awt::FontSlant_DONTKNOW == nFontSlant); + aPropExtractor.invalidateItem(PROPERTY_FONT_UNDERLINE, FontItemIds::CFID_UNDERLINE, *_pSet, css::awt::FontUnderline::DONTKNOW == nFontLineStyle); + aPropExtractor.invalidateItem(PROPERTY_FONT_STRIKEOUT, FontItemIds::CFID_STRIKEOUT, *_pSet, css::awt::FontStrikeout::DONTKNOW == nFontStrikeout); + aPropExtractor.invalidateItem(PROPERTY_WORDLINEMODE, FontItemIds::CFID_WORDLINEMODE, *_pSet); + aPropExtractor.invalidateItem(PROPERTY_TEXTCOLOR, FontItemIds::CFID_CHARCOLOR, *_pSet); + aPropExtractor.invalidateItem(PROPERTY_FONT_RELIEF, FontItemIds::CFID_RELIEF, *_pSet); + aPropExtractor.invalidateItem(PROPERTY_FONT_EMPHASIS_MARK, FontItemIds::CFID_EMPHASIS, *_pSet); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "ControlCharacterDialog::translatePropertiesToItems"); + } + + _pSet->DisableItem(SID_ATTR_CHAR_CJK_FONT); + _pSet->DisableItem(SID_ATTR_CHAR_CJK_FONTHEIGHT); + _pSet->DisableItem(SID_ATTR_CHAR_CJK_LANGUAGE); + _pSet->DisableItem(SID_ATTR_CHAR_CJK_POSTURE); + _pSet->DisableItem(SID_ATTR_CHAR_CJK_WEIGHT); + + _pSet->DisableItem(SID_ATTR_CHAR_CASEMAP); + _pSet->DisableItem(SID_ATTR_CHAR_CONTOUR); + _pSet->DisableItem(SID_ATTR_CHAR_SHADOWED); + } + + namespace + { + void lcl_pushBackPropertyValue( std::vector< NamedValue >& _out_properties, const OUString& _name, const Any& _value ) + { + _out_properties.push_back( NamedValue( _name, _value ) ); + } + } + + void ControlCharacterDialog::translateItemsToProperties( const SfxItemSet& _rSet, std::vector< NamedValue >& _out_properties ) + { + _out_properties.clear(); + + try + { + + // font name + SfxItemState eState = _rSet.GetItemState(FontItemIds::CFID_FONT); + + if ( eState == SfxItemState::SET ) + { + const SvxFontItem& rFontItem = + static_cast<const SvxFontItem&>(_rSet.Get(FontItemIds::CFID_FONT)); + + lcl_pushBackPropertyValue( _out_properties, PROPERTY_FONT_NAME , Any(rFontItem.GetFamilyName())); + lcl_pushBackPropertyValue( _out_properties, PROPERTY_FONT_STYLENAME, Any(rFontItem.GetStyleName())); + lcl_pushBackPropertyValue( _out_properties, PROPERTY_FONT_FAMILY , Any(static_cast<sal_Int16>(rFontItem.GetFamily()))); + lcl_pushBackPropertyValue( _out_properties, PROPERTY_FONT_CHARSET , Any(static_cast<sal_Int16>(rFontItem.GetCharSet()))); + } + + + // font height + eState = _rSet.GetItemState(FontItemIds::CFID_HEIGHT); + + if ( eState == SfxItemState::SET ) + { + const SvxFontHeightItem& rSvxFontHeightItem = + static_cast<const SvxFontHeightItem&>(_rSet.Get(FontItemIds::CFID_HEIGHT)); + + float nHeight = static_cast<float>(o3tl::convert(rSvxFontHeightItem.GetHeight(), o3tl::Length::twip, o3tl::Length::pt)); + lcl_pushBackPropertyValue( _out_properties, PROPERTY_FONT_HEIGHT,Any(nHeight)); + + } + + + // font weight + eState = _rSet.GetItemState(FontItemIds::CFID_WEIGHT); + + if ( eState == SfxItemState::SET ) + { + const SvxWeightItem& rWeightItem = + static_cast<const SvxWeightItem&>(_rSet.Get(FontItemIds::CFID_WEIGHT)); + + float nWeight = vcl::unohelper::ConvertFontWeight(rWeightItem.GetWeight()); + lcl_pushBackPropertyValue( _out_properties, PROPERTY_FONT_WEIGHT,Any(nWeight)); + } + + + // font slant + eState = _rSet.GetItemState(FontItemIds::CFID_POSTURE); + + if ( eState == SfxItemState::SET ) + { + const SvxPostureItem& rPostureItem = + static_cast<const SvxPostureItem&>(_rSet.Get(FontItemIds::CFID_POSTURE)); + + css::awt::FontSlant eSlant = vcl::unohelper::ConvertFontSlant(rPostureItem.GetPosture()); + lcl_pushBackPropertyValue( _out_properties, PROPERTY_FONT_SLANT, Any(static_cast<sal_Int16>(eSlant))); + } + + + // font underline + eState = _rSet.GetItemState(FontItemIds::CFID_UNDERLINE); + + if ( eState == SfxItemState::SET ) + { + const SvxUnderlineItem& rUnderlineItem = + static_cast<const SvxUnderlineItem&>(_rSet.Get(FontItemIds::CFID_UNDERLINE)); + + sal_Int16 nUnderline = static_cast<sal_Int16>(rUnderlineItem.GetLineStyle()); + lcl_pushBackPropertyValue( _out_properties, PROPERTY_FONT_UNDERLINE,Any(nUnderline)); + + // the text line color is transported in this item, too + Color nColor = rUnderlineItem.GetColor(); + + Any aUnoColor; + if (COL_AUTO != nColor) + aUnoColor <<= nColor; + + lcl_pushBackPropertyValue( _out_properties, PROPERTY_TEXTLINECOLOR, aUnoColor ); + } + + + // font strikeout + eState = _rSet.GetItemState(FontItemIds::CFID_STRIKEOUT); + + if ( eState == SfxItemState::SET ) + { + const SvxCrossedOutItem& rCrossedOutItem = + static_cast<const SvxCrossedOutItem&>(_rSet.Get(FontItemIds::CFID_STRIKEOUT)); + + sal_Int16 nStrikeout = static_cast<sal_Int16>(rCrossedOutItem.GetStrikeout()); + lcl_pushBackPropertyValue( _out_properties, PROPERTY_FONT_STRIKEOUT,Any(nStrikeout)); + } + + + // font wordline mode + eState = _rSet.GetItemState(FontItemIds::CFID_WORDLINEMODE); + + if ( eState == SfxItemState::SET ) + { + const SvxWordLineModeItem& rWordLineModeItem = + _rSet.Get(FontItemIds::CFID_WORDLINEMODE); + + lcl_pushBackPropertyValue( _out_properties, PROPERTY_WORDLINEMODE, css::uno::Any(rWordLineModeItem.GetValue())); + } + + + // text color + eState = _rSet.GetItemState(FontItemIds::CFID_CHARCOLOR); + + if ( eState == SfxItemState::SET ) + { + const SvxColorItem& rColorItem = + static_cast<const SvxColorItem&>(_rSet.Get(FontItemIds::CFID_CHARCOLOR)); + + Color nColor = rColorItem.GetValue(); + + Any aUnoColor; + if (COL_AUTO != nColor) + aUnoColor <<= nColor; + + lcl_pushBackPropertyValue( _out_properties, PROPERTY_TEXTCOLOR, aUnoColor ); + } + + + // font relief + eState = _rSet.GetItemState(FontItemIds::CFID_RELIEF); + + if ( eState == SfxItemState::SET ) + { + const SvxCharReliefItem& rReliefItem = + static_cast<const SvxCharReliefItem&>(_rSet.Get(FontItemIds::CFID_RELIEF)); + + lcl_pushBackPropertyValue( _out_properties, PROPERTY_FONT_RELIEF, Any(static_cast<sal_Int16>(rReliefItem.GetValue())) ); + } + + + // font emphasis mark + eState = _rSet.GetItemState(FontItemIds::CFID_EMPHASIS); + + if ( eState == SfxItemState::SET ) + { + const SvxEmphasisMarkItem& rEmphMarkItem = _rSet.Get(FontItemIds::CFID_EMPHASIS); + + lcl_pushBackPropertyValue( _out_properties, PROPERTY_FONT_EMPHASIS_MARK, Any(static_cast<sal_Int16>(rEmphMarkItem.GetEmphasisMark())) ); + } + } + catch (const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + void ControlCharacterDialog::translateItemsToProperties( const SfxItemSet& _rSet, const Reference< XPropertySet >& _rxModel) + { + OSL_ENSURE( _rxModel.is(), "ControlCharacterDialog::translateItemsToProperties: invalid arguments!" ); + if ( !_rxModel.is()) + return; + + std::vector< NamedValue > aPropertyValues; + translateItemsToProperties( _rSet, aPropertyValues ); + try + { + for ( const NamedValue& rNV : aPropertyValues ) + _rxModel->setPropertyValue( rNV.Name, rNV.Value ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + void ControlCharacterDialog::createItemSet(std::unique_ptr<SfxItemSet>& _rpSet, rtl::Reference<SfxItemPool>& _rpPool, std::vector<SfxPoolItem*>*& _rpDefaults) + { + // just to be sure... + _rpSet = nullptr; + _rpPool = nullptr; + _rpDefaults = nullptr; + + // create and initialize the defaults + _rpDefaults = new std::vector<SfxPoolItem*>(FontItemIds::CFID_LAST_ITEM_ID - FontItemIds::CFID_FIRST_ITEM_ID + 1); + + vcl::Font aDefaultVCLFont = Application::GetDefaultDevice()->GetSettings().GetStyleSettings().GetAppFont(); + + SfxPoolItem** pCounter = _rpDefaults->data(); // want to modify this without affecting the out param _rppDefaults + *pCounter++ = new SvxFontItem(aDefaultVCLFont.GetFamilyType(), aDefaultVCLFont.GetFamilyName(), aDefaultVCLFont.GetStyleName(), aDefaultVCLFont.GetPitch(), aDefaultVCLFont.GetCharSet(), FontItemIds::CFID_FONT); + *pCounter++ = new SvxFontHeightItem(aDefaultVCLFont.GetFontHeight(), 100, FontItemIds::CFID_HEIGHT); + *pCounter++ = new SvxWeightItem(aDefaultVCLFont.GetWeight(), FontItemIds::CFID_WEIGHT); + *pCounter++ = new SvxPostureItem(aDefaultVCLFont.GetItalic(), FontItemIds::CFID_POSTURE); + *pCounter++ = new SvxLanguageItem(Application::GetSettings().GetUILanguageTag().getLanguageType(), FontItemIds::CFID_LANGUAGE); + *pCounter++ = new SvxUnderlineItem(aDefaultVCLFont.GetUnderline(), FontItemIds::CFID_UNDERLINE); + *pCounter++ = new SvxCrossedOutItem(aDefaultVCLFont.GetStrikeout(), FontItemIds::CFID_STRIKEOUT); + *pCounter++ = new SvxWordLineModeItem(aDefaultVCLFont.IsWordLineMode(), FontItemIds::CFID_WORDLINEMODE); + *pCounter++ = new SvxColorItem(aDefaultVCLFont.GetColor(), FontItemIds::CFID_CHARCOLOR); + *pCounter++ = new SvxCharReliefItem(aDefaultVCLFont.GetRelief(), FontItemIds::CFID_RELIEF); + *pCounter++ = new SvxEmphasisMarkItem(aDefaultVCLFont.GetEmphasisMark(), FontItemIds::CFID_EMPHASIS); + + *pCounter++ = new SvxFontItem(aDefaultVCLFont.GetFamilyType(), aDefaultVCLFont.GetFamilyName(), aDefaultVCLFont.GetStyleName(), aDefaultVCLFont.GetPitch(), aDefaultVCLFont.GetCharSet(), FontItemIds::CFID_CJK_FONT); + *pCounter++ = new SvxFontHeightItem(aDefaultVCLFont.GetFontHeight(), 100, FontItemIds::CFID_CJK_HEIGHT); + *pCounter++ = new SvxWeightItem(aDefaultVCLFont.GetWeight(), FontItemIds::CFID_CJK_WEIGHT); + *pCounter++ = new SvxPostureItem(aDefaultVCLFont.GetItalic(), FontItemIds::CFID_CJK_POSTURE); + *pCounter++ = new SvxLanguageItem(Application::GetSettings().GetUILanguageTag().getLanguageType(), FontItemIds::CFID_CJK_LANGUAGE); + + *pCounter++ = new SvxCaseMapItem(SvxCaseMap::NotMapped, FontItemIds::CFID_CASEMAP); + *pCounter++ = new SvxContourItem(false, FontItemIds::CFID_CONTOUR); + *pCounter++ = new SvxShadowedItem(false, FontItemIds::CFID_SHADOWED); + + *pCounter++ = new SvxFontListItem (new FontList(Application::GetDefaultDevice()), FontItemIds::CFID_FONTLIST); + + // create the pool + static SfxItemInfo const aItemInfos[FontItemIds::CFID_LAST_ITEM_ID - FontItemIds::CFID_FIRST_ITEM_ID + 1] = + { + // _nSID, _bNeedsPoolRegistration, _bShareable + { SID_ATTR_CHAR_FONT, false, false }, + { SID_ATTR_CHAR_FONTHEIGHT, false, false }, + { SID_ATTR_CHAR_WEIGHT, false, false }, + { SID_ATTR_CHAR_POSTURE, false, false }, + { SID_ATTR_CHAR_LANGUAGE, false, false }, + { SID_ATTR_CHAR_UNDERLINE, false, false }, + { SID_ATTR_CHAR_STRIKEOUT, false, false }, + { SID_ATTR_CHAR_WORDLINEMODE, false, false }, + { SID_ATTR_CHAR_COLOR, false, false }, + { SID_ATTR_CHAR_RELIEF, false, false }, + { SID_ATTR_CHAR_EMPHASISMARK, false, false }, + { 0, false, false }, + { 0, false, false }, + { 0, false, false }, + { 0, false, false }, + { 0, false, false }, + { 0, false, false }, + { 0, false, false }, + { 0, false, false }, + { SID_ATTR_CHAR_FONTLIST, false, false } + }; + + _rpPool = new SfxItemPool("PCRControlFontItemPool", FontItemIds::CFID_FIRST_ITEM_ID, FontItemIds::CFID_LAST_ITEM_ID, + aItemInfos, _rpDefaults); + _rpPool->FreezeIdRanges(); + + // and, finally, the set + _rpSet.reset(new SfxItemSet(*_rpPool)); + } + + void ControlCharacterDialog::destroyItemSet(std::unique_ptr<SfxItemSet>& _rpSet, rtl::Reference<SfxItemPool>& _rpPool, std::vector<SfxPoolItem*>*& _rpDefaults) + { + // from the pool, get and remember the font list (needs to be deleted) + const SvxFontListItem& rFontListItem = static_cast<const SvxFontListItem&>(_rpPool->GetDefaultItem(FontItemIds::CFID_FONTLIST)); + const FontList* pFontList = rFontListItem.GetFontList(); + + // _first_ delete the set (referring the pool) + _rpSet.reset(); + + // delete the pool + _rpPool->ReleaseDefaults(true); + // the "true" means delete the items, too + _rpPool = nullptr; + + // reset the defaults ptr + _rpDefaults = nullptr; + // no need to explicitly delete the defaults, this has been done by the ReleaseDefaults + + delete pFontList; + } + + void ControlCharacterDialog::PageCreated(const OUString& rId, SfxTabPage& rPage) + { + SfxAllItemSet aSet(*(GetInputSetImpl()->GetPool())); + if (rId == "font") + { + aSet.Put (static_cast<const SvxFontListItem&>(GetInputSetImpl()->Get(FontItemIds::CFID_FONTLIST))); + aSet.Put (SfxUInt16Item(SID_DISABLE_CTL,DISABLE_HIDE_LANGUAGE)); + rPage.PageCreated(aSet); + } + } +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/fontdialog.hxx b/extensions/source/propctrlr/fontdialog.hxx new file mode 100644 index 0000000000..a594671967 --- /dev/null +++ b/extensions/source/propctrlr/fontdialog.hxx @@ -0,0 +1,68 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sfx2/tabdlg.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/NamedValue.hpp> + + +namespace pcr +{ + + + //= ControlCharacterDialog + class ControlCharacterDialog : public SfxTabDialogController + { + public: + ControlCharacterDialog(weld::Window* pParent, const SfxItemSet& rCoreSet); + virtual ~ControlCharacterDialog() override; + + /// creates an item set to be used with this dialog + static void createItemSet(std::unique_ptr<SfxItemSet>& _rpSet, rtl::Reference<SfxItemPool>& _rpPool, std::vector<SfxPoolItem*>*& _rpDefaults); + + /// destroys an item previously created with <method>createItemSet</method> + static void destroyItemSet(std::unique_ptr<SfxItemSet>& _rpSet, rtl::Reference<SfxItemPool>& _rpPool, std::vector<SfxPoolItem*>*& _rpDefaults); + + /// fills the given item set with values obtained from the given property set + static void translatePropertiesToItems( + const css::uno::Reference< css::beans::XPropertySet >& _rxModel, + SfxItemSet* _pSet); + + /** fills the given property set with values obtained from the given item set + */ + static void translateItemsToProperties( + const SfxItemSet& _rSet, + const css::uno::Reference< css::beans::XPropertySet >& _rxModel); + + /** fills the given property set with values obtained from the given item set + */ + static void translateItemsToProperties( + const SfxItemSet& _rSet, + std::vector< css::beans::NamedValue >& _out_properties ); + + protected: + virtual void PageCreated(const OUString& rId, SfxTabPage& rPage) override; + }; + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/formbrowsertools.cxx b/extensions/source/propctrlr/formbrowsertools.cxx new file mode 100644 index 0000000000..b31aa0ce40 --- /dev/null +++ b/extensions/source/propctrlr/formbrowsertools.cxx @@ -0,0 +1,132 @@ +/* -*- 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 "formbrowsertools.hxx" +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <osl/diagnose.h> +#include <strings.hrc> +#include "modulepcr.hxx" +#include "formstrings.hxx" + + +namespace pcr +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::form; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + + + OUString GetUIHeadlineName(sal_Int16 nClassId, const Any& aUnoObj) + { + OUString sClassName; + switch (nClassId) + { + case FormComponentType::TEXTFIELD: + { + Reference< XInterface > xIFace; + aUnoObj >>= xIFace; + sClassName = PcrRes(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(SERVICE_COMPONENT_FORMATTEDFIELD))) + sClassName = PcrRes(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(PROPERTY_FORMATSSUPPLIER)) + sClassName = PcrRes(RID_STR_PROPTITLE_FORMATTED); + } + } + } + } + break; + + case FormComponentType::COMMANDBUTTON: + sClassName = PcrRes(RID_STR_PROPTITLE_PUSHBUTTON); break; + case FormComponentType::RADIOBUTTON: + sClassName = PcrRes(RID_STR_PROPTITLE_RADIOBUTTON); break; + case FormComponentType::CHECKBOX: + sClassName = PcrRes(RID_STR_PROPTITLE_CHECKBOX); break; + case FormComponentType::LISTBOX: + sClassName = PcrRes(RID_STR_PROPTITLE_LISTBOX); break; + case FormComponentType::COMBOBOX: + sClassName = PcrRes(RID_STR_PROPTITLE_COMBOBOX); break; + case FormComponentType::GROUPBOX: + sClassName = PcrRes(RID_STR_PROPTITLE_GROUPBOX); break; + case FormComponentType::IMAGEBUTTON: + sClassName = PcrRes(RID_STR_PROPTITLE_IMAGEBUTTON); break; + case FormComponentType::FIXEDTEXT: + sClassName = PcrRes(RID_STR_PROPTITLE_FIXEDTEXT); break; + case FormComponentType::GRIDCONTROL: + sClassName = PcrRes(RID_STR_PROPTITLE_DBGRID); break; + case FormComponentType::FILECONTROL: + sClassName = PcrRes(RID_STR_PROPTITLE_FILECONTROL); break; + + case FormComponentType::DATEFIELD: + sClassName = PcrRes(RID_STR_PROPTITLE_DATEFIELD); break; + case FormComponentType::TIMEFIELD: + sClassName = PcrRes(RID_STR_PROPTITLE_TIMEFIELD); break; + case FormComponentType::NUMERICFIELD: + sClassName = PcrRes(RID_STR_PROPTITLE_NUMERICFIELD); break; + case FormComponentType::CURRENCYFIELD: + sClassName = PcrRes(RID_STR_PROPTITLE_CURRENCYFIELD); break; + case FormComponentType::PATTERNFIELD: + sClassName = PcrRes(RID_STR_PROPTITLE_PATTERNFIELD); break; + case FormComponentType::IMAGECONTROL: + sClassName = PcrRes(RID_STR_PROPTITLE_IMAGECONTROL); break; + case FormComponentType::HIDDENCONTROL: + sClassName = PcrRes(RID_STR_PROPTITLE_HIDDENCONTROL); break; + + case FormComponentType::CONTROL: + default: + sClassName = PcrRes(RID_STR_PROPTITLE_UNKNOWNCONTROL); break; + } + + return sClassName; + } + + + sal_Int16 classifyComponent( const Reference< XInterface >& _rxComponent ) + { + Reference< XPropertySet > xComponentProps( _rxComponent, UNO_QUERY_THROW ); + Reference< XPropertySetInfo > xPSI( xComponentProps->getPropertySetInfo(), UNO_SET_THROW ); + + sal_Int16 nControlType( FormComponentType::CONTROL ); + if ( xPSI->hasPropertyByName( PROPERTY_CLASSID ) ) + { + OSL_VERIFY( xComponentProps->getPropertyValue( PROPERTY_CLASSID ) >>= nControlType ); + } + return nControlType; + } + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/formbrowsertools.hxx b/extensions/source/propctrlr/formbrowsertools.hxx new file mode 100644 index 0000000000..707ae2bc6b --- /dev/null +++ b/extensions/source/propctrlr/formbrowsertools.hxx @@ -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 . + */ + +#pragma once + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/beans/Property.hpp> +#include <rtl/ustring.hxx> + +#include <set> +#include <utility> + + +namespace pcr +{ + + + OUString GetUIHeadlineName(sal_Int16 _nClassId, const css::uno::Any& _rUnoObject); + sal_Int16 classifyComponent( const css::uno::Reference< css::uno::XInterface >& _rxComponent ); + + + struct FindPropertyByHandle + { + private: + sal_Int32 m_nId; + + public: + explicit FindPropertyByHandle( sal_Int32 _nId ) : m_nId ( _nId ) { } + bool operator()( const css::beans::Property& _rProp ) const + { + return m_nId == _rProp.Handle; + } + }; + + + struct FindPropertyByName + { + private: + OUString m_sName; + + public: + explicit FindPropertyByName( OUString _aName ) : m_sName(std::move( _aName )) { } + bool operator()( const css::beans::Property& _rProp ) const + { + return m_sName == _rProp.Name; + } + }; + + + struct PropertyLessByName + { + bool operator() (const css::beans::Property& _rLhs, const css::beans::Property& _rRhs) const + { + return _rLhs.Name < _rRhs.Name; + } + }; + + + struct TypeLessByName + { + bool operator() (const css::uno::Type& _rLhs, const css::uno::Type& _rRhs) const + { + return _rLhs.getTypeName() < _rRhs.getTypeName(); + } + }; + + + typedef std::set< css::beans::Property, PropertyLessByName > PropertyBag; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/formcomponenthandler.cxx b/extensions/source/propctrlr/formcomponenthandler.cxx new file mode 100644 index 0000000000..ef79ad9230 --- /dev/null +++ b/extensions/source/propctrlr/formcomponenthandler.cxx @@ -0,0 +1,3299 @@ +/* -*- 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 "controltype.hxx" +#include "modulepcr.hxx" +#include <propctrlr.h> +#include <helpids.h> +#include "fontdialog.hxx" +#include "formcomponenthandler.hxx" +#include "formlinkdialog.hxx" +#include "formmetadata.hxx" +#include <strings.hrc> +#include <showhide.hrc> +#include <yesno.hrc> +#include "formstrings.hxx" +#include "handlerhelper.hxx" +#include "listselectiondlg.hxx" +#include "pcrcommon.hxx" +#include "selectlabeldialog.hxx" +#include "standardcontrol.hxx" +#include "taborder.hxx" +#include "usercontrol.hxx" + +#include <com/sun/star/lang/NullPointerException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/util/XNumberFormatsSupplier.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/form/XForm.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/sdb/OrderDialog.hpp> +#include <com/sun/star/sdb/FilterDialog.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/sdb/DatabaseContext.hpp> +#include <com/sun/star/form/XGridColumnFactory.hpp> +#include <com/sun/star/sdb/SQLContext.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <com/sun/star/sdb/XQueriesSupplier.hpp> +#include <com/sun/star/form/ListSourceType.hpp> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/ui/dialogs/XFilePicker3.hpp> +#include <com/sun/star/sdb/XSingleSelectQueryComposer.hpp> +#include <com/sun/star/ui/dialogs/XFilePickerControlAccess.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp> +#include <com/sun/star/awt/XTabControllerModel.hpp> +#include <com/sun/star/form/FormSubmitEncoding.hpp> +#include <com/sun/star/awt/VisualEffect.hpp> +#include <com/sun/star/form/FormButtonType.hpp> +#include <com/sun/star/inspection/PropertyControlType.hpp> +#include <com/sun/star/util/MeasureUnit.hpp> +#include <com/sun/star/inspection/XObjectInspectorUI.hpp> +#include <com/sun/star/inspection/PropertyLineElement.hpp> +#include <com/sun/star/resource/XStringResourceManager.hpp> +#include <com/sun/star/resource/MissingResourceException.hpp> +#include <com/sun/star/report/XReportDefinition.hpp> +#include <com/sun/star/graphic/GraphicObject.hpp> +#include <com/sun/star/text/WritingMode2.hpp> + +#include <comphelper/extract.hxx> +#include <comphelper/types.hxx> +#include <connectivity/dbconversion.hxx> +#include <connectivity/dbexception.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <sfx2/app.hxx> +#include <sfx2/basedlgs.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/filedlghelper.hxx> +#include <svl/ctloptions.hxx> +#include <svtools/colrdlg.hxx> +#include <svl/filenotation.hxx> +#include <svl/intitem.hxx> +#include <svl/itemset.hxx> +#include <svl/numformat.hxx> +#include <unotools/moduleoptions.hxx> +#include <svl/numuno.hxx> +#include <svl/urihelper.hxx> +#include <svx/dialogs.hrc> +#include <svx/numinf.hxx> +#include <svx/svxdlg.hxx> +#include <svx/svxids.hrc> +#include <vcl/graph.hxx> +#include <vcl/unohelp.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <sal/macros.h> +#include <sal/log.hxx> + +#include <limits> +#include <memory> +#include <string_view> + +namespace pcr +{ + + + using namespace ::com::sun::star; + using namespace uno; + using namespace lang; + using namespace beans; + using namespace frame; + using namespace script; + using namespace form; + using namespace util; + using namespace awt; + using namespace sdb; + using namespace sdbc; + using namespace sdbcx; + using namespace report; + using namespace container; + using namespace ui::dialogs; + using namespace inspection; + using namespace ::dbtools; + + namespace WritingMode2 = ::com::sun::star::text::WritingMode2; + + + //= FormComponentPropertyHandler + +#define PROPERTY_ID_ROWSET 1 + + FormComponentPropertyHandler::FormComponentPropertyHandler( const Reference< XComponentContext >& _rxContext ) + :PropertyHandlerComponent( _rxContext ) + ,::comphelper::OPropertyContainer(PropertyHandlerComponent::rBHelper) + ,m_sDefaultValueString( PcrRes(RID_STR_STANDARD) ) + ,m_eComponentClass( eUnknown ) + ,m_bComponentIsSubForm( false ) + ,m_bHaveListSource( false ) + ,m_bHaveCommand( false ) + ,m_nClassId( 0 ) + { + registerProperty(PROPERTY_ROWSET,PROPERTY_ID_ROWSET,0,&m_xRowSet,cppu::UnoType<decltype(m_xRowSet)>::get()); + } + + + FormComponentPropertyHandler::~FormComponentPropertyHandler() + { + } + + IMPLEMENT_FORWARD_XINTERFACE2(FormComponentPropertyHandler,PropertyHandlerComponent,::comphelper::OPropertyContainer) + + OUString FormComponentPropertyHandler::getImplementationName( ) + { + return "com.sun.star.comp.extensions.FormComponentPropertyHandler"; + } + + + Sequence< OUString > FormComponentPropertyHandler::getSupportedServiceNames( ) + { + return { "com.sun.star.form.inspection.FormComponentPropertyHandler" }; + } + + namespace { + + // TODO: -> export from toolkit + struct LanguageDependentProp + { + const char* pPropName; + sal_Int32 nPropNameLength; + }; + + } + + const LanguageDependentProp aLanguageDependentProp[] = + { + { "Text", 4 }, + { "Label", 5 }, + { "Title", 5 }, + { "HelpText", 8 }, + { "CurrencySymbol", 14 }, + { "StringItemList", 14 }, + { nullptr, 0 } + }; + + namespace + { + bool lcl_isLanguageDependentProperty( std::u16string_view aName ) + { + bool bRet = false; + + const LanguageDependentProp* pLangDepProp = aLanguageDependentProp; + while( pLangDepProp->pPropName != nullptr ) + { + if( o3tl::equalsAscii( aName, std::string_view(pLangDepProp->pPropName, pLangDepProp->nPropNameLength) )) + { + bRet = true; + break; + } + pLangDepProp++; + } + return bRet; + } + + Reference< resource::XStringResourceResolver > lcl_getStringResourceResolverForProperty + ( const Reference< XPropertySet >& _xComponent, std::u16string_view _rPropertyName, + const Any& _rPropertyValue ) + { + Reference< resource::XStringResourceResolver > xRet; + const TypeClass eType = _rPropertyValue.getValueType().getTypeClass(); + if ( (eType == TypeClass_STRING || eType == TypeClass_SEQUENCE) && + lcl_isLanguageDependentProperty( _rPropertyName ) ) + { + Reference< resource::XStringResourceResolver > xStringResourceResolver; + try + { + xStringResourceResolver.set( _xComponent->getPropertyValue( "ResourceResolver" ),UNO_QUERY); + if( xStringResourceResolver.is() && + xStringResourceResolver->getLocales().hasElements() ) + { + xRet = xStringResourceResolver; + } + } + catch(const UnknownPropertyException&) + { + // nii + } + } + + return xRet; + } + } + + + Any FormComponentPropertyHandler::impl_getPropertyValue_throw( const OUString& _rPropertyName ) const + { + const PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + // tdf#117159 crash with chart in database report + if (!m_xComponent) + return Any(); + + Any aPropertyValue( m_xComponent->getPropertyValue( _rPropertyName ) ); + + Reference< resource::XStringResourceResolver > xStringResourceResolver + = lcl_getStringResourceResolverForProperty( m_xComponent, _rPropertyName, aPropertyValue ); + if( xStringResourceResolver.is() ) + { + TypeClass eType = aPropertyValue.getValueType().getTypeClass(); + if( eType == TypeClass_STRING ) + { + OUString aPropStr; + aPropertyValue >>= aPropStr; + if( aPropStr.getLength() > 1 ) + { + OUString aPureIdStr = aPropStr.copy( 1 ); + if( xStringResourceResolver->hasEntryForId( aPureIdStr ) ) + { + OUString aResourceStr = xStringResourceResolver->resolveString( aPureIdStr ); + aPropertyValue <<= aResourceStr; + } + } + } + // StringItemList? + else if( eType == TypeClass_SEQUENCE ) + { + Sequence< OUString > aStrings; + aPropertyValue >>= aStrings; + + std::vector< OUString > aResolvedStrings; + aResolvedStrings.reserve( aStrings.getLength() ); + try + { + for ( const OUString& rIdStr : std::as_const(aStrings) ) + { + OUString aPureIdStr = rIdStr.copy( 1 ); + if( xStringResourceResolver->hasEntryForId( aPureIdStr ) ) + aResolvedStrings.push_back(xStringResourceResolver->resolveString( aPureIdStr )); + else + aResolvedStrings.push_back(rIdStr); + } + } + catch( const resource::MissingResourceException & ) + {} + aPropertyValue <<= comphelper::containerToSequence(aResolvedStrings); + } + } + else + impl_normalizePropertyValue_nothrow( aPropertyValue, nPropId ); + + return aPropertyValue; + } + + Any SAL_CALL FormComponentPropertyHandler::getPropertyValue( const OUString& _rPropertyName ) + { + if( _rPropertyName == PROPERTY_ROWSET ) + return ::comphelper::OPropertyContainer::getPropertyValue( _rPropertyName ); + + ::osl::MutexGuard aGuard( m_aMutex ); + return impl_getPropertyValue_throw( _rPropertyName ); + } + + void SAL_CALL FormComponentPropertyHandler::setPropertyValue( const OUString& _rPropertyName, const Any& _rValue ) + { + if( _rPropertyName == PROPERTY_ROWSET ) + { + ::comphelper::OPropertyContainer::setPropertyValue( _rPropertyName, _rValue ); + return; + } + + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); // check if property is known by the handler + + Reference< graphic::XGraphicObject > xGrfObj; + if ( PROPERTY_ID_IMAGE_URL == nPropId && ( _rValue >>= xGrfObj ) ) + { + DBG_ASSERT( xGrfObj.is(), "FormComponentPropertyHandler::setPropertyValue() xGrfObj is invalid"); + m_xComponent->setPropertyValue(PROPERTY_GRAPHIC, uno::Any(xGrfObj->getGraphic())); + } + else if ( PROPERTY_ID_FONT == nPropId ) + { + // special handling, the value is a faked value we generated ourself in impl_executeFontDialog_nothrow + Sequence< NamedValue > aFontPropertyValues; + if( ! (_rValue >>= aFontPropertyValues) ) + SAL_WARN("extensions.propctrlr", "setPropertyValue: unable to get property " << PROPERTY_ID_FONT); + + for ( const NamedValue& fontPropertyValue : std::as_const(aFontPropertyValues) ) + m_xComponent->setPropertyValue( fontPropertyValue.Name, fontPropertyValue.Value ); + } + else + { + Any aValue = _rValue; + + Reference< resource::XStringResourceResolver > xStringResourceResolver + = lcl_getStringResourceResolverForProperty( m_xComponent, _rPropertyName, _rValue ); + if( xStringResourceResolver.is() ) + { + Reference< resource::XStringResourceManager > + xStringResourceManager( xStringResourceResolver, UNO_QUERY ); + if( xStringResourceManager.is() ) + { + Any aPropertyValue( m_xComponent->getPropertyValue( _rPropertyName ) ); + TypeClass eType = aPropertyValue.getValueType().getTypeClass(); + if( eType == TypeClass_STRING ) + { + OUString aPropStr; + aPropertyValue >>= aPropStr; + if( aPropStr.getLength() > 1 ) + { + OUString aPureIdStr = aPropStr.copy( 1 ); + OUString aValueStr; + _rValue >>= aValueStr; + xStringResourceManager->setString( aPureIdStr, aValueStr ); + aValue = aPropertyValue; // set value to force modified + } + } + // StringItemList? + else if( eType == TypeClass_SEQUENCE ) + { + static const char aDot[] = "."; + + // Put strings into resource using new ids + Sequence< OUString > aNewStrings; + _rValue >>= aNewStrings; + + const sal_Int32 nNewCount = aNewStrings.getLength(); + + // Create new Ids + std::unique_ptr<OUString[]> pNewPureIds(new OUString[nNewCount]); + Any aNameAny = m_xComponent->getPropertyValue(PROPERTY_NAME); + OUString sControlName; + aNameAny >>= sControlName; + OUString aIdStrBase = aDot + + sControlName + + aDot + + _rPropertyName; + sal_Int32 i; + for ( i = 0; i < nNewCount; ++i ) + { + sal_Int32 nUniqueId = xStringResourceManager->getUniqueNumericId(); + OUString aPureIdStr = OUString::number( nUniqueId ) + aIdStrBase; + pNewPureIds[i] = aPureIdStr; + // Force usage of next Unique Id + xStringResourceManager->setString( aPureIdStr, OUString() ); + } + + // Move strings to new Ids for all locales + const Sequence< Locale > aLocaleSeq = xStringResourceManager->getLocales(); + Sequence< OUString > aOldIdStrings; + aPropertyValue >>= aOldIdStrings; + try + { + const OUString* pOldIdStrings = aOldIdStrings.getConstArray(); + sal_Int32 nOldIdCount = aOldIdStrings.getLength(); + for ( i = 0; i < nNewCount; ++i ) + { + OUString aOldPureIdStr; + if( i < nOldIdCount ) + { + OUString aOldIdStr = pOldIdStrings[i]; + aOldPureIdStr = aOldIdStr.copy( 1 ); + } + OUString aNewPureIdStr = pNewPureIds[i]; + + for ( const Locale& rLocale : aLocaleSeq ) + { + OUString aResourceStr; + if( !aOldPureIdStr.isEmpty() ) + { + if( xStringResourceManager->hasEntryForIdAndLocale( aOldPureIdStr, rLocale ) ) + { + aResourceStr = xStringResourceManager-> + resolveStringForLocale( aOldPureIdStr, rLocale ); + } + } + xStringResourceManager->setStringForLocale( aNewPureIdStr, aResourceStr, rLocale ); + } + } + } + catch( const resource::MissingResourceException & ) + {} + + + // Set new strings for current locale and create + // new Id sequence as new property value + Sequence< OUString > aNewIdStrings; + aNewIdStrings.realloc( nNewCount ); + OUString* pNewIdStrings = aNewIdStrings.getArray(); + for ( i = 0; i < nNewCount; ++i ) + { + const OUString& aPureIdStr = pNewPureIds[i]; + const OUString& aStr = aNewStrings[i]; + xStringResourceManager->setString( aPureIdStr, aStr ); + + pNewIdStrings[i] = "&" + aPureIdStr; + } + aValue <<= aNewIdStrings; + + // Remove old ids from resource for all locales + for( const OUString& rIdStr : std::as_const(aOldIdStrings) ) + { + OUString aPureIdStr = rIdStr.copy( 1 ); + for ( const Locale& rLocale : aLocaleSeq ) + { + try + { + xStringResourceManager->removeIdForLocale( aPureIdStr, rLocale ); + } + catch( const resource::MissingResourceException & ) + {} + } + } + } + } + } + + m_xComponent->setPropertyValue( _rPropertyName, aValue ); + } + } + + Any SAL_CALL FormComponentPropertyHandler::convertToPropertyValue( const OUString& _rPropertyName, const Any& _rControlValue ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + Property aProperty( impl_getPropertyFromId_throw( nPropId ) ); + + Any aPropertyValue( _rControlValue ); + if ( !aPropertyValue.hasValue() ) + { + if ( ( aProperty.Attributes & PropertyAttribute::MAYBEVOID ) == 0 ) + // default construct an instance of the proper type + aPropertyValue = Any( nullptr, aProperty.Type ); + // nothing to do + return aPropertyValue; + } + + /// care for the special "default" string, translate it to VOID + if ( m_aPropertiesWithDefListEntry.find( _rPropertyName ) != m_aPropertiesWithDefListEntry.end() ) + { + // it's a control with a string list + OUString sStringValue; + if ( _rControlValue >>= sStringValue ) + { // note that ColorListBoxes might transfer values either as string or as css.util.Color, + // so this check here is important + if ( sStringValue == m_sDefaultValueString ) + return Any(); + } + } + + switch ( nPropId ) + { + case PROPERTY_ID_DATASOURCE: + { + OUString sControlValue; + if( ! (_rControlValue >>= sControlValue) ) + SAL_WARN("extensions.propctrlr", "convertToPropertyValue: unable to get property " << PROPERTY_ID_DATASOURCE); + + if ( !sControlValue.isEmpty() ) + { + Reference< XDatabaseContext > xDatabaseContext = sdb::DatabaseContext::create( m_xContext ); + if ( !xDatabaseContext->hasByName( sControlValue ) ) + { + ::svt::OFileNotation aTransformer(sControlValue); + aPropertyValue <<= aTransformer.get( ::svt::OFileNotation::N_URL ); + } + } + } + break; // case PROPERTY_ID_DATASOURCE + + case PROPERTY_ID_SHOW_POSITION: + case PROPERTY_ID_SHOW_NAVIGATION: + case PROPERTY_ID_SHOW_RECORDACTIONS: + case PROPERTY_ID_SHOW_FILTERSORT: + { + OUString sControlValue; + if( ! (_rControlValue >>= sControlValue) ) + SAL_WARN("extensions.propctrlr", "convertToControlValue: unable to get property for Show/Hide"); + + static_assert(SAL_N_ELEMENTS(RID_RSC_ENUM_SHOWHIDE) == 2, "FormComponentPropertyHandler::convertToPropertyValue: broken resource for Show/Hide!"); + bool bShow = sControlValue == PcrRes(RID_RSC_ENUM_SHOWHIDE[1]); + + aPropertyValue <<= bShow; + } + break; + + case PROPERTY_ID_TARGET_URL: + case PROPERTY_ID_IMAGE_URL: + { + OUString sControlValue; + if( ! (_rControlValue >>= sControlValue) ) + SAL_WARN("extensions.propctrlr", "convertToPropertyValue: unable to get property for URLs"); + // Don't convert a placeholder + if ( nPropId == PROPERTY_ID_IMAGE_URL && sControlValue == PcrRes(RID_EMBED_IMAGE_PLACEHOLDER) ) + aPropertyValue <<= sControlValue; + else + { + INetURLObject aDocURL( impl_getDocumentURL_nothrow() ); + aPropertyValue <<= URIHelper::SmartRel2Abs( aDocURL, sControlValue, Link<OUString *, bool>(), false, true ); + } + } + break; + + case PROPERTY_ID_DATEMIN: + case PROPERTY_ID_DATEMAX: + case PROPERTY_ID_DEFAULT_DATE: + case PROPERTY_ID_DATE: + { + util::Date aDate; + if( ! (_rControlValue >>= aDate) ) + SAL_WARN("extensions.propctrlr", "convertToControlValue: unable to get property for date"); + aPropertyValue <<= aDate; + } + break; + + case PROPERTY_ID_TIMEMIN: + case PROPERTY_ID_TIMEMAX: + case PROPERTY_ID_DEFAULT_TIME: + case PROPERTY_ID_TIME: + { + util::Time aTime; + if( ! (_rControlValue >>= aTime) ) + SAL_WARN("extensions.propctrlr", "convertToControlValue: unable to get property for time"); + aPropertyValue <<= aTime; + } + break; + + case PROPERTY_ID_WRITING_MODE: + { + aPropertyValue = PropertyHandlerComponent::convertToPropertyValue( _rPropertyName, _rControlValue ); + + sal_Int16 nNormalizedValue( 2 ); + if( ! (aPropertyValue >>= nNormalizedValue) ) + SAL_WARN("extensions.propctrlr", "convertToControlValue: unable to get property for " << PROPERTY_ID_WRITING_MODE); + + sal_Int16 nWritingMode = WritingMode2::CONTEXT; + switch ( nNormalizedValue ) + { + case 0: nWritingMode = WritingMode2::LR_TB; break; + case 1: nWritingMode = WritingMode2::RL_TB; break; + case 2: nWritingMode = WritingMode2::CONTEXT; break; + default: + OSL_FAIL( "FormComponentPropertyHandler::convertToPropertyValue: unexpected 'normalized value' for WritingMode!" ); + nWritingMode = WritingMode2::CONTEXT; + break; + } + + aPropertyValue <<= nWritingMode; + } + break; + + default: + aPropertyValue = PropertyHandlerComponent::convertToPropertyValue( _rPropertyName, _rControlValue ); + break; // default + + } // switch ( nPropId ) + + return aPropertyValue; + } + + Any SAL_CALL FormComponentPropertyHandler::convertToControlValue( const OUString& _rPropertyName, const Any& _rPropertyValue, const Type& _rControlValueType ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + sal_Int32 nPropId = m_pInfoService->getPropertyId( _rPropertyName ); + DBG_ASSERT( nPropId != -1, "FormComponentPropertyHandler::convertToPropertyValue: not one of my properties!!" ); + + impl_getPropertyFromId_throw( nPropId ); + + Any aControlValue( _rPropertyValue ); + if ( !aControlValue.hasValue() ) + { + // if the property is represented with a list box or color list box, we need to + // translate this into the string "Default" + if ( m_aPropertiesWithDefListEntry.find( _rPropertyName ) != m_aPropertiesWithDefListEntry.end() ) + aControlValue <<= m_sDefaultValueString; + + return aControlValue; + } + + switch ( nPropId ) + { + + case PROPERTY_ID_SHOW_POSITION: + case PROPERTY_ID_SHOW_NAVIGATION: + case PROPERTY_ID_SHOW_RECORDACTIONS: + case PROPERTY_ID_SHOW_FILTERSORT: + { + static_assert(SAL_N_ELEMENTS(RID_RSC_ENUM_SHOWHIDE) == 2, "FormComponentPropertyHandler::convertToPropertyValue: broken resource for Show/Hide!"); + OUString sControlValue = ::comphelper::getBOOL(_rPropertyValue) + ? PcrRes(RID_RSC_ENUM_SHOWHIDE[1]) + : PcrRes(RID_RSC_ENUM_SHOWHIDE[0]); + aControlValue <<= sControlValue; + } + break; + + + case PROPERTY_ID_DATASOURCE: + { + OSL_ENSURE( _rControlValueType.getTypeClass() == TypeClass_STRING, + "FormComponentPropertyHandler::convertToControlValue: wrong ControlValueType!" ); + + OUString sDataSource; + _rPropertyValue >>= sDataSource; + if ( !sDataSource.isEmpty() ) + { + ::svt::OFileNotation aTransformer( sDataSource ); + sDataSource = aTransformer.get( ::svt::OFileNotation::N_SYSTEM ); + } + aControlValue <<= sDataSource; + } + break; + + + case PROPERTY_ID_CONTROLLABEL: + { + OUString sControlValue; + + Reference< XPropertySet > xSet; + _rPropertyValue >>= xSet; + Reference< XPropertySetInfo > xPSI; + if ( xSet.is() ) + xPSI = xSet->getPropertySetInfo(); + if ( xPSI.is() && xPSI->hasPropertyByName( PROPERTY_LABEL ) ) + { + OUString sLabel; + if( ! (xSet->getPropertyValue( PROPERTY_LABEL) >>= sLabel) ) + SAL_WARN("extensions.propctrlr", "convertToPropertyValue: unable to get property " << PROPERTY_LABEL); + sControlValue = "<" + sLabel + ">"; + } + + aControlValue <<= sControlValue; + } + break; + + + case PROPERTY_ID_DATEMIN: + case PROPERTY_ID_DATEMAX: + case PROPERTY_ID_DEFAULT_DATE: + case PROPERTY_ID_DATE: + { + sal_Int32 nDate = 0; + if( ! (_rPropertyValue >>= nDate) ) + SAL_WARN("extensions.propctrlr", "convertToControlValue: unable to get property for dates"); + aControlValue <<= DBTypeConversion::toDate( nDate ); + } + break; + + case PROPERTY_ID_TIMEMIN: + case PROPERTY_ID_TIMEMAX: + case PROPERTY_ID_DEFAULT_TIME: + case PROPERTY_ID_TIME: + { + sal_Int64 nTime = 0; + if( ! (_rPropertyValue >>= nTime) ) + SAL_WARN("extensions.propctrlr", "convertToControlValue: unable to get property for times"); + aControlValue <<= DBTypeConversion::toTime( nTime ); + } + break; + + case PROPERTY_ID_WRITING_MODE: + { + sal_Int16 nWritingMode( WritingMode2::CONTEXT ); + if( ! (_rPropertyValue >>= nWritingMode) ) + SAL_WARN("extensions.propctrlr", "convertToControlValue: unable to get property " << PROPERTY_ID_WRITING_MODE); + + sal_Int16 nNormalized = 2; + switch ( nWritingMode ) + { + case WritingMode2::LR_TB: nNormalized = 0; break; + case WritingMode2::RL_TB: nNormalized = 1; break; + case WritingMode2::CONTEXT: nNormalized = 2; break; + default: + OSL_FAIL( "FormComponentPropertyHandler::convertToControlValue: unsupported API value for WritingMode!" ); + nNormalized = 2; + break; + } + + aControlValue = PropertyHandlerComponent::convertToControlValue( _rPropertyName, Any( nNormalized ), _rControlValueType ); + } + break; + + case PROPERTY_ID_FONT: + { + FontDescriptor aFont; + if( ! (_rPropertyValue >>= aFont) ) + SAL_WARN("extensions.propctrlr", "convertToControlValue: unable to get property " << PROPERTY_ID_FONT); + + OUStringBuffer displayName; + if ( aFont.Name.isEmpty() ) + { + displayName.append( PcrRes(RID_STR_FONT_DEFAULT) ); + } + else + { + // font name + displayName.append( aFont.Name + ", " ); + + // font style + ::FontWeight eWeight = vcl::unohelper::ConvertFontWeight( aFont.Weight ); + TranslateId pStyleResID = RID_STR_FONTSTYLE_REGULAR; + if ( aFont.Slant == FontSlant_ITALIC ) + { + if ( eWeight > WEIGHT_NORMAL ) + pStyleResID = RID_STR_FONTSTYLE_BOLD_ITALIC; + else + pStyleResID = RID_STR_FONTSTYLE_ITALIC; + } + else + { + if ( eWeight > WEIGHT_NORMAL ) + pStyleResID = RID_STR_FONTSTYLE_BOLD; + } + displayName.append(PcrRes(pStyleResID)); + + // font size + if ( aFont.Height ) + { + displayName.append( ", " + OUString::number( sal_Int32( aFont.Height ) ) ); + } + } + + aControlValue <<= displayName.makeStringAndClear(); + } + break; + + default: + aControlValue = PropertyHandlerComponent::convertToControlValue( _rPropertyName, _rPropertyValue, _rControlValueType ); + break; + + } // switch ( nPropId ) + + return aControlValue; + } + + PropertyState SAL_CALL FormComponentPropertyHandler::getPropertyState( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( m_xPropertyState.is() ) + return m_xPropertyState->getPropertyState( _rPropertyName ); + return PropertyState_DIRECT_VALUE; + } + + void SAL_CALL FormComponentPropertyHandler::addPropertyChangeListener( const Reference< XPropertyChangeListener >& _rxListener ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyHandlerComponent::addPropertyChangeListener( _rxListener ); + if ( m_xComponent.is() ) + m_xComponent->addPropertyChangeListener( OUString(), _rxListener ); + } + + void SAL_CALL FormComponentPropertyHandler::removePropertyChangeListener( const Reference< XPropertyChangeListener >& _rxListener ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( m_xComponent.is() ) + m_xComponent->removePropertyChangeListener( OUString(), _rxListener ); + PropertyHandlerComponent::removePropertyChangeListener( _rxListener ); + } + + Sequence< Property > FormComponentPropertyHandler::doDescribeSupportedProperties() const + { + if ( !m_xComponentPropertyInfo.is() ) + return Sequence< Property >(); + + std::vector< Property > aProperties; + + Sequence< Property > aAllProperties( m_xComponentPropertyInfo->getProperties() ); + aProperties.reserve( aAllProperties.getLength() ); + + // filter the properties + PropertyId nPropId( 0 ); + OUString sDisplayName; + + for ( Property & rProperty : asNonConstRange(aAllProperties) ) + { + nPropId = m_pInfoService->getPropertyId( rProperty.Name ); + if ( nPropId == -1 ) + continue; + rProperty.Handle = nPropId; + + sDisplayName = m_pInfoService->getPropertyTranslation( nPropId ); + if ( sDisplayName.isEmpty() ) + continue; + + sal_uInt32 nPropertyUIFlags = m_pInfoService->getPropertyUIFlags( nPropId ); + bool bIsVisibleForForms = ( nPropertyUIFlags & PROP_FLAG_FORM_VISIBLE ) != 0; + bool bIsVisibleForDialogs = ( nPropertyUIFlags & PROP_FLAG_DIALOG_VISIBLE ) != 0; + + // depending on whether we're working for a form or a UNO dialog, some + // properties are not displayed + if ( ( m_eComponentClass == eFormControl && !bIsVisibleForForms ) + || ( m_eComponentClass == eDialogControl && !bIsVisibleForDialogs ) + ) + continue; + + // some generic sanity checks + if ( impl_shouldExcludeProperty_nothrow( rProperty ) ) + continue; + + switch ( nPropId ) + { + case PROPERTY_ID_BORDER: + case PROPERTY_ID_TABSTOP: + // BORDER and TABSTOP are normalized (see impl_normalizePropertyValue_nothrow) + // to not allow VOID values + rProperty.Attributes &= ~PropertyAttribute::MAYBEVOID; + break; + + case PROPERTY_ID_LISTSOURCE: + // no cursor source if no Base is installed. + if ( SvtModuleOptions().IsModuleInstalled( SvtModuleOptions::EModule::DATABASE ) ) + const_cast< FormComponentPropertyHandler* >( this )->m_bHaveListSource = true; + break; + + case PROPERTY_ID_COMMAND: + // no cursor source if no Base is installed. + if ( SvtModuleOptions().IsModuleInstalled( SvtModuleOptions::EModule::DATABASE ) ) + const_cast< FormComponentPropertyHandler* >( this )->m_bHaveCommand = true; + break; + } // switch ( nPropId ) + + aProperties.push_back( rProperty ); + } + + if ( aProperties.empty() ) + return Sequence< Property >(); + return comphelper::containerToSequence(aProperties); + } + + Sequence< OUString > SAL_CALL FormComponentPropertyHandler::getSupersededProperties( ) + { + return Sequence< OUString >( ); + } + + Sequence< OUString > SAL_CALL FormComponentPropertyHandler::getActuatingProperties( ) + { + return + { + PROPERTY_DATASOURCE, + PROPERTY_COMMAND, + PROPERTY_COMMANDTYPE, + PROPERTY_LISTSOURCE, + PROPERTY_LISTSOURCETYPE, + PROPERTY_SUBMIT_ENCODING, + PROPERTY_REPEAT, + PROPERTY_TABSTOP, + PROPERTY_BORDER, + PROPERTY_CONTROLSOURCE, + PROPERTY_DROPDOWN, + PROPERTY_IMAGE_URL, + PROPERTY_TARGET_URL, + PROPERTY_STRINGITEMLIST, + PROPERTY_BUTTONTYPE, + PROPERTY_ESCAPE_PROCESSING, + PROPERTY_TRISTATE, + PROPERTY_DECIMAL_ACCURACY, + PROPERTY_SHOWTHOUSANDSEP, + PROPERTY_FORMATKEY, + PROPERTY_EMPTY_IS_NULL, + PROPERTY_TOGGLE + }; + } + + LineDescriptor SAL_CALL FormComponentPropertyHandler::describePropertyLine( const OUString& _rPropertyName, + const Reference< XPropertyControlFactory >& _rxControlFactory ) + { + if ( !_rxControlFactory.is() ) + throw NullPointerException(); + + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + Property aProperty( impl_getPropertyFromId_throw( nPropId ) ); + + + // for the MultiLine property, we have different UI translations depending on the control + // type + if ( nPropId == PROPERTY_ID_MULTILINE ) + { + if ( ( m_nClassId == FormComponentType::FIXEDTEXT ) + || ( m_nClassId == FormComponentType::COMMANDBUTTON ) + || ( m_nClassId == FormComponentType::RADIOBUTTON ) + || ( m_nClassId == FormComponentType::CHECKBOX ) + ) + nPropId = PROPERTY_ID_WORDBREAK; + } + + OUString sDisplayName = m_pInfoService->getPropertyTranslation( nPropId ); + if ( sDisplayName.isEmpty() ) + { + OSL_FAIL( "FormComponentPropertyHandler::describePropertyLine: did getSupportedProperties not work properly?" ); + throw UnknownPropertyException(); + } + + + LineDescriptor aDescriptor; + aDescriptor.HelpURL = HelpIdUrl::getHelpURL( m_pInfoService->getPropertyHelpId( nPropId ) ); + aDescriptor.DisplayName = sDisplayName; + + // for the moment, assume a text field + sal_Int16 nControlType = PropertyControlType::TextField; + bool bReadOnly = false; + aDescriptor.Control.clear(); + + + bool bNeedDefaultStringIfVoidAllowed = false; + + TypeClass eType = aProperty.Type.getTypeClass(); + + switch ( nPropId ) + { + case PROPERTY_ID_DEFAULT_SELECT_SEQ: + case PROPERTY_ID_SELECTEDITEMS: + aDescriptor.PrimaryButtonId = UID_PROP_DLG_SELECTION; + break; + + case PROPERTY_ID_FILTER: + aDescriptor.PrimaryButtonId = UID_PROP_DLG_FILTER; + break; + + case PROPERTY_ID_SORT: + aDescriptor.PrimaryButtonId = UID_PROP_DLG_ORDER; + break; + + case PROPERTY_ID_MASTERFIELDS: + case PROPERTY_ID_DETAILFIELDS: + nControlType = PropertyControlType::StringListField; + aDescriptor.PrimaryButtonId = UID_PROP_DLG_FORMLINKFIELDS; + break; + + case PROPERTY_ID_COMMAND: + aDescriptor.PrimaryButtonId = UID_PROP_DLG_SQLCOMMAND; + break; + + case PROPERTY_ID_TABINDEX: + { + Reference< XControlContainer > xControlContext( impl_getContextControlContainer_nothrow() ); + if ( xControlContext.is() ) + aDescriptor.PrimaryButtonId = UID_PROP_DLG_TABINDEX; + nControlType = PropertyControlType::NumericField; + }; + break; + + case PROPERTY_ID_FONT: + bReadOnly = true; + aDescriptor.PrimaryButtonId = UID_PROP_DLG_FONT_TYPE; + break; + + case PROPERTY_ID_TARGET_URL: + case PROPERTY_ID_IMAGE_URL: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/urlcontrol.ui", m_xContext)); + auto pURLBox = std::make_unique<SvtURLBox>(xBuilder->weld_combo_box("urlcontrol")); + rtl::Reference<OFileUrlControl> pControl = new OFileUrlControl(std::move(pURLBox), std::move(xBuilder), false); + pControl->SetModifyHandler(); + aDescriptor.Control = pControl; + + aDescriptor.PrimaryButtonId = PROPERTY_ID_TARGET_URL == nPropId + ? UID_PROP_DLG_ATTR_TARGET_URL + : UID_PROP_DLG_IMAGE_URL; + break; + } + + case PROPERTY_ID_ECHO_CHAR: + nControlType = PropertyControlType::CharacterField; + break; + + case PROPERTY_ID_BACKGROUNDCOLOR: + case PROPERTY_ID_FILLCOLOR: + case PROPERTY_ID_SYMBOLCOLOR: + case PROPERTY_ID_BORDERCOLOR: + case PROPERTY_ID_GRIDLINECOLOR: + case PROPERTY_ID_HEADERBACKGROUNDCOLOR: + case PROPERTY_ID_HEADERTEXTCOLOR: + case PROPERTY_ID_ACTIVESELECTIONBACKGROUNDCOLOR: + case PROPERTY_ID_ACTIVESELECTIONTEXTCOLOR: + case PROPERTY_ID_INACTIVESELECTIONBACKGROUNDCOLOR: + case PROPERTY_ID_INACTIVESELECTIONTEXTCOLOR: + nControlType = PropertyControlType::ColorListBox; + + switch( nPropId ) + { + case PROPERTY_ID_BACKGROUNDCOLOR: + aDescriptor.PrimaryButtonId = UID_PROP_DLG_BACKGROUNDCOLOR; break; + case PROPERTY_ID_FILLCOLOR: + aDescriptor.PrimaryButtonId = UID_PROP_DLG_FILLCOLOR; break; + case PROPERTY_ID_SYMBOLCOLOR: + aDescriptor.PrimaryButtonId = UID_PROP_DLG_SYMBOLCOLOR; break; + case PROPERTY_ID_BORDERCOLOR: + aDescriptor.PrimaryButtonId = UID_PROP_DLG_BORDERCOLOR; break; + case PROPERTY_ID_GRIDLINECOLOR: + aDescriptor.PrimaryButtonId = HID_PROP_GRIDLINECOLOR; break; + case PROPERTY_ID_HEADERBACKGROUNDCOLOR: + aDescriptor.PrimaryButtonId = HID_PROP_HEADERBACKGROUNDCOLOR; break; + case PROPERTY_ID_HEADERTEXTCOLOR: + aDescriptor.PrimaryButtonId = HID_PROP_HEADERTEXTCOLOR; break; + case PROPERTY_ID_ACTIVESELECTIONBACKGROUNDCOLOR: + aDescriptor.PrimaryButtonId = HID_PROP_ACTIVESELECTIONBACKGROUNDCOLOR; break; + case PROPERTY_ID_ACTIVESELECTIONTEXTCOLOR: + aDescriptor.PrimaryButtonId = HID_PROP_ACTIVESELECTIONTEXTCOLOR; break; + case PROPERTY_ID_INACTIVESELECTIONBACKGROUNDCOLOR: + aDescriptor.PrimaryButtonId = HID_PROP_INACTIVESELECTIONBACKGROUNDCOLOR; break; + case PROPERTY_ID_INACTIVESELECTIONTEXTCOLOR: + aDescriptor.PrimaryButtonId = HID_PROP_INACTIVESELECTIONTEXTCOLOR; break; + } + break; + + case PROPERTY_ID_LABEL: + case PROPERTY_ID_URL: + nControlType = PropertyControlType::MultiLineTextField; + break; + + case PROPERTY_ID_DEFAULT_TEXT: + { + if (FormComponentType::FILECONTROL == m_nClassId) + nControlType = PropertyControlType::TextField; + else + nControlType = PropertyControlType::MultiLineTextField; + } + break; + + case PROPERTY_ID_TEXT: + if ( impl_componentHasProperty_throw( PROPERTY_MULTILINE ) ) + nControlType = PropertyControlType::MultiLineTextField; + break; + + case PROPERTY_ID_CONTROLLABEL: + bReadOnly = true; + aDescriptor.PrimaryButtonId = UID_PROP_DLG_CONTROLLABEL; + break; + + case PROPERTY_ID_FORMATKEY: + case PROPERTY_ID_EFFECTIVE_MIN: + case PROPERTY_ID_EFFECTIVE_MAX: + case PROPERTY_ID_EFFECTIVE_DEFAULT: + case PROPERTY_ID_EFFECTIVE_VALUE: + { + // and the supplier is really available + Reference< XNumberFormatsSupplier > xSupplier; + m_xComponent->getPropertyValue( PROPERTY_FORMATSSUPPLIER ) >>= xSupplier; + if (xSupplier.is()) + { + Reference< XUnoTunnel > xTunnel(xSupplier,UNO_QUERY); + DBG_ASSERT(xTunnel.is(), "FormComponentPropertyHandler::describePropertyLine : xTunnel is invalid!"); + if (auto pSupplier = comphelper::getFromUnoTunnel<SvNumberFormatsSupplierObj>(xTunnel)) + { + bool bIsFormatKey = (PROPERTY_ID_FORMATKEY == nPropId); + + bReadOnly = bIsFormatKey; + + if ( bIsFormatKey ) + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/formattedsample.ui", m_xContext)); + auto pContainer = xBuilder->weld_container("formattedsample"); + rtl::Reference<OFormatSampleControl> pControl = new OFormatSampleControl(std::move(pContainer), std::move(xBuilder), false); + pControl->SetModifyHandler(); + + pControl->SetFormatSupplier(pSupplier); + + aDescriptor.Control = pControl; + + aDescriptor.PrimaryButtonId = UID_PROP_DLG_NUMBER_FORMAT; + } + else + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/formattedcontrol.ui", m_xContext)); + auto pSpinButton = xBuilder->weld_formatted_spin_button("formattedcontrol"); + rtl::Reference<OFormattedNumericControl> pControl = new OFormattedNumericControl(std::move(pSpinButton), std::move(xBuilder), false); + pControl->SetModifyHandler(); + + FormatDescription aDesc; + aDesc.pSupplier = pSupplier; + Any aFormatKeyValue = m_xComponent->getPropertyValue(PROPERTY_FORMATKEY); + if ( !( aFormatKeyValue >>= aDesc.nKey ) ) + aDesc.nKey = 0; + + pControl->SetFormatDescription( aDesc ); + + aDescriptor.Control = pControl; + } + } + } + } + break; + + case PROPERTY_ID_DATEMIN: + case PROPERTY_ID_DATEMAX: + case PROPERTY_ID_DEFAULT_DATE: + case PROPERTY_ID_DATE: + nControlType = PropertyControlType::DateField; + break; + + case PROPERTY_ID_TIMEMIN: + case PROPERTY_ID_TIMEMAX: + case PROPERTY_ID_DEFAULT_TIME: + case PROPERTY_ID_TIME: + nControlType = PropertyControlType::TimeField; + break; + + case PROPERTY_ID_VALUEMIN: + case PROPERTY_ID_VALUEMAX: + case PROPERTY_ID_DEFAULT_VALUE: + case PROPERTY_ID_VALUE: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/formattedcontrol.ui", m_xContext)); + auto pSpinButton = xBuilder->weld_formatted_spin_button("formattedcontrol"); + rtl::Reference<OFormattedNumericControl> pControl = new OFormattedNumericControl(std::move(pSpinButton), std::move(xBuilder), false); + pControl->SetModifyHandler(); + aDescriptor.Control = pControl; + + // we don't set a formatter so the control uses a default (which uses the application + // language and a default numeric format) + // but we set the decimal digits + pControl->SetDecimalDigits( + ::comphelper::getINT16( m_xComponent->getPropertyValue( PROPERTY_DECIMAL_ACCURACY ) ) + ); + + // and the default value for the property + try + { + if (m_xPropertyState.is() && ((PROPERTY_ID_VALUEMIN == nPropId) || (PROPERTY_ID_VALUEMAX == nPropId))) + { + double nDefault = 0; + if ( m_xPropertyState->getPropertyDefault( aProperty.Name ) >>= nDefault ) + pControl->SetDefaultValue(nDefault); + } + } + catch (const Exception&) + { + // just ignore it + } + + break; + } + + default: + if ( TypeClass_BYTE <= eType && eType <= TypeClass_DOUBLE ) + { + sal_Int16 nDigits = 0; + sal_Int16 nValueUnit = -1; + sal_Int16 nDisplayUnit = -1; + if ( m_eComponentClass == eFormControl ) + { + if ( ( nPropId == PROPERTY_ID_WIDTH ) + || ( nPropId == PROPERTY_ID_ROWHEIGHT ) + || ( nPropId == PROPERTY_ID_HEIGHT ) + ) + { + nValueUnit = MeasureUnit::MM_10TH; + nDisplayUnit = impl_getDocumentMeasurementUnit_throw(); + nDigits = 2; + } + } + + Optional< double > aValueNotPresent( false, 0 ); + aDescriptor.Control = PropertyHandlerHelper::createNumericControl( + _rxControlFactory, nDigits, aValueNotPresent, aValueNotPresent ); + + Reference< XNumericControl > xNumericControl( aDescriptor.Control, UNO_QUERY_THROW ); + if ( nValueUnit != -1 ) + xNumericControl->setValueUnit( nValueUnit ); + if ( nDisplayUnit != -1 ) + xNumericControl->setDisplayUnit( nDisplayUnit ); + } + break; + } + + if ( eType == TypeClass_SEQUENCE ) + nControlType = PropertyControlType::StringListField; + + // boolean values + if ( eType == TypeClass_BOOLEAN ) + { + if ( ( nPropId == PROPERTY_ID_SHOW_POSITION ) + || ( nPropId == PROPERTY_ID_SHOW_NAVIGATION ) + || ( nPropId == PROPERTY_ID_SHOW_RECORDACTIONS ) + || ( nPropId == PROPERTY_ID_SHOW_FILTERSORT ) + ) + { + aDescriptor.Control = PropertyHandlerHelper::createListBoxControl(_rxControlFactory, RID_RSC_ENUM_SHOWHIDE, SAL_N_ELEMENTS(RID_RSC_ENUM_SHOWHIDE), false); + } + else + aDescriptor.Control = PropertyHandlerHelper::createListBoxControl(_rxControlFactory, RID_RSC_ENUM_YESNO, SAL_N_ELEMENTS(RID_RSC_ENUM_YESNO), false); + bNeedDefaultStringIfVoidAllowed = true; + } + + + // enum properties + sal_uInt32 nPropertyUIFlags = m_pInfoService->getPropertyUIFlags( nPropId ); + bool bIsEnumProperty = ( nPropertyUIFlags & PROP_FLAG_ENUM ) != 0; + if ( bIsEnumProperty || ( PROPERTY_ID_TARGET_FRAME == nPropId ) ) + { + std::vector< OUString > aEnumValues = m_pInfoService->getPropertyEnumRepresentations( nPropId ); + std::vector< OUString >::const_iterator pStart = aEnumValues.begin(); + std::vector< OUString >::const_iterator pEnd = aEnumValues.end(); + + // for a checkbox: if "ambiguous" is not allowed, remove this from the sequence + if ( ( PROPERTY_ID_DEFAULT_STATE == nPropId ) + || ( PROPERTY_ID_STATE == nPropId ) + ) + { + if ( impl_componentHasProperty_throw( PROPERTY_TRISTATE ) ) + { + if ( !::comphelper::getBOOL( m_xComponent->getPropertyValue( PROPERTY_TRISTATE ) ) ) + { // remove the last sequence element + if ( pEnd > pStart ) + --pEnd; + } + } + else + --pEnd; + } + + if ( PROPERTY_ID_LISTSOURCETYPE == nPropId ) + if ( FormComponentType::COMBOBOX == m_nClassId ) + // remove the first sequence element -> value list not possible for combo boxes + ++pStart; + + // copy the sequence + std::vector< OUString > aListEntries( pEnd - pStart ); + std::copy( pStart, pEnd, aListEntries.begin() ); + + // create the control + if ( PROPERTY_ID_TARGET_FRAME == nPropId ) + aDescriptor.Control = PropertyHandlerHelper::createComboBoxControl( _rxControlFactory, std::move(aListEntries), false ); + else + { + aDescriptor.Control = PropertyHandlerHelper::createListBoxControl( _rxControlFactory, std::move(aListEntries), false, false ); + bNeedDefaultStringIfVoidAllowed = true; + } + } + + + switch( nPropId ) + { + case PROPERTY_ID_REPEAT_DELAY: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/numericfield.ui", m_xContext)); + auto pSpinButton = xBuilder->weld_metric_spin_button("numericfield", FieldUnit::MILLISECOND); + rtl::Reference<ONumericControl> pControl = new ONumericControl(std::move(pSpinButton), std::move(xBuilder), bReadOnly); + pControl->SetModifyHandler(); + pControl->setMinValue( Optional< double >( true, 0 ) ); + pControl->setMaxValue( Optional< double >( true, std::numeric_limits< double >::max() ) ); + aDescriptor.Control = pControl; + } + break; + + case PROPERTY_ID_TABINDEX: + case PROPERTY_ID_BOUNDCOLUMN: + case PROPERTY_ID_VISIBLESIZE: + case PROPERTY_ID_MAXTEXTLEN: + case PROPERTY_ID_LINEINCREMENT: + case PROPERTY_ID_BLOCKINCREMENT: + case PROPERTY_ID_SPININCREMENT: + { + Optional< double > aMinValue( true, 0 ); + Optional< double > aMaxValue( true, 0x7FFFFFFF ); + + if ( nPropId == PROPERTY_ID_MAXTEXTLEN || nPropId == PROPERTY_ID_BOUNDCOLUMN ) + aMinValue.Value = -1; + else if ( nPropId == PROPERTY_ID_VISIBLESIZE ) + aMinValue.Value = 1; + else + aMinValue.Value = 0; + + aDescriptor.Control = PropertyHandlerHelper::createNumericControl( + _rxControlFactory, 0, aMinValue, aMaxValue ); + } + break; + + case PROPERTY_ID_DECIMAL_ACCURACY: + { + Optional< double > aMinValue( true, 0 ); + Optional< double > aMaxValue( true, 20 ); + + aDescriptor.Control = PropertyHandlerHelper::createNumericControl( + _rxControlFactory, 0, aMinValue, aMaxValue ); + } + break; + + + // DataSource + case PROPERTY_ID_DATASOURCE: + { + aDescriptor.PrimaryButtonId = UID_PROP_DLG_ATTR_DATASOURCE; + + std::vector< OUString > aListEntries; + + Reference< XDatabaseContext > xDatabaseContext = sdb::DatabaseContext::create( m_xContext ); + const Sequence< OUString > aDatasources = xDatabaseContext->getElementNames(); + aListEntries.resize( aDatasources.getLength() ); + std::copy( aDatasources.begin(), aDatasources.end(), aListEntries.begin() ); + aDescriptor.Control = PropertyHandlerHelper::createComboBoxControl( + _rxControlFactory, std::move(aListEntries), true ); + } + break; + + case PROPERTY_ID_CONTROLSOURCE: + { + std::vector< OUString > aFieldNames; + impl_initFieldList_nothrow( aFieldNames ); + aDescriptor.Control = PropertyHandlerHelper::createComboBoxControl( + _rxControlFactory, std::move(aFieldNames), false ); + } + break; + + case PROPERTY_ID_COMMAND: + impl_describeCursorSource_nothrow( aDescriptor, _rxControlFactory ); + break; + + case PROPERTY_ID_LISTSOURCE: + impl_describeListSourceUI_throw( aDescriptor, _rxControlFactory ); + break; + } + + if ( !aDescriptor.Control.is() ) + aDescriptor.Control = _rxControlFactory->createPropertyControl( nControlType, bReadOnly ); + + if ( ( aProperty.Attributes & PropertyAttribute::MAYBEVOID ) != 0 ) + { + // insert the string "Default" string, if necessary + if (bNeedDefaultStringIfVoidAllowed) + { + Reference< XStringListControl > xStringList( aDescriptor.Control, UNO_QUERY_THROW ); + xStringList->prependListEntry( m_sDefaultValueString ); + m_aPropertiesWithDefListEntry.insert( _rPropertyName ); + } + } + + if ( !aDescriptor.PrimaryButtonId.isEmpty() ) + aDescriptor.HasPrimaryButton = true; + if ( !aDescriptor.SecondaryButtonId.isEmpty() ) + aDescriptor.HasSecondaryButton = true; + + bool bIsDataProperty = ( nPropertyUIFlags & PROP_FLAG_DATA_PROPERTY ) != 0; + aDescriptor.Category = bIsDataProperty ? std::u16string_view(u"Data") : std::u16string_view(u"General"); + return aDescriptor; + } + + InteractiveSelectionResult SAL_CALL FormComponentPropertyHandler::onInteractivePropertySelection( const OUString& _rPropertyName, sal_Bool /*_bPrimary*/, Any& _rData, const Reference< XObjectInspectorUI >& _rxInspectorUI ) + { + if ( !_rxInspectorUI.is() ) + throw NullPointerException(); + + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + InteractiveSelectionResult eResult = InteractiveSelectionResult_Cancelled; + switch ( nPropId ) + { + case PROPERTY_ID_DEFAULT_SELECT_SEQ: + case PROPERTY_ID_SELECTEDITEMS: + if ( impl_dialogListSelection_nothrow( _rPropertyName, aGuard ) ) + eResult = InteractiveSelectionResult_Success; + break; + + case PROPERTY_ID_FILTER: + case PROPERTY_ID_SORT: + { + OUString sClause; + if ( impl_dialogFilterOrSort_nothrow( PROPERTY_ID_FILTER == nPropId, sClause, aGuard ) ) + { + _rData <<= sClause; + eResult = InteractiveSelectionResult_ObtainedValue; + } + } + break; + + case PROPERTY_ID_MASTERFIELDS: + case PROPERTY_ID_DETAILFIELDS: + if ( impl_dialogLinkedFormFields_nothrow( aGuard ) ) + eResult = InteractiveSelectionResult_Success; + break; + + case PROPERTY_ID_FORMATKEY: + if ( impl_dialogFormatting_nothrow( _rData, aGuard ) ) + eResult = InteractiveSelectionResult_ObtainedValue; + break; + + case PROPERTY_ID_IMAGE_URL: + if ( impl_browseForImage_nothrow( _rData, aGuard ) ) + eResult = InteractiveSelectionResult_ObtainedValue; + break; + + case PROPERTY_ID_TARGET_URL: + if ( impl_browseForTargetURL_nothrow( _rData, aGuard ) ) + eResult = InteractiveSelectionResult_ObtainedValue; + break; + + case PROPERTY_ID_FONT: + if ( impl_executeFontDialog_nothrow( _rData, aGuard ) ) + eResult = InteractiveSelectionResult_ObtainedValue; + break; + + case PROPERTY_ID_DATASOURCE: + if ( impl_browseForDatabaseDocument_throw( _rData, aGuard ) ) + eResult = InteractiveSelectionResult_ObtainedValue; + break; + + case PROPERTY_ID_BACKGROUNDCOLOR: + case PROPERTY_ID_FILLCOLOR: + case PROPERTY_ID_SYMBOLCOLOR: + case PROPERTY_ID_BORDERCOLOR: + case PROPERTY_ID_GRIDLINECOLOR: + case PROPERTY_ID_HEADERBACKGROUNDCOLOR: + case PROPERTY_ID_HEADERTEXTCOLOR: + case PROPERTY_ID_ACTIVESELECTIONBACKGROUNDCOLOR: + case PROPERTY_ID_ACTIVESELECTIONTEXTCOLOR: + case PROPERTY_ID_INACTIVESELECTIONBACKGROUNDCOLOR: + case PROPERTY_ID_INACTIVESELECTIONTEXTCOLOR: + if ( impl_dialogColorChooser_throw( nPropId, _rData, aGuard ) ) + eResult = InteractiveSelectionResult_ObtainedValue; + break; + + case PROPERTY_ID_CONTROLLABEL: + if ( impl_dialogChooseLabelControl_nothrow( _rData, aGuard ) ) + eResult = InteractiveSelectionResult_ObtainedValue; + break; + + case PROPERTY_ID_TABINDEX: + if ( impl_dialogChangeTabOrder_nothrow( aGuard ) ) + eResult = InteractiveSelectionResult_Success; + break; + + case PROPERTY_ID_COMMAND: + case PROPERTY_ID_LISTSOURCE: + if ( impl_doDesignSQLCommand_nothrow( _rxInspectorUI, nPropId ) ) + eResult = InteractiveSelectionResult_Pending; + break; + default: + OSL_FAIL( "FormComponentPropertyHandler::onInteractivePropertySelection: request for a property which does not have dedicated UI!" ); + break; + } + return eResult; + } + + namespace + { + void lcl_rebuildAndResetCommand( const Reference< XObjectInspectorUI >& _rxInspectorUI, const Reference< XPropertyHandler >& _rxHandler ) + { + OSL_PRECOND( _rxInspectorUI.is(), "lcl_rebuildAndResetCommand: invalid BrowserUI!" ); + OSL_PRECOND( _rxHandler.is(), "lcl_rebuildAndResetCommand: invalid handler!" ); + _rxInspectorUI->rebuildPropertyUI( PROPERTY_COMMAND ); + _rxHandler->setPropertyValue( PROPERTY_COMMAND, Any( OUString() ) ); + } + } + + void SAL_CALL FormComponentPropertyHandler::actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const Any& _rNewValue, const Any& /*_rOldValue*/, const Reference< XObjectInspectorUI >& _rxInspectorUI, sal_Bool _bFirstTimeInit ) + { + if ( !_rxInspectorUI.is() ) + throw NullPointerException(); + + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nActuatingPropId( impl_getPropertyId_nothrow( _rActuatingPropertyName ) ); + + std::vector< PropertyId > aDependentProperties; + + switch ( nActuatingPropId ) + { + // ----- EscapeProcessing ----- + case PROPERTY_ID_ESCAPE_PROCESSING: + aDependentProperties.push_back( PROPERTY_ID_FILTER ); + aDependentProperties.push_back( PROPERTY_ID_SORT ); + break; // case PROPERTY_ID_ESCAPE_PROCESSING + + // ----- CommandType ----- + case PROPERTY_ID_COMMANDTYPE: + // available commands (tables or queries) might have changed + if ( !_bFirstTimeInit && m_bHaveCommand ) + lcl_rebuildAndResetCommand( _rxInspectorUI, this ); + aDependentProperties.push_back( PROPERTY_ID_COMMAND ); + break; // case PROPERTY_ID_COMMANDTYPE + + // ----- DataSourceName ----- + case PROPERTY_ID_DATASOURCE: + // reset the connection, now that we have a new data source + m_xRowSetConnection.clear(); + + // available list source values (tables or queries) might have changed + if ( !_bFirstTimeInit && m_bHaveListSource ) + _rxInspectorUI->rebuildPropertyUI( PROPERTY_LISTSOURCE ); + + // available commands (tables or queries) might have changed + if ( !_bFirstTimeInit && m_bHaveCommand ) + lcl_rebuildAndResetCommand( _rxInspectorUI, this ); + + // Command also depends on DataSource + aDependentProperties.push_back( PROPERTY_ID_COMMAND ); + [[fallthrough]]; + + // ----- Command ----- + case PROPERTY_ID_COMMAND: + aDependentProperties.push_back( PROPERTY_ID_FILTER ); + aDependentProperties.push_back( PROPERTY_ID_SORT ); + if ( m_bComponentIsSubForm ) + aDependentProperties.push_back( PROPERTY_ID_DETAILFIELDS ); + break; + + // ----- ListSourceType ----- + case PROPERTY_ID_LISTSOURCETYPE: + if ( !_bFirstTimeInit && m_bHaveListSource ) + // available list source values (tables or queries) might have changed + _rxInspectorUI->rebuildPropertyUI( PROPERTY_LISTSOURCE ); + aDependentProperties.push_back( PROPERTY_ID_STRINGITEMLIST ); + aDependentProperties.push_back( PROPERTY_ID_TYPEDITEMLIST ); + aDependentProperties.push_back( PROPERTY_ID_BOUNDCOLUMN ); + [[fallthrough]]; + + // ----- StringItemList ----- + case PROPERTY_ID_STRINGITEMLIST: + aDependentProperties.push_back( PROPERTY_ID_TYPEDITEMLIST ); + aDependentProperties.push_back( PROPERTY_ID_SELECTEDITEMS ); + aDependentProperties.push_back( PROPERTY_ID_DEFAULT_SELECT_SEQ ); + break; + + // ----- ListSource ----- + case PROPERTY_ID_LISTSOURCE: + aDependentProperties.push_back( PROPERTY_ID_STRINGITEMLIST ); + aDependentProperties.push_back( PROPERTY_ID_TYPEDITEMLIST ); + break; + + // ----- DataField ----- + case PROPERTY_ID_CONTROLSOURCE: + { + OUString sControlSource; + _rNewValue >>= sControlSource; + if ( impl_componentHasProperty_throw( PROPERTY_FILTERPROPOSAL ) ) + _rxInspectorUI->enablePropertyUI( PROPERTY_FILTERPROPOSAL, !sControlSource.isEmpty() ); + if ( impl_componentHasProperty_throw( PROPERTY_EMPTY_IS_NULL ) ) + _rxInspectorUI->enablePropertyUI( PROPERTY_EMPTY_IS_NULL, !sControlSource.isEmpty() ); + + aDependentProperties.push_back( PROPERTY_ID_BOUNDCOLUMN ); + aDependentProperties.push_back( PROPERTY_ID_SCALEIMAGE ); + aDependentProperties.push_back( PROPERTY_ID_SCALE_MODE ); + aDependentProperties.push_back( PROPERTY_ID_INPUT_REQUIRED ); + } + break; + + case PROPERTY_ID_EMPTY_IS_NULL: + aDependentProperties.push_back( PROPERTY_ID_INPUT_REQUIRED ); + break; + + // ----- SubmitEncoding ----- + case PROPERTY_ID_SUBMIT_ENCODING: + { + FormSubmitEncoding eEncoding = FormSubmitEncoding_URL; + if( ! (_rNewValue >>= eEncoding) ) + SAL_WARN("extensions.propctrlr", "actuatingPropertyChanged: unable to get property " << PROPERTY_ID_SUBMIT_ENCODING); + _rxInspectorUI->enablePropertyUI( PROPERTY_SUBMIT_METHOD, eEncoding == FormSubmitEncoding_URL ); + } + break; + + // ----- Repeat ----- + case PROPERTY_ID_REPEAT: + { + bool bIsRepeating = false; + if( ! (_rNewValue >>= bIsRepeating) ) + SAL_WARN("extensions.propctrlr", "actuatingPropertyChanged: unable to get property " << PROPERTY_ID_REPEAT); + _rxInspectorUI->enablePropertyUI( PROPERTY_REPEAT_DELAY, bIsRepeating ); + } + break; + + // ----- TabStop ----- + case PROPERTY_ID_TABSTOP: + { + if ( !impl_componentHasProperty_throw( PROPERTY_TABINDEX ) ) + break; + bool bHasTabStop = false; + _rNewValue >>= bHasTabStop; + _rxInspectorUI->enablePropertyUI( PROPERTY_TABINDEX, bHasTabStop ); + } + break; + + // ----- Border ----- + case PROPERTY_ID_BORDER: + { + sal_Int16 nBordeType = VisualEffect::NONE; + if( ! (_rNewValue >>= nBordeType) ) + SAL_WARN("extensions.propctrlr", "actuatingPropertyChanged: unable to get property " << PROPERTY_ID_BORDER); + _rxInspectorUI->enablePropertyUI( PROPERTY_BORDERCOLOR, nBordeType == VisualEffect::FLAT ); + } + break; + + // ----- DropDown ----- + case PROPERTY_ID_DROPDOWN: + { + if ( impl_isSupportedProperty_nothrow( PROPERTY_ID_LINECOUNT ) ) + { + bool bDropDown = true; + _rNewValue >>= bDropDown; + _rxInspectorUI->enablePropertyUI( PROPERTY_LINECOUNT, bDropDown ); + } + } + break; + + // ----- ImageURL ----- + case PROPERTY_ID_IMAGE_URL: + { + if ( impl_isSupportedProperty_nothrow( PROPERTY_ID_IMAGEPOSITION ) ) + { + OUString sImageURL; + if( ! (_rNewValue >>= sImageURL) ) + SAL_WARN("extensions.propctrlr", "actuatingPropertyChanged: unable to get property " << PROPERTY_ID_IMAGE_URL); + _rxInspectorUI->enablePropertyUI( PROPERTY_IMAGEPOSITION, !sImageURL.isEmpty() ); + } + + aDependentProperties.push_back( PROPERTY_ID_SCALEIMAGE ); + aDependentProperties.push_back( PROPERTY_ID_SCALE_MODE ); + } + break; + + // ----- ButtonType ----- + case PROPERTY_ID_BUTTONTYPE: + { + FormButtonType eButtonType( FormButtonType_PUSH ); + if( ! (_rNewValue >>= eButtonType) ) + SAL_WARN("extensions.propctrlr", "actuatingPropertyChanged: unable to get property " << PROPERTY_ID_BUTTONTYPE); + _rxInspectorUI->enablePropertyUI( PROPERTY_TARGET_URL, FormButtonType_URL == eButtonType ); + [[fallthrough]]; + } + + // ----- TargetURL ----- + case PROPERTY_ID_TARGET_URL: + aDependentProperties.push_back( PROPERTY_ID_TARGET_FRAME ); + break; // case PROPERTY_ID_TARGET_URL + + // ----- TriState ----- + case PROPERTY_ID_TRISTATE: + if ( !_bFirstTimeInit ) + _rxInspectorUI->rebuildPropertyUI( m_eComponentClass == eFormControl ? PROPERTY_DEFAULT_STATE : PROPERTY_STATE ); + break; // case PROPERTY_ID_TRISTATE + + // ----- DecimalAccuracy ----- + case PROPERTY_ID_DECIMAL_ACCURACY: + // ----- ShowThousandsSeparator ----- + case PROPERTY_ID_SHOWTHOUSANDSEP: + { + bool bAccuracy = (PROPERTY_ID_DECIMAL_ACCURACY == nActuatingPropId); + sal_uInt16 nNewDigits = 0; + if ( bAccuracy ) + { + if( ! (_rNewValue >>= nNewDigits) ) + SAL_WARN("extensions.propctrlr", "actuatingPropertyChanged: unable to get property " << PROPERTY_ID_DECIMAL_ACCURACY); + } + else + { + bool bUseSep = false; + if( ! (_rNewValue >>= bUseSep) ) + SAL_WARN("extensions.propctrlr", "actuatingPropertyChanged: unable to get property " << PROPERTY_ID_SHOWTHOUSANDSEP); + } + + // propagate the changes to the min/max/default fields + OUString aAffectedProps[] = { PROPERTY_VALUE, PROPERTY_DEFAULT_VALUE, PROPERTY_VALUEMIN, PROPERTY_VALUEMAX }; + for (const OUString & aAffectedProp : aAffectedProps) + { + Reference< XPropertyControl > xControl; + try + { + xControl = _rxInspectorUI->getPropertyControl( aAffectedProp ); + } + catch( const UnknownPropertyException& ) {} + if ( xControl.is() ) + { + OFormattedNumericControl* pControl = dynamic_cast< OFormattedNumericControl* >( xControl.get() ); + DBG_ASSERT( pControl, "FormComponentPropertyHandler::actuatingPropertyChanged: invalid control!" ); + if (pControl) + { + if ( bAccuracy ) + pControl->SetDecimalDigits( nNewDigits ); + } + } + } + } + break; + + // ----- FormatKey ----- + case PROPERTY_ID_FORMATKEY: + { + FormatDescription aNewDesc; + + Reference< XNumberFormatsSupplier > xSupplier; + if( ! (m_xComponent->getPropertyValue( PROPERTY_FORMATSSUPPLIER ) >>= xSupplier) ) + SAL_WARN("extensions.propctrlr", "actuatingPropertyChanged: unable to get property " << PROPERTY_ID_FORMATKEY); + + Reference< XUnoTunnel > xTunnel( xSupplier, UNO_QUERY ); + DBG_ASSERT(xTunnel.is(), "FormComponentPropertyHandler::actuatingPropertyChanged: xTunnel is invalid!"); + if ( xTunnel.is() ) + { + SvNumberFormatsSupplierObj* pSupplier = reinterpret_cast<SvNumberFormatsSupplierObj*>(xTunnel->getSomething(SvNumberFormatsSupplierObj::getUnoTunnelId())); + // the same again + + aNewDesc.pSupplier = pSupplier; + if ( !( _rNewValue >>= aNewDesc.nKey ) ) + aNewDesc.nKey = 0; + + // give each control which has to know this an own copy of the description + OUString aFormattedPropertyControls[] = { + PROPERTY_EFFECTIVE_MIN, PROPERTY_EFFECTIVE_MAX, PROPERTY_EFFECTIVE_DEFAULT, PROPERTY_EFFECTIVE_VALUE + }; + for (const OUString & aFormattedPropertyControl : aFormattedPropertyControls) + { + Reference< XPropertyControl > xControl; + try + { + xControl = _rxInspectorUI->getPropertyControl( aFormattedPropertyControl ); + } + catch( const UnknownPropertyException& ) {} + if ( xControl.is() ) + { + OFormattedNumericControl* pControl = dynamic_cast< OFormattedNumericControl* >( xControl.get() ); + DBG_ASSERT( pControl, "FormComponentPropertyHandler::actuatingPropertyChanged: invalid control!" ); + if ( pControl ) + pControl->SetFormatDescription( aNewDesc ); + } + } + } + } + break; + + case PROPERTY_ID_TOGGLE: + { + bool bIsToggleButton = false; + if( ! (_rNewValue >>= bIsToggleButton) ) + SAL_WARN("extensions.propctrlr", "actuatingPropertyChanged: unable to get property " << PROPERTY_ID_TOGGLE); + _rxInspectorUI->enablePropertyUI( PROPERTY_DEFAULT_STATE, bIsToggleButton ); + } + break; + case -1: + throw RuntimeException(); + break; + default: + OSL_FAIL( "FormComponentPropertyHandler::actuatingPropertyChanged: did not register for this property!" ); + break; + + } // switch ( nActuatingPropId ) + + for (auto const& dependentProperty : aDependentProperties) + { + if ( impl_isSupportedProperty_nothrow(dependentProperty) ) + impl_updateDependentProperty_nothrow(dependentProperty, _rxInspectorUI); + } + } + + void FormComponentPropertyHandler::impl_updateDependentProperty_nothrow( PropertyId _nPropId, const Reference< XObjectInspectorUI >& _rxInspectorUI ) const + { + try + { + switch ( _nPropId ) + { + // ----- StringItemList ----- + case PROPERTY_ID_STRINGITEMLIST: + { + ListSourceType eLSType = ListSourceType_VALUELIST; + if( ! (impl_getPropertyValue_throw( PROPERTY_LISTSOURCETYPE ) >>= eLSType) ) + SAL_WARN("extensions.propctrlr", "impl_updateDependentProperty_nothrow: unable to get property " << PROPERTY_LISTSOURCETYPE); + + OUString sListSource; + { + Sequence< OUString > aListSource; + Any aListSourceValue( impl_getPropertyValue_throw( PROPERTY_LISTSOURCE ) ); + if ( aListSourceValue >>= aListSource ) + { + if ( aListSource.hasElements() ) + sListSource = aListSource[0]; + } + else + if( ! (aListSourceValue >>= sListSource) ) + SAL_WARN("extensions.propctrlr", "impl_updateDependentProperty_nothrow: unable to get property " << PROPERTY_LISTSOURCE); + } + + bool bIsEnabled = ( ( eLSType == ListSourceType_VALUELIST ) + || ( sListSource.isEmpty() ) + ); + _rxInspectorUI->enablePropertyUI( PROPERTY_STRINGITEMLIST, bIsEnabled ); + } + break; // case PROPERTY_ID_STRINGITEMLIST + + // ----- TypedItemList ----- + case PROPERTY_ID_TYPEDITEMLIST: + { + /* TODO: anything? */ + } + break; // case PROPERTY_ID_TYPEDITEMLIST + + // ----- BoundColumn ----- + case PROPERTY_ID_BOUNDCOLUMN: + { + ListSourceType eLSType = ListSourceType_VALUELIST; + if( ! (impl_getPropertyValue_throw( PROPERTY_LISTSOURCETYPE ) >>= eLSType) ) + SAL_WARN("extensions.propctrlr", "impl_updateDependentProperty_nothrow: unable to get property " << PROPERTY_LISTSOURCETYPE); + + _rxInspectorUI->enablePropertyUI( PROPERTY_BOUNDCOLUMN, + ( eLSType != ListSourceType_VALUELIST ) + ); + } + break; // case PROPERTY_ID_BOUNDCOLUMN + + // ----- ScaleImage, ScaleMode ----- + case PROPERTY_ID_SCALEIMAGE: + case PROPERTY_ID_SCALE_MODE: + { + OUString sControlSource; + if ( impl_isSupportedProperty_nothrow( PROPERTY_ID_CONTROLSOURCE ) ) + impl_getPropertyValue_throw( PROPERTY_CONTROLSOURCE ) >>= sControlSource; + + OUString sImageURL; + impl_getPropertyValue_throw( PROPERTY_IMAGE_URL ) >>= sImageURL; + + _rxInspectorUI->enablePropertyUI( impl_getPropertyNameFromId_nothrow( _nPropId ), + ( !sControlSource.isEmpty() ) || ( !sImageURL.isEmpty() ) + ); + } + break; // case PROPERTY_ID_SCALEIMAGE, PROPERTY_ID_SCALE_MODE + + // ----- InputRequired ----- + case PROPERTY_ID_INPUT_REQUIRED: + { + OUString sControlSource; + if( ! (impl_getPropertyValue_throw( PROPERTY_CONTROLSOURCE ) >>= sControlSource) ) + SAL_WARN("extensions.propctrlr", "impl_updateDependentProperty_nothrow: unable to get property " << PROPERTY_CONTROLSOURCE); + + bool bEmptyIsNULL = false; + bool bHasEmptyIsNULL = impl_componentHasProperty_throw( PROPERTY_EMPTY_IS_NULL ); + if ( bHasEmptyIsNULL ) + if( ! (impl_getPropertyValue_throw( PROPERTY_EMPTY_IS_NULL ) >>= bEmptyIsNULL) ) + SAL_WARN("extensions.propctrlr", "impl_updateDependentProperty_nothrow: unable to get property " << PROPERTY_EMPTY_IS_NULL); + + // if the control is not bound to a DB field, there is no sense in having the "Input required" + // property + // Also, if an empty input of this control are *not* written as NULL, but as empty strings, + // then "Input required" does not make sense, too (since there's always an input, even if the control + // is empty). + _rxInspectorUI->enablePropertyUI( PROPERTY_INPUT_REQUIRED, + ( !sControlSource.isEmpty() ) && ( !bHasEmptyIsNULL || bEmptyIsNULL ) + ); + } + break; + + // ----- SelectedItems, DefaultSelection ----- + case PROPERTY_ID_SELECTEDITEMS: + case PROPERTY_ID_DEFAULT_SELECT_SEQ: + { + Sequence< OUString > aEntries; + impl_getPropertyValue_throw( PROPERTY_STRINGITEMLIST ) >>= aEntries; + bool isEnabled = aEntries.hasElements(); + + if ( ( m_nClassId == FormComponentType::LISTBOX ) && ( m_eComponentClass == eFormControl ) ) + { + ListSourceType eLSType = ListSourceType_VALUELIST; + impl_getPropertyValue_throw( PROPERTY_LISTSOURCETYPE ) >>= eLSType; + isEnabled &= ( eLSType == ListSourceType_VALUELIST ); + } + _rxInspectorUI->enablePropertyUIElements( impl_getPropertyNameFromId_nothrow( _nPropId ), + PropertyLineElement::PrimaryButton, isEnabled ); + } + break; // case PROPERTY_ID_DEFAULT_SELECT_SEQ + + // ----- TargetFrame ------ + case PROPERTY_ID_TARGET_FRAME: + { + OUString sTargetURL; + impl_getPropertyValue_throw( PROPERTY_TARGET_URL ) >>= sTargetURL; + FormButtonType eButtonType( FormButtonType_URL ); + if ( 0 != m_nClassId ) + { + if( ! (impl_getPropertyValue_throw( PROPERTY_BUTTONTYPE ) >>= eButtonType) ) + SAL_WARN("extensions.propctrlr", "impl_updateDependentProperty_nothrow: unable to get property " << PROPERTY_BUTTONTYPE); + } + // if m_nClassId is 0, then we're inspecting a form. In this case, eButtonType is always + // FormButtonType_URL here + _rxInspectorUI->enablePropertyUI( PROPERTY_TARGET_FRAME, + ( eButtonType == FormButtonType_URL ) && ( !sTargetURL.isEmpty() ) + ); + } + break; + + // ----- Order ------ + case PROPERTY_ID_SORT: + // ----- Filter ------ + case PROPERTY_ID_FILTER: + { + Reference< XConnection > xConnection; + bool bAllowEmptyDS = ::dbtools::isEmbeddedInDatabase( m_xComponent, xConnection ); + + // if there's no escape processing, we cannot enter any values for this property + bool bDoEscapeProcessing( false ); + impl_getPropertyValue_throw( PROPERTY_ESCAPE_PROCESSING ) >>= bDoEscapeProcessing; + _rxInspectorUI->enablePropertyUI( + impl_getPropertyNameFromId_nothrow( _nPropId ), + bDoEscapeProcessing + ); + + // also care for the browse button - enabled if we have escape processing, and a valid + // data source signature + _rxInspectorUI->enablePropertyUIElements( + impl_getPropertyNameFromId_nothrow( _nPropId ), + PropertyLineElement::PrimaryButton, + impl_hasValidDataSourceSignature_nothrow( m_xComponent, bAllowEmptyDS ) + && bDoEscapeProcessing + ); + } + break; // case PROPERTY_ID_FILTER: + + // ----- Command ----- + case PROPERTY_ID_COMMAND: + { + sal_Int32 nCommandType( CommandType::COMMAND ); + if( ! (impl_getPropertyValue_throw( PROPERTY_COMMANDTYPE ) >>= nCommandType) ) + SAL_WARN("extensions.propctrlr", "impl_updateDependentProperty_nothrow: unable to get property " << PROPERTY_COMMANDTYPE); + + impl_ensureRowsetConnection_nothrow(); + Reference< XConnection > xConnection = m_xRowSetConnection.getTyped(); + bool bAllowEmptyDS = false; + if ( !xConnection.is() ) + bAllowEmptyDS = ::dbtools::isEmbeddedInDatabase( m_xComponent, xConnection ); + + bool doEnable = ( nCommandType == CommandType::COMMAND ) + && ( m_xRowSetConnection.is() + || xConnection.is() + || impl_hasValidDataSourceSignature_nothrow( m_xComponent, bAllowEmptyDS) + ); + + _rxInspectorUI->enablePropertyUIElements( + PROPERTY_COMMAND, + PropertyLineElement::PrimaryButton, + doEnable + ); + } + break; // case PROPERTY_ID_COMMAND + + // ----- DetailFields ----- + case PROPERTY_ID_DETAILFIELDS: + { + Reference< XConnection > xConnection; + bool bAllowEmptyDS = ::dbtools::isEmbeddedInDatabase( m_xComponent, xConnection ); + + // both our current form, and its parent form, need to have a valid + // data source signature + bool bDoEnableMasterDetailFields = + impl_hasValidDataSourceSignature_nothrow( m_xComponent, bAllowEmptyDS ) + && impl_hasValidDataSourceSignature_nothrow( Reference< XPropertySet >( m_xObjectParent, UNO_QUERY ), bAllowEmptyDS ); + + // in opposite to the other properties, here in real *two* properties are + // affected + _rxInspectorUI->enablePropertyUIElements( PROPERTY_DETAILFIELDS, PropertyLineElement::PrimaryButton, bDoEnableMasterDetailFields ); + _rxInspectorUI->enablePropertyUIElements( PROPERTY_MASTERFIELDS, PropertyLineElement::PrimaryButton, bDoEnableMasterDetailFields ); + } + break; + + default: + OSL_FAIL( "FormComponentPropertyHandler::impl_updateDependentProperty_nothrow: unexpected property to update!" ); + break; + + } // switch + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "FormComponentPropertyHandler::impl_updateDependentProperty_nothrow" ); + } + } + + void SAL_CALL FormComponentPropertyHandler::disposing() + { + PropertyHandlerComponent::disposing(); + if ( m_xCommandDesigner.is() && m_xCommandDesigner->isActive() ) + m_xCommandDesigner->dispose(); + } + + sal_Bool SAL_CALL FormComponentPropertyHandler::suspend( sal_Bool _bSuspend ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( _bSuspend ) + if ( m_xCommandDesigner.is() && m_xCommandDesigner->isActive() ) + return m_xCommandDesigner->suspend(); + return true; + } + + void FormComponentPropertyHandler::onNewComponent() + { + PropertyHandlerComponent::onNewComponent(); + if ( !m_xComponentPropertyInfo.is() && m_xComponent.is() ) + throw NullPointerException(); + + m_xPropertyState.set( m_xComponent, UNO_QUERY ); + m_eComponentClass = eUnknown; + m_bComponentIsSubForm = m_bHaveListSource = m_bHaveCommand = false; + m_nClassId = 0; + + try + { + // component class + m_eComponentClass = eUnknown; + + if ( impl_componentHasProperty_throw( PROPERTY_WIDTH ) + && impl_componentHasProperty_throw( PROPERTY_HEIGHT ) + && impl_componentHasProperty_throw( PROPERTY_POSITIONX ) + && impl_componentHasProperty_throw( PROPERTY_POSITIONY ) + && impl_componentHasProperty_throw( PROPERTY_STEP ) + && impl_componentHasProperty_throw( PROPERTY_TABINDEX ) + ) + { + m_eComponentClass = eDialogControl; + } + else + { + m_eComponentClass = eFormControl; + } + + + // (database) sub form? + Reference< XForm > xAsForm( m_xComponent, UNO_QUERY ); + if ( xAsForm.is() ) + { + Reference< XForm > xFormsParent( xAsForm->getParent(), css::uno::UNO_QUERY ); + m_bComponentIsSubForm = xFormsParent.is(); + } + + + // ClassId + Reference< XChild > xCompAsChild( m_xComponent, UNO_QUERY ); + if ( xCompAsChild.is() ) + m_xObjectParent = xCompAsChild->getParent(); + + + // ClassId + impl_classifyControlModel_throw(); + } + catch( const RuntimeException& ) + { + throw; + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "FormComponentPropertyHandler::onNewComponent" ); + } + } + + void FormComponentPropertyHandler::impl_classifyControlModel_throw( ) + { + if ( impl_componentHasProperty_throw( PROPERTY_CLASSID ) ) + { + if( ! (m_xComponent->getPropertyValue( PROPERTY_CLASSID ) >>= m_nClassId) ) + SAL_WARN("extensions.propctrlr", "impl_classifyControlModel_throw: unable to get property " << PROPERTY_CLASSID); + } + else if ( eDialogControl == m_eComponentClass ) + { + Reference< XServiceInfo > xServiceInfo( m_xComponent, UNO_QUERY ); + if ( xServiceInfo.is() ) + { + // it's a control model, and can tell about it's supported services + m_nClassId = FormComponentType::CONTROL; + + const char* aControlModelServiceNames[] = + { + "UnoControlButtonModel", + "UnoControlCheckBoxModel", + "UnoControlComboBoxModel", + "UnoControlCurrencyFieldModel", + "UnoControlDateFieldModel", + "UnoControlEditModel", + "UnoControlFileControlModel", + "UnoControlFixedTextModel", + "UnoControlGroupBoxModel", + "UnoControlImageControlModel", + "UnoControlListBoxModel", + "UnoControlNumericFieldModel", + "UnoControlPatternFieldModel", + "UnoControlRadioButtonModel", + "UnoControlScrollBarModel", + "UnoControlSpinButtonModel", + "UnoControlTimeFieldModel", + + "UnoControlFixedLineModel", + "UnoControlFormattedFieldModel", + "UnoControlProgressBarModel" + }; + const sal_Int16 nClassIDs[] = + { + FormComponentType::COMMANDBUTTON, + FormComponentType::CHECKBOX, + FormComponentType::COMBOBOX, + FormComponentType::CURRENCYFIELD, + FormComponentType::DATEFIELD, + FormComponentType::TEXTFIELD, + FormComponentType::FILECONTROL, + FormComponentType::FIXEDTEXT, + FormComponentType::GROUPBOX, + FormComponentType::IMAGECONTROL, + FormComponentType::LISTBOX, + FormComponentType::NUMERICFIELD, + FormComponentType::PATTERNFIELD, + FormComponentType::RADIOBUTTON, + FormComponentType::SCROLLBAR, + FormComponentType::SPINBUTTON, + FormComponentType::TIMEFIELD, + + ControlType::FIXEDLINE, + ControlType::FORMATTEDFIELD, + ControlType::PROGRESSBAR + }; + + sal_Int32 nKnownControlTypes = SAL_N_ELEMENTS( aControlModelServiceNames ); + OSL_ENSURE( nKnownControlTypes == SAL_N_ELEMENTS( nClassIDs ), + "FormComponentPropertyHandler::impl_classifyControlModel_throw: inconsistence" ); + + for ( sal_Int32 i = 0; i < nKnownControlTypes; ++i ) + { + OUString sServiceName = "com.sun.star.awt." + + OUString::createFromAscii( aControlModelServiceNames[ i ] ); + + if ( xServiceInfo->supportsService( sServiceName ) ) + { + m_nClassId = nClassIDs[ i ]; + break; + } + } + } + } + } + + void FormComponentPropertyHandler::impl_normalizePropertyValue_nothrow( Any& _rValue, PropertyId _nPropId ) const + { + switch ( _nPropId ) + { + case PROPERTY_ID_TABSTOP: + if ( !_rValue.hasValue() ) + { + switch ( m_nClassId ) + { + case FormComponentType::COMMANDBUTTON: + case FormComponentType::RADIOBUTTON: + case FormComponentType::CHECKBOX: + case FormComponentType::TEXTFIELD: + case FormComponentType::LISTBOX: + case FormComponentType::COMBOBOX: + case FormComponentType::FILECONTROL: + case FormComponentType::DATEFIELD: + case FormComponentType::TIMEFIELD: + case FormComponentType::NUMERICFIELD: + case ControlType::FORMATTEDFIELD: + case FormComponentType::CURRENCYFIELD: + case FormComponentType::PATTERNFIELD: + _rValue <<= true; + break; + default: + _rValue <<= false; + break; + } + } + break; + } + } + + bool FormComponentPropertyHandler::isReportModel() const + { + Reference<XModel> xModel(impl_getContextDocument_nothrow()); + Reference<XReportDefinition> xReportDef(xModel, css::uno::UNO_QUERY); + return xReportDef.is(); + } + + bool FormComponentPropertyHandler::impl_shouldExcludeProperty_nothrow( const Property& _rProperty ) const + { + OSL_ENSURE( _rProperty.Handle == m_pInfoService->getPropertyId( _rProperty.Name ), + "FormComponentPropertyHandler::impl_shouldExcludeProperty_nothrow: inconsistency in the property!" ); + + if ( _rProperty.Handle == PROPERTY_ID_CONTROLLABEL ) + // prevent that this is caught below + return false; + + if ( ( _rProperty.Type.getTypeClass() == TypeClass_INTERFACE ) + || ( _rProperty.Type.getTypeClass() == TypeClass_UNKNOWN ) + ) + return true; + + if ( ( _rProperty.Attributes & PropertyAttribute::TRANSIENT ) && ( m_eComponentClass != eDialogControl ) ) + // strange enough, dialog controls declare a lot of their properties as transient + return true; + + if ( _rProperty.Attributes & PropertyAttribute::READONLY ) + return true; + + switch ( _rProperty.Handle ) + { + case PROPERTY_ID_MASTERFIELDS: + case PROPERTY_ID_DETAILFIELDS: + if ( !m_bComponentIsSubForm ) + // no master and detail fields for forms which are no sub forms + return true; + break; + + case PROPERTY_ID_DATASOURCE: + { + // don't show DataSource if the component is part of an embedded form document + Reference< XConnection > xConn; + if ( isEmbeddedInDatabase( m_xComponent, xConn ) ) + return true; + } + break; + + case PROPERTY_ID_TEXT: + // don't show the "Text" property of formatted fields + if ( ControlType::FORMATTEDFIELD == m_nClassId ) + return true; + break; + + case PROPERTY_ID_FORMATKEY: + case PROPERTY_ID_EFFECTIVE_MIN: + case PROPERTY_ID_EFFECTIVE_MAX: + case PROPERTY_ID_EFFECTIVE_DEFAULT: + case PROPERTY_ID_EFFECTIVE_VALUE: + // only if the set has a formats supplier, too + if ( !impl_componentHasProperty_throw( PROPERTY_FORMATSSUPPLIER ) ) + return true; + // (form) date and time fields also have a formats supplier, but the format itself + // is reflected in another property + if ( ( FormComponentType::DATEFIELD == m_nClassId ) + || ( FormComponentType::TIMEFIELD == m_nClassId ) + ) + return true; + break; + + case PROPERTY_ID_SCALEIMAGE: + if ( impl_componentHasProperty_throw( PROPERTY_SCALE_MODE ) ) + // ScaleImage is superseded by ScaleMode + return true; + break; + + case PROPERTY_ID_WRITING_MODE: + if ( !SvtCTLOptions::IsCTLFontEnabled() ) + return true; + break; + } + + sal_uInt32 nPropertyUIFlags = m_pInfoService->getPropertyUIFlags( _rProperty.Handle ); + + // don't show experimental properties unless allowed to do so + if ( ( nPropertyUIFlags & PROP_FLAG_EXPERIMENTAL ) != 0 ) + return true; + + // no data properties if no Base is installed. + if ( ( nPropertyUIFlags & PROP_FLAG_DATA_PROPERTY ) != 0 ) + if ( !SvtModuleOptions().IsModuleInstalled( SvtModuleOptions::EModule::DATABASE ) ) + return true; + + if ((nPropertyUIFlags & PROP_FLAG_REPORT_INVISIBLE) != 0 && isReportModel()) + return true; + + return false; + } + + Reference< XRowSet > FormComponentPropertyHandler::impl_getRowSet_throw( ) const + { + Reference< XRowSet > xRowSet = m_xRowSet; + if ( !xRowSet.is() ) + { + xRowSet.set( m_xComponent, UNO_QUERY ); + if ( !xRowSet.is() ) + { + xRowSet.set( m_xObjectParent, UNO_QUERY ); + if ( !xRowSet.is() ) + { + // are we inspecting a grid column? + if (Reference< XGridColumnFactory >( m_xObjectParent, UNO_QUERY) .is()) + { // yes + Reference< XChild > xParentAsChild( m_xObjectParent, UNO_QUERY ); + if ( xParentAsChild.is() ) + xRowSet.set( xParentAsChild->getParent(), UNO_QUERY ); + } + } + if ( !xRowSet.is() ) + xRowSet = m_xRowSet; + } + DBG_ASSERT( xRowSet.is(), "FormComponentPropertyHandler::impl_getRowSet_throw: could not obtain the rowset for the introspectee!" ); + } + return xRowSet; + } + + + Reference< XRowSet > FormComponentPropertyHandler::impl_getRowSet_nothrow( ) const + { + Reference< XRowSet > xReturn; + try + { + xReturn = impl_getRowSet_throw(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "FormComponentPropertyHandler::impl_getRowSet_nothrow" ); + } + return xReturn; + } + + + void FormComponentPropertyHandler::impl_initFieldList_nothrow( std::vector< OUString >& _rFieldNames ) const + { + clearContainer( _rFieldNames ); + try + { + weld::WaitObject aWaitCursor(impl_getDefaultDialogFrame_nothrow()); + + // get the form of the control we're inspecting + Reference< XPropertySet > xFormSet( impl_getRowSet_throw(), UNO_QUERY ); + if ( !xFormSet.is() ) + return; + + OUString sObjectName; + if( ! (xFormSet->getPropertyValue( PROPERTY_COMMAND ) >>= sObjectName) ) + SAL_WARN("extensions.propctrlr", "impl_initFieldList_nothrow: unable to get property " << PROPERTY_COMMAND); + // when there is no command we don't need to ask for columns + if ( !sObjectName.isEmpty() && impl_ensureRowsetConnection_nothrow() ) + { + OUString aDatabaseName; + if( ! (xFormSet->getPropertyValue( PROPERTY_DATASOURCE ) >>= aDatabaseName) ) + SAL_WARN("extensions.propctrlr", "impl_initFieldList_nothrow: unable to get property " << PROPERTY_DATASOURCE); + sal_Int32 nObjectType = CommandType::COMMAND; + if( ! (xFormSet->getPropertyValue( PROPERTY_COMMANDTYPE ) >>= nObjectType) ) + SAL_WARN("extensions.propctrlr", "impl_initFieldList_nothrow: unable to get property " << PROPERTY_COMMANDTYPE); + + const Sequence<OUString> aNames = ::dbtools::getFieldNamesByCommandDescriptor( m_xRowSetConnection, nObjectType, sObjectName ); + _rFieldNames.insert( _rFieldNames.end(), aNames.begin(), aNames.end() ); + } + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "FormComponentPropertyHandler::impl_initFieldList_nothrow" ); + } + } + + void FormComponentPropertyHandler::impl_displaySQLError_nothrow( const ::dbtools::SQLExceptionInfo& _rErrorDescriptor ) const + { + auto pTopLevel = impl_getDefaultDialogFrame_nothrow(); + ::dbtools::showError(_rErrorDescriptor, pTopLevel ? pTopLevel->GetXWindow() : nullptr, m_xContext); + } + + bool FormComponentPropertyHandler::impl_ensureRowsetConnection_nothrow() const + { + if ( !m_xRowSetConnection.is() ) + { + uno::Reference<sdbc::XConnection> xConnection; + Any any = m_xContext->getValueByName( "ActiveConnection" ); + any >>= xConnection; + m_xRowSetConnection.reset(xConnection,::dbtools::SharedConnection::NoTakeOwnership); + } + if ( m_xRowSetConnection.is() ) + return true; + + Reference< XRowSet > xRowSet( impl_getRowSet_throw() ); + Reference< XPropertySet > xRowSetProps( xRowSet, UNO_QUERY ); + + // connect the row set - this is delegated to elsewhere - while observing errors + SQLExceptionInfo aError; + try + { + if ( xRowSetProps.is() ) + { + weld::WaitObject aWaitCursor(impl_getDefaultDialogFrame_nothrow()); + m_xRowSetConnection = ::dbtools::ensureRowSetConnection( xRowSet, m_xContext, nullptr ); + } + } + catch ( const SQLException& ) { aError = SQLExceptionInfo( ::cppu::getCaughtException() ); } + catch ( const WrappedTargetException& e ) { aError = SQLExceptionInfo( e.TargetException ); } + catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); } + + // report errors, if necessary + if ( aError.isValid() ) + { + OUString sDataSourceName; + try + { + xRowSetProps->getPropertyValue( PROPERTY_DATASOURCE ) >>= sDataSourceName; + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "FormComponentPropertyHandler::impl_ensureRowsetConnection_nothrow: caught an exception during error handling!" ); + } + // additional info about what happened + INetURLObject aParser( sDataSourceName ); + if ( aParser.GetProtocol() != INetProtocol::NotValid ) + sDataSourceName = aParser.getBase( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ); + OUString sInfo(PcrRes(RID_STR_UNABLETOCONNECT).replaceAll("$name$", sDataSourceName)); + SQLContext aContext(sInfo, {}, {}, 0, aError.get(), {}); + impl_displaySQLError_nothrow( aContext ); + } + + return m_xRowSetConnection.is(); + } + + + void FormComponentPropertyHandler::impl_describeCursorSource_nothrow( LineDescriptor& _out_rProperty, const Reference< XPropertyControlFactory >& _rxControlFactory ) const + { + try + { + weld::WaitObject aWaitCursor(impl_getDefaultDialogFrame_nothrow()); + + + // Set the UI data + _out_rProperty.DisplayName = m_pInfoService->getPropertyTranslation( PROPERTY_ID_COMMAND ); + + _out_rProperty.HelpURL = HelpIdUrl::getHelpURL( m_pInfoService->getPropertyHelpId( PROPERTY_ID_COMMAND ) ); + _out_rProperty.PrimaryButtonId = UID_PROP_DLG_SQLCOMMAND; + + + sal_Int32 nCommandType = CommandType::COMMAND; + impl_getPropertyValue_throw( PROPERTY_COMMANDTYPE ) >>= nCommandType; + + switch ( nCommandType ) + { + case CommandType::TABLE: + case CommandType::QUERY: + { + std::vector< OUString > aNames; + if ( impl_ensureRowsetConnection_nothrow() ) + { + if ( nCommandType == CommandType::TABLE ) + impl_fillTableNames_throw( aNames ); + else + impl_fillQueryNames_throw( aNames ); + } + _out_rProperty.Control = PropertyHandlerHelper::createComboBoxControl( _rxControlFactory, std::move(aNames), true ); + } + break; + + default: + _out_rProperty.Control = _rxControlFactory->createPropertyControl( PropertyControlType::MultiLineTextField, false ); + break; + } + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "FormComponentPropertyHandler::impl_describeCursorSource_nothrow"); + } + } + + + void FormComponentPropertyHandler::impl_fillTableNames_throw( std::vector< OUString >& _out_rNames ) const + { + OSL_PRECOND( m_xRowSetConnection.is(), "FormComponentPropertyHandler::impl_fillTableNames_throw: need a connection!" ); + _out_rNames.resize( 0 ); + + Reference< XTablesSupplier > xSupplyTables( m_xRowSetConnection, UNO_QUERY ); + Reference< XNameAccess > xTableNames; + if ( xSupplyTables.is() ) + xTableNames = xSupplyTables->getTables(); + DBG_ASSERT( xTableNames.is(), "FormComponentPropertyHandler::impl_fillTableNames_throw: no way to obtain the tables of the connection!" ); + if ( !xTableNames.is() ) + return; + + const Sequence<OUString> aNames = xTableNames->getElementNames(); + _out_rNames.insert( _out_rNames.end(), aNames.begin(), aNames.end() ); + } + + + void FormComponentPropertyHandler::impl_fillQueryNames_throw( std::vector< OUString >& _out_rNames ) const + { + OSL_PRECOND( m_xRowSetConnection.is(), "FormComponentPropertyHandler::impl_fillQueryNames_throw: need a connection!" ); + _out_rNames.resize( 0 ); + + Reference< XQueriesSupplier > xSupplyQueries( m_xRowSetConnection, UNO_QUERY ); + Reference< XNameAccess > xQueryNames; + if ( xSupplyQueries.is() ) + { + xQueryNames = xSupplyQueries->getQueries(); + impl_fillQueryNames_throw(xQueryNames,_out_rNames); + } + } + + void FormComponentPropertyHandler::impl_fillQueryNames_throw( const Reference< XNameAccess >& _xQueryNames,std::vector< OUString >& _out_rNames,std::u16string_view _sName ) const + { + DBG_ASSERT( _xQueryNames.is(), "FormComponentPropertyHandler::impl_fillQueryNames_throw: no way to obtain the queries of the connection!" ); + if ( !_xQueryNames.is() ) + return; + + bool bAdd = !_sName.empty(); + + const Sequence<OUString> aQueryNames =_xQueryNames->getElementNames(); + for ( const OUString& rQueryName : aQueryNames ) + { + OUStringBuffer sTemp; + if ( bAdd ) + { + sTemp.append(OUString::Concat(_sName) + "/"); + } + sTemp.append(rQueryName); + Reference< XNameAccess > xSubQueries(_xQueryNames->getByName(rQueryName),UNO_QUERY); + if ( xSubQueries.is() ) + impl_fillQueryNames_throw(xSubQueries,_out_rNames,sTemp); + else + _out_rNames.push_back( sTemp.makeStringAndClear() ); + } + } + + + void FormComponentPropertyHandler::impl_describeListSourceUI_throw( LineDescriptor& _out_rDescriptor, const Reference< XPropertyControlFactory >& _rxControlFactory ) const + { + OSL_PRECOND( m_xComponent.is(), "FormComponentPropertyHandler::impl_describeListSourceUI_throw: no component!" ); + + + // read out ListSourceTypes + Any aListSourceType( m_xComponent->getPropertyValue( PROPERTY_LISTSOURCETYPE ) ); + + sal_Int32 nListSourceType = sal_Int32(ListSourceType_VALUELIST); + ::cppu::enum2int( nListSourceType, aListSourceType ); + ListSourceType eListSourceType = static_cast<ListSourceType>(nListSourceType); + + _out_rDescriptor.DisplayName = m_pInfoService->getPropertyTranslation( PROPERTY_ID_LISTSOURCE ); + _out_rDescriptor.HelpURL = HelpIdUrl::getHelpURL( m_pInfoService->getPropertyHelpId( PROPERTY_ID_LISTSOURCE ) ); + + + // set enums + switch( eListSourceType ) + { + case ListSourceType_VALUELIST: + _out_rDescriptor.Control = _rxControlFactory->createPropertyControl( PropertyControlType::StringListField, false ); + break; + + case ListSourceType_TABLEFIELDS: + case ListSourceType_TABLE: + case ListSourceType_QUERY: + { + std::vector< OUString > aListEntries; + if ( impl_ensureRowsetConnection_nothrow() ) + { + if ( eListSourceType == ListSourceType_QUERY ) + impl_fillQueryNames_throw( aListEntries ); + else + impl_fillTableNames_throw( aListEntries ); + } + _out_rDescriptor.Control = PropertyHandlerHelper::createComboBoxControl( _rxControlFactory, std::move(aListEntries), false ); + } + break; + case ListSourceType_SQL: + case ListSourceType_SQLPASSTHROUGH: + impl_ensureRowsetConnection_nothrow(); + _out_rDescriptor.HasPrimaryButton = m_xRowSetConnection.is(); + break; + default: break; + } + } + + bool FormComponentPropertyHandler::impl_dialogListSelection_nothrow( const OUString& _rProperty, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const + { + OSL_PRECOND(m_pInfoService, "FormComponentPropertyHandler::impl_dialogListSelection_" + "nothrow: no property meta data!"); + + OUString sPropertyUIName( m_pInfoService->getPropertyTranslation( m_pInfoService->getPropertyId( _rProperty ) ) ); + ListSelectionDialog aDialog(impl_getDefaultDialogFrame_nothrow(), m_xComponent, _rProperty, sPropertyUIName); + _rClearBeforeDialog.clear(); + return ( RET_OK == aDialog.run() ); + } + + bool FormComponentPropertyHandler::impl_dialogFilterOrSort_nothrow( bool _bFilter, OUString& _out_rSelectedClause, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const + { + OSL_PRECOND( Reference< XRowSet >( m_xComponent, UNO_QUERY ).is(), + "FormComponentPropertyHandler::impl_dialogFilterOrSort_nothrow: to be called for forms only!" ); + + _out_rSelectedClause.clear(); + bool bSuccess = false; + SQLExceptionInfo aErrorInfo; + try + { + if ( !impl_ensureRowsetConnection_nothrow() ) + return false; + + // get a composer for the statement which the form is currently based on + Reference< XSingleSelectQueryComposer > xComposer( ::dbtools::getCurrentSettingsComposer( m_xComponent, m_xContext, nullptr ) ); + OSL_ENSURE( xComposer.is(), "FormComponentPropertyHandler::impl_dialogFilterOrSort_nothrow: could not obtain a composer!" ); + if ( !xComposer.is() ) + return false; + + OUString sPropertyUIName( m_pInfoService->getPropertyTranslation( _bFilter ? PROPERTY_ID_FILTER : PROPERTY_ID_SORT ) ); + + // create the dialog + Reference< XExecutableDialog > xDialog; + if ( _bFilter) + { + xDialog.set( sdb::FilterDialog::createDefault(m_xContext) ); + } + else + { + xDialog.set( sdb::OrderDialog::createDefault(m_xContext) ); + } + + + // initialize the dialog + Reference< XPropertySet > xDialogProps( xDialog, UNO_QUERY_THROW ); + xDialogProps->setPropertyValue("QueryComposer", Any( xComposer ) ); + xDialogProps->setPropertyValue("RowSet", Any( m_xComponent ) ); + if (auto pTopLevel = impl_getDefaultDialogFrame_nothrow()) + xDialogProps->setPropertyValue("ParentWindow", Any(pTopLevel->GetXWindow())); + xDialogProps->setPropertyValue("Title", Any( sPropertyUIName ) ); + + _rClearBeforeDialog.clear(); + bSuccess = ( xDialog->execute() != 0 ); + if ( bSuccess ) + _out_rSelectedClause = _bFilter ? xComposer->getFilter() : xComposer->getOrder(); + } + catch (const SQLContext& e) { aErrorInfo = e; } + catch (const SQLWarning& e) { aErrorInfo = e; } + catch (const SQLException& e) { aErrorInfo = e; } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "FormComponentPropertyHandler::impl_dialogFilterOrSort_nothrow" ); + } + + if ( aErrorInfo.isValid() ) + impl_displaySQLError_nothrow( aErrorInfo ); + + return bSuccess; + } + + + bool FormComponentPropertyHandler::impl_dialogLinkedFormFields_nothrow( ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const + { + Reference< XForm > xDetailForm( m_xComponent, UNO_QUERY ); + Reference< XForm > xMasterForm( m_xObjectParent, UNO_QUERY ); + uno::Reference<beans::XPropertySet> xMasterProp(m_xObjectParent,uno::UNO_QUERY); + OSL_PRECOND( xDetailForm.is() && xMasterForm.is(), "FormComponentPropertyHandler::impl_dialogLinkedFormFields_nothrow: no forms!" ); + if ( !xDetailForm.is() || !xMasterForm.is() ) + return false; + + FormLinkDialog aDialog(impl_getDefaultDialogFrame_nothrow(), m_xComponent, xMasterProp, m_xContext); + _rClearBeforeDialog.clear(); + return ( RET_OK == aDialog.run() ); + } + + bool FormComponentPropertyHandler::impl_dialogFormatting_nothrow( Any& _out_rNewValue, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const + { + bool bChanged = false; + try + { + // create the itemset for the dialog + SfxItemSet aCoreSet( + SfxGetpApp()->GetPool(), + svl::Items< + SID_ATTR_NUMBERFORMAT_VALUE, SID_ATTR_NUMBERFORMAT_INFO>); + // ripped this somewhere ... don't understand it :( + + // get the number formats supplier + Reference< XNumberFormatsSupplier > xSupplier; + m_xComponent->getPropertyValue( PROPERTY_FORMATSSUPPLIER ) >>= xSupplier; + + DBG_ASSERT(xSupplier.is(), "FormComponentPropertyHandler::impl_dialogFormatting_nothrow: invalid call !" ); + Reference< XUnoTunnel > xTunnel( xSupplier, UNO_QUERY_THROW ); + SvNumberFormatsSupplierObj* pSupplier = + reinterpret_cast< SvNumberFormatsSupplierObj* >( xTunnel->getSomething( SvNumberFormatsSupplierObj::getUnoTunnelId() ) ); + DBG_ASSERT( pSupplier != nullptr, "FormComponentPropertyHandler::impl_dialogFormatting_nothrow: invalid call !" ); + + sal_Int32 nFormatKey = 0; + impl_getPropertyValue_throw( PROPERTY_FORMATKEY ) >>= nFormatKey; + aCoreSet.Put( SfxUInt32Item( SID_ATTR_NUMBERFORMAT_VALUE, nFormatKey ) ); + + SvNumberFormatter* pFormatter = pSupplier->GetNumberFormatter(); + double dPreviewVal = OFormatSampleControl::getPreviewValue(pFormatter,nFormatKey); + SvxNumberInfoItem aFormatter( pFormatter, dPreviewVal, PcrRes(RID_STR_TEXT_FORMAT), SID_ATTR_NUMBERFORMAT_INFO ); + aCoreSet.Put( aFormatter ); + + // a tab dialog with a single page + SfxSingleTabDialogController aDialog(impl_getDefaultDialogFrame_nothrow(), &aCoreSet, + "cui/ui/formatnumberdialog.ui", "FormatNumberDialog"); + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + ::CreateTabPage fnCreatePage = pFact->GetTabPageCreatorFunc( RID_SVXPAGE_NUMBERFORMAT ); + if ( !fnCreatePage ) + throw RuntimeException(); // caught below + + aDialog.SetTabPage((*fnCreatePage)(aDialog.get_content_area(), &aDialog, &aCoreSet)); + + _rClearBeforeDialog.clear(); + if ( RET_OK == aDialog.run() ) + { + const SfxItemSet* pResult = aDialog.GetOutputItemSet(); + + if (const SvxNumberInfoItem* pInfoItem = pResult->GetItem( SID_ATTR_NUMBERFORMAT_INFO )) + { + for (sal_uInt32 key : pInfoItem->GetDelFormats()) + pFormatter->DeleteEntry(key); + } + + if ( const SfxUInt32Item* pItem = pResult->GetItemIfSet( SID_ATTR_NUMBERFORMAT_VALUE, false ) ) + { + _out_rNewValue <<= static_cast<sal_Int32>( pItem->GetValue() ); + bChanged = true; + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "FormComponentPropertyHandler::impl_dialogFormatting_nothrow" ); + } + return bChanged; + } + + bool FormComponentPropertyHandler::impl_browseForImage_nothrow( Any& _out_rNewValue, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const + { + bool bIsLink = true;// reflect the legacy behavior + OUString aStrTrans = m_pInfoService->getPropertyTranslation( PROPERTY_ID_IMAGE_URL ); + + weld::Window* pWin = impl_getDefaultDialogFrame_nothrow(); + ::sfx2::FileDialogHelper aFileDlg( + ui::dialogs::TemplateDescription::FILEOPEN_LINK_PREVIEW, + FileDialogFlags::Graphic, pWin); + aFileDlg.SetContext(sfx2::FileDialogHelper::FormsInsertImage); + aFileDlg.SetTitle(aStrTrans); + // non-linked images ( e.g. those located in the document + // stream ) only if document is available + bool bHandleNonLink; + { + Reference< XModel > xModel( impl_getContextDocument_nothrow() ); + bHandleNonLink = xModel.is(); + // Not implemented in reports + if (bHandleNonLink) + { + Reference< XReportDefinition > xReportDef( xModel, css::uno::UNO_QUERY ); + bHandleNonLink = !xReportDef.is(); + } + } + + Reference< XFilePickerControlAccess > xController(aFileDlg.GetFilePicker(), UNO_QUERY); + DBG_ASSERT(xController.is(), "FormComponentPropertyHandler::impl_browseForImage_nothrow: missing the controller interface on the file picker!"); + if (xController.is()) + { + // do a preview by default + xController->setValue(ExtendedFilePickerElementIds::CHECKBOX_PREVIEW, 0, css::uno::Any(true)); + + xController->setValue(ExtendedFilePickerElementIds::CHECKBOX_LINK, 0, css::uno::Any(bIsLink)); + xController->enableControl(ExtendedFilePickerElementIds::CHECKBOX_LINK, bHandleNonLink ); + + } + + OUString sCurValue; + if( ! (impl_getPropertyValue_throw( PROPERTY_IMAGE_URL ) >>= sCurValue) ) + SAL_WARN("extensions.propctrlr", "impl_browseForImage_nothrow: unable to get property " << PROPERTY_IMAGE_URL); + if (!sCurValue.isEmpty()) + { + aFileDlg.SetDisplayDirectory( sCurValue ); + // TODO: need to set the display directory _and_ the default name + } + + _rClearBeforeDialog.clear(); + bool bSuccess = ( ERRCODE_NONE == aFileDlg.Execute() ); + if ( bSuccess ) + { + if ( bHandleNonLink && xController.is() ) + { + xController->getValue(ExtendedFilePickerElementIds::CHECKBOX_LINK, 0) >>= bIsLink; + } + if ( !bIsLink ) + { + Graphic aGraphic; + aFileDlg.GetGraphic(aGraphic); + + Reference< graphic::XGraphicObject > xGrfObj = graphic::GraphicObject::create( m_xContext ); + xGrfObj->setGraphic( aGraphic.GetXGraphic() ); + + _out_rNewValue <<= xGrfObj; + + } + else + _out_rNewValue <<= aFileDlg.GetPath(); + } + return bSuccess; + } + + bool FormComponentPropertyHandler::impl_browseForTargetURL_nothrow( Any& _out_rNewValue, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const + { + weld::Window* pWin = impl_getDefaultDialogFrame_nothrow(); + ::sfx2::FileDialogHelper aFileDlg( + ui::dialogs::TemplateDescription::FILEOPEN_READONLY_VERSION, + FileDialogFlags::NONE, pWin); + + OUString sURL; + if( ! (impl_getPropertyValue_throw( PROPERTY_TARGET_URL ) >>= sURL) ) + SAL_WARN("extensions.propctrlr", "impl_browseForTargetURL_nothrow: unable to get property " << PROPERTY_TARGET_URL); + INetURLObject aParser( sURL ); + if ( INetProtocol::File == aParser.GetProtocol() ) + // set the initial directory only for file-URLs. Everything else + // is considered to be potentially expensive + aFileDlg.SetDisplayDirectory( sURL ); + + _rClearBeforeDialog.clear(); + bool bSuccess = ( ERRCODE_NONE == aFileDlg.Execute() ); + if ( bSuccess ) + _out_rNewValue <<= aFileDlg.GetPath(); + return bSuccess; + } + + bool FormComponentPropertyHandler::impl_executeFontDialog_nothrow( Any& _out_rNewValue, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const + { + bool bSuccess = false; + + // create an item set for use with the dialog + std::unique_ptr<SfxItemSet> pSet; + rtl::Reference<SfxItemPool> pPool; + std::vector<SfxPoolItem*>* pDefaults = nullptr; + ControlCharacterDialog::createItemSet(pSet, pPool, pDefaults); + ControlCharacterDialog::translatePropertiesToItems(m_xComponent, pSet.get()); + + { // do this in an own block. The dialog needs to be destroyed before we call + // destroyItemSet + ControlCharacterDialog aDlg(impl_getDefaultDialogFrame_nothrow(), *pSet); + _rClearBeforeDialog.clear(); + if (RET_OK == aDlg.run()) + { + const SfxItemSet* pOut = aDlg.GetOutputItemSet(); + if ( pOut ) + { + std::vector< NamedValue > aFontPropertyValues; + ControlCharacterDialog::translateItemsToProperties( *pOut, aFontPropertyValues ); + _out_rNewValue <<= comphelper::containerToSequence(aFontPropertyValues); + bSuccess = true; + } + } + } + + ControlCharacterDialog::destroyItemSet(pSet, pPool, pDefaults); + return bSuccess; + } + + + bool FormComponentPropertyHandler::impl_browseForDatabaseDocument_throw( Any& _out_rNewValue, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const + { + weld::Window* pWin = impl_getDefaultDialogFrame_nothrow(); + ::sfx2::FileDialogHelper aFileDlg( + ui::dialogs::TemplateDescription::FILEOPEN_READONLY_VERSION, FileDialogFlags::NONE, + "sdatabase", SfxFilterFlags::NONE, SfxFilterFlags::NONE, pWin); + + OUString sDataSource; + if( ! (impl_getPropertyValue_throw( PROPERTY_DATASOURCE ) >>= sDataSource) ) + SAL_WARN("extensions.propctrlr", "impl_browseForDatabaseDocument_throw: unable to get property " << PROPERTY_DATASOURCE); + INetURLObject aParser( sDataSource ); + if ( INetProtocol::File == aParser.GetProtocol() ) + // set the initial directory only for file-URLs. Everything else + // is considered to be potentially expensive + aFileDlg.SetDisplayDirectory( sDataSource ); + + std::shared_ptr<const SfxFilter> pFilter = SfxFilter::GetFilterByName("StarOffice XML (Base)"); + OSL_ENSURE(pFilter,"Filter: StarOffice XML (Base) could not be found!"); + if ( pFilter ) + { + aFileDlg.SetCurrentFilter(pFilter->GetUIName()); + //aFileDlg.AddFilter(pFilter->GetFilterName(),pFilter->GetDefaultExtension()); + } + + _rClearBeforeDialog.clear(); + bool bSuccess = ( ERRCODE_NONE == aFileDlg.Execute() ); + if ( bSuccess ) + _out_rNewValue <<= aFileDlg.GetPath(); + return bSuccess; + } + + bool FormComponentPropertyHandler::impl_dialogColorChooser_throw( sal_Int32 _nColorPropertyId, Any& _out_rNewValue, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const + { + ::Color aColor; + if( ! (impl_getPropertyValue_throw( impl_getPropertyNameFromId_nothrow( _nColorPropertyId )) >>= aColor) ) + SAL_WARN("extensions.propctrlr", "impl_dialogColorChooser_throw: unable to get property " << _nColorPropertyId); + SvColorDialog aColorDlg; + aColorDlg.SetColor( aColor ); + + _rClearBeforeDialog.clear(); + weld::Window* pParent = impl_getDefaultDialogFrame_nothrow(); + if (!aColorDlg.Execute(pParent)) + return false; + + _out_rNewValue <<= aColorDlg.GetColor(); + return true; + } + + bool FormComponentPropertyHandler::impl_dialogChooseLabelControl_nothrow( Any& _out_rNewValue, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const + { + weld::Window* pParent = impl_getDefaultDialogFrame_nothrow(); + OSelectLabelDialog dlgSelectLabel(pParent, m_xComponent); + _rClearBeforeDialog.clear(); + bool bSuccess = (RET_OK == dlgSelectLabel.run()); + if ( bSuccess ) + _out_rNewValue <<= dlgSelectLabel.GetSelected(); + return bSuccess; + } + + + Reference< XControlContainer > FormComponentPropertyHandler::impl_getContextControlContainer_nothrow() const + { + Reference< XControlContainer > xControlContext; + Any any = m_xContext->getValueByName( "ControlContext" ); + any >>= xControlContext; + return xControlContext; + } + + + bool FormComponentPropertyHandler::impl_dialogChangeTabOrder_nothrow( ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const + { + OSL_PRECOND( impl_getContextControlContainer_nothrow().is(), "FormComponentPropertyHandler::impl_dialogChangeTabOrder_nothrow: invalid control context!" ); + + Reference< XTabControllerModel > xTabControllerModel( impl_getRowSet_nothrow(), UNO_QUERY ); + TabOrderDialog aDialog(impl_getDefaultDialogFrame_nothrow(), xTabControllerModel, + impl_getContextControlContainer_nothrow(), m_xContext); + _rClearBeforeDialog.clear(); + return RET_OK == aDialog.run(); + } + + namespace + { + + //- ISQLCommandPropertyUI + + class ISQLCommandPropertyUI : public ISQLCommandAdapter + { + public: + /** returns the empty-string-terminated list of names of properties + whose UI is to be disabled while the SQL command property is + being edited. + */ + virtual OUString* getPropertiesToDisable() = 0; + }; + + + //- SQLCommandPropertyUI + + class SQLCommandPropertyUI : public ISQLCommandPropertyUI + { + protected: + explicit SQLCommandPropertyUI( const Reference< XPropertySet >& _rxObject ) + : m_xObject(_rxObject) + { + if ( !m_xObject.is() ) + throw NullPointerException(); + } + + protected: + Reference< XPropertySet > m_xObject; + }; + + + //- FormSQLCommandUI - declaration + + class FormSQLCommandUI : public SQLCommandPropertyUI + { + public: + explicit FormSQLCommandUI( const Reference< XPropertySet >& _rxForm ); + + // ISQLCommandAdapter + virtual OUString getSQLCommand() const override; + virtual bool getEscapeProcessing() const override; + virtual void setSQLCommand( const OUString& _rCommand ) const override; + virtual void setEscapeProcessing( const bool _bEscapeProcessing ) const override; + + // ISQLCommandPropertyUI + virtual OUString* getPropertiesToDisable() override; + }; + + + //- FormSQLCommandUI - implementation + + + FormSQLCommandUI::FormSQLCommandUI( const Reference< XPropertySet >& _rxForm ) + :SQLCommandPropertyUI( _rxForm ) + { + } + + + OUString FormSQLCommandUI::getSQLCommand() const + { + OUString sCommand; + if( ! (m_xObject->getPropertyValue( PROPERTY_COMMAND ) >>= sCommand) ) + SAL_WARN("extensions.propctrlr", "getSQLCommand: unable to get property " << PROPERTY_COMMAND); + return sCommand; + } + + + bool FormSQLCommandUI::getEscapeProcessing() const + { + bool bEscapeProcessing( false ); + if( ! (m_xObject->getPropertyValue( PROPERTY_ESCAPE_PROCESSING ) >>= bEscapeProcessing) ) + SAL_WARN("extensions.propctrlr", "getSQLCommand: unable to get property " << PROPERTY_ESCAPE_PROCESSING); + return bEscapeProcessing; + } + + + void FormSQLCommandUI::setSQLCommand( const OUString& _rCommand ) const + { + m_xObject->setPropertyValue( PROPERTY_COMMAND, Any( _rCommand ) ); + } + + + void FormSQLCommandUI::setEscapeProcessing( const bool _bEscapeProcessing ) const + { + m_xObject->setPropertyValue( PROPERTY_ESCAPE_PROCESSING, Any( _bEscapeProcessing ) ); + } + + + OUString* FormSQLCommandUI::getPropertiesToDisable() + { + static OUString s_aCommandProps[] = { + PROPERTY_DATASOURCE, + PROPERTY_COMMAND, + PROPERTY_COMMANDTYPE, + PROPERTY_ESCAPE_PROCESSING, + OUString() + }; + return s_aCommandProps; + } + + //- ValueListCommandUI - declaration + + class ValueListCommandUI : public SQLCommandPropertyUI + { + public: + explicit ValueListCommandUI( const Reference< XPropertySet >& _rxListOrCombo ); + + // ISQLCommandAdapter + virtual OUString getSQLCommand() const override; + virtual bool getEscapeProcessing() const override; + virtual void setSQLCommand( const OUString& _rCommand ) const override; + virtual void setEscapeProcessing( const bool _bEscapeProcessing ) const override; + + // ISQLCommandPropertyUI + virtual OUString* getPropertiesToDisable() override; + private: + mutable bool m_bPropertyValueIsList; + }; + + + //- ValueListCommandUI - implementation + + + ValueListCommandUI::ValueListCommandUI( const Reference< XPropertySet >& _rxListOrCombo ) + :SQLCommandPropertyUI( _rxListOrCombo ) + ,m_bPropertyValueIsList( false ) + { + } + + + OUString ValueListCommandUI::getSQLCommand() const + { + OUString sValue; + m_bPropertyValueIsList = false; + + // for combo boxes, the property is a mere string + Any aValue( m_xObject->getPropertyValue( PROPERTY_LISTSOURCE ) ); + if ( aValue >>= sValue ) + return sValue; + + Sequence< OUString > aValueList; + if ( aValue >>= aValueList ) + { + m_bPropertyValueIsList = true; + if ( aValueList.hasElements() ) + sValue = aValueList[0]; + return sValue; + } + + OSL_FAIL( "ValueListCommandUI::getSQLCommand: unexpected property type!" ); + return sValue; + } + + + bool ValueListCommandUI::getEscapeProcessing() const + { + ListSourceType eType = ListSourceType_SQL; + if( ! (m_xObject->getPropertyValue( PROPERTY_LISTSOURCETYPE ) >>= eType) ) + SAL_WARN("extensions.propctrlr", "getEscapeProcessing: unable to get property " << PROPERTY_LISTSOURCETYPE); + OSL_ENSURE( ( eType == ListSourceType_SQL ) || ( eType == ListSourceType_SQLPASSTHROUGH ), + "ValueListCommandUI::getEscapeProcessing: unexpected list source type!" ); + return ( eType == ListSourceType_SQL ); + } + + + void ValueListCommandUI::setSQLCommand( const OUString& _rCommand ) const + { + Any aValue; + if ( m_bPropertyValueIsList ) + aValue <<= Sequence< OUString >( &_rCommand, 1 ); + else + aValue <<= _rCommand; + m_xObject->setPropertyValue( PROPERTY_LISTSOURCE, aValue ); + } + + + void ValueListCommandUI::setEscapeProcessing( const bool _bEscapeProcessing ) const + { + m_xObject->setPropertyValue( PROPERTY_LISTSOURCETYPE, Any( + _bEscapeProcessing ? ListSourceType_SQL : ListSourceType_SQLPASSTHROUGH ) ); + } + + + OUString* ValueListCommandUI::getPropertiesToDisable() + { + static OUString s_aListSourceProps[] = { + PROPERTY_LISTSOURCETYPE, + PROPERTY_LISTSOURCE, + OUString() + }; + return s_aListSourceProps; + } + } + + + bool FormComponentPropertyHandler::impl_doDesignSQLCommand_nothrow( const Reference< XObjectInspectorUI >& _rxInspectorUI, PropertyId _nDesignForProperty ) + { + try + { + if ( m_xCommandDesigner.is() ) + { + if ( m_xCommandDesigner->isActive() ) + { + m_xCommandDesigner->raise(); + return true; + } + m_xCommandDesigner->dispose(); + m_xCommandDesigner.clear(); + } + + if ( !impl_ensureRowsetConnection_nothrow() ) + return false; + + Reference< XPropertySet > xComponentProperties( m_xComponent, UNO_SET_THROW ); + + ::rtl::Reference< ISQLCommandPropertyUI > xCommandUI; + switch ( _nDesignForProperty ) + { + case PROPERTY_ID_COMMAND: + xCommandUI = new FormSQLCommandUI( xComponentProperties ); + break; + case PROPERTY_ID_LISTSOURCE: + xCommandUI = new ValueListCommandUI( xComponentProperties ); + break; + default: + OSL_FAIL( "FormComponentPropertyHandler::OnDesignerClosed: invalid property id!" ); + return false; + } + + m_xCommandDesigner.set( new SQLCommandDesigner( m_xContext, xCommandUI, m_xRowSetConnection, LINK( this, FormComponentPropertyHandler, OnDesignerClosed ) ) ); + + DBG_ASSERT( _rxInspectorUI.is(), "FormComponentPropertyHandler::OnDesignerClosed: no access to the property browser ui!" ); + if ( m_xCommandDesigner->isActive() && _rxInspectorUI.is() ) + { + m_xBrowserUI = _rxInspectorUI; + // disable everything which would affect this property + const OUString* pToDisable = xCommandUI->getPropertiesToDisable(); + while ( !pToDisable->isEmpty() ) + { + m_xBrowserUI->enablePropertyUIElements( *pToDisable++, PropertyLineElement::All, false ); + } + + // but enable the browse button for the property itself - so it can be used to raise the query designer + OUString sPropertyName( impl_getPropertyNameFromId_nothrow( _nDesignForProperty ) ); + m_xBrowserUI->enablePropertyUIElements( sPropertyName, PropertyLineElement::PrimaryButton, true ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + return m_xCommandDesigner.is(); + } + + + IMPL_LINK_NOARG( FormComponentPropertyHandler, OnDesignerClosed, SQLCommandDesigner&, void ) + { + OSL_ENSURE( m_xBrowserUI.is() && m_xCommandDesigner.is(), "FormComponentPropertyHandler::OnDesignerClosed: too many NULLs!" ); + if ( !(m_xBrowserUI.is() && m_xCommandDesigner.is()) ) + return; + + try + { + ::rtl::Reference< ISQLCommandPropertyUI > xCommandUI( + dynamic_cast< ISQLCommandPropertyUI* >( m_xCommandDesigner->getPropertyAdapter().get() ) ); + if ( !xCommandUI.is() ) + throw NullPointerException(); + + const OUString* pToEnable = xCommandUI->getPropertiesToDisable(); + while ( !pToEnable->isEmpty() ) + { + m_xBrowserUI->enablePropertyUIElements( *pToEnable++, PropertyLineElement::All, true ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + + bool FormComponentPropertyHandler::impl_hasValidDataSourceSignature_nothrow( const Reference< XPropertySet >& _xFormProperties, bool _bAllowEmptyDataSourceName ) + { + bool bHas = false; + if ( _xFormProperties.is() ) + { + try + { + OUString sPropertyValue; + // first, we need the name of an existent data source + if ( _xFormProperties->getPropertySetInfo()->hasPropertyByName(PROPERTY_DATASOURCE) ) + _xFormProperties->getPropertyValue( PROPERTY_DATASOURCE ) >>= sPropertyValue; + bHas = ( !sPropertyValue.isEmpty() ) || _bAllowEmptyDataSourceName; + + // then, the command should not be empty + if ( bHas ) + { + if ( _xFormProperties->getPropertySetInfo()->hasPropertyByName(PROPERTY_COMMAND) ) + _xFormProperties->getPropertyValue( PROPERTY_COMMAND ) >>= sPropertyValue; + bHas = !sPropertyValue.isEmpty(); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "FormComponentPropertyHandler::impl_hasValidDataSourceSignature_nothrow" ); + } + } + return bHas; + } + + OUString FormComponentPropertyHandler::impl_getDocumentURL_nothrow() const + { + OUString sURL; + try + { + Reference< XModel > xDocument( impl_getContextDocument_nothrow() ); + if ( xDocument.is() ) + sURL = xDocument->getURL(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + return sURL; + } + + ::cppu::IPropertyArrayHelper* FormComponentPropertyHandler::createArrayHelper( ) const + { + uno::Sequence< beans::Property > aProps; + describeProperties(aProps); + return new ::cppu::OPropertyArrayHelper(aProps); + + } + + ::cppu::IPropertyArrayHelper & FormComponentPropertyHandler::getInfoHelper() + { + return *getArrayHelper(); + } + + uno::Reference< beans::XPropertySetInfo > SAL_CALL FormComponentPropertyHandler::getPropertySetInfo( ) + { + return ::cppu::OPropertySetHelper::createPropertySetInfo(getInfoHelper()); + } + + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_FormComponentPropertyHandler_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::FormComponentPropertyHandler(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/formcomponenthandler.hxx b/extensions/source/propctrlr/formcomponenthandler.hxx new file mode 100644 index 0000000000..bc7367abbe --- /dev/null +++ b/extensions/source/propctrlr/formcomponenthandler.hxx @@ -0,0 +1,435 @@ +/* -*- 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 . + */ + +#pragma once + +#include <memory> +#include "propertyhandler.hxx" +#include "sqlcommanddesign.hxx" +#include <comphelper/uno3.hxx> +#include <comphelper/proparrhlp.hxx> +#include <comphelper/propertycontainer.hxx> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/sdbc/XRowSet.hpp> +#include <com/sun/star/awt/XControlContainer.hpp> +#include <connectivity/dbtools.hxx> + +#include <set> + + +namespace pcr +{ + + + //= ComponentClassification + + enum ComponentClassification + { + eFormControl, + eDialogControl, + eUnknown + }; + + + //= FormComponentPropertyHandler + + class FormComponentPropertyHandler; + typedef ::comphelper::OPropertyArrayUsageHelper<FormComponentPropertyHandler> FormComponentPropertyHandler_PROP; + /** default ->XPropertyHandler for all form components. + */ + class FormComponentPropertyHandler : public PropertyHandlerComponent, + public ::comphelper::OPropertyContainer, + public FormComponentPropertyHandler_PROP + { + private: + /// access to property states + css::uno::Reference< css::beans::XPropertyState > m_xPropertyState; + /// the parent of our component + css::uno::Reference< css::uno::XInterface > m_xObjectParent; + + /// the database connection. Owned by us if and only if we created it ourself. + mutable ::dbtools::SharedConnection m_xRowSetConnection; + css::uno::Reference< css::sdbc::XRowSet > m_xRowSet; + /** helper component encapsulating the handling for the QueryDesign component for + interactively designing an SQL command + */ + ::rtl::Reference< SQLCommandDesigner > m_xCommandDesigner; + css::uno::Reference< css::inspection::XObjectInspectorUI > m_xBrowserUI; + + /// the string indicating a "default" (VOID) value in list-like controls + OUString m_sDefaultValueString; + /// all properties to whose control's we added ->m_sDefaultValueString + std::set< OUString > m_aPropertiesWithDefListEntry; + /// type of our component + ComponentClassification m_eComponentClass; + /// is our component a (database) sub form? + bool m_bComponentIsSubForm : 1; + /// our component has a "ListSource" property + bool m_bHaveListSource : 1; + /// our component has a "Command" property + bool m_bHaveCommand : 1; + /// the class id of the component - if applicable + sal_Int16 m_nClassId; + + public: + explicit FormComponentPropertyHandler( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext + ); + + DECLARE_XINTERFACE( ) + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + + protected: + virtual ~FormComponentPropertyHandler() override; + + protected: + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames () override; + + virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const override; + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; + // XPropertyHandler overridables + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& _rPropertyName ) override; + virtual void SAL_CALL setPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rValue ) override; + virtual css::uno::Any SAL_CALL convertToPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rControlValue ) override; + virtual css::uno::Any SAL_CALL convertToControlValue( const OUString& _rPropertyName, const css::uno::Any& _rPropertyValue, const css::uno::Type& _rControlValueType ) override; + virtual css::beans::PropertyState SAL_CALL getPropertyState( const OUString& _rPropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupersededProperties() override; + virtual css::uno::Sequence< OUString > SAL_CALL getActuatingProperties() override; + virtual css::inspection::LineDescriptor SAL_CALL describePropertyLine( const OUString& _rPropertyName, const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory ) override; + virtual css::inspection::InteractiveSelectionResult + SAL_CALL onInteractivePropertySelection( const OUString& _rPropertyName, sal_Bool _bPrimary, css::uno::Any& _rData, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI ) override; + virtual void SAL_CALL actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const css::uno::Any& _rNewValue, const css::uno::Any& _rOldValue, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI, sal_Bool _bFirstTimeInit ) override; + virtual sal_Bool SAL_CALL suspend( sal_Bool _bSuspend ) override; + + // XComponent + virtual void SAL_CALL disposing() override; + + // PropertyHandler + virtual css::uno::Sequence< css::beans::Property > + doDescribeSupportedProperties() const override; + virtual void onNewComponent() override; + + private: + /** classifies our component, in case it's a control model, by ClassId + + Note that UNO dialog controls are also classified, though they don't have the ClassId property + */ + void impl_classifyControlModel_throw(); + + bool isReportModel() const; + + /** const-version of ->getPropertyValue + */ + css::uno::Any impl_getPropertyValue_throw( const OUString& _rPropertyName ) const; + + // some property values are faked, and not used in the way they're provided by our component + void impl_normalizePropertyValue_nothrow( css::uno::Any& _rValue, PropertyId _nPropId ) const; + + /** determines whether we should exclude a given property from our "supported properties" + */ + bool impl_shouldExcludeProperty_nothrow( const css::beans::Property& _rProperty ) const; + + /** initializes the list of field names, if we're handling a control which supports the + DataField property + */ + void impl_initFieldList_nothrow( std::vector< OUString >& rFieldNames ) const; + + /** obtains the RowSet to which our component belongs + + If the component is a RowSet itself, it's returned directly. Else, the parent + is examined for the XRowSet interface. If the parent is no XRowSet, then + a check is made whether our component is a grid control column, and if so, + the parent of the grid control is examined for the XRowSet interface. + + Normally, at least one of those methods should succeed. + */ + css::uno::Reference< css::sdbc::XRowSet > impl_getRowSet_throw( ) const; + + /** nothrow-version of ->impl_getRowSet_throw + */ + css::uno::Reference< css::sdbc::XRowSet > impl_getRowSet_nothrow( ) const; + + /** connects the row set belonging to our introspected data aware form component, + and remembers the connection in ->m_xRowSetConnection. + + If the row set already is connected, ->m_xRowSetConnection will be set, too, but + not take the ownership of the connection. + + If ->m_xRowSetConnection is already set, nothing happens, so if you want to + force creation of a connection, you need to clear ->m_xRowSetConnection. + */ + bool impl_ensureRowsetConnection_nothrow() const; + + /** fills an ->LineDescriptor with information to represent a cursor source + of our form - that is, a table, a query, or an SQL statement. + + As an example, if our form has currently a CommandType of TABLE, then the + value list in the LineDescriptor will contain a list of all tables + of the data source which the form is bound to. + + @seealso impl_fillTableNames_throw + @seealso impl_fillQueryNames_throw + */ + void impl_describeCursorSource_nothrow( + css::inspection::LineDescriptor& _out_rProperty, + const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory + ) const; + + /** describes the UI for selecting a table name + + @precond + m_xRowSetConnection is not <NULL/> + */ + void impl_fillTableNames_throw( std::vector< OUString >& _out_rNames ) const; + + /** describes the UI for selecting a query name + + @precond + m_xRowSetConnection is not <NULL/> + */ + void impl_fillQueryNames_throw( std::vector< OUString >& _out_rNames ) const; + + /** describes the UI for selecting a query name + + @precond + m_xRowSetConnection is not <NULL/> + */ + void impl_fillQueryNames_throw( const css::uno::Reference< css::container::XNameAccess >& _xQueryNames + ,std::vector< OUString >& _out_rNames + ,std::u16string_view _sName = std::u16string_view() ) const; + + /** describes the UI for selecting a ListSource (for list-like form controls) + @precond + ->m_xRowSetConnection is not <NULL/> + @precond + ->m_xComponent is not <NULL/> + */ + void impl_describeListSourceUI_throw( + css::inspection::LineDescriptor& _out_rDescriptor, + const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory + ) const; + + /** displays a database-related error to the user + */ + void impl_displaySQLError_nothrow( const ::dbtools::SQLExceptionInfo& _rErrorDescriptor ) const; + + /** let's the user chose a selection of entries from a string list, and stores this + selection in the given property + @return + <TRUE/> if and only if the user successfully changed the property + */ + bool impl_dialogListSelection_nothrow( const OUString& _rProperty, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const; + + /** executes a dialog for choosing a filter or sort criterion for a database form + @param _bFilter + <TRUE/> if the Filter property should be used, <FALSE/> if it's the Order + property + @param _out_rSelectedClause + the filter or order clause as chosen by the user + @precond + we're really inspecting a database form (well, a RowSet at least) + @return + <TRUE/> if and only if the user successfully chose a clause + */ + bool impl_dialogFilterOrSort_nothrow( bool _bFilter, OUString& _out_rSelectedClause, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const; + + /** executes a dialog which allows the user to choose the columns linking + a sub to a master form, and sets the respective MasterFields / SlaveFields + properties at the form. + @precond + we're inspecting (sub) database form + @return + <TRUE/> if and only if the user successfully enter master and slave fields + */ + bool impl_dialogLinkedFormFields_nothrow( ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const; + + /** executes a dialog which allows the user to modify the FormatKey + property of our component, by choosing a (number) format. + @precond + Our component actually has a FormatKey property. + @param _out_rNewValue + the new property value, if the user chose a new formatting + @return + <TRUE/> if and only if a new formatting has been chosen by the user. + In this case, ->_out_rNewValue is filled with the new property value + */ + bool impl_dialogFormatting_nothrow( css::uno::Any& _out_rNewValue, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const; + + /** executes a dialog which allows to the user to change the ImageURL property + of our component by browsing for an image file. + @precond + our component actually has an ImageURL property + @param _out_rNewValue + the new property value, if the user chose a new image url + @return + <TRUE/> if and only if a new image URL has been chosen by the user. + In this case, ->_out_rNewValue is filled with the new property value + */ + bool impl_browseForImage_nothrow( css::uno::Any& _out_rNewValue, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const; + + /** executes a dialog which allows the user to change the TargetURL property of + our component + @precond + our component actually has a TargetURL property + @param _out_rNewValue + the new property value, if the user chose a new TargetURL + @return + <TRUE/> if and only if a new TargetURL has been chosen by the user. + In this case, ->_out_rNewValue is filled with the new property value + */ + bool impl_browseForTargetURL_nothrow( css::uno::Any& _out_rNewValue, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const; + + /** executes a dialog which allows the user to change the font, plus related properties, + of our component + @precond + our component actually has a Font property + @param _out_rNewValue + a value describing the new font, as <code>Sequence< NamedValue ></code> + @return + <TRUE/> if and only if the user successfully changed the font of our component + */ + bool impl_executeFontDialog_nothrow( css::uno::Any& _out_rNewValue, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const; + + /** allows the user browsing for a database document + @precond + our component actually has a DataSource property + @param _out_rNewValue + the new property value, if the user chose a new DataSource + @return + <TRUE/> if and only if a new DataSource has been chosen by the user. + In this case, ->_out_rNewValue is filled with the new property value + */ + bool impl_browseForDatabaseDocument_throw( css::uno::Any& _out_rNewValue, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const; + + /** raises a dialog which allows the user to choose a color + @param _nColorPropertyId + the ID of the color property + @param _out_rNewValue + the chosen color value + @return + <TRUE/> if and only if a color was chosen by the user + */ + bool impl_dialogColorChooser_throw( sal_Int32 _nColorPropertyId, css::uno::Any& _out_rNewValue, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const; + + /** raises a dialog which allows the user to choose a label control for our component + @param _out_rNewValue + the chosen label control, if any + @return + <TRUE/> if and only if a label control was chosen by the user + */ + bool impl_dialogChooseLabelControl_nothrow( css::uno::Any& _out_rNewValue, ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const; + + /** raises a dialog which lets the user chose the tab order of controls of a form + @precond + we have a view control container in which our controls live + @return + <TRUE/> if and only if the user successfully changed the tab order + @seealso impl_getContextControlContainer_nothrow + */ + bool impl_dialogChangeTabOrder_nothrow( ::osl::ClearableMutexGuard& _rClearBeforeDialog ) const; + + /** retrieves the context for controls, whose model(s) we're inspecting + + If we're inspecting a control model, this is usually part of a set of controls + and control models, where the controls live in a certain context (a ->XControlContainer). + If we know this context, we can enable additional special functionality. + + The ->XComponentContext in which we were created is examined for a value + named "ControlContext", and this value is returned. + */ + css::uno::Reference< css::awt::XControlContainer > + impl_getContextControlContainer_nothrow() const; + + /** opens a query design window for interactively designing the SQL command of a + database form + @param _rxUIUpdate + access to the property browser UI + @param _nDesignForProperty + the ID for the property for which the designer is opened + @return + <TRUE/> if the window was successfully opened, or was previously open, + <FALSE/> otherwise + */ + bool impl_doDesignSQLCommand_nothrow( + const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI, + PropertyId _nDesignForProperty + ); + + /** updates a property (UI) whose state depends on more than one other property + + ->actuatingPropertyChanged is called for certain properties in whose changes + we expressed interes (->getActuatingProperty). Now such a property change can + result in simple UI updates, for instance another property being enabled or disabled. + + However, it can also result in a more complex change: The current (UI) state might + depend on the value of more than one other property. Those dependent properties (their + UI, more precisely) are updated in this method. + + @param _nPropid + the ->PropertyId of the dependent property whose UI state is to be updated + + @param _rxInspectorUI + provides access to the property browser UI. Must not be <NULL/>. + */ + void impl_updateDependentProperty_nothrow( PropertyId _nPropId, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI ) const; + + /** determines whether the given form has a valid data source signature. + + Valid here means that the DataSource property denotes an existing data source, and the + Command property is not empty. No check is made whether the value of the Command property + denotes an existent object, since this would be way too expensive. + + @param _xFormProperties + the form to check. Must not be <NULL/>. + @param _bAllowEmptyDataSourceName + determine whether an empty data source name is allowed (<TRUE/>), and should not + lead to rejection + */ + static bool impl_hasValidDataSourceSignature_nothrow( + const css::uno::Reference< css::beans::XPropertySet >& _xFormProperties, + bool _bAllowEmptyDataSourceName ); + + /** returns the URL of our context document + @return + */ + OUString impl_getDocumentURL_nothrow() const; + + private: + DECL_LINK( OnDesignerClosed, SQLCommandDesigner&, void ); + + private: + FormComponentPropertyHandler( const FormComponentPropertyHandler& ) = delete; + FormComponentPropertyHandler& operator=( const FormComponentPropertyHandler& ) = delete; + + private: + using ::comphelper::OPropertyContainer::addPropertyChangeListener; + using ::comphelper::OPropertyContainer::removePropertyChangeListener; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/formcontroller.cxx b/extensions/source/propctrlr/formcontroller.cxx new file mode 100644 index 0000000000..f2daf2dbd3 --- /dev/null +++ b/extensions/source/propctrlr/formcontroller.cxx @@ -0,0 +1,248 @@ +/* -*- 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 "formcontroller.hxx" +#include "pcrcommon.hxx" +#include "formstrings.hxx" +#include "defaultforminspection.hxx" + +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/util/VetoException.hpp> +#include <cppuhelper/typeprovider.hxx> +#include <utility> + + +namespace pcr +{ + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::TypeClass_INTERFACE; + using ::com::sun::star::uno::TypeClass_STRING; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::inspection::XObjectInspectorModel; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::beans::XPropertySetInfo; + using ::com::sun::star::beans::XPropertySet; + using ::com::sun::star::beans::Property; + using ::com::sun::star::uno::Any; + using ::com::sun::star::lang::IllegalArgumentException; + using ::com::sun::star::uno::Type; + using ::com::sun::star::util::VetoException; + using ::com::sun::star::beans::PropertyVetoException; + using ::com::sun::star::uno::UNO_QUERY; + + namespace PropertyAttribute = css::beans::PropertyAttribute; + + + //= FormController + + + FormController::FormController( const Reference< XComponentContext >& _rxContext, + OUString sImplementationName, + const css::uno::Sequence<OUString>& aSupportedServiceNames, + bool _bUseFormFormComponentHandlers ) + :OPropertyBrowserController( _rxContext ) + ,FormController_PropertyBase1( m_aBHelper ) + ,m_sImplementationName(std::move( sImplementationName )) + ,m_aSupportedServiceNames( aSupportedServiceNames ) + { + osl_atomic_increment( &m_refCount ); + { + Reference< XObjectInspectorModel > xModel( + *(new DefaultFormComponentInspectorModel( _bUseFormFormComponentHandlers )), + UNO_QUERY_THROW + ); + setInspectorModel( xModel ); + } + osl_atomic_decrement( &m_refCount ); + } + + + FormController::~FormController() + { + } + + + IMPLEMENT_FORWARD_XINTERFACE2( FormController, OPropertyBrowserController, FormController_PropertyBase1 ) + + + Sequence< Type > SAL_CALL FormController::getTypes( ) + { + ::cppu::OTypeCollection aTypes( + cppu::UnoType<XPropertySet>::get(), + cppu::UnoType<XMultiPropertySet>::get(), + cppu::UnoType<XFastPropertySet>::get(), + OPropertyBrowserController::getTypes()); + return aTypes.getTypes(); + } + + + IMPLEMENT_GET_IMPLEMENTATION_ID( FormController ) + + + OUString SAL_CALL FormController::getImplementationName( ) + { + return m_sImplementationName; + } + + + Sequence< OUString > SAL_CALL FormController::getSupportedServiceNames( ) + { + Sequence< OUString > aSupported( m_aSupportedServiceNames ); + aSupported.realloc( aSupported.getLength() + 1 ); + aSupported.getArray()[ aSupported.getLength() - 1 ] = "com.sun.star.inspection.ObjectInspector"; + return aSupported; + } + + + Reference< XPropertySetInfo > SAL_CALL FormController::getPropertySetInfo( ) + { + return ::cppu::OPropertySetHelper::createPropertySetInfo(getInfoHelper()); + } + + + ::cppu::IPropertyArrayHelper& SAL_CALL FormController::getInfoHelper() + { + return *getArrayHelper(); + } + + + ::cppu::IPropertyArrayHelper* FormController::createArrayHelper( ) const + { + Sequence< Property > aProps{ + Property( + PROPERTY_CURRENTPAGE, + static_cast<sal_Int32>(OwnPropertyId::CURRENTPAGE), + ::cppu::UnoType<OUString>::get(), + PropertyAttribute::TRANSIENT + ), + Property( + PROPERTY_INTROSPECTEDOBJECT, + static_cast<sal_Int32>(OwnPropertyId::INTROSPECTEDOBJECT), + cppu::UnoType<XPropertySet>::get(), + PropertyAttribute::TRANSIENT | PropertyAttribute::CONSTRAINED + ) + }; + return new ::cppu::OPropertyArrayHelper( aProps ); + } + + + sal_Bool SAL_CALL FormController::convertFastPropertyValue( Any & rConvertedValue, Any & rOldValue, sal_Int32 nHandle, const Any& rValue ) + { + switch ( static_cast<OwnPropertyId>(nHandle) ) + { + case OwnPropertyId::INTROSPECTEDOBJECT: + if ( rValue.getValueTypeClass() != TypeClass_INTERFACE ) + throw IllegalArgumentException(); + break; + case OwnPropertyId::CURRENTPAGE: + if ( rValue.getValueTypeClass() != TypeClass_STRING ) + throw IllegalArgumentException(); + break; + default: + break; + } + + getFastPropertyValue( rOldValue, nHandle ); + rConvertedValue = rValue; + return true; + } + + + void SAL_CALL FormController::setFastPropertyValue_NoBroadcast(sal_Int32 _nHandle, const Any& _rValue) + { + switch ( static_cast<OwnPropertyId>(_nHandle) ) + { + case OwnPropertyId::INTROSPECTEDOBJECT: + { + Reference< XObjectInspectorModel > xModel( getInspectorModel() ); + if ( xModel.is() ) + { + try + { + m_xCurrentInspectee.set( _rValue, UNO_QUERY ); + Sequence< Reference< XInterface > > aObjects; + if ( m_xCurrentInspectee.is() ) + { + aObjects = { m_xCurrentInspectee }; + } + + Reference< XObjectInspector > xInspector( *this, UNO_QUERY_THROW ); + xInspector->inspect( aObjects ); + } + catch( const VetoException& e ) + { + throw PropertyVetoException( e.Message, e.Context ); + } + } + } + break; + case OwnPropertyId::CURRENTPAGE: + restoreViewData( _rValue ); + break; + default: + break; + } + } + + + void SAL_CALL FormController::getFastPropertyValue( css::uno::Any& rValue, sal_Int32 nHandle ) const + { + switch ( static_cast<OwnPropertyId>(nHandle) ) + { + case OwnPropertyId::INTROSPECTEDOBJECT: + rValue <<= m_xCurrentInspectee; + break; + + case OwnPropertyId::CURRENTPAGE: + rValue = const_cast< FormController* >( this )->getViewData(); + break; + default: + break; + } + } + + + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_FormController_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::FormController( context, + "org.openoffice.comp.extensions.FormController", + { "com.sun.star.form.PropertyBrowserController" }, + true ) ); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_DialogController_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::FormController( context, + "org.openoffice.comp.extensions.DialogController", + { "com.sun.star.awt.PropertyBrowserController" }, + false ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/formcontroller.hxx b/extensions/source/propctrlr/formcontroller.hxx new file mode 100644 index 0000000000..f3415057fc --- /dev/null +++ b/extensions/source/propctrlr/formcontroller.hxx @@ -0,0 +1,103 @@ +/* -*- 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 . + */ +#pragma once + +#include "propcontroller.hxx" + +#include <cppuhelper/propshlp.hxx> +#include <comphelper/proparrhlp.hxx> +#include <comphelper/uno3.hxx> + + +namespace pcr +{ + + + //= FormController + + class FormController; + typedef ::cppu::OPropertySetHelper FormController_PropertyBase1; + typedef ::comphelper::OPropertyArrayUsageHelper< FormController > FormController_PropertyBase2; + + /** Legacy implementation of com.sun.star.form.PropertyBrowserController + + Nowadays only a wrapper around an ObjectInspector using a + DefaultFormComponentInspectorModel. + */ + class FormController :public OPropertyBrowserController + ,public FormController_PropertyBase1 + ,public FormController_PropertyBase2 + { + private: + css::uno::Reference< css::beans::XPropertySet > + m_xCurrentInspectee; + OUString m_sImplementationName; + css::uno::Sequence<OUString> m_aSupportedServiceNames; + public: + FormController( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext, + OUString sImplementName, + const css::uno::Sequence<OUString>& aSupportedServiceNames, + bool _bUseFormFormComponentHandlers + ); + + protected: + virtual ~FormController() override; + + DECLARE_XINTERFACE() + DECLARE_XTYPEPROVIDER() + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // XPropertySet and friends + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; + virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const override; + + virtual sal_Bool SAL_CALL convertFastPropertyValue( + css::uno::Any & rConvertedValue, css::uno::Any & rOldValue, sal_Int32 nHandle, const css::uno::Any& rValue + ) override; + virtual void SAL_CALL setFastPropertyValue_NoBroadcast( + sal_Int32 nHandle, const css::uno::Any& rValue + ) override; + virtual void SAL_CALL getFastPropertyValue( + css::uno::Any& rValue, sal_Int32 nHandle + ) const override; + private: + using FormController_PropertyBase1::getFastPropertyValue; + }; + + + //= DialogController + + /** Legacy implementation of com.sun.star.awt.PropertyBrowserController + */ + class DialogController + { + private: + DialogController( const DialogController& ) = delete; + DialogController& operator=( const DialogController& ) = delete; + }; + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/formgeometryhandler.cxx b/extensions/source/propctrlr/formgeometryhandler.cxx new file mode 100644 index 0000000000..da31294d1b --- /dev/null +++ b/extensions/source/propctrlr/formgeometryhandler.cxx @@ -0,0 +1,821 @@ +/* -*- 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 "propertyhandler.hxx" +#include "formmetadata.hxx" +#include "formstrings.hxx" +#include "handlerhelper.hxx" +#include "cellbindinghelper.hxx" + +#include <com/sun/star/inspection/XObjectInspectorUI.hpp> +#include <com/sun/star/awt/XControlModel.hpp> +#include <com/sun/star/drawing/XControlShape.hpp> +#include <com/sun/star/container/XMap.hpp> +#include <com/sun/star/inspection/XNumericControl.hpp> +#include <com/sun/star/util/MeasureUnit.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/lang/NullPointerException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/sheet/XSpreadsheet.hpp> +#include <com/sun/star/table/XColumnRowRange.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/form/XGridColumnFactory.hpp> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/interfacecontainer.hxx> +#include <comphelper/interfacecontainer2.hxx> +#include <comphelper/componentbase.hxx> +#include <rtl/ref.hxx> +#include <utility> +#include <comphelper/diagnose_ex.hxx> + +namespace pcr +{ + + + 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::Sequence; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::beans::Property; + using ::com::sun::star::awt::XControlModel; + using ::com::sun::star::drawing::XControlShape; + using ::com::sun::star::container::XMap; + using ::com::sun::star::inspection::LineDescriptor; + using ::com::sun::star::inspection::XPropertyControlFactory; + using ::com::sun::star::lang::NullPointerException; + using ::com::sun::star::beans::Optional; + using ::com::sun::star::inspection::XNumericControl; + using ::com::sun::star::drawing::XShape; + using ::com::sun::star::beans::PropertyChangeEvent; + using ::com::sun::star::lang::EventObject; + using ::com::sun::star::beans::XPropertySet; + using ::com::sun::star::beans::XPropertyChangeListener; + using ::com::sun::star::text::TextContentAnchorType; + using ::com::sun::star::text::TextContentAnchorType_AT_PARAGRAPH; + using ::com::sun::star::text::TextContentAnchorType_AS_CHARACTER; + using ::com::sun::star::beans::XPropertySetInfo; + using ::com::sun::star::inspection::XObjectInspectorUI; + using ::com::sun::star::lang::XServiceInfo; + using ::com::sun::star::sheet::XSpreadsheet; + using ::com::sun::star::table::XColumnRowRange; + using ::com::sun::star::table::XTableColumns; + using ::com::sun::star::table::XTableRows; + using ::com::sun::star::container::XIndexAccess; + using ::com::sun::star::container::XChild; + using ::com::sun::star::form::XGridColumnFactory; + + namespace MeasureUnit = css::util::MeasureUnit; + + #define ANCHOR_TO_SHEET 0 + #define ANCHOR_TO_CELL 1 + + + //= BroadcastHelperBase + + namespace { + + class BroadcastHelperBase + { + protected: + explicit BroadcastHelperBase( ::osl::Mutex& _rMutex ) + :maBHelper( _rMutex ) + { + } + + protected: + ::cppu::OBroadcastHelper& getBroadcastHelper() { return maBHelper; } + + private: + ::cppu::OBroadcastHelper maBHelper; + }; + + } + + //= ShapeGeometryChangeNotifier - declaration + + /** helper class to work around the ...unfortunate implementation of property change broadcasts + in the XShape implementation, which broadcasts way too generous and unspecified + */ + typedef ::comphelper::ComponentBase ShapeGeometryChangeNotifier_CBase; + typedef ::cppu::WeakImplHelper < css::beans::XPropertyChangeListener + > ShapeGeometryChangeNotifier_IBase; + + namespace { + + class ShapeGeometryChangeNotifier :public BroadcastHelperBase + ,public ShapeGeometryChangeNotifier_CBase + ,public ShapeGeometryChangeNotifier_IBase + { + public: + ShapeGeometryChangeNotifier( ::cppu::OWeakObject& _rParent, ::osl::Mutex& _rParentMutex, const Reference< XShape >& _shape ) + :BroadcastHelperBase( _rParentMutex ) + ,ShapeGeometryChangeNotifier_CBase( BroadcastHelperBase::getBroadcastHelper(), ::comphelper::ComponentBase::NoInitializationNeeded() ) + ,m_rParent( _rParent ) + ,m_aPropertyChangeListeners( _rParentMutex ) + ,m_xShape( _shape ) + { + ENSURE_OR_THROW( m_xShape.is(), "illegal shape!" ); + impl_init_nothrow(); + } + + // property change broadcasting + void addPropertyChangeListener( const Reference< XPropertyChangeListener >& _listener ) + { + m_aPropertyChangeListeners.addInterface( _listener ); + } + void removePropertyChangeListener( const Reference< XPropertyChangeListener >& _listener ) + { + m_aPropertyChangeListeners.removeInterface( _listener ); + } + + // XComponent equivalent + void dispose() + { + ::osl::MutexGuard aGuard( getMutex() ); + impl_dispose_nothrow(); + } + + // XInterface + virtual void SAL_CALL acquire( ) noexcept override + { + m_rParent.acquire(); + } + + virtual void SAL_CALL release( ) noexcept override + { + m_rParent.release(); + } + + // XPropertyChangeListener + virtual void SAL_CALL propertyChange( const PropertyChangeEvent& _event ) override; + + // XEventListener + virtual void SAL_CALL disposing( const EventObject& _event ) override; + + protected: + virtual ~ShapeGeometryChangeNotifier() override + { + if ( !getBroadcastHelper().bDisposed ) + { + acquire(); + dispose(); + } + } + + protected: + ::cppu::OBroadcastHelper& getBroadcastHelper() { return BroadcastHelperBase::getBroadcastHelper(); } + + private: + void impl_init_nothrow(); + void impl_dispose_nothrow(); + + private: + ::cppu::OWeakObject& m_rParent; + ::comphelper::OInterfaceContainerHelper2 m_aPropertyChangeListeners; + Reference< XShape > m_xShape; + }; + + } + + //= FormGeometryHandler - declaration + + namespace { + + class FormGeometryHandler; + + } + + /** a property handler for any virtual string properties + */ + + namespace { + + class FormGeometryHandler : public PropertyHandlerComponent + { + public: + explicit FormGeometryHandler( + const Reference< XComponentContext >& _rxContext + ); + + protected: + virtual ~FormGeometryHandler() override; + + protected: + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames () override; + + // XPropertyHandler overriables + virtual Any SAL_CALL getPropertyValue( const OUString& _rPropertyName ) override; + virtual void SAL_CALL setPropertyValue( const OUString& _rPropertyName, const Any& _rValue ) override; + virtual LineDescriptor SAL_CALL describePropertyLine( const OUString& _rPropertyName, const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory ) override; + virtual void SAL_CALL addPropertyChangeListener( const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ) override; + virtual Sequence< OUString > SAL_CALL getActuatingProperties( ) override; + virtual void SAL_CALL actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const Any& _rNewValue, const Any& _rOldValue, const Reference< XObjectInspectorUI >& _rxInspectorUI, sal_Bool _bFirstTimeInit ) override; + + // OComponentHandler overridables + virtual void SAL_CALL disposing() override; + + // PropertyHandler overridables + virtual Sequence< Property > doDescribeSupportedProperties() const override; + + protected: + virtual void onNewComponent() override; + + private: + bool impl_haveTextAnchorType_nothrow() const; + bool impl_haveSheetAnchorType_nothrow() const; + void impl_setSheetAnchorType_nothrow( const sal_Int32 _nAnchorType ) const; + + private: + Reference< XControlShape > m_xAssociatedShape; + Reference< XPropertySet > m_xShapeProperties; + ::rtl::Reference< ShapeGeometryChangeNotifier > m_xChangeNotifier; + }; + + } + + //= FormGeometryHandler - implementation + + + FormGeometryHandler::FormGeometryHandler( const Reference< XComponentContext >& _rxContext ) + :PropertyHandlerComponent( _rxContext ) + { + } + + + FormGeometryHandler::~FormGeometryHandler( ) + { + if ( !rBHelper.bDisposed ) + { + acquire(); + dispose(); + } + + } + + + void FormGeometryHandler::onNewComponent() + { + if ( m_xChangeNotifier.is() ) + { + m_xChangeNotifier->dispose(); + m_xChangeNotifier.clear(); + } + m_xAssociatedShape.clear(); + m_xShapeProperties.clear(); + + PropertyHandlerComponent::onNewComponent(); + + try + { + Reference< XControlModel > xControlModel( m_xComponent, UNO_QUERY ); + if ( xControlModel.is() ) + { + // do not ask the map for shapes for grid control columns... + Reference< XChild > xCompChild( m_xComponent, UNO_QUERY_THROW ); + Reference< XGridColumnFactory > xCheckGrid( xCompChild->getParent(), UNO_QUERY ); + if ( !xCheckGrid.is() ) + { + Reference< XMap > xControlMap; + Any any = m_xContext->getValueByName( "ControlShapeAccess" ); + any >>= xControlMap; + m_xAssociatedShape.set( xControlMap->get( Any( xControlModel ) ), UNO_QUERY_THROW ); + m_xShapeProperties.set( m_xAssociatedShape, UNO_QUERY_THROW ); + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + + if ( m_xAssociatedShape.is() ) + m_xChangeNotifier = new ShapeGeometryChangeNotifier( *this, m_aMutex, m_xAssociatedShape ); + } + + + OUString FormGeometryHandler::getImplementationName( ) + { + return "com.sun.star.comp.extensions.FormGeometryHandler"; + } + + + Sequence< OUString > FormGeometryHandler::getSupportedServiceNames( ) + { + return { "com.sun.star.form.inspection.FormGeometryHandler" }; + } + + + Any SAL_CALL FormGeometryHandler::getPropertyValue( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + ENSURE_OR_THROW2( m_xAssociatedShape.is(), "internal error: properties, but no shape!", *this ); + ENSURE_OR_THROW2( m_xShapeProperties.is(), "internal error: no shape properties!", *this ); + + Any aReturn; + try + { + switch ( nPropId ) + { + case PROPERTY_ID_POSITIONX: + aReturn <<= m_xAssociatedShape->getPosition().X; + break; + case PROPERTY_ID_POSITIONY: + aReturn <<= m_xAssociatedShape->getPosition().Y; + break; + case PROPERTY_ID_WIDTH: + aReturn <<= m_xAssociatedShape->getSize().Width; + break; + case PROPERTY_ID_HEIGHT: + aReturn <<= m_xAssociatedShape->getSize().Height; + break; + case PROPERTY_ID_TEXT_ANCHOR_TYPE: + aReturn = m_xShapeProperties->getPropertyValue( PROPERTY_ANCHOR_TYPE ); + OSL_ENSURE( aReturn.hasValue(), "FormGeometryHandler::getPropertyValue: illegal anchor type!" ); + break; + case PROPERTY_ID_SHEET_ANCHOR_TYPE: + { + Reference< XSpreadsheet > xAnchorSheet( m_xShapeProperties->getPropertyValue( PROPERTY_ANCHOR ), UNO_QUERY ); + aReturn <<= sal_Int32( xAnchorSheet.is() ? ANCHOR_TO_SHEET : ANCHOR_TO_CELL ); + } + break; + + default: + OSL_FAIL( "FormGeometryHandler::getPropertyValue: huh?" ); + break; + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + return aReturn; + } + + + void SAL_CALL FormGeometryHandler::setPropertyValue( const OUString& _rPropertyName, const Any& _rValue ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + ENSURE_OR_THROW2( m_xAssociatedShape.is(), "internal error: properties, but no shape!", *this ); + ENSURE_OR_THROW2( m_xShapeProperties.is(), "internal error: properties, but no shape!", *this ); + + try + { + switch ( nPropId ) + { + case PROPERTY_ID_POSITIONX: + case PROPERTY_ID_POSITIONY: + { + sal_Int32 nPosition(0); + OSL_VERIFY( _rValue >>= nPosition ); + + css::awt::Point aPos( m_xAssociatedShape->getPosition() ); + if ( nPropId == PROPERTY_ID_POSITIONX ) + aPos.X = nPosition; + else + aPos.Y = nPosition; + m_xAssociatedShape->setPosition( aPos ); + } + break; + + case PROPERTY_ID_WIDTH: + case PROPERTY_ID_HEIGHT: + { + sal_Int32 nSize(0); + OSL_VERIFY( _rValue >>= nSize ); + + css::awt::Size aSize( m_xAssociatedShape->getSize() ); + if ( nPropId == PROPERTY_ID_WIDTH ) + aSize.Width = nSize; + else + aSize.Height = nSize; + m_xAssociatedShape->setSize( aSize ); + } + break; + + case PROPERTY_ID_TEXT_ANCHOR_TYPE: + m_xShapeProperties->setPropertyValue( PROPERTY_ANCHOR_TYPE, _rValue ); + break; + + case PROPERTY_ID_SHEET_ANCHOR_TYPE: + { + sal_Int32 nSheetAnchorType = 0; + OSL_VERIFY( _rValue >>= nSheetAnchorType ); + impl_setSheetAnchorType_nothrow( nSheetAnchorType ); + } + break; + + default: + OSL_FAIL( "FormGeometryHandler::getPropertyValue: huh?" ); + break; + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + + LineDescriptor SAL_CALL FormGeometryHandler::describePropertyLine( const OUString& _rPropertyName, + const Reference< XPropertyControlFactory >& _rxControlFactory ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + LineDescriptor aLineDesc( PropertyHandler::describePropertyLine( _rPropertyName, _rxControlFactory ) ); + try + { + bool bIsSize = false; + switch ( nPropId ) + { + case PROPERTY_ID_WIDTH: + case PROPERTY_ID_HEIGHT: + bIsSize = true; + [[fallthrough]]; + case PROPERTY_ID_POSITIONX: + case PROPERTY_ID_POSITIONY: + { + Optional< double > aZero( true, 0 ); + Optional< double > aValueNotPresent( false, 0 ); + aLineDesc.Control = PropertyHandlerHelper::createNumericControl( + _rxControlFactory, 2, bIsSize ? aZero : aValueNotPresent, aValueNotPresent ); + + Reference< XNumericControl > xNumericControl( aLineDesc.Control, UNO_QUERY_THROW ); + xNumericControl->setValueUnit( MeasureUnit::MM_100TH ); + xNumericControl->setDisplayUnit( impl_getDocumentMeasurementUnit_throw() ); + } + break; + + case PROPERTY_ID_TEXT_ANCHOR_TYPE: + case PROPERTY_ID_SHEET_ANCHOR_TYPE: + // default handling from PropertyHandler is sufficient + break; + + default: + OSL_FAIL( "FormGeometryHandler::describePropertyLine: huh?" ); + break; + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + return aLineDesc; + } + + + void SAL_CALL FormGeometryHandler::addPropertyChangeListener( const Reference< XPropertyChangeListener >& _listener ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + OSL_PRECOND( m_xChangeNotifier.is(), "FormGeometryHandler::addPropertyChangeListener: no notified, implies no shape!?" ); + if ( m_xChangeNotifier.is() ) + m_xChangeNotifier->addPropertyChangeListener( _listener ); + } + + + void SAL_CALL FormGeometryHandler::removePropertyChangeListener( const Reference< XPropertyChangeListener >& _listener ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + OSL_PRECOND( m_xChangeNotifier.is(), "FormGeometryHandler::removePropertyChangeListener: no notified, implies no shape!?" ); + if ( m_xChangeNotifier.is() ) + m_xChangeNotifier->removePropertyChangeListener( _listener ); + } + + + Sequence< OUString > SAL_CALL FormGeometryHandler::getActuatingProperties( ) + { + Sequence< OUString > aInterestedIn { PROPERTY_TEXT_ANCHOR_TYPE }; + return aInterestedIn; + } + + + void SAL_CALL FormGeometryHandler::actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const Any& _rNewValue, const Any& /*_rOldValue*/, const Reference< XObjectInspectorUI >& _rxInspectorUI, sal_Bool /*_bFirstTimeInit*/ ) + { + if ( !_rxInspectorUI.is() ) + throw NullPointerException(); + + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nActuatingPropId( impl_getPropertyId_nothrow( _rActuatingPropertyName ) ); + + switch ( nActuatingPropId ) + { + case PROPERTY_ID_TEXT_ANCHOR_TYPE: + { + TextContentAnchorType eAnchorType( TextContentAnchorType_AT_PARAGRAPH ); + OSL_VERIFY( _rNewValue >>= eAnchorType ); + _rxInspectorUI->enablePropertyUI( PROPERTY_POSITIONX, eAnchorType != TextContentAnchorType_AS_CHARACTER ); + } + break; + case -1: + throw RuntimeException(); + break; + default: + OSL_FAIL( "FormGeometryHandler::actuatingPropertyChanged: not registered for this property!" ); + break; + } + } + + + Sequence< Property > FormGeometryHandler::doDescribeSupportedProperties() const + { + if ( !m_xAssociatedShape.is() ) + return Sequence< Property >(); + + std::vector< Property > aProperties; + + addInt32PropertyDescription( aProperties, PROPERTY_POSITIONX ); + addInt32PropertyDescription( aProperties, PROPERTY_POSITIONY ); + addInt32PropertyDescription( aProperties, PROPERTY_WIDTH ); + addInt32PropertyDescription( aProperties, PROPERTY_HEIGHT ); + + if ( impl_haveTextAnchorType_nothrow() ) + implAddPropertyDescription( aProperties, PROPERTY_TEXT_ANCHOR_TYPE, ::cppu::UnoType< TextContentAnchorType >::get() ); + + if ( impl_haveSheetAnchorType_nothrow() ) + addInt32PropertyDescription( aProperties, PROPERTY_SHEET_ANCHOR_TYPE ); + + return comphelper::containerToSequence(aProperties); + } + + + void SAL_CALL FormGeometryHandler::disposing() + { + PropertyHandlerComponent::disposing(); + + if ( m_xChangeNotifier.is() ) + { + m_xChangeNotifier->dispose(); + m_xChangeNotifier.clear(); + } + } + + + bool FormGeometryHandler::impl_haveTextAnchorType_nothrow() const + { + ENSURE_OR_THROW( m_xShapeProperties.is(), "not to be called without shape properties" ); + try + { + Reference< XPropertySetInfo > xPSI( m_xShapeProperties->getPropertySetInfo(), UNO_SET_THROW ); + if ( xPSI->hasPropertyByName( PROPERTY_ANCHOR_TYPE ) ) + return true; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + return false; + } + + + bool FormGeometryHandler::impl_haveSheetAnchorType_nothrow() const + { + ENSURE_OR_THROW( m_xShapeProperties.is(), "not to be called without shape properties" ); + try + { + Reference< XPropertySetInfo > xPSI( m_xShapeProperties->getPropertySetInfo(), UNO_SET_THROW ); + if ( !xPSI->hasPropertyByName( PROPERTY_ANCHOR ) ) + return false; + Reference< XServiceInfo > xSI( m_xAssociatedShape, UNO_QUERY_THROW ); + if ( xSI->supportsService("com.sun.star.sheet.Shape") ) + return true; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + return false; + } + + + namespace + { + sal_Int32 lcl_getLowerBoundRowOrColumn( const Reference< XIndexAccess >& _rxRowsOrColumns, const bool _bRows, + const css::awt::Point& _rRelativePosition ) + { + sal_Int32 nAccumulated = 0; + + const sal_Int32& rRelativePos = _bRows ? _rRelativePosition.Y : _rRelativePosition.X; + + sal_Int32 nElements = _rxRowsOrColumns->getCount(); + sal_Int32 currentPos = 0; + for ( currentPos=0; currentPos<nElements; ++currentPos ) + { + Reference< XPropertySet > xRowOrColumn( _rxRowsOrColumns->getByIndex( currentPos ), UNO_QUERY_THROW ); + + bool bIsVisible = true; + OSL_VERIFY( xRowOrColumn->getPropertyValue( PROPERTY_IS_VISIBLE ) >>= bIsVisible ); + if ( !bIsVisible ) + continue; + + sal_Int32 nHeightOrWidth( 0 ); + OSL_VERIFY( xRowOrColumn->getPropertyValue( _bRows ? PROPERTY_HEIGHT : PROPERTY_WIDTH ) >>= nHeightOrWidth ); + + if ( nAccumulated + nHeightOrWidth > rRelativePos ) + break; + + nAccumulated += nHeightOrWidth; + } + + return currentPos; + } + } + + + void FormGeometryHandler::impl_setSheetAnchorType_nothrow( const sal_Int32 _nAnchorType ) const + { + ENSURE_OR_THROW( m_xShapeProperties.is(), "illegal to be called without shape properties." ); + try + { + CellBindingHelper aHelper( m_xComponent, impl_getContextDocument_nothrow() ); + // find the sheet which the control belongs to + Reference< XSpreadsheet > xSheet; + aHelper.getControlSheetIndex( xSheet ); + + switch ( _nAnchorType ) + { + case ANCHOR_TO_SHEET: + OSL_ENSURE( xSheet.is(), + "FormGeometryHandler::impl_setSheetAnchorType_nothrow: sheet not found!" ); + if ( xSheet.is() ) + { + css::awt::Point aPreservePosition( m_xAssociatedShape->getPosition() ); + m_xShapeProperties->setPropertyValue( PROPERTY_ANCHOR, Any( xSheet ) ); + m_xAssociatedShape->setPosition( aPreservePosition ); + } + break; + + case ANCHOR_TO_CELL: + { + Reference< XColumnRowRange > xColsRows( xSheet, UNO_QUERY_THROW ); + + // get the current anchor + Reference< XSpreadsheet > xCurrentAnchor; + OSL_VERIFY( m_xShapeProperties->getPropertyValue( PROPERTY_ANCHOR ) >>= xCurrentAnchor ); + OSL_ENSURE( xCurrentAnchor.is(), "FormGeometryHandler::impl_setSheetAnchorType_nothrow: only to be called when currently anchored to a sheet!" ); + + // get the current position + css::awt::Point aRelativePosition( m_xAssociatedShape->getPosition() ); + + Reference< XTableColumns > xCols( xColsRows->getColumns(), UNO_SET_THROW ); + sal_Int32 nNewAnchorCol = lcl_getLowerBoundRowOrColumn( xCols, false, aRelativePosition ); + + Reference< XTableRows > xRows( xColsRows->getRows(), UNO_SET_THROW ); + sal_Int32 nNewAnchorRow = lcl_getLowerBoundRowOrColumn( xRows, true, aRelativePosition ); + + Any aNewAnchorCell( xSheet->getCellByPosition( nNewAnchorCol, nNewAnchorRow ) ); + m_xShapeProperties->setPropertyValue( PROPERTY_ANCHOR, aNewAnchorCell ); + } + break; + + default: + OSL_FAIL( "FormGeometryHandler::impl_setSheetAnchorType_nothrow: illegal anchor type!" ); + break; + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + + //= ShapeGeometryChangeNotifier - implementation + + namespace + { + struct EventTranslation + { + OUString sPropertyName; + Any aNewPropertyValue; + + EventTranslation( OUString _propertyName, Any _newPropertyValue ) + :sPropertyName(std::move( _propertyName )) + ,aNewPropertyValue(std::move( _newPropertyValue )) + { + } + }; + } + + + void SAL_CALL ShapeGeometryChangeNotifier::propertyChange( const PropertyChangeEvent& _event ) + { + ::comphelper::ComponentMethodGuard aGuard( *this ); + + std::vector< EventTranslation > aEventTranslations; + aEventTranslations.reserve(2); + + if ( _event.PropertyName == "Position" ) + { + css::awt::Point aPos = m_xShape->getPosition(); + aEventTranslations.push_back( EventTranslation( PROPERTY_POSITIONX, Any( aPos.X ) ) ); + aEventTranslations.push_back( EventTranslation( PROPERTY_POSITIONY, Any( aPos.Y ) ) ); + } + else if ( _event.PropertyName == "Size" ) + { + css::awt::Size aSize = m_xShape->getSize(); + aEventTranslations.push_back( EventTranslation( PROPERTY_WIDTH, Any( aSize.Width ) ) ); + aEventTranslations.push_back( EventTranslation( PROPERTY_HEIGHT, Any( aSize.Height ) ) ); + } + else if ( _event.PropertyName == PROPERTY_ANCHOR_TYPE ) + { + aEventTranslations.push_back( EventTranslation( PROPERTY_TEXT_ANCHOR_TYPE, _event.NewValue ) ); + } + else if ( _event.PropertyName == PROPERTY_ANCHOR ) + { + aEventTranslations.push_back( EventTranslation( PROPERTY_SHEET_ANCHOR_TYPE, _event.NewValue ) ); + } + + PropertyChangeEvent aTranslatedEvent( _event ); + aTranslatedEvent.Source = m_rParent; + + aGuard.clear(); + for (auto const& eventTranslation : aEventTranslations) + { + aTranslatedEvent.PropertyName = eventTranslation.sPropertyName; + aTranslatedEvent.NewValue = eventTranslation.aNewPropertyValue; + m_aPropertyChangeListeners.notifyEach( &XPropertyChangeListener::propertyChange, aTranslatedEvent ); + } + } + + + void SAL_CALL ShapeGeometryChangeNotifier::disposing( const EventObject& /*_event*/ ) + { + ::comphelper::ComponentMethodGuard aGuard( *this ); + impl_dispose_nothrow(); + } + + + void ShapeGeometryChangeNotifier::impl_init_nothrow() + { + osl_atomic_increment( &m_refCount ); + try + { + Reference< XPropertySet > xShapeProperties( m_xShape, UNO_QUERY_THROW ); + xShapeProperties->addPropertyChangeListener( OUString(), this ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + osl_atomic_decrement( &m_refCount ); + } + + + void ShapeGeometryChangeNotifier::impl_dispose_nothrow() + { + try + { + Reference< XPropertySet > xShapeProperties( m_xShape, UNO_QUERY_THROW ); + xShapeProperties->removePropertyChangeListener( OUString(), this ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + + getBroadcastHelper().bDisposed = true; + } + + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_FormGeometryHandler_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::FormGeometryHandler(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/formlinkdialog.cxx b/extensions/source/propctrlr/formlinkdialog.cxx new file mode 100644 index 0000000000..c46cc95cbe --- /dev/null +++ b/extensions/source/propctrlr/formlinkdialog.cxx @@ -0,0 +1,629 @@ +/* -*- 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 "formlinkdialog.hxx" + +#include "modulepcr.hxx" +#include <strings.hrc> +#include "formstrings.hxx" +#include <sal/log.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <connectivity/dbtools.hxx> +#include <connectivity/dbexception.hxx> +#include <comphelper/sequence.hxx> + +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <com/sun/star/sdbcx/XKeysSupplier.hpp> +#include <com/sun/star/sdbcx/KeyType.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <com/sun/star/sdbc/XRowSet.hpp> +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/sdb/SQLContext.hpp> +#include <com/sun/star/sdb/XSingleSelectQueryComposer.hpp> + +namespace pcr +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + 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; + + + //= FieldLinkRow + + class FieldLinkRow + { + private: + std::unique_ptr<weld::ComboBox> m_xDetailColumn; + std::unique_ptr<weld::ComboBox> m_xMasterColumn; + + Link<FieldLinkRow&,void> m_aLinkChangeHandler; + + public: + FieldLinkRow(std::unique_ptr<weld::ComboBox> xDetailColumn, + std::unique_ptr<weld::ComboBox> xMasterColumn); + + + void SetLinkChangeHandler( const Link<FieldLinkRow&,void>& _rHdl ) { m_aLinkChangeHandler = _rHdl; } + + enum LinkParticipant + { + eDetailField, + eMasterField + }; + /** retrieves the selected field name for either the master or the detail field + @return <TRUE/> if and only a valid field is selected + */ + bool GetFieldName( LinkParticipant _eWhich, OUString& /* [out] */ _rName ) const; + void SetFieldName( LinkParticipant _eWhich, const OUString& _rName ); + + void fillList( LinkParticipant _eWhich, const Sequence< OUString >& _rFieldNames ); + + void Show() + { + m_xDetailColumn->show(); + m_xMasterColumn->show(); + } + + private: + DECL_LINK( OnFieldNameChanged, weld::ComboBox&, void ); + }; + + + FieldLinkRow::FieldLinkRow(std::unique_ptr<weld::ComboBox> xDetailColumn, + std::unique_ptr<weld::ComboBox> xMasterColumn) + : m_xDetailColumn(std::move(xDetailColumn)) + , m_xMasterColumn(std::move(xMasterColumn)) + { + m_xDetailColumn->connect_changed( LINK( this, FieldLinkRow, OnFieldNameChanged ) ); + m_xMasterColumn->connect_changed( LINK( this, FieldLinkRow, OnFieldNameChanged ) ); + } + + void FieldLinkRow::fillList( LinkParticipant _eWhich, const Sequence< OUString >& _rFieldNames ) + { + weld::ComboBox* pBox = ( _eWhich == eDetailField ) ? m_xDetailColumn.get() : m_xMasterColumn.get(); + + const OUString* pFieldName = _rFieldNames.getConstArray(); + const OUString* pFieldNameEnd = pFieldName + _rFieldNames.getLength(); + for ( ; pFieldName != pFieldNameEnd; ++pFieldName ) + pBox->append_text( *pFieldName ); + } + + bool FieldLinkRow::GetFieldName( LinkParticipant _eWhich, OUString& /* [out] */ _rName ) const + { + const weld::ComboBox* pBox = ( _eWhich == eDetailField ) ? m_xDetailColumn.get() : m_xMasterColumn.get(); + _rName = pBox->get_active_text(); + return !_rName.isEmpty(); + } + + void FieldLinkRow::SetFieldName( LinkParticipant _eWhich, const OUString& _rName ) + { + weld::ComboBox* pBox = ( _eWhich == eDetailField ) ? m_xDetailColumn.get() : m_xMasterColumn.get(); + pBox->set_entry_text( _rName ); + } + + IMPL_LINK_NOARG( FieldLinkRow, OnFieldNameChanged, weld::ComboBox&, void ) + { + m_aLinkChangeHandler.Call( *this ); + } + + //= FormLinkDialog + + FormLinkDialog::FormLinkDialog(weld::Window* _pParent, const Reference< XPropertySet >& _rxDetailForm, + const Reference< XPropertySet >& _rxMasterForm, const Reference< XComponentContext >& _rxContext, + const OUString& _sExplanation, + OUString _sDetailLabel, + OUString _sMasterLabel) + : GenericDialogController(_pParent, "modules/spropctrlr/ui/formlinksdialog.ui", "FormLinks") + , m_xContext ( _rxContext ) + , m_xDetailForm( _rxDetailForm ) + , m_xMasterForm( _rxMasterForm ) + , m_sDetailLabel(std::move(_sDetailLabel)) + , m_sMasterLabel(std::move(_sMasterLabel)) + , m_xExplanation(m_xBuilder->weld_label("explanationLabel")) + , m_xDetailLabel(m_xBuilder->weld_label("detailLabel")) + , m_xMasterLabel(m_xBuilder->weld_label("masterLabel")) + , m_xRow1(std::make_unique<FieldLinkRow>(m_xBuilder->weld_combo_box("detailCombobox1"), + m_xBuilder->weld_combo_box("masterCombobox1"))) + , m_xRow2(std::make_unique<FieldLinkRow>(m_xBuilder->weld_combo_box("detailCombobox2"), + m_xBuilder->weld_combo_box("masterCombobox2"))) + , m_xRow3(std::make_unique<FieldLinkRow>(m_xBuilder->weld_combo_box("detailCombobox3"), + m_xBuilder->weld_combo_box("masterCombobox3"))) + , m_xRow4(std::make_unique<FieldLinkRow>(m_xBuilder->weld_combo_box("detailCombobox4"), + m_xBuilder->weld_combo_box("masterCombobox4"))) + , m_xOK(m_xBuilder->weld_button("ok")) + , m_xSuggest(m_xBuilder->weld_button("suggestButton")) + { + m_xRow1->Show(); + m_xRow2->Show(); + m_xRow3->Show(); + m_xRow4->Show(); + m_xDialog->set_size_request(600, -1); + + if ( !_sExplanation.isEmpty() ) + m_xExplanation->set_label(_sExplanation); + + m_xSuggest->connect_clicked(LINK(this, FormLinkDialog, OnSuggest)); + m_xRow1->SetLinkChangeHandler( LINK( this, FormLinkDialog, OnFieldChanged ) ); + m_xRow2->SetLinkChangeHandler( LINK( this, FormLinkDialog, OnFieldChanged ) ); + m_xRow3->SetLinkChangeHandler( LINK( this, FormLinkDialog, OnFieldChanged ) ); + m_xRow4->SetLinkChangeHandler( LINK( this, FormLinkDialog, OnFieldChanged ) ); + + Application::PostUserEvent(LINK(this, FormLinkDialog, OnInitialize)); + + updateOkButton(); + } + + FormLinkDialog::~FormLinkDialog() + { + } + + void FormLinkDialog::commitLinkPairs() + { + // collect the field lists from the rows + std::vector< OUString > aDetailFields; aDetailFields.reserve( 4 ); + std::vector< OUString > aMasterFields; aMasterFields.reserve( 4 ); + + const FieldLinkRow* aRows[] = { + m_xRow1.get(), m_xRow2.get(), m_xRow3.get(), m_xRow4.get() + }; + + for (const FieldLinkRow* aRow : aRows) + { + OUString sDetailField, sMasterField; + aRow->GetFieldName( FieldLinkRow::eDetailField, sDetailField ); + aRow->GetFieldName( FieldLinkRow::eMasterField, sMasterField ); + if ( sDetailField.isEmpty() && sMasterField.isEmpty() ) + continue; + + aDetailFields.push_back( sDetailField ); + aMasterFields.push_back( sMasterField ); + } + + // and set as property values + try + { + if ( m_xDetailForm.is() ) + { + m_xDetailForm->setPropertyValue( PROPERTY_DETAILFIELDS, Any( Sequence< OUString >( aDetailFields.data(), aDetailFields.size() ) ) ); + m_xDetailForm->setPropertyValue( PROPERTY_MASTERFIELDS, Any( Sequence< OUString >( aMasterFields.data(), aMasterFields.size() ) ) ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION("extensions.propctrlr", + "caught an exception while setting the properties!"); + } + } + + short FormLinkDialog::run() + { + short nResult = GenericDialogController::run(); + + if ( RET_OK == nResult ) + commitLinkPairs(); + + return nResult; + } + + void FormLinkDialog::initializeFieldLists() + { + Sequence< OUString > sDetailFields; + getFormFields( m_xDetailForm, sDetailFields ); + + Sequence< OUString > sMasterFields; + getFormFields( m_xMasterForm, sMasterFields ); + + FieldLinkRow* aRows[] = { + m_xRow1.get(), m_xRow2.get(), m_xRow3.get(), m_xRow4.get() + }; + for (FieldLinkRow* aRow : aRows) + { + aRow->fillList( FieldLinkRow::eDetailField, sDetailFields ); + aRow->fillList( FieldLinkRow::eMasterField, sMasterFields ); + } + + } + + + void FormLinkDialog::initializeColumnLabels() + { + // label for the detail form + OUString sDetailType = getFormDataSourceType( m_xDetailForm ); + if ( sDetailType.isEmpty() ) + { + if ( m_sDetailLabel.isEmpty() ) + { + m_sDetailLabel = PcrRes(STR_DETAIL_FORM); + } + sDetailType = m_sDetailLabel; + } + m_xDetailLabel->set_label( sDetailType ); + + // label for the master form + OUString sMasterType = getFormDataSourceType( m_xMasterForm ); + if ( sMasterType.isEmpty() ) + { + if ( m_sMasterLabel.isEmpty() ) + { + m_sMasterLabel = PcrRes(STR_MASTER_FORM); + } + sMasterType = m_sMasterLabel; + } + m_xMasterLabel->set_label( sMasterType ); + } + + void FormLinkDialog::initializeFieldRowsFrom( std::vector< OUString >& _rDetailFields, std::vector< OUString >& _rMasterFields ) + { + // our UI does allow 4 fields max + _rDetailFields.resize( 4 ); + _rMasterFields.resize( 4 ); + + FieldLinkRow* aRows[] = { + m_xRow1.get(), m_xRow2.get(), m_xRow3.get(), m_xRow4.get() + }; + for ( sal_Int32 i = 0; i < 4; ++i ) + { + aRows[ i ]->SetFieldName( FieldLinkRow::eDetailField, _rDetailFields[i] ); + aRows[ i ]->SetFieldName( FieldLinkRow::eMasterField, _rMasterFields[i] ); + } + } + + + void FormLinkDialog::initializeLinks() + { + try + { + Sequence< OUString > aDetailFields; + Sequence< OUString > aMasterFields; + + if ( m_xDetailForm.is() ) + { + m_xDetailForm->getPropertyValue( PROPERTY_DETAILFIELDS ) >>= aDetailFields; + m_xDetailForm->getPropertyValue( PROPERTY_MASTERFIELDS ) >>= aMasterFields; + } + + std::vector< OUString > aDetailFields1; + comphelper::sequenceToContainer(aDetailFields1, aDetailFields); + std::vector< OUString > aMasterFields1; + comphelper::sequenceToContainer(aMasterFields1, aMasterFields); + initializeFieldRowsFrom( aDetailFields1, aMasterFields1 ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "FormLinkDialog::initializeLinks" ); + } + } + + + void FormLinkDialog::updateOkButton() + { + // in all rows, there must be either two valid selections, or none at all + // If there is at least one row with exactly one valid selection, then the + // OKButton needs to be disabled + bool bEnable = true; + + const FieldLinkRow* aRows[] = { + m_xRow1.get(), m_xRow2.get(), m_xRow3.get(), m_xRow4.get() + }; + + for ( sal_Int32 i = 0; ( i < 4 ) && bEnable; ++i ) + { + OUString sNotInterestedInRightNow; + if ( aRows[ i ]->GetFieldName( FieldLinkRow::eDetailField, sNotInterestedInRightNow ) + != aRows[ i ]->GetFieldName( FieldLinkRow::eMasterField, sNotInterestedInRightNow ) + ) + bEnable = false; + } + + m_xOK->set_sensitive(bEnable); + } + + OUString FormLinkDialog::getFormDataSourceType( const Reference< XPropertySet >& _rxForm ) + { + OUString sReturn; + if ( !_rxForm.is() ) + return sReturn; + + try + { + sal_Int32 nCommandType = CommandType::COMMAND; + OUString sCommand; + + _rxForm->getPropertyValue( PROPERTY_COMMANDTYPE ) >>= nCommandType; + _rxForm->getPropertyValue( PROPERTY_COMMAND ) >>= sCommand; + + if ( ( nCommandType == CommandType::TABLE ) + || ( nCommandType == CommandType::QUERY ) + ) + sReturn = sCommand; + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "FormLinkDialog::getFormDataSourceType" ); + } + return sReturn; + } + + void FormLinkDialog::getFormFields( const Reference< XPropertySet >& _rxForm, Sequence< OUString >& /* [out] */ _rNames ) const + { + _rNames.realloc( 0 ); + + ::dbtools::SQLExceptionInfo aErrorInfo; + OUString sCommand; + try + { + weld::WaitObject aWaitCursor(m_xDialog.get()); + + OSL_ENSURE( _rxForm.is(), "FormLinkDialog::getFormFields: invalid form!" ); + + sal_Int32 nCommandType = CommandType::COMMAND; + + _rxForm->getPropertyValue( PROPERTY_COMMANDTYPE ) >>= nCommandType; + _rxForm->getPropertyValue( PROPERTY_COMMAND ) >>= sCommand; + + Reference< XConnection > xConnection; + ensureFormConnection( _rxForm, xConnection ); + + _rNames = ::dbtools::getFieldNamesByCommandDescriptor( + xConnection, + nCommandType, + sCommand, + &aErrorInfo + ); + } + catch (const SQLContext& e) { aErrorInfo = e; } + catch (const SQLWarning& e) { aErrorInfo = e; } + catch (const SQLException& e ) { aErrorInfo = e; } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "FormLinkDialog::getFormFields: caught a non-SQL exception!" ); + } + + if ( !aErrorInfo.isValid() ) + return; + + OUString sErrorMessage; + { + sErrorMessage = PcrRes(STR_ERROR_RETRIEVING_COLUMNS); + sErrorMessage = sErrorMessage.replaceFirst("#", sCommand); + } + + SQLContext aContext(sErrorMessage, {}, {}, 0, aErrorInfo.get(), {}); + ::dbtools::showError(aContext, m_xDialog->GetXWindow(), m_xContext); + } + + void FormLinkDialog::ensureFormConnection( const Reference< XPropertySet >& _rxFormProps, Reference< XConnection >& /* [out] */ _rxConnection ) const + { + OSL_PRECOND( _rxFormProps.is(), "FormLinkDialog::ensureFormConnection: invalid form!" ); + if ( !_rxFormProps.is() ) + return; + if ( _rxFormProps->getPropertySetInfo()->hasPropertyByName(PROPERTY_ACTIVE_CONNECTION) ) + _rxConnection.set(_rxFormProps->getPropertyValue(PROPERTY_ACTIVE_CONNECTION),UNO_QUERY); + + if ( !_rxConnection.is() ) + _rxConnection = ::dbtools::connectRowset( Reference< XRowSet >( _rxFormProps, UNO_QUERY ), m_xContext, nullptr ); + } + + + void FormLinkDialog::getConnectionMetaData( const Reference< XPropertySet >& _rxFormProps, Reference< XDatabaseMetaData >& /* [out] */ _rxMeta ) + { + if ( _rxFormProps.is() ) + { + Reference< XConnection > xConnection; + if ( !::dbtools::isEmbeddedInDatabase( _rxFormProps, xConnection ) ) + _rxFormProps->getPropertyValue( PROPERTY_ACTIVE_CONNECTION ) >>= xConnection; + if ( xConnection.is() ) + _rxMeta = xConnection->getMetaData(); + } + } + + + Reference< XPropertySet > FormLinkDialog::getCanonicUnderlyingTable( const Reference< XPropertySet >& _rxFormProps ) const + { + Reference< XPropertySet > xTable; + try + { + Reference< XTablesSupplier > xTablesInForm( ::dbtools::getCurrentSettingsComposer( _rxFormProps, m_xContext, nullptr ), UNO_QUERY ); + Reference< XNameAccess > xTables; + if ( xTablesInForm.is() ) + xTables = xTablesInForm->getTables(); + Sequence< OUString > aTableNames; + if ( xTables.is() ) + aTableNames = xTables->getElementNames(); + + if ( aTableNames.getLength() == 1 ) + { + xTables->getByName( aTableNames[ 0 ] ) >>= xTable; + OSL_ENSURE( xTable.is(), "FormLinkDialog::getCanonicUnderlyingTable: invalid table!" ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "FormLinkDialog::getCanonicUnderlyingTable" ); + } + return xTable; + } + + + bool FormLinkDialog::getExistingRelation( const Reference< XPropertySet >& _rxLHS, const Reference< XPropertySet >& /*_rxRHS*/, + // TODO: fix the usage of _rxRHS. This is issue #i81956#. + std::vector< OUString >& _rLeftFields, std::vector< OUString >& _rRightFields ) + { + try + { + Reference< XKeysSupplier > xSuppKeys( _rxLHS, UNO_QUERY ); + Reference< XIndexAccess > xKeys; + if ( xSuppKeys.is() ) + xKeys = xSuppKeys->getKeys(); + + if ( xKeys.is() ) + { + Reference< XPropertySet > xKey; + Reference< XColumnsSupplier > xKeyColSupp( xKey, UNO_QUERY ); + Reference< XIndexAccess > xKeyColumns; + Reference< XPropertySet > xKeyColumn; + OUString sColumnName, sRelatedColumnName; + + const sal_Int32 keyCount = xKeys->getCount(); + for ( sal_Int32 key = 0; key < keyCount; ++key ) + { + xKeys->getByIndex( key ) >>= xKey; + sal_Int32 nKeyType = 0; + xKey->getPropertyValue("Type") >>= nKeyType; + if ( nKeyType != KeyType::FOREIGN ) + continue; + + xKeyColumns.clear(); + xKeyColSupp.set(xKey, css::uno::UNO_QUERY); + if ( xKeyColSupp.is() ) + xKeyColumns.set(xKeyColSupp->getColumns(), css::uno::UNO_QUERY); + OSL_ENSURE( xKeyColumns.is(), "FormLinkDialog::getExistingRelation: could not obtain the columns for the key!" ); + + if ( !xKeyColumns.is() ) + continue; + + const sal_Int32 columnCount = xKeyColumns->getCount(); + _rLeftFields.resize( columnCount ); + _rRightFields.resize( columnCount ); + for ( sal_Int32 column = 0; column < columnCount; ++column ) + { + xKeyColumn.clear(); + xKeyColumns->getByIndex( column ) >>= xKeyColumn; + OSL_ENSURE( xKeyColumn.is(), "FormLinkDialog::getExistingRelation: invalid key column!" ); + if ( xKeyColumn.is() ) + { + xKeyColumn->getPropertyValue( PROPERTY_NAME ) >>= sColumnName; + xKeyColumn->getPropertyValue("RelatedColumn") >>= sRelatedColumnName; + + _rLeftFields[ column ] = sColumnName; + _rRightFields[ column ] = sRelatedColumnName; + } + } + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "FormLinkDialog::getExistingRelation" ); + } + + return ( !_rLeftFields.empty() ) && ( !_rLeftFields[ 0 ].isEmpty() ); + } + + + void FormLinkDialog::initializeSuggest() + { + if ( !m_xDetailForm.is() || !m_xMasterForm.is() ) + return; + + try + { + // only show the button when both forms are based on the same data source + OUString sMasterDS, sDetailDS; + m_xMasterForm->getPropertyValue( PROPERTY_DATASOURCE ) >>= sMasterDS; + m_xDetailForm->getPropertyValue( PROPERTY_DATASOURCE ) >>= sDetailDS; + bool bEnable = ( sMasterDS == sDetailDS ); + + // only show the button when the connection supports relations + if ( bEnable ) + { + Reference< XDatabaseMetaData > xMeta; + getConnectionMetaData( m_xDetailForm, xMeta ); + OSL_ENSURE( xMeta.is(), "FormLinkDialog::initializeSuggest: unable to retrieve the meta data for the connection!" ); + try + { + bEnable = xMeta.is() && xMeta->supportsIntegrityEnhancementFacility(); + } + catch(const Exception&) + { + bEnable = false; + } + } + + // only enable the button if there is a "canonic" table underlying both forms + Reference< XPropertySet > xDetailTable, xMasterTable; + if ( bEnable ) + { + xDetailTable = getCanonicUnderlyingTable( m_xDetailForm ); + xMasterTable = getCanonicUnderlyingTable( m_xMasterForm ); + bEnable = xDetailTable.is() && xMasterTable.is(); + } + + // only enable the button if there is a relation between both tables + m_aRelationDetailColumns.clear(); + m_aRelationMasterColumns.clear(); + if ( bEnable ) + { + bEnable = getExistingRelation( xDetailTable, xMasterTable, m_aRelationDetailColumns, m_aRelationMasterColumns ); + SAL_WARN_IF( m_aRelationMasterColumns.size() != m_aRelationDetailColumns.size(), + "extensions.propctrlr", + "FormLinkDialog::initializeSuggest: nonsense!" ); + if ( m_aRelationMasterColumns.empty() ) + { // okay, there is no relation "pointing" (via a foreign key) from the detail table to the master table + // but perhaps the other way round (would make less sense, but who knows ...) + bEnable = getExistingRelation( xMasterTable, xDetailTable, m_aRelationMasterColumns, m_aRelationDetailColumns ); + } + } + + // only enable the button if the relation contains at most 4 field pairs + if ( bEnable ) + { + bEnable = ( m_aRelationMasterColumns.size() <= 4 ); + } + + m_xSuggest->set_sensitive(bEnable); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "FormLinkDialog::initializeSuggest" ); + } + } + + IMPL_LINK_NOARG( FormLinkDialog, OnSuggest, weld::Button&, void ) + { + initializeFieldRowsFrom( m_aRelationDetailColumns, m_aRelationMasterColumns ); + } + + IMPL_LINK_NOARG( FormLinkDialog, OnFieldChanged, FieldLinkRow&, void ) + { + updateOkButton(); + } + + IMPL_LINK_NOARG( FormLinkDialog, OnInitialize, void*, void ) + { + initializeColumnLabels(); + initializeFieldLists(); + initializeLinks(); + initializeSuggest(); + } + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/formlinkdialog.hxx b/extensions/source/propctrlr/formlinkdialog.hxx new file mode 100644 index 0000000000..58e9fe27de --- /dev/null +++ b/extensions/source/propctrlr/formlinkdialog.hxx @@ -0,0 +1,127 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/weld.hxx> + +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <memory> + + +namespace pcr +{ + class FieldLinkRow; + + //= FormLinkDialog + + class FormLinkDialog : public weld::GenericDialogController + { + private: + css::uno::Reference< css::uno::XComponentContext > + m_xContext; + css::uno::Reference< css::beans::XPropertySet > + m_xDetailForm; + css::uno::Reference< css::beans::XPropertySet > + m_xMasterForm; + + std::vector< OUString > m_aRelationDetailColumns; + std::vector< OUString > m_aRelationMasterColumns; + + OUString m_sDetailLabel; + OUString m_sMasterLabel; + + std::unique_ptr<weld::Label> m_xExplanation; + std::unique_ptr<weld::Label> m_xDetailLabel; + std::unique_ptr<weld::Label> m_xMasterLabel; + std::unique_ptr<FieldLinkRow> m_xRow1; + std::unique_ptr<FieldLinkRow> m_xRow2; + std::unique_ptr<FieldLinkRow> m_xRow3; + std::unique_ptr<FieldLinkRow> m_xRow4; + std::unique_ptr<weld::Button> m_xOK; + std::unique_ptr<weld::Button> m_xSuggest; + + public: + FormLinkDialog( + weld::Window* _pParent, + const css::uno::Reference< css::beans::XPropertySet >& _rxDetailForm, + const css::uno::Reference< css::beans::XPropertySet >& _rxMasterForm, + const css::uno::Reference< css::uno::XComponentContext >& _rxContext, + const OUString& _sExplanation = OUString(), + OUString _sDetailLabel = OUString(), + OUString _sMasterLabel = OUString() + ); + + virtual ~FormLinkDialog() override; + + // Dialog overridables + virtual short run() override; + + private: + DECL_LINK( OnSuggest, weld::Button&, void ); + DECL_LINK( OnFieldChanged, FieldLinkRow&, void ); + DECL_LINK( OnInitialize, void*, void); + + void updateOkButton(); + void initializeFieldLists(); + void initializeColumnLabels(); + void initializeLinks(); + void initializeSuggest(); + void commitLinkPairs(); + + void initializeFieldRowsFrom( + std::vector< OUString >& _rDetailFields, + std::vector< OUString >& _rMasterFields + ); + + static OUString getFormDataSourceType( + const css::uno::Reference< css::beans::XPropertySet >& _rxForm + ); + + void getFormFields( + const css::uno::Reference< css::beans::XPropertySet >& _rxForm, + css::uno::Sequence< OUString >& /* [out] */ _rNames + ) const; + + void ensureFormConnection( + const css::uno::Reference< css::beans::XPropertySet >& _rxFormProps, + css::uno::Reference< css::sdbc::XConnection >& /* [out] */ _rxConnection + ) const; + + static void getConnectionMetaData( + const css::uno::Reference< css::beans::XPropertySet >& _rxFormProps, + css::uno::Reference< css::sdbc::XDatabaseMetaData >& /* [out] */ _rxMeta + ); + + css::uno::Reference< css::beans::XPropertySet > + getCanonicUnderlyingTable( const css::uno::Reference< css::beans::XPropertySet >& _rxFormProps ) const; + static bool getExistingRelation( + const css::uno::Reference< css::beans::XPropertySet >& _rxLHS, + const css::uno::Reference< css::beans::XPropertySet >& _rxRHS, + std::vector< OUString >& /* [out] */ _rLeftFields, + std::vector< OUString >& /* [out] */ _rRightFields + ); + }; + +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/formmetadata.cxx b/extensions/source/propctrlr/formmetadata.cxx new file mode 100644 index 0000000000..c6e1e0d602 --- /dev/null +++ b/extensions/source/propctrlr/formmetadata.cxx @@ -0,0 +1,694 @@ +/* -*- 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 "formmetadata.hxx" +#include "formstrings.hxx" +#include "modulepcr.hxx" +#include <command.hrc> +#include <helpids.h> +#include <strings.hrc> +#include <stringarrays.hrc> +#include <comphelper/extract.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <algorithm> +#include <utility> + +namespace pcr +{ + using namespace ::com::sun::star::uno; + + + //= OPropertyInfoImpl + + struct OPropertyInfoImpl + { + OUString sName; + OUString sTranslation; + OUString sHelpId; + sal_Int32 nId; + sal_uInt16 nPos; + sal_uInt32 nUIFlags; + + OPropertyInfoImpl( + OUString aName, + sal_Int32 _nId, + OUString aTranslation, + sal_uInt16 nPosId, + OUString , + sal_uInt32 _nUIFlags); + }; + + + OPropertyInfoImpl::OPropertyInfoImpl(OUString _aName, sal_Int32 _nId, + OUString aString, sal_uInt16 nP, OUString sHid, sal_uInt32 _nUIFlags) + :sName(std::move(_aName)) + ,sTranslation(std::move(aString)) + ,sHelpId(std::move(sHid)) + ,nId(_nId) + ,nPos(nP) + ,nUIFlags(_nUIFlags) + { + } + + namespace { + + // Compare PropertyInfo + struct PropertyInfoLessByName + { + bool operator()( const OPropertyInfoImpl& _rLHS, const OPropertyInfoImpl& _rRHS ) + { + return _rLHS.sName.compareTo( _rRHS.sName ) < 0; + } + }; + + } + + //= OPropertyInfoService + +#define DEF_INFO( ident, uinameres, pos, helpid, flags ) \ + OPropertyInfoImpl( PROPERTY_##ident, PROPERTY_ID_##ident, \ + PcrRes( RID_STR_##uinameres ), pos, HID_PROP_##helpid, flags ) + +#define DEF_INFO_1( ident, uinameres, pos, helpid, flag1 ) \ + DEF_INFO( ident, uinameres, pos, helpid, PROP_FLAG_##flag1 ) + +#define DEF_INFO_2( ident, uinameres, pos, helpid, flag1, flag2 ) \ + DEF_INFO( ident, uinameres, pos, helpid, PROP_FLAG_##flag1 | PROP_FLAG_##flag2 ) + +#define DEF_INFO_3( ident, uinameres, pos, helpid, flag1, flag2, flag3 ) \ + DEF_INFO( ident, uinameres, pos, helpid, PROP_FLAG_##flag1 | PROP_FLAG_##flag2 | PROP_FLAG_##flag3 ) + +#define DEF_INFO_4( ident, uinameres, pos, helpid, flag1, flag2, flag3, flag4 ) \ + DEF_INFO( ident, uinameres, pos, helpid, PROP_FLAG_##flag1 | PROP_FLAG_##flag2 | PROP_FLAG_##flag3 | PROP_FLAG_##flag4 ) + + std::size_t OPropertyInfoService::s_nCount = 0; + OPropertyInfoImpl* OPropertyInfoService::s_pPropertyInfos = nullptr; + + const OPropertyInfoImpl* OPropertyInfoService::getPropertyInfo() + { + if ( s_pPropertyInfos ) + return s_pPropertyInfos; + + static OPropertyInfoImpl aPropertyInfos[] = + { + /* + DEF_INFO_?( propname and id, resource id, pos, help id, flags ), + */ + DEF_INFO_3( NAME, NAME, 0, NAME, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( TITLE, TITLE, 1, TITLE, FORM_VISIBLE, DIALOG_VISIBLE ), + DEF_INFO_3( LABEL, LABEL, 2, LABEL, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( CONTROLLABEL, LABELCONTROL, 3, CONTROLLABEL, FORM_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( WRITING_MODE, WRITING_MODE, 4, WRITING_MODE, FORM_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_3( GROUP_NAME, GROUP_NAME, 5, GROUP_NAME, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( TEXT, TEXT, 6, TEXT, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( MAXTEXTLEN, MAXTEXTLEN, 7, MAXTEXTLEN, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( EDITMASK, EDITMASK, 8, EDITMASK, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( LITERALMASK, LITERALMASK, 9, LITERALMASK, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( STRICTFORMAT, STRICTFORMAT, 10, STRICTFORMAT, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( ENABLED, ENABLED, 11, ENABLED, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( ENABLE_VISIBLE, ENABLE_VISIBLE, 12, ENABLE_VISIBLE, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( READONLY, READONLY, 13, READONLY, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( PRINTABLE, PRINTABLE, 14, PRINTABLE, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( STEP, STEP, 15, STEP, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( WHEEL_BEHAVIOR, WHEEL_BEHAVIOR, 16, WHEEL_BEHAVIOR, FORM_VISIBLE | PROP_FLAG_REPORT_INVISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_3( TABSTOP, TABSTOP, 17, TABSTOP, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( TABINDEX, TABINDEX, 18, TABINDEX, FORM_VISIBLE, DIALOG_VISIBLE ), + + DEF_INFO_2( BOUND_CELL, BOUND_CELL, 19, BOUND_CELL, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_3( CELL_EXCHANGE_TYPE,CELL_EXCHANGE_TYPE, 20, CELL_EXCHANGE_TYPE,FORM_VISIBLE, DATA_PROPERTY, ENUM ), + DEF_INFO_2( LIST_CELL_RANGE, LIST_CELL_RANGE, 21, LIST_CELL_RANGE, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_3( CONTROLSOURCE, CONTROLSOURCE, 22, CONTROLSOURCE, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + DEF_INFO_3( EMPTY_IS_NULL, EMPTY_IS_NULL, 23, EMPTY_IS_NULL, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + DEF_INFO_3( INPUT_REQUIRED, INPUT_REQUIRED, 24, INPUT_REQUIRED, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + DEF_INFO_3( REFVALUE, REFVALUE, 25, REFVALUE, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + DEF_INFO_3( UNCHECKEDREFVALUE, UNCHECKEDREFVALUE, 26, UNCHECKEDREFVALUE, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + DEF_INFO_3( DATASOURCE, DATASOURCE, 27, DATASOURCE, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + DEF_INFO_4( COMMANDTYPE, CURSORSOURCETYPE, 28, CURSORSOURCETYPE, FORM_VISIBLE, DATA_PROPERTY, ENUM, COMPOSEABLE ), + DEF_INFO_3( COMMAND, CURSORSOURCE, 29, CURSORSOURCE, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + DEF_INFO_3( ESCAPE_PROCESSING, ESCAPE_PROCESSING, 30, ESCAPE_PROCESSING, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + DEF_INFO_3( FILTER, FILTER, 31, FILTER, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + DEF_INFO_3( SORT, SORT_CRITERIA, 32, SORT_CRITERIA, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + DEF_INFO_2( MASTERFIELDS, MASTERFIELDS, 33, MASTERFIELDS, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( DETAILFIELDS, SLAVEFIELDS, 34, SLAVEFIELDS, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_3( ALLOWADDITIONS, ALLOW_ADDITIONS, 35, ALLOW_ADDITIONS, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + DEF_INFO_3( ALLOWEDITS, ALLOW_EDITS, 36, ALLOW_EDITS, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + DEF_INFO_3( ALLOWDELETIONS, ALLOW_DELETIONS, 37, ALLOW_DELETIONS, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + DEF_INFO_3( INSERTONLY, DATAENTRY, 38, DATAENTRY, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + DEF_INFO_4( NAVIGATION, NAVIGATION, 39, NAVIGATION, FORM_VISIBLE, DATA_PROPERTY, ENUM, COMPOSEABLE ), + DEF_INFO_4( CYCLE, CYCLE, 40, CYCLE, FORM_VISIBLE, DATA_PROPERTY, ENUM, COMPOSEABLE ), + DEF_INFO_3( FILTERPROPOSAL, FILTERPROPOSAL, 41, FILTERPROPOSAL, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + DEF_INFO_4( LISTSOURCETYPE, LISTSOURCETYPE, 42, LISTSOURCETYPE, FORM_VISIBLE, DATA_PROPERTY, ENUM, COMPOSEABLE ), + DEF_INFO_3( LISTSOURCE, LISTSOURCE, 43, LISTSOURCE, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + DEF_INFO_3( BOUNDCOLUMN, BOUNDCOLUMN, 44, BOUNDCOLUMN, FORM_VISIBLE, DATA_PROPERTY, COMPOSEABLE ), + + // <!-----------------> + // XML node binding + DEF_INFO_2( LIST_BINDING, LIST_BINDING, 45, LIST_BINDING, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XML_DATA_MODEL, XML_DATA_MODEL, 46, XML_DATA_MODEL, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( BINDING_NAME, BINDING_NAME, 47, BINDING_NAME, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( BIND_EXPRESSION, BIND_EXPRESSION, 48, BIND_EXPRESSION, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_REQUIRED, XSD_REQUIRED, 49, XSD_REQUIRED, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_RELEVANT, XSD_RELEVANT, 50, XSD_RELEVANT, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_READONLY, XSD_READONLY, 51, XSD_READONLY, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_CONSTRAINT, XSD_CONSTRAINT, 52, XSD_CONSTRAINT, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_CALCULATION, XSD_CALCULATION, 53, XSD_CALCULATION, FORM_VISIBLE, DATA_PROPERTY ), + + // data type + DEF_INFO_2( XSD_DATA_TYPE, XSD_DATA_TYPE, 54, XSD_DATA_TYPE, FORM_VISIBLE, DATA_PROPERTY ), + // data types facets + // common + DEF_INFO_3( XSD_WHITESPACES, XSD_WHITESPACES, 55, XSD_WHITESPACES, FORM_VISIBLE, DATA_PROPERTY, ENUM ), + DEF_INFO_2( XSD_PATTERN, XSD_PATTERN, 56, XSD_PATTERN, FORM_VISIBLE, DATA_PROPERTY ), + // string + DEF_INFO_2( XSD_LENGTH, XSD_LENGTH, 57, XSD_LENGTH, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MIN_LENGTH, XSD_MIN_LENGTH, 58, XSD_MIN_LENGTH, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MAX_LENGTH, XSD_MAX_LENGTH, 59, XSD_MAX_LENGTH, FORM_VISIBLE, DATA_PROPERTY ), + // decimal + DEF_INFO_2( XSD_TOTAL_DIGITS, XSD_TOTAL_DIGITS, 60, XSD_TOTAL_DIGITS, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_FRACTION_DIGITS,XSD_FRACTION_DIGITS,61,XSD_FRACTION_DIGITS,FORM_VISIBLE, DATA_PROPERTY ), + // int value types (year, month, day) + DEF_INFO_2( XSD_MAX_INCLUSIVE_INT, XSD_MAX_INCLUSIVE, 62, XSD_MAX_INCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MAX_EXCLUSIVE_INT, XSD_MAX_EXCLUSIVE, 63, XSD_MAX_EXCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MIN_INCLUSIVE_INT, XSD_MIN_INCLUSIVE, 64, XSD_MIN_INCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MIN_EXCLUSIVE_INT, XSD_MIN_EXCLUSIVE, 65, XSD_MIN_EXCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + // double value types (double, float, decimal) + DEF_INFO_2( XSD_MAX_INCLUSIVE_DOUBLE, XSD_MAX_INCLUSIVE, 66, XSD_MAX_INCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MAX_EXCLUSIVE_DOUBLE, XSD_MAX_EXCLUSIVE, 67, XSD_MAX_EXCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MIN_INCLUSIVE_DOUBLE, XSD_MIN_INCLUSIVE, 68, XSD_MIN_INCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MIN_EXCLUSIVE_DOUBLE, XSD_MIN_EXCLUSIVE, 69, XSD_MIN_EXCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + // date value type + DEF_INFO_2( XSD_MAX_INCLUSIVE_DATE, XSD_MAX_INCLUSIVE, 70, XSD_MAX_INCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MAX_EXCLUSIVE_DATE, XSD_MAX_EXCLUSIVE, 71, XSD_MAX_EXCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MIN_INCLUSIVE_DATE, XSD_MIN_INCLUSIVE, 72, XSD_MIN_INCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MIN_EXCLUSIVE_DATE, XSD_MIN_EXCLUSIVE, 73, XSD_MIN_EXCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + // time value type + DEF_INFO_2( XSD_MAX_INCLUSIVE_TIME, XSD_MAX_INCLUSIVE, 74, XSD_MAX_INCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MAX_EXCLUSIVE_TIME, XSD_MAX_EXCLUSIVE, 75, XSD_MAX_EXCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MIN_INCLUSIVE_TIME, XSD_MIN_INCLUSIVE, 76, XSD_MIN_INCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MIN_EXCLUSIVE_TIME, XSD_MIN_EXCLUSIVE, 77, XSD_MIN_EXCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + // dateTime value type + DEF_INFO_2( XSD_MAX_INCLUSIVE_DATE_TIME, XSD_MAX_INCLUSIVE, 78, XSD_MAX_INCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MAX_EXCLUSIVE_DATE_TIME, XSD_MAX_EXCLUSIVE, 79, XSD_MAX_EXCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MIN_INCLUSIVE_DATE_TIME, XSD_MIN_INCLUSIVE, 80, XSD_MIN_INCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + DEF_INFO_2( XSD_MIN_EXCLUSIVE_DATE_TIME, XSD_MIN_EXCLUSIVE, 81, XSD_MIN_EXCLUSIVE, FORM_VISIBLE, DATA_PROPERTY ), + // <!-----------------> + + DEF_INFO_2( HIDDEN_VALUE, VALUE, 82, HIDDEN_VALUE, FORM_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( VALUE, VALUE, 83, VALUE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( VALUEMIN, VALUEMIN, 84, VALUEMIN, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( VALUEMAX, VALUEMAX, 85, VALUEMAX, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( VALUESTEP, VALUESTEP, 86, VALUESTEP, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( DEFAULT_VALUE, DEFAULTVALUE, 87, DEFAULT_LONG_VALUE,FORM_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( DECIMAL_ACCURACY, DECIMAL_ACCURACY, 88, DECIMAL_ACCURACY, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( SHOWTHOUSANDSEP, SHOWTHOUSANDSEP, 89, SHOWTHOUSANDSEP, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + + DEF_INFO_3( CURRENCYSYMBOL, CURRENCYSYMBOL, 90, CURRENCYSYMBOL, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( CURRSYM_POSITION, CURRSYM_POSITION, 91, CURRSYM_POSITION, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + + DEF_INFO_2( DATE, DATE, 92, DATE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( DATEMIN, DATEMIN, 93, DATEMIN, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( DATEMAX, DATEMAX, 94, DATEMAX, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_4( DATEFORMAT, DATEFORMAT, 95, DATEFORMAT, FORM_VISIBLE, DIALOG_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_2( DEFAULT_DATE, DEFAULTDATE, 96, DEFAULT_DATE, FORM_VISIBLE, COMPOSEABLE ), + + DEF_INFO_2( TIME, TIME, 97, TIME, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( TIMEMIN, TIMEMIN, 98, TIMEMIN, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( TIMEMAX, TIMEMAX, 99, TIMEMAX, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_4( TIMEFORMAT, TIMEFORMAT, 100, TIMEFORMAT, FORM_VISIBLE, DIALOG_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_2( DEFAULT_TIME, DEFAULTTIME, 101, DEFAULT_TIME, FORM_VISIBLE, COMPOSEABLE ), + + DEF_INFO_1( EFFECTIVE_VALUE, VALUE, 102, VALUE, DIALOG_VISIBLE ), + DEF_INFO_3( EFFECTIVE_MIN, VALUEMIN, 103, EFFECTIVEMIN, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( EFFECTIVE_MAX, VALUEMAX, 104, EFFECTIVEMAX, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( EFFECTIVE_DEFAULT, DEFAULTVALUE, 105, EFFECTIVEDEFAULT, FORM_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( FORMATKEY, FORMATKEY, 106, FORMATKEY, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + + DEF_INFO_3( PROGRESSVALUE, PROGRESSVALUE, 107, PROGRESSVALUE, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( PROGRESSVALUE_MIN, PROGRESSVALUE_MIN, 108, PROGRESSVALUE_MIN, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( PROGRESSVALUE_MAX, PROGRESSVALUE_MAX, 109, PROGRESSVALUE_MAX, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + + DEF_INFO_2( SCROLLVALUE, SCROLLVALUE, 110, SCROLLVALUE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( SCROLLVALUE_MIN, SCROLLVALUE_MIN, 111, SCROLLVALUE_MIN, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( SCROLLVALUE_MAX, SCROLLVALUE_MAX, 112, SCROLLVALUE_MAX, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( SCROLL_WIDTH, SCROLL_WIDTH, 113, SCROLL_WIDTH, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( SCROLL_HEIGHT, SCROLL_HEIGHT, 114, SCROLL_HEIGHT, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( SCROLL_TOP, SCROLL_TOP, 115, SCROLL_TOP, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( SCROLL_LEFT, SCROLL_LEFT, 116, SCROLL_LEFT, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( DEFAULT_SCROLLVALUE,DEFAULT_SCROLLVALUE,117,DEFAULT_SCROLLVALUE,FORM_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( LINEINCREMENT, LINEINCREMENT, 118, LINEINCREMENT, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( BLOCKINCREMENT, BLOCKINCREMENT, 119, BLOCKINCREMENT, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + + DEF_INFO_2( SPINVALUE, VALUE, 120, SPINVALUE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( SPINVALUE_MIN, VALUEMIN, 121, SPINVALUE_MIN, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( SPINVALUE_MAX, VALUEMAX, 122, SPINVALUE_MAX, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( DEFAULT_SPINVALUE,DEFAULTVALUE, 123, DEFAULT_SPINVALUE, FORM_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( SPININCREMENT, VALUESTEP, 124, SPININCREMENT, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + + DEF_INFO_3( SPIN, SPIN, 125, SPIN, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( REPEAT, REPEAT, 126, REPEAT, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( REPEAT_DELAY, REPEAT_DELAY, 127, REPEAT_DELAY, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( VISIBLESIZE, VISIBLESIZE, 128, VISIBLESIZE, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_4( ORIENTATION, ORIENTATION, 129, ORIENTATION, FORM_VISIBLE, DIALOG_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_3( FOCUSONCLICK, FOCUSONCLICK, 130, FOCUSONCLICK, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( TOGGLE, TOGGLE, 131, TOGGLE, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( DEFAULT_STATE, DEFAULT_STATE, 132, DEFAULT_STATE, FORM_VISIBLE, ENUM, COMPOSEABLE ), + + DEF_INFO_3( TEXT_ANCHOR_TYPE, ANCHOR_TYPE, 133, ANCHOR_TYPE, FORM_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_3( SHEET_ANCHOR_TYPE, ANCHOR_TYPE, 134, ANCHOR_TYPE, FORM_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_3( POSITIONX, POSITIONX, 135, POSITIONX, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( POSITIONY, POSITIONY, 136, POSITIONY, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( WIDTH, WIDTH, 137, WIDTH, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( HEIGHT, HEIGHT, 138, HEIGHT, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + + DEF_INFO_1( LISTINDEX, LISTINDEX, 139, LISTINDEX, FORM_VISIBLE ), + DEF_INFO_3( STRINGITEMLIST, STRINGITEMLIST, 140, STRINGITEMLIST, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( DEFAULT_TEXT, DEFAULTTEXT, 141, DEFAULTVALUE, FORM_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( FONT, FONT, 142, FONT, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_4( VISUALEFFECT, VISUALEFFECT, 143, VISUALEFFECT, FORM_VISIBLE, DIALOG_VISIBLE, ENUM_ONE, COMPOSEABLE ), + DEF_INFO_4( ALIGN, ALIGN, 144, ALIGN, FORM_VISIBLE, DIALOG_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_4( VERTICAL_ALIGN, VERTICAL_ALIGN, 145, VERTICAL_ALIGN, FORM_VISIBLE, DIALOG_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_3( ROWHEIGHT, ROWHEIGHT, 146, ROWHEIGHT, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( BACKGROUNDCOLOR, BACKGROUNDCOLOR, 147, BACKGROUNDCOLOR, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( SYMBOLCOLOR, SYMBOLCOLOR, 148, SYMBOLCOLOR, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( FILLCOLOR, FILLCOLOR, 149, FILLCOLOR, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( LINECOLOR, LINECOLOR, 150, LINECOLOR, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_4( BORDER, BORDER, 151, BORDER, FORM_VISIBLE, DIALOG_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_3( BORDERCOLOR, BORDERCOLOR, 152, BORDERCOLOR, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( ICONSIZE, ICONSIZE, 153, ICONSIZE, FORM_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_2( SHOW_POSITION, SHOW_POSITION, 154, SHOW_POSITION, FORM_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( SHOW_NAVIGATION, SHOW_NAVIGATION, 155, SHOW_NAVIGATION, FORM_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( SHOW_RECORDACTIONS,SHOW_RECORDACTIONS, 156, SHOW_RECORDACTIONS,FORM_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( SHOW_FILTERSORT, SHOW_FILTERSORT, 157, SHOW_FILTERSORT, FORM_VISIBLE, COMPOSEABLE ), + + DEF_INFO_3( DROPDOWN, DROPDOWN, 158, DROPDOWN, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( LINECOUNT, LINECOUNT, 159, LINECOUNT, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( AUTOCOMPLETE, AUTOCOMPLETE, 160, AUTOCOMPLETE, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( MULTILINE, MULTILINE, 161, MULTILINE, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( WORDBREAK, WORDBREAK, 162, WORDBREAK, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( TEXTTYPE, TEXTTYPE, 163, TEXTTYPE, FORM_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_3( LINEEND_FORMAT, LINEEND_FORMAT, 164, LINEEND_FORMAT, FORM_VISIBLE, ENUM_ONE, COMPOSEABLE ), + DEF_INFO_3( MULTISELECTION, MULTISELECTION, 165, MULTISELECTION, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_4( SHOW_SCROLLBARS, SHOW_SCROLLBARS, 166, SHOW_SCROLLBARS, FORM_VISIBLE, DIALOG_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_3( HSCROLL, HSCROLL, 167, HSCROLL, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( VSCROLL, VSCROLL, 168, VSCROLL, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( BUTTONTYPE, BUTTONTYPE, 169, BUTTONTYPE, FORM_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_2( XFORMS_BUTTONTYPE, BUTTONTYPE, 170, BUTTONTYPE, FORM_VISIBLE, ENUM ), + DEF_INFO_1( SUBMISSION_ID, SUBMISSION_ID, 171, SUBMISSION_ID, FORM_VISIBLE ), + DEF_INFO_2( PUSHBUTTONTYPE, PUSHBUTTONTYPE, 172, PUSHBUTTONTYPE, DIALOG_VISIBLE, ENUM ), + DEF_INFO_2( TARGET_URL, TARGET_URL, 173, TARGET_URL, FORM_VISIBLE, COMPOSEABLE ), + DEF_INFO_1( TARGET_FRAME, TARGET_FRAME, 174, TARGET_FRAME, FORM_VISIBLE ), + DEF_INFO_2( SUBMIT_ACTION, SUBMIT_ACTION, 175, SUBMIT_ACTION, FORM_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( SUBMIT_TARGET, SUBMIT_TARGET, 176, SUBMIT_TARGET, FORM_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( SUBMIT_ENCODING, SUBMIT_ENCODING, 177, SUBMIT_ENCODING, FORM_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_3( SUBMIT_METHOD, SUBMIT_METHOD, 178, SUBMIT_METHOD, FORM_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_3( STATE, STATE, 179, STATE, DIALOG_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_3( DEFAULTBUTTON, DEFAULT_BUTTON, 180, DEFAULT_BUTTON, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( IMAGE_URL, IMAGE_URL, 181, IMAGE_URL, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_4( IMAGEPOSITION, IMAGEPOSITION, 182, IMAGEPOSITION, FORM_VISIBLE, DIALOG_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_3( SCALEIMAGE, SCALEIMAGE, 183, SCALEIMAGE, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_4( SCALE_MODE, SCALEIMAGE, 184, SCALEIMAGE, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE, ENUM ), + DEF_INFO_2( DEFAULT_SELECT_SEQ,DEFAULT_SELECT_SEQ, 185, DEFAULT_SELECT_SEQ,FORM_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( SELECTEDITEMS, SELECTEDITEMS, 186, SELECTEDITEMS, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( ECHO_CHAR, ECHO_CHAR, 187, ECHO_CHAR, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( HIDEINACTIVESELECTION, HIDEINACTIVESELECTION, 188, HIDEINACTIVESELECTION, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( TRISTATE, TRISTATE, 189, TRISTATE, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( HASNAVIGATION, NAVIGATION, 190, NAVIGATIONBAR, FORM_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( RECORDMARKER, RECORDMARKER, 191, RECORDMARKER, FORM_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( TAG, TAG, 192, TAG, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( HELPTEXT, HELPTEXT, 193, HELPTEXT, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( HELPURL, HELPURL, 194, HELPURL, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( SELECTION_TYPE, SELECTION_TYPE, 195, SELECTION_TYPE, DIALOG_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_2( ROOT_DISPLAYED, ROOT_DISPLAYED, 196, ROOT_DISPLAYED, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( SHOWS_HANDLES, SHOWS_HANDLES, 197, SHOWS_HANDLES, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( SHOWS_ROOT_HANDLES, SHOWS_ROOT_HANDLES, 198, SHOWS_ROOT_HANDLES, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( EDITABLE, EDITABLE, 199, EDITABLE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( INVOKES_STOP_NOT_EDITING, INVOKES_STOP_NOT_EDITING, 200, INVOKES_STOP_NOT_EDITING, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( DECORATION, DECORATION, 201, DECORATION, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( NOLABEL, NOLABEL, 202, NOLABEL, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_3( SELECTIONMODEL, SELECTIONMODEL, 203, SELECTIONMODEL, DIALOG_VISIBLE, ENUM, COMPOSEABLE ), + DEF_INFO_2( USEGRIDLINE, USEGRIDLINE, 204, USEGRIDLINE, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( GRIDLINECOLOR, GRIDLINECOLOR, 205, GRIDLINECOLOR, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( SHOWCOLUMNHEADER, SHOWCOLUMNHEADER, 206, SHOWCOLUMNHEADER, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( SHOWROWHEADER, SHOWROWHEADER, 207, SHOWROWHEADER, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( HEADERBACKGROUNDCOLOR, HEADERBACKGROUNDCOLOR, 208, HEADERBACKGROUNDCOLOR, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( HEADERTEXTCOLOR, HEADERTEXTCOLOR, 209, HEADERTEXTCOLOR, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( ACTIVESELECTIONBACKGROUNDCOLOR, ACTIVESELECTIONBACKGROUNDCOLOR, 210, ACTIVESELECTIONBACKGROUNDCOLOR, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( ACTIVESELECTIONTEXTCOLOR, ACTIVESELECTIONTEXTCOLOR, 211, ACTIVESELECTIONTEXTCOLOR, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( INACTIVESELECTIONBACKGROUNDCOLOR, INACTIVESELECTIONBACKGROUNDCOLOR, 212, INACTIVESELECTIONBACKGROUNDCOLOR, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( INACTIVESELECTIONTEXTCOLOR, INACTIVESELECTIONTEXTCOLOR, 213, INACTIVESELECTIONTEXTCOLOR, DIALOG_VISIBLE, COMPOSEABLE ), + DEF_INFO_2( URL, URL, 214, URL, DIALOG_VISIBLE, COMPOSEABLE ), + + DEF_INFO_3( AUTOGROW, AUTOGROW, 215, AUTOGROW, FORM_VISIBLE, DIALOG_VISIBLE, COMPOSEABLE) + }; + + s_pPropertyInfos = aPropertyInfos; + s_nCount = std::size(aPropertyInfos); + + // sort + std::sort( s_pPropertyInfos, s_pPropertyInfos + s_nCount, PropertyInfoLessByName() ); + +#if OSL_DEBUG_LEVEL > 0 + for ( const OPropertyInfoImpl* pCheck = s_pPropertyInfos; pCheck != s_pPropertyInfos + s_nCount - 1; ++pCheck ) + { + OSL_ENSURE( pCheck->sName != ( pCheck + 1 )->sName, "OPropertyInfoService::getPropertyInfo: duplicate entry in the table!" ); + } +#endif + + return s_pPropertyInfos; + } + + + sal_Int32 OPropertyInfoService::getPropertyId(const OUString& _rName) const + { + const OPropertyInfoImpl* pInfo = getPropertyInfo(_rName); + return pInfo ? pInfo->nId : -1; + } + + + OUString OPropertyInfoService::getPropertyTranslation(sal_Int32 _nId) const + { + const OPropertyInfoImpl* pInfo = getPropertyInfo(_nId); + return pInfo ? pInfo->sTranslation : OUString(); + } + + OUString OPropertyInfoService::getPropertyHelpId(sal_Int32 _nId) const + { + const OPropertyInfoImpl* pInfo = getPropertyInfo(_nId); + return pInfo ? pInfo->sHelpId : OUString(); + } + + sal_Int16 OPropertyInfoService::getPropertyPos(sal_Int32 _nId) const + { + const OPropertyInfoImpl* pInfo = getPropertyInfo(_nId); + return pInfo ? pInfo->nPos : 0xFFFF; + } + + sal_uInt32 OPropertyInfoService::getPropertyUIFlags(sal_Int32 _nId) const + { + const OPropertyInfoImpl* pInfo = getPropertyInfo(_nId); + return pInfo ? pInfo->nUIFlags : 0; + } + + std::vector< OUString > OPropertyInfoService::getPropertyEnumRepresentations(sal_Int32 _nId) const + { + OSL_ENSURE( ( ( getPropertyUIFlags( _nId ) & PROP_FLAG_ENUM ) != 0 ) || ( _nId == PROPERTY_ID_TARGET_FRAME ), + "OPropertyInfoService::getPropertyEnumRepresentations: this is no enum property!" ); + + if (_nId == PROPERTY_ID_SUBMIT_METHOD) + { + return { "Get", "Post" }; + } + const TranslateId* pStringItemsResId = nullptr; + std::size_t nElements = 0; + switch ( _nId ) + { + case PROPERTY_ID_IMAGEPOSITION: + pStringItemsResId = RID_RSC_ENUM_IMAGE_POSITION; + nElements = std::size(RID_RSC_ENUM_IMAGE_POSITION); + break; + case PROPERTY_ID_BORDER: + pStringItemsResId = RID_RSC_ENUM_BORDER_TYPE; + nElements = std::size(RID_RSC_ENUM_BORDER_TYPE); + break; + case PROPERTY_ID_ICONSIZE: + pStringItemsResId = RID_RSC_ENUM_ICONSIZE_TYPE; + nElements = std::size(RID_RSC_ENUM_ICONSIZE_TYPE); + break; + case PROPERTY_ID_COMMANDTYPE: + pStringItemsResId = RID_RSC_ENUM_COMMAND_TYPE; + nElements = std::size(RID_RSC_ENUM_COMMAND_TYPE); + break; + case PROPERTY_ID_LISTSOURCETYPE: + pStringItemsResId = RID_RSC_ENUM_LISTSOURCE_TYPE; + nElements = std::size(RID_RSC_ENUM_LISTSOURCE_TYPE); + break; + case PROPERTY_ID_ALIGN: + pStringItemsResId = RID_RSC_ENUM_ALIGNMENT; + nElements = std::size(RID_RSC_ENUM_ALIGNMENT); + break; + case PROPERTY_ID_VERTICAL_ALIGN: + pStringItemsResId = RID_RSC_ENUM_VERTICAL_ALIGN; + nElements = std::size(RID_RSC_ENUM_VERTICAL_ALIGN); + break; + case PROPERTY_ID_BUTTONTYPE: + pStringItemsResId = RID_RSC_ENUM_BUTTONTYPE; + nElements = std::size(RID_RSC_ENUM_BUTTONTYPE); + break; + case PROPERTY_ID_PUSHBUTTONTYPE: + pStringItemsResId = RID_RSC_ENUM_PUSHBUTTONTYPE; + nElements = std::size(RID_RSC_ENUM_PUSHBUTTONTYPE); + break; + case PROPERTY_ID_SUBMIT_ENCODING: + pStringItemsResId = RID_RSC_ENUM_SUBMIT_ENCODING; + nElements = std::size(RID_RSC_ENUM_SUBMIT_ENCODING); + break; + case PROPERTY_ID_DATEFORMAT: + pStringItemsResId = RID_RSC_ENUM_DATEFORMAT_LIST; + nElements = std::size(RID_RSC_ENUM_DATEFORMAT_LIST); + break; + case PROPERTY_ID_TIMEFORMAT: + pStringItemsResId = RID_RSC_ENUM_TIMEFORMAT_LIST; + nElements = std::size(RID_RSC_ENUM_TIMEFORMAT_LIST); + break; + case PROPERTY_ID_DEFAULT_STATE: + case PROPERTY_ID_STATE: + pStringItemsResId = RID_RSC_ENUM_CHECKED; + nElements = std::size(RID_RSC_ENUM_CHECKED); + break; + case PROPERTY_ID_CYCLE: + pStringItemsResId = RID_RSC_ENUM_CYCLE; + nElements = std::size(RID_RSC_ENUM_CYCLE); + break; + case PROPERTY_ID_NAVIGATION: + pStringItemsResId = RID_RSC_ENUM_NAVIGATION; + nElements = std::size(RID_RSC_ENUM_NAVIGATION); + break; + case PROPERTY_ID_TARGET_FRAME: + pStringItemsResId = RID_RSC_ENUM_SUBMIT_TARGET; + nElements = std::size(RID_RSC_ENUM_SUBMIT_TARGET); + break; + case PROPERTY_ID_ORIENTATION: + pStringItemsResId = RID_RSC_ENUM_ORIENTATION; + nElements = std::size(RID_RSC_ENUM_ORIENTATION); + break; + case PROPERTY_ID_CELL_EXCHANGE_TYPE: + pStringItemsResId = RID_RSC_ENUM_CELL_EXCHANGE_TYPE; + nElements = std::size(RID_RSC_ENUM_CELL_EXCHANGE_TYPE); + break; + case PROPERTY_ID_SHOW_SCROLLBARS: + pStringItemsResId = RID_RSC_ENUM_SCROLLBARS; + nElements = std::size(RID_RSC_ENUM_SCROLLBARS); + break; + case PROPERTY_ID_VISUALEFFECT: + pStringItemsResId = RID_RSC_ENUM_VISUALEFFECT; + nElements = std::size(RID_RSC_ENUM_VISUALEFFECT); + break; + case PROPERTY_ID_TEXTTYPE: + pStringItemsResId = RID_RSC_ENUM_TEXTTYPE; + nElements = std::size(RID_RSC_ENUM_TEXTTYPE); + break; + case PROPERTY_ID_LINEEND_FORMAT: + pStringItemsResId = RID_RSC_ENUM_LINEEND_FORMAT; + nElements = std::size(RID_RSC_ENUM_LINEEND_FORMAT); + break; + case PROPERTY_ID_XSD_WHITESPACES: + pStringItemsResId = RID_RSC_ENUM_WHITESPACE_HANDLING; + nElements = std::size(RID_RSC_ENUM_WHITESPACE_HANDLING); + break; + case PROPERTY_ID_SELECTION_TYPE: + case PROPERTY_ID_SELECTIONMODEL: + pStringItemsResId = RID_RSC_ENUM_SELECTION_TYPE; + nElements = std::size(RID_RSC_ENUM_SELECTION_TYPE); + break; + case PROPERTY_ID_SCALE_MODE: + pStringItemsResId = RID_RSC_ENUM_SCALE_MODE; + nElements = std::size(RID_RSC_ENUM_SCALE_MODE); + break; + case PROPERTY_ID_WRITING_MODE: + pStringItemsResId = RID_RSC_ENUM_WRITING_MODE; + nElements = std::size(RID_RSC_ENUM_WRITING_MODE); + break; + case PROPERTY_ID_WHEEL_BEHAVIOR: + pStringItemsResId = RID_RSC_ENUM_WHEEL_BEHAVIOR; + nElements = std::size(RID_RSC_ENUM_WHEEL_BEHAVIOR); + break; + case PROPERTY_ID_TEXT_ANCHOR_TYPE: + pStringItemsResId = RID_RSC_ENUM_TEXT_ANCHOR_TYPE; + nElements = std::size(RID_RSC_ENUM_TEXT_ANCHOR_TYPE); + break; + case PROPERTY_ID_SHEET_ANCHOR_TYPE: + pStringItemsResId = RID_RSC_ENUM_SHEET_ANCHOR_TYPE; + nElements = std::size(RID_RSC_ENUM_SHEET_ANCHOR_TYPE); + break; + default: + OSL_FAIL( "OPropertyInfoService::getPropertyEnumRepresentations: unknown enum property!" ); + break; + } + + std::vector< OUString > aReturn; + + aReturn.reserve(nElements); + for (std::size_t i = 0; i < nElements; ++i) + { + aReturn.push_back(PcrRes(pStringItemsResId[i])); + } + + return aReturn; + } + + bool OPropertyInfoService::isComposeable( const OUString& _rPropertyName ) const + { + sal_Int32 nId = getPropertyId( _rPropertyName ); + if ( nId == -1 ) + return false; + + sal_uInt32 nFlags = getPropertyUIFlags( nId ); + return ( nFlags & PROP_FLAG_COMPOSEABLE ) != 0; + } + + + const OPropertyInfoImpl* OPropertyInfoService::getPropertyInfo(const OUString& _rName) + { + // Initialization + if(!s_pPropertyInfos) + getPropertyInfo(); + OPropertyInfoImpl aSearch(_rName, 0, OUString(), 0, "", 0); + + const OPropertyInfoImpl* pInfo = std::lower_bound( + s_pPropertyInfos, s_pPropertyInfos + s_nCount, aSearch, PropertyInfoLessByName() ); + + if ( pInfo == s_pPropertyInfos + s_nCount ) + return nullptr; + + if ( pInfo->sName != _rName ) + return nullptr; + + return pInfo; + } + + + const OPropertyInfoImpl* OPropertyInfoService::getPropertyInfo(sal_Int32 _nId) + { + // Initialization + if(!s_pPropertyInfos) + getPropertyInfo(); + + // TODO: a real structure which allows quick access by name as well as by id + for (std::size_t i = 0; i < s_nCount; ++i) + if (s_pPropertyInfos[i].nId == _nId) + return &s_pPropertyInfos[i]; + + return nullptr; + } + + + //= DefaultEnumRepresentation + + + DefaultEnumRepresentation::DefaultEnumRepresentation( const IPropertyInfoService& _rInfo, const Type& _rType, sal_Int32 _nPropertyId ) + :m_rMetaData( _rInfo ) + ,m_aType( _rType ) + ,m_nPropertyId( _nPropertyId ) + { + } + + + DefaultEnumRepresentation::~DefaultEnumRepresentation() + { + } + + + std::vector< OUString > DefaultEnumRepresentation::getDescriptions() const + { + return m_rMetaData.getPropertyEnumRepresentations( m_nPropertyId ); + } + + + void DefaultEnumRepresentation::getValueFromDescription( const OUString& _rDescription, Any& _out_rValue ) const + { + sal_uInt32 nPropertyUIFlags = m_rMetaData.getPropertyUIFlags( m_nPropertyId ); + std::vector< OUString > aEnumStrings = m_rMetaData.getPropertyEnumRepresentations( m_nPropertyId ); + std::vector< OUString >::const_iterator pos = std::find( aEnumStrings.begin(), aEnumStrings.end(), _rDescription ); + if ( pos != aEnumStrings.end() ) + { + sal_Int32 nPos = pos - aEnumStrings.begin(); + if ( ( nPropertyUIFlags & PROP_FLAG_ENUM_ONE ) == PROP_FLAG_ENUM_ONE ) + // enum value starting with 1 + ++nPos; + + switch ( m_aType.getTypeClass() ) + { + case TypeClass_ENUM: + _out_rValue = ::cppu::int2enum( nPos, m_aType ); + break; + + case TypeClass_SHORT: + _out_rValue <<= static_cast<sal_Int16>(nPos); + break; + + case TypeClass_UNSIGNED_SHORT: + _out_rValue <<= static_cast<sal_uInt16>(nPos); + break; + + case TypeClass_UNSIGNED_LONG: + _out_rValue <<= static_cast<sal_uInt32>(nPos); + break; + + default: + _out_rValue <<= nPos; + break; + } + } + else + { + OSL_FAIL( "DefaultEnumRepresentation::getValueFromDescription: could not translate the enum string!" ); + _out_rValue.clear(); + } + } + + + OUString DefaultEnumRepresentation::getDescriptionForValue( const Any& _rEnumValue ) const + { + OUString sReturn; + sal_Int32 nIntValue = -1; + OSL_VERIFY( ::cppu::enum2int( nIntValue, _rEnumValue ) ); + + sal_uInt32 nUIFlags = m_rMetaData.getPropertyUIFlags( m_nPropertyId ); + if ( ( nUIFlags & PROP_FLAG_ENUM_ONE ) == PROP_FLAG_ENUM_ONE ) + // enum value starting with 1 + --nIntValue; + + std::vector< OUString > aEnumStrings = m_rMetaData.getPropertyEnumRepresentations( m_nPropertyId ); + if ( ( nIntValue >= 0 ) && ( o3tl::make_unsigned(nIntValue) < aEnumStrings.size() ) ) + { + sReturn = aEnumStrings[ nIntValue ]; + } + else + { + OSL_FAIL( "DefaultEnumRepresentation::getDescriptionForValue: could not translate an enum value" ); + } + return sReturn; + } + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/formmetadata.hxx b/extensions/source/propctrlr/formmetadata.hxx new file mode 100644 index 0000000000..c2c297740b --- /dev/null +++ b/extensions/source/propctrlr/formmetadata.hxx @@ -0,0 +1,345 @@ +/* -*- 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 . + */ + +#pragma once + +#include "propertyinfo.hxx" +#include "enumrepresentation.hxx" + + +namespace pcr +{ + + + struct OPropertyInfoImpl; + + + //= OPropertyInfoService + + class OPropertyInfoService final + :public IPropertyInfoService + { + static std::size_t s_nCount; + static OPropertyInfoImpl* s_pPropertyInfos; + // TODO: a real structure which allows quick access by name as well as by id + + public: + // IPropertyInfoService + virtual sal_Int32 getPropertyId(const OUString& _rName) const override; + virtual OUString getPropertyTranslation(sal_Int32 _nId) const override; + virtual OUString getPropertyHelpId(sal_Int32 _nId) const override; + virtual sal_Int16 getPropertyPos(sal_Int32 _nId) const override; + virtual sal_uInt32 getPropertyUIFlags(sal_Int32 _nId) const override; + virtual std::vector< OUString > getPropertyEnumRepresentations(sal_Int32 _nId) const override; + + bool isComposeable( const OUString& _rPropertyName ) const; + + private: + static const OPropertyInfoImpl* getPropertyInfo(); + + static const OPropertyInfoImpl* getPropertyInfo(const OUString& _rName); + static const OPropertyInfoImpl* getPropertyInfo(sal_Int32 _nId); + }; + + + //= DefaultEnumRepresentation + + /** an implementation of the IPropertyEnumRepresentation + + To be used with properties which, in formmetadata.cxx, are declared as ENUM. + */ + class DefaultEnumRepresentation : public IPropertyEnumRepresentation + { + private: + const IPropertyInfoService& m_rMetaData; + css::uno::Type m_aType; + const sal_Int32 m_nPropertyId; + + public: + /** constructs an instance + + @param _rInfo + An instance implementing IPropertyInfoService. Must live at least as + long as the DefaultEnumRepresentation should live. + */ + DefaultEnumRepresentation( const IPropertyInfoService& _rInfo, const css::uno::Type& _rType, sal_Int32 _nPropertyId ); + + protected: + virtual ~DefaultEnumRepresentation() override; + + protected: + // IPropertyEnumRepresentation implementqation + virtual std::vector< OUString > + getDescriptions() const override; + virtual void getValueFromDescription( const OUString& _rDescription, css::uno::Any& _out_rValue ) const override; + virtual OUString getDescriptionForValue( const css::uno::Any& _rEnumValue ) const override; + + private: + DefaultEnumRepresentation( const DefaultEnumRepresentation& ) = delete; + DefaultEnumRepresentation& operator=( const DefaultEnumRepresentation& ) = delete; + }; + + + //= UI flags (for all browsable properties) + + +#define PROP_FLAG_NONE 0x00000000 // no special flag +#define PROP_FLAG_FORM_VISIBLE 0x00000001 // the property is visible when inspecting a form object +#define PROP_FLAG_DIALOG_VISIBLE 0x00000002 // the property is visible when inspecting a dialog object +#define PROP_FLAG_DATA_PROPERTY 0x00000004 // the property is to appear on the "Data" page +#define PROP_FLAG_ENUM 0x00000020 // the property is some kind of enum property, i.e. its + // value is chosen from a fixed list of possible values +#define PROP_FLAG_ENUM_ONE 0x00000060 // the property is an enum property starting with 1 + // (note that this includes PROP_FLAG_ENUM) +#define PROP_FLAG_COMPOSEABLE 0x00000080 // the property is "composeable", i.e. an intersection of property + // sets should expose it, if all elements do +#define PROP_FLAG_EXPERIMENTAL 0x00000100 // the property is experimental, i.e. should not appear in the + // UI, unless experimental properties are enabled by a configuration + // option +#define PROP_FLAG_REPORT_INVISIBLE 0x00000200 // the property should not appear in the Report Designer UI + + + //= property ids (for all browsable properties) + + + #define PROPERTY_ID_NAME 1 + #define PROPERTY_ID_LABEL 2 + #define PROPERTY_ID_CONTROLLABEL 3 + #define PROPERTY_ID_MAXTEXTLEN 4 + #define PROPERTY_ID_EDITMASK 5 + #define PROPERTY_ID_LITERALMASK 6 + #define PROPERTY_ID_STRICTFORMAT 7 + #define PROPERTY_ID_ENABLED 8 + #define PROPERTY_ID_READONLY 9 + #define PROPERTY_ID_PRINTABLE 10 + #define PROPERTY_ID_CONTROLSOURCE 11 + #define PROPERTY_ID_TABSTOP 12 + #define PROPERTY_ID_TABINDEX 13 + #define PROPERTY_ID_DATASOURCE 14 + #define PROPERTY_ID_COMMAND 15 + #define PROPERTY_ID_COMMANDTYPE 16 + #define PROPERTY_ID_FILTER 17 + #define PROPERTY_ID_SORT 18 + #define PROPERTY_ID_INSERTONLY 19 + #define PROPERTY_ID_ALLOWADDITIONS 20 + #define PROPERTY_ID_ALLOWEDITS 21 + #define PROPERTY_ID_ALLOWDELETIONS 22 + #define PROPERTY_ID_GROUP_NAME 23 + #define PROPERTY_ID_NAVIGATION 24 + #define PROPERTY_ID_CYCLE 25 + #define PROPERTY_ID_HIDDEN_VALUE 26 + #define PROPERTY_ID_VALUEMIN 27 + #define PROPERTY_ID_VALUEMAX 28 + #define PROPERTY_ID_VALUESTEP 29 + #define PROPERTY_ID_DEFAULT_VALUE 30 + #define PROPERTY_ID_DECIMAL_ACCURACY 31 + #define PROPERTY_ID_SHOWTHOUSANDSEP 32 + #define PROPERTY_ID_REFVALUE 33 + #define PROPERTY_ID_CURRENCYSYMBOL 34 + #define PROPERTY_ID_CURRSYM_POSITION 35 + #define PROPERTY_ID_DATEMIN 36 + #define PROPERTY_ID_DATEMAX 37 + #define PROPERTY_ID_DATEFORMAT 38 + #define PROPERTY_ID_SELECTEDITEMS 39 + #define PROPERTY_ID_DEFAULT_DATE 40 + #define PROPERTY_ID_TIMEMIN 41 + #define PROPERTY_ID_TIMEMAX 42 + #define PROPERTY_ID_TIMEFORMAT 43 + #define PROPERTY_ID_DEFAULT_TIME 44 + #define PROPERTY_ID_EFFECTIVE_MIN 45 + #define PROPERTY_ID_EFFECTIVE_MAX 46 + #define PROPERTY_ID_EFFECTIVE_DEFAULT 47 + #define PROPERTY_ID_FORMATKEY 48 + #define PROPERTY_ID_CLASSID 50 + #define PROPERTY_ID_HEIGHT 51 + #define PROPERTY_ID_WIDTH 52 + #define PROPERTY_ID_BOUNDCOLUMN 53 + #define PROPERTY_ID_LISTSOURCETYPE 54 + #define PROPERTY_ID_LISTSOURCE 55 + #define PROPERTY_ID_LISTINDEX 56 + #define PROPERTY_ID_STRINGITEMLIST 57 + #define PROPERTY_ID_DEFAULT_TEXT 58 + #define PROPERTY_ID_FONT 59 + #define PROPERTY_ID_ALIGN 60 + #define PROPERTY_ID_ROWHEIGHT 61 + #define PROPERTY_ID_BACKGROUNDCOLOR 62 + #define PROPERTY_ID_FILLCOLOR 63 + #define PROPERTY_ID_ESCAPE_PROCESSING 64 + #define PROPERTY_ID_LINECOLOR 65 + #define PROPERTY_ID_BORDER 66 + #define PROPERTY_ID_DROPDOWN 67 + #define PROPERTY_ID_AUTOCOMPLETE 68 + #define PROPERTY_ID_LINECOUNT 69 + #define PROPERTY_ID_WORDBREAK 70 + #define PROPERTY_ID_MULTILINE 71 + #define PROPERTY_ID_MULTISELECTION 72 + #define PROPERTY_ID_AUTOLINEBREAK 73 + #define PROPERTY_ID_HSCROLL 74 + #define PROPERTY_ID_VSCROLL 75 + #define PROPERTY_ID_SPIN 76 + #define PROPERTY_ID_BUTTONTYPE 77 + #define PROPERTY_ID_TARGET_URL 78 + #define PROPERTY_ID_TARGET_FRAME 79 + #define PROPERTY_ID_SUBMIT_ACTION 80 + #define PROPERTY_ID_SUBMIT_TARGET 81 + #define PROPERTY_ID_SUBMIT_METHOD 82 + #define PROPERTY_ID_SUBMIT_ENCODING 83 + #define PROPERTY_ID_DEFAULT_STATE 84 + #define PROPERTY_ID_DEFAULTBUTTON 85 + #define PROPERTY_ID_IMAGE_URL 86 + #define PROPERTY_ID_DEFAULT_SELECT_SEQ 87 + #define PROPERTY_ID_ECHO_CHAR 88 + #define PROPERTY_ID_EMPTY_IS_NULL 89 + #define PROPERTY_ID_TRISTATE 90 + #define PROPERTY_ID_MASTERFIELDS 91 + #define PROPERTY_ID_DETAILFIELDS 92 + #define PROPERTY_ID_RECORDMARKER 93 + #define PROPERTY_ID_FILTERPROPOSAL 94 + #define PROPERTY_ID_TAG 95 + #define PROPERTY_ID_HELPTEXT 96 + #define PROPERTY_ID_HELPURL 97 + #define PROPERTY_ID_HASNAVIGATION 98 + #define PROPERTY_ID_POSITIONX 99 + #define PROPERTY_ID_POSITIONY 100 + #define PROPERTY_ID_TITLE 101 + #define PROPERTY_ID_STEP 102 + #define PROPERTY_ID_PROGRESSVALUE 103 + #define PROPERTY_ID_PROGRESSVALUE_MIN 104 + #define PROPERTY_ID_PROGRESSVALUE_MAX 105 + #define PROPERTY_ID_SCROLLVALUE 106 + #define PROPERTY_ID_SCROLLVALUE_MAX 107 + #define PROPERTY_ID_LINEINCREMENT 108 + #define PROPERTY_ID_BLOCKINCREMENT 109 + #define PROPERTY_ID_VISIBLESIZE 110 + #define PROPERTY_ID_ORIENTATION 111 + #define PROPERTY_ID_IMAGEPOSITION 112 + #define PROPERTY_ID_DATE 113 + #define PROPERTY_ID_STATE 114 + #define PROPERTY_ID_TIME 115 + #define PROPERTY_ID_VALUE 116 + #define PROPERTY_ID_SCALEIMAGE 117 + #define PROPERTY_ID_PUSHBUTTONTYPE 118 + #define PROPERTY_ID_EFFECTIVE_VALUE 119 + #define PROPERTY_ID_TEXT 120 + #define PROPERTY_ID_BOUND_CELL 121 + #define PROPERTY_ID_LIST_CELL_RANGE 122 + #define PROPERTY_ID_CELL_EXCHANGE_TYPE 123 + #define PROPERTY_ID_SCROLLVALUE_MIN 124 + #define PROPERTY_ID_DEFAULT_SCROLLVALUE 125 + #define PROPERTY_ID_REPEAT_DELAY 126 + #define PROPERTY_ID_SYMBOLCOLOR 127 + #define PROPERTY_ID_SPINVALUE 128 + #define PROPERTY_ID_SPINVALUE_MIN 129 + #define PROPERTY_ID_SPINVALUE_MAX 130 + #define PROPERTY_ID_DEFAULT_SPINVALUE 131 + #define PROPERTY_ID_SPININCREMENT 132 + #define PROPERTY_ID_REPEAT 133 + #define PROPERTY_ID_SHOW_SCROLLBARS 134 + #define PROPERTY_ID_ICONSIZE 135 + #define PROPERTY_ID_SHOW_POSITION 136 + #define PROPERTY_ID_SHOW_NAVIGATION 137 + #define PROPERTY_ID_SHOW_RECORDACTIONS 138 + #define PROPERTY_ID_SHOW_FILTERSORT 139 + #define PROPERTY_ID_TEXTTYPE 140 + #define PROPERTY_ID_LINEEND_FORMAT 141 + #define PROPERTY_ID_TOGGLE 142 + #define PROPERTY_ID_FOCUSONCLICK 143 + #define PROPERTY_ID_HIDEINACTIVESELECTION 144 + #define PROPERTY_ID_VISUALEFFECT 145 + #define PROPERTY_ID_BORDERCOLOR 146 + #define PROPERTY_ID_XML_DATA_MODEL 147 + #define PROPERTY_ID_BIND_EXPRESSION 148 + #define PROPERTY_ID_XSD_REQUIRED 149 + #define PROPERTY_ID_XSD_RELEVANT 150 + #define PROPERTY_ID_XSD_READONLY 151 + #define PROPERTY_ID_XSD_CONSTRAINT 152 + #define PROPERTY_ID_XSD_CALCULATION 153 + #define PROPERTY_ID_XSD_DATA_TYPE 154 + #define PROPERTY_ID_XSD_WHITESPACES 155 + #define PROPERTY_ID_XSD_PATTERN 156 + #define PROPERTY_ID_XSD_LENGTH 157 + #define PROPERTY_ID_XSD_MIN_LENGTH 158 + #define PROPERTY_ID_XSD_MAX_LENGTH 159 + #define PROPERTY_ID_XSD_TOTAL_DIGITS 160 + #define PROPERTY_ID_XSD_FRACTION_DIGITS 161 + #define PROPERTY_ID_XSD_MAX_INCLUSIVE_INT 162 + #define PROPERTY_ID_XSD_MAX_EXCLUSIVE_INT 163 + #define PROPERTY_ID_XSD_MIN_INCLUSIVE_INT 164 + #define PROPERTY_ID_XSD_MIN_EXCLUSIVE_INT 165 + #define PROPERTY_ID_XSD_MAX_INCLUSIVE_DOUBLE 166 + #define PROPERTY_ID_XSD_MAX_EXCLUSIVE_DOUBLE 167 + #define PROPERTY_ID_XSD_MIN_INCLUSIVE_DOUBLE 168 + #define PROPERTY_ID_XSD_MIN_EXCLUSIVE_DOUBLE 169 + #define PROPERTY_ID_XSD_MAX_INCLUSIVE_DATE 170 + #define PROPERTY_ID_XSD_MAX_EXCLUSIVE_DATE 171 + #define PROPERTY_ID_XSD_MIN_INCLUSIVE_DATE 172 + #define PROPERTY_ID_XSD_MIN_EXCLUSIVE_DATE 173 + #define PROPERTY_ID_XSD_MAX_INCLUSIVE_TIME 174 + #define PROPERTY_ID_XSD_MAX_EXCLUSIVE_TIME 175 + #define PROPERTY_ID_XSD_MIN_INCLUSIVE_TIME 176 + #define PROPERTY_ID_XSD_MIN_EXCLUSIVE_TIME 177 + #define PROPERTY_ID_XSD_MAX_INCLUSIVE_DATE_TIME 178 + #define PROPERTY_ID_XSD_MAX_EXCLUSIVE_DATE_TIME 179 + #define PROPERTY_ID_XSD_MIN_INCLUSIVE_DATE_TIME 180 + #define PROPERTY_ID_XSD_MIN_EXCLUSIVE_DATE_TIME 181 + #define PROPERTY_ID_UNCHECKEDREFVALUE 182 + #define PROPERTY_ID_SUBMISSION_ID 183 + #define PROPERTY_ID_XFORMS_BUTTONTYPE 184 + #define PROPERTY_ID_LIST_BINDING 185 + #define PROPERTY_ID_VERTICAL_ALIGN 186 + #define PROPERTY_ID_BINDING_NAME 187 + #define PROPERTY_ID_DECORATION 188 + #define PROPERTY_ID_SELECTION_TYPE 189 + #define PROPERTY_ID_ROOT_DISPLAYED 190 + #define PROPERTY_ID_SHOWS_HANDLES 191 + #define PROPERTY_ID_SHOWS_ROOT_HANDLES 192 + #define PROPERTY_ID_EDITABLE 193 + #define PROPERTY_ID_INVOKES_STOP_NOT_EDITING 194 + #define PROPERTY_ID_NOLABEL 195 + #define PROPERTY_ID_SCALE_MODE 196 + #define PROPERTY_ID_INPUT_REQUIRED 197 + #define PROPERTY_ID_WRITING_MODE 198 + #define PROPERTY_ID_ENABLE_VISIBLE 199 + #define PROPERTY_ID_WHEEL_BEHAVIOR 200 + #define PROPERTY_ID_TEXT_ANCHOR_TYPE 201 + #define PROPERTY_ID_SHEET_ANCHOR_TYPE 202 + #define PROPERTY_ID_SCROLL_WIDTH 203 + #define PROPERTY_ID_SCROLL_HEIGHT 204 + #define PROPERTY_ID_SCROLL_TOP 205 + #define PROPERTY_ID_SCROLL_LEFT 206 + #define PROPERTY_ID_TYPEDITEMLIST 207 + #define PROPERTY_ID_SELECTIONMODEL 208 + #define PROPERTY_ID_USEGRIDLINE 209 + #define PROPERTY_ID_GRIDLINECOLOR 210 + #define PROPERTY_ID_SHOWCOLUMNHEADER 211 + #define PROPERTY_ID_SHOWROWHEADER 212 + #define PROPERTY_ID_HEADERBACKGROUNDCOLOR 213 + #define PROPERTY_ID_HEADERTEXTCOLOR 214 + #define PROPERTY_ID_ACTIVESELECTIONBACKGROUNDCOLOR 215 + #define PROPERTY_ID_ACTIVESELECTIONTEXTCOLOR 216 + #define PROPERTY_ID_INACTIVESELECTIONBACKGROUNDCOLOR 217 + #define PROPERTY_ID_INACTIVESELECTIONTEXTCOLOR 218 + #define PROPERTY_ID_URL 219 + #define PROPERTY_ID_AUTOGROW 220 + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/formstrings.hxx b/extensions/source/propctrlr/formstrings.hxx new file mode 100644 index 0000000000..41deed74c8 --- /dev/null +++ b/extensions/source/propctrlr/formstrings.hxx @@ -0,0 +1,302 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> + +inline constexpr OUString PROPERTY_DEFAULTCONTROL = u"DefaultControl"_ustr; +inline constexpr OUString PROPERTY_INTROSPECTEDOBJECT = u"IntrospectedObject"_ustr; +inline constexpr OUString PROPERTY_CURRENTPAGE = u"CurrentPage"_ustr; +inline constexpr OUString PROPERTY_CONTROLCONTEXT = u"ControlContext"_ustr; + +// properties +inline constexpr OUString PROPERTY_CLASSID = u"ClassId"_ustr; +inline constexpr OUString PROPERTY_CONTROLLABEL = u"LabelControl"_ustr; +inline constexpr OUString PROPERTY_LABEL = u"Label"_ustr; +inline constexpr OUString PROPERTY_TABINDEX = u"TabIndex"_ustr; +inline constexpr OUString PROPERTY_WHEEL_BEHAVIOR = u"MouseWheelBehavior"_ustr; +inline constexpr OUString PROPERTY_TAG = u"Tag"_ustr; +inline constexpr OUString PROPERTY_NAME = u"Name"_ustr; +inline constexpr OUString PROPERTY_GROUP_NAME = u"GroupName"_ustr; +inline constexpr OUString PROPERTY_VALUE = u"Value"_ustr; +inline constexpr OUString PROPERTY_TEXT = u"Text"_ustr; +inline constexpr OUString PROPERTY_NAVIGATION = u"NavigationBarMode"_ustr; +inline constexpr OUString PROPERTY_CYCLE = u"Cycle"_ustr; +inline constexpr OUString PROPERTY_CONTROLSOURCE = u"DataField"_ustr; +inline constexpr OUString PROPERTY_INPUT_REQUIRED = u"InputRequired"_ustr; +inline constexpr OUString PROPERTY_ENABLED = u"Enabled"_ustr; +inline constexpr OUString PROPERTY_ENABLE_VISIBLE = u"EnableVisible"_ustr; +inline constexpr OUString PROPERTY_READONLY = u"ReadOnly"_ustr; +inline constexpr OUString PROPERTY_FILTER = u"Filter"_ustr; +inline constexpr OUString PROPERTY_WIDTH = u"Width"_ustr; +inline constexpr OUString PROPERTY_MULTILINE = u"MultiLine"_ustr; +inline constexpr OUString PROPERTY_WORDBREAK = u"WordBreak"_ustr; +inline constexpr OUString PROPERTY_TARGET_URL = u"TargetURL"_ustr; +inline constexpr OUString PROPERTY_TARGET_FRAME = u"TargetFrame"_ustr; +inline constexpr OUString PROPERTY_MAXTEXTLEN = u"MaxTextLen"_ustr; +inline constexpr OUString PROPERTY_EDITMASK = u"EditMask"_ustr; +inline constexpr OUString PROPERTY_SPIN = u"Spin"_ustr; +inline constexpr OUString PROPERTY_TRISTATE = u"TriState"_ustr; +inline constexpr OUString PROPERTY_HIDDEN_VALUE = u"HiddenValue"_ustr; +inline constexpr OUString PROPERTY_BUTTONTYPE = u"ButtonType"_ustr; +inline constexpr OUString PROPERTY_XFORMS_BUTTONTYPE = u"XFormsButtonType"_ustr; +inline constexpr OUString PROPERTY_STRINGITEMLIST = u"StringItemList"_ustr; +inline constexpr OUString PROPERTY_TYPEDITEMLIST = u"TypedItemList"_ustr; +inline constexpr OUString PROPERTY_DEFAULT_TEXT = u"DefaultText"_ustr; +inline constexpr OUString PROPERTY_DEFAULT_STATE = u"DefaultState"_ustr; +inline constexpr OUString PROPERTY_FORMATKEY = u"FormatKey"_ustr; +inline constexpr OUString PROPERTY_FORMATSSUPPLIER = u"FormatsSupplier"_ustr; +inline constexpr OUString PROPERTY_SUBMIT_ACTION = u"SubmitAction"_ustr; +inline constexpr OUString PROPERTY_SUBMIT_TARGET = u"SubmitTarget"_ustr; +inline constexpr OUString PROPERTY_SUBMIT_METHOD = u"SubmitMethod"_ustr; +inline constexpr OUString PROPERTY_SUBMIT_ENCODING = u"SubmitEncoding"_ustr; +inline constexpr OUString PROPERTY_IMAGE_URL = u"ImageURL"_ustr; +inline constexpr OUString PROPERTY_GRAPHIC = u"Graphic"_ustr; +inline constexpr OUString PROPERTY_EMPTY_IS_NULL = u"ConvertEmptyToNull"_ustr; +inline constexpr OUString PROPERTY_LISTSOURCETYPE = u"ListSourceType"_ustr; +inline constexpr OUString PROPERTY_LISTSOURCE = u"ListSource"_ustr; +inline constexpr OUString PROPERTY_DEFAULT_SELECT_SEQ = u"DefaultSelection"_ustr; +inline constexpr OUString PROPERTY_MULTISELECTION = u"MultiSelection"_ustr; +inline constexpr OUString PROPERTY_ALIGN = u"Align"_ustr; +inline constexpr OUString PROPERTY_VERTICAL_ALIGN = u"VerticalAlign"_ustr; +inline constexpr OUString PROPERTY_DEFAULT_DATE = u"DefaultDate"_ustr; +inline constexpr OUString PROPERTY_DEFAULT_TIME = u"DefaultTime"_ustr; +inline constexpr OUString PROPERTY_DEFAULT_VALUE = u"DefaultValue"_ustr; +inline constexpr OUString PROPERTY_DECIMAL_ACCURACY = u"DecimalAccuracy"_ustr; +inline constexpr OUString PROPERTY_REFVALUE = u"RefValue"_ustr; +inline constexpr OUString PROPERTY_UNCHECKEDREFVALUE = u"SecondaryRefValue"_ustr; +inline constexpr OUString PROPERTY_VALUEMIN = u"ValueMin"_ustr; +inline constexpr OUString PROPERTY_VALUEMAX = u"ValueMax"_ustr; +inline constexpr OUString PROPERTY_STRICTFORMAT = u"StrictFormat"_ustr; +inline constexpr OUString PROPERTY_ALLOWADDITIONS = u"AllowInserts"_ustr; +inline constexpr OUString PROPERTY_ALLOWEDITS = u"AllowUpdates"_ustr; +inline constexpr OUString PROPERTY_ALLOWDELETIONS = u"AllowDeletes"_ustr; +inline constexpr OUString PROPERTY_MASTERFIELDS = u"MasterFields"_ustr; +inline constexpr OUString PROPERTY_LITERALMASK = u"LiteralMask"_ustr; +inline constexpr OUString PROPERTY_VALUESTEP = u"ValueStep"_ustr; +inline constexpr OUString PROPERTY_SHOWTHOUSANDSEP = u"ShowThousandsSeparator"_ustr; +inline constexpr OUString PROPERTY_CURRENCYSYMBOL = u"CurrencySymbol"_ustr; +inline constexpr OUString PROPERTY_DATEFORMAT = u"DateFormat"_ustr; +inline constexpr OUString PROPERTY_DATEMIN = u"DateMin"_ustr; +inline constexpr OUString PROPERTY_DATEMAX = u"DateMax"_ustr; +inline constexpr OUString PROPERTY_TIMEFORMAT = u"TimeFormat"_ustr; +inline constexpr OUString PROPERTY_TIMEMIN = u"TimeMin"_ustr; +inline constexpr OUString PROPERTY_TIMEMAX = u"TimeMax"_ustr; +inline constexpr OUString PROPERTY_LINECOUNT = u"LineCount"_ustr; +inline constexpr OUString PROPERTY_BOUNDCOLUMN = u"BoundColumn"_ustr; +inline constexpr OUString PROPERTY_BACKGROUNDCOLOR = u"BackgroundColor"_ustr; +inline constexpr OUString PROPERTY_FILLCOLOR = u"FillColor"_ustr; +inline constexpr OUString PROPERTY_TEXTCOLOR = u"TextColor"_ustr; +inline constexpr OUString PROPERTY_LINECOLOR = u"LineColor"_ustr; +inline constexpr OUString PROPERTY_BORDER = u"Border"_ustr; +inline constexpr OUString PROPERTY_ICONSIZE = u"IconSize"_ustr; +inline constexpr OUString PROPERTY_DROPDOWN = u"Dropdown"_ustr; +inline constexpr OUString PROPERTY_HSCROLL = u"HScroll"_ustr; +inline constexpr OUString PROPERTY_VSCROLL = u"VScroll"_ustr; +inline constexpr OUString PROPERTY_SHOW_SCROLLBARS = u"ShowScrollbars"_ustr; +inline constexpr OUString PROPERTY_TABSTOP = u"Tabstop"_ustr; +inline constexpr OUString PROPERTY_AUTOCOMPLETE = u"Autocomplete"_ustr; +inline constexpr OUString PROPERTY_PRINTABLE = u"Printable"_ustr; +inline constexpr OUString PROPERTY_ECHO_CHAR = u"EchoChar"_ustr; +inline constexpr OUString PROPERTY_ROWHEIGHT = u"RowHeight"_ustr; +inline constexpr OUString PROPERTY_HELPTEXT = u"HelpText"_ustr; +inline constexpr OUString PROPERTY_FONT = u"FontDescriptor"_ustr; +inline constexpr OUString PROPERTY_FONT_NAME = u"FontName"_ustr; +inline constexpr OUString PROPERTY_FONT_STYLENAME = u"FontStyleName"_ustr; +inline constexpr OUString PROPERTY_FONT_FAMILY = u"FontFamily"_ustr; +inline constexpr OUString PROPERTY_FONT_CHARSET = u"FontCharset"_ustr; +inline constexpr OUString PROPERTY_FONT_HEIGHT = u"FontHeight"_ustr; +inline constexpr OUString PROPERTY_FONT_WEIGHT = u"FontWeight"_ustr; +inline constexpr OUString PROPERTY_FONT_SLANT = u"FontSlant"_ustr; +inline constexpr OUString PROPERTY_FONT_UNDERLINE = u"FontUnderline"_ustr; +inline constexpr OUString PROPERTY_FONT_STRIKEOUT = u"FontStrikeout"_ustr; +inline constexpr OUString PROPERTY_FONT_RELIEF = u"FontRelief"_ustr; +inline constexpr OUString PROPERTY_FONT_EMPHASIS_MARK = u"FontEmphasisMark"_ustr; +inline constexpr OUString PROPERTY_TEXTLINECOLOR = u"TextLineColor"_ustr; +inline constexpr OUString PROPERTY_HELPURL = u"HelpURL"_ustr; +inline constexpr OUString PROPERTY_RECORDMARKER = u"HasRecordMarker"_ustr; +inline constexpr OUString PROPERTY_EFFECTIVE_DEFAULT = u"EffectiveDefault"_ustr; +inline constexpr OUString PROPERTY_EFFECTIVE_MIN = u"EffectiveMin"_ustr; +inline constexpr OUString PROPERTY_EFFECTIVE_MAX = u"EffectiveMax"_ustr; +inline constexpr OUString PROPERTY_FILTERPROPOSAL = u"UseFilterValueProposal"_ustr; +inline constexpr OUString PROPERTY_CURRSYM_POSITION = u"PrependCurrencySymbol"_ustr; +inline constexpr OUString PROPERTY_COMMAND = u"Command"_ustr; +inline constexpr OUString PROPERTY_COMMANDTYPE = u"CommandType"_ustr; +inline constexpr OUString PROPERTY_INSERTONLY = u"IgnoreResult"_ustr; +inline constexpr OUString PROPERTY_ESCAPE_PROCESSING = u"EscapeProcessing"_ustr; +inline constexpr OUString PROPERTY_TITLE = u"Title"_ustr; +inline constexpr OUString PROPERTY_SORT = u"Order"_ustr; +inline constexpr OUString PROPERTY_DATASOURCE = u"DataSourceName"_ustr; +inline constexpr OUString PROPERTY_DETAILFIELDS = u"DetailFields"_ustr; +inline constexpr OUString PROPERTY_DEFAULTBUTTON = u"DefaultButton"_ustr; +inline constexpr OUString PROPERTY_LISTINDEX = u"ListIndex"_ustr; +inline constexpr OUString PROPERTY_HEIGHT = u"Height"_ustr; +inline constexpr OUString PROPERTY_HASNAVIGATION = u"HasNavigationBar"_ustr; +inline constexpr OUString PROPERTY_POSITIONX = u"PositionX"_ustr; +inline constexpr OUString PROPERTY_POSITIONY = u"PositionY"_ustr; +inline constexpr OUString PROPERTY_AUTOGROW = u"AutoGrow"_ustr; +inline constexpr OUString PROPERTY_STEP = u"Step"_ustr; +inline constexpr OUString PROPERTY_WORDLINEMODE = u"FontWordLineMode"_ustr; +inline constexpr OUString PROPERTY_PROGRESSVALUE = u"ProgressValue"_ustr; +inline constexpr OUString PROPERTY_PROGRESSVALUE_MIN = u"ProgressValueMin"_ustr; +inline constexpr OUString PROPERTY_PROGRESSVALUE_MAX = u"ProgressValueMax"_ustr; +inline constexpr OUString PROPERTY_SCROLLVALUE = u"ScrollValue"_ustr; +inline constexpr OUString PROPERTY_DEFAULT_SCROLLVALUE = u"DefaultScrollValue"_ustr; +inline constexpr OUString PROPERTY_SCROLLVALUE_MIN = u"ScrollValueMin"_ustr; +inline constexpr OUString PROPERTY_SCROLLVALUE_MAX = u"ScrollValueMax"_ustr; +inline constexpr OUString PROPERTY_SCROLL_WIDTH = u"ScrollWidth"_ustr; +inline constexpr OUString PROPERTY_SCROLL_HEIGHT = u"ScrollHeight"_ustr; +inline constexpr OUString PROPERTY_SCROLL_TOP = u"ScrollTop"_ustr; +inline constexpr OUString PROPERTY_SCROLL_LEFT = u"ScrollLeft"_ustr; +inline constexpr OUString PROPERTY_LINEINCREMENT = u"LineIncrement"_ustr; +inline constexpr OUString PROPERTY_BLOCKINCREMENT = u"BlockIncrement"_ustr; +inline constexpr OUString PROPERTY_VISIBLESIZE = u"VisibleSize"_ustr; +inline constexpr OUString PROPERTY_ORIENTATION = u"Orientation"_ustr; +inline constexpr OUString PROPERTY_IMAGEPOSITION = u"ImagePosition"_ustr; +inline constexpr OUString PROPERTY_ACTIVE_CONNECTION = u"ActiveConnection"_ustr; +inline constexpr OUString PROPERTY_ACTIVECOMMAND = u"ActiveCommand"_ustr; +inline constexpr OUString PROPERTY_DATE = u"Date"_ustr; +inline constexpr OUString PROPERTY_STATE = u"State"_ustr; +inline constexpr OUString PROPERTY_TIME = u"Time"_ustr; +inline constexpr OUString PROPERTY_SCALEIMAGE = u"ScaleImage"_ustr; +inline constexpr OUString PROPERTY_SCALE_MODE = u"ScaleMode"_ustr; +inline constexpr OUString PROPERTY_PUSHBUTTONTYPE = u"PushButtonType"_ustr; +inline constexpr OUString PROPERTY_EFFECTIVE_VALUE = u"EffectiveValue"_ustr; +inline constexpr OUString PROPERTY_SELECTEDITEMS = u"SelectedItems"_ustr; +inline constexpr OUString PROPERTY_REPEAT = u"Repeat"_ustr; +inline constexpr OUString PROPERTY_REPEAT_DELAY = u"RepeatDelay"_ustr; +inline constexpr OUString PROPERTY_SYMBOLCOLOR = u"SymbolColor"_ustr; +inline constexpr OUString PROPERTY_SPINVALUE = u"SpinValue"_ustr; +inline constexpr OUString PROPERTY_SPINVALUE_MIN = u"SpinValueMin"_ustr; +inline constexpr OUString PROPERTY_SPINVALUE_MAX = u"SpinValueMax"_ustr; +inline constexpr OUString PROPERTY_DEFAULT_SPINVALUE = u"DefaultSpinValue"_ustr; +inline constexpr OUString PROPERTY_SPININCREMENT = u"SpinIncrement"_ustr; +inline constexpr OUString PROPERTY_SHOW_POSITION = u"ShowPosition"_ustr; +inline constexpr OUString PROPERTY_SHOW_NAVIGATION = u"ShowNavigation"_ustr; +inline constexpr OUString PROPERTY_SHOW_RECORDACTIONS = u"ShowRecordActions"_ustr; +inline constexpr OUString PROPERTY_SHOW_FILTERSORT = u"ShowFilterSort"_ustr; +inline constexpr OUString PROPERTY_LINEEND_FORMAT = u"LineEndFormat"_ustr; +inline constexpr OUString PROPERTY_DECORATION = u"Decoration"_ustr; +inline constexpr OUString PROPERTY_NOLABEL = u"NoLabel"_ustr; +inline constexpr OUString PROPERTY_URL = u"URL"_ustr; + +inline constexpr OUString PROPERTY_SELECTION_TYPE = u"SelectionType"_ustr; +inline constexpr OUString PROPERTY_ROOT_DISPLAYED = u"RootDisplayed"_ustr; +inline constexpr OUString PROPERTY_SHOWS_HANDLES = u"ShowsHandles"_ustr; +inline constexpr OUString PROPERTY_SHOWS_ROOT_HANDLES = u"ShowsRootHandles"_ustr; +inline constexpr OUString PROPERTY_EDITABLE = u"Editable"_ustr; +inline constexpr OUString PROPERTY_INVOKES_STOP_NOT_EDITING = u"InvokesStopNodeEditing"_ustr; + +inline constexpr OUString PROPERTY_TOGGLE = u"Toggle"_ustr; +inline constexpr OUString PROPERTY_FOCUSONCLICK = u"FocusOnClick"_ustr; +inline constexpr OUString PROPERTY_HIDEINACTIVESELECTION = u"HideInactiveSelection"_ustr; +inline constexpr OUString PROPERTY_VISUALEFFECT = u"VisualEffect"_ustr; +inline constexpr OUString PROPERTY_BORDERCOLOR = u"BorderColor"_ustr; + +inline constexpr OUString PROPERTY_ADDRESS = u"Address"_ustr; +inline constexpr OUString PROPERTY_REFERENCE_SHEET = u"ReferenceSheet"_ustr; +inline constexpr OUString PROPERTY_UI_REPRESENTATION = u"UserInterfaceRepresentation"_ustr; + +inline constexpr OUString PROPERTY_XML_DATA_MODEL = u"XMLDataModel"_ustr; +inline constexpr OUString PROPERTY_BINDING_NAME = u"BindingName"_ustr; +inline constexpr OUString PROPERTY_BIND_EXPRESSION = u"BindingExpression"_ustr; +inline constexpr OUString PROPERTY_LIST_BINDING = u"ListBinding"_ustr; +inline constexpr OUString PROPERTY_XSD_REQUIRED = u"RequiredExpression"_ustr; +inline constexpr OUString PROPERTY_XSD_RELEVANT = u"RelevantExpression"_ustr; +inline constexpr OUString PROPERTY_XSD_READONLY = u"ReadonlyExpression"_ustr; +inline constexpr OUString PROPERTY_XSD_CONSTRAINT = u"ConstraintExpression"_ustr; +inline constexpr OUString PROPERTY_XSD_CALCULATION = u"CalculateExpression"_ustr; +inline constexpr OUString PROPERTY_XSD_DATA_TYPE = u"Type"_ustr; +inline constexpr OUString PROPERTY_XSD_WHITESPACES = u"WhiteSpace"_ustr; +inline constexpr OUString PROPERTY_XSD_PATTERN = u"Pattern"_ustr; +inline constexpr OUString PROPERTY_XSD_LENGTH = u"Length"_ustr; +inline constexpr OUString PROPERTY_XSD_MIN_LENGTH = u"MinLength"_ustr; +inline constexpr OUString PROPERTY_XSD_MAX_LENGTH = u"MaxLength"_ustr; +inline constexpr OUString PROPERTY_XSD_TOTAL_DIGITS = u"TotalDigits"_ustr; +inline constexpr OUString PROPERTY_XSD_FRACTION_DIGITS = u"FractionDigits"_ustr; +inline constexpr OUString PROPERTY_XSD_MAX_INCLUSIVE_INT = u"MaxInclusiveInt"_ustr; +inline constexpr OUString PROPERTY_XSD_MAX_EXCLUSIVE_INT = u"MaxExclusiveInt"_ustr; +inline constexpr OUString PROPERTY_XSD_MIN_INCLUSIVE_INT = u"MinInclusiveInt"_ustr; +inline constexpr OUString PROPERTY_XSD_MIN_EXCLUSIVE_INT = u"MinExclusiveInt"_ustr; +inline constexpr OUString PROPERTY_XSD_MAX_INCLUSIVE_DOUBLE = u"MaxInclusiveDouble"_ustr; +inline constexpr OUString PROPERTY_XSD_MAX_EXCLUSIVE_DOUBLE = u"MaxExclusiveDouble"_ustr; +inline constexpr OUString PROPERTY_XSD_MIN_INCLUSIVE_DOUBLE = u"MinInclusiveDouble"_ustr; +inline constexpr OUString PROPERTY_XSD_MIN_EXCLUSIVE_DOUBLE = u"MinExclusiveDouble"_ustr; +inline constexpr OUString PROPERTY_XSD_MAX_INCLUSIVE_DATE = u"MaxInclusiveDate"_ustr; +inline constexpr OUString PROPERTY_XSD_MAX_EXCLUSIVE_DATE = u"MaxExclusiveDate"_ustr; +inline constexpr OUString PROPERTY_XSD_MIN_INCLUSIVE_DATE = u"MinInclusiveDate"_ustr; +inline constexpr OUString PROPERTY_XSD_MIN_EXCLUSIVE_DATE = u"MinExclusiveDate"_ustr; +inline constexpr OUString PROPERTY_XSD_MAX_INCLUSIVE_TIME = u"MaxInclusiveTime"_ustr; +inline constexpr OUString PROPERTY_XSD_MAX_EXCLUSIVE_TIME = u"MaxExclusiveTime"_ustr; +inline constexpr OUString PROPERTY_XSD_MIN_INCLUSIVE_TIME = u"MinInclusiveTime"_ustr; +inline constexpr OUString PROPERTY_XSD_MIN_EXCLUSIVE_TIME = u"MinExclusiveTime"_ustr; +inline constexpr OUString PROPERTY_XSD_MAX_INCLUSIVE_DATE_TIME = u"MaxInclusiveDateTime"_ustr; +inline constexpr OUString PROPERTY_XSD_MAX_EXCLUSIVE_DATE_TIME = u"MaxExclusiveDateTime"_ustr; +inline constexpr OUString PROPERTY_XSD_MIN_INCLUSIVE_DATE_TIME = u"MinInclusiveDateTime"_ustr; +inline constexpr OUString PROPERTY_XSD_MIN_EXCLUSIVE_DATE_TIME = u"MinExclusiveDateTime"_ustr; +inline constexpr OUString PROPERTY_SUBMISSION_ID = u"SubmissionID"_ustr; +inline constexpr OUString PROPERTY_BINDING_ID = u"BindingID"_ustr; +inline constexpr OUString PROPERTY_WRITING_MODE = u"WritingMode"_ustr; +inline constexpr OUString PROPERTY_TEXT_ANCHOR_TYPE = u"TextAnchorType"_ustr; +inline constexpr OUString PROPERTY_SHEET_ANCHOR_TYPE = u"SheetAnchorType"_ustr; +inline constexpr OUString PROPERTY_ANCHOR_TYPE = u"AnchorType"_ustr; +inline constexpr OUString PROPERTY_ANCHOR = u"Anchor"_ustr; +inline constexpr OUString PROPERTY_IS_VISIBLE = u"IsVisible"_ustr; + +inline constexpr OUString PROPERTY_MODEL = u"Model"_ustr; + +inline constexpr OUString PROPERTY_CELL_EXCHANGE_TYPE = u"ExchangeSelectionIndex"_ustr; +inline constexpr OUString PROPERTY_BOUND_CELL = u"BoundCell"_ustr; +inline constexpr OUString PROPERTY_LIST_CELL_RANGE = u"CellRange"_ustr; +inline constexpr OUString PROPERTY_TEXTTYPE = u"TextType"_ustr; +inline constexpr OUString PROPERTY_RICHTEXT = u"RichText"_ustr; +inline constexpr OUString PROPERTY_ROWSET = u"RowSet"_ustr; +inline constexpr OUString PROPERTY_SELECTIONMODEL = u"SelectionModel"_ustr; +inline constexpr OUString PROPERTY_USEGRIDLINE = u"UseGridLines"_ustr; +inline constexpr OUString PROPERTY_GRIDLINECOLOR = u"GridLineColor"_ustr; +inline constexpr OUString PROPERTY_SHOWCOLUMNHEADER = u"ShowColumnHeader"_ustr; +inline constexpr OUString PROPERTY_SHOWROWHEADER = u"ShowRowHeader"_ustr; +inline constexpr OUString PROPERTY_HEADERBACKGROUNDCOLOR = u"HeaderBackgroundColor"_ustr; +inline constexpr OUString PROPERTY_HEADERTEXTCOLOR = u"HeaderTextColor"_ustr; +inline constexpr OUString PROPERTY_ACTIVESELECTIONBACKGROUNDCOLOR = u"ActiveSelectionBackgroundColor"_ustr; +inline constexpr OUString PROPERTY_ACTIVESELECTIONTEXTCOLOR = u"ActiveSelectionTextColor"_ustr; +inline constexpr OUString PROPERTY_INACTIVESELECTIONBACKGROUNDCOLOR = u"InactiveSelectionBackgroundColor"_ustr; +inline constexpr OUString PROPERTY_INACTIVESELECTIONTEXTCOLOR = u"InactiveSelectionTextColor"_ustr; + +// services +inline constexpr OUString SERVICE_COMPONENT_GROUPBOX = u"com.sun.star.form.component.GroupBox"_ustr; +inline constexpr OUString SERVICE_COMPONENT_FIXEDTEXT = u"com.sun.star.form.component.FixedText"_ustr; +inline constexpr OUString SERVICE_COMPONENT_FORMATTEDFIELD = u"com.sun.star.form.component.FormattedField"_ustr; + +inline constexpr OUString SERVICE_TEXT_DOCUMENT = u"com.sun.star.text.TextDocument"_ustr; +inline constexpr OUString SERVICE_WEB_DOCUMENT = u"com.sun.star.text.WebDocument"_ustr; +inline constexpr OUString SERVICE_SPREADSHEET_DOCUMENT = u"com.sun.star.sheet.SpreadsheetDocument"_ustr; +inline constexpr OUString SERVICE_DRAWING_DOCUMENT = u"com.sun.star.drawing.DrawingDocument"_ustr; +inline constexpr OUString SERVICE_PRESENTATION_DOCUMENT = u"com.sun.star.presentation.PresentationDocument"_ustr; + +inline constexpr OUString SERVICE_SHEET_CELL_BINDING = u"com.sun.star.table.CellValueBinding"_ustr; +inline constexpr OUString SERVICE_SHEET_CELL_INT_BINDING = u"com.sun.star.table.ListPositionCellBinding"_ustr; +inline constexpr OUString SERVICE_SHEET_CELLRANGE_LISTSOURCE = u"com.sun.star.table.CellRangeListSource"_ustr; +inline constexpr OUString SERVICE_ADDRESS_CONVERSION = u"com.sun.star.table.CellAddressConversion"_ustr; +inline constexpr OUString SERVICE_RANGEADDRESS_CONVERSION = u"com.sun.star.table.CellRangeAddressConversion"_ustr; + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/genericpropertyhandler.cxx b/extensions/source/propctrlr/genericpropertyhandler.cxx new file mode 100644 index 0000000000..cf359bc152 --- /dev/null +++ b/extensions/source/propctrlr/genericpropertyhandler.cxx @@ -0,0 +1,634 @@ +/* -*- 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 "enumrepresentation.hxx" +#include "genericpropertyhandler.hxx" +#include "handlerhelper.hxx" + +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/lang/NullPointerException.hpp> +#include <com/sun/star/reflection/XEnumTypeDescription.hpp> +#include <com/sun/star/beans/theIntrospection.hpp> +#include <com/sun/star/inspection/PropertyControlType.hpp> +#include <com/sun/star/inspection/XHyperlinkControl.hpp> +#include <com/sun/star/awt/XActionListener.hpp> +#include <com/sun/star/script/Converter.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/frame/Desktop.hpp> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/extract.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/types.hxx> +#include <o3tl/safeint.hxx> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <algorithm> + +namespace pcr +{ + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::script; + using namespace ::com::sun::star::frame; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::util; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::reflection; + using namespace ::com::sun::star::inspection; + using ::com::sun::star::awt::XActionListener; + using ::com::sun::star::awt::ActionEvent; + + namespace { + + class EnumRepresentation : public IPropertyEnumRepresentation + { + private: + Reference< XEnumTypeDescription > m_xTypeDescription; + Type m_aEnumType; + + public: + EnumRepresentation( const Reference< XComponentContext >& _rxContext, const Type& _rEnumType ); + EnumRepresentation(const EnumRepresentation&) = delete; + EnumRepresentation& operator=(const EnumRepresentation&) = delete; + + // IPropertyEnumRepresentation implementqation + virtual std::vector< OUString > + getDescriptions() const override; + virtual void getValueFromDescription( const OUString& _rDescription, css::uno::Any& _out_rValue ) const override; + virtual OUString getDescriptionForValue( const css::uno::Any& _rEnumValue ) const override; + + private: + void impl_getValues( Sequence< sal_Int32 >& _out_rValues ) const; + }; + + } + + EnumRepresentation::EnumRepresentation( const Reference< XComponentContext >& _rxContext, const Type& _rEnumType ) + :m_aEnumType( _rEnumType ) + { + try + { + if ( _rxContext.is() ) + { + Reference< XHierarchicalNameAccess > xTypeDescProv( + _rxContext->getValueByName("/singletons/com.sun.star.reflection.theTypeDescriptionManager"), + UNO_QUERY_THROW ); + + m_xTypeDescription.set( xTypeDescProv->getByHierarchicalName( m_aEnumType.getTypeName() ), UNO_QUERY_THROW ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EnumRepresentation::EnumRepresentation" ); + } + } + + std::vector< OUString > EnumRepresentation::getDescriptions() const + { + Sequence< OUString > aNames; + try + { + if ( m_xTypeDescription.is() ) + aNames = m_xTypeDescription->getEnumNames(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EnumRepresentation::getDescriptions" ); + } + + return std::vector< OUString >( std::cbegin(aNames), std::cend(aNames) ); + } + + void EnumRepresentation::impl_getValues( Sequence< sal_Int32 >& _out_rValues ) const + { + _out_rValues.realloc( 0 ); + try + { + if ( m_xTypeDescription.is() ) + _out_rValues = m_xTypeDescription->getEnumValues(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "EnumRepresentation::impl_getValues" ); + } + } + + void EnumRepresentation::getValueFromDescription( const OUString& _rDescription, Any& _out_rValue ) const + { + std::vector< OUString > aDescriptions( getDescriptions() ); + + sal_Int32 index = std::find( aDescriptions.begin(), aDescriptions.end(), + _rDescription ) - aDescriptions.begin(); + + Sequence< sal_Int32 > aValues; + impl_getValues( aValues ); + + if ( ( index >= 0 ) && ( index < aValues.getLength() ) ) + _out_rValue = ::cppu::int2enum( aValues[ index ], m_aEnumType ); + else + { + OSL_FAIL( "EnumRepresentation::getValueFromDescription: cannot convert!" ); + _out_rValue.clear(); + } + } + + OUString EnumRepresentation::getDescriptionForValue( const Any& _rEnumValue ) const + { + OUString sDescription; + + sal_Int32 nAsInt = 0; + OSL_VERIFY( ::cppu::enum2int( nAsInt, _rEnumValue ) ); + + Sequence< sal_Int32 > aValues; + impl_getValues( aValues ); + + sal_Int32 index = std::find( std::cbegin(aValues), std::cend(aValues), nAsInt ) - std::cbegin(aValues); + + std::vector< OUString > aDescriptions( getDescriptions() ); + if ( ( index >= 0 ) && ( o3tl::make_unsigned(index) < aDescriptions.size() ) ) + sDescription = aDescriptions[ index ]; + else + { + OSL_FAIL( "EnumRepresentation::getDescriptionForValue: cannot convert!" ); + } + return sDescription; + } + + typedef ::cppu::WeakImplHelper < XActionListener + > UrlClickHandler_Base; + + namespace { + + class UrlClickHandler : public UrlClickHandler_Base + { + Reference<XComponentContext> m_xContext; + public: + UrlClickHandler( const Reference<XComponentContext>& _rContext, const Reference< XHyperlinkControl >& _rxControl ); + + protected: + virtual ~UrlClickHandler() override; + + // XActionListener + virtual void SAL_CALL actionPerformed( const ActionEvent& rEvent ) override; + + // XEventListener + virtual void SAL_CALL disposing( const EventObject& Source ) override; + + protected: + void impl_dispatch_throw( const OUString& _rURL ); + }; + + } + + UrlClickHandler::UrlClickHandler( const Reference<XComponentContext>& _rContext, const Reference< XHyperlinkControl >& _rxControl ) + :m_xContext( _rContext ) + { + if ( !_rxControl.is() ) + throw NullPointerException(); + + osl_atomic_increment( &m_refCount ); + { + _rxControl->addActionListener( this ); + } + osl_atomic_decrement( &m_refCount ); + OSL_ENSURE( m_refCount > 0, "UrlClickHandler::UrlClickHandler: leaking!" ); + + } + + UrlClickHandler::~UrlClickHandler() + { + } + + void SAL_CALL UrlClickHandler::actionPerformed( const ActionEvent& rEvent ) + { + Reference< XPropertyControl > xControl( rEvent.Source, UNO_QUERY_THROW ); + Any aControlValue( xControl->getValue() ); + + OUString sURL; + if ( aControlValue.hasValue() && !( aControlValue >>= sURL ) ) + throw RuntimeException( OUString(), *this ); + + if ( sURL.isEmpty() ) + return; + + impl_dispatch_throw( sURL ); + } + + void SAL_CALL UrlClickHandler::disposing( const EventObject& /*Source*/ ) + { + // not interested in + } + + void UrlClickHandler::impl_dispatch_throw( const OUString& _rURL ) + { + Reference< XURLTransformer > xTransformer( URLTransformer::create(m_xContext) ); + URL aURL; aURL.Complete = ".uno:OpenHyperlink"; + xTransformer->parseStrict( aURL ); + + Reference< XDesktop2 > xDispProv = Desktop::create( m_xContext ); + Reference< XDispatch > xDispatch( xDispProv->queryDispatch( aURL, OUString(), 0 ), UNO_SET_THROW ); + + Sequence aDispatchArgs{ comphelper::makePropertyValue("URL", _rURL) }; + + xDispatch->dispatch( aURL, aDispatchArgs ); + } + + + GenericPropertyHandler::GenericPropertyHandler( const Reference< XComponentContext >& _rxContext ) + :GenericPropertyHandler_Base( m_aMutex ) + ,m_xContext( _rxContext ) + ,m_aPropertyListeners( m_aMutex ) + ,m_bPropertyMapInitialized( false ) + { + m_xTypeConverter = Converter::create(_rxContext); + } + + GenericPropertyHandler::~GenericPropertyHandler() + { + } + + OUString SAL_CALL GenericPropertyHandler::getImplementationName( ) + { + return "com.sun.star.comp.extensions.GenericPropertyHandler"; + } + + sal_Bool SAL_CALL GenericPropertyHandler::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService(this, ServiceName); + } + + Sequence< OUString > SAL_CALL GenericPropertyHandler::getSupportedServiceNames( ) + { + return { "com.sun.star.inspection.GenericPropertyHandler" }; + } + + void SAL_CALL GenericPropertyHandler::inspect( const Reference< XInterface >& _rxIntrospectee ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( !_rxIntrospectee.is() ) + throw NullPointerException(); + + // revoke old property change listeners + ::comphelper::OInterfaceIteratorHelper2 iterRemove( m_aPropertyListeners ); + ::comphelper::OInterfaceIteratorHelper2 iterReAdd( m_aPropertyListeners ); // this holds a copy of the container ... + while ( iterRemove.hasMoreElements() ) + m_xComponent->removePropertyChangeListener( OUString(), static_cast< XPropertyChangeListener* >( iterRemove.next() ) ); + + m_xComponentIntrospectionAccess.clear(); + m_xComponent.clear(); + m_xPropertyState.clear(); + + // create an introspection adapter for the component + Reference< XIntrospection > xIntrospection = theIntrospection::get( m_xContext ); + + Reference< XIntrospectionAccess > xIntrospectionAccess( xIntrospection->inspect( Any( _rxIntrospectee ) ) ); + if ( !xIntrospectionAccess.is() ) + throw RuntimeException("The introspection service could not handle the given component.", *this ); + + m_xComponent.set( xIntrospectionAccess->queryAdapter( cppu::UnoType<XPropertySet>::get() ), UNO_QUERY_THROW ); + // now that we survived so far, remember m_xComponentIntrospectionAccess + m_xComponentIntrospectionAccess = xIntrospectionAccess; + m_xPropertyState.set(m_xComponent, css::uno::UNO_QUERY); + + m_bPropertyMapInitialized = false; + m_aProperties.clear(); + + // re-add the property change listeners + while ( iterReAdd.hasMoreElements() ) + m_xComponent->addPropertyChangeListener( OUString(), static_cast< XPropertyChangeListener* >( iterReAdd.next() ) ); + } + + Any SAL_CALL GenericPropertyHandler::getPropertyValue( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !m_xComponent.is() ) + throw UnknownPropertyException(_rPropertyName); + + return m_xComponent->getPropertyValue( _rPropertyName ); + } + + void SAL_CALL GenericPropertyHandler::setPropertyValue( const OUString& _rPropertyName, const Any& _rValue ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !m_xComponent.is() ) + throw UnknownPropertyException(_rPropertyName); + + m_xComponent->setPropertyValue( _rPropertyName, _rValue ); + } + + ::rtl::Reference< IPropertyEnumRepresentation > GenericPropertyHandler::impl_getEnumConverter( const Type& _rEnumType ) + { + ::rtl::Reference< IPropertyEnumRepresentation >& rConverter = m_aEnumConverters[ _rEnumType ]; + if ( !rConverter.is() ) + rConverter = new EnumRepresentation( m_xContext, _rEnumType ); + return rConverter; + } + + Any SAL_CALL GenericPropertyHandler::convertToPropertyValue( const OUString& _rPropertyName, const Any& _rControlValue ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + impl_ensurePropertyMap(); + + PropertyMap::const_iterator pos = m_aProperties.find( _rPropertyName ); + if ( pos == m_aProperties.end() ) + throw UnknownPropertyException(_rPropertyName); + + Any aPropertyValue; + if ( !_rControlValue.hasValue() ) + // NULL is converted to NULL + return aPropertyValue; + + if ( pos->second.Type.getTypeClass() == TypeClass_ENUM ) + { + OUString sControlValue; + OSL_VERIFY( _rControlValue >>= sControlValue ); + impl_getEnumConverter( pos->second.Type )->getValueFromDescription( sControlValue, aPropertyValue ); + } + else + aPropertyValue = PropertyHandlerHelper::convertToPropertyValue( m_xContext, m_xTypeConverter, pos->second, _rControlValue ); + + return aPropertyValue; + } + + Any SAL_CALL GenericPropertyHandler::convertToControlValue( const OUString& _rPropertyName, const Any& _rPropertyValue, const Type& _rControlValueType ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + impl_ensurePropertyMap(); + + PropertyMap::const_iterator pos = m_aProperties.find( _rPropertyName ); + if ( pos == m_aProperties.end() ) + throw UnknownPropertyException(_rPropertyName); + + Any aControlValue; + if ( !_rPropertyValue.hasValue() ) + // NULL is converted to NULL + return aControlValue; + + if ( pos->second.Type.getTypeClass() == TypeClass_ENUM ) + { + aControlValue <<= impl_getEnumConverter( pos->second.Type )->getDescriptionForValue( _rPropertyValue ); + } + else + aControlValue = PropertyHandlerHelper::convertToControlValue( m_xContext, m_xTypeConverter, _rPropertyValue, _rControlValueType ); + return aControlValue; + } + + PropertyState SAL_CALL GenericPropertyHandler::getPropertyState( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyState eState = PropertyState_DIRECT_VALUE; + if ( m_xPropertyState.is() ) + eState = m_xPropertyState->getPropertyState( _rPropertyName ); + return eState; + } + + void SAL_CALL GenericPropertyHandler::addPropertyChangeListener(const Reference< XPropertyChangeListener >& _rxListener) + { + if ( !_rxListener.is() ) + throw NullPointerException(); + + ::osl::MutexGuard aGuard( m_aMutex ); + m_aPropertyListeners.addInterface( _rxListener ); + if ( m_xComponent.is() ) + { + try + { + m_xComponent->addPropertyChangeListener( OUString(), _rxListener ); + } + catch( const UnknownPropertyException& ) + { + OSL_FAIL( "GenericPropertyHandler::addPropertyChangeListener:\nThe inspected component does not allow registering for all properties at once! This violates the interface contract!" ); + } + } + } + + void SAL_CALL GenericPropertyHandler::removePropertyChangeListener( const Reference< XPropertyChangeListener >& _rxListener ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( m_xComponent.is() ) + { + try + { + m_xComponent->removePropertyChangeListener( OUString(), _rxListener ); + } + catch( const UnknownPropertyException& ) + { + OSL_FAIL( "GenericPropertyHandler::removePropertyChangeListener:\nThe inspected component does not allow de-registering for all properties at once! This violates the interface contract!" ); + } + } + m_aPropertyListeners.removeInterface( _rxListener ); + } + + void GenericPropertyHandler::impl_ensurePropertyMap() + { + if ( m_bPropertyMapInitialized ) + return; + + m_bPropertyMapInitialized = true; + try + { + Reference< XPropertySetInfo > xPSI; + if ( m_xComponent.is() ) + xPSI = m_xComponent->getPropertySetInfo(); + Sequence< Property > aProperties; + if ( xPSI.is() ) + aProperties = xPSI->getProperties(); + DBG_ASSERT( aProperties.hasElements(), "GenericPropertyHandler::getSupportedProperties: no properties!" ); + + for ( auto const & property : std::as_const(aProperties) ) + { + switch ( property.Type.getTypeClass() ) + { + case TypeClass_BOOLEAN: + case TypeClass_BYTE: + case TypeClass_SHORT: + case TypeClass_UNSIGNED_SHORT: + case TypeClass_LONG: + case TypeClass_UNSIGNED_LONG: + case TypeClass_HYPER: + case TypeClass_UNSIGNED_HYPER: + case TypeClass_FLOAT: + case TypeClass_DOUBLE: + case TypeClass_ENUM: + case TypeClass_STRING: + // allowed, we can handle this type + break; + + case TypeClass_SEQUENCE: + { + TypeClass eElementTypeClass = ::comphelper::getSequenceElementType( property.Type ).getTypeClass(); + if ( ( eElementTypeClass != TypeClass_STRING ) + && ( eElementTypeClass != TypeClass_BYTE ) + && ( eElementTypeClass != TypeClass_SHORT ) + && ( eElementTypeClass != TypeClass_UNSIGNED_SHORT ) + && ( eElementTypeClass != TypeClass_LONG ) + && ( eElementTypeClass != TypeClass_UNSIGNED_LONG ) + ) + // can only handle the above + continue; + } + break; + + default: + // next property, we don't support this type + continue; + } + + m_aProperties.emplace( property.Name, property ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "GenericPropertyHandler::impl_ensurePropertyMap" ); + } + } + + Sequence< Property > SAL_CALL GenericPropertyHandler::getSupportedProperties() + { + ::osl::MutexGuard aGuard( m_aMutex ); + impl_ensurePropertyMap(); + + return comphelper::mapValuesToSequence( m_aProperties ); + } + + Sequence< OUString > SAL_CALL GenericPropertyHandler::getSupersededProperties( ) + { + // no superseded properties at all. This handler offers the very basic PropertyHandler + // functionality, so it's much more likely that other handlers want to supersede + // *our* properties... + return Sequence< OUString >( ); + } + + Sequence< OUString > SAL_CALL GenericPropertyHandler::getActuatingProperties( ) + { + // This basic PropertyHandler implementation is too dumb^Wgeneric to know + // anything about property dependencies + return Sequence< OUString >( ); + } + + LineDescriptor SAL_CALL GenericPropertyHandler::describePropertyLine( const OUString& _rPropertyName, + const Reference< XPropertyControlFactory >& _rxControlFactory ) + { + if ( !_rxControlFactory.is() ) + throw NullPointerException(); + + ::osl::MutexGuard aGuard( m_aMutex ); + impl_ensurePropertyMap(); + + PropertyMap::const_iterator pos = m_aProperties.find( _rPropertyName ); + if ( pos == m_aProperties.end() ) + throw UnknownPropertyException(_rPropertyName); + + LineDescriptor aDescriptor; + aDescriptor.DisplayName = _rPropertyName; + switch ( pos->second.Type.getTypeClass() ) + { + case TypeClass_ENUM: + aDescriptor.Control = PropertyHandlerHelper::createListBoxControl( _rxControlFactory, + impl_getEnumConverter( pos->second.Type )->getDescriptions(), + PropertyHandlerHelper::requiresReadOnlyControl( pos->second.Attributes ), + false ); + break; + case TypeClass_STRING: + { + // some special handling for URL properties + bool bIsURLProperty = _rPropertyName.endsWith( "URL" ); + if ( bIsURLProperty ) + { + aDescriptor.Control = _rxControlFactory->createPropertyControl( + PropertyControlType::HyperlinkField, PropertyHandlerHelper::requiresReadOnlyControl( pos->second.Attributes ) ); + + Reference< XHyperlinkControl > xControl( aDescriptor.Control, UNO_QUERY_THROW ); + new UrlClickHandler( m_xContext, xControl ); + } + } + break; + default: + break; + } + // fallback + if ( !aDescriptor.Control.is() ) + PropertyHandlerHelper::describePropertyLine( pos->second, aDescriptor, _rxControlFactory ); + + aDescriptor.Category = "General"; + return aDescriptor; + } + + sal_Bool SAL_CALL GenericPropertyHandler::isComposable( const OUString& /*_rPropertyName*/ ) + { + return false; + } + + InteractiveSelectionResult SAL_CALL GenericPropertyHandler::onInteractivePropertySelection( const OUString& /*_rPropertyName*/, sal_Bool /*_bPrimary*/, Any& /*_rData*/, const Reference< XObjectInspectorUI >& /*_rxInspectorUI*/ ) + { + OSL_FAIL( "GenericPropertyHandler::onInteractivePropertySelection: I'm too dumb to know anything about property browse buttons!" ); + return InteractiveSelectionResult_Cancelled; + } + + void SAL_CALL GenericPropertyHandler::actuatingPropertyChanged( const OUString& /*_rActuatingPropertyName*/, const Any& /*_rNewValue*/, const Any& /*_rOldValue*/, const Reference< XObjectInspectorUI >& /*_rxInspectorUI*/, sal_Bool /*_bFirstTimeInit*/ ) + { + OSL_FAIL( "GenericPropertyHandler::actuatingPropertyChanged: no no no, I did not register for any actuating properties!" ); + } + + sal_Bool SAL_CALL GenericPropertyHandler::suspend( sal_Bool /*_bSuspend*/ ) + { + return true; + } + + void SAL_CALL GenericPropertyHandler::disposing() + { + m_aPropertyListeners.clear(); + // not disposeAndClear: the listeners are (virtually) listeners at our introspectee, not + // at this handler instance + } + + void SAL_CALL GenericPropertyHandler::dispose( ) + { + GenericPropertyHandler_Base::WeakComponentImplHelperBase::dispose(); + m_xComponentIntrospectionAccess.clear(); + m_xComponent.clear(); + m_xTypeConverter.clear(); + m_xPropertyState.clear(); + } + void SAL_CALL GenericPropertyHandler::addEventListener( const css::uno::Reference< css::lang::XEventListener >& Listener ) + { + GenericPropertyHandler_Base::WeakComponentImplHelperBase::addEventListener( Listener ); + } + void SAL_CALL GenericPropertyHandler::removeEventListener( const css::uno::Reference< css::lang::XEventListener >& Listener ) + { + GenericPropertyHandler_Base::WeakComponentImplHelperBase::removeEventListener( Listener ); + } + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_GenericPropertyHandler_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::GenericPropertyHandler(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/genericpropertyhandler.hxx b/extensions/source/propctrlr/genericpropertyhandler.hxx new file mode 100644 index 0000000000..b2114be3cc --- /dev/null +++ b/extensions/source/propctrlr/genericpropertyhandler.hxx @@ -0,0 +1,139 @@ +/* -*- 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 . + */ + +#pragma once + +#include "pcrcommontypes.hxx" +#include "pcrcommon.hxx" + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/beans/XIntrospectionAccess.hpp> +#include <com/sun/star/inspection/XPropertyHandler.hpp> +#include <com/sun/star/script/XTypeConverter.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <comphelper/interfacecontainer2.hxx> +#include <cppuhelper/compbase.hxx> +#include <rtl/ref.hxx> + +#include <map> + + +namespace pcr +{ + + + struct TypeLess + { + bool operator()( const css::uno::Type& _rLHS, const css::uno::Type& _rRHS ) const + { + return _rLHS.getTypeName() < _rRHS.getTypeName(); + } + }; + + class IPropertyEnumRepresentation; + + //= GenericPropertyHandler + + typedef ::cppu::WeakComponentImplHelper < css::inspection::XPropertyHandler + , css::lang::XServiceInfo + > GenericPropertyHandler_Base; + class GenericPropertyHandler final : public GenericPropertyHandler_Base + { + mutable ::osl::Mutex m_aMutex; + + /// the service factory for creating services + css::uno::Reference< css::uno::XComponentContext > m_xContext; + /// need this to keep alive as long as m_xComponent lives + css::uno::Reference< css::beans::XIntrospectionAccess > m_xComponentIntrospectionAccess; + /// the properties of the object we're handling + css::uno::Reference< css::beans::XPropertySet > m_xComponent; + /// cached interface of ->m_xComponent + css::uno::Reference< css::beans::XPropertyState > m_xPropertyState; + /// type converter, needed on various occasions + css::uno::Reference< css::script::XTypeConverter > m_xTypeConverter; + /// cache of our supported properties + PropertyMap m_aProperties; + /// property change listeners + ::comphelper::OInterfaceContainerHelper2 m_aPropertyListeners; + std::map< css::uno::Type, ::rtl::Reference< IPropertyEnumRepresentation >, TypeLess > + m_aEnumConverters; + + /// has ->m_aProperties been initialized? + bool m_bPropertyMapInitialized : 1; + + public: + explicit GenericPropertyHandler( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext + ); + + virtual ~GenericPropertyHandler() override; + + private: + // XPropertyHandler overridables + virtual void SAL_CALL inspect( const css::uno::Reference< css::uno::XInterface >& _rxIntrospectee ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& _rPropertyName ) override; + virtual void SAL_CALL setPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rValue ) override; + virtual css::uno::Any SAL_CALL convertToPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rControlValue ) override; + virtual css::uno::Any SAL_CALL convertToControlValue( const OUString& _rPropertyName, const css::uno::Any& _rPropertyValue, const css::uno::Type& _rControlValueType ) override; + virtual css::beans::PropertyState SAL_CALL getPropertyState( const OUString& _rPropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ) override; + virtual css::uno::Sequence< css::beans::Property > + SAL_CALL getSupportedProperties() override; + virtual css::uno::Sequence< OUString > + SAL_CALL getSupersededProperties() override; + virtual css::uno::Sequence< OUString > SAL_CALL getActuatingProperties() override; + virtual css::inspection::LineDescriptor SAL_CALL describePropertyLine( const OUString& _rPropertyName, const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory ) override; + virtual sal_Bool SAL_CALL isComposable( const OUString& _rPropertyName ) override; + virtual css::inspection::InteractiveSelectionResult + SAL_CALL onInteractivePropertySelection( const OUString& _rPropertyName, sal_Bool _bPrimary, css::uno::Any& _rData, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI ) override; + virtual void SAL_CALL actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const css::uno::Any& _rNewValue, const css::uno::Any& _rOldValue, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI, sal_Bool _bFirstTimeInit ) override; + virtual sal_Bool SAL_CALL suspend( sal_Bool _bSuspend ) override; + + // XComponent + DECLARE_XCOMPONENT() + virtual void SAL_CALL disposing() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + /** ensures that ->m_aProperties is initialized + @precond + our mutex is locked + */ + void impl_ensurePropertyMap(); + + /** retrieves the enum converter for the given ENUM type + */ + ::rtl::Reference< IPropertyEnumRepresentation > + impl_getEnumConverter( const css::uno::Type& _rEnumType ); + + GenericPropertyHandler( const GenericPropertyHandler& ) = delete; + GenericPropertyHandler& operator=( const GenericPropertyHandler& ) = delete; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/handlerhelper.cxx b/extensions/source/propctrlr/handlerhelper.cxx new file mode 100644 index 0000000000..77743c622a --- /dev/null +++ b/extensions/source/propctrlr/handlerhelper.cxx @@ -0,0 +1,314 @@ +/* -*- 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 "handlerhelper.hxx" +#include <yesno.hrc> +#include "modulepcr.hxx" + +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/inspection/StringRepresentation.hpp> +#include <com/sun/star/util/XModifiable.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/inspection/LineDescriptor.hpp> +#include <com/sun/star/inspection/PropertyControlType.hpp> +#include <com/sun/star/inspection/XStringListControl.hpp> +#include <com/sun/star/inspection/XNumericControl.hpp> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/weldutils.hxx> + +#include <algorithm> + + +namespace pcr +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::awt; + using namespace ::com::sun::star::util; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::script; + using namespace ::com::sun::star::inspection; + + + //= PropertyHandlerHelper + + + void PropertyHandlerHelper::describePropertyLine( const Property& _rProperty, + LineDescriptor& /* [out] */ _out_rDescriptor, const Reference< XPropertyControlFactory >& _rxControlFactory ) + { + // display the pure property name - no L10N + _out_rDescriptor.DisplayName = _rProperty.Name; + + OSL_PRECOND( _rxControlFactory.is(), "PropertyHandlerHelper::describePropertyLine: no factory -> no control!" ); + if ( !_rxControlFactory.is() ) + return; + + bool bReadOnlyControl = requiresReadOnlyControl( _rProperty.Attributes ); + + // special handling for booleans (this will become a list) + if ( _rProperty.Type.getTypeClass() == TypeClass_BOOLEAN ) + { + _out_rDescriptor.Control = createListBoxControl(_rxControlFactory, RID_RSC_ENUM_YESNO, SAL_N_ELEMENTS(RID_RSC_ENUM_YESNO), bReadOnlyControl); + return; + } + + sal_Int16 nControlType = PropertyControlType::TextField; + switch ( _rProperty.Type.getTypeClass() ) + { + case TypeClass_BYTE: + case TypeClass_SHORT: + case TypeClass_UNSIGNED_SHORT: + case TypeClass_LONG: + case TypeClass_UNSIGNED_LONG: + case TypeClass_HYPER: + case TypeClass_UNSIGNED_HYPER: + case TypeClass_FLOAT: + case TypeClass_DOUBLE: + nControlType = PropertyControlType::NumericField; + break; + + case TypeClass_SEQUENCE: + nControlType = PropertyControlType::StringListField; + break; + + default: + OSL_FAIL( "PropertyHandlerHelper::describePropertyLine: don't know how to represent this at the UI!" ); + [[fallthrough]]; + + case TypeClass_STRING: + nControlType = PropertyControlType::TextField; + break; + } + + // create a control + _out_rDescriptor.Control = _rxControlFactory->createPropertyControl( nControlType, bReadOnlyControl ); + } + + + namespace + { + Reference< XPropertyControl > lcl_implCreateListLikeControl( + const Reference< XPropertyControlFactory >& _rxControlFactory, + std::vector< OUString >&& _rInitialListEntries, + bool _bReadOnlyControl, + bool _bSorted, + bool _bTrueIfListBoxFalseIfComboBox + ) + { + Reference< XStringListControl > xListControl( + _rxControlFactory->createPropertyControl( + _bTrueIfListBoxFalseIfComboBox ? PropertyControlType::ListBox : PropertyControlType::ComboBox, _bReadOnlyControl + ), + UNO_QUERY_THROW + ); + + if ( _bSorted ) + std::sort( _rInitialListEntries.begin(), _rInitialListEntries.end() ); + + for (auto const& initialEntry : _rInitialListEntries) + xListControl->appendListEntry(initialEntry); + return xListControl; + } + } + + Reference< XPropertyControl > PropertyHandlerHelper::createListBoxControl( const Reference< XPropertyControlFactory >& _rxControlFactory, + std::vector< OUString >&& _rInitialListEntries, bool _bReadOnlyControl, bool _bSorted ) + { + return lcl_implCreateListLikeControl(_rxControlFactory, std::move(_rInitialListEntries), _bReadOnlyControl, _bSorted, true); + } + + Reference< XPropertyControl > PropertyHandlerHelper::createListBoxControl( const Reference< XPropertyControlFactory >& _rxControlFactory, + const TranslateId* pTransIds, size_t nElements, bool _bReadOnlyControl ) + { + std::vector<OUString> aInitialListEntries; + for (size_t i = 0; i < nElements; ++i) + aInitialListEntries.push_back(PcrRes(pTransIds[i])); + return lcl_implCreateListLikeControl(_rxControlFactory, std::move(aInitialListEntries), _bReadOnlyControl, /*_bSorted*/false, true); + } + + Reference< XPropertyControl > PropertyHandlerHelper::createComboBoxControl( const Reference< XPropertyControlFactory >& _rxControlFactory, + std::vector< OUString >&& _rInitialListEntries, bool _bSorted ) + { + return lcl_implCreateListLikeControl( _rxControlFactory, std::move(_rInitialListEntries), /*_bReadOnlyControl*/false, _bSorted, false ); + } + + + Reference< XPropertyControl > PropertyHandlerHelper::createNumericControl( const Reference< XPropertyControlFactory >& _rxControlFactory, + sal_Int16 _nDigits, const Optional< double >& _rMinValue, const Optional< double >& _rMaxValue ) + { + Reference< XNumericControl > xNumericControl( + _rxControlFactory->createPropertyControl( PropertyControlType::NumericField, /*_bReadOnlyControl*/false ), + UNO_QUERY_THROW + ); + + xNumericControl->setDecimalDigits( _nDigits ); + xNumericControl->setMinValue( _rMinValue ); + xNumericControl->setMaxValue( _rMaxValue ); + + return xNumericControl; + } + + + Any PropertyHandlerHelper::convertToPropertyValue( const Reference< XComponentContext >& _rxContext,const Reference< XTypeConverter >& _rxTypeConverter, + const Property& _rProperty, const Any& _rControlValue ) + { + Any aPropertyValue( _rControlValue ); + if ( !aPropertyValue.hasValue() ) + // NULL is converted to NULL + return aPropertyValue; + + if ( aPropertyValue.getValueType().equals( _rProperty.Type ) ) + // nothing to do, type is already as desired + return aPropertyValue; + + if ( _rControlValue.getValueType().getTypeClass() == TypeClass_STRING ) + { + OUString sControlValue; + _rControlValue >>= sControlValue; + + Reference< XStringRepresentation > xConversionHelper = StringRepresentation::create( _rxContext,_rxTypeConverter ); + aPropertyValue = xConversionHelper->convertToPropertyValue( sControlValue, _rProperty.Type ); + } + else + { + try + { + if ( _rxTypeConverter.is() ) + aPropertyValue = _rxTypeConverter->convertTo( _rControlValue, _rProperty.Type ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION("extensions.propctrlr", + "caught an exception while converting via TypeConverter!"); + } + } + + return aPropertyValue; + } + + + Any PropertyHandlerHelper::convertToControlValue( const Reference< XComponentContext >& _rxContext,const Reference< XTypeConverter >& _rxTypeConverter, + const Any& _rPropertyValue, const Type& _rControlValueType ) + { + Any aControlValue( _rPropertyValue ); + if ( !aControlValue.hasValue() ) + // NULL is converted to NULL + return aControlValue; + + if ( _rControlValueType.getTypeClass() == TypeClass_STRING ) + { + Reference< XStringRepresentation > xConversionHelper = StringRepresentation::create( _rxContext,_rxTypeConverter ); + aControlValue <<= xConversionHelper->convertToControlValue( _rPropertyValue ); + } + else + { + try + { + if ( _rxTypeConverter.is() ) + aControlValue = _rxTypeConverter->convertTo( _rPropertyValue, _rControlValueType ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION("extensions.propctrlr", + "caught an exception while converting via TypeConverter!"); + } + } + + return aControlValue; + } + + + void PropertyHandlerHelper::setContextDocumentModified( const Reference<XComponentContext> & rContext ) + { + try + { + Reference< XModifiable > xDocumentModifiable( getContextDocument_throw(rContext), UNO_QUERY_THROW ); + xDocumentModifiable->setModified( true ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + Reference< XInterface > PropertyHandlerHelper::getContextDocument( const Reference<XComponentContext> & rContext ) + { + Reference< XInterface > xI; + try + { + xI = getContextDocument_throw( rContext ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "PropertyHandler::getContextValueByName" ); + } + return xI; + } + + Reference< XInterface > PropertyHandlerHelper::getContextDocument_throw( const Reference<XComponentContext> & rContext ) + { + Reference< XInterface > xI; + Any aReturn = rContext->getValueByName( "ContextDocument" ); + aReturn >>= xI; + return xI; + } + + weld::Window* PropertyHandlerHelper::getDialogParentFrame(const Reference<XComponentContext>& rContext) + { + weld::Window* pInspectorWindow = nullptr; + try + { + Reference< XWindow > xInspectorWindow(rContext->getValueByName( "DialogParentWindow" ), UNO_QUERY_THROW); + pInspectorWindow = Application::GetFrameWeld(xInspectorWindow); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + return pInspectorWindow; + } + + std::unique_ptr<weld::Builder> PropertyHandlerHelper::makeBuilder(const OUString& rUIFile, const Reference<XComponentContext>& rContext) + { + Reference<XWindow> xWindow(rContext->getValueByName("BuilderParent"), UNO_QUERY_THROW); + weld::TransportAsXWindow& rTunnel = dynamic_cast<weld::TransportAsXWindow&>(*xWindow); + return Application::CreateBuilder(rTunnel.getWidget(), rUIFile); + } + + void PropertyHandlerHelper::setBuilderParent(const css::uno::Reference<css::uno::XComponentContext>& rContext, weld::Widget* pParent) + { + Reference<css::container::XNameContainer> xName(rContext, UNO_QUERY_THROW); + Reference<XWindow> xWindow(new weld::TransportAsXWindow(pParent)); + xName->insertByName("BuilderParent", Any(xWindow)); + } + + void PropertyHandlerHelper::clearBuilderParent(const css::uno::Reference<css::uno::XComponentContext>& rContext) + { + Reference<css::container::XNameContainer> xName(rContext, UNO_QUERY_THROW); + xName->removeByName("BuilderParent"); + } + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/handlerhelper.hxx b/extensions/source/propctrlr/handlerhelper.hxx new file mode 100644 index 0000000000..555b8ee825 --- /dev/null +++ b/extensions/source/propctrlr/handlerhelper.hxx @@ -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 . + */ + +#pragma once + +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/script/XTypeConverter.hpp> +#include <com/sun/star/inspection/XPropertyControlFactory.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/Optional.hpp> +#include <unotools/resmgr.hxx> + +#include <memory> +#include <vector> + +namespace weld { class Builder; class Widget; class Window; } +namespace com::sun::star { + namespace inspection { + struct LineDescriptor; + } +} + +namespace pcr +{ + + + //= PropertyHandlerHelper + + class PropertyHandlerHelper + { + public: + /** helper for implementing XPropertyHandler::describePropertyLine in a generic way + */ + static void describePropertyLine( + const css::beans::Property& _rProperty, + css::inspection::LineDescriptor& /* [out] */ _out_rDescriptor, + const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory + ); + + /** helper for implementing XPropertyHandler::convertToPropertyValue + */ + static css::uno::Any convertToPropertyValue( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext, + const css::uno::Reference< css::script::XTypeConverter >& _rxTypeConverter, + const css::beans::Property& _rProperty, + const css::uno::Any& _rControlValue + ); + + /// helper for implementing XPropertyHandler::convertToControlValue + static css::uno::Any convertToControlValue( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext, + const css::uno::Reference< css::script::XTypeConverter >& _rxTypeConverter, + const css::uno::Any& _rPropertyValue, + const css::uno::Type& _rControlValueType + ); + + /** creates an <member scope="css::inspection">PropertyControlType::ListBox</member>-type control + and fills it with initial values + + @param _rxControlFactory + A control factory. Must not be <NULL/>. + + @param _rInitialListEntries + the initial values of the control + + @param _bReadOnlyControl + determines whether the control should be read-only + + @param _bSorted + determines whether the list entries should be sorted + + @return + the newly created control + */ + static css::uno::Reference< css::inspection::XPropertyControl > + createListBoxControl( + const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory, + std::vector< OUString >&& _rInitialListEntries, + bool _bReadOnlyControl, + bool _bSorted + ); + + /** creates an <member scope="css::inspection">PropertyControlType::ListBox</member>-type control + and fills it with initial values. + + @param _rxControlFactory + A control factory. Must not be <NULL/>. + + @param pTransIds + the initial translation ids for the value of the control + + @param nElements + the count of initial values of the control + + @param _bReadOnlyControl + determines whether the control should be read-only + + @return + the newly created control + */ + static css::uno::Reference< css::inspection::XPropertyControl > + createListBoxControl( + const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory, + const TranslateId* pTransIds, size_t nElements, + bool _bReadOnlyControl + ); + + /** creates an <member scope="css::inspection">PropertyControlType::ComboBox</member>-type control + and fills it with initial values + + @param _rxControlFactory + A control factory. Must not be <NULL/>. + + @param _rInitialListEntries + the initial values of the control + + @param _bSorted + determines whether the list entries should be sorted + + @return + the newly created control + */ + static css::uno::Reference< css::inspection::XPropertyControl > + createComboBoxControl( + const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory, + std::vector< OUString >&& _rInitialListEntries, + bool _bSorted + ); + + /** creates an <member scope="css::inspection">PropertyControlType::NumericField</member>-type control + and initializes it + + @param _rxControlFactory + A control factory. Must not be <NULL/>. + @param _nDigits + number of decimal digits for the control + (<member scope="css::inspection">XNumericControl::DecimalDigits</member>) + @param _rMinValue + minimum value which can be entered in the control + (<member scope="css::inspection">XNumericControl::MinValue</member>) + @param _rMaxValue + maximum value which can be entered in the control + (<member scope="css::inspection">XNumericControl::MaxValue</member>) + + @return + the newly created control + */ + static css::uno::Reference< css::inspection::XPropertyControl > + createNumericControl( + const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory, + sal_Int16 _nDigits, + const css::beans::Optional< double >& _rMinValue, + const css::beans::Optional< double >& _rMaxValue + ); + + /** marks the document passed in our UNO context as modified + + The method looks up a value called "ContextDocument" in the given UNO component context, + queries it for the ->css::util::XModifiable interface, and calls its + setModified method. If either of those steps fails, this is asserted in a non-product + version, and silently ignore otherwise. + + @param _rContext + the component context which was used to create the component calling this method + */ + static void setContextDocumentModified( + const css::uno::Reference< css::uno::XComponentContext > & _rContext + ); + + static css::uno::Reference< css::uno::XInterface > getContextDocument( const css::uno::Reference<css::uno::XComponentContext> & _rContext ); + + /// @throws css::uno::RuntimeException + static css::uno::Reference< css::uno::XInterface > getContextDocument_throw( const css::uno::Reference<css::uno::XComponentContext> & _rContext ); + + /** gets the window of the ObjectInspector in which a property handler lives + + The method looks up a value called "DialogParentWindow" in the given UNO component context, + queries it for XWindow, and returns the respective weld::Window*. If either of those steps fails, + this is asserted in a non-product version, and silently ignore otherwise. + + @param _rContext + the component context which was used to create the component calling this method + */ + static weld::Window* getDialogParentFrame( const css::uno::Reference< css::uno::XComponentContext > & _rContext ); + + + /** determines whether given PropertyAttributes require a to-be-created + <type scope="css::inspection">XPropertyControl</type> to be read-only + + @param _nPropertyAttributes + the attributes of the property which should be reflected by a to-be-created + <type scope="css::inspection">XPropertyControl</type> + */ + static bool requiresReadOnlyControl( sal_Int16 _nPropertyAttributes ) + { + return ( _nPropertyAttributes & css::beans::PropertyAttribute::READONLY ) != 0; + } + + static std::unique_ptr<weld::Builder> makeBuilder(const OUString& rUIFile, const css::uno::Reference<css::uno::XComponentContext>& rContext); + + static void setBuilderParent(const css::uno::Reference<css::uno::XComponentContext>& rContext, weld::Widget* pParent); + + static void clearBuilderParent(const css::uno::Reference<css::uno::XComponentContext>& rContext); + + private: + PropertyHandlerHelper( const PropertyHandlerHelper& ) = delete; + PropertyHandlerHelper& operator=( const PropertyHandlerHelper& ) = delete; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/inspectorhelpwindow.cxx b/extensions/source/propctrlr/inspectorhelpwindow.cxx new file mode 100644 index 0000000000..a57c4ffedb --- /dev/null +++ b/extensions/source/propctrlr/inspectorhelpwindow.cxx @@ -0,0 +1,42 @@ +/* -*- 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 "inspectorhelpwindow.hxx" + + +namespace pcr +{ + //= InspectorHelpWindow + InspectorHelpWindow::InspectorHelpWindow(weld::Builder& rBuilder) + : m_xHelpFrame(rBuilder.weld_widget("helpframe")) + , m_xHelpText(rBuilder.weld_text_view("helptext")) + { + } + + InspectorHelpWindow::~InspectorHelpWindow() + { + } + + void InspectorHelpWindow::SetText(const OUString& rStr) + { + m_xHelpText->set_text(rStr); + } + +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/inspectorhelpwindow.hxx b/extensions/source/propctrlr/inspectorhelpwindow.hxx new file mode 100644 index 0000000000..98b8efbab2 --- /dev/null +++ b/extensions/source/propctrlr/inspectorhelpwindow.hxx @@ -0,0 +1,44 @@ +/* -*- 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 . + */ +#pragma once + +#include <vcl/weld.hxx> + +namespace pcr +{ + //= InspectorHelpWindow + class InspectorHelpWindow + { + private: + std::unique_ptr<weld::Widget> m_xHelpFrame; + std::unique_ptr<weld::TextView> m_xHelpText; + + public: + explicit InspectorHelpWindow(weld::Builder& rBuilder); + ~InspectorHelpWindow(); + + void SetText(const OUString& rStr); + + void Show(bool bShow) { m_xHelpFrame->set_visible(bShow); } + bool IsVisible() const { return m_xHelpFrame->get_visible(); } + }; + +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/inspectormodelbase.cxx b/extensions/source/propctrlr/inspectormodelbase.cxx new file mode 100644 index 0000000000..aaa994e94a --- /dev/null +++ b/extensions/source/propctrlr/inspectormodelbase.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 <memory> +#include "inspectormodelbase.hxx" + +#include <com/sun/star/beans/PropertyAttribute.hpp> + +#include <comphelper/propertycontainerhelper.hxx> +#include <cppuhelper/supportsservice.hxx> + + +namespace pcr +{ + namespace + { + enum class ModelPropertyId + { + HAS_HELP_SECTION = 2000, + MIN_HELP_TEXT_LINES = 2001, + MAX_HELP_TEXT_LINES = 2002, + IS_READ_ONLY = 2003 + }; + }; + using ::com::sun::star::uno::Reference; + using ::com::sun::star::beans::XPropertySetInfo; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::beans::Property; + + namespace PropertyAttribute = ::com::sun::star::beans::PropertyAttribute; + + + //= InspectorModelProperties + + /** helper class for implementing the property set related functionality + of an ImplInspectorModel + */ + class InspectorModelProperties : public ::comphelper::OPropertyContainerHelper + { + private: + ::osl::Mutex& m_rMutex; + bool m_bHasHelpSection; + sal_Int32 m_nMinHelpTextLines; + sal_Int32 m_nMaxHelpTextLines; + bool m_bIsReadOnly; + std::unique_ptr< ::cppu::IPropertyArrayHelper > + m_pPropertyInfo; + + public: + explicit InspectorModelProperties( ::osl::Mutex& _rMutex ); + + using ::comphelper::OPropertyContainerHelper::convertFastPropertyValue; + using ::comphelper::OPropertyContainerHelper::setFastPropertyValue; + using ::comphelper::OPropertyContainerHelper::getFastPropertyValue; + + public: + bool hasHelpSection() const { return m_bHasHelpSection; } + bool isReadOnly() const { return m_bIsReadOnly; } + sal_Int32 getMinHelpTextLines() const { return m_nMinHelpTextLines; } + sal_Int32 getMaxHelpTextLines() const { return m_nMaxHelpTextLines; } + + css::uno::Reference< css::beans::XPropertySetInfo > + getPropertySetInfo(); + ::cppu::IPropertyArrayHelper& + getInfoHelper(); + + void constructWithHelpSection( sal_Int32 _nMinHelpTextLines, sal_Int32 _nMaxHelpTextLines ); + }; + + + //= InspectorModelProperties + + + InspectorModelProperties::InspectorModelProperties( ::osl::Mutex& _rMutex ) + :m_rMutex( _rMutex ) + ,m_bHasHelpSection( false ) + ,m_nMinHelpTextLines( 3 ) + ,m_nMaxHelpTextLines( 8 ) + ,m_bIsReadOnly( false ) + { + registerProperty( + "HasHelpSection", + static_cast<sal_Int32>(ModelPropertyId::HAS_HELP_SECTION), + PropertyAttribute::READONLY, + &m_bHasHelpSection, cppu::UnoType<decltype(m_bHasHelpSection)>::get() + ); + registerProperty( + "MinHelpTextLines", + static_cast<sal_Int32>(ModelPropertyId::MIN_HELP_TEXT_LINES), + PropertyAttribute::READONLY, + &m_nMinHelpTextLines, cppu::UnoType<decltype(m_nMinHelpTextLines)>::get() + ); + registerProperty( + "MaxHelpTextLines", + static_cast<sal_Int32>(ModelPropertyId::MAX_HELP_TEXT_LINES), + PropertyAttribute::READONLY, + &m_nMaxHelpTextLines, cppu::UnoType<decltype(m_nMaxHelpTextLines)>::get() + ); + registerProperty( + "IsReadOnly", + static_cast<sal_Int32>(ModelPropertyId::IS_READ_ONLY), + PropertyAttribute::BOUND, + &m_bIsReadOnly, cppu::UnoType<decltype(m_bIsReadOnly)>::get() + ); + } + + + void InspectorModelProperties::constructWithHelpSection( sal_Int32 _nMinHelpTextLines, sal_Int32 _nMaxHelpTextLines ) + { + m_bHasHelpSection = true; + m_nMinHelpTextLines = _nMinHelpTextLines; + m_nMaxHelpTextLines = _nMaxHelpTextLines; + // no need to notify this, those properties are not bound. Also, the method should + // only be used during construction phase, where we don't expect to have any listeners. + } + + + ::cppu::IPropertyArrayHelper& InspectorModelProperties::getInfoHelper() + { + ::osl::MutexGuard aGuard( m_rMutex ); + if (m_pPropertyInfo == nullptr) + { + Sequence< Property > aProperties; + describeProperties( aProperties ); + + m_pPropertyInfo.reset( new ::cppu::OPropertyArrayHelper( aProperties ) ); + } + return *m_pPropertyInfo; + } + + + Reference< XPropertySetInfo > InspectorModelProperties::getPropertySetInfo() + { + return ::cppu::OPropertySetHelper::createPropertySetInfo( getInfoHelper() ); + } + + + //= ImplInspectorModel + + ImplInspectorModel::ImplInspectorModel() + :ImplInspectorModel_PBase( GetBroadcastHelper() ) + ,m_pProperties( new InspectorModelProperties( m_aMutex ) ) + { + } + + + ImplInspectorModel::~ImplInspectorModel() + { + } + + + IMPLEMENT_FORWARD_XINTERFACE2( ImplInspectorModel, ImplInspectorModel_Base, ImplInspectorModel_PBase ) + + + IMPLEMENT_FORWARD_XTYPEPROVIDER2( ImplInspectorModel, ImplInspectorModel_Base, ImplInspectorModel_PBase ) + + + Reference< XPropertySetInfo > SAL_CALL ImplInspectorModel::getPropertySetInfo( ) + { + return m_pProperties->getPropertySetInfo(); + } + + + ::cppu::IPropertyArrayHelper& SAL_CALL ImplInspectorModel::getInfoHelper() + { + return m_pProperties->getInfoHelper(); + } + + + sal_Bool SAL_CALL ImplInspectorModel::convertFastPropertyValue( Any & rConvertedValue, Any & rOldValue, sal_Int32 nHandle, const Any& rValue ) + { + return m_pProperties->convertFastPropertyValue( rConvertedValue, rOldValue, nHandle, rValue ); + } + + + void SAL_CALL ImplInspectorModel::setFastPropertyValue_NoBroadcast( sal_Int32 nHandle, const Any& rValue ) + { + m_pProperties->setFastPropertyValue( nHandle, rValue ); + } + + + void SAL_CALL ImplInspectorModel::getFastPropertyValue( Any& rValue, sal_Int32 nHandle ) const + { + m_pProperties->getFastPropertyValue( rValue, nHandle ); + } + + + sal_Bool SAL_CALL ImplInspectorModel::getHasHelpSection() + { + return m_pProperties->hasHelpSection(); + } + + + ::sal_Int32 SAL_CALL ImplInspectorModel::getMinHelpTextLines() + { + return m_pProperties->getMinHelpTextLines(); + } + + + ::sal_Int32 SAL_CALL ImplInspectorModel::getMaxHelpTextLines() + { + return m_pProperties->getMaxHelpTextLines(); + } + + + sal_Bool SAL_CALL ImplInspectorModel::getIsReadOnly() + { + return m_pProperties->isReadOnly(); + } + + + void SAL_CALL ImplInspectorModel::setIsReadOnly( sal_Bool IsReadOnly ) + { + setFastPropertyValue( static_cast<sal_Int32>(ModelPropertyId::IS_READ_ONLY), Any( IsReadOnly ) ); + } + + sal_Bool SAL_CALL ImplInspectorModel::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService(this, ServiceName); + } + + + void ImplInspectorModel::enableHelpSectionProperties( sal_Int32 _nMinHelpTextLines, sal_Int32 _nMaxHelpTextLines ) + { + m_pProperties->constructWithHelpSection( _nMinHelpTextLines, _nMaxHelpTextLines ); + } + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/inspectormodelbase.hxx b/extensions/source/propctrlr/inspectormodelbase.hxx new file mode 100644 index 0000000000..527668b2fd --- /dev/null +++ b/extensions/source/propctrlr/inspectormodelbase.hxx @@ -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 . + */ + +#pragma once + +#include <com/sun/star/inspection/XObjectInspectorModel.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/propshlp.hxx> + +#include <comphelper/broadcasthelper.hxx> +#include <comphelper/uno3.hxx> + +#include <memory> + + +namespace pcr +{ + + + class InspectorModelProperties; + + //= ImplInspectorModel + + typedef ::cppu::WeakImplHelper < css::inspection::XObjectInspectorModel + , css::lang::XInitialization + , css::lang::XServiceInfo + > ImplInspectorModel_Base; + typedef ::cppu::OPropertySetHelper ImplInspectorModel_PBase; + + class ImplInspectorModel + :public ::comphelper::OMutexAndBroadcastHelper + ,public ImplInspectorModel_Base + ,public ImplInspectorModel_PBase + { + std::unique_ptr< InspectorModelProperties > m_pProperties; + + protected: + virtual ~ImplInspectorModel() override; + + public: + ImplInspectorModel(); + + DECLARE_XINTERFACE() + DECLARE_XTYPEPROVIDER() + + // css::beans::XPropertySet and friends + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + virtual ::cppu::IPropertyArrayHelper & SAL_CALL getInfoHelper() override; + virtual sal_Bool SAL_CALL convertFastPropertyValue( css::uno::Any & rConvertedValue, css::uno::Any & rOldValue, sal_Int32 nHandle, const css::uno::Any& rValue ) override; + virtual void SAL_CALL setFastPropertyValue_NoBroadcast( sal_Int32 nHandle, const css::uno::Any& rValue ) override; + virtual void SAL_CALL getFastPropertyValue( css::uno::Any& rValue, sal_Int32 nHandle ) const override; + + // css::inspection::XObjectInspectorModel + virtual sal_Bool SAL_CALL getHasHelpSection() override; + virtual ::sal_Int32 SAL_CALL getMinHelpTextLines() override; + virtual ::sal_Int32 SAL_CALL getMaxHelpTextLines() override; + virtual sal_Bool SAL_CALL getIsReadOnly() override; + virtual void SAL_CALL setIsReadOnly( sal_Bool IsReadOnly ) override; + + // css::lang::XServiceInfo + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + + protected: + void enableHelpSectionProperties( sal_Int32 _nMinHelpTextLines, sal_Int32 _nMaxHelpTextLines ); + + private: + using ImplInspectorModel_PBase::getFastPropertyValue; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/linedescriptor.hxx b/extensions/source/propctrlr/linedescriptor.hxx new file mode 100644 index 0000000000..a73eda7cf2 --- /dev/null +++ b/extensions/source/propctrlr/linedescriptor.hxx @@ -0,0 +1,51 @@ +/* -*- 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 . + */ +#pragma once + +#include <com/sun/star/inspection/LineDescriptor.hpp> +#include <com/sun/star/inspection/XPropertyHandler.hpp> + +namespace pcr +{ + //= OLineDescriptor + struct OLineDescriptor : public css::inspection::LineDescriptor + { + OUString sName; // the name of the property + css::uno::Reference< css::inspection::XPropertyHandler > + xPropertyHandler; // the handler for this property + css::uno::Any aValue; // the current value of the property + + bool bUnknownValue : 1; // is the property value currently "unknown"? (PropertyState_AMBIGUOUS) + bool bReadOnly : 1; + + OLineDescriptor() + :bUnknownValue( false ) + ,bReadOnly( false ) + { + } + + void assignFrom( const css::inspection::LineDescriptor& _rhs ) + { + css::inspection::LineDescriptor::operator=( _rhs ); + } + }; + +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/listselectiondlg.cxx b/extensions/source/propctrlr/listselectiondlg.cxx new file mode 100644 index 0000000000..521afdf449 --- /dev/null +++ b/extensions/source/propctrlr/listselectiondlg.cxx @@ -0,0 +1,139 @@ +/* -*- 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 "listselectiondlg.hxx" + +#include "formstrings.hxx" +#include <comphelper/sequence.hxx> +#include <utility> +#include <comphelper/diagnose_ex.hxx> + +namespace pcr +{ + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::beans; + + ListSelectionDialog::ListSelectionDialog(weld::Window* pParent, const Reference< XPropertySet >& _rxListBox, + OUString _sPropertyName, const OUString& _rPropertyUIName) + : GenericDialogController(pParent, "modules/spropctrlr/ui/listselectdialog.ui", "ListSelectDialog") + , m_xListBox ( _rxListBox ) + , m_sPropertyName(std::move( _sPropertyName )) + , m_xFrame(m_xBuilder->weld_frame("frame")) + , m_xEntries(m_xBuilder->weld_tree_view("treeview")) + { + OSL_PRECOND( m_xListBox.is(), "ListSelectionDialog::ListSelectionDialog: invalid list box!" ); + + m_xEntries->set_size_request(m_xEntries->get_approximate_digit_width() * 40, m_xEntries->get_height_rows(9)); + + m_xDialog->set_title(_rPropertyUIName); + m_xFrame->set_label(_rPropertyUIName); + + initialize( ); + } + + ListSelectionDialog::~ListSelectionDialog() + { + } + + short ListSelectionDialog::run() + { + short nResult = GenericDialogController::run(); + + if ( RET_OK == nResult ) + commitSelection(); + + return nResult; + } + + + void ListSelectionDialog::initialize( ) + { + if ( !m_xListBox.is() ) + return; + + try + { + // initialize the multi-selection flag + bool bMultiSelection = false; + OSL_VERIFY( m_xListBox->getPropertyValue( PROPERTY_MULTISELECTION ) >>= bMultiSelection ); + m_xEntries->set_selection_mode(bMultiSelection ? SelectionMode::Single : SelectionMode::Multiple); + + // fill the list box with all entries + Sequence< OUString > aListEntries; + OSL_VERIFY( m_xListBox->getPropertyValue( PROPERTY_STRINGITEMLIST ) >>= aListEntries ); + fillEntryList( aListEntries ); + + // select entries according to the property + Sequence< sal_Int16 > aSelection; + OSL_VERIFY( m_xListBox->getPropertyValue( m_sPropertyName ) >>= aSelection ); + selectEntries( aSelection ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "ListSelectionDialog::initialize" ); + } + } + + void ListSelectionDialog::commitSelection() + { + if ( !m_xListBox.is() ) + return; + + std::vector< sal_Int16 > aSelection; + collectSelection( aSelection ); + + try + { + m_xListBox->setPropertyValue( m_sPropertyName, Any( comphelper::containerToSequence(aSelection) ) ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "ListSelectionDialog::commitSelection" ); + } + } + + void ListSelectionDialog::fillEntryList( const Sequence< OUString >& _rListEntries ) + { + m_xEntries->freeze(); + m_xEntries->clear(); + for (auto const & entry : _rListEntries) + m_xEntries->append_text(entry); + m_xEntries->thaw(); + } + + void ListSelectionDialog::collectSelection( std::vector< sal_Int16 >& /* [out] */ _rSelection ) + { + auto aSelection = m_xEntries->get_selected_rows(); + _rSelection.resize(aSelection.size()); + for (auto row : aSelection) + _rSelection.push_back(row); + } + + void ListSelectionDialog::selectEntries( const Sequence< sal_Int16 >& /* [in ] */ _rSelection ) + { + m_xEntries->unselect_all(); + for (auto const & selection : _rSelection) + m_xEntries->select(selection); + } + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/listselectiondlg.hxx b/extensions/source/propctrlr/listselectiondlg.hxx new file mode 100644 index 0000000000..368e9e9aac --- /dev/null +++ b/extensions/source/propctrlr/listselectiondlg.hxx @@ -0,0 +1,60 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/weld.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> + +namespace pcr +{ + class ListSelectionDialog : public weld::GenericDialogController + { + private: + css::uno::Reference<css::beans::XPropertySet> m_xListBox; + OUString m_sPropertyName; + std::unique_ptr<weld::Frame> m_xFrame; + std::unique_ptr<weld::TreeView> m_xEntries; + + public: + ListSelectionDialog( + weld::Window* _pParent, + const css::uno::Reference< css::beans::XPropertySet >& _rxListBox, + OUString _sPropertyName, + const OUString& _rPropertyUIName + ); + virtual ~ListSelectionDialog() override; + + virtual short run() override; + + private: + void initialize( ); + void commitSelection(); + + void fillEntryList ( const css::uno::Sequence< OUString >& _rListEntries ); + + void selectEntries ( const css::uno::Sequence< sal_Int16 >& /* [in ] */ _rSelection ); + void collectSelection( std::vector< sal_Int16 >& /* [out] */ _rSelection ); + }; + +} // namespacepcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/modulepcr.cxx b/extensions/source/propctrlr/modulepcr.cxx new file mode 100644 index 0000000000..da8336e109 --- /dev/null +++ b/extensions/source/propctrlr/modulepcr.cxx @@ -0,0 +1,30 @@ +/* -*- 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 "modulepcr.hxx" + +#include <unotools/resmgr.hxx> + +namespace pcr +{ +OUString PcrRes(TranslateId aId) { return Translate::get(aId, Translate::Create("pcr")); } + +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/modulepcr.hxx b/extensions/source/propctrlr/modulepcr.hxx new file mode 100644 index 0000000000..d0b854893e --- /dev/null +++ b/extensions/source/propctrlr/modulepcr.hxx @@ -0,0 +1,30 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <unotools/resmgr.hxx> + +namespace pcr +{ +OUString PcrRes(TranslateId pId); +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/newdatatype.cxx b/extensions/source/propctrlr/newdatatype.cxx new file mode 100644 index 0000000000..b2399d6806 --- /dev/null +++ b/extensions/source/propctrlr/newdatatype.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 "newdatatype.hxx" + +namespace pcr +{ + + + //= NewDataTypeDialog + + + NewDataTypeDialog::NewDataTypeDialog(weld::Window* pParent, std::u16string_view _rNameBase, const std::vector< OUString >& _rProhibitedNames) + : GenericDialogController(pParent, "modules/spropctrlr/ui/datatypedialog.ui", "DataTypeDialog") + , m_aProhibitedNames( _rProhibitedNames.begin(), _rProhibitedNames.end() ) + , m_xName(m_xBuilder->weld_entry("entry")) + , m_xOK(m_xBuilder->weld_button("ok")) + { + m_xName->connect_changed(LINK(this, NewDataTypeDialog, OnNameModified)); + + // find an initial name + // for this, first remove trailing digits + sal_Int32 nStripUntil = _rNameBase.size(); + while ( nStripUntil > 0 ) + { + sal_Unicode nChar = _rNameBase[ --nStripUntil ]; + if ( ( nChar < '0' ) || ( nChar > '9' ) ) + { + if ( nChar == ' ' ) + --nStripUntil; // strip the space, too + break; + } + } + + OUString sNameBase = OUString::Concat(_rNameBase.substr( 0, nStripUntil ? nStripUntil + 1 : 0 )) + " "; + OUString sInitialName; + sal_Int32 nPostfixNumber = 1; + do + { + sInitialName = sNameBase + OUString::number(nPostfixNumber++); + } + while ( m_aProhibitedNames.find( sInitialName ) != m_aProhibitedNames.end() ); + + m_xName->set_text(sInitialName); + OnNameModified(*m_xName); + } + + NewDataTypeDialog::~NewDataTypeDialog() + { + } + + IMPL_LINK_NOARG(NewDataTypeDialog, OnNameModified, weld::Entry&, void) + { + OUString sCurrentName = GetName(); + bool bNameIsOK = ( !sCurrentName.isEmpty() ) + && ( m_aProhibitedNames.find( sCurrentName ) == m_aProhibitedNames.end() ); + + m_xOK->set_sensitive(bNameIsOK); + } +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/newdatatype.hxx b/extensions/source/propctrlr/newdatatype.hxx new file mode 100644 index 0000000000..ba4b6fa382 --- /dev/null +++ b/extensions/source/propctrlr/newdatatype.hxx @@ -0,0 +1,53 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/weld.hxx> + +#include <set> +#include <vector> + + +namespace pcr +{ + //= NewDataTypeDialog + class NewDataTypeDialog : public weld::GenericDialogController + { + private: + std::set<OUString> m_aProhibitedNames; + + std::unique_ptr<weld::Entry> m_xName; + std::unique_ptr<weld::Button> m_xOK; + public: + NewDataTypeDialog(weld::Window* _pParent, std::u16string_view _rNameBase, + const std::vector< OUString >& _rProhibitedNames ); + virtual ~NewDataTypeDialog() override; + + OUString GetName() const { return m_xName->get_text(); } + + private: + DECL_LINK(OnNameModified, weld::Entry&, void); + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/objectinspectormodel.cxx b/extensions/source/propctrlr/objectinspectormodel.cxx new file mode 100644 index 0000000000..a05d9347f9 --- /dev/null +++ b/extensions/source/propctrlr/objectinspectormodel.cxx @@ -0,0 +1,193 @@ +/* -*- 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 "pcrcommon.hxx" +#include "inspectormodelbase.hxx" + +#include <com/sun/star/ucb/AlreadyInitializedException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + + +namespace pcr +{ + + + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::uno::Any; + using ::com::sun::star::inspection::PropertyCategoryDescriptor; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::lang::IllegalArgumentException; + using ::com::sun::star::ucb::AlreadyInitializedException; + + + //= ObjectInspectorModel + + namespace { + + class ObjectInspectorModel : public ImplInspectorModel + { + private: + Sequence< Any > m_aFactories; + + public: + ObjectInspectorModel(); + + // XObjectInspectorModel + virtual Sequence< Any > SAL_CALL getHandlerFactories() override; + virtual Sequence< PropertyCategoryDescriptor > SAL_CALL describeCategories( ) override; + virtual ::sal_Int32 SAL_CALL getPropertyOrderIndex( const OUString& PropertyName ) override; + + // XInitialization + virtual void SAL_CALL initialize( const Sequence< Any >& aArguments ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + protected: + void createDefault(); + void createWithHandlerFactories( const Sequence< Any >& _rFactories ); + void createWithHandlerFactoriesAndHelpSection( const Sequence< Any >& _rFactories, sal_Int32 _nMinHelpTextLines, sal_Int32 _nMaxHelpTextLines ); + + private: + /** checks a given condition to be <TRUE/>, and throws an IllegalArgumentException if not + */ + void impl_verifyArgument_throw( bool _bCondition, sal_Int16 _nArgumentPosition ); + }; + + } + + //= ObjectInspectorModel + + ObjectInspectorModel::ObjectInspectorModel() + { + } + + + Sequence< Any > SAL_CALL ObjectInspectorModel::getHandlerFactories() + { + return m_aFactories; + } + + + Sequence< PropertyCategoryDescriptor > SAL_CALL ObjectInspectorModel::describeCategories( ) + { + // no category info provided by this default implementation + return Sequence< PropertyCategoryDescriptor >( ); + } + + + ::sal_Int32 SAL_CALL ObjectInspectorModel::getPropertyOrderIndex( const OUString& /*PropertyName*/ ) + { + // no ordering provided by this default implementation + return 0; + } + + + void SAL_CALL ObjectInspectorModel::initialize( const Sequence< Any >& _arguments ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( m_aFactories.hasElements() ) + throw AlreadyInitializedException(); + + StlSyntaxSequence< Any > arguments( _arguments ); + if ( arguments.empty() ) + { // constructor: "createDefault()" + createDefault(); + return; + } + + Sequence< Any > factories; + impl_verifyArgument_throw( arguments[0] >>= factories, 1 ); + + if ( arguments.size() == 1 ) + { // constructor: "createWithHandlerFactories( any[] )" + createWithHandlerFactories( factories ); + return; + } + + if ( arguments.size() == 3 ) + { // constructor: "createWithHandlerFactoriesAndHelpSection( any[], long, long )" + sal_Int32 nMinHelpTextLines( 0 ), nMaxHelpTextLines( 0 ); + impl_verifyArgument_throw( arguments[1] >>= nMinHelpTextLines, 2 ); + impl_verifyArgument_throw( arguments[2] >>= nMaxHelpTextLines, 3 ); + createWithHandlerFactoriesAndHelpSection( factories, nMinHelpTextLines, nMaxHelpTextLines ); + return; + } + + impl_verifyArgument_throw( false, 2 ); + } + + + OUString SAL_CALL ObjectInspectorModel::getImplementationName( ) + { + return "org.openoffice.comp.extensions.ObjectInspectorModel"; + } + + + Sequence< OUString > SAL_CALL ObjectInspectorModel::getSupportedServiceNames( ) + { + return { "com.sun.star.inspection.ObjectInspectorModel" }; + } + + + void ObjectInspectorModel::createDefault() + { + m_aFactories = { Any(OUString( "com.sun.star.inspection.GenericPropertyHandler" )) }; + } + + + void ObjectInspectorModel::createWithHandlerFactories( const Sequence< Any >& _rFactories ) + { + impl_verifyArgument_throw( _rFactories.hasElements(), 1 ); + m_aFactories = _rFactories; + } + + + void ObjectInspectorModel::createWithHandlerFactoriesAndHelpSection( const Sequence< Any >& _rFactories, sal_Int32 _nMinHelpTextLines, sal_Int32 _nMaxHelpTextLines ) + { + impl_verifyArgument_throw( _rFactories.hasElements(), 1 ); + impl_verifyArgument_throw( _nMinHelpTextLines >= 1, 2 ); + impl_verifyArgument_throw( _nMaxHelpTextLines >= 1, 3 ); + impl_verifyArgument_throw( _nMinHelpTextLines <= _nMaxHelpTextLines, 2 ); + + m_aFactories = _rFactories; + enableHelpSectionProperties( _nMinHelpTextLines, _nMaxHelpTextLines ); + } + + + void ObjectInspectorModel::impl_verifyArgument_throw( bool _bCondition, sal_Int16 _nArgumentPosition ) + { + if ( !_bCondition ) + throw IllegalArgumentException( OUString(), *this, _nArgumentPosition ); + } + + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_ObjectInspectorModel_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::ObjectInspectorModel()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/pcr.component b/extensions/source/propctrlr/pcr.component new file mode 100644 index 0000000000..3d0214ded4 --- /dev/null +++ b/extensions/source/propctrlr/pcr.component @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="StringRepresentation" + constructor="extensions_propctrlr_StringRepresentation_get_implementation"> + <service name="com.sun.star.inspection.StringRepresentation"/> + </implementation> + <implementation name="com.sun.star.comp.extensions.ButtonNavigationHandler" + constructor="extensions_propctrlr_ButtonNavigationHandler_get_implementation"> + <service name="com.sun.star.form.inspection.ButtonNavigationHandler"/> + </implementation> + <implementation name="com.sun.star.comp.extensions.CellBindingPropertyHandler" + constructor="extensions_propctrlr_CellBindingPropertyHandler_get_implementation"> + <service name="com.sun.star.form.inspection.CellBindingPropertyHandler"/> + </implementation> + <implementation name="com.sun.star.comp.extensions.EFormsPropertyHandler" + constructor="extensions_propctrlr_EFormsPropertyHandler_get_implementation"> + <service name="com.sun.star.form.inspection.XMLFormsPropertyHandler"/> + </implementation> + <implementation name="com.sun.star.comp.extensions.EditPropertyHandler" + constructor="extensions_propctrlr_EditPropertyHandler_get_implementation"> + <service name="com.sun.star.form.inspection.EditPropertyHandler"/> + </implementation> + <implementation name="com.sun.star.comp.extensions.EventHandler" + constructor="extensions_propctrlr_EventHandler_get_implementation"> + <service name="com.sun.star.form.inspection.EventHandler"/> + </implementation> + <implementation name="com.sun.star.comp.extensions.FormComponentPropertyHandler" + constructor="extensions_propctrlr_FormComponentPropertyHandler_get_implementation"> + <service name="com.sun.star.form.inspection.FormComponentPropertyHandler"/> + </implementation> + <implementation name="com.sun.star.comp.extensions.FormGeometryHandler" + constructor="extensions_propctrlr_FormGeometryHandler_get_implementation"> + <service name="com.sun.star.form.inspection.FormGeometryHandler"/> + </implementation> + <implementation name="com.sun.star.comp.extensions.GenericPropertyHandler" + constructor="extensions_propctrlr_GenericPropertyHandler_get_implementation"> + <service name="com.sun.star.inspection.GenericPropertyHandler"/> + </implementation> + <implementation name="com.sun.star.comp.extensions.SubmissionPropertyHandler" + constructor="extensions_propctrlr_SubmissionPropertyHandler_get_implementation"> + <service name="com.sun.star.form.inspection.SubmissionPropertyHandler"/> + </implementation> + <implementation name="com.sun.star.comp.extensions.XSDValidationPropertyHandler" + constructor="extensions_propctrlr_XSDValidationPropertyHandler_get_implementation"> + <service name="com.sun.star.form.inspection.XSDValidationPropertyHandler"/> + </implementation> + <implementation name="org.openoffice.comp.extensions.DefaultFormComponentInspectorModel" + constructor="extensions_propctrlr_DefaultFormComponentInspectorModel_get_implementation"> + <service name="com.sun.star.form.inspection.DefaultFormComponentInspectorModel"/> + </implementation> + <implementation name="org.openoffice.comp.extensions.DefaultHelpProvider" + constructor="extensions_propctrlr_DefaultHelpProvider_get_implementation"> + <service name="com.sun.star.inspection.DefaultHelpProvider"/> + </implementation> + <implementation name="org.openoffice.comp.extensions.DialogController" + constructor="extensions_propctrlr_DialogController_get_implementation"> + <service name="com.sun.star.awt.PropertyBrowserController"/> + </implementation> + <implementation name="org.openoffice.comp.extensions.FormController" + constructor="extensions_propctrlr_FormController_get_implementation"> + <service name="com.sun.star.form.PropertyBrowserController"/> + </implementation> + <implementation name="org.openoffice.comp.extensions.ObjectInspector" + constructor="extensions_propctrlr_OPropertyBrowserController_get_implementation"> + <service name="com.sun.star.inspection.ObjectInspector"/> + </implementation> + <implementation name="org.openoffice.comp.extensions.ObjectInspectorModel" + constructor="extensions_propctrlr_ObjectInspectorModel_get_implementation"> + <service name="com.sun.star.inspection.ObjectInspectorModel"/> + </implementation> + <implementation name="org.openoffice.comp.form.ui.MasterDetailLinkDialog" + constructor="extensions_propctrlr_MasterDetailLinkDialog_get_implementation"> + <service name="com.sun.star.form.MasterDetailLinkDialog"/> + </implementation> + <implementation name="org.openoffice.comp.form.ui.OControlFontDialog" + constructor="extensions_propctrlr_OControlFontDialog_get_implementation"> + <service name="com.sun.star.form.ControlFontDialog"/> + </implementation> + <implementation name="org.openoffice.comp.form.ui.OTabOrderDialog" + constructor="extensions_propcrltr_OTabOrderDialog_get_implementation"> + <service name="com.sun.star.form.ui.TabOrderDialog"/> + <service name="com.sun.star.form.TabOrderDialog"/> + </implementation> +</component> diff --git a/extensions/source/propctrlr/pcrcommon.cxx b/extensions/source/propctrlr/pcrcommon.cxx new file mode 100644 index 0000000000..08da46ae8a --- /dev/null +++ b/extensions/source/propctrlr/pcrcommon.cxx @@ -0,0 +1,60 @@ +/* -*- 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 "pcrcommon.hxx" + +#include <com/sun/star/util/MeasureUnit.hpp> +#include <rtl/ustrbuf.hxx> +#include <tools/urlobj.hxx> + + +namespace pcr +{ + + + using namespace ::com::sun::star::util; + + + //= HelpIdUrl + + + OUString HelpIdUrl::getHelpId( std::u16string_view _rHelpURL ) + { + INetURLObject aHID( _rHelpURL ); + if ( aHID.GetProtocol() == INetProtocol::Hid ) + return aHID.GetURLPath(); + else + return OUString(_rHelpURL); + } + + + OUString HelpIdUrl::getHelpURL( std::u16string_view sHelpId ) + { + OUStringBuffer aBuffer; + INetURLObject aHID( sHelpId ); + if ( aHID.GetProtocol() == INetProtocol::NotValid ) + aBuffer.append( INET_HID_SCHEME ); + aBuffer.append( sHelpId ); + return aBuffer.makeStringAndClear(); + } + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/pcrcommon.hxx b/extensions/source/propctrlr/pcrcommon.hxx new file mode 100644 index 0000000000..f2a8b847c0 --- /dev/null +++ b/extensions/source/propctrlr/pcrcommon.hxx @@ -0,0 +1,123 @@ +/* -*- 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 . + */ + +#pragma once + +#define EDITOR_LIST_APPEND (SAL_MAX_UINT16) +#define EDITOR_LIST_ENTRY_NOTFOUND (SAL_MAX_UINT16) + +#include <sal/config.h> + +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/XPropertyChangeListener.hpp> +#include <comphelper/interfacecontainer3.hxx> + +namespace pcr +{ + + enum class OwnPropertyId + { + INTROSPECTEDOBJECT = 0x0010, + CURRENTPAGE = 0x0011, + CONTROLCONTEXT = 0x0012, + TABBINGMODEL = 0x0013 + }; + + + //= types + + typedef ::comphelper::OInterfaceContainerHelper3 < css::beans::XPropertyChangeListener + > PropertyChangeListeners; + + + //= helper + + // small helper to make the "swap" call on an STL container a single-line call, which + // in its canonic form "aFoo.swap( Container() )" doesn't compile with GCC + template< class CONTAINER > + void clearContainer( CONTAINER& _rContainer ) + { + CONTAINER().swap(_rContainer); + } + + + //= HelpIdUrl + + /// small helper to translate help ids into help urls + class HelpIdUrl + { + public: + static OUString getHelpId( std::u16string_view _rHelpURL ); + static OUString getHelpURL( std::u16string_view ); + }; + + + //= StlSyntaxSequence + + template< class ELEMENT > + class StlSyntaxSequence : public css::uno::Sequence< ELEMENT > + { + private: + typedef css::uno::Sequence< ELEMENT > UnoBase; + + public: + StlSyntaxSequence() : UnoBase() { } + explicit StlSyntaxSequence( const UnoBase& rSeq ) : UnoBase( rSeq ) { } + explicit StlSyntaxSequence( sal_Int32 len ) : UnoBase( len ) { } + + typedef const ELEMENT* const_iterator; + typedef ELEMENT* iterator; + + const_iterator begin() const { return UnoBase::getConstArray(); } + const_iterator end() const { return UnoBase::getConstArray() + UnoBase::getLength(); } + + iterator begin() { return UnoBase::getArray(); } + iterator end() { return UnoBase::getArray() + UnoBase::getLength(); } + + sal_Int32 size() const { return UnoBase::getLength(); } + bool empty() const { return !UnoBase::hasElements(); } + }; + + + //= UNO helpers + +#define DECLARE_XCOMPONENT() \ + virtual void SAL_CALL dispose( ) override; \ + virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; \ + virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override; + +#define IMPLEMENT_FORWARD_XCOMPONENT( classname, baseclass ) \ + void SAL_CALL classname::dispose( ) \ + { \ + baseclass::WeakComponentImplHelperBase::dispose(); \ + } \ + void SAL_CALL classname::addEventListener( const css::uno::Reference< css::lang::XEventListener >& Listener ) \ + { \ + baseclass::WeakComponentImplHelperBase::addEventListener( Listener ); \ + } \ + void SAL_CALL classname::removeEventListener( const css::uno::Reference< css::lang::XEventListener >& Listener ) \ + { \ + baseclass::WeakComponentImplHelperBase::removeEventListener( Listener ); \ + } \ + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/pcrcommontypes.hxx b/extensions/source/propctrlr/pcrcommontypes.hxx new file mode 100644 index 0000000000..e2aa3570ec --- /dev/null +++ b/extensions/source/propctrlr/pcrcommontypes.hxx @@ -0,0 +1,33 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/beans/Property.hpp> +#include <rtl/ustring.hxx> + +#include <unordered_map> + +namespace pcr +{ +typedef std::unordered_map<OUString, css::beans::Property> PropertyMap; + +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/pcrstrings.hxx b/extensions/source/propctrlr/pcrstrings.hxx new file mode 100644 index 0000000000..3e9ac3ea55 --- /dev/null +++ b/extensions/source/propctrlr/pcrstrings.hxx @@ -0,0 +1,35 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> + +namespace pcr +{ + + + // properties + constexpr OUString PROPERTY_TABBINGMODEL = u"TabbingModel"_ustr; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/pcrunodialogs.cxx b/extensions/source/propctrlr/pcrunodialogs.cxx new file mode 100644 index 0000000000..a6c7703a8d --- /dev/null +++ b/extensions/source/propctrlr/pcrunodialogs.cxx @@ -0,0 +1,143 @@ +/* -*- 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 <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <vcl/svapp.hxx> +#include "pcrunodialogs.hxx" +#include "formstrings.hxx" +#include "pcrstrings.hxx" +#include "taborder.hxx" +#include "pcrcommon.hxx" + + +namespace pcr +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + + + //= OTabOrderDialog + + + OTabOrderDialog::OTabOrderDialog( const Reference< XComponentContext >& _rxContext ) + :OGenericUnoDialog( _rxContext ) + { + registerProperty( PROPERTY_CONTROLCONTEXT, static_cast<sal_Int32>(OwnPropertyId::CONTROLCONTEXT), + PropertyAttribute::BOUND | PropertyAttribute::TRANSIENT, + &m_xControlContext, cppu::UnoType<decltype(m_xControlContext)>::get() ); + + registerProperty( PROPERTY_TABBINGMODEL, static_cast<sal_Int32>(OwnPropertyId::TABBINGMODEL), + PropertyAttribute::BOUND | PropertyAttribute::TRANSIENT, + &m_xTabbingModel, cppu::UnoType<decltype(m_xTabbingModel)>::get() ); + } + + OTabOrderDialog::~OTabOrderDialog() + { + if (m_xDialog) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if (m_xDialog) + destroyDialog(); + } + } + + Sequence<sal_Int8> SAL_CALL OTabOrderDialog::getImplementationId( ) + { + return css::uno::Sequence<sal_Int8>(); + } + + OUString SAL_CALL OTabOrderDialog::getImplementationName() + { + return "org.openoffice.comp.form.ui.OTabOrderDialog"; + } + + + css::uno::Sequence<OUString> SAL_CALL OTabOrderDialog::getSupportedServiceNames() + { + return { "com.sun.star.form.ui.TabOrderDialog", "com.sun.star.form.TabOrderDialog" }; + } + + + Reference<XPropertySetInfo> SAL_CALL OTabOrderDialog::getPropertySetInfo() + { + Reference<XPropertySetInfo> xInfo( createPropertySetInfo( getInfoHelper() ) ); + return xInfo; + } + + ::cppu::IPropertyArrayHelper& OTabOrderDialog::getInfoHelper() + { + return *getArrayHelper(); + } + + ::cppu::IPropertyArrayHelper* OTabOrderDialog::createArrayHelper( ) const + { + Sequence< Property > aProps; + describeProperties( aProps ); + return new ::cppu::OPropertyArrayHelper( aProps ); + } + + std::unique_ptr<weld::DialogController> OTabOrderDialog::createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) + { + return std::make_unique<TabOrderDialog>(Application::GetFrameWeld(rParent), m_xTabbingModel, m_xControlContext, m_aContext); + } + + void OTabOrderDialog::initialize( const Sequence< Any >& aArguments ) + { + Reference<css::awt::XTabControllerModel> xTabbingModel; + Reference<css::awt::XControlContainer> xControlContext; + Reference<css::awt::XWindow> xParentWindow; + if (aArguments.getLength() == 3 && (aArguments[0] >>= xTabbingModel) && (aArguments[1] >>= xControlContext) && (aArguments[2] >>= xParentWindow)) + { + Sequence< Any > aNewArguments{ + Any(NamedValue( + "TabbingModel", + Any( xTabbingModel ) + )), + Any(NamedValue( + "ControlContext", + Any( xControlContext ) + )), + Any(NamedValue( + "ParentWindow", + Any( xParentWindow ) + )) + }; + OTabOrderDialog_DBase::initialize(aNewArguments); + } + else + OTabOrderDialog_DBase::initialize(aArguments); + } + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propcrltr_OTabOrderDialog_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::OTabOrderDialog(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/pcrunodialogs.hxx b/extensions/source/propctrlr/pcrunodialogs.hxx new file mode 100644 index 0000000000..87c99db357 --- /dev/null +++ b/extensions/source/propctrlr/pcrunodialogs.hxx @@ -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 . + */ +#pragma once + +#include <svtools/genericunodialog.hxx> +#include <com/sun/star/awt/XTabControllerModel.hpp> +#include <com/sun/star/awt/XControlContainer.hpp> +#include <comphelper/proparrhlp.hxx> + + +namespace pcr +{ + + + //= OTabOrderDialog + + class OTabOrderDialog; + typedef ::svt::OGenericUnoDialog OTabOrderDialog_DBase; + typedef ::comphelper::OPropertyArrayUsageHelper< OTabOrderDialog > OTabOrderDialog_PBase; + + class OTabOrderDialog + :public OTabOrderDialog_DBase + ,public OTabOrderDialog_PBase + { + protected: + // <properties> + css::uno::Reference< css::awt::XTabControllerModel > + m_xTabbingModel; + css::uno::Reference< css::awt::XControlContainer > + m_xControlContext; + // </properties> + + public: + explicit OTabOrderDialog( const css::uno::Reference< css::uno::XComponentContext >& _rxContext ); + virtual ~OTabOrderDialog() override; + + // XTypeProvider + virtual css::uno::Sequence<sal_Int8> SAL_CALL getImplementationId( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo> SAL_CALL getPropertySetInfo() override; + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; + + // OPropertyArrayUsageHelper + virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const override; + + protected: + // OGenericUnoDialog overridables + virtual std::unique_ptr<weld::DialogController> createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) override; + }; + + +} // namespacepcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/propcontroller.cxx b/extensions/source/propctrlr/propcontroller.cxx new file mode 100644 index 0000000000..836f5844e3 --- /dev/null +++ b/extensions/source/propctrlr/propcontroller.cxx @@ -0,0 +1,1655 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "propcontroller.hxx" +#include "handlerhelper.hxx" +#include "standardcontrol.hxx" +#include "linedescriptor.hxx" +#include <strings.hrc> +#include "propertyeditor.hxx" +#include "modulepcr.hxx" +#include "formstrings.hxx" +#include "formbrowsertools.hxx" +#include "propertycomposer.hxx" + +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/inspection/PropertyControlType.hpp> +#include <com/sun/star/ucb/AlreadyInitializedException.hpp> +#include <com/sun/star/lang/XSingleComponentFactory.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/util/VetoException.hpp> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/weldutils.hxx> +#include <osl/mutex.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <algorithm> +#include <sal/log.hxx> + +namespace pcr +{ + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::awt; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::script; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::frame; + using namespace ::com::sun::star::util; + using namespace ::com::sun::star::inspection; + using namespace ::com::sun::star::ucb; + using namespace ::comphelper; + + //= OPropertyBrowserController + OPropertyBrowserController::OPropertyBrowserController( const Reference< XComponentContext >& _rxContext ) + :m_xContext(_rxContext) + ,m_aDisposeListeners( m_aMutex ) + ,m_aControlObservers( m_aMutex ) + ,m_bContainerFocusListening( false ) + ,m_bSuspendingPropertyHandlers( false ) + ,m_bConstructed( false ) + ,m_bBindingIntrospectee( false ) + { + } + + OPropertyBrowserController::~OPropertyBrowserController() + { + // stop listening for property changes + acquire(); + stopInspection( true ); + } + + IMPLEMENT_FORWARD_REFCOUNT( OPropertyBrowserController, OPropertyBrowserController_Base ) + + Any SAL_CALL OPropertyBrowserController::queryInterface( const Type& _rType ) + { + Any aReturn = OPropertyBrowserController_Base::queryInterface( _rType ); + if ( !aReturn.hasValue() ) + aReturn = ::cppu::queryInterface( + _rType, + static_cast< XObjectInspectorUI* >( this ) + ); + return aReturn; + } + + + void OPropertyBrowserController::startContainerWindowListening() + { + if (m_bContainerFocusListening) + return; + + if (m_xFrame.is()) + { + Reference< XWindow > xContainerWindow = m_xFrame->getContainerWindow(); + if (xContainerWindow.is()) + { + xContainerWindow->addFocusListener(this); + m_bContainerFocusListening = true; + } + } + + DBG_ASSERT(m_bContainerFocusListening, "OPropertyBrowserController::startContainerWindowListening: unable to start listening (inconsistence)!"); + } + + + void OPropertyBrowserController::stopContainerWindowListening() + { + if (!m_bContainerFocusListening) + return; + + if (m_xFrame.is()) + { + Reference< XWindow > xContainerWindow = m_xFrame->getContainerWindow(); + if (xContainerWindow.is()) + { + xContainerWindow->removeFocusListener(this); + m_bContainerFocusListening = false; + } + } + + DBG_ASSERT(!m_bContainerFocusListening, "OPropertyBrowserController::stopContainerWindowListening: unable to stop listening (inconsistence)!"); + } + + + Reference< XObjectInspectorModel > SAL_CALL OPropertyBrowserController::getInspectorModel() + { + return m_xModel; + } + + + void OPropertyBrowserController::impl_initializeView_nothrow() + { + OSL_PRECOND( haveView(), "OPropertyBrowserController::impl_initializeView_nothrow: not to be called when we have no view!" ); + if ( !haveView() ) + return; + + if ( !m_xModel.is() ) + // allowed + return; + + try + { + getPropertyBox().EnableHelpSection( m_xModel->getHasHelpSection() ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + + bool OPropertyBrowserController::impl_isReadOnlyModel_throw() const + { + if ( !m_xModel.is() ) + return false; + + return m_xModel->getIsReadOnly(); + } + + + void OPropertyBrowserController::impl_startOrStopModelListening_nothrow( bool _bDoListen ) const + { + try + { + Reference< XPropertySet > xModelProperties( m_xModel, UNO_QUERY ); + if ( !xModelProperties.is() ) + // okay, so the model doesn't want to change its properties + // dynamically - fine with us + return; + + void (SAL_CALL XPropertySet::*pListenerOperation)( const OUString&, const Reference< XPropertyChangeListener >& ) + = _bDoListen ? &XPropertySet::addPropertyChangeListener : &XPropertySet::removePropertyChangeListener; + + (xModelProperties.get()->*pListenerOperation)( + OUString( "IsReadOnly" ), + const_cast< OPropertyBrowserController* >( this ) + ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + + void OPropertyBrowserController::impl_bindToNewModel_nothrow( const Reference< XObjectInspectorModel >& _rxInspectorModel ) + { + impl_startOrStopModelListening_nothrow( false ); + m_xModel = _rxInspectorModel; + impl_startOrStopModelListening_nothrow( true ); + + // initialize the view, if we already have one + if ( haveView() ) + impl_initializeView_nothrow(); + + // inspect again, if we already have inspectees + if ( !m_aInspectedObjects.empty() ) + impl_rebindToInspectee_nothrow( std::vector(m_aInspectedObjects) ); + } + + + void SAL_CALL OPropertyBrowserController::setInspectorModel( const Reference< XObjectInspectorModel >& _inspectorModel ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( m_xModel == _inspectorModel ) + return; + + impl_bindToNewModel_nothrow( _inspectorModel ); + } + + + Reference< XObjectInspectorUI > SAL_CALL OPropertyBrowserController::getInspectorUI() + { + // we're derived from this interface, though we do not expose it in queryInterface and getTypes. + return this; + } + + + void SAL_CALL OPropertyBrowserController::inspect( const Sequence< Reference< XInterface > >& _rObjects ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( m_bSuspendingPropertyHandlers || !suspendAll_nothrow() ) + { // we already are trying to suspend the component (this is somewhere up the stack) + // OR one of our property handlers raised a veto against closing. Well, we *need* to close + // it in order to inspect another object. + throw VetoException(); + } + if ( m_bBindingIntrospectee ) + throw VetoException(); + + m_bBindingIntrospectee = true; + impl_rebindToInspectee_nothrow( InterfaceArray( _rObjects.begin(), _rObjects.end() ) ); + m_bBindingIntrospectee = false; + + } + + + Reference< XDispatch > SAL_CALL OPropertyBrowserController::queryDispatch( const URL& /*URL*/, const OUString& /*TargetFrameName*/, ::sal_Int32 /*SearchFlags*/ ) + { + // we don't have any dispatches at all, right now + return Reference< XDispatch >(); + } + + + Sequence< Reference< XDispatch > > SAL_CALL OPropertyBrowserController::queryDispatches( const Sequence< DispatchDescriptor >& Requests ) + { + Sequence< Reference< XDispatch > > aReturn; + sal_Int32 nLen = Requests.getLength(); + aReturn.realloc( nLen ); + + Reference< XDispatch >* pReturn = aReturn.getArray(); + const Reference< XDispatch >* pReturnEnd = aReturn.getArray() + nLen; + const DispatchDescriptor* pDescripts = Requests.getConstArray(); + + for ( ; pReturn != pReturnEnd; ++ pReturn, ++pDescripts ) + *pReturn = queryDispatch( pDescripts->FeatureURL, pDescripts->FrameName, pDescripts->SearchFlags ); + + return aReturn; + } + + + void SAL_CALL OPropertyBrowserController::initialize( const Sequence< Any >& _arguments ) + { + if ( m_bConstructed ) + throw AlreadyInitializedException(); + + StlSyntaxSequence< Any > arguments( _arguments ); + if ( arguments.empty() ) + { // constructor: "createDefault()" + m_bConstructed = true; + return; + } + + Reference< XObjectInspectorModel > xModel; + if ( arguments.size() == 1 ) + { // constructor: "createWithModel( XObjectInspectorModel )" + if ( !( arguments[0] >>= xModel ) ) + throw IllegalArgumentException( OUString(), *this, 0 ); + createWithModel( xModel ); + return; + } + + throw IllegalArgumentException( OUString(), *this, 0 ); + } + + + void OPropertyBrowserController::createWithModel( const Reference< XObjectInspectorModel >& _rxModel ) + { + osl_atomic_increment( &m_refCount ); + { + setInspectorModel( _rxModel ); + } + osl_atomic_decrement( &m_refCount ); + + m_bConstructed = true; + } + + + void SAL_CALL OPropertyBrowserController::attachFrame( const Reference< XFrame >& _rxFrame ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + if (_rxFrame.is() && haveView()) + throw RuntimeException("Unable to attach to a second frame.",*this); + + // revoke as focus listener from the old container window + stopContainerWindowListening(); + + m_xPropView.reset(); + m_xBuilder.reset(); + + m_xFrame = _rxFrame; + if (!m_xFrame.is()) + return; + + // TODO: this construction perhaps should be done outside. Don't know the exact meaning of attachFrame. + // Maybe it is intended to only announce the frame to the controller, and the instance doing this + // announcement is responsible for calling setComponent, too. + Reference<XWindow> xContainerWindow = m_xFrame->getContainerWindow(); + + OUString sUIFile("modules/spropctrlr/ui/formproperties.ui"); + std::unique_ptr<weld::Builder> xBuilder; + + if (weld::TransportAsXWindow* pTunnel = dynamic_cast<weld::TransportAsXWindow*>(xContainerWindow.get())) + { + xBuilder = Application::CreateBuilder(pTunnel->getWidget(), sUIFile); + } + else + { + VclPtr<vcl::Window> pParentWin = VCLUnoHelper::GetWindow(xContainerWindow); + if (!pParentWin) + throw RuntimeException("The frame is invalid. Unable to extract the container window.",*this); + xBuilder = Application::CreateInterimBuilder(pParentWin, sUIFile, true); + } + + Construct(xContainerWindow, std::move(xBuilder)); + + startContainerWindowListening(); + + UpdateUI(); + } + + sal_Bool SAL_CALL OPropertyBrowserController::attachModel( const Reference< XModel >& _rxModel ) + { + Reference< XObjectInspectorModel > xModel( _rxModel, UNO_QUERY ); + if ( !xModel.is() ) + return false; + + setInspectorModel( xModel ); + return getInspectorModel() == _rxModel; + } + + + bool OPropertyBrowserController::suspendAll_nothrow() + { + // if there is a handle inside its "onInteractivePropertySelection" method, + // then veto + // Normally, we could expect every handler to do this itself, but being + // realistic, it's safer to handle this here in general. + if ( m_xInteractiveHandler.is() ) + return false; + + m_bSuspendingPropertyHandlers = true; + bool bHandlerVeto = !suspendPropertyHandlers_nothrow( true ); + m_bSuspendingPropertyHandlers = false; + return !bHandlerVeto; + } + + + bool OPropertyBrowserController::suspendPropertyHandlers_nothrow( bool _bSuspend ) + { + PropertyHandlerArray aAllHandlers; // will contain every handler exactly once + for (auto const& propertyHandler : m_aPropertyHandlers) + { + if ( std::find( aAllHandlers.begin(), aAllHandlers.end(), propertyHandler.second ) != aAllHandlers.end() ) + // already visited this particular handler (m_aPropertyHandlers usually contains + // the same handler more than once) + continue; + aAllHandlers.push_back(propertyHandler.second); + } + + for (auto const& handler : aAllHandlers) + { + try + { + if ( !handler->suspend( _bSuspend ) ) + if ( _bSuspend ) + // if we're not suspending, but reactivating, ignore the error + return false; + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "OPropertyBrowserController::suspendPropertyHandlers_nothrow" ); + } + } + return true; + } + + + sal_Bool SAL_CALL OPropertyBrowserController::suspend( sal_Bool _bSuspend ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + OSL_ENSURE( haveView(), "OPropertyBrowserController::suspend: don't have a view anymore!" ); + + if ( !_bSuspend ) + { // this means a "suspend" is to be "revoked" + suspendPropertyHandlers_nothrow( false ); + // we ourself cannot revoke our suspend + return false; + } + + if ( !suspendAll_nothrow() ) + return false; + + // commit the editor's content + if ( haveView() ) + getPropertyBox().CommitModified(); + + // stop listening + stopContainerWindowListening(); + + // outta here + return true; + } + + + Any SAL_CALL OPropertyBrowserController::getViewData( ) + { + return Any( m_sPageSelection ); + } + + + void SAL_CALL OPropertyBrowserController::restoreViewData( const Any& Data ) + { + OUString sPageSelection; + if ( ( Data >>= sPageSelection ) && !sPageSelection.isEmpty() ) + { + m_sPageSelection = sPageSelection; + selectPageFromViewData(); + } + } + + Reference< XModel > SAL_CALL OPropertyBrowserController::getModel( ) + { + // have no model + return Reference< XModel >(); + } + + Reference< XFrame > SAL_CALL OPropertyBrowserController::getFrame( ) + { + return m_xFrame; + } + + void SAL_CALL OPropertyBrowserController::dispose() + { + SolarMutexGuard aSolarGuard; + + // stop inspecting the current object + stopInspection( false ); + + // say our dispose listeners goodbye + css::lang::EventObject aEvt; + aEvt.Source = static_cast< ::cppu::OWeakObject* >(this); + m_aDisposeListeners.disposeAndClear(aEvt); + m_aControlObservers.disposeAndClear(aEvt); + + m_xPropView.reset(); + m_xBuilder.reset(); + + if ( m_xView.is() ) + m_xView->removeEventListener( static_cast< XPropertyChangeListener* >( this ) ); + m_xView.clear( ); + + m_aInspectedObjects.clear(); + impl_bindToNewModel_nothrow( nullptr ); + m_xModel.clear(); + m_xInteractiveHandler.clear(); + m_xFrame.clear(); + } + + void SAL_CALL OPropertyBrowserController::addEventListener( const Reference< XEventListener >& _rxListener ) + { + m_aDisposeListeners.addInterface(_rxListener); + } + + void SAL_CALL OPropertyBrowserController::removeEventListener( const Reference< XEventListener >& _rxListener ) + { + m_aDisposeListeners.removeInterface(_rxListener); + } + + OUString SAL_CALL OPropertyBrowserController::getImplementationName( ) + { + return "org.openoffice.comp.extensions.ObjectInspector"; + } + + sal_Bool SAL_CALL OPropertyBrowserController::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService(this, ServiceName); + } + + + Sequence< OUString > SAL_CALL OPropertyBrowserController::getSupportedServiceNames( ) + { + return { "com.sun.star.inspection.ObjectInspector" }; + } + + + void SAL_CALL OPropertyBrowserController::focusGained( const FocusEvent& _rSource ) + { + Reference< XWindow > xSourceWindow(_rSource.Source, UNO_QUERY); + Reference< XWindow > xContainerWindow; + if (m_xFrame.is()) + xContainerWindow = m_xFrame->getContainerWindow(); + + if ( xContainerWindow.get() == xSourceWindow.get() ) + { // our container window got the focus + if ( haveView() ) + getPropertyBox().GrabFocus(); + } + } + + + void SAL_CALL OPropertyBrowserController::focusLost( const FocusEvent& /*_rSource*/ ) + { + // not interested in + } + + + void SAL_CALL OPropertyBrowserController::disposing( const EventObject& _rSource ) + { + if ( m_xView.is() && ( m_xView == _rSource.Source ) ) + { + m_xView = nullptr; + m_xPropView.reset(); + m_xBuilder.reset(); + } + + auto it = std::find_if(m_aInspectedObjects.begin(), m_aInspectedObjects.end(), + [&_rSource](const InterfaceArray::value_type& rxObj) { return rxObj == _rSource.Source; }); + if (it != m_aInspectedObjects.end()) + m_aInspectedObjects.erase(it); + } + + + IMPL_LINK_NOARG(OPropertyBrowserController, OnPageActivation, LinkParamNone*, void) + { + updateViewDataFromActivePage(); + } + + + void OPropertyBrowserController::updateViewDataFromActivePage() + { + if (!haveView()) + return; + + OUString sOldSelection = m_sPageSelection; + m_sPageSelection.clear(); + + const sal_uInt16 nCurrentPage = m_xPropView->getActivePage(); + if ( sal_uInt16(-1) != nCurrentPage ) + { + for (auto const& pageId : m_aPageIds) + { + if ( nCurrentPage == pageId.second ) + { + m_sPageSelection = pageId.first; + break; + } + } + } + + if ( !m_sPageSelection.isEmpty() ) + m_sLastValidPageSelection = m_sPageSelection; + else if ( !sOldSelection.isEmpty() ) + m_sLastValidPageSelection = sOldSelection; + } + + + sal_uInt16 OPropertyBrowserController::impl_getPageIdForCategory_nothrow( const OUString& _rCategoryName ) const + { + sal_uInt16 nPageId = sal_uInt16(-1); + HashString2Int16::const_iterator pagePos = m_aPageIds.find( _rCategoryName ); + if ( pagePos != m_aPageIds.end() ) + nPageId = pagePos->second; + return nPageId; + } + + void OPropertyBrowserController::selectPageFromViewData() + { + sal_uInt16 nNewPage = impl_getPageIdForCategory_nothrow( m_sPageSelection ); + + if ( haveView() && ( nNewPage != sal_uInt16(-1) ) ) + m_xPropView->activatePage( nNewPage ); + + // just in case ... + updateViewDataFromActivePage(); + } + + void OPropertyBrowserController::Construct(const Reference<XWindow>& rContainerWindow, std::unique_ptr<weld::Builder> xBuilder) + { + DBG_ASSERT(!haveView(), "OPropertyBrowserController::Construct: already have a view!"); + assert(xBuilder && "OPropertyBrowserController::Construct: invalid parent window!"); + + m_xBuilder = std::move(xBuilder); + + m_xPropView.reset(new OPropertyBrowserView(m_xContext, *m_xBuilder)); + m_xPropView->setPageActivationHandler(LINK(this, OPropertyBrowserController, OnPageActivation)); + + // add as dispose listener for our view. The view is disposed by the frame we're plugged into, + // and this disposal _deletes_ the view, so it would be deadly if we use our m_xPropView member + // after that + m_xView = rContainerWindow; + if (m_xView.is()) + m_xView->addEventListener( static_cast< XPropertyChangeListener* >( this ) ); + + getPropertyBox().SetLineListener(this); + getPropertyBox().SetControlObserver(this); + impl_initializeView_nothrow(); + } + + void SAL_CALL OPropertyBrowserController::propertyChange( const PropertyChangeEvent& _rEvent ) + { + if ( _rEvent.Source == m_xModel ) + { + if ( _rEvent.PropertyName == "IsReadOnly" ) + // this is a huge cudgel, admitted. + // The problem is that in case we were previously read-only, all our controls + // were created read-only, too. We cannot simply switch them to not-read-only. + // Even if they had an API for this, we do not know whether they were + // originally created read-only, or if they are read-only just because + // the model was. + impl_rebindToInspectee_nothrow( std::vector(m_aInspectedObjects) ); + return; + } + + if ( m_sCommittingProperty == _rEvent.PropertyName ) + return; + + if ( !haveView() ) + return; + + Any aNewValue( _rEvent.NewValue ); + if ( impl_hasPropertyHandlerFor_nothrow( _rEvent.PropertyName ) ) + { + // forward the new value to the property box, to reflect the change in the UI + aNewValue = impl_getPropertyValue_throw( _rEvent.PropertyName ); + + // check whether the state is ambiguous. This is interesting in case we display the properties + // for multiple objects at once: In this case, we'll get a notification from one of the objects, + // but need to care for the "composed" value, which can be "ambiguous". + PropertyHandlerRef xHandler( impl_getHandlerForProperty_throw( _rEvent.PropertyName ), UNO_SET_THROW ); + PropertyState ePropertyState( xHandler->getPropertyState( _rEvent.PropertyName ) ); + bool bAmbiguousValue = ( PropertyState_AMBIGUOUS_VALUE == ePropertyState ); + + getPropertyBox().SetPropertyValue( _rEvent.PropertyName, aNewValue, bAmbiguousValue ); + } + + // if it's an actuating property, then update the UI for any dependent + // properties + if ( impl_isActuatingProperty_nothrow( _rEvent.PropertyName ) ) + impl_broadcastPropertyChange_nothrow( _rEvent.PropertyName, aNewValue, _rEvent.OldValue, false ); + } + + Reference< XPropertyControl > SAL_CALL OPropertyBrowserController::createPropertyControl( ::sal_Int16 ControlType, sal_Bool bCreateReadOnly ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + Reference< XPropertyControl > xControl; + + // read-only-ness + bCreateReadOnly |= impl_isReadOnlyModel_throw() ? 1 : 0; + + switch ( ControlType ) + { + case PropertyControlType::MultiLineTextField: + case PropertyControlType::StringListField: + { + bool bMultiLineTextField = ControlType == PropertyControlType::MultiLineTextField; + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/multiline.ui", m_xContext)); + auto pContainer = xBuilder->weld_container("multiline"); + rtl::Reference<OMultilineEditControl> pControl = new OMultilineEditControl(std::move(pContainer), std::move(xBuilder), + bMultiLineTextField ? eMultiLineText : eStringList, bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::ListBox: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/listbox.ui", m_xContext)); + auto pComboBox = xBuilder->weld_combo_box("listbox"); + rtl::Reference<OListboxControl> pControl = new OListboxControl(std::move(pComboBox), std::move(xBuilder), bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::ComboBox: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/combobox.ui", m_xContext)); + auto pComboBox = xBuilder->weld_combo_box("combobox"); + rtl::Reference<OComboboxControl> pControl = new OComboboxControl(std::move(pComboBox), std::move(xBuilder), bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::TextField: + case PropertyControlType::CharacterField: + { + bool bCharacterField = ControlType == PropertyControlType::CharacterField; + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/textfield.ui", m_xContext)); + auto pEntry = xBuilder->weld_entry("textfield"); + rtl::Reference<OEditControl> pControl = new OEditControl(std::move(pEntry), std::move(xBuilder), bCharacterField, bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::NumericField: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/numericfield.ui", m_xContext)); + auto pSpinButton = xBuilder->weld_metric_spin_button("numericfield", FieldUnit::NONE); + rtl::Reference<ONumericControl> pControl = new ONumericControl(std::move(pSpinButton), std::move(xBuilder), bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::DateTimeField: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/datetimefield.ui", m_xContext)); + auto pContainer = xBuilder->weld_container("datetimefield"); + rtl::Reference<ODateTimeControl> pControl = new ODateTimeControl(std::move(pContainer), std::move(xBuilder), bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::DateField: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/datefield.ui", m_xContext)); + auto pContainer = xBuilder->weld_container("datefield"); + rtl::Reference<ODateControl> pControl = new ODateControl(std::move(pContainer), std::move(xBuilder), bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::TimeField: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/timefield.ui", m_xContext)); + auto pTimeSpinButton = xBuilder->weld_formatted_spin_button("timefield"); + rtl::Reference<OTimeControl> pControl = new OTimeControl(std::move(pTimeSpinButton), std::move(xBuilder), bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::ColorListBox: + { + auto lambda = [this]{ return PropertyHandlerHelper::getDialogParentFrame(m_xContext); }; + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/colorlistbox.ui", m_xContext)); + auto pMenuButton = xBuilder->weld_menu_button("colorlistbox"); + rtl::Reference<OColorControl> pControl = new OColorControl(std::make_unique<ColorListBox>(std::move(pMenuButton), lambda), std::move(xBuilder), bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + case PropertyControlType::HyperlinkField: + { + std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder("modules/spropctrlr/ui/hyperlinkfield.ui", m_xContext)); + auto pContainer = xBuilder->weld_container("hyperlinkfield"); + rtl::Reference<OHyperlinkControl> pControl = new OHyperlinkControl(std::move(pContainer), std::move(xBuilder), bCreateReadOnly); + pControl->SetModifyHandler(); + xControl = pControl; + break; + } + + default: + throw IllegalArgumentException( OUString(), *this, 1 ); + } + + return xControl; + } + + + void OPropertyBrowserController::impl_toggleInspecteeListening_nothrow( bool _bOn ) + { + for (auto const& inspectedObject : m_aInspectedObjects) + { + try + { + Reference< XComponent > xComp( inspectedObject, UNO_QUERY ); + if ( xComp.is() ) + { + if ( _bOn ) + xComp->addEventListener( static_cast< XPropertyChangeListener* >( this ) ); + else + xComp->removeEventListener( static_cast< XPropertyChangeListener* >( this ) ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + } + + + void OPropertyBrowserController::stopInspection( bool _bCommitModified ) + { + if ( haveView() ) + { + if ( _bCommitModified ) + // commit the editor's content + getPropertyBox().CommitModified(); + + // hide the property box so that it does not flicker + getPropertyBox().Hide(); + + // clear the property box + getPropertyBox().ClearAll(); + } + + // destroy the view first + if ( haveView() ) + { + // remove the pages + for (auto const& pageId : m_aPageIds) + getPropertyBox().RemovePage( pageId.second ); + clearContainer( m_aPageIds ); + } + + clearContainer( m_aProperties ); + + // de-register as dispose-listener from our inspected objects + impl_toggleInspecteeListening_nothrow( false ); + + // handlers are obsolete, so is our "composer" for their UI requests + if (m_pUIRequestComposer) + m_pUIRequestComposer->dispose(); + m_pUIRequestComposer.reset(); + + // clean up the property handlers + PropertyHandlerArray aAllHandlers; // will contain every handler exactly once + for (auto const& propertyHandler : m_aPropertyHandlers) + if ( std::find( aAllHandlers.begin(), aAllHandlers.end(), propertyHandler.second ) == aAllHandlers.end() ) + aAllHandlers.push_back( propertyHandler.second ); + + for (auto const& handler : aAllHandlers) + { + try + { + handler->removePropertyChangeListener( this ); + handler->dispose(); + } + catch( const DisposedException& ) + { + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + clearContainer( m_aPropertyHandlers ); + clearContainer( m_aDependencyHandlers ); + } + + + bool OPropertyBrowserController::impl_hasPropertyHandlerFor_nothrow( const OUString& _rPropertyName ) const + { + PropertyHandlerRepository::const_iterator handlerPos = m_aPropertyHandlers.find( _rPropertyName ); + return ( handlerPos != m_aPropertyHandlers.end() ); + } + + + OPropertyBrowserController::PropertyHandlerRef const & OPropertyBrowserController::impl_getHandlerForProperty_throw( const OUString& _rPropertyName ) const + { + PropertyHandlerRepository::const_iterator handlerPos = m_aPropertyHandlers.find( _rPropertyName ); + if ( handlerPos == m_aPropertyHandlers.end() ) + throw RuntimeException(); + return handlerPos->second; + } + + + Any OPropertyBrowserController::impl_getPropertyValue_throw( const OUString& _rPropertyName ) + { + PropertyHandlerRef handler = impl_getHandlerForProperty_throw( _rPropertyName ); + return handler->getPropertyValue( _rPropertyName ); + } + + + void OPropertyBrowserController::impl_rebindToInspectee_nothrow( InterfaceArray&& _rObjects ) + { + try + { + // stop inspecting the old object(s) + stopInspection( true ); + + // inspect the new object(s) + m_aInspectedObjects = std::move(_rObjects); + doInspection(); + + // update the user interface + UpdateUI(); + } + + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.propctrlr", ""); + } + } + + + void OPropertyBrowserController::doInspection() + { + try + { + + // obtain the properties of the object + std::vector< Property > aProperties; + + PropertyHandlerArray aPropertyHandlers; + getPropertyHandlers( m_aInspectedObjects, aPropertyHandlers ); + + PropertyHandlerArray::iterator aHandler( aPropertyHandlers.begin() ); + while ( aHandler != aPropertyHandlers.end() ) + { + DBG_ASSERT( aHandler->get(), "OPropertyBrowserController::doInspection: invalid handler!" ); + + StlSyntaxSequence< Property > aThisHandlersProperties( (*aHandler)->getSupportedProperties() ); + + if ( aThisHandlersProperties.empty() ) + { + // this handler doesn't know anything about the current inspectee -> ignore it + (*aHandler)->dispose(); + aHandler = aPropertyHandlers.erase( aHandler ); + continue; + } + + // append these properties to our "all properties" array + aProperties.reserve( std::max<size_t>(aProperties.size() + aThisHandlersProperties.size(), aProperties.size() * 2) ); + for (const auto & aThisHandlersProperty : aThisHandlersProperties) + { + auto noPrevious = std::none_of( + aProperties.begin(), + aProperties.end(), + FindPropertyByName( aThisHandlersProperty.Name ) + ); + if ( noPrevious ) + { + aProperties.push_back( aThisHandlersProperty ); + continue; + } + + // there already was another (previous) handler which supported this property. + // Don't add it to aProperties, again. + + // Also, ensure that handlers which previously expressed interest in *changes* + // of this property are not notified. + // This is 'cause we have a new handler which is responsible for this property, + // which means it can give it a completely different meaning than the previous + // handler for this property is prepared for. + std::pair< PropertyHandlerMultiRepository::iterator, PropertyHandlerMultiRepository::iterator > + aDepHandlers = m_aDependencyHandlers.equal_range( aThisHandlersProperty.Name ); + m_aDependencyHandlers.erase( aDepHandlers.first, aDepHandlers.second ); + } + + // determine the superseded properties + StlSyntaxSequence< OUString > aSupersededByThisHandler( (*aHandler)->getSupersededProperties() ); + for (const auto & superseded : aSupersededByThisHandler) + { + std::vector< Property >::iterator existent = std::find_if( + aProperties.begin(), + aProperties.end(), + FindPropertyByName( superseded ) + ); + if ( existent != aProperties.end() ) + // one of the properties superseded by this handler was supported by a previous + // one -> erase + aProperties.erase( existent ); + } + + // be notified of changes which this handler is responsible for + (*aHandler)->addPropertyChangeListener( this ); + + // remember this handler for every of the properties which it is responsible + // for + for (const auto & aThisHandlersProperty : aThisHandlersProperties) + { + m_aPropertyHandlers[ aThisHandlersProperty.Name ] = *aHandler; + // note that this implies that if two handlers support the same property, + // the latter wins + } + + // see if the handler expresses interest in any actuating properties + StlSyntaxSequence< OUString > aInterestingActuations( (*aHandler)->getActuatingProperties() ); + for (const auto & aInterestingActuation : aInterestingActuations) + { + m_aDependencyHandlers.emplace( aInterestingActuation, *aHandler ); + } + + ++aHandler; + } + + // create a new composer for UI requests coming from the handlers + m_pUIRequestComposer.reset( new ComposedPropertyUIUpdate( getInspectorUI(), this ) ); + + // sort the properties by relative position, as indicated by the model + sal_Int32 nPos = 0; + for (auto const& sourceProps : aProperties) + { + sal_Int32 nRelativePropertyOrder = nPos; + if ( m_xModel.is() ) + nRelativePropertyOrder = m_xModel->getPropertyOrderIndex( sourceProps.Name ); + m_aProperties.emplace(nRelativePropertyOrder, sourceProps); + ++nPos; + } + + // be notified when one of our inspectees dies + impl_toggleInspecteeListening_nothrow( true ); + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.propctrlr", ""); + } + } + + + css::awt::Size SAL_CALL OPropertyBrowserController::getMinimumSize() + { + css::awt::Size aSize; + if( m_xPropView ) + return m_xPropView->getMinimumSize(); + else + return aSize; + } + + + css::awt::Size SAL_CALL OPropertyBrowserController::getPreferredSize() + { + return getMinimumSize(); + } + + + css::awt::Size SAL_CALL OPropertyBrowserController::calcAdjustedSize( const css::awt::Size& _rNewSize ) + { + awt::Size aMinSize = getMinimumSize( ); + awt::Size aAdjustedSize( _rNewSize ); + if ( aAdjustedSize.Width < aMinSize.Width ) + aAdjustedSize.Width = aMinSize.Width; + if ( aAdjustedSize.Height < aMinSize.Height ) + aAdjustedSize.Height = aMinSize.Height; + return aAdjustedSize; + } + + + void OPropertyBrowserController::describePropertyLine( const Property& _rProperty, OLineDescriptor& _rDescriptor ) + { + try + { + PropertyHandlerRepository::const_iterator handler = m_aPropertyHandlers.find( _rProperty.Name ); + if ( handler == m_aPropertyHandlers.end() ) + throw RuntimeException(); // caught below + + _rDescriptor.assignFrom( handler->second->describePropertyLine( _rProperty.Name, this ) ); + + + _rDescriptor.xPropertyHandler = handler->second; + _rDescriptor.sName = _rProperty.Name; + _rDescriptor.aValue = _rDescriptor.xPropertyHandler->getPropertyValue( _rProperty.Name ); + + if ( _rDescriptor.DisplayName.isEmpty() ) + { + #ifdef DBG_UTIL + SAL_WARN( "extensions.propctrlr", "OPropertyBrowserController::describePropertyLine: handler did not provide a display name for '" + <<_rProperty.Name << "'!" ); + #endif + _rDescriptor.DisplayName = _rProperty.Name; + } + + PropertyState ePropertyState( _rDescriptor.xPropertyHandler->getPropertyState( _rProperty.Name ) ); + if ( PropertyState_AMBIGUOUS_VALUE == ePropertyState ) + { + _rDescriptor.bUnknownValue = true; + _rDescriptor.aValue.clear(); + } + + _rDescriptor.bReadOnly = impl_isReadOnlyModel_throw(); + + // for ui-testing try and distinguish different instances of the controls + auto xWindow = _rDescriptor.Control->getControlWindow(); + if (weld::TransportAsXWindow* pTunnel = dynamic_cast<weld::TransportAsXWindow*>(xWindow.get())) + { + weld::Widget* m_pControlWindow = pTunnel->getWidget(); + if (m_pControlWindow) + m_pControlWindow->set_buildable_name(m_pControlWindow->get_buildable_name() + "-" + _rDescriptor.DisplayName); + } + + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "OPropertyBrowserController::describePropertyLine" ); + } + } + + + void OPropertyBrowserController::impl_buildCategories_throw() + { + OSL_PRECOND( m_aPageIds.empty(), "OPropertyBrowserController::impl_buildCategories_throw: duplicate call!" ); + + StlSyntaxSequence< PropertyCategoryDescriptor > aCategories; + if ( m_xModel.is() ) + aCategories = StlSyntaxSequence< PropertyCategoryDescriptor >(m_xModel->describeCategories()); + + for (auto const& category : aCategories) + { + OSL_ENSURE( m_aPageIds.find( category.ProgrammaticName ) == m_aPageIds.end(), + "OPropertyBrowserController::impl_buildCategories_throw: duplicate programmatic name!" ); + + m_aPageIds[ category.ProgrammaticName ] = + getPropertyBox().AppendPage( category.UIName, HelpIdUrl::getHelpId( category.HelpURL ) ); + } + } + + + void OPropertyBrowserController::UpdateUI() + { + try + { + if ( !haveView() ) + // too early, will return later + return; + + // create our tab pages + impl_buildCategories_throw(); + // (and allow for pages to be actually unused) + std::set< sal_uInt16 > aUsedPages; + + // when building the UI below, remember which properties are actuating, + // to allow for an initial actuatingPropertyChanged call + std::vector< OUString > aActuatingProperties; + std::vector< Any > aActuatingPropertyValues; + + // ask the handlers to describe the property UI, and insert the resulting + // entries into our list boxes + for (auto const& property : m_aProperties) + { + OLineDescriptor aDescriptor; + describePropertyLine( property.second, aDescriptor ); + + bool bIsActuatingProperty = impl_isActuatingProperty_nothrow( property.second.Name ); + + SAL_WARN_IF( aDescriptor.Category.isEmpty(), "extensions.propctrlr", + "OPropertyBrowserController::UpdateUI: empty category provided for property '" + << property.second.Name << "'!"); + // finally insert this property control + sal_uInt16 nTargetPageId = impl_getPageIdForCategory_nothrow( aDescriptor.Category ); + if ( nTargetPageId == sal_uInt16(-1) ) + { + // this category does not yet exist. This is allowed, as an inspector model might be lazy, and not provide + // any category information of its own. In this case, we have a fallback ... + m_aPageIds[ aDescriptor.Category ] = + getPropertyBox().AppendPage(aDescriptor.Category, {}); + nTargetPageId = impl_getPageIdForCategory_nothrow( aDescriptor.Category ); + } + + getPropertyBox().InsertEntry( aDescriptor, nTargetPageId ); + aUsedPages.insert( nTargetPageId ); + + // if it's an actuating property, remember it + if ( bIsActuatingProperty ) + { + aActuatingProperties.push_back( property.second.Name ); + aActuatingPropertyValues.push_back( impl_getPropertyValue_throw( property.second.Name ) ); + } + } + + // update any dependencies for the actuating properties which we encountered + { + std::vector< Any >::const_iterator aPropertyValue = aActuatingPropertyValues.begin(); + for (auto const& actuatingProperty : aActuatingProperties) + { + impl_broadcastPropertyChange_nothrow( actuatingProperty, *aPropertyValue, *aPropertyValue, true ); + ++aPropertyValue; + } + } + + // remove any unused pages (which we did not encounter properties for) + HashString2Int16 aSurvivingPageIds; + for (auto const& pageId : m_aPageIds) + { + if ( aUsedPages.find( pageId.second ) == aUsedPages.end() ) + getPropertyBox().RemovePage( pageId.second ); + else + aSurvivingPageIds.insert(pageId); + } + m_aPageIds.swap( aSurvivingPageIds ); + + getPropertyBox().Show(); + + // activate the first page + if ( !m_aPageIds.empty() ) + { + Sequence< PropertyCategoryDescriptor > aCategories( m_xModel->describeCategories() ); + if ( aCategories.hasElements() ) + m_xPropView->activatePage( m_aPageIds[ aCategories[0].ProgrammaticName ] ); + else + // allowed: if we default-created the pages ... + m_xPropView->activatePage( m_aPageIds.begin()->second ); + } + + // activate the previously active page (if possible) + if ( !m_sLastValidPageSelection.isEmpty() ) + m_sPageSelection = m_sLastValidPageSelection; + selectPageFromViewData(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + + void OPropertyBrowserController::Clicked( const OUString& _rName, bool _bPrimary ) + { + try + { + // since the browse buttons do not get the focus when clicked with the mouse, + // we need to commit the changes in the current property field + getPropertyBox().CommitModified(); + + PropertyHandlerRepository::const_iterator handler = m_aPropertyHandlers.find( _rName ); + DBG_ASSERT( handler != m_aPropertyHandlers.end(), "OPropertyBrowserController::Clicked: a property without handler? This will crash!" ); + + ComposedUIAutoFireGuard aAutoFireGuard( *m_pUIRequestComposer ); + + Any aData; + m_xInteractiveHandler = handler->second; + InteractiveSelectionResult eResult = + handler->second->onInteractivePropertySelection( _rName, _bPrimary, aData, + m_pUIRequestComposer->getUIForPropertyHandler( handler->second ) ); + + switch ( eResult ) + { + case InteractiveSelectionResult_Cancelled: + case InteractiveSelectionResult_Success: + // okay, nothing to do + break; + case InteractiveSelectionResult_ObtainedValue: + handler->second->setPropertyValue( _rName, aData ); + break; + case InteractiveSelectionResult_Pending: + // also okay, we expect that the handler has disabled the UI as necessary + break; + default: + OSL_FAIL( "OPropertyBrowserController::Clicked: unknown result value!" ); + break; + } + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + m_xInteractiveHandler = nullptr; + } + + + bool OPropertyBrowserController::hasPropertyByName( const OUString& _rName ) + { + for (auto const& property : m_aProperties) + if ( property.second.Name == _rName ) + return true; + return false; + } + + + void OPropertyBrowserController::Commit( const OUString& rName, const Any& _rValue ) + { + try + { + OUString sPlcHolder = PcrRes(RID_EMBED_IMAGE_PLACEHOLDER); + bool bIsPlaceHolderValue = false; + + if ( rName == PROPERTY_IMAGE_URL ) + { + // if the prop value is the PlaceHolder + // can ignore it + OUString sVal; + _rValue >>= sVal; + if ( sVal == sPlcHolder ) + bIsPlaceHolderValue = true; + } + m_sCommittingProperty = rName; + + bool bIsActuatingProperty = impl_isActuatingProperty_nothrow( rName ); + + Any aOldValue; + if ( bIsActuatingProperty ) + aOldValue = impl_getPropertyValue_throw( rName ); + + // do we have a dedicated handler for this property, which we can delegate some tasks to? + PropertyHandlerRef handler = impl_getHandlerForProperty_throw( rName ); + + + // set the value ( only if it's not a placeholder ) + if ( !bIsPlaceHolderValue ) + handler->setPropertyValue( rName, _rValue ); + + + // re-retrieve the value + Any aNormalizedValue = handler->getPropertyValue( rName ); + + // care for any inter-property dependencies + if ( bIsActuatingProperty ) + impl_broadcastPropertyChange_nothrow( rName, aNormalizedValue, aOldValue, false ); + + // and display it again. This ensures proper formatting + getPropertyBox().SetPropertyValue( rName, aNormalizedValue, false ); + } + catch(const PropertyVetoException& eVetoException) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(m_xPropView->getPropertyBox().getWidget(), + VclMessageType::Info, VclButtonsType::Ok, + eVetoException.Message)); + xInfoBox->run(); + PropertyHandlerRef handler = impl_getHandlerForProperty_throw( rName ); + Any aNormalizedValue = handler->getPropertyValue( rName ); + getPropertyBox().SetPropertyValue( rName, aNormalizedValue, false ); + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION("extensions.propctrlr", ""); + } + + m_sCommittingProperty.clear(); + } + + + void OPropertyBrowserController::focusGained( const Reference< XPropertyControl >& Control ) + { + m_aControlObservers.notifyEach( &XPropertyControlObserver::focusGained, Control ); + } + + + void OPropertyBrowserController::valueChanged( const Reference< XPropertyControl >& Control ) + { + m_aControlObservers.notifyEach( &XPropertyControlObserver::valueChanged, Control ); + } + + + namespace + { + Reference< XPropertyHandler > lcl_createHandler( const Reference<XComponentContext>& _rContext, const Any& _rFactoryDescriptor ) + { + Reference< XPropertyHandler > xHandler; + + OUString sServiceName; + Reference< XSingleServiceFactory > xServiceFac; + Reference< XSingleComponentFactory > xComponentFac; + + if ( _rFactoryDescriptor >>= sServiceName ) + xHandler.set( _rContext->getServiceManager()->createInstanceWithContext( sServiceName, _rContext ), UNO_QUERY ); + else if ( _rFactoryDescriptor >>= xServiceFac ) + xHandler.set(xServiceFac->createInstance(), css::uno::UNO_QUERY); + else if ( _rFactoryDescriptor >>= xComponentFac ) + xHandler.set(xComponentFac->createInstanceWithContext( _rContext ), css::uno::UNO_QUERY); + OSL_ENSURE(xHandler.is(),"lcl_createHandler: Can not create handler"); + return xHandler; + } + } + + + void OPropertyBrowserController::getPropertyHandlers( const InterfaceArray& _rObjects, PropertyHandlerArray& _rHandlers ) + { + _rHandlers.resize( 0 ); + if ( _rObjects.empty() ) + return; + + Sequence< Any > aHandlerFactories; + if ( m_xModel.is() ) + aHandlerFactories = m_xModel->getHandlerFactories(); + + for ( auto const & handlerFactory : std::as_const(aHandlerFactories) ) + { + if ( _rObjects.size() == 1 ) + { // we're inspecting only one object -> one handler + Reference< XPropertyHandler > xHandler( lcl_createHandler( m_xContext, handlerFactory ) ); + if ( xHandler.is() ) + { + xHandler->inspect( _rObjects[0] ); + _rHandlers.push_back( xHandler ); + } + } + else + { + // create a single handler for every single object + std::vector< Reference< XPropertyHandler > > aSingleHandlers( _rObjects.size() ); + std::vector< Reference< XPropertyHandler > >::iterator pHandler = aSingleHandlers.begin(); + + for (auto const& elem : _rObjects) + { + *pHandler = lcl_createHandler( m_xContext, handlerFactory ); + if ( pHandler->is() ) + { + (*pHandler)->inspect(elem); + ++pHandler; + } + } + aSingleHandlers.resize( pHandler - aSingleHandlers.begin() ); + + // then create a handler which composes information out of those single handlers + if ( !aSingleHandlers.empty() ) + _rHandlers.push_back( new PropertyComposer( std::move(aSingleHandlers) ) ); + } + } + + // note that the handlers will not be used by our caller, if they indicate that there are no + // properties they feel responsible for + } + + + bool OPropertyBrowserController::impl_findObjectProperty_nothrow( const OUString& _rName, OrderedPropertyMap::const_iterator* _pProperty ) + { + OrderedPropertyMap::const_iterator search = std::find_if(m_aProperties.begin(), m_aProperties.end(), + [&_rName](const OrderedPropertyMap::value_type& rEntry) { return rEntry.second.Name == _rName; }); + if ( _pProperty ) + *_pProperty = search; + return ( search != m_aProperties.end() ); + } + + + void OPropertyBrowserController::rebuildPropertyUI( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !haveView() ) + throw RuntimeException(); + + OrderedPropertyMap::const_iterator propertyPos; + if ( !impl_findObjectProperty_nothrow( _rPropertyName, &propertyPos ) ) + return; + + OLineDescriptor aDescriptor; + try + { + describePropertyLine( propertyPos->second, aDescriptor ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "OPropertyBrowserController::rebuildPropertyUI" ); + } + + getPropertyBox().ChangeEntry( aDescriptor ); + } + + + void OPropertyBrowserController::enablePropertyUI( const OUString& _rPropertyName, sal_Bool _bEnable ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !haveView() ) + throw RuntimeException(); + + if ( !impl_findObjectProperty_nothrow( _rPropertyName ) ) + return; + + getPropertyBox().EnablePropertyLine( _rPropertyName, _bEnable ); + } + + + void OPropertyBrowserController::enablePropertyUIElements( const OUString& _rPropertyName, sal_Int16 _nElements, sal_Bool _bEnable ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !haveView() ) + throw RuntimeException(); + + if ( !impl_findObjectProperty_nothrow( _rPropertyName ) ) + return; + + getPropertyBox().EnablePropertyControls( _rPropertyName, _nElements, _bEnable ); + } + + + void OPropertyBrowserController::showPropertyUI( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !haveView() ) + throw RuntimeException(); + + // look up the property in our object properties + OrderedPropertyMap::const_iterator propertyPos; + if ( !impl_findObjectProperty_nothrow( _rPropertyName, &propertyPos ) ) + return; + + if ( getPropertyBox().GetPropertyPos( _rPropertyName ) != EDITOR_LIST_ENTRY_NOTFOUND ) + { + rebuildPropertyUI( _rPropertyName ); + return; + } + + OLineDescriptor aDescriptor; + describePropertyLine( propertyPos->second, aDescriptor ); + + // look for the position to insert the property + + // side note: The methods GetPropertyPos and InsertEntry of the OPropertyEditor work + // only on the current page. This implies that it's impossible to use this method here + // to show property lines which are *not* on the current page. + // This is sufficient for now, but should be changed in the future. + + // by definition, the properties in m_aProperties are in the order in which they appear in the UI + // So all we need is a predecessor of pProperty in m_aProperties + sal_uInt16 nUIPos = EDITOR_LIST_ENTRY_NOTFOUND; + do + { + if ( propertyPos != m_aProperties.begin() ) + --propertyPos; + nUIPos = getPropertyBox().GetPropertyPos( propertyPos->second.Name ); + } + while ( ( nUIPos == EDITOR_LIST_ENTRY_NOTFOUND ) && ( propertyPos != m_aProperties.begin() ) ); + + if ( nUIPos == EDITOR_LIST_ENTRY_NOTFOUND ) + // insert at the very top + nUIPos = 0; + else + // insert right after the predecessor we found + ++nUIPos; + + getPropertyBox().InsertEntry( + aDescriptor, impl_getPageIdForCategory_nothrow( aDescriptor.Category ), nUIPos ); + } + + + void OPropertyBrowserController::hidePropertyUI( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !haveView() ) + throw RuntimeException(); + + if ( !impl_findObjectProperty_nothrow( _rPropertyName ) ) + return; + + getPropertyBox().RemoveEntry( _rPropertyName ); + } + + + void OPropertyBrowserController::showCategory( const OUString& rCategory, sal_Bool bShow ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !haveView() ) + throw RuntimeException(); + + sal_uInt16 nPageId = impl_getPageIdForCategory_nothrow( rCategory ); + OSL_ENSURE( nPageId != sal_uInt16(-1), "OPropertyBrowserController::showCategory: invalid category!" ); + + getPropertyBox().ShowPropertyPage( nPageId, bShow ); + } + + + Reference< XPropertyControl > SAL_CALL OPropertyBrowserController::getPropertyControl( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !haveView() ) + throw RuntimeException(); + + Reference< XPropertyControl > xControl( getPropertyBox().GetPropertyControl( _rPropertyName ) ); + return xControl; + } + + + void SAL_CALL OPropertyBrowserController::registerControlObserver( const Reference< XPropertyControlObserver >& Observer ) + { + m_aControlObservers.addInterface( Observer ); + } + + + void SAL_CALL OPropertyBrowserController::revokeControlObserver( const Reference< XPropertyControlObserver >& Observer ) + { + m_aControlObservers.removeInterface( Observer ); + } + + + void SAL_CALL OPropertyBrowserController::setHelpSectionText( const OUString& _rHelpText ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( !haveView() ) + throw DisposedException(); + + if ( !getPropertyBox().HasHelpSection() ) + throw NoSupportException(); + + getPropertyBox().SetHelpText( _rHelpText ); + } + + + void OPropertyBrowserController::impl_broadcastPropertyChange_nothrow( const OUString& _rPropertyName, const Any& _rNewValue, const Any& _rOldValue, bool _bFirstTimeInit ) const + { + // are there one or more handlers which are interested in the actuation? + std::pair< PropertyHandlerMultiRepository::const_iterator, PropertyHandlerMultiRepository::const_iterator > aInterestedHandlers = + m_aDependencyHandlers.equal_range( _rPropertyName ); + if ( aInterestedHandlers.first == aInterestedHandlers.second ) + // none of our handlers is interested in this + return; + + ComposedUIAutoFireGuard aAutoFireGuard( *m_pUIRequestComposer ); + try + { + // collect the responses from all interested handlers + PropertyHandlerMultiRepository::const_iterator handler = aInterestedHandlers.first; + while ( handler != aInterestedHandlers.second ) + { + handler->second->actuatingPropertyChanged( _rPropertyName, _rNewValue, _rOldValue, + m_pUIRequestComposer->getUIForPropertyHandler( handler->second ), + _bFirstTimeInit ); + ++handler; + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_OPropertyBrowserController_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::OPropertyBrowserController(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/propcontroller.hxx b/extensions/source/propctrlr/propcontroller.hxx new file mode 100644 index 0000000000..a149d7ff23 --- /dev/null +++ b/extensions/source/propctrlr/propcontroller.hxx @@ -0,0 +1,365 @@ +/* -*- 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 . + */ + +#pragma once + +#include "composeduiupdate.hxx" +#include "proplinelistener.hxx" +#include "propcontrolobserver.hxx" +#include "browserview.hxx" + +#include <com/sun/star/awt/XFocusListener.hpp> +#include <com/sun/star/beans/XPropertyChangeListener.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/awt/XLayoutConstrains.hpp> +#include <com/sun/star/inspection/XPropertyControlFactory.hpp> +#include <com/sun/star/inspection/XObjectInspector.hpp> +#include <com/sun/star/inspection/XObjectInspectorUI.hpp> +#include <com/sun/star/inspection/XPropertyHandler.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <comphelper/interfacecontainer2.hxx> +#include <comphelper/uno3.hxx> +#include <cppuhelper/implbase.hxx> +#include <comphelper/broadcasthelper.hxx> +#include <vcl/weld.hxx> + +#include <map> +#include <memory> +#include <unordered_map> +#include <vector> + +namespace pcr +{ + class OPropertyEditor; + struct OLineDescriptor; + + typedef ::cppu::WeakImplHelper < css::lang::XServiceInfo + , css::awt::XFocusListener + , css::awt::XLayoutConstrains + , css::beans::XPropertyChangeListener + , css::inspection::XPropertyControlFactory + , css::inspection::XObjectInspector + , css::lang::XInitialization + > OPropertyBrowserController_Base; + + class OPropertyBrowserController + :public ::comphelper::OMutexAndBroadcastHelper + ,public OPropertyBrowserController_Base + ,public css::inspection::XObjectInspectorUI + // that's intentionally *not* part of the OPropertyBrowserController_Base + // We do not want this to be available in queryInterface, getTypes, and the like. + ,public IPropertyLineListener + ,public IPropertyControlObserver + ,public IPropertyExistenceCheck + { + private: + typedef std::multimap< sal_Int32, css::beans::Property > OrderedPropertyMap; + typedef std::vector< css::uno::Reference< css::uno::XInterface > > + InterfaceArray; + + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::frame::XFrame > m_xFrame; + css::uno::Reference< css::awt::XWindow > m_xView; + + ::comphelper::OInterfaceContainerHelper2 m_aDisposeListeners; + ::comphelper::OInterfaceContainerHelper2 m_aControlObservers; + // meta data about the properties + std::unique_ptr<weld::Builder> m_xBuilder; + std::unique_ptr<OPropertyBrowserView> m_xPropView; + + OUString m_sPageSelection; + OUString m_sLastValidPageSelection; + + typedef css::uno::Reference< css::inspection::XPropertyHandler > + PropertyHandlerRef; + typedef std::vector< PropertyHandlerRef > PropertyHandlerArray; + typedef std::unordered_map< OUString, PropertyHandlerRef > + PropertyHandlerRepository; + typedef std::unordered_multimap< OUString, PropertyHandlerRef > + PropertyHandlerMultiRepository; + PropertyHandlerRepository m_aPropertyHandlers; + PropertyHandlerMultiRepository m_aDependencyHandlers; + PropertyHandlerRef m_xInteractiveHandler; + + std::unique_ptr< ComposedPropertyUIUpdate > m_pUIRequestComposer; + + /// our InspectorModel + css::uno::Reference< css::inspection::XObjectInspectorModel > + m_xModel; + /// the object(s) we're currently inspecting + InterfaceArray m_aInspectedObjects; + /// the properties of the currently inspected object(s) + OrderedPropertyMap m_aProperties; + /// the property we're just committing + OUString m_sCommittingProperty; + + typedef std::unordered_map< OUString, sal_uInt16 > HashString2Int16; + HashString2Int16 m_aPageIds; + + bool m_bContainerFocusListening; + bool m_bSuspendingPropertyHandlers; + bool m_bConstructed; + bool m_bBindingIntrospectee; + + protected: + DECLARE_XINTERFACE() + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // XController + virtual void SAL_CALL attachFrame( const css::uno::Reference< css::frame::XFrame >& xFrame ) override; + virtual sal_Bool SAL_CALL attachModel( const css::uno::Reference< css::frame::XModel >& xModel ) override; + virtual sal_Bool SAL_CALL suspend( sal_Bool bSuspend ) override; + virtual css::uno::Any SAL_CALL getViewData( ) override; + virtual void SAL_CALL restoreViewData( const css::uno::Any& Data ) override; + virtual css::uno::Reference< css::frame::XModel > SAL_CALL getModel( ) override; + virtual css::uno::Reference< css::frame::XFrame > SAL_CALL getFrame( ) override; + + // XComponent + virtual void SAL_CALL dispose( ) override; + virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override; + + // XFocusListener + virtual void SAL_CALL focusGained( const css::awt::FocusEvent& _rSource ) override; + virtual void SAL_CALL focusLost( const css::awt::FocusEvent& _rSource ) override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + // XLayoutConstrains + virtual css::awt::Size SAL_CALL getMinimumSize( ) override; + virtual css::awt::Size SAL_CALL getPreferredSize( ) override; + virtual css::awt::Size SAL_CALL calcAdjustedSize( const css::awt::Size& rNewSize ) override; + + // XPropertyChangeListener + virtual void SAL_CALL propertyChange( const css::beans::PropertyChangeEvent& _rEvent ) override; + + /** XPropertyControlFactory + */ + virtual css::uno::Reference< css::inspection::XPropertyControl > SAL_CALL createPropertyControl( ::sal_Int16 ControlType, sal_Bool CreateReadOnly ) override; + + public: + explicit OPropertyBrowserController( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext); + + protected: + virtual ~OPropertyBrowserController() override; + + // IPropertyLineListener + virtual void Clicked( const OUString& _rName, bool _bPrimary ) override; + virtual void Commit( const OUString& _rName, const css::uno::Any& _rVal ) override; + + // IPropertyControlObserver + virtual void focusGained( const css::uno::Reference< css::inspection::XPropertyControl >& Control ) override; + virtual void valueChanged( const css::uno::Reference< css::inspection::XPropertyControl >& Control ) override; + + // IPropertyExistenceCheck + virtual bool hasPropertyByName( const OUString& _rName ) override; + + // XObjectInspectorUI + virtual void SAL_CALL enablePropertyUI( const OUString& _rPropertyName, sal_Bool _bEnable ) override; + virtual void SAL_CALL enablePropertyUIElements( const OUString& _rPropertyName, ::sal_Int16 _nElements, sal_Bool _bEnable ) override; + virtual void SAL_CALL rebuildPropertyUI( const OUString& _rPropertyName ) override; + virtual void SAL_CALL showPropertyUI( const OUString& _rPropertyName ) override; + virtual void SAL_CALL hidePropertyUI( const OUString& _rPropertyName ) override; + virtual void SAL_CALL showCategory( const OUString& _rCategory, sal_Bool _bShow ) override; + virtual css::uno::Reference< css::inspection::XPropertyControl > SAL_CALL getPropertyControl( const OUString& _rPropertyName ) override; + virtual void SAL_CALL registerControlObserver( const css::uno::Reference< css::inspection::XPropertyControlObserver >& Observer ) override; + virtual void SAL_CALL revokeControlObserver( const css::uno::Reference< css::inspection::XPropertyControlObserver >& Observer ) override; + virtual void SAL_CALL setHelpSectionText( const OUString& HelpText ) override; + + // XObjectInspector + virtual css::uno::Reference< css::inspection::XObjectInspectorModel > SAL_CALL getInspectorModel() override; + virtual void SAL_CALL setInspectorModel( const css::uno::Reference< css::inspection::XObjectInspectorModel >& _inspectormodel ) override; + virtual css::uno::Reference< css::inspection::XObjectInspectorUI > SAL_CALL getInspectorUI() override; + virtual void SAL_CALL inspect( const css::uno::Sequence< css::uno::Reference< css::uno::XInterface > >& Objects ) override; + + // XDispatchProvider + virtual css::uno::Reference< css::frame::XDispatch > SAL_CALL queryDispatch( const css::util::URL& URL, const OUString& TargetFrameName, ::sal_Int32 SearchFlags ) override; + virtual css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL queryDispatches( const css::uno::Sequence< css::frame::DispatchDescriptor >& Requests ) override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + private: + void UpdateUI(); + + void startContainerWindowListening(); + void stopContainerWindowListening(); + + // stop the inspection + void stopInspection( bool _bCommitModified ); + + bool haveView() const { return bool(m_xPropView); } + OPropertyEditor& getPropertyBox() { return m_xPropView->getPropertyBox(); } + + // does the inspection of the objects as indicated by our model + void doInspection(); + + // bind the browser to m_xIntrospecteeAsProperty + void impl_rebindToInspectee_nothrow( InterfaceArray&& _rObjects ); + + /** retrieves special property handlers for our introspectee + */ + void getPropertyHandlers( const InterfaceArray& _rObjects, PropertyHandlerArray& _rHandlers ); + + /** called when a property changed, to broadcast any handlers which might have + registered for this property + + @param _bFirstTimeInit + if set to <FALSE/>, this is a real change in the property value, not just a call + for purposes of initialization. + */ + void impl_broadcastPropertyChange_nothrow( const OUString& _rPropertyName, const css::uno::Any& _rNewValue, const css::uno::Any& _rOldValue, bool _bFirstTimeInit ) const; + + /** determines whether the given property is an actuating property, that is, at least one + handler expressed interest in changes to this property's value. + */ + bool impl_isActuatingProperty_nothrow( const OUString& _rPropertyName ) const + { + return ( m_aDependencyHandlers.find( _rPropertyName ) != m_aDependencyHandlers.end() ); + } + + /** retrieves the value of the given property, by asking the appropriate XPropertyHandler + @param _rPropertyName + the name whose handler is to be obtained. Must be the name of a property + for which a handler is registered. + @throws + RuntimeException if there is no handler for the given property + @return + the value of this property + */ + css::uno::Any + impl_getPropertyValue_throw( const OUString& _rPropertyName ); + + /// calls XPropertyHandler::suspend for all our property handlers + bool suspendPropertyHandlers_nothrow( bool _bSuspend ); + + /// suspends the complete inspector + bool suspendAll_nothrow(); + + /** selects a page according to our current view data + */ + void selectPageFromViewData(); + + /** updates our view data from the currently active page + */ + void updateViewDataFromActivePage(); + + /// describes the UI for the given property + void describePropertyLine( const css::beans::Property& _rPropertyName, OLineDescriptor& _rDescriptor ); + + /** retrieves the position of the property given by name in m_aProperties + @return + <TRUE/> if and only if the property could be found. In this case, <arg>_pProperty</arg> (if + not <NULL/> contains the iterator pointing to this property. + */ + bool impl_findObjectProperty_nothrow( const OUString& _rName, OrderedPropertyMap::const_iterator* _pProperty = nullptr ); + + void Construct(const css::uno::Reference<css::awt::XWindow>& rContainerWindow, std::unique_ptr<weld::Builder> xBuilder); + + /** retrieves the property handler for a given property name + @param _rPropertyName + the name whose handler is to be obtained. Must be the name of a property + for which a handler is registered. + @throws + RuntimeException if there is no handler for the given property + @return + the handler which is responsible for the given property + */ + PropertyHandlerRef const & + impl_getHandlerForProperty_throw( const OUString& _rPropertyName ) const; + + /** determines whether we have a handler for the given property + @param _rPropertyName + the name of the property for which the existence of a handler should be checked + */ + bool + impl_hasPropertyHandlerFor_nothrow( const OUString& _rPropertyName ) const; + + /** builds up m_aPageIds from InspectorModel::describeCategories, and insert all the + respective tab pages into our view + @precond + m_aPageIds is empty + @throws css::uno::RuntimeException + if one of the callees of this method throws this exception + */ + void + impl_buildCategories_throw(); + + /** retrieves the id of the tab page which represents a given category. + @param _rCategoryName + the programmatic name of a category. + @return + the id of the tab page, or <code>(sal_uInt16)-1</code> if there + is no tab page for the given category + */ + sal_uInt16 + impl_getPageIdForCategory_nothrow( const OUString& _rCategoryName ) const; + + /** adds or removes ourself as XEventListener to/from all our inspectees + */ + void impl_toggleInspecteeListening_nothrow( bool _bOn ); + + /** binds the instance to a new model + */ + void impl_bindToNewModel_nothrow( const css::uno::Reference< css::inspection::XObjectInspectorModel >& _rxInspectorModel ); + + /** initializes our view, as indicated by the model's view-relevant properties + + It's allowed to call this method when no model exists, yet. In this case, nothing + happens. + */ + void impl_initializeView_nothrow(); + + /** determines whether the view should be readonly. + + Effectively, this means that the method simply checks the IsReadOnly attribute of the model. + If there is no model, <FALSE/> is returned. + + @throws css::uno::RuntimeException + in case asking the model for its IsReadOnly attribute throws a css::uno::RuntimeException + itself. + */ + bool impl_isReadOnlyModel_throw() const; + + /** starts or stops listening at the model + */ + void impl_startOrStopModelListening_nothrow( bool _bDoListen ) const; + + private: + DECL_LINK(OnPageActivation, LinkParamNone*, void); + + private: + // constructors + void createWithModel( const css::uno::Reference< css::inspection::XObjectInspectorModel >& _rxModel ); + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/propcontrolobserver.hxx b/extensions/source/propctrlr/propcontrolobserver.hxx new file mode 100644 index 0000000000..93e11053da --- /dev/null +++ b/extensions/source/propctrlr/propcontrolobserver.hxx @@ -0,0 +1,46 @@ +/* -*- 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 . + */ +#pragma once + +#include <com/sun/star/inspection/XPropertyControl.hpp> + + +namespace pcr +{ + + + //= IPropertyControlObserver + + /** non-UNO version of the XPropertyControlObserver + */ + class IPropertyControlObserver + { + public: + virtual void focusGained( const css::uno::Reference< css::inspection::XPropertyControl >& Control ) = 0; + virtual void valueChanged( const css::uno::Reference< css::inspection::XPropertyControl >& Control ) = 0; + + protected: + ~IPropertyControlObserver() {} + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/propertycomposer.cxx b/extensions/source/propctrlr/propertycomposer.cxx new file mode 100644 index 0000000000..869cba77c7 --- /dev/null +++ b/extensions/source/propctrlr/propertycomposer.cxx @@ -0,0 +1,485 @@ +/* -*- 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 "propertycomposer.hxx" + +#include <com/sun/star/lang/NullPointerException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <comphelper/sequence.hxx> +#include <osl/diagnose.h> +#include <comphelper/diagnose_ex.hxx> + +#include <algorithm> +#include <iterator> +#include <utility> + + +namespace pcr +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::inspection; + + + //= helper + + namespace + { + + struct SetPropertyValue + { + OUString sPropertyName; + const Any& rValue; + SetPropertyValue( OUString _aPropertyName, const Any& _rValue ) : sPropertyName(std::move( _aPropertyName )), rValue( _rValue ) { } + void operator()( const Reference< XPropertyHandler >& _rHandler ) + { + _rHandler->setPropertyValue( sPropertyName, rValue ); + } + }; + + + template < class BagType > + void putIntoBag( const Sequence< typename BagType::value_type >& _rArray, BagType& /* [out] */ _rBag ) + { + std::copy( _rArray.begin(), _rArray.end(), + std::insert_iterator< BagType >( _rBag, _rBag.begin() ) ); + } + + + template < class BagType > + void copyBagToArray( const BagType& /* [out] */ _rBag, Sequence< typename BagType::value_type >& _rArray ) + { + _rArray.realloc( _rBag.size() ); + std::copy( _rBag.begin(), _rBag.end(), _rArray.getArray() ); + } + } + + + //= PropertyComposer + + + // TODO: there are various places where we determine the first handler in our array which + // supports a given property id. This is, at the moment, done with searching all handlers, + // which is O( n * k ) at worst (n being the number of handlers, k being the maximum number + // of supported properties per handler). Shouldn't we cache this? So that it is O( log k )? + + + PropertyComposer::PropertyComposer( std::vector< Reference< XPropertyHandler > >&& _rSlaveHandlers ) + :PropertyComposer_Base ( m_aMutex ) + ,m_aSlaveHandlers ( std::move(_rSlaveHandlers) ) + ,m_aPropertyListeners ( m_aMutex ) + ,m_bSupportedPropertiesAreKnown ( false ) + { + if ( m_aSlaveHandlers.empty() ) + throw IllegalArgumentException(); + + osl_atomic_increment( &m_refCount ); + { + Reference< XPropertyChangeListener > xMeMyselfAndI( this ); + for (auto const& slaveHandler : m_aSlaveHandlers) + { + if ( !slaveHandler.is() ) + throw NullPointerException(); + slaveHandler->addPropertyChangeListener( xMeMyselfAndI ); + } + } + osl_atomic_decrement( &m_refCount ); + } + + + void SAL_CALL PropertyComposer::inspect( const Reference< XInterface >& _rxIntrospectee ) + { + MethodGuard aGuard( *this ); + + for (auto const& slaveHandler : m_aSlaveHandlers) + { + slaveHandler->inspect( _rxIntrospectee ); + } + } + + + Any SAL_CALL PropertyComposer::getPropertyValue( const OUString& _rPropertyName ) + { + MethodGuard aGuard( *this ); + return m_aSlaveHandlers[0]->getPropertyValue( _rPropertyName ); + } + + + void SAL_CALL PropertyComposer::setPropertyValue( const OUString& _rPropertyName, const Any& _rValue ) + { + MethodGuard aGuard( *this ); + std::for_each( m_aSlaveHandlers.begin(), m_aSlaveHandlers.end(), SetPropertyValue( _rPropertyName, _rValue ) ); + } + + + Any SAL_CALL PropertyComposer::convertToPropertyValue( const OUString& _rPropertyName, const Any& _rControlValue ) + { + MethodGuard aGuard( *this ); + return m_aSlaveHandlers[0]->convertToPropertyValue( _rPropertyName, _rControlValue ); + } + + + Any SAL_CALL PropertyComposer::convertToControlValue( const OUString& _rPropertyName, const Any& _rPropertyValue, const Type& _rControlValueType ) + { + MethodGuard aGuard( *this ); + return m_aSlaveHandlers[0]->convertToControlValue( _rPropertyName, _rPropertyValue, _rControlValueType ); + } + + + PropertyState SAL_CALL PropertyComposer::getPropertyState( const OUString& _rPropertyName ) + { + MethodGuard aGuard( *this ); + + // assume DIRECT for the moment. This will stay this way if *all* slaves + // tell the property has DIRECT state, and if *all* values equal + PropertyState eState = PropertyState_DIRECT_VALUE; + + // check the master state + Reference< XPropertyHandler > xPrimary( *m_aSlaveHandlers.begin() ); + Any aPrimaryValue = xPrimary->getPropertyValue( _rPropertyName ); + eState = xPrimary->getPropertyState( _rPropertyName ); + + // loop through the secondary sets + PropertyState eSecondaryState = PropertyState_DIRECT_VALUE; + for ( HandlerArray::const_iterator loop = m_aSlaveHandlers.begin() + 1; + loop != m_aSlaveHandlers.end(); + ++loop + ) + { + // the secondary state + eSecondaryState = (*loop)->getPropertyState( _rPropertyName ); + + // the secondary value + Any aSecondaryValue( (*loop)->getPropertyValue( _rPropertyName ) ); + + if ( ( PropertyState_AMBIGUOUS_VALUE == eSecondaryState ) // secondary is ambiguous + || ( aPrimaryValue != aSecondaryValue ) // unequal values + ) + { + eState = PropertyState_AMBIGUOUS_VALUE; + break; + } + } + + return eState; + } + + + void SAL_CALL PropertyComposer::addPropertyChangeListener( const Reference< XPropertyChangeListener >& _rxListener ) + { + MethodGuard aGuard( *this ); + m_aPropertyListeners.addInterface( _rxListener ); + } + + + void SAL_CALL PropertyComposer::removePropertyChangeListener( const Reference< XPropertyChangeListener >& _rxListener ) + { + MethodGuard aGuard( *this ); + m_aPropertyListeners.removeInterface( _rxListener ); + } + + + Sequence< Property > SAL_CALL PropertyComposer::getSupportedProperties() + { + MethodGuard aGuard( *this ); + + if ( !m_bSupportedPropertiesAreKnown ) + { + // we support a property if and only if all of our slaves support it + + // initially, use all the properties of an arbitrary handler (we take the first one) + putIntoBag( (*m_aSlaveHandlers.begin())->getSupportedProperties(), m_aSupportedProperties ); + + // now intersect with the properties of *all* other handlers + for ( HandlerArray::const_iterator loop = m_aSlaveHandlers.begin() + 1; + loop != m_aSlaveHandlers.end(); + ++loop + ) + { + // the properties supported by the current handler + PropertyBag aThisRound; + putIntoBag( (*loop)->getSupportedProperties(), aThisRound ); + + // the intersection of those properties with all we already have + PropertyBag aIntersection; + std::set_intersection( aThisRound.begin(), aThisRound.end(), m_aSupportedProperties.begin(), m_aSupportedProperties.end(), + std::insert_iterator< PropertyBag >( aIntersection, aIntersection.begin() ), PropertyLessByName() ); + + m_aSupportedProperties.swap( aIntersection ); + if ( m_aSupportedProperties.empty() ) + break; + } + + // remove those properties which are not composable + for ( PropertyBag::iterator check = m_aSupportedProperties.begin(); + check != m_aSupportedProperties.end(); + ) + { + bool bIsComposable = isComposable( check->Name ); + if ( !bIsComposable ) + { + check = m_aSupportedProperties.erase( check ); + } + else + ++check; + } + + m_bSupportedPropertiesAreKnown = true; + } + + return comphelper::containerToSequence( m_aSupportedProperties ); + } + + + static void uniteStringArrays( const PropertyComposer::HandlerArray& _rHandlers, Sequence< OUString > (SAL_CALL XPropertyHandler::*pGetter)( ), + Sequence< OUString >& /* [out] */ _rUnion ) + { + std::set< OUString > aUnitedBag; + + Sequence< OUString > aThisRound; + for (auto const& handler : _rHandlers) + { + aThisRound = (handler.get()->*pGetter)(); + putIntoBag( aThisRound, aUnitedBag ); + } + + copyBagToArray( aUnitedBag, _rUnion ); + } + + + Sequence< OUString > SAL_CALL PropertyComposer::getSupersededProperties( ) + { + MethodGuard aGuard( *this ); + + // we supersede those properties which are superseded by at least one of our slaves + Sequence< OUString > aSuperseded; + uniteStringArrays( m_aSlaveHandlers, &XPropertyHandler::getSupersededProperties, aSuperseded ); + return aSuperseded; + } + + + Sequence< OUString > SAL_CALL PropertyComposer::getActuatingProperties( ) + { + MethodGuard aGuard( *this ); + + // we're interested in those properties which at least one handler wants to have + Sequence< OUString > aActuating; + uniteStringArrays( m_aSlaveHandlers, &XPropertyHandler::getActuatingProperties, aActuating ); + return aActuating; + } + + + LineDescriptor SAL_CALL PropertyComposer::describePropertyLine( const OUString& _rPropertyName, + const Reference< XPropertyControlFactory >& _rxControlFactory ) + { + MethodGuard aGuard( *this ); + return m_aSlaveHandlers[0]->describePropertyLine( _rPropertyName, _rxControlFactory ); + } + + + sal_Bool SAL_CALL PropertyComposer::isComposable( const OUString& _rPropertyName ) + { + MethodGuard aGuard( *this ); + return m_aSlaveHandlers[0]->isComposable( _rPropertyName ); + } + + + InteractiveSelectionResult SAL_CALL PropertyComposer::onInteractivePropertySelection( const OUString& _rPropertyName, sal_Bool _bPrimary, Any& _rData, const Reference< XObjectInspectorUI >& _rxInspectorUI ) + { + if ( !_rxInspectorUI.is() ) + throw NullPointerException(); + + MethodGuard aGuard( *this ); + + impl_ensureUIRequestComposer( _rxInspectorUI ); + ComposedUIAutoFireGuard aAutoFireGuard( *m_pUIRequestComposer ); + + // ask the first of the handlers + InteractiveSelectionResult eResult = (*m_aSlaveHandlers.begin())->onInteractivePropertySelection( + _rPropertyName, + _bPrimary, + _rData, + m_pUIRequestComposer->getUIForPropertyHandler( *m_aSlaveHandlers.begin() ) + ); + + switch ( eResult ) + { + case InteractiveSelectionResult_Cancelled: + // fine + break; + + case InteractiveSelectionResult_Success: + case InteractiveSelectionResult_Pending: + OSL_FAIL( "PropertyComposer::onInteractivePropertySelection: no chance to forward the new value to the other handlers!" ); + // This means that we cannot know the new property value, which either has already been set + // at the first component ("Success"), or will be set later on once the asynchronous input + // is finished ("Pending"). So, we also cannot forward this new property value to the other + // handlers. + // We would need to be a listener at the property at the first component, but even this wouldn't + // be sufficient, since the property handler is free to change *any* property during a dedicated + // property UI. + eResult = InteractiveSelectionResult_Cancelled; + break; + + case InteractiveSelectionResult_ObtainedValue: + // OK. Our own caller will pass this as setPropertyValue, and we will then pass it to + // all slave handlers + break; + + default: + OSL_FAIL( "OPropertyBrowserController::onInteractivePropertySelection: unknown result value!" ); + break; + } + + return eResult; + } + + + void PropertyComposer::impl_ensureUIRequestComposer( const Reference< XObjectInspectorUI >& _rxInspectorUI ) + { + OSL_ENSURE(!m_pUIRequestComposer + || m_pUIRequestComposer->getDelegatorUI().get() == _rxInspectorUI.get(), + "PropertyComposer::impl_ensureUIRequestComposer: somebody's changing the horse " + "in the mid of the race!"); + + if (!m_pUIRequestComposer) + m_pUIRequestComposer.reset( new ComposedPropertyUIUpdate( _rxInspectorUI, this ) ); + } + + + void SAL_CALL PropertyComposer::actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const Any& _rNewValue, const Any& _rOldValue, const Reference< XObjectInspectorUI >& _rxInspectorUI, sal_Bool _bFirstTimeInit ) + { + if ( !_rxInspectorUI.is() ) + throw NullPointerException(); + + MethodGuard aGuard( *this ); + + impl_ensureUIRequestComposer( _rxInspectorUI ); + ComposedUIAutoFireGuard aAutoFireGuard( *m_pUIRequestComposer ); + + // ask all handlers which expressed interest in this particular property, and "compose" their + // commands for the UIUpdater + for (auto const& slaveHandler : m_aSlaveHandlers) + { + // TODO: make this cheaper (cache it?) + const StlSyntaxSequence< OUString > aThisHandlersActuatingProps( slaveHandler->getActuatingProperties() ); + for (const auto & aThisHandlersActuatingProp : aThisHandlersActuatingProps) + { + if ( aThisHandlersActuatingProp == _rActuatingPropertyName ) + { + slaveHandler->actuatingPropertyChanged( _rActuatingPropertyName, _rNewValue, _rOldValue, + m_pUIRequestComposer->getUIForPropertyHandler(slaveHandler), + _bFirstTimeInit ); + break; + } + } + } + } + + + IMPLEMENT_FORWARD_XCOMPONENT( PropertyComposer, PropertyComposer_Base ) + + + void SAL_CALL PropertyComposer::disposing() + { + MethodGuard aGuard( *this ); + + // dispose our slave handlers + for (auto const& slaveHandler : m_aSlaveHandlers) + { + slaveHandler->removePropertyChangeListener( this ); + slaveHandler->dispose(); + } + + clearContainer( m_aSlaveHandlers ); + + if (m_pUIRequestComposer) + m_pUIRequestComposer->dispose(); + m_pUIRequestComposer.reset(); + } + + + void SAL_CALL PropertyComposer::propertyChange( const PropertyChangeEvent& evt ) + { + if ( !impl_isSupportedProperty_nothrow( evt.PropertyName ) ) + // A slave handler might fire events for more properties than we support. Ignore those. + return; + + PropertyChangeEvent aTranslatedEvent( evt ); + try + { + aTranslatedEvent.NewValue = getPropertyValue( evt.PropertyName ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + m_aPropertyListeners.notifyEach( &XPropertyChangeListener::propertyChange, aTranslatedEvent ); + } + + + void SAL_CALL PropertyComposer::disposing( const EventObject& Source ) + { + MethodGuard aGuard( *this ); + m_aPropertyListeners.disposeAndClear( Source ); + } + + + sal_Bool SAL_CALL PropertyComposer::suspend( sal_Bool _bSuspend ) + { + MethodGuard aGuard( *this ); + for ( PropertyComposer::HandlerArray::const_iterator loop = m_aSlaveHandlers.begin(); + loop != m_aSlaveHandlers.end(); + ++loop + ) + { + if ( !(*loop)->suspend( _bSuspend ) ) + { + if ( _bSuspend && ( loop != m_aSlaveHandlers.begin() ) ) + { + // if we tried to suspend, but one of the slave handlers vetoed, + // re-activate the handlers which actually did *not* veto + // the suspension + do + { + --loop; + (*loop)->suspend( false ); + } + while ( loop != m_aSlaveHandlers.begin() ); + } + return false; + } + } + return true; + } + + + bool PropertyComposer::hasPropertyByName( const OUString& _rName ) + { + return impl_isSupportedProperty_nothrow( _rName ); + } + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/propertycomposer.hxx b/extensions/source/propctrlr/propertycomposer.hxx new file mode 100644 index 0000000000..5bcc58e23f --- /dev/null +++ b/extensions/source/propctrlr/propertycomposer.hxx @@ -0,0 +1,142 @@ +/* -*- 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 . + */ + +#pragma once + +#include "pcrcommon.hxx" +#include "composeduiupdate.hxx" +#include "formbrowsertools.hxx" + +#include <com/sun/star/inspection/XPropertyHandler.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> + +#include <memory> +#include <vector> +#include <set> + + +namespace pcr +{ + + + //= PropertyComposer + + typedef ::cppu::WeakComponentImplHelper < css::inspection::XPropertyHandler + , css::beans::XPropertyChangeListener + > PropertyComposer_Base; + /** implements an <type>XPropertyHandler</type> which composes its information + from a set of other property handlers + */ + class PropertyComposer :public ::cppu::BaseMutex + ,public PropertyComposer_Base + ,public IPropertyExistenceCheck + { + public: + typedef std::vector< css::uno::Reference< css::inspection::XPropertyHandler > > + HandlerArray; + + private: + HandlerArray m_aSlaveHandlers; + std::unique_ptr< ComposedPropertyUIUpdate > m_pUIRequestComposer; + PropertyChangeListeners m_aPropertyListeners; + bool m_bSupportedPropertiesAreKnown; + PropertyBag m_aSupportedProperties; + + public: + /** constructs an <type>XPropertyHandler</type> which composes its information from a set + of other property handlers + + @param _rSlaveHandlers + the set of slave handlers to invoke. Must not be <NULL/> + */ + explicit PropertyComposer( std::vector< css::uno::Reference< css::inspection::XPropertyHandler > >&& _rSlaveHandlers ); + + public: + // XPropertyHandler overridables + virtual void SAL_CALL inspect( const css::uno::Reference< css::uno::XInterface >& _rxIntrospectee ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& _rPropertyName ) override; + virtual void SAL_CALL setPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rValue ) override; + virtual css::uno::Any SAL_CALL convertToPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rControlValue ) override; + virtual css::uno::Any SAL_CALL convertToControlValue( const OUString& _rPropertyName, const css::uno::Any& _rPropertyValue, const css::uno::Type& _rControlValueType ) override; + virtual css::beans::PropertyState + SAL_CALL getPropertyState( const OUString& _rPropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ) override; + virtual css::uno::Sequence< css::beans::Property > + SAL_CALL getSupportedProperties() override; + virtual css::uno::Sequence< OUString > + SAL_CALL getSupersededProperties( ) override; + virtual css::uno::Sequence< OUString > + SAL_CALL getActuatingProperties( ) override; + virtual css::inspection::LineDescriptor + SAL_CALL describePropertyLine( const OUString& _rPropertyName, const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory ) override; + virtual sal_Bool SAL_CALL isComposable( const OUString& _rPropertyName ) override; + virtual css::inspection::InteractiveSelectionResult + SAL_CALL onInteractivePropertySelection( const OUString& _rPropertyName, sal_Bool _bPrimary, css::uno::Any& _rData, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI ) override; + virtual void SAL_CALL actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const css::uno::Any& _rNewValue, const css::uno::Any& _rOldValue, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI, sal_Bool _bFirstTimeInit ) override; + virtual sal_Bool SAL_CALL suspend( sal_Bool _bSuspend ) override; + + // XComponent + DECLARE_XCOMPONENT() + virtual void SAL_CALL disposing() override; + + // XPropertyChangeListener + virtual void SAL_CALL propertyChange( const css::beans::PropertyChangeEvent& evt ) override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + // IPropertyExistenceCheck + virtual bool hasPropertyByName( const OUString& _rName ) override; + + private: + /** ensures that m_pUIRequestComposer exists + */ + void impl_ensureUIRequestComposer( const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI ); + + /** checks whether a given property exists in <member>m_aSupportedProperties</member> + */ + bool impl_isSupportedProperty_nothrow( const OUString& _rPropertyName ) + { + css::beans::Property aDummy; aDummy.Name = _rPropertyName; + return m_aSupportedProperties.find( aDummy ) != m_aSupportedProperties.end(); + } + + private: + class MethodGuard; + friend class MethodGuard; + class MethodGuard : public ::osl::MutexGuard + { + public: + explicit MethodGuard( PropertyComposer& _rInstance ) + : ::osl::MutexGuard( _rInstance.m_aMutex ) + { + if ( _rInstance.m_aSlaveHandlers.empty() ) + throw css::lang::DisposedException( OUString(), _rInstance ); + } + }; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/propertycontrolextender.cxx b/extensions/source/propctrlr/propertycontrolextender.cxx new file mode 100644 index 0000000000..f2854f447a --- /dev/null +++ b/extensions/source/propctrlr/propertycontrolextender.cxx @@ -0,0 +1,114 @@ +/* -*- 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 "propertycontrolextender.hxx" + +#include <com/sun/star/awt/KeyFunction.hpp> + +#include <comphelper/diagnose_ex.hxx> + + +namespace pcr +{ + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::UNO_SET_THROW; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::uno::Any; + using ::com::sun::star::awt::KeyEvent; + using ::com::sun::star::inspection::XPropertyControl; + using ::com::sun::star::lang::EventObject; + using ::com::sun::star::inspection::XPropertyControlContext; + + namespace KeyFunction = ::com::sun::star::awt::KeyFunction; + + + //= PropertyControlExtender + + + PropertyControlExtender::PropertyControlExtender( const Reference< XPropertyControl >& _rxObservedControl ) + { + try + { + mxControl.set( _rxObservedControl, UNO_SET_THROW ); + mxControlWindow.set( mxControl->getControlWindow(), UNO_SET_THROW ); + mxControlWindow->addKeyListener( this ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + + PropertyControlExtender::~PropertyControlExtender() + { + } + + + void SAL_CALL PropertyControlExtender::keyPressed( const KeyEvent& _event ) + { + OSL_ENSURE( _event.Source == mxControlWindow, "PropertyControlExtender::keyPressed: where does this come from?" ); + if ( ( _event.KeyFunc != KeyFunction::DELETE ) + || ( _event.Modifiers != 0 ) + ) + return; + + try + { + Reference< XPropertyControl > xControl( mxControl, UNO_SET_THROW ); + + // reset the value + xControl->setValue( Any() ); + + // and notify the change + // don't use XPropertyControl::notifyModifiedValue. It only notifies when the control content + // is recognized as being modified by the user, which is not the case, since we just modified + // it programmatically. + Reference< XPropertyControlContext > xControlContext( xControl->getControlContext(), UNO_SET_THROW ); + xControlContext->valueChanged( xControl ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + + void SAL_CALL PropertyControlExtender::keyReleased( const KeyEvent& /*_event*/ ) + { + // not interested in + } + + + void SAL_CALL PropertyControlExtender::disposing( const EventObject& Source ) + { + OSL_ENSURE( Source.Source == mxControlWindow, "PropertyControlExtender::disposing: where does this come from?" ); + (void)Source.Source; + mxControlWindow.clear(); + mxControl.clear(); + } + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/propertycontrolextender.hxx b/extensions/source/propctrlr/propertycontrolextender.hxx new file mode 100644 index 0000000000..e376223ebf --- /dev/null +++ b/extensions/source/propctrlr/propertycontrolextender.hxx @@ -0,0 +1,64 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/awt/XKeyListener.hpp> +#include <com/sun/star/inspection/XPropertyControl.hpp> + +#include <cppuhelper/implbase.hxx> + +#include <memory> + + +namespace pcr +{ + + + //= PropertyControlExtender + + struct PropertyControlExtender_Data; + typedef ::cppu::WeakImplHelper < css::awt::XKeyListener + > PropertyControlExtender_Base; + class PropertyControlExtender : public PropertyControlExtender_Base + { + public: + explicit PropertyControlExtender( + const css::uno::Reference< css::inspection::XPropertyControl >& _rxObservedControl + ); + + // XKeyListener + virtual void SAL_CALL keyPressed( const css::awt::KeyEvent& e ) override; + virtual void SAL_CALL keyReleased( const css::awt::KeyEvent& e ) override; + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + protected: + virtual ~PropertyControlExtender() override; + + private: + css::uno::Reference< css::inspection::XPropertyControl > mxControl; + css::uno::Reference< css::awt::XWindow > mxControlWindow; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/propertyeditor.cxx b/extensions/source/propctrlr/propertyeditor.cxx new file mode 100644 index 0000000000..1e026c5b5b --- /dev/null +++ b/extensions/source/propctrlr/propertyeditor.cxx @@ -0,0 +1,381 @@ +/* -*- 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 "handlerhelper.hxx" +#include "propertyeditor.hxx" +#include "browserpage.hxx" +#include "linedescriptor.hxx" + +#include <tools/debug.hxx> +#include <utility> +#include <osl/diagnose.h> + +namespace pcr +{ + using ::com::sun::star::uno::Any; + using ::com::sun::star::inspection::XPropertyControl; + using ::com::sun::star::uno::Reference; + + OPropertyEditor::OPropertyEditor(const css::uno::Reference<css::uno::XComponentContext>& rContext, weld::Builder& rBuilder) + : m_xContainer(rBuilder.weld_container("box")) + , m_xTabControl(rBuilder.weld_notebook("tabcontrol")) + , m_xControlHoldingParent(rBuilder.weld_container("controlparent")) // controls initially have this parent before they are moved + , m_xContext(rContext) + , m_pListener(nullptr) + , m_pObserver(nullptr) + , m_nNextId(1) + , m_bHasHelpSection(false) + { + PropertyHandlerHelper::setBuilderParent(rContext, m_xControlHoldingParent.get()); + + m_xTabControl->connect_leave_page(LINK(this, OPropertyEditor, OnPageDeactivate)); + m_xTabControl->connect_enter_page(LINK(this, OPropertyEditor, OnPageActivate)); + } + + OPropertyEditor::~OPropertyEditor() + { + PropertyHandlerHelper::clearBuilderParent(m_xContext); + ClearAll(); + } + + void OPropertyEditor::ClearAll() + { + m_nNextId=1; + + m_aPropertyPageIds.clear(); + m_aShownPages.clear(); + m_aHiddenPages.clear(); + + int nCount = m_xTabControl->get_n_pages(); + for (int i = nCount - 1; i >= 0; --i) + { + OUString sID = m_xTabControl->get_page_ident(i); + m_xTabControl->remove_page(sID); + } + + assert(m_xTabControl->get_n_pages() == 0); + } + + Size OPropertyEditor::get_preferred_size() const + { + return m_xTabControl->get_preferred_size(); + } + + void OPropertyEditor::CommitModified() + { + // commit all of my pages, if necessary + for (const auto& page : m_aShownPages) + { + OBrowserPage* pPage = page.second.xPage.get(); + if (pPage && pPage->getListBox().IsModified() ) + pPage->getListBox().CommitModified(); + } + } + + OBrowserPage* OPropertyEditor::getPage(const OUString& rPropertyName) + { + OBrowserPage* pPage = nullptr; + MapStringToPageId::const_iterator aPropertyPageIdPos = m_aPropertyPageIds.find(rPropertyName); + if (aPropertyPageIdPos != m_aPropertyPageIds.end()) + pPage = getPage(aPropertyPageIdPos->second); + return pPage; + } + + const OBrowserPage* OPropertyEditor::getPage( const OUString& _rPropertyName ) const + { + return const_cast< OPropertyEditor* >( this )->getPage( _rPropertyName ); + } + + OBrowserPage* OPropertyEditor::getPage(sal_uInt16 rPageId) + { + OBrowserPage* pPage = nullptr; + auto aPagePos = m_aShownPages.find(rPageId); + if (aPagePos != m_aShownPages.end()) + pPage = aPagePos->second.xPage.get(); + return pPage; + } + + const OBrowserPage* OPropertyEditor::getPage(sal_uInt16 rPageId) const + { + return const_cast<OPropertyEditor*>(this)->getPage(rPageId); + } + + sal_uInt16 OPropertyEditor::AppendPage(const OUString& rText, const OUString& rHelpId) + { + // obtain a new id + sal_uInt16 nId = m_nNextId++; + // insert the id + OUString sIdent = OUString::number(nId); + m_xTabControl->append_page(sIdent, rText); + + // create a new page + auto xPage = std::make_unique<OBrowserPage>(m_xTabControl->get_page(sIdent), m_xControlHoldingParent.get()); + // some knittings + xPage->getListBox().SetListener(m_pListener); + xPage->getListBox().SetObserver(m_pObserver); + xPage->getListBox().EnableHelpSection(m_bHasHelpSection); + xPage->SetHelpId(rHelpId); + + m_aShownPages[nId] = PropertyPage(m_xTabControl->get_n_pages() - 1, rText, std::move(xPage)); + + // immediately activate the page + m_xTabControl->set_current_page(sIdent); + + return nId; + } + + void OPropertyEditor::SetHelpId( const OUString& rHelpId ) + { + m_xTabControl->set_help_id(rHelpId); + } + + void OPropertyEditor::RemovePage(sal_uInt16 nID) + { + auto aPagePos = m_aShownPages.find(nID); + if (aPagePos == m_aShownPages.end()) + return; + + m_aShownPages.erase(aPagePos); + OUString sIdent(OUString::number(nID)); + m_xTabControl->remove_page(sIdent); + } + + void OPropertyEditor::SetPage(sal_uInt16 nId) + { + m_xTabControl->set_current_page(OUString::number(nId)); + } + + sal_uInt16 OPropertyEditor::GetCurPage() const + { + return m_xTabControl->get_current_page_ident().toUInt32(); + } + + void OPropertyEditor::forEachPage( PageOperation _pOperation ) + { + int nCount = m_xTabControl->get_n_pages(); + for (int i = 0; i < nCount; ++i) + { + sal_uInt16 nID = m_xTabControl->get_page_ident(i).toUInt32(); + OBrowserPage* pPage = getPage(nID); + if (!pPage) + continue; + (this->*_pOperation)( *pPage, nullptr ); + } + } + + void OPropertyEditor::setPageLineListener( OBrowserPage& rPage, const void* ) + { + rPage.getListBox().SetListener( m_pListener ); + } + + void OPropertyEditor::SetLineListener(IPropertyLineListener* pListener) + { + m_pListener = pListener; + forEachPage( &OPropertyEditor::setPageLineListener ); + } + + void OPropertyEditor::setPageControlObserver( OBrowserPage& rPage, const void* ) + { + rPage.getListBox().SetObserver( m_pObserver ); + } + + void OPropertyEditor::SetControlObserver( IPropertyControlObserver* _pObserver ) + { + m_pObserver = _pObserver; + forEachPage( &OPropertyEditor::setPageControlObserver ); + } + + void OPropertyEditor::EnableHelpSection( bool bEnable ) + { + m_bHasHelpSection = bEnable; + forEachPage( &OPropertyEditor::enableHelpSection ); + } + + void OPropertyEditor::SetHelpText( const OUString& rHelpText ) + { + int nCount = m_xTabControl->get_n_pages(); + for (int i = 0; i < nCount; ++i) + { + sal_uInt16 nID = m_xTabControl->get_page_ident(i).toUInt32(); + OBrowserPage* pPage = getPage(nID); + if (!pPage) + continue; + setHelpSectionText( *pPage, &rHelpText ); + } + } + + void OPropertyEditor::enableHelpSection( OBrowserPage& rPage, const void* ) + { + rPage.getListBox().EnableHelpSection( m_bHasHelpSection ); + } + + void OPropertyEditor::setHelpSectionText( OBrowserPage& rPage, const void* pPointerToOUString ) + { + OSL_ENSURE( pPointerToOUString, "OPropertyEditor::setHelpSectionText: invalid argument!" ); + if ( !pPointerToOUString ) + return; + + const OUString& rText( *static_cast<const OUString*>(pPointerToOUString) ); + rPage.getListBox().SetHelpText( rText ); + } + + void OPropertyEditor::InsertEntry( const OLineDescriptor& rData, sal_uInt16 nPageId, sal_uInt16 nPos ) + { + // let the current page handle this + OBrowserPage* pPage = getPage(nPageId); + DBG_ASSERT( pPage, "OPropertyEditor::InsertEntry: don't have such a page!" ); + if ( !pPage ) + return; + + pPage->getListBox().InsertEntry( rData, nPos ); + + OSL_ENSURE( m_aPropertyPageIds.find( rData.sName ) == m_aPropertyPageIds.end(), + "OPropertyEditor::InsertEntry: property already present in the map!" ); + m_aPropertyPageIds.emplace( rData.sName, nPageId ); + } + + void OPropertyEditor::RemoveEntry( const OUString& rName ) + { + OBrowserPage* pPage = getPage( rName ); + if ( pPage ) + { + OSL_VERIFY( pPage->getListBox().RemoveEntry( rName ) ); + + OSL_ENSURE( m_aPropertyPageIds.find( rName ) != m_aPropertyPageIds.end(), + "OPropertyEditor::RemoveEntry: property not present in the map!" ); + m_aPropertyPageIds.erase( rName ); + } + } + + void OPropertyEditor::ChangeEntry( const OLineDescriptor& rData ) + { + OBrowserPage* pPage = getPage( rData.sName ); + if ( pPage ) + pPage->getListBox().ChangeEntry( rData, EDITOR_LIST_REPLACE_EXISTING ); + } + + void OPropertyEditor::SetPropertyValue( const OUString& rEntryName, const Any& _rValue, bool _bUnknownValue ) + { + OBrowserPage* pPage = getPage( rEntryName ); + if ( pPage ) + pPage->getListBox().SetPropertyValue( rEntryName, _rValue, _bUnknownValue ); + } + + sal_uInt16 OPropertyEditor::GetPropertyPos( const OUString& rEntryName ) const + { + sal_uInt16 nVal=EDITOR_LIST_ENTRY_NOTFOUND; + const OBrowserPage* pPage = getPage( rEntryName ); + if ( pPage ) + nVal = pPage->getListBox().GetPropertyPos( rEntryName ); + return nVal; + } + + void OPropertyEditor::ShowPropertyPage(sal_uInt16 nPageId, bool bShow) + { + assert((m_aHiddenPages.find(nPageId) != m_aHiddenPages.end() || + m_aShownPages.find(nPageId) != m_aShownPages.end()) && "page doesn't exist"); + OUString sIdent(OUString::number(nPageId)); + if (!bShow) + { + auto aPagePos = m_aShownPages.find(nPageId); + if (aPagePos != m_aShownPages.end()) + { + aPagePos->second.xPage->detach(); + m_xTabControl->remove_page(sIdent); + + m_aHiddenPages[nPageId] = std::move(aPagePos->second); + m_aShownPages.erase(aPagePos); + } + } + else + { + auto aPagePos = m_aHiddenPages.find(nPageId); + if (aPagePos != m_aHiddenPages.end()) + { + m_xTabControl->insert_page(sIdent, aPagePos->second.sLabel, aPagePos->second.nPos); + aPagePos->second.xPage->reattach(m_xTabControl->get_page(sIdent)); + + m_aShownPages[nPageId] = std::move(aPagePos->second); + m_aHiddenPages.erase(aPagePos); + } + } + } + + void OPropertyEditor::EnablePropertyControls( const OUString& rEntryName, sal_Int16 nControls, bool bEnable ) + { + for (const auto& rPage : m_aShownPages) + { + OBrowserPage* pPage = rPage.second.xPage.get(); + if (pPage) + pPage->getListBox().EnablePropertyControls( rEntryName, nControls, bEnable ); + } + } + + void OPropertyEditor::EnablePropertyLine( const OUString& rEntryName, bool bEnable ) + { + for (const auto& rPage : m_aShownPages) + { + OBrowserPage* pPage = rPage.second.xPage.get(); + if (pPage) + pPage->getListBox().EnablePropertyLine( rEntryName, bEnable ); + } + } + + Reference< XPropertyControl > OPropertyEditor::GetPropertyControl(const OUString& rEntryName) + { + Reference< XPropertyControl > xControl; + // let the current page handle this + OBrowserPage* pPage = getPage(m_xTabControl->get_current_page_ident().toUInt32()); + if (pPage) + xControl = pPage->getListBox().GetPropertyControl(rEntryName); + return xControl; + } + + IMPL_LINK(OPropertyEditor, OnPageActivate, const OUString&, rNewPage, void) + { + m_aPageActivationHandler.Call(rNewPage); + } + + IMPL_LINK(OPropertyEditor, OnPageDeactivate, const OUString&, rIdent, bool) + { + // commit the data on the current (to-be-deactivated) tab page + // (79404) + OBrowserPage* pCurrentPage = getPage(rIdent.toUInt32()); + if (!pCurrentPage) + return true; + + if (pCurrentPage->getListBox().IsModified()) + pCurrentPage->getListBox().CommitModified(); + + return true; + } + + OPropertyEditor::PropertyPage::PropertyPage() + : nPos(0) + { + } + + OPropertyEditor::PropertyPage::PropertyPage(sal_uInt16 nPagePos, OUString aLabel, std::unique_ptr<OBrowserPage> pPage) + : nPos(nPagePos), sLabel(std::move(aLabel)), xPage(std::move(pPage)) + { + } + +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/propertyeditor.hxx b/extensions/source/propctrlr/propertyeditor.hxx new file mode 100644 index 0000000000..96199b31af --- /dev/null +++ b/extensions/source/propctrlr/propertyeditor.hxx @@ -0,0 +1,134 @@ +/* -*- 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 . + */ + +#pragma once + +#include "browserpage.hxx" +#include "pcrcommon.hxx" + +#include <com/sun/star/inspection/XPropertyControl.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <vcl/weld.hxx> +#include <map> + +namespace pcr +{ + class IPropertyLineListener; + class IPropertyControlObserver; + class OBrowserPage; + struct OLineDescriptor; + class OBrowserListBox; + + //= OPropertyEditor + class OPropertyEditor final + { + private: + typedef std::map< OUString, sal_uInt16 > MapStringToPageId; + struct PropertyPage + { + sal_uInt16 nPos; + OUString sLabel; + std::unique_ptr<OBrowserPage> xPage; + PropertyPage(); + PropertyPage(sal_uInt16 nPagePos, OUString aLabel, std::unique_ptr<OBrowserPage> pPage); + }; + + std::unique_ptr<weld::Container> m_xContainer; + std::unique_ptr<weld::Notebook> m_xTabControl; + // controls initially have this parent before they are moved + std::unique_ptr<weld::Container> m_xControlHoldingParent; + css::uno::Reference<css::uno::XComponentContext> m_xContext; + IPropertyLineListener* m_pListener; + IPropertyControlObserver* m_pObserver; + sal_uInt16 m_nNextId; + Link<const OUString&,void> m_aPageActivationHandler; + bool m_bHasHelpSection; + + MapStringToPageId m_aPropertyPageIds; + std::map<sal_uInt16, PropertyPage> m_aShownPages; + std::map<sal_uInt16, PropertyPage> m_aHiddenPages; + + public: + explicit OPropertyEditor(const css::uno::Reference<css::uno::XComponentContext>& rContext, weld::Builder& rBuilder); + ~OPropertyEditor(); + + void SetLineListener( IPropertyLineListener* ); + void SetControlObserver( IPropertyControlObserver* ); + + void EnableHelpSection( bool _bEnable ); + bool HasHelpSection() const { return m_bHasHelpSection; } + void SetHelpText( const OUString& _rHelpText ); + + void SetHelpId( const OUString& sHelpId ); + sal_uInt16 AppendPage( const OUString& r, const OUString& _rHelpId ); + void SetPage( sal_uInt16 ); + void RemovePage(sal_uInt16 nID); + sal_uInt16 GetCurPage() const; + void ClearAll(); + + void SetPropertyValue(const OUString& _rEntryName, const css::uno::Any& _rValue, bool _bUnknownValue ); + sal_uInt16 GetPropertyPos(const OUString& rEntryName ) const; + css::uno::Reference< css::inspection::XPropertyControl > + GetPropertyControl( const OUString& rEntryName ); + void EnablePropertyLine( const OUString& _rEntryName, bool _bEnable ); + void EnablePropertyControls( const OUString& _rEntryName, sal_Int16 _nControls, bool _bEnable ); + + void ShowPropertyPage( sal_uInt16 _nPageId, bool _bShow ); + + void InsertEntry( const OLineDescriptor&, sal_uInt16 _nPageId, sal_uInt16 nPos = EDITOR_LIST_APPEND ); + void RemoveEntry( const OUString& _rName ); + void ChangeEntry( const OLineDescriptor& ); + + void setPageActivationHandler(const Link<const OUString&,void>& _rHdl) { m_aPageActivationHandler = _rHdl; } + + Size get_preferred_size() const; + + weld::Widget* getWidget() const { return m_xTabControl.get(); } + + void Show() { m_xTabControl->show(); } + void Hide() { m_xTabControl->hide(); } + void GrabFocus() { m_xTabControl->grab_focus(); } + + void CommitModified(); + + private: + OBrowserPage* getPage( sal_uInt16 _rPageId ); + const OBrowserPage* getPage( sal_uInt16 _rPageId ) const; + + OBrowserPage* getPage( const OUString& _rPropertyName ); + const OBrowserPage* getPage( const OUString& _rPropertyName ) const; + + typedef void (OPropertyEditor::*PageOperation)( OBrowserPage&, const void* ); + void forEachPage( PageOperation _pOperation ); + + void setPageLineListener( OBrowserPage& _rPage, const void* ); + void setPageControlObserver( OBrowserPage& _rPage, const void* ); + void enableHelpSection( OBrowserPage& _rPage, const void* ); + static void setHelpSectionText( OBrowserPage& _rPage, const void* _pPointerToOUString ); + + DECL_LINK(OnPageDeactivate, const OUString&, bool); + DECL_LINK(OnPageActivate, const OUString&, void); + }; + + +} // namespace pcr + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/propertyhandler.cxx b/extensions/source/propctrlr/propertyhandler.cxx new file mode 100644 index 0000000000..1c58a6202b --- /dev/null +++ b/extensions/source/propctrlr/propertyhandler.cxx @@ -0,0 +1,436 @@ +/* -*- 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 "propertyhandler.hxx" +#include "formmetadata.hxx" +#include "formbrowsertools.hxx" +#include "handlerhelper.hxx" +#include "formstrings.hxx" + +#include <com/sun/star/lang/NullPointerException.hpp> +#include <com/sun/star/util/XModifiable.hpp> +#include <com/sun/star/script/Converter.hpp> + +#include <cppuhelper/supportsservice.hxx> +#include <tools/debug.hxx> +#include <unotools/confignode.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/syslocale.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <rtl/ref.hxx> + +#include <algorithm> + +namespace pcr +{ + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::awt; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::script; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::util; + using namespace ::com::sun::star::frame; + using namespace ::com::sun::star::inspection; + using namespace ::comphelper; + + + PropertyHandler::PropertyHandler( const Reference< XComponentContext >& _rxContext ) + :PropertyHandler_Base( m_aMutex ) + ,m_bSupportedPropertiesAreKnown( false ) + ,m_aPropertyListeners( m_aMutex ) + ,m_xContext( _rxContext ) + ,m_pInfoService ( new OPropertyInfoService ) + { + + m_xTypeConverter = Converter::create(_rxContext); + } + + PropertyHandler::~PropertyHandler() + { + } + + void SAL_CALL PropertyHandler::inspect( const Reference< XInterface >& _rxIntrospectee ) + { + if ( !_rxIntrospectee.is() ) + throw NullPointerException(); + + ::osl::MutexGuard aGuard( m_aMutex ); + + Reference< XPropertySet > xNewComponent( _rxIntrospectee, UNO_QUERY ); + if ( xNewComponent == m_xComponent ) + return; + + // remove all old property change listeners + ::comphelper::OInterfaceIteratorHelper3 removeListener(m_aPropertyListeners); + ::comphelper::OInterfaceIteratorHelper3 readdListener(m_aPropertyListeners); // will copy the container as needed + while ( removeListener.hasMoreElements() ) + removePropertyChangeListener( removeListener.next() ); + OSL_ENSURE( m_aPropertyListeners.getLength() == 0, "PropertyHandler::inspect: derived classes are expected to forward the removePropertyChangeListener call to their base class (me)!" ); + + // remember the new component, and give derived classes the chance to react on it + m_xComponent = xNewComponent; + onNewComponent(); + + // add the listeners, again + while ( readdListener.hasMoreElements() ) + addPropertyChangeListener( readdListener.next() ); + } + + void PropertyHandler::onNewComponent() + { + if ( m_xComponent.is() ) + m_xComponentPropertyInfo = m_xComponent->getPropertySetInfo(); + else + m_xComponentPropertyInfo.clear(); + + m_bSupportedPropertiesAreKnown = false; + m_aSupportedProperties.realloc( 0 ); + } + + Sequence< Property > SAL_CALL PropertyHandler::getSupportedProperties() + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !m_bSupportedPropertiesAreKnown ) + { + m_aSupportedProperties = StlSyntaxSequence<css::beans::Property>(doDescribeSupportedProperties()); + m_bSupportedPropertiesAreKnown = true; + } + return m_aSupportedProperties; + } + + Sequence< OUString > SAL_CALL PropertyHandler::getSupersededProperties( ) + { + return Sequence< OUString >(); + } + + Sequence< OUString > SAL_CALL PropertyHandler::getActuatingProperties( ) + { + return Sequence< OUString >(); + } + + Any SAL_CALL PropertyHandler::convertToPropertyValue( const OUString& _rPropertyName, const Any& _rControlValue ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId = m_pInfoService->getPropertyId( _rPropertyName ); + Property aProperty( impl_getPropertyFromName_throw( _rPropertyName ) ); + + Any aPropertyValue; + if ( !_rControlValue.hasValue() ) + // NULL is converted to NULL + return aPropertyValue; + + if ( ( m_pInfoService->getPropertyUIFlags( nPropId ) & PROP_FLAG_ENUM ) != 0 ) + { + OUString sControlValue; + OSL_VERIFY( _rControlValue >>= sControlValue ); + ::rtl::Reference< IPropertyEnumRepresentation > aEnumConversion( + new DefaultEnumRepresentation( *m_pInfoService, aProperty.Type, nPropId ) ); + // TODO/UNOize: cache those converters? + aEnumConversion->getValueFromDescription( sControlValue, aPropertyValue ); + } + else + aPropertyValue = PropertyHandlerHelper::convertToPropertyValue( + m_xContext, m_xTypeConverter, aProperty, _rControlValue ); + return aPropertyValue; + } + + Any SAL_CALL PropertyHandler::convertToControlValue( const OUString& _rPropertyName, const Any& _rPropertyValue, const Type& _rControlValueType ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId = m_pInfoService->getPropertyId( _rPropertyName ); + + if ( ( m_pInfoService->getPropertyUIFlags( nPropId ) & PROP_FLAG_ENUM ) != 0 ) + { + DBG_ASSERT( _rControlValueType.getTypeClass() == TypeClass_STRING, "PropertyHandler::convertToControlValue: ENUM, but not STRING?" ); + + ::rtl::Reference< IPropertyEnumRepresentation > aEnumConversion( + new DefaultEnumRepresentation( *m_pInfoService, _rPropertyValue.getValueType(), nPropId ) ); + // TODO/UNOize: cache those converters? + return Any( aEnumConversion->getDescriptionForValue( _rPropertyValue ) ); + } + + return PropertyHandlerHelper::convertToControlValue( + m_xContext, m_xTypeConverter, _rPropertyValue, _rControlValueType ); + } + + PropertyState SAL_CALL PropertyHandler::getPropertyState( const OUString& /*_rPropertyName*/ ) + { + return PropertyState_DIRECT_VALUE; + } + + LineDescriptor SAL_CALL PropertyHandler::describePropertyLine( const OUString& _rPropertyName, + const Reference< XPropertyControlFactory >& _rxControlFactory ) + { + if ( !_rxControlFactory.is() ) + throw NullPointerException(); + + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + const Property& rProperty( impl_getPropertyFromId_throw( nPropId ) ); + + LineDescriptor aDescriptor; + if ( ( m_pInfoService->getPropertyUIFlags( nPropId ) & PROP_FLAG_ENUM ) != 0 ) + { + aDescriptor.Control = PropertyHandlerHelper::createListBoxControl( + _rxControlFactory, m_pInfoService->getPropertyEnumRepresentations( nPropId ), + PropertyHandlerHelper::requiresReadOnlyControl( rProperty.Attributes ), false ); + } + else + PropertyHandlerHelper::describePropertyLine( rProperty, aDescriptor, _rxControlFactory ); + + aDescriptor.HelpURL = HelpIdUrl::getHelpURL( m_pInfoService->getPropertyHelpId( nPropId ) ); + aDescriptor.DisplayName = m_pInfoService->getPropertyTranslation( nPropId ); + + if ( ( m_pInfoService->getPropertyUIFlags( nPropId ) & PROP_FLAG_DATA_PROPERTY ) != 0 ) + aDescriptor.Category = "Data"; + else + aDescriptor.Category = "General"; + return aDescriptor; + } + + sal_Bool SAL_CALL PropertyHandler::isComposable( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + return m_pInfoService->isComposeable( _rPropertyName ); + } + + InteractiveSelectionResult SAL_CALL PropertyHandler::onInteractivePropertySelection( const OUString& /*_rPropertyName*/, sal_Bool /*_bPrimary*/, Any& /*_rData*/, const Reference< XObjectInspectorUI >& /*_rxInspectorUI*/ ) + { + OSL_FAIL( "PropertyHandler::onInteractivePropertySelection: not implemented!" ); + return InteractiveSelectionResult_Cancelled; + } + + void SAL_CALL PropertyHandler::actuatingPropertyChanged( const OUString& /*_rActuatingPropertyName*/, const Any& /*_rNewValue*/, const Any& /*_rOldValue*/, const Reference< XObjectInspectorUI >& /*_rxInspectorUI*/, sal_Bool /*_bFirstTimeInit*/ ) + { + OSL_FAIL( "PropertyHandler::actuatingPropertyChanged: not implemented!" ); + } + + void SAL_CALL PropertyHandler::addPropertyChangeListener( const Reference< XPropertyChangeListener >& _rxListener ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !_rxListener.is() ) + throw NullPointerException(); + m_aPropertyListeners.addInterface( _rxListener ); + } + + void SAL_CALL PropertyHandler::removePropertyChangeListener( const Reference< XPropertyChangeListener >& _rxListener ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + m_aPropertyListeners.removeInterface( _rxListener ); + } + + sal_Bool SAL_CALL PropertyHandler::suspend( sal_Bool /*_bSuspend*/ ) + { + return true; + } + + void SAL_CALL PropertyHandler::dispose( ) + { + PropertyHandler_Base::WeakComponentImplHelperBase::dispose(); + m_xComponent.clear(); + m_xComponentPropertyInfo.clear(); + m_xTypeConverter.clear(); + } + void SAL_CALL PropertyHandler::addEventListener( const css::uno::Reference< css::lang::XEventListener >& Listener ) + { + PropertyHandler_Base::WeakComponentImplHelperBase::addEventListener( Listener ); + } + void SAL_CALL PropertyHandler::removeEventListener( const css::uno::Reference< css::lang::XEventListener >& Listener ) + { + PropertyHandler_Base::WeakComponentImplHelperBase::removeEventListener( Listener ); + } + + + void SAL_CALL PropertyHandler::disposing() + { + m_xComponent.clear(); + m_aPropertyListeners.clear(); + m_xTypeConverter.clear(); + m_aSupportedProperties.realloc( 0 ); + } + + void PropertyHandler::firePropertyChange( const OUString& _rPropName, PropertyId _nPropId, const Any& _rOldValue, const Any& _rNewValue ) + { + PropertyChangeEvent aEvent; + aEvent.Source = m_xComponent; + aEvent.PropertyHandle = _nPropId; + aEvent.PropertyName = _rPropName; + aEvent.OldValue = _rOldValue; + aEvent.NewValue = _rNewValue; + m_aPropertyListeners.notifyEach( &XPropertyChangeListener::propertyChange, aEvent ); + } + + const Property* PropertyHandler::impl_getPropertyFromId_nothrow( PropertyId _nPropId ) const + { + const_cast< PropertyHandler* >( this )->getSupportedProperties(); + const Property* pFound = std::find_if( m_aSupportedProperties.begin(), m_aSupportedProperties.end(), + FindPropertyByHandle( _nPropId ) + ); + if ( pFound != m_aSupportedProperties.end() ) + return pFound; + return nullptr; + } + + const Property& PropertyHandler::impl_getPropertyFromId_throw( PropertyId _nPropId ) const + { + const Property* pProperty = impl_getPropertyFromId_nothrow( _nPropId ); + if ( !pProperty ) + throw UnknownPropertyException(); + + return *pProperty; + } + + const Property& PropertyHandler::impl_getPropertyFromName_throw( const OUString& _rPropertyName ) const + { + const_cast< PropertyHandler* >( this )->getSupportedProperties(); + StlSyntaxSequence< Property >::const_iterator pFound = std::find_if( m_aSupportedProperties.begin(), m_aSupportedProperties.end(), + FindPropertyByName( _rPropertyName ) + ); + if ( pFound == m_aSupportedProperties.end() ) + throw UnknownPropertyException(_rPropertyName); + + return *pFound; + } + + void PropertyHandler::implAddPropertyDescription( std::vector< Property >& _rProperties, const OUString& _rPropertyName, const Type& _rType, sal_Int16 _nAttribs ) const + { + _rProperties.push_back( Property( + _rPropertyName, + m_pInfoService->getPropertyId( _rPropertyName ), + _rType, + _nAttribs + ) ); + } + + weld::Window* PropertyHandler::impl_getDefaultDialogFrame_nothrow() const + { + return PropertyHandlerHelper::getDialogParentFrame(m_xContext); + } + + PropertyId PropertyHandler::impl_getPropertyId_throwUnknownProperty( const OUString& _rPropertyName ) const + { + PropertyId nPropId = m_pInfoService->getPropertyId( _rPropertyName ); + if ( nPropId == -1 ) + throw UnknownPropertyException(_rPropertyName); + return nPropId; + } + + PropertyId PropertyHandler::impl_getPropertyId_throwRuntime( const OUString& _rPropertyName ) const + { + PropertyId nPropId = m_pInfoService->getPropertyId( _rPropertyName ); + if ( nPropId == -1 ) + throw RuntimeException(); + return nPropId; + } + + PropertyId PropertyHandler::impl_getPropertyId_nothrow( const OUString& _rPropertyName ) const + { + return m_pInfoService->getPropertyId( _rPropertyName ); + } + + void PropertyHandler::impl_setContextDocumentModified_nothrow() const + { + Reference< XModifiable > xModifiable( impl_getContextDocument_nothrow(), UNO_QUERY ); + if ( xModifiable.is() ) + xModifiable->setModified( true ); + } + + bool PropertyHandler::impl_componentHasProperty_throw( const OUString& _rPropName ) const + { + return m_xComponentPropertyInfo.is() && m_xComponentPropertyInfo->hasPropertyByName( _rPropName ); + } + + sal_Int16 PropertyHandler::impl_getDocumentMeasurementUnit_throw() const + { + FieldUnit eUnit = FieldUnit::NONE; + + Reference< XServiceInfo > xDocumentSI( impl_getContextDocument_nothrow(), UNO_QUERY ); + OSL_ENSURE( xDocumentSI.is(), "PropertyHandlerHelper::impl_getDocumentMeasurementUnit_throw: No context document - where do I live?" ); + if ( xDocumentSI.is() ) + { + // determine the application type we live in + OUString sConfigurationLocation; + OUString sConfigurationProperty; + if ( xDocumentSI->supportsService( SERVICE_WEB_DOCUMENT ) ) + { // writer + sConfigurationLocation = "/org.openoffice.Office.WriterWeb/Layout/Other"; + sConfigurationProperty = "MeasureUnit"; + } + else if ( xDocumentSI->supportsService( SERVICE_TEXT_DOCUMENT ) ) + { // writer + sConfigurationLocation = "/org.openoffice.Office.Writer/Layout/Other"; + sConfigurationProperty = "MeasureUnit"; + } + else if ( xDocumentSI->supportsService( SERVICE_SPREADSHEET_DOCUMENT ) ) + { // calc + sConfigurationLocation = "/org.openoffice.Office.Calc/Layout/Other/MeasureUnit"; + sConfigurationProperty = "Metric"; + } + else if ( xDocumentSI->supportsService( SERVICE_DRAWING_DOCUMENT ) ) + { + sConfigurationLocation = "/org.openoffice.Office.Draw/Layout/Other/MeasureUnit"; + sConfigurationProperty = "Metric"; + } + else if ( xDocumentSI->supportsService( SERVICE_PRESENTATION_DOCUMENT ) ) + { + sConfigurationLocation = "/org.openoffice.Office.Impress/Layout/Other/MeasureUnit"; + sConfigurationProperty = "Metric"; + } + + // read the measurement unit from the configuration + if ( !(sConfigurationLocation.isEmpty() || sConfigurationProperty.isEmpty()) ) + { + ::utl::OConfigurationTreeRoot aConfigTree( ::utl::OConfigurationTreeRoot::createWithComponentContext( + m_xContext, sConfigurationLocation, -1, ::utl::OConfigurationTreeRoot::CM_READONLY ) ); + sal_Int32 nUnitAsInt = sal_Int32(FieldUnit::NONE); + aConfigTree.getNodeValue( sConfigurationProperty ) >>= nUnitAsInt; + + // if this denotes a valid (and accepted) unit, then use it + if ( ( nUnitAsInt > sal_Int32(FieldUnit::NONE) ) && ( nUnitAsInt <= sal_Int32(FieldUnit::MM_100TH) ) ) + eUnit = static_cast< FieldUnit >( nUnitAsInt ); + } + } + + if ( FieldUnit::NONE == eUnit ) + { + MeasurementSystem eSystem = SvtSysLocale().GetLocaleData().getMeasurementSystemEnum(); + eUnit = MeasurementSystem::Metric == eSystem ? FieldUnit::CM : FieldUnit::INCH; + } + + return VCLUnoHelper::ConvertToMeasurementUnit( eUnit, 1 ); + } + + PropertyHandlerComponent::PropertyHandlerComponent( const Reference< XComponentContext >& _rxContext ) + :PropertyHandler( _rxContext ) + { + } + + IMPLEMENT_FORWARD_XINTERFACE2( PropertyHandlerComponent, PropertyHandler, PropertyHandlerComponent_Base ) + IMPLEMENT_FORWARD_XTYPEPROVIDER2( PropertyHandlerComponent, PropertyHandler, PropertyHandlerComponent_Base ) + + sal_Bool SAL_CALL PropertyHandlerComponent::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService(this, ServiceName); + } + +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/propertyhandler.hxx b/extensions/source/propctrlr/propertyhandler.hxx new file mode 100644 index 0000000000..3491a89be3 --- /dev/null +++ b/extensions/source/propctrlr/propertyhandler.hxx @@ -0,0 +1,369 @@ +/* -*- 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 . + */ + +#pragma once + +#include "pcrcommon.hxx" + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/beans/PropertyState.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/script/XTypeConverter.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/util/Time.hpp> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/inspection/XPropertyHandler.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/implbase1.hxx> +#include <comphelper/uno3.hxx> + +#include <memory> +#include <vector> + +namespace com::sun::star { + namespace inspection { + struct LineDescriptor; + class XPropertyControlFactory; + } +} + +namespace weld { class Window; } + +namespace pcr +{ + + + typedef sal_Int32 PropertyId; + + + //= PropertyHandler + + class OPropertyInfoService; + typedef ::cppu::WeakComponentImplHelper < css::inspection::XPropertyHandler + > PropertyHandler_Base; + /** the base class for property handlers + */ + class PropertyHandler : public ::cppu::BaseMutex + , public PropertyHandler_Base + { + private: + /// cache for getSupportedProperties + mutable StlSyntaxSequence< css::beans::Property > + m_aSupportedProperties; + mutable bool m_bSupportedPropertiesAreKnown; + + private: + /// the property listener which has been registered + PropertyChangeListeners m_aPropertyListeners; + + protected: + /// the context in which the instance was created + css::uno::Reference< css::uno::XComponentContext > m_xContext; + /// the component we're inspecting + css::uno::Reference< css::beans::XPropertySet > m_xComponent; + /// info about our component's properties + css::uno::Reference< css::beans::XPropertySetInfo > m_xComponentPropertyInfo; + /// type converter, needed on various occasions + css::uno::Reference< css::script::XTypeConverter > m_xTypeConverter; + /// access to property meta data + std::unique_ptr< OPropertyInfoService > m_pInfoService; + + protected: + explicit PropertyHandler( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext + ); + virtual ~PropertyHandler() override; + + // default implementations for XPropertyHandler + virtual void SAL_CALL inspect( const css::uno::Reference< css::uno::XInterface >& _rxIntrospectee ) override; + virtual css::uno::Sequence< css::beans::Property > SAL_CALL getSupportedProperties() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupersededProperties( ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getActuatingProperties( ) override; + virtual css::uno::Any SAL_CALL convertToPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rControlValue ) override; + virtual css::uno::Any SAL_CALL convertToControlValue( const OUString& _rPropertyName, const css::uno::Any& _rPropertyValue, const css::uno::Type& _rControlValueType ) override; + virtual css::beans::PropertyState SAL_CALL getPropertyState( const OUString& _rPropertyName ) override; + virtual css::inspection::LineDescriptor SAL_CALL describePropertyLine( const OUString& _rPropertyName, const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory ) override; + virtual sal_Bool SAL_CALL isComposable( const OUString& _rPropertyName ) override; + virtual css::inspection::InteractiveSelectionResult SAL_CALL onInteractivePropertySelection( const OUString& _rPropertyName, sal_Bool _bPrimary, css::uno::Any& _rData, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI ) override; + virtual void SAL_CALL actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const css::uno::Any& _rNewValue, const css::uno::Any& _rOldValue, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI, sal_Bool _bFirstTimeInit ) override; + virtual void SAL_CALL addPropertyChangeListener( const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ) override; + virtual sal_Bool SAL_CALL suspend( sal_Bool _bSuspend ) override; + + // XComponent + DECLARE_XCOMPONENT() + virtual void SAL_CALL disposing() override; + + // own overridables + virtual css::uno::Sequence< css::beans::Property > + doDescribeSupportedProperties() const = 0; + + /// called when XPropertyHandler::inspect has been called, and we thus have a new component to inspect + virtual void onNewComponent(); + + protected: + /** fires the change in a property value to our listener (if any) + @see addPropertyChangeListener + */ + void firePropertyChange( const OUString& _rPropName, PropertyId _nPropId, + const css::uno::Any& _rOldValue, const css::uno::Any& _rNewValue ); + + /** retrieves a window which can be used as parent for dialogs + */ + weld::Window* impl_getDefaultDialogFrame_nothrow() const; + + /** retrieves the property id for a given property name + @throw css::beans::UnknownPropertyException + if the property name is not known to our ->m_pInfoService + */ + PropertyId impl_getPropertyId_throwUnknownProperty( const OUString& _rPropertyName ) const; + + /** retrieves the property id for a given property name + @throw css::uno::RuntimeException + if the property name is not known to our ->m_pInfoService + */ + PropertyId impl_getPropertyId_throwRuntime( const OUString& _rPropertyName ) const; + + + /** retrieves the property id for a given property name + @returns -1 + if the property name is not known to our ->m_pInfoService + */ + PropertyId impl_getPropertyId_nothrow( const OUString& _rPropertyName ) const; + + // helper for implementing doDescribeSupportedProperties + /** adds a description for the given string property to the given property vector + Most probably to be called from within getSupportedProperties + */ + inline void addStringPropertyDescription( + std::vector< css::beans::Property >& _rProperties, + const OUString& _rPropertyName + ) const; + + /** adds a description for the given int32 property to the given property vector + */ + inline void addInt32PropertyDescription( + std::vector< css::beans::Property >& _rProperties, + const OUString& _rPropertyName, + sal_Int16 _nAttribs = 0 + ) const; + + /** adds a description for the given int16 property to the given property vector + */ + inline void addInt16PropertyDescription( + std::vector< css::beans::Property >& _rProperties, + const OUString& _rPropertyName, + sal_Int16 _nAttribs = 0 + ) const; + + /** adds a description for the given double property to the given property vector + */ + inline void addDoublePropertyDescription( + std::vector< css::beans::Property >& _rProperties, + const OUString& _rPropertyName, + sal_Int16 _nAttribs + ) const; + + /** adds a description for the given date property to the given property vector + */ + inline void addDatePropertyDescription( + std::vector< css::beans::Property >& _rProperties, + const OUString& _rPropertyName, + sal_Int16 _nAttribs + ) const; + + /** adds a description for the given time property to the given property vector + */ + inline void addTimePropertyDescription( + std::vector< css::beans::Property >& _rProperties, + const OUString& _rPropertyName, + sal_Int16 _nAttribs + ) const; + + /** adds a description for the given DateTime property to the given property vector + */ + inline void addDateTimePropertyDescription( + std::vector< css::beans::Property >& _rProperties, + const OUString& _rPropertyName, + sal_Int16 _nAttribs + ) const; + + /// adds a Property, given by name only, to a given vector of Properties + void implAddPropertyDescription( + std::vector< css::beans::Property >& _rProperties, + const OUString& _rPropertyName, + const css::uno::Type& _rType, + sal_Int16 _nAttribs = 0 + ) const; + + + // helper for accessing and maintaining meta data about our supported properties + + /** retrieves a property given by handle + + @return + a pointer to the descriptor for the given properties, if it is one of our + supported properties, <NULL/> else. + + @see doDescribeSupportedProperties + @see impl_getPropertyFromId_throw + */ + const css::beans::Property* + impl_getPropertyFromId_nothrow( PropertyId _nPropId ) const; + + /** retrieves a property given by handle + + @throws UnknownPropertyException + if the handler does not support a property with the given handle + + @seealso doDescribeSupportedProperties + @see impl_getPropertyFromId_nothrow + */ + const css::beans::Property& + impl_getPropertyFromId_throw( PropertyId _nPropId ) const; + + /** determines whether a given property id is part of our supported properties + @see getSupportedProperties + @see doDescribeSupportedProperties + */ + bool impl_isSupportedProperty_nothrow( PropertyId _nPropId ) const + { + return impl_getPropertyFromId_nothrow( _nPropId ) != nullptr; + } + + /** retrieves a property given by name + + @throws UnknownPropertyException + if the handler does not support a property with the given name + + @seealso doDescribeSupportedProperties + */ + const css::beans::Property& + impl_getPropertyFromName_throw( const OUString& _rPropertyName ) const; + + /** get the name of a property given by handle + */ + inline OUString + impl_getPropertyNameFromId_nothrow( PropertyId _nPropId ) const; + + /** returns the value of the ContextDocument property in the ComponentContext which was used to create + this handler. + */ + css::uno::Reference< css::frame::XModel > + impl_getContextDocument_nothrow() const + { + return css::uno::Reference< css::frame::XModel >( + m_xContext->getValueByName( "ContextDocument" ), css::uno::UNO_QUERY ); + } + + /** marks the context document as modified + + @see impl_getContextDocument_nothrow + */ + void impl_setContextDocumentModified_nothrow() const; + + /// determines whether our component has a given property + bool impl_componentHasProperty_throw( const OUString& _rPropName ) const; + + /** determines the default measure unit for the document in which our component lives + */ + sal_Int16 impl_getDocumentMeasurementUnit_throw() const; + + private: + PropertyHandler( const PropertyHandler& ) = delete; + PropertyHandler& operator=( const PropertyHandler& ) = delete; + }; + + + inline void PropertyHandler::addStringPropertyDescription( std::vector< css::beans::Property >& _rProperties, const OUString& _rPropertyName ) const + { + implAddPropertyDescription( _rProperties, _rPropertyName, ::cppu::UnoType<OUString>::get() ); + } + + inline void PropertyHandler::addInt32PropertyDescription( std::vector< css::beans::Property >& _rProperties, const OUString& _rPropertyName, sal_Int16 _nAttribs ) const + { + implAddPropertyDescription( _rProperties, _rPropertyName, ::cppu::UnoType<sal_Int32>::get(), _nAttribs ); + } + + inline void PropertyHandler::addInt16PropertyDescription( std::vector< css::beans::Property >& _rProperties, const OUString& _rPropertyName, sal_Int16 _nAttribs ) const + { + implAddPropertyDescription( _rProperties, _rPropertyName, ::cppu::UnoType<sal_Int16>::get(), _nAttribs ); + } + + inline void PropertyHandler::addDoublePropertyDescription( std::vector< css::beans::Property >& _rProperties, const OUString& _rPropertyName, sal_Int16 _nAttribs ) const + { + implAddPropertyDescription( _rProperties, _rPropertyName, ::cppu::UnoType<double>::get(), _nAttribs ); + } + + inline void PropertyHandler::addDatePropertyDescription( std::vector< css::beans::Property >& _rProperties, const OUString& _rPropertyName, sal_Int16 _nAttribs ) const + { + implAddPropertyDescription( _rProperties, _rPropertyName, ::cppu::UnoType<css::util::Date>::get(), _nAttribs ); + } + + inline void PropertyHandler::addTimePropertyDescription( std::vector< css::beans::Property >& _rProperties, const OUString& _rPropertyName, sal_Int16 _nAttribs ) const + { + implAddPropertyDescription( _rProperties, _rPropertyName, ::cppu::UnoType<css::util::Time>::get(), _nAttribs ); + } + + inline void PropertyHandler::addDateTimePropertyDescription( std::vector< css::beans::Property >& _rProperties, const OUString& _rPropertyName, sal_Int16 _nAttribs ) const + { + implAddPropertyDescription( _rProperties, _rPropertyName, ::cppu::UnoType<css::util::DateTime>::get(), _nAttribs ); + } + + inline OUString PropertyHandler::impl_getPropertyNameFromId_nothrow( PropertyId _nPropId ) const + { + const css::beans::Property* pProp = impl_getPropertyFromId_nothrow( _nPropId ); + return pProp ? pProp->Name : OUString(); + } + + + //= PropertyHandlerComponent + + typedef ::cppu::ImplHelper1 < css::lang::XServiceInfo + > PropertyHandlerComponent_Base; + /** PropertyHandler implementation which additionally supports XServiceInfo + */ + class PropertyHandlerComponent :public PropertyHandler + ,public PropertyHandlerComponent_Base + { + protected: + explicit PropertyHandlerComponent( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext + ); + + DECLARE_XINTERFACE() + DECLARE_XTYPEPROVIDER() + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override = 0; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) final override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override = 0; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/propertyinfo.hxx b/extensions/source/propctrlr/propertyinfo.hxx new file mode 100644 index 0000000000..592557c2a3 --- /dev/null +++ b/extensions/source/propctrlr/propertyinfo.hxx @@ -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 . + */ + +#pragma once + +#include <sal/types.h> +#include <rtl/ustring.hxx> +#include <vector> + + +namespace pcr +{ + + + //= IPropertyInfoService + + class SAL_NO_VTABLE IPropertyInfoService + { + public: + virtual sal_Int32 getPropertyId(const OUString& _rName) const = 0; + virtual OUString getPropertyTranslation(sal_Int32 _nId) const = 0; + virtual OUString getPropertyHelpId(sal_Int32 _nId) const = 0; + virtual sal_Int16 getPropertyPos(sal_Int32 _nId) const = 0; + virtual sal_uInt32 getPropertyUIFlags(sal_Int32 _nId) const = 0; + virtual std::vector< OUString > getPropertyEnumRepresentations(sal_Int32 _nId) const = 0; + + virtual ~IPropertyInfoService() { } + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/propeventtranslation.cxx b/extensions/source/propctrlr/propeventtranslation.cxx new file mode 100644 index 0000000000..736c1e06fc --- /dev/null +++ b/extensions/source/propctrlr/propeventtranslation.cxx @@ -0,0 +1,89 @@ +/* -*- 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 "propeventtranslation.hxx" + +#include <com/sun/star/lang/DisposedException.hpp> + + +namespace pcr +{ + + + using ::com::sun::star::beans::PropertyChangeEvent; + using ::com::sun::star::uno::RuntimeException; + using ::com::sun::star::lang::EventObject; + using ::com::sun::star::uno::Reference; + using ::com::sun::star::beans::XPropertyChangeListener; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::lang::DisposedException; + + + //= PropertyEventTranslation + + + PropertyEventTranslation::PropertyEventTranslation( const Reference< XPropertyChangeListener >& _rxDelegator, + const Reference< XInterface >& _rxTranslatedEventSource ) + :m_xDelegator( _rxDelegator ) + ,m_xTranslatedEventSource( _rxTranslatedEventSource ) + { + if ( !m_xDelegator.is() ) + throw RuntimeException(); + } + + + void SAL_CALL PropertyEventTranslation::propertyChange( const PropertyChangeEvent& evt ) + { + if ( !m_xDelegator.is() ) + throw DisposedException(); + + if ( !m_xTranslatedEventSource.is() ) + m_xDelegator->propertyChange( evt ); + else + { + PropertyChangeEvent aTranslatedEvent( evt ); + aTranslatedEvent.Source = m_xTranslatedEventSource; + m_xDelegator->propertyChange( aTranslatedEvent ); + } + } + + + void SAL_CALL PropertyEventTranslation::disposing( const EventObject& Source ) + { + if ( !m_xDelegator.is() ) + throw DisposedException(); + + if ( !m_xTranslatedEventSource.is() ) + m_xDelegator->disposing( Source ); + else + { + EventObject aTranslatedEvent( Source ); + aTranslatedEvent.Source = m_xTranslatedEventSource; + m_xDelegator->disposing( aTranslatedEvent ); + } + + m_xDelegator.clear(); + m_xTranslatedEventSource.clear(); + } + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/propeventtranslation.hxx b/extensions/source/propctrlr/propeventtranslation.hxx new file mode 100644 index 0000000000..43155b8c67 --- /dev/null +++ b/extensions/source/propctrlr/propeventtranslation.hxx @@ -0,0 +1,70 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/beans/XPropertyChangeListener.hpp> +#include <cppuhelper/implbase.hxx> + + +namespace pcr +{ + + + //= PropertyEventTranslation + + typedef ::cppu::WeakImplHelper < css::beans::XPropertyChangeListener + > PropertyEventTranslation_Base; + + class PropertyEventTranslation : public PropertyEventTranslation_Base + { + css::uno::Reference< css::beans::XPropertyChangeListener > + m_xDelegator; + css::uno::Reference< css::uno::XInterface > + m_xTranslatedEventSource; + + public: + /** constructs the object + @throws NullPointerException + if <arg>_rxDelegator</arg> is <NULL/> + */ + PropertyEventTranslation( + const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxDelegator, + const css::uno::Reference< css::uno::XInterface >& _rxTranslatedEventSource + ); + + const css::uno::Reference< css::beans::XPropertyChangeListener >& + getDelegator() const { return m_xDelegator; } + + protected: + // XPropertyChangeListener + virtual void SAL_CALL propertyChange( const css::beans::PropertyChangeEvent& evt ) override; + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + private: + PropertyEventTranslation( const PropertyEventTranslation& ) = delete; + PropertyEventTranslation& operator=( const PropertyEventTranslation& ) = delete; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/proplinelistener.hxx b/extensions/source/propctrlr/proplinelistener.hxx new file mode 100644 index 0000000000..6931a1f33b --- /dev/null +++ b/extensions/source/propctrlr/proplinelistener.hxx @@ -0,0 +1,43 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Any.hxx> + +namespace pcr +{ + + + class IPropertyLineListener + { + public: + virtual void Clicked( const OUString& _rName, bool _bPrimary ) = 0; + virtual void Commit( const OUString& _rName, const css::uno::Any& _rVal ) = 0; + + protected: + ~IPropertyLineListener() {} + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/pushbuttonnavigation.cxx b/extensions/source/propctrlr/pushbuttonnavigation.cxx new file mode 100644 index 0000000000..130cf322d1 --- /dev/null +++ b/extensions/source/propctrlr/pushbuttonnavigation.cxx @@ -0,0 +1,298 @@ +/* -*- 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 "pushbuttonnavigation.hxx" +#include <com/sun/star/beans/XPropertyState.hpp> +#include "formstrings.hxx" +#include <comphelper/extract.hxx> +#include <comphelper/property.hxx> +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> +#include <comphelper/diagnose_ex.hxx> + + +namespace pcr +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::form; + + + namespace + { + const sal_Int32 s_nFirstVirtualButtonType = 1 + sal_Int32(FormButtonType_URL); + + const char* pNavigationURLs[] = + { + ".uno:FormController/moveToFirst", + ".uno:FormController/moveToPrev", + ".uno:FormController/moveToNext", + ".uno:FormController/moveToLast", + ".uno:FormController/saveRecord", + ".uno:FormController/undoRecord", + ".uno:FormController/moveToNew", + ".uno:FormController/deleteRecord", + ".uno:FormController/refreshForm", + nullptr + }; + + sal_Int32 lcl_getNavigationURLIndex( std::u16string_view _rNavURL ) + { + const char** pLookup = pNavigationURLs; + while ( *pLookup ) + { + if ( o3tl::equalsAscii( _rNavURL, *pLookup ) ) + return pLookup - pNavigationURLs; + ++pLookup; + } + return -1; + } + + const char* lcl_getNavigationURL( sal_Int32 _nButtonTypeIndex ) + { + const char** pLookup = pNavigationURLs; + while ( _nButtonTypeIndex-- && *pLookup++ ) + ; + OSL_ENSURE( *pLookup, "lcl_getNavigationURL: invalid index!" ); + return *pLookup; + } + } + + + //= PushButtonNavigation + + + PushButtonNavigation::PushButtonNavigation( const Reference< XPropertySet >& _rxControlModel ) + :m_xControlModel( _rxControlModel ) + ,m_bIsPushButton( false ) + { + OSL_ENSURE( m_xControlModel.is(), "PushButtonNavigation::PushButtonNavigation: invalid control model!" ); + + try + { + m_bIsPushButton = ::comphelper::hasProperty( PROPERTY_BUTTONTYPE, m_xControlModel ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "PushButtonNavigation::PushButtonNavigation" ); + } + } + + + FormButtonType PushButtonNavigation::implGetCurrentButtonType() const + { + sal_Int32 nButtonType = sal_Int32(FormButtonType_PUSH); + if ( !m_xControlModel.is() ) + return static_cast<FormButtonType>(nButtonType); + OSL_VERIFY( ::cppu::enum2int( nButtonType, m_xControlModel->getPropertyValue( PROPERTY_BUTTONTYPE ) ) ); + + if ( nButtonType == sal_Int32(FormButtonType_URL) ) + { + // there's a chance that this is a "virtual" button type + // (which are realized by special URLs) + OUString sTargetURL; + m_xControlModel->getPropertyValue( PROPERTY_TARGET_URL ) >>= sTargetURL; + + sal_Int32 nNavigationURLIndex = lcl_getNavigationURLIndex( sTargetURL ); + if ( nNavigationURLIndex >= 0) + // it actually *is* a virtual button type + nButtonType = s_nFirstVirtualButtonType + nNavigationURLIndex; + } + return static_cast<FormButtonType>(nButtonType); + } + + + Any PushButtonNavigation::getCurrentButtonType() const + { + OSL_ENSURE( m_bIsPushButton, "PushButtonNavigation::getCurrentButtonType: not expected to be called for forms!" ); + Any aReturn; + + try + { + aReturn <<= implGetCurrentButtonType(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "PushButtonNavigation::getCurrentButtonType" ); + } + return aReturn; + } + + + void PushButtonNavigation::setCurrentButtonType( const Any& _rValue ) const + { + OSL_ENSURE( m_bIsPushButton, "PushButtonNavigation::setCurrentButtonType: not expected to be called for forms!" ); + if ( !m_xControlModel.is() ) + return; + + try + { + sal_Int32 nButtonType = sal_Int32(FormButtonType_PUSH); + OSL_VERIFY( ::cppu::enum2int( nButtonType, _rValue ) ); + OUString sTargetURL; + + bool bIsVirtualButtonType = nButtonType >= s_nFirstVirtualButtonType; + if ( bIsVirtualButtonType ) + { + const char* pURL = lcl_getNavigationURL( nButtonType - s_nFirstVirtualButtonType ); + sTargetURL = OUString::createFromAscii( pURL ); + + nButtonType = sal_Int32(FormButtonType_URL); + } + + m_xControlModel->setPropertyValue( PROPERTY_BUTTONTYPE, Any( static_cast< FormButtonType >( nButtonType ) ) ); + m_xControlModel->setPropertyValue( PROPERTY_TARGET_URL, Any( sTargetURL ) ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "PushButtonNavigation::setCurrentButtonType" ); + } + } + + + PropertyState PushButtonNavigation::getCurrentButtonTypeState( ) const + { + OSL_ENSURE( m_bIsPushButton, "PushButtonNavigation::getCurrentButtonTypeState: not expected to be called for forms!" ); + PropertyState eState = PropertyState_DIRECT_VALUE; + + try + { + Reference< XPropertyState > xStateAccess( m_xControlModel, UNO_QUERY ); + if ( xStateAccess.is() ) + { + // let's see what the model says about the ButtonType property + eState = xStateAccess->getPropertyState( PROPERTY_BUTTONTYPE ); + if ( eState == PropertyState_DIRECT_VALUE ) + { + sal_Int32 nRealButtonType = sal_Int32(FormButtonType_PUSH); + OSL_VERIFY( ::cppu::enum2int( nRealButtonType, m_xControlModel->getPropertyValue( PROPERTY_BUTTONTYPE ) ) ); + // perhaps it's one of the virtual button types? + if ( sal_Int32(FormButtonType_URL) == nRealButtonType ) + { + // yes, it is -> rely on the state of the URL property + eState = xStateAccess->getPropertyState( PROPERTY_TARGET_URL ); + } + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "PushButtonNavigation::getCurrentButtonTypeState" ); + } + + return eState; + } + + + Any PushButtonNavigation::getCurrentTargetURL() const + { + Any aReturn; + if ( !m_xControlModel.is() ) + return aReturn; + + try + { + aReturn = m_xControlModel->getPropertyValue( PROPERTY_TARGET_URL ); + if ( m_bIsPushButton ) + { + FormButtonType nCurrentButtonType = implGetCurrentButtonType(); + bool bIsVirtualButtonType = nCurrentButtonType >= FormButtonType(s_nFirstVirtualButtonType); + if ( bIsVirtualButtonType ) + { + // pretend (to the user) that there's no URL set - since + // virtual button types imply a special (technical) URL which + // the user should not see + aReturn <<= OUString(); + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "PushButtonNavigation::getCurrentTargetURL" ); + } + return aReturn; + } + + + void PushButtonNavigation::setCurrentTargetURL( const Any& _rValue ) const + { + if ( !m_xControlModel.is() ) + return; + + try + { + m_xControlModel->setPropertyValue( PROPERTY_TARGET_URL, _rValue ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "PushButtonNavigation::setCurrentTargetURL" ); + } + } + + + PropertyState PushButtonNavigation::getCurrentTargetURLState( ) const + { + PropertyState eState = PropertyState_DIRECT_VALUE; + + try + { + Reference< XPropertyState > xStateAccess( m_xControlModel, UNO_QUERY ); + if ( xStateAccess.is() ) + { + eState = xStateAccess->getPropertyState( PROPERTY_TARGET_URL ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "PushButtonNavigation::setCurrentTargetURL" ); + } + + return eState; + } + + + bool PushButtonNavigation::currentButtonTypeIsOpenURL() const + { + FormButtonType nButtonType( FormButtonType_PUSH ); + try + { + nButtonType = implGetCurrentButtonType(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + return nButtonType == FormButtonType_URL; + } + + + bool PushButtonNavigation::hasNonEmptyCurrentTargetURL() const + { + OUString sTargetURL; + OSL_VERIFY( getCurrentTargetURL() >>= sTargetURL ); + return !sTargetURL.isEmpty(); + } + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/pushbuttonnavigation.hxx b/extensions/source/propctrlr/pushbuttonnavigation.hxx new file mode 100644 index 0000000000..7248fb27d4 --- /dev/null +++ b/extensions/source/propctrlr/pushbuttonnavigation.hxx @@ -0,0 +1,98 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyState.hpp> +#include <com/sun/star/form/FormButtonType.hpp> + + +namespace pcr +{ + + + //= PushButtonNavigation + + class PushButtonNavigation final + { + css::uno::Reference< css::beans::XPropertySet > + m_xControlModel; + bool m_bIsPushButton; + + public: + /** ctor + @param _rxControlModel + the control model which is or will be bound + */ + explicit PushButtonNavigation( + const css::uno::Reference< css::beans::XPropertySet >& _rxControlModel + ); + + /** returns the current value of the "ButtonType" property, taking into account + the "virtual" button types such as "move-to-next-record button". + */ + css::uno::Any + getCurrentButtonType() const; + + /** sets the current value of the "ButtonType" property, taking into account + the "virtual" button types such as "move-to-next-record button". + */ + void setCurrentButtonType( const css::uno::Any& _rValue ) const; + + /** retrieves the current state of the "ButtonType" property, taking into account + the "virtual" button types such as "move-to-next-record button". + */ + css::beans::PropertyState + getCurrentButtonTypeState( ) const; + + /** returns the current value of the "TargetURL" property, taking into account + that some URLs are special values caused by "virtual" ButtonTypes + */ + css::uno::Any + getCurrentTargetURL() const; + + /** sets the current value of the "TargetURL" property, taking into account + that some URLs are special values caused by "virtual" ButtonTypes + */ + void setCurrentTargetURL( const css::uno::Any& _rValue ) const; + + /** retrieves the current state of the "TargetURL" property, taking into account + that some URLs are special values caused by "virtual" ButtonTypes + */ + css::beans::PropertyState + getCurrentTargetURLState( ) const; + + /** determines whether the current button type is FormButtonType_URL + */ + bool currentButtonTypeIsOpenURL() const; + + /** determines whether the TargetURL property does currently denote a non-empty string + */ + bool hasNonEmptyCurrentTargetURL() const; + + private: + css::form::FormButtonType implGetCurrentButtonType() const; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/selectlabeldialog.cxx b/extensions/source/propctrlr/selectlabeldialog.cxx new file mode 100644 index 0000000000..706e6eb3c8 --- /dev/null +++ b/extensions/source/propctrlr/selectlabeldialog.cxx @@ -0,0 +1,279 @@ +/* -*- 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 "selectlabeldialog.hxx" +#include <strings.hrc> +#include <bitmaps.hlst> +#include "formbrowsertools.hxx" +#include "formstrings.hxx" +#include "modulepcr.hxx" +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <comphelper/property.hxx> +#include <comphelper/types.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> + + +namespace pcr +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::form; + using namespace ::com::sun::star::sdbc; + using namespace ::com::sun::star::lang; + + + // OSelectLabelDialog + OSelectLabelDialog::OSelectLabelDialog(weld::Window* pParent, Reference< XPropertySet > const & _xControlModel) + : GenericDialogController(pParent, "modules/spropctrlr/ui/labelselectiondialog.ui", "LabelSelectionDialog") + , m_xControlModel(_xControlModel) + , m_bLastSelected(false) + , m_bHaveAssignableControl(false) + , m_xMainDesc(m_xBuilder->weld_label("label")) + , m_xControlTree(m_xBuilder->weld_tree_view("control")) + , m_xScratchIter(m_xControlTree->make_iterator()) + , m_xNoAssignment(m_xBuilder->weld_check_button("noassignment")) + { + m_xControlTree->connect_changed(LINK(this, OSelectLabelDialog, OnEntrySelected)); + m_xControlTree->set_size_request(-1, m_xControlTree->get_height_rows(8)); + + // fill the description + OUString sDescription = m_xMainDesc->get_label(); + sal_Int16 nClassID = FormComponentType::CONTROL; + if (::comphelper::hasProperty(PROPERTY_CLASSID, m_xControlModel)) + nClassID = ::comphelper::getINT16(m_xControlModel->getPropertyValue(PROPERTY_CLASSID)); + + sDescription = sDescription.replaceAll("$controlclass$", + GetUIHeadlineName(nClassID, Any(m_xControlModel))); + OUString sName = ::comphelper::getString(m_xControlModel->getPropertyValue(PROPERTY_NAME)); + sDescription = sDescription.replaceAll("$controlname$", sName); + m_xMainDesc->set_label(sDescription); + + // search for the root of the form hierarchy + Reference< XChild > xCont(m_xControlModel, UNO_QUERY); + Reference< XInterface > xSearch( xCont.is() ? xCont->getParent() : Reference< XInterface > ()); + Reference< XResultSet > xParentAsResultSet(xSearch, UNO_QUERY); + while (xParentAsResultSet.is()) + { + xCont.set(xSearch, UNO_QUERY); + xSearch = xCont.is() ? xCont->getParent() : Reference< XInterface > (); + xParentAsResultSet.set(xSearch, UNO_QUERY); + } + + // and insert all entries below this root into the listbox + if (xSearch.is()) + { + // check which service the allowed components must support + sal_Int16 nClassId = 0; + try { nClassId = ::comphelper::getINT16(m_xControlModel->getPropertyValue(PROPERTY_CLASSID)); } catch(...) { } + m_sRequiredService = (FormComponentType::RADIOBUTTON == nClassId) ? SERVICE_COMPONENT_GROUPBOX : SERVICE_COMPONENT_FIXEDTEXT; + m_aRequiredControlImage = (FormComponentType::RADIOBUTTON == nClassId) ? RID_EXTBMP_GROUPBOX : RID_EXTBMP_FIXEDTEXT; + + // calc the currently set label control (so InsertEntries can calc m_xInitialSelection) + Any aCurrentLabelControl( m_xControlModel->getPropertyValue(PROPERTY_CONTROLLABEL) ); + DBG_ASSERT((aCurrentLabelControl.getValueTypeClass() == TypeClass_INTERFACE) || !aCurrentLabelControl.hasValue(), + + "OSelectLabelDialog::OSelectLabelDialog : invalid ControlLabel property !"); + if (aCurrentLabelControl.hasValue()) + aCurrentLabelControl >>= m_xInitialLabelControl; + + // insert the root + OUString sRootName(PcrRes(RID_STR_FORMS)); + m_xControlTree->insert(nullptr, -1, &sRootName, nullptr, + nullptr, nullptr, false, m_xScratchIter.get()); + m_xControlTree->set_image(*m_xScratchIter, RID_EXTBMP_FORMS); + + // build the tree + m_xInitialSelection.reset(); + m_bHaveAssignableControl = false; + std::unique_ptr<weld::TreeIter> xRoot = m_xControlTree->make_iterator(); + m_xControlTree->get_iter_first(*xRoot); + InsertEntries(xSearch, *xRoot); + m_xControlTree->expand_row(*xRoot); + } + + if (m_xInitialSelection) + { + m_xControlTree->scroll_to_row(*m_xInitialSelection); + m_xControlTree->select(*m_xInitialSelection); + } + else + { + m_xControlTree->scroll_to_row(0); + m_xControlTree->unselect_all(); + m_xNoAssignment->set_active(true); + } + + if (!m_bHaveAssignableControl) + { // no controls which can be assigned + m_xNoAssignment->set_active(true); + m_xNoAssignment->set_sensitive(false); + } + + m_xLastSelected = m_xControlTree->make_iterator(nullptr); + + m_xNoAssignment->connect_toggled(LINK(this, OSelectLabelDialog, OnNoAssignmentClicked)); + OnNoAssignmentClicked(*m_xNoAssignment); + } + + OSelectLabelDialog::~OSelectLabelDialog() + { + } + + sal_Int32 OSelectLabelDialog::InsertEntries(const Reference< XInterface > & _xContainer, const weld::TreeIter& rContainerEntry) + { + Reference< XIndexAccess > xContainer(_xContainer, UNO_QUERY); + if (!xContainer.is()) + return 0; + + sal_Int32 nChildren = 0; + OUString sName; + Reference< XPropertySet > xAsSet; + for (sal_Int32 i=0; i<xContainer->getCount(); ++i) + { + xContainer->getByIndex(i) >>= xAsSet; + if (!xAsSet.is()) + { + SAL_INFO("extensions.propctrlr", "OSelectLabelDialog::InsertEntries : strange : a form component which isn't a property set !"); + continue; + } + + if (!::comphelper::hasProperty(PROPERTY_NAME, xAsSet)) + // we need at least a name for displaying ... + continue; + sName = ::comphelper::getString(xAsSet->getPropertyValue(PROPERTY_NAME)); + + // we need to check if the control model supports the required service + Reference< XServiceInfo > xInfo(xAsSet, UNO_QUERY); + if (!xInfo.is()) + continue; + + if (!xInfo->supportsService(m_sRequiredService)) + { // perhaps it is a container + Reference< XIndexAccess > xCont(xAsSet, UNO_QUERY); + if (xCont.is() && xCont->getCount()) + { // yes -> step down + m_xControlTree->insert(&rContainerEntry, -1, &sName, nullptr, + nullptr, nullptr, false, m_xScratchIter.get()); + m_xControlTree->set_image(*m_xScratchIter, RID_EXTBMP_FORM); + auto xIter = m_xControlTree->make_iterator(&rContainerEntry); + m_xControlTree->iter_nth_child(*xIter, nChildren); + sal_Int32 nContChildren = InsertEntries(xCont, *xIter); + if (nContChildren) + { + m_xControlTree->expand_row(*xIter); + ++nChildren; + } + else + { // oops, no valid children -> remove the entry + m_xControlTree->remove(*xIter); + } + } + continue; + } + + // get the label + if (!::comphelper::hasProperty(PROPERTY_LABEL, xAsSet)) + continue; + + OUString sDisplayName = + ::comphelper::getString(xAsSet->getPropertyValue(PROPERTY_LABEL)) + + " (" + sName + ")"; + + // all requirements met -> insert + m_xUserData.emplace_back(new Reference<XPropertySet>(xAsSet)); + OUString sId(weld::toId(m_xUserData.back().get())); + m_xControlTree->insert(&rContainerEntry, -1, &sDisplayName, &sId, nullptr, nullptr, false, m_xScratchIter.get()); + m_xControlTree->set_image(*m_xScratchIter, m_aRequiredControlImage); + + if (m_xInitialLabelControl == xAsSet) + { + m_xInitialSelection = m_xControlTree->make_iterator(&rContainerEntry); + m_xControlTree->iter_nth_child(*m_xInitialSelection, nChildren); + } + + ++nChildren; + m_bHaveAssignableControl = true; + } + + return nChildren; + } + + IMPL_LINK(OSelectLabelDialog, OnEntrySelected, weld::TreeView&, rLB, void) + { + DBG_ASSERT(&rLB == m_xControlTree.get(), "OSelectLabelDialog::OnEntrySelected : where did this come from ?"); + std::unique_ptr<weld::TreeIter> xIter = m_xControlTree->make_iterator(); + bool bSelected = m_xControlTree->get_selected(xIter.get()); + OUString sData = bSelected ? m_xControlTree->get_id(*xIter) : OUString(); + if (!sData.isEmpty()) + m_xSelectedControl.set(*weld::fromId<Reference<XPropertySet>*>(sData)); + m_xNoAssignment->set_active(sData.isEmpty()); + } + + IMPL_LINK(OSelectLabelDialog, OnNoAssignmentClicked, weld::Toggleable&, rButton, void) + { + DBG_ASSERT(&rButton == m_xNoAssignment.get(), "OSelectLabelDialog::OnNoAssignmentClicked : where did this come from ?"); + + if (m_xNoAssignment->get_active()) + { + m_bLastSelected = m_xControlTree->get_selected(m_xLastSelected.get()); + } + else + { + DBG_ASSERT(m_bHaveAssignableControl, "OSelectLabelDialog::OnNoAssignmentClicked"); + // search the first assignable entry + auto xSearch = m_xControlTree->make_iterator(nullptr); + bool bSearch = m_xControlTree->get_iter_first(*xSearch); + while (bSearch) + { + if (m_xControlTree->get_id(*xSearch).toInt64()) + break; + bSearch = m_xControlTree->iter_next(*xSearch); + } + // and select it + if (bSearch) + { + m_xControlTree->copy_iterator(*xSearch, *m_xLastSelected); + m_xControlTree->select(*m_xLastSelected); + m_bLastSelected = true; + } + } + + if (m_bLastSelected) + { + if (!m_xNoAssignment->get_active()) + m_xControlTree->select(*m_xLastSelected); + else + m_xControlTree->unselect(*m_xLastSelected); + } + } +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/selectlabeldialog.hxx b/extensions/source/propctrlr/selectlabeldialog.hxx new file mode 100644 index 0000000000..9affa3512a --- /dev/null +++ b/extensions/source/propctrlr/selectlabeldialog.hxx @@ -0,0 +1,62 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/weld.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> + +namespace pcr +{ + // OSelectLabelDialog + class OSelectLabelDialog final : public weld::GenericDialogController + { + css::uno::Reference< css::beans::XPropertySet > m_xControlModel; + OUString m_sRequiredService; + OUString m_aRequiredControlImage; + std::unique_ptr<weld::TreeIter> m_xInitialSelection; + // the entry data of the listbox entries + std::vector<std::unique_ptr<css::uno::Reference<css::beans::XPropertySet>>> m_xUserData; + css::uno::Reference< css::beans::XPropertySet > m_xInitialLabelControl; + + css::uno::Reference< css::beans::XPropertySet > m_xSelectedControl; + std::unique_ptr<weld::TreeIter> m_xLastSelected; + bool m_bLastSelected; + bool m_bHaveAssignableControl; + + std::unique_ptr<weld::Label> m_xMainDesc; + std::unique_ptr<weld::TreeView> m_xControlTree; + std::unique_ptr<weld::TreeIter> m_xScratchIter; + std::unique_ptr<weld::CheckButton> m_xNoAssignment; + + public: + OSelectLabelDialog(weld::Window* pParent, css::uno::Reference< css::beans::XPropertySet > const & _xControlModel); + virtual ~OSelectLabelDialog() override; + + css::uno::Reference< css::beans::XPropertySet > GetSelected() const { return m_xNoAssignment->get_active() ? css::uno::Reference< css::beans::XPropertySet > () : m_xSelectedControl; } + + private: + sal_Int32 InsertEntries(const css::uno::Reference< css::uno::XInterface >& _xContainer, const weld::TreeIter& rContainerEntry); + + DECL_LINK(OnEntrySelected, weld::TreeView&, void); + DECL_LINK(OnNoAssignmentClicked, weld::Toggleable&, void); + }; +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/sqlcommanddesign.cxx b/extensions/source/propctrlr/sqlcommanddesign.cxx new file mode 100644 index 0000000000..d98b2a5692 --- /dev/null +++ b/extensions/source/propctrlr/sqlcommanddesign.cxx @@ -0,0 +1,358 @@ +/* -*- 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 "sqlcommanddesign.hxx" +#include "formstrings.hxx" +#include <command.hrc> +#include "modulepcr.hxx" +#include "unourl.hxx" + +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XTitle.hpp> +#include <com/sun/star/frame/XComponentLoader.hpp> +#include <com/sun/star/lang/NullPointerException.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/frame/FrameSearchFlag.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/sdb/CommandType.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <utility> +#include <comphelper/diagnose_ex.hxx> +#include <osl/diagnose.h> + + +namespace pcr +{ + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::beans::PropertyChangeEvent; + using ::com::sun::star::uno::RuntimeException; + using ::com::sun::star::frame::XFrame; + using ::com::sun::star::awt::XTopWindow; + using ::com::sun::star::awt::XWindow; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::beans::PropertyValue; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::lang::XComponent; + using ::com::sun::star::frame::XComponentLoader; + using ::com::sun::star::beans::XPropertySet; + using ::com::sun::star::frame::XTitle; + using ::com::sun::star::lang::EventObject; + using ::com::sun::star::lang::NullPointerException; + using ::com::sun::star::lang::DisposedException; + using ::com::sun::star::uno::XComponentContext; + using ::com::sun::star::frame::XFrames; + using ::com::sun::star::util::XCloseable; + using ::com::sun::star::lang::XMultiServiceFactory; + using ::com::sun::star::frame::XDispatchProvider; + using ::com::sun::star::frame::XDispatch; + using ::com::sun::star::frame::Desktop; + using ::com::sun::star::frame::XDesktop2; + + namespace FrameSearchFlag = ::com::sun::star::frame::FrameSearchFlag; + namespace CommandType = ::com::sun::star::sdb::CommandType; + + + //= ISQLCommandAdapter + + + ISQLCommandAdapter::~ISQLCommandAdapter() + { + } + + + //= SQLCommandDesigner + + + SQLCommandDesigner::SQLCommandDesigner( const Reference< XComponentContext >& _rxContext, + const ::rtl::Reference< ISQLCommandAdapter >& _rxPropertyAdapter, + ::dbtools::SharedConnection _aConnection, const Link<SQLCommandDesigner&,void>& _rCloseLink ) + :m_xContext( _rxContext ) + ,m_xConnection(std::move( _aConnection )) + ,m_xObjectAdapter( _rxPropertyAdapter ) + ,m_aCloseLink( _rCloseLink ) + { + if ( m_xContext.is() ) + m_xORB = m_xContext->getServiceManager(); + if ( !m_xORB.is() || !_rxPropertyAdapter.is() || !m_xConnection.is() ) + throw NullPointerException(); + + impl_doOpenDesignerFrame_nothrow(); + } + + + SQLCommandDesigner::~SQLCommandDesigner() + { + } + + + void SAL_CALL SQLCommandDesigner::propertyChange( const PropertyChangeEvent& Event ) + { + OSL_ENSURE( m_xDesigner.is() && ( Event.Source == m_xDesigner ), "SQLCommandDesigner::propertyChange: where did this come from?" ); + + if ( !(m_xDesigner.is() && ( Event.Source == m_xDesigner )) ) + return; + + try + { + if ( PROPERTY_ACTIVECOMMAND == Event.PropertyName ) + { + OUString sCommand; + OSL_VERIFY( Event.NewValue >>= sCommand ); + m_xObjectAdapter->setSQLCommand( sCommand ); + } + else if ( PROPERTY_ESCAPE_PROCESSING == Event.PropertyName ) + { + bool bEscapeProcessing( false ); + OSL_VERIFY( Event.NewValue >>= bEscapeProcessing ); + m_xObjectAdapter->setEscapeProcessing( bEscapeProcessing ); + } + } + catch( const RuntimeException& ) { throw; } + catch( const Exception& ) + { + // not allowed to leave, so silence it + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + + void SAL_CALL SQLCommandDesigner::disposing( const EventObject& Source ) + { + if ( m_xDesigner.is() && ( Source.Source == m_xDesigner ) ) + { + m_aCloseLink.Call( *this ); + m_xDesigner.clear(); + } + } + + + void SQLCommandDesigner::dispose() + { + if ( impl_isDisposed() ) + return; + + if ( isActive() ) + impl_closeDesigner_nothrow(); + + m_xConnection.clear(); + m_xContext.clear(); + m_xORB.clear(); + m_xDesigner.clear(); + m_xObjectAdapter.clear(); + } + + + void SQLCommandDesigner::impl_checkDisposed_throw() const + { + if ( impl_isDisposed() ) + throw DisposedException(); + } + + + void SQLCommandDesigner::raise() const + { + impl_checkDisposed_throw(); + impl_raise_nothrow(); + } + + + bool SQLCommandDesigner::suspend() const + { + impl_checkDisposed_throw(); + return impl_trySuspendDesigner_nothrow(); + } + + + void SQLCommandDesigner::impl_raise_nothrow() const + { + OSL_PRECOND( isActive(), "SQLCommandDesigner::impl_raise_nothrow: not active!" ); + if ( !isActive() ) + return; + + try + { + // activate the frame for this component + Reference< XFrame > xFrame( m_xDesigner->getFrame(), css::uno::UNO_SET_THROW ); + Reference< XWindow > xWindow( xFrame->getContainerWindow(), css::uno::UNO_SET_THROW ); + Reference< XTopWindow > xTopWindow( xWindow, UNO_QUERY_THROW ); + + xTopWindow->toFront(); + xWindow->setFocus(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + + void SQLCommandDesigner::impl_doOpenDesignerFrame_nothrow() + { + OSL_PRECOND( !isActive(), + "SQLCommandDesigner::impl_doOpenDesignerFrame_nothrow: already active!" ); + OSL_PRECOND( m_xConnection.is(), "SQLCommandDesigner::impl_doOpenDesignerFrame_nothrow: this will crash!" ); + osl_atomic_increment(&m_refCount); + + try + { + // for various reasons, we don't want the new frame to appear in the desktop's frame list + // thus, we create a blank frame at the desktop, remove it from the desktop's frame list + // immediately, and then load the component into this blank (and now parent-less) frame + Reference< XComponentLoader > xLoader( impl_createEmptyParentlessTask_nothrow(), UNO_QUERY_THROW ); + const bool bEscapeProcessing = m_xObjectAdapter->getEscapeProcessing(); + Sequence< PropertyValue > aArgs{ + comphelper::makePropertyValue(PROPERTY_ACTIVE_CONNECTION, m_xConnection.getTyped()), + comphelper::makePropertyValue(PROPERTY_COMMAND, m_xObjectAdapter->getSQLCommand()), + comphelper::makePropertyValue(PROPERTY_COMMANDTYPE, CommandType::COMMAND), + comphelper::makePropertyValue(PROPERTY_ESCAPE_PROCESSING, bEscapeProcessing), + comphelper::makePropertyValue("GraphicalDesign", bEscapeProcessing) + }; + + Reference< XComponent > xQueryDesign = xLoader->loadComponentFromURL( + ".component:DB/QueryDesign", + "_self", + FrameSearchFlag::TASKS | FrameSearchFlag::CREATE, + aArgs + ); + + // remember this newly loaded component - we need to care for it e.g. when we're suspended + m_xDesigner.set(xQueryDesign, css::uno::UNO_QUERY); + OSL_ENSURE( m_xDesigner.is() || !xQueryDesign.is(), "SQLCommandDesigner::impl_doOpenDesignerFrame_nothrow: the component is expected to be a controller!" ); + if ( m_xDesigner.is() ) + { + Reference< XPropertySet > xQueryDesignProps( m_xDesigner, UNO_QUERY ); + OSL_ENSURE( xQueryDesignProps.is(), "SQLCommandDesigner::impl_doOpenDesignerFrame_nothrow: the controller should have properties!" ); + if ( xQueryDesignProps.is() ) + { + xQueryDesignProps->addPropertyChangeListener( PROPERTY_ACTIVECOMMAND, this ); + xQueryDesignProps->addPropertyChangeListener( PROPERTY_ESCAPE_PROCESSING, this ); + } + } + + // get the frame which we just opened and set its title + Reference< XTitle> xTitle(xQueryDesign,UNO_QUERY); + if ( xTitle.is() ) + { + OUString sDisplayName = PcrRes(RID_RSC_ENUM_COMMAND_TYPE[CommandType::COMMAND]); + xTitle->setTitle(sDisplayName); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + m_xDesigner.clear(); + } + osl_atomic_decrement(&m_refCount); + } + + + Reference< XFrame > SQLCommandDesigner::impl_createEmptyParentlessTask_nothrow( ) const + { + OSL_PRECOND( m_xORB.is(), "SQLCommandDesigner::impl_createEmptyParentlessTask_nothrow: this will crash!" ); + + Reference< XFrame > xFrame; + try + { + Reference< XDesktop2 > xDesktop = Desktop::create(m_xContext); + + Reference< XFrames > xDesktopFramesCollection( xDesktop->getFrames(), css::uno::UNO_SET_THROW ); + xFrame = xDesktop->findFrame( "_blank", FrameSearchFlag::CREATE ); + OSL_ENSURE( xFrame.is(), "SQLCommandDesigner::impl_createEmptyParentlessTask_nothrow: could not create an empty frame!" ); + xDesktopFramesCollection->remove( xFrame ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + return xFrame; + } + + + void SQLCommandDesigner::impl_closeDesigner_nothrow() + { + OSL_PRECOND( isActive(), "SQLCommandDesigner::impl_closeDesigner_nothrow: invalid call!" ); + // close it + try + { + // do not listen anymore... + Reference< XPropertySet > xProps( m_xDesigner, UNO_QUERY ); + if ( xProps.is() ) + xProps->removePropertyChangeListener( PROPERTY_ACTIVECOMMAND, this ); + + // we need to close the frame via the "user interface", by dispatching a close command, + // instead of calling XCloseable::close directly. The latter method would also close + // the frame, but not care for things like shutting down the office when the last + // frame is gone ... + const UnoURL aCloseURL( ".uno:CloseDoc", + Reference< XMultiServiceFactory >( m_xORB, UNO_QUERY ) ); + + Reference< XDispatchProvider > xProvider( m_xDesigner->getFrame(), UNO_QUERY_THROW ); + Reference< XDispatch > xDispatch( xProvider->queryDispatch( aCloseURL, "_top", FrameSearchFlag::SELF ) ); + OSL_ENSURE( xDispatch.is(), "SQLCommandDesigner::impl_closeDesigner_nothrow: no dispatcher for the CloseDoc command!" ); + if ( xDispatch.is() ) + { + xDispatch->dispatch( aCloseURL, Sequence< PropertyValue >( ) ); + } + else + { + // fallback: use the XCloseable::close (with all possible disadvantages) + Reference< XCloseable > xClose( m_xDesigner->getFrame(), UNO_QUERY ); + if ( xClose.is() ) + xClose->close( true ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + + m_xDesigner.clear(); + } + + + bool SQLCommandDesigner::impl_trySuspendDesigner_nothrow() const + { + OSL_PRECOND( isActive(), "SQLCommandDesigner::impl_trySuspendDesigner_nothrow: no active designer, this will crash!" ); + bool bAllow = true; + try + { + bAllow = m_xDesigner->suspend( true ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + return bAllow; + } + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/sqlcommanddesign.hxx b/extensions/source/propctrlr/sqlcommanddesign.hxx new file mode 100644 index 0000000000..767506eca7 --- /dev/null +++ b/extensions/source/propctrlr/sqlcommanddesign.hxx @@ -0,0 +1,193 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/lang/XMultiComponentFactory.hpp> +#include <com/sun/star/beans/XPropertyChangeListener.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <connectivity/dbtools.hxx> +#include <tools/link.hxx> +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> +#include <salhelper/simplereferenceobject.hxx> + + +namespace pcr +{ + + + class ISQLCommandAdapter; + + //= SQLCommandDesigner + + typedef ::cppu::WeakImplHelper < css::beans::XPropertyChangeListener + > SQLCommandDesigner_Base; + /** encapsulates the code for calling and managing a query design frame, used + for interactively designing the Command property of a ->RowSet + */ + class SQLCommandDesigner final : public SQLCommandDesigner_Base + { + private: + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::lang::XMultiComponentFactory > m_xORB; + ::dbtools::SharedConnection m_xConnection; + css::uno::Reference< css::frame::XController > m_xDesigner; + ::rtl::Reference< ISQLCommandAdapter > m_xObjectAdapter; + Link<SQLCommandDesigner&,void> m_aCloseLink; + + public: + /** creates the instance, and immediately opens the SQL command design frame + + @param _rxContext + our component context. Must not be <NULL/>, and must provide a non-<NULL/> XMultiComponentFactory + @param _rxPropertyAdapter + an adapter to the object's SQL command related properties + @param _rConnection + the current connection of ->_rxRowSet. Must not be <NULL/>. + @param _rCloseLink + link to call when the component has been closed + @throws css::lang::NullPointerException + if any of the arguments (except ->_rCloseLink) is <NULL/>, or if the component context + does not provide a valid component factory. + */ + SQLCommandDesigner( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext, + const ::rtl::Reference< ISQLCommandAdapter >& _rxPropertyAdapter, + ::dbtools::SharedConnection _aConnection, + const Link<SQLCommandDesigner&,void>& _rCloseLink + ); + + /** determines whether the SQL Command designer is currently active, i.e. + if there currently exists a frame which allows the user entering the SQL command + */ + bool isActive() const { return m_xDesigner.is(); } + + /** returns the property adapter used by the instance + */ + const ::rtl::Reference< ISQLCommandAdapter >& getPropertyAdapter() const { return m_xObjectAdapter; } + + /** raises the designer window to top + @precond + the designer is active (->isActive) + @precond + the instance is not disposed + */ + void raise() const; + + /** suspends the designer + @precond + the designer is active (->isActive) + @precond + the instance is not disposed + */ + bool suspend() const; + + /** disposes the instance so that it becomes non-functional + */ + void dispose(); + + private: + // XPropertyChangeListener + virtual void SAL_CALL propertyChange( const css::beans::PropertyChangeEvent& evt ) override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + virtual ~SQLCommandDesigner() override; + + /** opens a new frame for interactively designing an SQL command + @precond + the designer is not currently active (see ->isActive) + @precond + ->m_xConnection is not <NULL/> + */ + void impl_doOpenDesignerFrame_nothrow(); + + /** impl-version of ->raise + */ + void impl_raise_nothrow() const; + + /** determines whether we are already disposed + */ + bool impl_isDisposed() const + { + return !m_xContext.is(); + } + /** checks whether we are already disposed + @throws css::lang::DisposedException + if we in fact are disposed + */ + void impl_checkDisposed_throw() const; + + /** create an empty top-level frame, which does not belong to the desktop's frame list + @precond + ->m_xORB is not <NULL/> + */ + css::uno::Reference< css::frame::XFrame > + impl_createEmptyParentlessTask_nothrow() const; + + /** closes the component denoted by m_xDesigner + @precond + our designer component is actually active (->isActive) + @precond + we're not disposed already + */ + void impl_closeDesigner_nothrow(); + + /** suspends our designer component + @precond + the designer component is actually active (->isActive) + @return + <TRUE/> if the suspension was successful, <FALSE/> if it was vetoed + */ + bool impl_trySuspendDesigner_nothrow() const; + + SQLCommandDesigner( const SQLCommandDesigner& ) = delete; + SQLCommandDesigner& operator=( const SQLCommandDesigner& ) = delete; + }; + + + //= ISQLCommandAdapter + + /** an adapter to forward changed SQL command property values to a component + */ + class ISQLCommandAdapter : public salhelper::SimpleReferenceObject + { + public: + /// retrieves the current SQL command of the component + virtual OUString getSQLCommand() const = 0; + /// retrieves the current value of the EscapeProcessing property of the component + virtual bool getEscapeProcessing() const = 0; + + /// sets a new SQL command + virtual void setSQLCommand( const OUString& _rCommand ) const = 0; + /// sets a new EscapeProcessing property value + virtual void setEscapeProcessing( const bool _bEscapeProcessing ) const = 0; + + virtual ~ISQLCommandAdapter() override; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/standardcontrol.cxx b/extensions/source/propctrlr/standardcontrol.cxx new file mode 100644 index 0000000000..ad978253b0 --- /dev/null +++ b/extensions/source/propctrlr/standardcontrol.cxx @@ -0,0 +1,864 @@ +/* -*- 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 "standardcontrol.hxx" +#include "pcrcommon.hxx" + +#include <com/sun/star/beans/IllegalTypeException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/util/Time.hpp> +#include <com/sun/star/util/Color.hpp> +#include <com/sun/star/util/MeasureUnit.hpp> +#include <com/sun/star/inspection/PropertyControlType.hpp> +#include <comphelper/string.hxx> +#include <o3tl/float_int_conversion.hxx> +#include <toolkit/helper/vclunohelper.hxx> + + +// ugly dependencies for the OColorControl +#include <svx/svxids.hrc> + +#include <tools/datetime.hxx> +#include <unotools/datetime.hxx> +#include <o3tl/string_view.hxx> + +#include <limits> +#include <memory> + +namespace pcr +{ + using namespace ::com::sun::star; + 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::beans; + using namespace ::com::sun::star::inspection; + + //= OTimeControl + OTimeControl::OTimeControl(std::unique_ptr<weld::FormattedSpinButton> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly) + : OTimeControl_Base(PropertyControlType::TimeField, std::move(xBuilder), std::move(xWidget), bReadOnly) + , m_xFormatter(new weld::TimeFormatter(*getTypedControlWindow())) + { + m_xFormatter->SetExtFormat(ExtTimeFieldFormat::LongDuration); + } + + void SAL_CALL OTimeControl::setValue( const Any& _rValue ) + { + util::Time aUNOTime; + if ( !( _rValue >>= aUNOTime ) ) + { + getTypedControlWindow()->set_text(""); + m_xFormatter->SetTime(tools::Time(tools::Time::EMPTY)); + } + else + { + m_xFormatter->SetTime(::tools::Time(aUNOTime)); + } + } + + Any SAL_CALL OTimeControl::getValue() + { + Any aPropValue; + if ( !getTypedControlWindow()->get_text().isEmpty() ) + { + aPropValue <<= m_xFormatter->GetTime().GetUNOTime(); + } + return aPropValue; + } + + Type SAL_CALL OTimeControl::getValueType() + { + return ::cppu::UnoType<util::Time>::get(); + } + + //= ODateControl + ODateControl::ODateControl(std::unique_ptr<weld::Container> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly) + : ODateControl_Base(PropertyControlType::DateField, std::move(xBuilder), std::move(xWidget), bReadOnly) + , m_xEntry(m_xBuilder->weld_entry("entry")) + , m_xCalendarBox(std::make_unique<SvtCalendarBox>(m_xBuilder->weld_menu_button("button"), false)) + { + m_xEntryFormatter.reset(new weld::DateFormatter(*m_xEntry)); + + m_xEntryFormatter->SetStrictFormat(true); + m_xEntryFormatter->SetMin(::Date(1, 1, 1600)); + m_xEntryFormatter->SetMax(::Date(1, 1, 9999)); + + m_xEntryFormatter->SetExtDateFormat(ExtDateFieldFormat::SystemShortYYYY); + m_xEntryFormatter->EnableEmptyField(true); + + m_xCalendarBox->connect_activated(LINK(this, ODateControl, ActivateHdl)); + + m_xCalendarBox->get_button().connect_toggled(LINK(this, ODateControl, ToggleHdl)); + } + + void SAL_CALL ODateControl::disposing() + { + m_xEntryFormatter.reset(); + m_xEntry.reset(); + m_xCalendarBox.reset(); + ODateControl_Base::disposing(); + } + + void SAL_CALL ODateControl::setValue( const Any& _rValue ) + { + util::Date aUNODate; + if ( !( _rValue >>= aUNODate ) ) + { + m_xEntry->set_text(OUString()); + } + else + { + ::Date aDate( aUNODate.Day, aUNODate.Month, aUNODate.Year ); + m_xEntryFormatter->SetDate(aDate); + } + } + + IMPL_LINK_NOARG(ODateControl, ActivateHdl, SvtCalendarBox&, void) + { + m_xEntryFormatter->SetDate(m_xCalendarBox->get_date()); + setModified(); + m_xEntry->grab_focus(); + } + + IMPL_LINK(ODateControl, ToggleHdl, weld::Toggleable&, rToggle, void) + { + if (!rToggle.get_active()) + return; + ::Date aDate = m_xEntryFormatter->GetDate(); + if (aDate.IsEmpty()) + { + // with an empty date preselect today in the calendar + aDate = ::Date(::Date::SYSTEM); + } + m_xCalendarBox->set_date(aDate); + } + + Any SAL_CALL ODateControl::getValue() + { + Any aPropValue; + if (!m_xEntry->get_text().isEmpty()) + { + ::Date aDate(m_xEntryFormatter->GetDate()); + aPropValue <<= aDate.GetUNODate(); + } + return aPropValue; + } + + Type SAL_CALL ODateControl::getValueType() + { + return ::cppu::UnoType<util::Date>::get(); + } + + //= OEditControl + OEditControl::OEditControl(std::unique_ptr<weld::Entry> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bPW, bool bReadOnly) + : OEditControl_Base( bPW ? PropertyControlType::CharacterField : PropertyControlType::TextField, std::move(xBuilder), std::move(xWidget), bReadOnly ) + { + m_bIsPassword = bPW; + + auto pWidget = getTypedControlWindow(); + pWidget->set_sensitive(true); + pWidget->set_editable(!bReadOnly); + + if (m_bIsPassword) + pWidget->set_max_length( 1 ); + } + + void SAL_CALL OEditControl::setValue( const Any& _rValue ) + { + OUString sText; + if ( m_bIsPassword ) + { + sal_Int16 nValue = 0; + _rValue >>= nValue; + if ( nValue ) + { + sText = OUString(static_cast<sal_Unicode>(nValue)); + } + } + else + _rValue >>= sText; + + getTypedControlWindow()->set_text( sText ); + } + + Any SAL_CALL OEditControl::getValue() + { + Any aPropValue; + + OUString sText( getTypedControlWindow()->get_text() ); + if ( m_bIsPassword ) + { + if ( !sText.isEmpty() ) + aPropValue <<= static_cast<sal_Int16>(sText[0]); + } + else + aPropValue <<= sText; + + return aPropValue; + } + + Type SAL_CALL OEditControl::getValueType() + { + return m_bIsPassword ? ::cppu::UnoType<sal_Int16>::get() : ::cppu::UnoType<OUString>::get(); + } + + void OEditControl::setModified() + { + OEditControl_Base::setModified(); + + // for password controls, we fire a commit for every single change + if ( m_bIsPassword ) + notifyModifiedValue(); + } + + static sal_Int64 ImplCalcLongValue( double nValue, sal_uInt16 nDigits ) + { + double n = nValue; + for ( sal_uInt16 d = 0; d < nDigits; ++d ) + n *= 10; + + return o3tl::saturating_cast<sal_Int64>(n); + } + + static double ImplCalcDoubleValue(sal_Int64 nValue, sal_uInt16 nDigits ) + { + double n = nValue; + for ( sal_uInt16 d = 0; d < nDigits; ++d ) + n /= 10; + return n; + } + + ODateTimeControl::ODateTimeControl(std::unique_ptr<weld::Container> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly) + : ODateTimeControl_Base(PropertyControlType::DateTimeField, std::move(xBuilder), std::move(xWidget), bReadOnly) + , m_xDate(std::make_unique<SvtCalendarBox>(m_xBuilder->weld_menu_button("datefield"))) + , m_xTime(m_xBuilder->weld_formatted_spin_button("timefield")) + , m_xFormatter(new weld::TimeFormatter(*m_xTime)) + { + m_xFormatter->SetExtFormat(ExtTimeFieldFormat::LongDuration); + } + + void SAL_CALL ODateTimeControl::setValue( const Any& _rValue ) + { + if ( !_rValue.hasValue() ) + { + m_xDate->set_date(::Date(::Date::SYSTEM)); + m_xTime->set_text(""); + m_xFormatter->SetTime(tools::Time(tools::Time::EMPTY)); + } + else + { + util::DateTime aUNODateTime; + OSL_VERIFY( _rValue >>= aUNODateTime ); + + ::DateTime aDateTime( ::DateTime::EMPTY ); + ::utl::typeConvert( aUNODateTime, aDateTime ); + + m_xDate->set_date(aDateTime); + m_xFormatter->SetTime(aDateTime); + } + } + + Any SAL_CALL ODateTimeControl::getValue() + { + Any aPropValue; + if (!m_xTime->get_text().isEmpty()) + { + ::DateTime aDateTime(m_xDate->get_date(), m_xFormatter->GetTime()); + + util::DateTime aUNODateTime; + ::utl::typeConvert( aDateTime, aUNODateTime ); + + aPropValue <<= aUNODateTime; + } + return aPropValue; + } + + Type SAL_CALL ODateTimeControl::getValueType() + { + return ::cppu::UnoType<util::DateTime>::get(); + } + + //= OHyperlinkControl + OHyperlinkControl::OHyperlinkControl(std::unique_ptr<weld::Container> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly) + : OHyperlinkControl_Base(PropertyControlType::HyperlinkField, std::move(xBuilder), std::move(xWidget), bReadOnly) + , m_xEntry(m_xBuilder->weld_entry("entry")) + , m_xButton(m_xBuilder->weld_button("button")) + , m_aActionListeners(m_aMutex) + { + auto pWidget = getTypedControlWindow(); + pWidget->set_sensitive(true); + m_xEntry->set_editable(!bReadOnly); + + m_xButton->connect_clicked(LINK(this, OHyperlinkControl, OnHyperlinkClicked)); + } + + Any SAL_CALL OHyperlinkControl::getValue() + { + OUString sText = m_xEntry->get_text(); + return Any( sText ); + } + + void SAL_CALL OHyperlinkControl::setValue( const Any& _value ) + { + OUString sText; + _value >>= sText; + m_xEntry->set_text( sText ); + } + + Type SAL_CALL OHyperlinkControl::getValueType() + { + return ::cppu::UnoType<OUString>::get(); + } + + void SAL_CALL OHyperlinkControl::addActionListener( const Reference< XActionListener >& listener ) + { + if ( listener.is() ) + m_aActionListeners.addInterface( listener ); + } + + void SAL_CALL OHyperlinkControl::removeActionListener( const Reference< XActionListener >& listener ) + { + m_aActionListeners.removeInterface( listener ); + } + + void SAL_CALL OHyperlinkControl::disposing() + { + m_xButton.reset(); + m_xEntry.reset(); + OHyperlinkControl_Base::disposing(); + + EventObject aEvent( *this ); + m_aActionListeners.disposeAndClear( aEvent ); + } + + IMPL_LINK_NOARG( OHyperlinkControl, OnHyperlinkClicked, weld::Button&, void ) + { + ActionEvent aEvent( *this, "clicked" ); + m_aActionListeners.forEach< XActionListener >( + [&aEvent] (uno::Reference<awt::XActionListener> const& xListener) + { return xListener->actionPerformed(aEvent); }); + } + + //= ONumericControl + ONumericControl::ONumericControl(std::unique_ptr<weld::MetricSpinButton> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly) + : ONumericControl_Base(PropertyControlType::NumericField, std::move(xBuilder), std::move(xWidget), bReadOnly) + , m_eValueUnit( FieldUnit::NONE ) + , m_nFieldToUNOValueFactor( 1 ) + { + Optional< double > value( getMaxValue() ); + value.Value = -value.Value; + setMinValue( value ); + } + + ::sal_Int16 SAL_CALL ONumericControl::getDecimalDigits() + { + return getTypedControlWindow()->get_digits(); + } + + void SAL_CALL ONumericControl::setDecimalDigits( ::sal_Int16 decimaldigits ) + { + weld::MetricSpinButton* pControlWindow = getTypedControlWindow(); + sal_Int64 min, max; + pControlWindow->get_range(min, max, FieldUnit::NONE); + pControlWindow->set_digits(decimaldigits); + pControlWindow->set_range(min, max, FieldUnit::NONE); + } + + Optional< double > SAL_CALL ONumericControl::getMinValue() + { + Optional< double > aReturn( true, 0 ); + + sal_Int64 minValue = getTypedControlWindow()->get_min(FieldUnit::NONE); + if ( minValue == std::numeric_limits<sal_Int64>::min() ) + aReturn.IsPresent = false; + else + aReturn.Value = static_cast<double>(minValue); + + return aReturn; + } + + void SAL_CALL ONumericControl::setMinValue( const Optional< double >& _minvalue ) + { + if ( !_minvalue.IsPresent ) + getTypedControlWindow()->set_min( std::numeric_limits<sal_Int64>::min(), FieldUnit::NONE ); + else + getTypedControlWindow()->set_min( impl_apiValueToFieldValue_nothrow( _minvalue.Value ) , m_eValueUnit); + } + + Optional< double > SAL_CALL ONumericControl::getMaxValue() + { + Optional< double > aReturn( true, 0 ); + + sal_Int64 maxValue = getTypedControlWindow()->get_max(FieldUnit::NONE); + if ( maxValue == std::numeric_limits<sal_Int64>::max() ) + aReturn.IsPresent = false; + else + aReturn.Value = static_cast<double>(maxValue); + + return aReturn; + } + + void SAL_CALL ONumericControl::setMaxValue( const Optional< double >& _maxvalue ) + { + if ( !_maxvalue.IsPresent ) + getTypedControlWindow()->set_max( std::numeric_limits<sal_Int64>::max(), FieldUnit::NONE ); + else + getTypedControlWindow()->set_max( impl_apiValueToFieldValue_nothrow( _maxvalue.Value ), m_eValueUnit ); + } + + ::sal_Int16 SAL_CALL ONumericControl::getDisplayUnit() + { + return VCLUnoHelper::ConvertToMeasurementUnit( getTypedControlWindow()->get_unit(), 1 ); + } + + void SAL_CALL ONumericControl::setDisplayUnit( ::sal_Int16 _displayunit ) + { + if ( ( _displayunit < MeasureUnit::MM_100TH ) || ( _displayunit > MeasureUnit::PERCENT ) ) + throw IllegalArgumentException(); + if ( ( _displayunit == MeasureUnit::MM_100TH ) + || ( _displayunit == MeasureUnit::MM_10TH ) + || ( _displayunit == MeasureUnit::INCH_1000TH ) + || ( _displayunit == MeasureUnit::INCH_100TH ) + || ( _displayunit == MeasureUnit::INCH_10TH ) + || ( _displayunit == MeasureUnit::PERCENT ) + ) + throw IllegalArgumentException(); + + sal_Int16 nDummyFactor = 1; + FieldUnit eFieldUnit = VCLUnoHelper::ConvertToFieldUnit( _displayunit, nDummyFactor ); + if ( nDummyFactor != 1 ) + // everything which survived the checks above should result in a factor of 1, i.e., + // it should have a direct counterpart as FieldUnit + throw RuntimeException(); + getTypedControlWindow()->set_unit(eFieldUnit); + } + + ::sal_Int16 SAL_CALL ONumericControl::getValueUnit() + { + return VCLUnoHelper::ConvertToMeasurementUnit( m_eValueUnit, m_nFieldToUNOValueFactor ); + } + + void SAL_CALL ONumericControl::setValueUnit( ::sal_Int16 _valueunit ) + { + if ( ( _valueunit < MeasureUnit::MM_100TH ) || ( _valueunit > MeasureUnit::PERCENT ) ) + throw IllegalArgumentException(); + m_eValueUnit = VCLUnoHelper::ConvertToFieldUnit( _valueunit, m_nFieldToUNOValueFactor ); + } + + void SAL_CALL ONumericControl::setValue( const Any& _rValue ) + { + if ( !_rValue.hasValue() ) + { + getTypedControlWindow()->set_text( "" ); + } + else + { + double nValue( 0 ); + OSL_VERIFY( _rValue >>= nValue ); + auto nControlValue = impl_apiValueToFieldValue_nothrow( nValue ); + getTypedControlWindow()->set_value( nControlValue, m_eValueUnit ); + } + } + + sal_Int64 ONumericControl::impl_apiValueToFieldValue_nothrow( double _nApiValue ) const + { + sal_Int64 nControlValue = ImplCalcLongValue( _nApiValue, getTypedControlWindow()->get_digits() ); + nControlValue /= m_nFieldToUNOValueFactor; + return nControlValue; + } + + double ONumericControl::impl_fieldValueToApiValue_nothrow(sal_Int64 nFieldValue) const + { + double nApiValue = ImplCalcDoubleValue( nFieldValue, getTypedControlWindow()->get_digits() ); + nApiValue *= m_nFieldToUNOValueFactor; + return nApiValue; + } + + Any SAL_CALL ONumericControl::getValue() + { + Any aPropValue; + if ( !getTypedControlWindow()->get_text().isEmpty() ) + { + double nValue = impl_fieldValueToApiValue_nothrow( getTypedControlWindow()->get_value( m_eValueUnit ) ); + if (nValue) + aPropValue <<= nValue; + } + return aPropValue; + } + + Type SAL_CALL ONumericControl::getValueType() + { + return ::cppu::UnoType<double>::get(); + } + + //= OColorControl + OColorControl::OColorControl(std::unique_ptr<ColorListBox> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly) + : OColorControl_Base(PropertyControlType::ColorListBox, std::move(xBuilder), std::move(xWidget), bReadOnly) + { + getTypedControlWindow()->SetSlotId(SID_FM_CTL_PROPERTIES); + } + + void SAL_CALL OColorControl::setValue( const Any& _rValue ) + { + css::util::Color nColor = sal_uInt32(COL_TRANSPARENT); + if (_rValue.hasValue()) + _rValue >>= nColor; + getTypedControlWindow()->SelectEntry(::Color(ColorTransparency, nColor)); + } + + Any SAL_CALL OColorControl::getValue() + { + Any aPropValue; + ::Color aRgbCol = getTypedControlWindow()->GetSelectEntryColor(); + if (aRgbCol == COL_TRANSPARENT) + return aPropValue; + aPropValue <<= aRgbCol; + return aPropValue; + } + + Type SAL_CALL OColorControl::getValueType() + { + return ::cppu::UnoType<sal_Int32>::get(); + } + + void OColorControl::setModified() + { + OColorControl_Base::setModified(); + + // fire a commit + notifyModifiedValue(); + } + + //= OListboxControl + OListboxControl::OListboxControl(std::unique_ptr<weld::ComboBox> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly) + : OListboxControl_Base(PropertyControlType::ListBox, std::move(xBuilder), std::move(xWidget), bReadOnly) + { + } + + Any SAL_CALL OListboxControl::getValue() + { + OUString sControlValue( getTypedControlWindow()->get_active_text() ); + + Any aPropValue; + if ( !sControlValue.isEmpty() ) + aPropValue <<= sControlValue; + return aPropValue; + } + + Type SAL_CALL OListboxControl::getValueType() + { + return ::cppu::UnoType<OUString>::get(); + } + + void SAL_CALL OListboxControl::setValue( const Any& _rValue ) + { + if ( !_rValue.hasValue() ) + getTypedControlWindow()->set_active(-1); + else + { + OUString sSelection; + _rValue >>= sSelection; + + if (getTypedControlWindow()->find_text(sSelection) == -1) + getTypedControlWindow()->insert_text(0, sSelection); + + if (sSelection != getTypedControlWindow()->get_active_text()) + getTypedControlWindow()->set_active_text(sSelection); + } + } + + void SAL_CALL OListboxControl::clearList() + { + getTypedControlWindow()->clear(); + } + + void SAL_CALL OListboxControl::prependListEntry( const OUString& NewEntry ) + { + getTypedControlWindow()->insert_text(0, NewEntry); + } + + void SAL_CALL OListboxControl::appendListEntry( const OUString& NewEntry ) + { + getTypedControlWindow()->append_text(NewEntry); + } + + Sequence< OUString > SAL_CALL OListboxControl::getListEntries() + { + const sal_Int32 nCount = getTypedControlWindow()->get_count(); + Sequence< OUString > aRet(nCount); + OUString* pIter = aRet.getArray(); + for (sal_Int32 i = 0; i < nCount ; ++i,++pIter) + *pIter = getTypedControlWindow()->get_text(i); + + return aRet; + } + + void OListboxControl::setModified() + { + OListboxControl_Base::setModified(); + + // fire a commit + notifyModifiedValue(); + } + + //= OComboboxControl + OComboboxControl::OComboboxControl(std::unique_ptr<weld::ComboBox> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly) + : OComboboxControl_Base(PropertyControlType::ComboBox, std::move(xBuilder), std::move(xWidget), bReadOnly) + { + getTypedControlWindow()->connect_changed( LINK( this, OComboboxControl, OnEntrySelected ) ); + } + + void SAL_CALL OComboboxControl::setValue( const Any& _rValue ) + { + OUString sText; + _rValue >>= sText; + weld::ComboBox* pControlWindow = getTypedControlWindow(); + // tdf#138701 leave current cursor valid if the contents won't change + if (pControlWindow->get_active_text() != sText) + pControlWindow->set_entry_text(sText); + } + + Any SAL_CALL OComboboxControl::getValue() + { + return Any( getTypedControlWindow()->get_active_text() ); + } + + Type SAL_CALL OComboboxControl::getValueType() + { + return ::cppu::UnoType<OUString>::get(); + } + + void SAL_CALL OComboboxControl::clearList() + { + getTypedControlWindow()->clear(); + } + + void SAL_CALL OComboboxControl::prependListEntry( const OUString& NewEntry ) + { + getTypedControlWindow()->insert_text(0, NewEntry); + } + + void SAL_CALL OComboboxControl::appendListEntry( const OUString& NewEntry ) + { + getTypedControlWindow()->append_text( NewEntry ); + } + + Sequence< OUString > SAL_CALL OComboboxControl::getListEntries( ) + { + const sal_Int32 nCount = getTypedControlWindow()->get_count(); + Sequence< OUString > aRet(nCount); + OUString* pIter = aRet.getArray(); + for (sal_Int32 i = 0; i < nCount ; ++i,++pIter) + *pIter = getTypedControlWindow()->get_text(i); + + return aRet; + } + + IMPL_LINK_NOARG( OComboboxControl, OnEntrySelected, weld::ComboBox&, void ) + { + // fire a commit + notifyModifiedValue(); + } + + namespace + { + + StlSyntaxSequence< OUString > lcl_convertMultiLineToList( std::u16string_view _rCompsedTextWithLineBreaks ) + { + sal_Int32 nLines = comphelper::string::getTokenCount(_rCompsedTextWithLineBreaks, '\n'); + StlSyntaxSequence< OUString > aStrings( nLines ); + if (nLines) + { + StlSyntaxSequence< OUString >::iterator stringItem = aStrings.begin(); + sal_Int32 nIdx {0}; + do + { + *stringItem = o3tl::getToken(_rCompsedTextWithLineBreaks, 0, '\n', nIdx ); + ++stringItem; + } + while (nIdx>0); + } + return aStrings; + } + + OUString lcl_convertListToMultiLine( const StlSyntaxSequence< OUString >& _rStrings ) + { + OUStringBuffer sMultiLineText; + for ( StlSyntaxSequence< OUString >::const_iterator item = _rStrings.begin(); + item != _rStrings.end(); + ) + { + sMultiLineText.append(*item); + if ( ++item != _rStrings.end() ) + sMultiLineText.append("\n"); + } + return sMultiLineText.makeStringAndClear(); + } + + + OUString lcl_convertListToDisplayText( const StlSyntaxSequence< OUString >& _rStrings ) + { + OUStringBuffer aComposed; + for ( StlSyntaxSequence< OUString >::const_iterator strings = _rStrings.begin(); + strings != _rStrings.end(); + ++strings + ) + { + if ( strings != _rStrings.begin() ) + aComposed.append( ';' ); + aComposed.append( "\"" + *strings + "\"" ); + } + return aComposed.makeStringAndClear(); + } + } + + void OMultilineEditControl::CheckEntryTextViewMisMatch() + { + // if there are newlines or something else which the entry cannot show, then make + // just the multiline dropdown editable as the canonical source for text + m_xEntry->set_sensitive(m_xEntry->get_text() == m_xTextView->get_text()); + } + + void OMultilineEditControl::SetStringListValue(const StlSyntaxSequence<OUString>& rStrings) + { + m_xEntry->set_text(lcl_convertListToDisplayText(rStrings)); + m_xTextView->set_text(lcl_convertListToMultiLine(rStrings)); + CheckEntryTextViewMisMatch(); + } + + StlSyntaxSequence<OUString> OMultilineEditControl::GetStringListValue() const + { + return lcl_convertMultiLineToList(m_xTextView->get_text()); + } + + void OMultilineEditControl::SetTextValue(const OUString& rText) + { + OSL_PRECOND( m_nOperationMode == eMultiLineText, "OMultilineEditControl::SetTextValue: illegal call!" ); + + m_xTextView->set_text(rText); + m_xEntry->set_text(rText); + CheckEntryTextViewMisMatch(); + } + + OUString OMultilineEditControl::GetTextValue() const + { + OSL_PRECOND( m_nOperationMode == eMultiLineText, "OMultilineEditControl::GetTextValue: illegal call!" ); + return m_xTextView->get_text(); + } + + //= OMultilineEditControl + OMultilineEditControl::OMultilineEditControl(std::unique_ptr<weld::Container> xWidget, std::unique_ptr<weld::Builder> xBuilder, MultiLineOperationMode eMode, bool bReadOnly) + : OMultilineEditControl_Base(eMode == eMultiLineText ? PropertyControlType::MultiLineTextField : PropertyControlType::StringListField, + std::move(xBuilder), std::move(xWidget), bReadOnly) + , m_nOperationMode(eMode) + , m_xEntry(m_xBuilder->weld_entry("entry")) + , m_xButton(m_xBuilder->weld_menu_button("button")) + , m_xPopover(m_xBuilder->weld_widget("popover")) + , m_xTextView(m_xBuilder->weld_text_view("textview")) + , m_xOk(m_xBuilder->weld_button("ok")) + { + m_xButton->set_popover(m_xPopover.get()); + m_xTextView->set_size_request(m_xTextView->get_approximate_digit_width() * 30, m_xTextView->get_height_rows(8)); + m_xOk->connect_clicked(LINK(this, OMultilineEditControl, ButtonHandler)); + } + + IMPL_LINK_NOARG(OMultilineEditControl, TextViewModifiedHdl, weld::TextView&, void) + { + // tdf#139070 during editing update the entry to look like how it will + // look once editing is finished so that the default behaviour of vcl + // to strip newlines and the default behaviour of gtk to show a newline + // symbol is suppressed + OUString sText = m_xTextView->get_text(); + auto aSeq = lcl_convertMultiLineToList(sText); + if (aSeq.getLength() > 1) + m_xEntry->set_text(lcl_convertListToDisplayText(aSeq)); + else + m_xEntry->set_text(sText); + CheckEntryTextViewMisMatch(); + setModified(); + } + + void OMultilineEditControl::editChanged() + { + m_xTextView->set_text(m_xEntry->get_text()); + CheckEntryTextViewMisMatch(); + setModified(); + } + + IMPL_LINK_NOARG(OMultilineEditControl, ButtonHandler, weld::Button&, void) + { + m_xButton->set_active(false); + notifyModifiedValue(); + } + + void SAL_CALL OMultilineEditControl::setValue( const Any& _rValue ) + { + impl_checkDisposed_throw(); + + switch (m_nOperationMode) + { + case eMultiLineText: + { + OUString sText; + if ( !( _rValue >>= sText ) && _rValue.hasValue() ) + throw IllegalTypeException(); + SetTextValue(sText); + break; + } + case eStringList: + { + Sequence< OUString > aStringLines; + if ( !( _rValue >>= aStringLines ) && _rValue.hasValue() ) + throw IllegalTypeException(); + SetStringListValue( StlSyntaxSequence<OUString>(aStringLines) ); + break; + } + } + } + + Any SAL_CALL OMultilineEditControl::getValue() + { + impl_checkDisposed_throw(); + + Any aValue; + switch (m_nOperationMode) + { + case eMultiLineText: + aValue <<= GetTextValue(); + break; + case eStringList: + aValue <<= GetStringListValue(); + break; + } + return aValue; + } + + Type SAL_CALL OMultilineEditControl::getValueType() + { + if (m_nOperationMode == eMultiLineText) + return ::cppu::UnoType<OUString>::get(); + return cppu::UnoType<Sequence< OUString >>::get(); + } + +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/standardcontrol.hxx b/extensions/source/propctrlr/standardcontrol.hxx new file mode 100644 index 0000000000..fcd194886a --- /dev/null +++ b/extensions/source/propctrlr/standardcontrol.hxx @@ -0,0 +1,412 @@ +/* -*- 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 . + */ + +#pragma once + +#include "commoncontrol.hxx" +#include "pcrcommon.hxx" + +#include <com/sun/star/inspection/XNumericControl.hpp> +#include <com/sun/star/inspection/XStringListControl.hpp> +#include <com/sun/star/inspection/XHyperlinkControl.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <comphelper/interfacecontainer2.hxx> +#include <svtools/ctrlbox.hxx> +#include <svx/colorbox.hxx> + +namespace pcr +{ + //= OTimeControl + typedef CommonBehaviourControl<css::inspection::XPropertyControl, weld::FormattedSpinButton> OTimeControl_Base; + class OTimeControl : public OTimeControl_Base + { + std::unique_ptr<weld::TimeFormatter> m_xFormatter; + public: + OTimeControl(std::unique_ptr<weld::FormattedSpinButton> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly); + + virtual void SAL_CALL disposing() override + { + m_xFormatter.reset(); + OTimeControl_Base::disposing(); + } + + // XPropertyControl + virtual css::uno::Any SAL_CALL getValue() override; + virtual void SAL_CALL setValue( const css::uno::Any& _value ) override; + virtual css::uno::Type SAL_CALL getValueType() override; + + virtual void SetModifyHandler() override + { + OTimeControl_Base::SetModifyHandler(); + getTypedControlWindow()->connect_value_changed( LINK( this, CommonBehaviourControlHelper, TimeModifiedHdl ) ); + } + + virtual weld::Widget* getWidget() override { return getTypedControlWindow(); } + }; + + //= ODateControl + typedef CommonBehaviourControl<css::inspection::XPropertyControl, weld::Container> ODateControl_Base; + class ODateControl : public ODateControl_Base + { + std::unique_ptr<weld::Entry> m_xEntry; + std::unique_ptr<SvtCalendarBox> m_xCalendarBox; + std::unique_ptr<weld::DateFormatter> m_xEntryFormatter; + + DECL_LINK(ActivateHdl, SvtCalendarBox&, void); + DECL_LINK(ToggleHdl, weld::Toggleable&, void); + + public: + ODateControl(std::unique_ptr<weld::Container> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly); + + // XPropertyControl + virtual css::uno::Any SAL_CALL getValue() override; + virtual void SAL_CALL setValue( const css::uno::Any& _value ) override; + virtual css::uno::Type SAL_CALL getValueType() override; + + virtual void SetModifyHandler() override + { + ODateControl_Base::SetModifyHandler(); + + m_xEntry->connect_focus_in( LINK( this, CommonBehaviourControlHelper, GetFocusHdl ) ); + m_xEntryFormatter->connect_focus_out( LINK( this, CommonBehaviourControlHelper, LoseFocusHdl ) ); + m_xCalendarBox->connect_focus_in( LINK( this, CommonBehaviourControlHelper, GetFocusHdl ) ); + m_xCalendarBox->connect_focus_out( LINK( this, CommonBehaviourControlHelper, LoseFocusHdl ) ); + + m_xEntryFormatter->connect_changed(LINK(this, CommonBehaviourControlHelper, EditModifiedHdl)); + } + + virtual void SAL_CALL disposing() override; + + virtual weld::Widget* getWidget() override { return getTypedControlWindow(); } + }; + + //= OEditControl + typedef CommonBehaviourControl<css::inspection::XPropertyControl, weld::Entry> OEditControl_Base; + class OEditControl final : public OEditControl_Base + { + bool m_bIsPassword : 1; + + public: + OEditControl(std::unique_ptr<weld::Entry> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bPassWord, bool bReadOnly); + + // XPropertyControl + virtual css::uno::Any SAL_CALL getValue() override; + virtual void SAL_CALL setValue( const css::uno::Any& _value ) override; + virtual css::uno::Type SAL_CALL getValueType() override; + + virtual void SetModifyHandler() override + { + OEditControl_Base::SetModifyHandler(); + getTypedControlWindow()->connect_changed( LINK( this, CommonBehaviourControlHelper, EditModifiedHdl ) ); + } + + private: + // CommonBehaviourControlHelper::modified + virtual void setModified() override; + virtual weld::Widget* getWidget() override { return getTypedControlWindow(); } + }; + + //= ODateTimeControl + typedef CommonBehaviourControl<css::inspection::XPropertyControl, weld::Container> ODateTimeControl_Base; + class ODateTimeControl : public ODateTimeControl_Base + { + private: + std::unique_ptr<SvtCalendarBox> m_xDate; + std::unique_ptr<weld::FormattedSpinButton> m_xTime; + std::unique_ptr<weld::TimeFormatter> m_xFormatter; + + public: + ODateTimeControl(std::unique_ptr<weld::Container> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly); + + virtual void SetModifyHandler() override + { + m_xDate->connect_focus_in( LINK( this, CommonBehaviourControlHelper, GetFocusHdl ) ); + m_xDate->connect_focus_out( LINK( this, CommonBehaviourControlHelper, LoseFocusHdl ) ); + m_xTime->connect_focus_in( LINK( this, CommonBehaviourControlHelper, GetFocusHdl ) ); + m_xTime->connect_focus_out( LINK( this, CommonBehaviourControlHelper, LoseFocusHdl ) ); + + m_xDate->connect_selected( LINK( this, CommonBehaviourControlHelper, DateModifiedHdl ) ); + m_xTime->connect_value_changed( LINK( this, CommonBehaviourControlHelper, TimeModifiedHdl ) ); + } + + virtual void SAL_CALL disposing() override + { + m_xFormatter.reset(); + m_xTime.reset(); + m_xDate.reset(); + ODateTimeControl_Base::disposing(); + } + + // XPropertyControl + virtual css::uno::Any SAL_CALL getValue() override; + virtual void SAL_CALL setValue( const css::uno::Any& _value ) override; + virtual css::uno::Type SAL_CALL getValueType() override; + + virtual weld::Widget* getWidget() override { return getTypedControlWindow(); } + }; + + //= OHyperlinkControl + typedef CommonBehaviourControl<css::inspection::XHyperlinkControl, weld::Container> OHyperlinkControl_Base; + class OHyperlinkControl final : public OHyperlinkControl_Base + { + private: + std::unique_ptr<weld::Entry> m_xEntry; + std::unique_ptr<weld::Button> m_xButton; + + ::comphelper::OInterfaceContainerHelper2 m_aActionListeners; + + public: + OHyperlinkControl(std::unique_ptr<weld::Container> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly); + + // XPropertyControl + virtual css::uno::Any SAL_CALL getValue() override; + virtual void SAL_CALL setValue( const css::uno::Any& _value ) override; + virtual css::uno::Type SAL_CALL getValueType() override; + + virtual void SetModifyHandler() override + { + m_xEntry->connect_focus_in( LINK( this, CommonBehaviourControlHelper, GetFocusHdl ) ); + m_xEntry->connect_focus_out( LINK( this, CommonBehaviourControlHelper, LoseFocusHdl ) ); + m_xButton->connect_focus_in( LINK( this, CommonBehaviourControlHelper, GetFocusHdl ) ); + m_xButton->connect_focus_out( LINK( this, CommonBehaviourControlHelper, LoseFocusHdl ) ); + + m_xEntry->connect_changed( LINK( this, CommonBehaviourControlHelper, EditModifiedHdl ) ); + } + + virtual weld::Widget* getWidget() override { return getTypedControlWindow(); } + + // XHyperlinkControl + virtual void SAL_CALL addActionListener( const css::uno::Reference< css::awt::XActionListener >& listener ) override; + virtual void SAL_CALL removeActionListener( const css::uno::Reference< css::awt::XActionListener >& listener ) override; + + private: + // XComponent + virtual void SAL_CALL disposing() override; + + DECL_LINK(OnHyperlinkClicked, weld::Button&, void); + }; + + //= ONumericControl + typedef CommonBehaviourControl<css::inspection::XNumericControl, weld::MetricSpinButton> ONumericControl_Base; + class ONumericControl : public ONumericControl_Base + { + private: + FieldUnit m_eValueUnit; + sal_Int16 m_nFieldToUNOValueFactor; + + public: + ONumericControl(std::unique_ptr<weld::MetricSpinButton> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly); + + // XPropertyControl + virtual css::uno::Any SAL_CALL getValue() override; + virtual void SAL_CALL setValue( const css::uno::Any& _value ) override; + virtual css::uno::Type SAL_CALL getValueType() override; + + // XNumericControl + virtual ::sal_Int16 SAL_CALL getDecimalDigits() override; + virtual void SAL_CALL setDecimalDigits( ::sal_Int16 _decimaldigits ) override; + virtual css::beans::Optional< double > SAL_CALL getMinValue() override; + virtual void SAL_CALL setMinValue( const css::beans::Optional< double >& _minvalue ) override; + virtual css::beans::Optional< double > SAL_CALL getMaxValue() override; + virtual void SAL_CALL setMaxValue( const css::beans::Optional< double >& _maxvalue ) override; + virtual ::sal_Int16 SAL_CALL getDisplayUnit() override; + virtual void SAL_CALL setDisplayUnit( ::sal_Int16 _displayunit ) override; + virtual ::sal_Int16 SAL_CALL getValueUnit() override; + virtual void SAL_CALL setValueUnit( ::sal_Int16 _valueunit ) override; + + virtual void SetModifyHandler() override + { + ONumericControl_Base::SetModifyHandler(); + weld::MetricSpinButton* pSpinButton = getTypedControlWindow(); + pSpinButton->connect_value_changed( LINK( this, CommonBehaviourControlHelper, MetricModifiedHdl ) ); + pSpinButton->get_widget().connect_changed( LINK( this, CommonBehaviourControlHelper, EditModifiedHdl ) ); + } + + private: + virtual weld::Widget* getWidget() override { return &getTypedControlWindow()->get_widget(); } + + /** converts an API value (<code>double</code>, as passed into <code>set[Max|Min|]Value) into + a <code>int</code> value which can be passed to our NumericField. + + The conversion respects our decimal digits as well as our value factor (<member>m_nFieldToUNOValueFactor</member>). + */ + sal_Int64 impl_apiValueToFieldValue_nothrow( double nApiValue ) const; + + /** converts a control value, as obtained from our Numeric field, into a value which can passed + to outer callers via our UNO API. + */ + double impl_fieldValueToApiValue_nothrow(sal_Int64 nFieldValue) const; + }; + + //= OColorControl + typedef CommonBehaviourControl<css::inspection::XPropertyControl, ColorListBox> OColorControl_Base; + class OColorControl : public OColorControl_Base + { + public: + OColorControl(std::unique_ptr<ColorListBox> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly); + + // XPropertyControl + virtual css::uno::Any SAL_CALL getValue() override; + virtual void SAL_CALL setValue( const css::uno::Any& _value ) override; + virtual css::uno::Type SAL_CALL getValueType() override; + + virtual void SetModifyHandler() override + { + OColorControl_Base::SetModifyHandler(); + getTypedControlWindow()->SetSelectHdl(LINK(this, CommonBehaviourControlHelper, ColorModifiedHdl)); + } + + protected: + // CommonBehaviourControlHelper::setModified + virtual void setModified() override; + + private: + virtual weld::Widget* getWidget() override { return &getTypedControlWindow()->get_widget(); } + }; + + //= OListboxControl + typedef CommonBehaviourControl<css::inspection::XStringListControl, weld::ComboBox> OListboxControl_Base; + class OListboxControl : public OListboxControl_Base + { + public: + OListboxControl(std::unique_ptr<weld::ComboBox> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly); + + // XPropertyControl + virtual css::uno::Any SAL_CALL getValue() override; + virtual void SAL_CALL setValue( const css::uno::Any& _value ) override; + virtual css::uno::Type SAL_CALL getValueType() override; + + // XStringListControl + virtual void SAL_CALL clearList( ) override; + virtual void SAL_CALL prependListEntry( const OUString& NewEntry ) override; + virtual void SAL_CALL appendListEntry( const OUString& NewEntry ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getListEntries( ) override; + + virtual void SetModifyHandler() override + { + OListboxControl_Base::SetModifyHandler(); + getTypedControlWindow()->connect_changed(LINK(this, CommonBehaviourControlHelper, ModifiedHdl)); + } + + protected: + // CommonBehaviourControlHelper::setModified + virtual void setModified() override; + virtual weld::Widget* getWidget() override { return getTypedControlWindow(); } + }; + + //= OComboboxControl + typedef CommonBehaviourControl< css::inspection::XStringListControl, weld::ComboBox > OComboboxControl_Base; + class OComboboxControl final : public OComboboxControl_Base + { + public: + OComboboxControl(std::unique_ptr<weld::ComboBox> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly); + + // XPropertyControl + virtual css::uno::Any SAL_CALL getValue() override; + virtual void SAL_CALL setValue( const css::uno::Any& _value ) override; + virtual css::uno::Type SAL_CALL getValueType() override; + + // XStringListControl + virtual void SAL_CALL clearList( ) override; + virtual void SAL_CALL prependListEntry( const OUString& NewEntry ) override; + virtual void SAL_CALL appendListEntry( const OUString& NewEntry ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getListEntries( ) override; + + virtual void SetModifyHandler() override + { + OComboboxControl_Base::SetModifyHandler(); + getTypedControlWindow()->connect_changed(LINK(this, CommonBehaviourControlHelper, ModifiedHdl)); + } + + // CommonBehaviourControlHelper::setModified + virtual weld::Widget* getWidget() override { return getTypedControlWindow(); } + + private: + DECL_LINK( OnEntrySelected, weld::ComboBox&, void ); + }; + + + //= DropDownEditControl + + enum MultiLineOperationMode + { + eStringList, + eMultiLineText + }; + + //= OMultilineEditControl + typedef CommonBehaviourControl<css::inspection::XPropertyControl, weld::Container> OMultilineEditControl_Base; + class OMultilineEditControl : public OMultilineEditControl_Base + { + private: + MultiLineOperationMode m_nOperationMode; + std::unique_ptr<weld::Entry> m_xEntry; + std::unique_ptr<weld::MenuButton> m_xButton; + std::unique_ptr<weld::Widget> m_xPopover; + std::unique_ptr<weld::TextView> m_xTextView; + std::unique_ptr<weld::Button> m_xOk; + + void SetTextValue(const OUString& rText); + OUString GetTextValue() const; + + void SetStringListValue( const StlSyntaxSequence< OUString >& _rStrings ); + StlSyntaxSequence< OUString > + GetStringListValue() const; + + DECL_LINK(ButtonHandler, weld::Button&, void); + DECL_LINK(TextViewModifiedHdl, weld::TextView&, void); + + void CheckEntryTextViewMisMatch(); + + public: + OMultilineEditControl(std::unique_ptr<weld::Container> xWidget, std::unique_ptr<weld::Builder> xBuilder, MultiLineOperationMode eMode, bool bReadOnly); + + // XPropertyControl + virtual css::uno::Any SAL_CALL getValue() override; + virtual void SAL_CALL setValue( const css::uno::Any& _value ) override; + virtual css::uno::Type SAL_CALL getValueType() override; + virtual weld::Widget* getWidget() override { return getTypedControlWindow(); } + + virtual void editChanged() override; + + virtual void SetModifyHandler() override + { + m_xEntry->connect_focus_in( LINK( this, CommonBehaviourControlHelper, GetFocusHdl ) ); + m_xEntry->connect_focus_out( LINK( this, CommonBehaviourControlHelper, LoseFocusHdl ) ); + m_xButton->connect_focus_in( LINK( this, CommonBehaviourControlHelper, GetFocusHdl ) ); + m_xButton->connect_focus_out( LINK( this, CommonBehaviourControlHelper, LoseFocusHdl ) ); + + m_xEntry->connect_changed( LINK( this, CommonBehaviourControlHelper, EditModifiedHdl ) ); + m_xTextView->connect_changed( LINK( this, OMultilineEditControl, TextViewModifiedHdl ) ); + } + + virtual void SAL_CALL disposing() override + { + m_xOk.reset(); + m_xTextView.reset(); + m_xButton.reset(); + m_xEntry.reset(); + OMultilineEditControl_Base::disposing(); + } + + }; + +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/stringrepresentation.cxx b/extensions/source/propctrlr/stringrepresentation.cxx new file mode 100644 index 0000000000..6f40c09c9d --- /dev/null +++ b/extensions/source/propctrlr/stringrepresentation.cxx @@ -0,0 +1,604 @@ +/* -*- 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 <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/inspection/XStringRepresentation.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/script/CannotConvertException.hpp> +#include <com/sun/star/script/XTypeConverter.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/reflection/XConstantsTypeDescription.hpp> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/util/Time.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <connectivity/dbconversion.hxx> +#include <osl/diagnose.h> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <utility> +#include <yesno.hrc> +#include <comphelper/types.hxx> +#include <o3tl/string_view.hxx> +#include "modulepcr.hxx" + +#include <algorithm> + +namespace pcr{ + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace { + +class StringRepresentation: + public ::cppu::WeakImplHelper< + lang::XServiceInfo, + inspection::XStringRepresentation, + lang::XInitialization> +{ +public: + explicit StringRepresentation(uno::Reference< uno::XComponentContext > context); + StringRepresentation (const StringRepresentation&) = delete; + StringRepresentation& operator=(const StringRepresentation&) = delete; + + // lang::XServiceInfo: + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString & ServiceName) override; + virtual uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // inspection::XStringRepresentation: + virtual OUString SAL_CALL convertToControlValue(const uno::Any & PropertyValue) override; + virtual uno::Any SAL_CALL convertToPropertyValue(const OUString & ControlValue, const uno::Type & ControlValueType) override; + + // lang::XInitialization: + virtual void SAL_CALL initialize(const uno::Sequence< uno::Any > & aArguments) override; + +private: + virtual ~StringRepresentation() override {} + + /** converts a generic value into a string representation + + If you want to convert values whose string representation does not depend + on a concrete property, use this version + + @return <TRUE/> + if and only if the value could be converted + */ + static bool convertGenericValueToString( + const uno::Any& _rValue, + OUString& _rStringRep + ); + + /** converts string representation into generic value + + If you want to convert values whose string representation does not depend + on a concrete property, use this version + + @return <TRUE/> + if and only if the value could be converted + */ + static bool convertStringToGenericValue( + const OUString& _rStringRep, + uno::Any& _rValue, + const uno::Type& _rTargetType + ); + + /** uses the simple convert method from the type converter + * + * \param _rValue the value to be converted + * \return the converted string. + */ + OUString convertSimpleToString( const uno::Any& _rValue ); + + /** converts a string into his constant value if it exists, otherwise the type converter is used. + * \param _rValue the value to be converted + * \param _ePropertyType the type of the property to be converted into + * \return the converted value + */ + uno::Any convertStringToSimple( const OUString& _rValue,const uno::TypeClass& _ePropertyType ); + + uno::Reference< uno::XComponentContext > m_xContext; + uno::Reference< script::XTypeConverter > m_xTypeConverter; + uno::Reference< reflection::XConstantsTypeDescription > m_xTypeDescription; + uno::Sequence< OUString > m_aValues; + uno::Sequence< uno::Reference< reflection::XConstantTypeDescription> > m_aConstants; + +}; + +} + +StringRepresentation::StringRepresentation(uno::Reference< uno::XComponentContext > context) : + m_xContext(std::move(context)) +{} + +// com.sun.star.uno.XServiceInfo: +OUString SAL_CALL StringRepresentation::getImplementationName() +{ + return "StringRepresentation"; +} + +sal_Bool SAL_CALL StringRepresentation::supportsService(OUString const & serviceName) +{ + return cppu::supportsService(this, serviceName); +} + +uno::Sequence< OUString > SAL_CALL StringRepresentation::getSupportedServiceNames() +{ + return { "com.sun.star.inspection.StringRepresentation" }; +} + +// inspection::XStringRepresentation: +OUString SAL_CALL StringRepresentation::convertToControlValue(const uno::Any & PropertyValue) +{ + OUString sReturn; + if ( !convertGenericValueToString( PropertyValue, sReturn ) ) + { + sReturn = convertSimpleToString( PropertyValue ); +#ifdef DBG_UTIL + if ( sReturn.isEmpty() && PropertyValue.hasValue() ) + { + SAL_WARN( "extensions.propctrlr", "StringRepresentation::convertPropertyValueToStringRepresentation: cannot convert values of type '" + << PropertyValue.getValueType().getTypeName() + << "'!" ); + } +#endif + } + + return sReturn; +} + +uno::Any SAL_CALL StringRepresentation::convertToPropertyValue(const OUString & ControlValue, const uno::Type & ControlValueType) +{ + uno::Any aReturn; + + uno::TypeClass ePropertyType = ControlValueType.getTypeClass(); + switch ( ePropertyType ) + { + case uno::TypeClass_FLOAT: + case uno::TypeClass_DOUBLE: + case uno::TypeClass_BYTE: + case uno::TypeClass_SHORT: + case uno::TypeClass_LONG: + case uno::TypeClass_HYPER: + case uno::TypeClass_UNSIGNED_SHORT: + case uno::TypeClass_UNSIGNED_LONG: + case uno::TypeClass_UNSIGNED_HYPER: + try + { + aReturn = convertStringToSimple(ControlValue, ePropertyType); + } + catch( const script::CannotConvertException& ) { } + catch( const lang::IllegalArgumentException& ) { } + break; + + default: + #if OSL_DEBUG_LEVEL > 0 + bool bCanConvert = + #endif + convertStringToGenericValue( ControlValue, aReturn, ControlValueType ); + + #if OSL_DEBUG_LEVEL > 0 + // could not convert ... + if ( !bCanConvert && !ControlValue.isEmpty() ) + { + SAL_WARN( "extensions.propctrlr", "StringRepresentation::convertStringRepresentationToPropertyValue: cannot convert into values of type '" + << ControlValueType.getTypeName() << "'!" ); + } + #endif + } + + return aReturn; +} + +namespace { + +// This comparison functor assumes an underlying set of constants with pairwise +// unequal values that are all of UNO SHORT or LONG type: +struct CompareConstants { + bool operator ()( + css::uno::Reference< css::reflection::XConstantTypeDescription > const & + c1, + css::uno::Reference< css::reflection::XConstantTypeDescription > const & + c2) const + { + return c1->getConstantValue().get<sal_Int32>() + < c2->getConstantValue().get<sal_Int32>(); + } +}; + +} + +// lang::XInitialization: +void SAL_CALL StringRepresentation::initialize(const uno::Sequence< uno::Any > & aArguments) +{ + sal_Int32 nLength = aArguments.getLength(); + if ( !nLength ) + return; + + const uno::Any* pIter = aArguments.getConstArray(); + m_xTypeConverter.set(*pIter++,uno::UNO_QUERY); + if ( nLength != 3 ) + return; + + OUString sConstantName; + *pIter++ >>= sConstantName; + *pIter >>= m_aValues; + + if ( !m_xContext.is() ) + return; + + uno::Reference< container::XHierarchicalNameAccess > xTypeDescProv( + m_xContext->getValueByName("/singletons/com.sun.star.reflection.theTypeDescriptionManager"), + uno::UNO_QUERY_THROW ); + + m_xTypeDescription.set( xTypeDescProv->getByHierarchicalName( sConstantName ), uno::UNO_QUERY_THROW ); + uno::Sequence< + uno::Reference< reflection::XConstantTypeDescription > > + cs(m_xTypeDescription->getConstants()); + auto [begin, end] = asNonConstRange(cs); + std::sort(begin, end, CompareConstants()); + m_aConstants = cs; +} + +OUString StringRepresentation::convertSimpleToString( const uno::Any& _rValue ) +{ + OUString sReturn; + if ( m_xTypeConverter.is() && _rValue.hasValue() ) + { + try + { + if ( m_aConstants.hasElements() ) + { + sal_Int16 nConstantValue = 0; + if ( _rValue >>= nConstantValue ) + { + const uno::Reference< reflection::XConstantTypeDescription>* pIter = m_aConstants.getConstArray(); + const uno::Reference< reflection::XConstantTypeDescription>* pEnd = pIter + m_aConstants.getLength(); + for(sal_Int32 i = 0;pIter != pEnd;++pIter,++i) + { + if ( (*pIter)->getConstantValue() == _rValue ) + { + OSL_ENSURE(i < m_aValues.getLength() ,"StringRepresentation::convertSimpleToString: Index is not in range of m_aValues"); + sReturn = m_aValues[i]; + break; + } + } + } + } + + if ( sReturn.isEmpty() ) + m_xTypeConverter->convertToSimpleType( _rValue, uno::TypeClass_STRING ) >>= sReturn; + } + catch( const script::CannotConvertException& ) { } + catch( const lang::IllegalArgumentException& ) { } + } + return sReturn; +} + + +namespace +{ + struct ConvertIntegerFromAndToString + { + OUString operator()( sal_Int32 _rIntValue ) const + { + return OUString::number( _rIntValue ); + } + sal_Int32 operator()( std::u16string_view _rStringValue ) const + { + return o3tl::toInt32(_rStringValue); + } + }; + + struct StringIdentity + { + OUString operator()( const OUString& _rValue ) const + { + return _rValue; + } + }; + + template < class ElementType, class Transformer > + OUString composeSequenceElements( const Sequence< ElementType >& _rElements, const Transformer& _rTransformer ) + { + OUStringBuffer sCompose; + + // loop through the elements and concatenate the string representations of the integers + // (separated by a line break) + for (const auto& rElement : _rElements) + { + sCompose.append(OUString(_rTransformer(rElement)) + "\n"); + } + sCompose.stripEnd('\n'); + + return sCompose.makeStringAndClear(); + } + + template < class ElementType, class Transformer > + void splitComposedStringToSequence( std::u16string_view _rComposed, Sequence< ElementType >& _out_SplitUp, const Transformer& _rTransformer ) + { + _out_SplitUp.realloc( 0 ); + if ( _rComposed.empty() ) + return; + sal_Int32 tokenPos = 0; + do + { + _out_SplitUp.realloc( _out_SplitUp.getLength() + 1 ); + _out_SplitUp.getArray()[ _out_SplitUp.getLength() - 1 ] = static_cast<ElementType>(_rTransformer( OUString(o3tl::getToken(_rComposed, 0, '\n', tokenPos )) )); + } + while ( tokenPos != -1 ); + } +} + + +bool StringRepresentation::convertGenericValueToString( const uno::Any& _rValue, OUString& _rStringRep ) +{ + bool bCanConvert = true; + + switch ( _rValue.getValueTypeClass() ) + { + case uno::TypeClass_STRING: + _rValue >>= _rStringRep; + break; + + case uno::TypeClass_BOOLEAN: + { + bool bValue = false; + _rValue >>= bValue; + _rStringRep = bValue ? PcrRes(RID_RSC_ENUM_YESNO[1]) + : PcrRes(RID_RSC_ENUM_YESNO[0]); + } + break; + + // some sequence types + case uno::TypeClass_SEQUENCE: + { + Sequence< OUString > aStringValues; + Sequence< sal_Int8 > aInt8Values; + Sequence< sal_uInt16 > aUInt16Values; + Sequence< sal_Int16 > aInt16Values; + Sequence< sal_uInt32 > aUInt32Values; + Sequence< sal_Int32 > aInt32Values; + + // string sequences + if ( _rValue >>= aStringValues ) + { + _rStringRep = composeSequenceElements( aStringValues, StringIdentity() ); + } + // byte sequences + else if ( _rValue >>= aInt8Values ) + { + _rStringRep = composeSequenceElements( aInt8Values, ConvertIntegerFromAndToString() ); + } + // uInt16 sequences + else if ( _rValue >>= aUInt16Values ) + { + _rStringRep = composeSequenceElements( aUInt16Values, ConvertIntegerFromAndToString() ); + } + // Int16 sequences + else if ( _rValue >>= aInt16Values ) + { + _rStringRep = composeSequenceElements( aInt16Values, ConvertIntegerFromAndToString() ); + } + // uInt32 sequences + else if ( _rValue >>= aUInt32Values ) + { + _rStringRep = composeSequenceElements( aUInt32Values, ConvertIntegerFromAndToString() ); + } + // Int32 sequences + else if ( _rValue >>= aInt32Values ) + { + _rStringRep = composeSequenceElements( aInt32Values, ConvertIntegerFromAndToString() ); + } + else + bCanConvert = false; + } + break; + case uno::TypeClass_CONSTANT: + break; + + // some structs + case uno::TypeClass_STRUCT: + OSL_FAIL( "StringRepresentation::convertGenericValueToString(STRUCT): this is dead code - isn't it?" ); + if ( _rValue.getValueType().equals( cppu::UnoType< util::Date >::get() )) + { + // weird enough, the string representation of dates, as used + // by the control displaying dates, and thus as passed through the layers, + // is YYYYMMDD. + util::Date aUnoDate; + _rValue >>= aUnoDate; + _rStringRep = ::dbtools::DBTypeConversion::toDateString(aUnoDate); + } + else if ( _rValue.getValueType().equals( cppu::UnoType< util::Time >::get() )) + { + // similar for time (HHMMSSHH) + util::Time aUnoTime; + _rValue >>= aUnoTime; + _rStringRep = ::dbtools::DBTypeConversion::toTimeString(aUnoTime); + } + else if ( _rValue.getValueType().equals( cppu::UnoType< util::DateTime >::get() )) + { + util::DateTime aUnoDateTime; + _rValue >>= aUnoDateTime; + _rStringRep = ::dbtools::DBTypeConversion::toDateTimeString(aUnoDateTime); + } + else + bCanConvert = false; + break; + + default: + bCanConvert = false; + break; + } + + return bCanConvert; +} + +uno::Any StringRepresentation::convertStringToSimple( const OUString& _rValue,const uno::TypeClass& _ePropertyType ) +{ + uno::Any aReturn; + if ( m_xTypeConverter.is() && !_rValue.isEmpty() ) + { + try + { + if ( m_aConstants.hasElements() && m_aValues.hasElements() ) + { + const OUString* pIter = m_aValues.getConstArray(); + const OUString* pEnd = pIter + m_aValues.getLength(); + for(sal_Int32 i = 0;pIter != pEnd;++pIter,++i) + { + if ( *pIter == _rValue ) + { + OSL_ENSURE(i < m_aConstants.getLength() ,"StringRepresentation::convertSimpleToString: Index is not in range of m_aValues"); + aReturn = m_aConstants[i]->getConstantValue(); + break; + } + } + } + + if ( !aReturn.hasValue() ) + aReturn = m_xTypeConverter->convertToSimpleType( Any( _rValue ), _ePropertyType ); + } + catch( const script::CannotConvertException& ) { } + catch( const lang::IllegalArgumentException& ) { } + } + return aReturn; +} + +bool StringRepresentation::convertStringToGenericValue( const OUString& _rStringRep, uno::Any& _rValue, const uno::Type& _rTargetType ) +{ + bool bCanConvert = true; + + switch ( _rTargetType.getTypeClass() ) + { + case uno::TypeClass_STRING: + _rValue <<= _rStringRep; + break; + + case uno::TypeClass_BOOLEAN: + { + _rValue <<= PcrRes(RID_RSC_ENUM_YESNO[0]) != _rStringRep; + } + break; + + case uno::TypeClass_SEQUENCE: + { + uno::Type aElementType = ::comphelper::getSequenceElementType( _rTargetType ); + + switch ( aElementType.getTypeClass() ) + { + case uno::TypeClass_STRING: + { + Sequence< OUString > aElements; + splitComposedStringToSequence( _rStringRep, aElements, StringIdentity() ); + _rValue <<= aElements; + } + break; + case uno::TypeClass_SHORT: + { + Sequence< sal_Int16 > aElements; + splitComposedStringToSequence( _rStringRep, aElements, ConvertIntegerFromAndToString() ); + _rValue <<= aElements; + } + break; + case uno::TypeClass_UNSIGNED_SHORT: + { + Sequence< sal_uInt16 > aElements; + splitComposedStringToSequence( _rStringRep, aElements, ConvertIntegerFromAndToString() ); + _rValue <<= aElements; + } + break; + case uno::TypeClass_LONG: + { + Sequence< sal_Int32 > aElements; + splitComposedStringToSequence( _rStringRep, aElements, ConvertIntegerFromAndToString() ); + _rValue <<= aElements; + } + break; + case uno::TypeClass_UNSIGNED_LONG: + { + Sequence< sal_uInt32 > aElements; + splitComposedStringToSequence( _rStringRep, aElements, ConvertIntegerFromAndToString() ); + _rValue <<= aElements; + } + break; + case uno::TypeClass_BYTE: + { + Sequence< sal_Int8 > aElements; + splitComposedStringToSequence( _rStringRep, aElements, ConvertIntegerFromAndToString() ); + _rValue <<= aElements; + } + break; + default: + bCanConvert = false; + break; + } + } + break; + + case uno::TypeClass_STRUCT: + OSL_FAIL( "StringRepresentation::convertStringToGenericValue(STRUCT): this is dead code - isn't it?" ); + if ( _rTargetType.equals( cppu::UnoType< util::Date >::get() )) + { + // weird enough, the string representation of dates, as used + // by the control displaying dates, and thus as passed through the layers, + // is YYYYMMDD. + + _rValue <<= ::dbtools::DBTypeConversion::toDate(_rStringRep); + } + else if ( _rTargetType.equals( cppu::UnoType< util::Time >::get() )) + { + // similar for time (HHMMSSHH) + _rValue <<= ::dbtools::DBTypeConversion::toTime(_rStringRep); + } + else if ( _rTargetType.equals( cppu::UnoType< util::DateTime >::get() )) + { + _rValue <<= ::dbtools::DBTypeConversion::toDateTime(_rStringRep); + } + else + bCanConvert = false; + break; + + default: + bCanConvert = false; + break; + } + + return bCanConvert; +} + + +} // pcr + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_StringRepresentation_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::StringRepresentation(context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/submissionhandler.cxx b/extensions/source/propctrlr/submissionhandler.cxx new file mode 100644 index 0000000000..f3cc5dad60 --- /dev/null +++ b/extensions/source/propctrlr/submissionhandler.cxx @@ -0,0 +1,431 @@ +/* -*- 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 "submissionhandler.hxx" +#include "formmetadata.hxx" +#include "formstrings.hxx" +#include "handlerhelper.hxx" + +#include <com/sun/star/form/FormButtonType.hpp> +#include <com/sun/star/form/submission/XSubmissionSupplier.hpp> +#include <com/sun/star/inspection/XObjectInspectorUI.hpp> +#include <com/sun/star/lang/NullPointerException.hpp> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> + + +namespace pcr +{ + + + using namespace ::comphelper; + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::script; + using namespace ::com::sun::star::form; + using namespace ::com::sun::star::xforms; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::inspection; + + + //= SubmissionHelper + + + SubmissionHelper::SubmissionHelper( ::osl::Mutex& _rMutex, const Reference< XPropertySet >& _rxIntrospectee, const Reference< frame::XModel >& _rxContextDocument ) + :EFormsHelper( _rMutex, _rxIntrospectee, _rxContextDocument ) + { + OSL_ENSURE( canTriggerSubmissions( _rxIntrospectee, _rxContextDocument ), + "SubmissionHelper::SubmissionHelper: you should not have instantiated me!" ); + } + + + bool SubmissionHelper::canTriggerSubmissions( const Reference< XPropertySet >& _rxControlModel, + const Reference< frame::XModel >& _rxContextDocument ) + { + if ( !EFormsHelper::isEForm( _rxContextDocument ) ) + return false; + + try + { + Reference< submission::XSubmissionSupplier > xSubmissionSupp( _rxControlModel, UNO_QUERY ); + if ( xSubmissionSupp.is() ) + return true; + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "SubmissionHelper::canTriggerSubmissions" ); + } + return false; + } + + + //= SubmissionPropertyHandler + + + SubmissionPropertyHandler::SubmissionPropertyHandler( const Reference< XComponentContext >& _rxContext ) + :PropertyHandlerComponent( _rxContext ) + ,OPropertyChangeListener( m_aMutex ) + { + } + + + SubmissionPropertyHandler::~SubmissionPropertyHandler( ) + { + disposeAdapter(); + } + + + OUString SubmissionPropertyHandler::getImplementationName( ) + { + return "com.sun.star.comp.extensions.SubmissionPropertyHandler"; + } + + + Sequence< OUString > SubmissionPropertyHandler::getSupportedServiceNames( ) + { + return { "com.sun.star.form.inspection.SubmissionPropertyHandler" }; + } + + + Any SAL_CALL SubmissionPropertyHandler::getPropertyValue( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + OSL_ENSURE(m_pHelper, "SubmissionPropertyHandler::getPropertyValue: inconsistency!"); + // if we survived impl_getPropertyId_throwUnknownProperty, we should have a helper, since no helper implies no properties + + Any aReturn; + try + { + switch ( nPropId ) + { + case PROPERTY_ID_SUBMISSION_ID: + { + Reference< submission::XSubmissionSupplier > xSubmissionSupp( m_xComponent, UNO_QUERY ); + OSL_ENSURE( xSubmissionSupp.is(), "SubmissionPropertyHandler::getPropertyValue: this should never happen ..." ); + // this handler is not intended for components which are no XSubmissionSupplier + Reference< submission::XSubmission > xSubmission; + if ( xSubmissionSupp.is() ) + xSubmission = xSubmissionSupp->getSubmission( ); + aReturn <<= xSubmission; + } + break; + + case PROPERTY_ID_XFORMS_BUTTONTYPE: + { + FormButtonType eType = FormButtonType_PUSH; + OSL_VERIFY( m_xComponent->getPropertyValue( PROPERTY_BUTTONTYPE ) >>= eType ); + if ( ( eType != FormButtonType_PUSH ) && ( eType != FormButtonType_SUBMIT ) ) + eType = FormButtonType_PUSH; + aReturn <<= eType; + } + break; + + default: + OSL_FAIL( "SubmissionPropertyHandler::getPropertyValue: cannot handle this property!" ); + break; + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "SubmissionPropertyHandler::getPropertyValue" ); + } + + return aReturn; + } + + + void SAL_CALL SubmissionPropertyHandler::setPropertyValue( const OUString& _rPropertyName, const Any& _rValue ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + OSL_ENSURE(m_pHelper, "SubmissionPropertyHandler::setPropertyValue: inconsistency!"); + // if we survived impl_getPropertyId_throwUnknownProperty, we should have a helper, since no helper implies no properties + + try + { + switch ( nPropId ) + { + case PROPERTY_ID_SUBMISSION_ID: + { + Reference< submission::XSubmission > xSubmission; + OSL_VERIFY( _rValue >>= xSubmission ); + + Reference< submission::XSubmissionSupplier > xSubmissionSupp( m_xComponent, UNO_QUERY ); + OSL_ENSURE( xSubmissionSupp.is(), "SubmissionPropertyHandler::setPropertyValue: this should never happen ..." ); + // this handler is not intended for components which are no XSubmissionSupplier + if ( xSubmissionSupp.is() ) + { + xSubmissionSupp->setSubmission( xSubmission ); + impl_setContextDocumentModified_nothrow(); + } + } + break; + + case PROPERTY_ID_XFORMS_BUTTONTYPE: + m_xComponent->setPropertyValue( PROPERTY_BUTTONTYPE, _rValue ); + break; + + default: + OSL_FAIL( "SubmissionPropertyHandler::setPropertyValue: cannot handle this id!" ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "SubmissionPropertyHandler::setPropertyValue" ); + } + } + + + Sequence< OUString > SAL_CALL SubmissionPropertyHandler::getActuatingProperties( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if (!m_pHelper) + return Sequence< OUString >(); + + Sequence<OUString> aReturn { PROPERTY_XFORMS_BUTTONTYPE }; + return aReturn; + } + + + Sequence< OUString > SAL_CALL SubmissionPropertyHandler::getSupersededProperties( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if (!m_pHelper) + return Sequence< OUString >(); + + Sequence< OUString > aReturn{ PROPERTY_TARGET_URL, + PROPERTY_TARGET_FRAME, + PROPERTY_BUTTONTYPE }; + return aReturn; + } + + + void SubmissionPropertyHandler::onNewComponent() + { + if ( m_xPropChangeMultiplexer.is() ) + { + m_xPropChangeMultiplexer->dispose(); + m_xPropChangeMultiplexer.clear(); + } + + PropertyHandlerComponent::onNewComponent(); + + Reference< frame::XModel > xDocument( impl_getContextDocument_nothrow() ); + DBG_ASSERT( xDocument.is(), "SubmissionPropertyHandler::onNewComponent: no document!" ); + + m_pHelper.reset(); + + if ( SubmissionHelper::canTriggerSubmissions( m_xComponent, xDocument ) ) + { + m_pHelper.reset( new SubmissionHelper( m_aMutex, m_xComponent, xDocument ) ); + + m_xPropChangeMultiplexer = new OPropertyChangeMultiplexer( this, m_xComponent ); + m_xPropChangeMultiplexer->addProperty( PROPERTY_BUTTONTYPE ); + } + } + + + Sequence< Property > SubmissionPropertyHandler::doDescribeSupportedProperties() const + { + std::vector< Property > aProperties; + if (m_pHelper) + { + implAddPropertyDescription( aProperties, PROPERTY_SUBMISSION_ID, cppu::UnoType<submission::XSubmission>::get() ); + implAddPropertyDescription( aProperties, PROPERTY_XFORMS_BUTTONTYPE, ::cppu::UnoType<FormButtonType>::get() ); + } + if ( aProperties.empty() ) + return Sequence< Property >(); + return comphelper::containerToSequence(aProperties); + } + + + LineDescriptor SAL_CALL SubmissionPropertyHandler::describePropertyLine( const OUString& _rPropertyName, + const Reference< XPropertyControlFactory >& _rxControlFactory ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !_rxControlFactory.is() ) + throw NullPointerException(); + if (!m_pHelper) + throw RuntimeException(); + + std::vector< OUString > aListEntries; + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + switch ( nPropId ) + { + case PROPERTY_ID_SUBMISSION_ID: + m_pHelper->getAllElementUINames(EFormsHelper::Submission, aListEntries, false); + break; + + case PROPERTY_ID_XFORMS_BUTTONTYPE: + { + // available options are nearly the same as for the "normal" button type, but only the + // first two options + aListEntries = m_pInfoService->getPropertyEnumRepresentations( PROPERTY_ID_BUTTONTYPE ); + aListEntries.resize( 2 ); + } + break; + + default: + OSL_FAIL( "SubmissionPropertyHandler::describePropertyLine: cannot handle this id!" ); + return LineDescriptor(); + } + + LineDescriptor aDescriptor; + aDescriptor.Control = PropertyHandlerHelper::createListBoxControl( _rxControlFactory, std::move(aListEntries), false, true ); + aDescriptor.DisplayName = m_pInfoService->getPropertyTranslation( nPropId ); + aDescriptor.Category = "General"; + aDescriptor.HelpURL = HelpIdUrl::getHelpURL( m_pInfoService->getPropertyHelpId( nPropId ) ); + return aDescriptor; + } + + + void SAL_CALL SubmissionPropertyHandler::actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const Any& _rNewValue, const Any& /*_rOldValue*/, const Reference< XObjectInspectorUI >& _rxInspectorUI, sal_Bool ) + { + if ( !_rxInspectorUI.is() ) + throw NullPointerException(); + + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nActuatingPropId( impl_getPropertyId_throwRuntime( _rActuatingPropertyName ) ); + OSL_PRECOND(m_pHelper, + "SubmissionPropertyHandler::actuatingPropertyChanged: inconsistency!"); + // if we survived impl_getPropertyId_throwRuntime, we should have a helper, since no helper implies no properties + + switch ( nActuatingPropId ) + { + case PROPERTY_ID_XFORMS_BUTTONTYPE: + { + FormButtonType eType = FormButtonType_PUSH; + OSL_VERIFY( _rNewValue >>= eType ); + _rxInspectorUI->enablePropertyUI( PROPERTY_SUBMISSION_ID, eType == FormButtonType_SUBMIT ); + } + break; + + default: + OSL_FAIL( "SubmissionPropertyHandler::actuatingPropertyChanged: cannot handle this id!" ); + } + } + + + Any SAL_CALL SubmissionPropertyHandler::convertToPropertyValue( const OUString& _rPropertyName, const Any& _rControlValue ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + Any aPropertyValue; + + OSL_ENSURE( + m_pHelper, + "SubmissionPropertyHandler::convertToPropertyValue: we have no SupportedProperties!"); + if (!m_pHelper) + return aPropertyValue; + + OUString sControlValue; + OSL_VERIFY( _rControlValue >>= sControlValue ); + + PropertyId nPropId( m_pInfoService->getPropertyId( _rPropertyName ) ); + switch ( nPropId ) + { + case PROPERTY_ID_SUBMISSION_ID: + { + Reference< XSubmission > xSubmission( m_pHelper->getModelElementFromUIName( EFormsHelper::Submission, sControlValue ), UNO_QUERY ); + aPropertyValue <<= xSubmission; + } + break; + + case PROPERTY_ID_XFORMS_BUTTONTYPE: + { + ::rtl::Reference< IPropertyEnumRepresentation > aEnumConversion( + new DefaultEnumRepresentation( *m_pInfoService, ::cppu::UnoType<FormButtonType>::get(), PROPERTY_ID_BUTTONTYPE ) ); + // TODO/UNOize: make aEnumConversion a member? + aEnumConversion->getValueFromDescription( sControlValue, aPropertyValue ); + } + break; + + default: + OSL_FAIL( "SubmissionPropertyHandler::convertToPropertyValue: cannot handle this id!" ); + } + + return aPropertyValue; + } + + + Any SAL_CALL SubmissionPropertyHandler::convertToControlValue( const OUString& _rPropertyName, const Any& _rPropertyValue, const Type& _rControlValueType ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + Any aControlValue; + + OSL_ENSURE( + m_pHelper, + "SubmissionPropertyHandler::convertToControlValue: we have no SupportedProperties!"); + if (!m_pHelper) + return aControlValue; + + OSL_ENSURE( _rControlValueType.getTypeClass() == TypeClass_STRING, + "SubmissionPropertyHandler::convertToControlValue: all our controls should use strings for value exchange!" ); + + PropertyId nPropId( m_pInfoService->getPropertyId( _rPropertyName ) ); + switch ( nPropId ) + { + case PROPERTY_ID_SUBMISSION_ID: + { + Reference< XPropertySet > xSubmission( _rPropertyValue, UNO_QUERY ); + if ( xSubmission.is() ) + aControlValue <<= EFormsHelper::getModelElementUIName( EFormsHelper::Submission, xSubmission ); + } + break; + + case PROPERTY_ID_XFORMS_BUTTONTYPE: + { + ::rtl::Reference< IPropertyEnumRepresentation > aEnumConversion( + new DefaultEnumRepresentation( *m_pInfoService, _rPropertyValue.getValueType(), PROPERTY_ID_BUTTONTYPE ) ); + // TODO/UNOize: make aEnumConversion a member? + aControlValue <<= aEnumConversion->getDescriptionForValue( _rPropertyValue ); + } + break; + + default: + OSL_FAIL( "SubmissionPropertyHandler::convertToControlValue: cannot handle this id!" ); + } + + return aControlValue; + } + + + void SubmissionPropertyHandler::_propertyChanged( const PropertyChangeEvent& _rEvent ) + { + if ( _rEvent.PropertyName == PROPERTY_BUTTONTYPE ) + firePropertyChange( PROPERTY_XFORMS_BUTTONTYPE, PROPERTY_ID_XFORMS_BUTTONTYPE, _rEvent.OldValue, _rEvent.NewValue ); + } + + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_SubmissionPropertyHandler_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::SubmissionPropertyHandler(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/submissionhandler.hxx b/extensions/source/propctrlr/submissionhandler.hxx new file mode 100644 index 0000000000..f263041b21 --- /dev/null +++ b/extensions/source/propctrlr/submissionhandler.hxx @@ -0,0 +1,110 @@ +/* -*- 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 . + */ + +#pragma once + +#include <memory> +#include "propertyhandler.hxx" +#include "eformshelper.hxx" + +#include <comphelper/propmultiplex.hxx> +#include <rtl/ref.hxx> + +namespace comphelper +{ + class OPropertyChangeMultiplexer; +} + + +namespace pcr +{ + + + //= SubmissionHelper + + class SubmissionHelper : public EFormsHelper + { + public: + SubmissionHelper( + osl::Mutex& _rMutex, + const css::uno::Reference< css::beans::XPropertySet >& _rxIntrospectee, + const css::uno::Reference< css::frame::XModel >& _rxContextDocument + ); + + /** determines whether the given control model is able to trigger submissions + + Instances of the <type>SubmissionHelper</type> class should not be instantiated + for components where this method returned <FALSE/> + */ + static bool canTriggerSubmissions( + const css::uno::Reference< css::beans::XPropertySet >& _rxControlModel, + const css::uno::Reference< css::frame::XModel >& _rxContextDocument + ); + }; + + + //= SubmissionPropertyHandler + + /** a property handler for any virtual string properties + */ + class SubmissionPropertyHandler : public PropertyHandlerComponent, public ::comphelper::OPropertyChangeListener + { + private: + std::unique_ptr< SubmissionHelper > m_pHelper; + rtl::Reference<::comphelper::OPropertyChangeMultiplexer> m_xPropChangeMultiplexer; + + public: + explicit SubmissionPropertyHandler( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext + ); + + virtual ~SubmissionPropertyHandler() override; + protected: + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames () override; + + // XPropertyHandler overriables + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& _rPropertyName ) override; + virtual void SAL_CALL setPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rValue ) override; + virtual css::uno::Sequence< OUString > + SAL_CALL getActuatingProperties( ) override; + virtual css::uno::Sequence< OUString > + SAL_CALL getSupersededProperties( ) override; + virtual css::inspection::LineDescriptor + SAL_CALL describePropertyLine( const OUString& _rPropertyName, const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory ) override; + virtual void SAL_CALL actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const css::uno::Any& _rNewValue, const css::uno::Any& _rOldValue, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI, sal_Bool ) override; + virtual css::uno::Any SAL_CALL convertToPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rControlValue ) override; + virtual css::uno::Any SAL_CALL convertToControlValue( const OUString& _rPropertyName, const css::uno::Any& _rPropertyValue, const css::uno::Type& _rControlValueType ) override; + + // PropertyHandler overridables + virtual css::uno::Sequence< css::beans::Property > + doDescribeSupportedProperties() const override; + virtual void onNewComponent() override; + + private: + // OPropertyChangeListener + virtual void _propertyChanged(const css::beans::PropertyChangeEvent& _rEvent) override; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/taborder.cxx b/extensions/source/propctrlr/taborder.cxx new file mode 100644 index 0000000000..af7b9ced74 --- /dev/null +++ b/extensions/source/propctrlr/taborder.cxx @@ -0,0 +1,320 @@ +/* -*- 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 "taborder.hxx" + +#include <bitmaps.hlst> +#include "formstrings.hxx" +#include <comphelper/types.hxx> +#include <comphelper/property.hxx> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/form/runtime/FormController.hpp> +#include <osl/diagnose.h> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> + +namespace pcr +{ + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::awt; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::form; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::datatransfer; + + namespace { + + OUString GetImage( const Reference< XPropertySet >& _rxSet ) + { + OUString sImageId = RID_EXTBMP_CONTROL; + // TODO: classify controls also in Basic propbrw + if ( _rxSet.is() && ::comphelper::hasProperty( PROPERTY_CLASSID, _rxSet ) ) + { + switch( ::comphelper::getINT16( _rxSet->getPropertyValue( PROPERTY_CLASSID ) ) ) + { + case FormComponentType::COMMANDBUTTON: sImageId = RID_EXTBMP_BUTTON; break; + case FormComponentType::FIXEDTEXT: sImageId = RID_EXTBMP_FIXEDTEXT; break; + case FormComponentType::TEXTFIELD: sImageId = RID_EXTBMP_EDITBOX; break; + case FormComponentType::RADIOBUTTON: sImageId = RID_EXTBMP_RADIOBUTTON; break; + case FormComponentType::CHECKBOX: sImageId = RID_EXTBMP_CHECKBOX; break; + case FormComponentType::LISTBOX: sImageId = RID_EXTBMP_LISTBOX; break; + case FormComponentType::COMBOBOX: sImageId = RID_EXTBMP_COMBOBOX; break; + case FormComponentType::GROUPBOX: sImageId = RID_EXTBMP_GROUPBOX; break; + case FormComponentType::IMAGEBUTTON: sImageId = RID_EXTBMP_IMAGEBUTTON; break; + case FormComponentType::FILECONTROL: sImageId = RID_EXTBMP_FILECONTROL; break; + case FormComponentType::HIDDENCONTROL: sImageId = RID_EXTBMP_HIDDEN; break; + case FormComponentType::DATEFIELD: sImageId = RID_EXTBMP_DATEFIELD; break; + case FormComponentType::TIMEFIELD: sImageId = RID_EXTBMP_TIMEFIELD; break; + case FormComponentType::NUMERICFIELD: sImageId = RID_EXTBMP_NUMERICFIELD; break; + case FormComponentType::CURRENCYFIELD: sImageId = RID_EXTBMP_CURRENCYFIELD; break; + case FormComponentType::PATTERNFIELD: sImageId = RID_EXTBMP_PATTERNFIELD; break; + case FormComponentType::IMAGECONTROL: sImageId = RID_EXTBMP_IMAGECONTROL; break; + case FormComponentType::GRIDCONTROL: sImageId = RID_EXTBMP_GRID; break; + case FormComponentType::SCROLLBAR: sImageId = RID_EXTBMP_SCROLLBAR; break; + case FormComponentType::SPINBUTTON: sImageId = RID_EXTBMP_SPINBUTTON; break; + case FormComponentType::NAVIGATIONBAR: sImageId = RID_EXTBMP_NAVIGATIONBAR; break; + default: + OSL_FAIL( "TabOrderDialog::GetImage: unknown control type" ); + } + } + + return sImageId; + } + + //= OSimpleTabModel + + class OSimpleTabModel : public ::cppu::WeakImplHelper< XTabControllerModel> + { + Sequence< Reference< XControlModel > > m_aModels; + + public: + explicit OSimpleTabModel( const Sequence< Reference< XControlModel > >& _rModels ) + :m_aModels( _rModels ) + { + } + + // XTabControllerModel + virtual void SAL_CALL setControlModels(const Sequence< Reference< XControlModel > >& rModels) override {m_aModels = rModels;} + virtual Sequence< Reference< XControlModel > > SAL_CALL getControlModels() override {return m_aModels;} + virtual void SAL_CALL setGroup(const Sequence< Reference< XControlModel > >& /*Group*/, const OUString& /*GroupName*/) override {} + virtual sal_Int32 SAL_CALL getGroupCount() override {return 0;} + virtual void SAL_CALL getGroup(sal_Int32 /*nGroup*/, Sequence< Reference< XControlModel > >& /*Group*/, OUString& /*Name*/) override {} + virtual void SAL_CALL getGroupByName(const OUString& /*Name*/, Sequence< Reference< XControlModel > >& /*Group*/) override {} + virtual sal_Bool SAL_CALL getGroupControl() override {return false;} ; + virtual void SAL_CALL setGroupControl(sal_Bool /*GroupControl*/) override {}; + }; + + } + + //= TabOrderDialog + TabOrderDialog::TabOrderDialog(weld::Window* _pParent, const Reference< XTabControllerModel >& _rxTabModel, + const Reference< XControlContainer >& _rxControlCont, const Reference< XComponentContext >& _rxORB) + : GenericDialogController( _pParent, "modules/spropctrlr/ui/taborder.ui", "TabOrderDialog") + , m_xModel( _rxTabModel ) + , m_xControlContainer( _rxControlCont ) + , m_xORB( _rxORB ) + , m_xLB_Controls(m_xBuilder->weld_tree_view("CTRLtree")) + , m_xPB_OK(m_xBuilder->weld_button("ok")) + , m_xPB_MoveUp(m_xBuilder->weld_button("upB")) + , m_xPB_MoveDown(m_xBuilder->weld_button("downB")) + , m_xPB_AutoOrder(m_xBuilder->weld_button("autoB")) + { + m_xLB_Controls->set_size_request(m_xLB_Controls->get_approximate_digit_width() * 60, + m_xLB_Controls->get_height_rows(10)); + m_xLB_Controls->set_selection_mode(SelectionMode::Multiple); + + m_xLB_Controls->connect_model_changed(LINK(this, TabOrderDialog, ModelHasMoved)); + m_xPB_MoveUp->connect_clicked( LINK( this, TabOrderDialog, MoveUpClickHdl ) ); + m_xPB_MoveDown->connect_clicked( LINK( this, TabOrderDialog, MoveDownClickHdl ) ); + m_xPB_AutoOrder->connect_clicked( LINK( this, TabOrderDialog, AutoOrderClickHdl ) ); + m_xPB_OK->connect_clicked( LINK( this, TabOrderDialog, OKClickHdl ) ); + m_xPB_OK->set_sensitive(false); + + if ( m_xModel.is() ) + m_xTempModel = new OSimpleTabModel( m_xModel->getControlModels() ); + + if ( m_xTempModel.is() && m_xControlContainer.is() ) + FillList(); + + if (m_xLB_Controls->n_children() < 2) + { + m_xPB_MoveUp->set_sensitive(false); + m_xPB_MoveDown->set_sensitive(false); + m_xPB_AutoOrder->set_sensitive(false); + } + + } + + void TabOrderDialog::SetModified() + { + m_xPB_OK->set_sensitive(true); + } + + TabOrderDialog::~TabOrderDialog() + { + } + + void TabOrderDialog::FillList() + { + DBG_ASSERT( m_xTempModel.is() && m_xControlContainer.is(), "TabOrderDialog::FillList: invalid call!" ); + if ( !m_xTempModel.is() || !m_xControlContainer.is() ) + return; + + m_xLB_Controls->clear(); + + try + { + OUString aName; + OUString aImage; + + const Sequence<Reference<css::awt::XControlModel>> aControlModels = m_xTempModel->getControlModels(); + for ( auto const& rControlModel : aControlModels ) + { + Reference< XPropertySet > xControl( rControlModel, UNO_QUERY ); + Reference< XPropertySetInfo > xPI; + if ( xControl.is() ) + xPI = xControl->getPropertySetInfo(); + + if ( xPI.is() ) + { + if ( xPI->hasPropertyByName( PROPERTY_TABSTOP ) ) + { + aName = ::comphelper::getString( xControl->getPropertyValue( PROPERTY_NAME ) ); + // TODO: do Basic controls have a name? + aImage = GetImage( xControl ); + OUString sId(weld::toId(xControl.get())); + m_xLB_Controls->append(sId, aName, aImage); + } + } + else + { + // no property set -> no tab order + OSL_FAIL( "TabOrderDialog::FillList: invalid control encountered!" ); + m_xLB_Controls->clear(); + break; + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "TabOrderDialog::FillList" ); + } + + // select first entry + if (m_xLB_Controls->n_children()) + m_xLB_Controls->select(0); + } + + IMPL_LINK_NOARG( TabOrderDialog, MoveUpClickHdl, weld::Button&, void ) + { + MoveSelection(-1); + } + + IMPL_LINK_NOARG( TabOrderDialog, MoveDownClickHdl, weld::Button&, void ) + { + MoveSelection(1); + } + + IMPL_LINK_NOARG( TabOrderDialog, AutoOrderClickHdl, weld::Button&, void ) + { + try + { + Reference< css::form::runtime::XFormController > xTabController = css::form::runtime::FormController::create( m_xORB ); + + xTabController->setModel( m_xTempModel ); + xTabController->setContainer( m_xControlContainer ); + xTabController->autoTabOrder(); + + SetModified(); + FillList(); + + ::comphelper::disposeComponent( xTabController ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "TabOrderDialog::AutoOrderClickHdl" ); + } + } + + IMPL_LINK_NOARG( TabOrderDialog, OKClickHdl, weld::Button&, void ) + { + int nEntryCount = m_xLB_Controls->n_children(); + Sequence< Reference< XControlModel > > aSortedControlModelSeq( nEntryCount ); + const Sequence< Reference< XControlModel > > aControlModels( m_xTempModel->getControlModels()); + Reference< XControlModel > * pSortedControlModels = aSortedControlModelSeq.getArray(); + + for (int i = 0; i < nEntryCount; ++i) + { + XPropertySet* pEntry = weld::fromId<XPropertySet*>(m_xLB_Controls->get_id(i)); + for( auto const& rControlModel : aControlModels ) + { + Reference< XPropertySet > xSet(rControlModel, UNO_QUERY); + if (xSet.get() == pEntry) + { + pSortedControlModels[i] = rControlModel; + break; + } + } + } + + // TODO: UNO action (to bracket all the single actions which are being created) + m_xModel->setControlModels( aSortedControlModelSeq ); + + m_xDialog->response(RET_OK); + } + + IMPL_LINK_NOARG(TabOrderDialog, ModelHasMoved, weld::TreeView&, void) + { + SetModified(); + } + + void TabOrderDialog::MoveSelection(int nRelPos) + { + std::vector<int> aRows(m_xLB_Controls->get_selected_rows()); + if (aRows.empty()) + return; + + m_xLB_Controls->unselect_all(); + for (int i = 0; i < abs(nRelPos); ++i) + { + SetModified(); + + // move entries + if (nRelPos < 0) + { + std::sort(aRows.begin(), aRows.end()); + + int nFirstSelPos = aRows[0]; + if (nFirstSelPos == 0) return; + + for (auto row : aRows) + { + int nInsertPos = row - 1; + m_xLB_Controls->swap(nInsertPos, row); + } + + for (auto row : aRows) + m_xLB_Controls->select(row - 1); + } + else if (nRelPos > 0) + { + std::sort(aRows.rbegin(), aRows.rend()); + + int nLastSelPos = aRows[0]; + if( (nLastSelPos + nRelPos - i) > (m_xLB_Controls->n_children()-1) ) return; + + for (auto row : aRows) + { + int nInsertPos = row + 1; + m_xLB_Controls->swap(nInsertPos, row); + } + + for (auto row : aRows) + m_xLB_Controls->select(row + 1); + } + } + } + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/taborder.hxx b/extensions/source/propctrlr/taborder.hxx new file mode 100644 index 0000000000..e43f010e82 --- /dev/null +++ b/extensions/source/propctrlr/taborder.hxx @@ -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 . + */ + +#pragma once + +#include <com/sun/star/awt/XTabControllerModel.hpp> +#include <com/sun/star/awt/XControlContainer.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <vcl/weld.hxx> + +namespace pcr +{ + //= TabOrderDialog + class TabOrderDialog : public weld::GenericDialogController + { + css::uno::Reference< css::awt::XTabControllerModel > + m_xTempModel; + css::uno::Reference< css::awt::XTabControllerModel > + m_xModel; + css::uno::Reference< css::awt::XControlContainer > + m_xControlContainer; + css::uno::Reference< css::uno::XComponentContext > + m_xORB; + + std::unique_ptr<weld::TreeView> m_xLB_Controls; + std::unique_ptr<weld::Button> m_xPB_OK; + std::unique_ptr<weld::Button> m_xPB_MoveUp; + std::unique_ptr<weld::Button> m_xPB_MoveDown; + std::unique_ptr<weld::Button> m_xPB_AutoOrder; + + DECL_LINK( ModelHasMoved, weld::TreeView&, void ); + DECL_LINK( MoveUpClickHdl, weld::Button&, void ); + DECL_LINK( MoveDownClickHdl, weld::Button&, void ); + DECL_LINK( AutoOrderClickHdl, weld::Button&, void ); + DECL_LINK( OKClickHdl, weld::Button&, void ); + + void FillList(); + void MoveSelection(int nRelPos); + + public: + TabOrderDialog( + weld::Window* pParent, + const css::uno::Reference< css::awt::XTabControllerModel >& _rxTabModel, + const css::uno::Reference< css::awt::XControlContainer >& _rxControlCont, + const css::uno::Reference< css::uno::XComponentContext >& _rxORB + ); + + virtual ~TabOrderDialog() override; + + void SetModified(); + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/unourl.cxx b/extensions/source/propctrlr/unourl.cxx new file mode 100644 index 0000000000..dba37915e6 --- /dev/null +++ b/extensions/source/propctrlr/unourl.cxx @@ -0,0 +1,65 @@ +/* -*- 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 "unourl.hxx" +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <comphelper/processfactory.hxx> +#include <osl/diagnose.h> +#include <comphelper/diagnose_ex.hxx> + + +namespace pcr +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::util; + + + //= UnoURL + + UnoURL::UnoURL( const OUString& _rCompleteURL, const Reference< XMultiServiceFactory >& _rxORB ) + { + m_aURL.Complete = _rCompleteURL; + + OSL_ENSURE( _rxORB.is(), "UnoURL::UnoURL: invalid ORB!" ); + Reference< XURLTransformer > xTransform; + try + { + if ( _rxORB.is() ) + { + xTransform.set( URLTransformer::create(comphelper::getComponentContext(_rxORB)) ); + OSL_ENSURE( xTransform.is(), "UnoURL::UnoURL: could not create a URL transformer!" ); + if ( xTransform.is() ) + xTransform->parseStrict( m_aURL ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "UnoURL::UnoURL" ); + } + } + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/unourl.hxx b/extensions/source/propctrlr/unourl.hxx new file mode 100644 index 0000000000..824c865ea9 --- /dev/null +++ b/extensions/source/propctrlr/unourl.hxx @@ -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 . + */ +#pragma once + +#include <rtl/ustring.hxx> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/util/URL.hpp> + + +namespace pcr +{ + + + //= UnoURL + + class UnoURL + { + private: + css::util::URL m_aURL; + + public: + UnoURL( + const OUString& _rCompleteURL, + const css::uno::Reference< css::lang::XMultiServiceFactory >& _rxORB + ); + + operator const css::util::URL& () const { return m_aURL; } + }; + + +} // namespacepcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/usercontrol.cxx b/extensions/source/propctrlr/usercontrol.cxx new file mode 100644 index 0000000000..8cccb48e84 --- /dev/null +++ b/extensions/source/propctrlr/usercontrol.cxx @@ -0,0 +1,276 @@ +/* -*- 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 "usercontrol.hxx" + +#include <com/sun/star/inspection/PropertyControlType.hpp> +#include <svl/numuno.hxx> +#include <vcl/GraphicObject.hxx> +#include <vcl/event.hxx> +#include <tools/debug.hxx> +#include <svl/numformat.hxx> +#include <svl/zformat.hxx> +#include <connectivity/dbconversion.hxx> +#include "modulepcr.hxx" +#include <strings.hrc> + + +namespace pcr +{ + + + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::Type; + + namespace PropertyControlType = ::com::sun::star::inspection::PropertyControlType; + + IMPL_LINK(OFormatSampleControl, KeyInputHdl, const KeyEvent&, rKeyEvent, bool) + { + // want to handle two keys myself : Del/Backspace should empty the window (setting my prop to "standard" this way) + sal_uInt16 nKey = rKeyEvent.GetKeyCode().GetCode(); + if ((KEY_DELETE == nKey) || (KEY_BACKSPACE == nKey)) + { + m_xSpinButton->set_text(""); + m_xEntry->set_text(""); + setModified(); + } + + return true; + } + + void OFormatSampleControl::SetFormatSupplier( const SvNumberFormatsSupplierObj* pSupplier ) + { + Formatter& rFieldFormatter = m_xSpinButton->GetFormatter(); + if (pSupplier) + { + rFieldFormatter.TreatAsNumber(true); + + SvNumberFormatter* pFormatter = pSupplier->GetNumberFormatter(); + rFieldFormatter.SetFormatter(pFormatter); + rFieldFormatter.SetValue( 1234.56789 ); + } + else + { + rFieldFormatter.TreatAsNumber(false); + rFieldFormatter.SetFormatter(nullptr); + m_xSpinButton->set_text( "" ); + } + + m_xEntry->set_text(m_xSpinButton->get_text()); + } + + OFormatSampleControl::OFormatSampleControl(std::unique_ptr<weld::Container> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly) + : OFormatSampleControl_Base(PropertyControlType::Unknown, std::move(xBuilder), std::move(xWidget), bReadOnly) + , m_xSpinButton(m_xBuilder->weld_formatted_spin_button("sample")) + , m_xEntry(m_xBuilder->weld_entry("entry")) + { + Formatter& rFieldFormatter = m_xSpinButton->GetFormatter(); + rFieldFormatter.TreatAsNumber(true); + rFieldFormatter.ClearMinValue(); + rFieldFormatter.ClearMaxValue(); + m_xEntry->connect_key_press(LINK(this, OFormatSampleControl, KeyInputHdl)); + } + + void SAL_CALL OFormatSampleControl::setValue( const Any& _rValue ) + { + sal_Int32 nFormatKey = 0; + if ( _rValue >>= nFormatKey ) + { + // else set the new format key, the text will be reformatted + Formatter& rFieldFormatter = m_xSpinButton->GetFormatter(); + rFieldFormatter.SetFormatKey(nFormatKey); + + SvNumberFormatter* pNF = rFieldFormatter.GetFormatter(); + const SvNumberformat* pEntry = pNF->GetEntry( nFormatKey ); + OSL_ENSURE( pEntry, "OFormatSampleControl::setValue: invalid format entry!" ); + + const bool bIsTextFormat = ( pEntry && pEntry->IsTextFormat() ); + if ( bIsTextFormat ) + m_xSpinButton->set_text( PcrRes( RID_STR_TEXT_FORMAT ) ); + else + rFieldFormatter.SetValue( pEntry ? getPreviewValue( *pEntry ) : 1234.56789 ); + } + else + m_xSpinButton->set_text( "" ); + + m_xEntry->set_text(m_xSpinButton->get_text()); + } + + double OFormatSampleControl::getPreviewValue( const SvNumberformat& i_rEntry ) + { + double nValue = 1234.56789; + switch ( i_rEntry.GetType() & ~SvNumFormatType::DEFINED ) + { + case SvNumFormatType::DATE: + { + Date aCurrentDate( Date::SYSTEM ); + static css::util::Date STANDARD_DB_DATE(30,12,1899); + nValue = ::dbtools::DBTypeConversion::toDouble(::dbtools::DBTypeConversion::toDate(aCurrentDate.GetDate()),STANDARD_DB_DATE); + } + break; + case SvNumFormatType::TIME: + case SvNumFormatType::DATETIME: + { + tools::Time aCurrentTime( tools::Time::SYSTEM ); + nValue = ::dbtools::DBTypeConversion::toDouble(::dbtools::DBTypeConversion::toTime(aCurrentTime.GetTime())); + } + break; + default: + break; + } + return nValue; + } + + + double OFormatSampleControl::getPreviewValue(SvNumberFormatter const * _pNF, sal_Int32 _nFormatKey) + { + const SvNumberformat* pEntry = _pNF->GetEntry(_nFormatKey); + DBG_ASSERT( pEntry, "OFormattedNumericControl::SetFormatDescription: invalid format key!" ); + double nValue = 1234.56789; + if ( pEntry ) + nValue = getPreviewValue( *pEntry ); + return nValue; + } + + Any SAL_CALL OFormatSampleControl::getValue() + { + Any aPropValue; + if ( !m_xSpinButton->get_text().isEmpty() ) + { + Formatter& rFieldFormatter = m_xSpinButton->GetFormatter(); + aPropValue <<= rFieldFormatter.GetValue(); + } + return aPropValue; + } + + Type SAL_CALL OFormatSampleControl::getValueType() + { + return ::cppu::UnoType<sal_Int32>::get(); + } + + OFormattedNumericControl::OFormattedNumericControl(std::unique_ptr<weld::FormattedSpinButton> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly) + : OFormattedNumericControl_Base(PropertyControlType::Unknown, std::move(xBuilder), std::move(xWidget), bReadOnly) + { + Formatter& rFormatter = getTypedControlWindow()->GetFormatter(); + rFormatter.TreatAsNumber(true); + rFormatter.ClearMinValue(); + rFormatter.ClearMaxValue(); + } + + OFormattedNumericControl::~OFormattedNumericControl() + { + } + + void SAL_CALL OFormattedNumericControl::setValue( const Any& _rValue ) + { + double nValue( 0 ); + if ( _rValue >>= nValue ) + getTypedControlWindow()->GetFormatter().SetValue(nValue); + else + getTypedControlWindow()->set_text(""); + } + + Any SAL_CALL OFormattedNumericControl::getValue() + { + Any aPropValue; + if ( !getTypedControlWindow()->get_text().isEmpty() ) + aPropValue <<= getTypedControlWindow()->GetFormatter().GetValue(); + return aPropValue; + } + + Type SAL_CALL OFormattedNumericControl::getValueType() + { + return ::cppu::UnoType<double>::get(); + } + + void OFormattedNumericControl::SetFormatDescription(const FormatDescription& rDesc) + { + bool bFallback = true; + + Formatter& rFieldFormatter = getTypedControlWindow()->GetFormatter(); + if (rDesc.pSupplier) + { + rFieldFormatter.TreatAsNumber(true); + + SvNumberFormatter* pFormatter = rDesc.pSupplier->GetNumberFormatter(); + if (pFormatter != rFieldFormatter.GetFormatter()) + rFieldFormatter.SetFormatter(pFormatter); + rFieldFormatter.SetFormatKey(rDesc.nKey); + + const SvNumberformat* pEntry = rFieldFormatter.GetFormatter()->GetEntry(rFieldFormatter.GetFormatKey()); + DBG_ASSERT( pEntry, "OFormattedNumericControl::SetFormatDescription: invalid format key!" ); + if ( pEntry ) + { + bFallback = false; + } + + } + + if ( bFallback ) + { + rFieldFormatter.TreatAsNumber(false); + rFieldFormatter.SetFormatter(nullptr); + getTypedControlWindow()->set_text(""); + } + } + + //= OFileUrlControl + OFileUrlControl::OFileUrlControl(std::unique_ptr<SvtURLBox> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly) + : OFileUrlControl_Base(PropertyControlType::Unknown, std::move(xBuilder), std::move(xWidget), bReadOnly) + { + getTypedControlWindow()->DisableHistory(); + getTypedControlWindow()->SetPlaceHolder( PcrRes( RID_EMBED_IMAGE_PLACEHOLDER ) ) ; + } + + OFileUrlControl::~OFileUrlControl() + { + } + + void SAL_CALL OFileUrlControl::setValue(const Any& rValue) + { + OUString sURL; + SvtURLBox* pControlWindow = getTypedControlWindow(); + bool bSuccess = rValue >>= sURL; + if (bSuccess && GraphicObject::isGraphicObjectUniqueIdURL(sURL)) + sURL = pControlWindow->GetPlaceHolder(); + pControlWindow->set_entry_text(sURL); + } + + Any SAL_CALL OFileUrlControl::getValue() + { + Any aPropValue; + if (!getTypedControlWindow()->get_active_text().isEmpty()) + aPropValue <<= getTypedControlWindow()->GetURL(); + return aPropValue; + } + + Type SAL_CALL OFileUrlControl::getValueType() + { + return ::cppu::UnoType<OUString>::get(); + } + + IMPL_LINK_NOARG(OFileUrlControl, URLModifiedHdl, weld::ComboBox&, void) + { + editChanged(); + } + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/usercontrol.hxx b/extensions/source/propctrlr/usercontrol.hxx new file mode 100644 index 0000000000..86b53e7f12 --- /dev/null +++ b/extensions/source/propctrlr/usercontrol.hxx @@ -0,0 +1,148 @@ +/* -*- 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 . + */ + +#pragma once + +#include "commoncontrol.hxx" +#include <svtools/inettbc.hxx> +#include <svl/zforlist.hxx> + +class SvNumberFormatsSupplierObj; + +namespace pcr +{ + //= OFormatSampleControl + typedef CommonBehaviourControl<css::inspection::XPropertyControl, weld::Container> OFormatSampleControl_Base; + class OFormatSampleControl : public OFormatSampleControl_Base + { + private: + std::unique_ptr<weld::FormattedSpinButton> m_xSpinButton; + std::unique_ptr<weld::Entry> m_xEntry; + + DECL_LINK(KeyInputHdl, const KeyEvent&, bool); + + public: + OFormatSampleControl(std::unique_ptr<weld::Container> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly); + + // XPropertyControl + virtual css::uno::Any SAL_CALL getValue() override; + virtual void SAL_CALL setValue( const css::uno::Any& _value ) override; + virtual css::uno::Type SAL_CALL getValueType() override; + + virtual void SAL_CALL disposing() override + { + m_xEntry.reset(); + m_xSpinButton.reset(); + OFormatSampleControl_Base::disposing(); + } + + virtual void SetModifyHandler() override + { + m_xEntry->connect_focus_in( LINK( this, CommonBehaviourControlHelper, GetFocusHdl ) ); + m_xEntry->connect_focus_out( LINK( this, CommonBehaviourControlHelper, LoseFocusHdl ) ); + m_xSpinButton->connect_value_changed(LINK(this, CommonBehaviourControlHelper, FormattedModifiedHdl)); + m_xSpinButton->connect_changed(LINK(this, CommonBehaviourControlHelper, EditModifiedHdl)); + } + + void SetFormatSupplier(const SvNumberFormatsSupplierObj* pSupplier); + + virtual weld::Widget* getWidget() override { return getTypedControlWindow(); } + + /** returns the default preview value for the given format key + * + * \param _pNF the number formatter + * \param _nFormatKey the format key + * \return current date or time or the value 1234.56789 + */ + static double getPreviewValue(SvNumberFormatter const * pNF, sal_Int32 nFormatKey); + + private: + static double getPreviewValue( const SvNumberformat& i_rEntry ); + }; + + //= FormatDescription + struct FormatDescription + { + SvNumberFormatsSupplierObj* pSupplier; + sal_Int32 nKey; + }; + + //= OFormattedNumericControl + typedef CommonBehaviourControl<css::inspection::XPropertyControl, weld::FormattedSpinButton> OFormattedNumericControl_Base; + class OFormattedNumericControl : public OFormattedNumericControl_Base + { + public: + OFormattedNumericControl(std::unique_ptr<weld::FormattedSpinButton> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly); + + // XPropertyControl + virtual css::uno::Any SAL_CALL getValue() override; + virtual void SAL_CALL setValue( const css::uno::Any& _value ) override; + virtual css::uno::Type SAL_CALL getValueType() override; + + void SetFormatDescription( const FormatDescription& rDesc ); + + // make some FormattedField methods available + void SetDecimalDigits(sal_uInt16 nPrecision) { getTypedControlWindow()->GetFormatter().SetDecimalDigits(nPrecision); } + void SetDefaultValue(double dDef) { getTypedControlWindow()->GetFormatter().SetDefaultValue(dDef); } + + virtual void SetModifyHandler() override + { + OFormattedNumericControl_Base::SetModifyHandler(); + getTypedControlWindow()->connect_value_changed(LINK(this, CommonBehaviourControlHelper, FormattedModifiedHdl)); + getTypedControlWindow()->connect_changed(LINK(this, CommonBehaviourControlHelper, EditModifiedHdl)); + } + + virtual weld::Widget* getWidget() override { return getTypedControlWindow(); } + + protected: + virtual ~OFormattedNumericControl() override; + }; + + //= OFileUrlControl + typedef CommonBehaviourControl<css::inspection::XPropertyControl, SvtURLBox> OFileUrlControl_Base; + class OFileUrlControl : public OFileUrlControl_Base + { + private: + DECL_LINK(URLModifiedHdl, weld::ComboBox&, void); + public: + OFileUrlControl(std::unique_ptr<SvtURLBox> xWidget, std::unique_ptr<weld::Builder> xBuilder, bool bReadOnly); + + // XPropertyControl + virtual css::uno::Any SAL_CALL getValue() override; + virtual void SAL_CALL setValue( const css::uno::Any& _value ) override; + virtual css::uno::Type SAL_CALL getValueType() override; + + virtual void SetModifyHandler() override + { + OFileUrlControl_Base::SetModifyHandler(); + SvtURLBox* pControlWindow = getTypedControlWindow(); + // tdf#140239 and tdf#141084 don't notify that the control has changed content until focus-out + pControlWindow->connect_focus_out(LINK(this, CommonBehaviourControlHelper, LoseFocusHdl)); + pControlWindow->connect_changed(LINK(this, OFileUrlControl, URLModifiedHdl)); + } + + virtual weld::Widget* getWidget() override { return getTypedControlWindow()->getWidget(); } + + protected: + virtual ~OFileUrlControl() override; + }; + +} // namespace pcr + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/xsddatatypes.cxx b/extensions/source/propctrlr/xsddatatypes.cxx new file mode 100644 index 0000000000..25af46895a --- /dev/null +++ b/extensions/source/propctrlr/xsddatatypes.cxx @@ -0,0 +1,185 @@ +/* -*- 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 "xsddatatypes.hxx" + +#include <com/sun/star/xsd/DataTypeClass.hpp> +#include <com/sun/star/xsd/XDataType.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <tools/debug.hxx> +#include <osl/diagnose.h> +#include <comphelper/diagnose_ex.hxx> + + +namespace pcr +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::xsd; + using namespace ::com::sun::star::beans; + + template< typename INTERFACE, typename ARGUMENT > + static ARGUMENT getSave( INTERFACE* pObject, ARGUMENT ( SAL_CALL INTERFACE::*pGetter )( ) ) + { + ARGUMENT aReturn = ARGUMENT(); + try + { + aReturn = (pObject->*pGetter)( ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "XSDDataType: getSave" ); + } + return aReturn; + } + + XSDDataType::XSDDataType( const Reference< XDataType >& _rxDataType ) + :m_xDataType( _rxDataType ) + { + DBG_ASSERT( m_xDataType.is(), "XSDDataType::XSDDataType: invalid UNO object!" ); + if ( m_xDataType.is() ) + m_xFacetInfo = m_xDataType->getPropertySetInfo(); + } + + + XSDDataType::~XSDDataType() + { + } + + + sal_Int16 XSDDataType::classify() const + { + sal_Int16 nTypeClass = DataTypeClass::STRING; + try + { + if ( m_xDataType.is() ) + nTypeClass = m_xDataType->getTypeClass(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "XSDDataType::classify" ); + } + return nTypeClass; + } + + + bool XSDDataType::isBasicType() const + { + return getSave( m_xDataType.get(), &XDataType::getIsBasic ); + } + + + OUString XSDDataType::getName() const + { + return getSave( m_xDataType.get(), &XDataType::getName ); + } + + + void XSDDataType::setFacet( const OUString& _rFacetName, const Any& _rValue ) + { + try + { + m_xDataType->setPropertyValue( _rFacetName, _rValue ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "XSDDataType::setFacet: caught an exception - sure this is the right data type class for this property?" ); + } + } + + + bool XSDDataType::hasFacet( const OUString& _rFacetName ) const + { + bool bReturn = false; + try + { + bReturn = m_xFacetInfo.is() && m_xFacetInfo->hasPropertyByName( _rFacetName ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "XSDDataType::hasFacet" ); + } + return bReturn; + } + + Any XSDDataType::getFacet( const OUString& _rFacetName ) + { + Any aReturn; + try + { + aReturn = m_xDataType->getPropertyValue( _rFacetName ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "XSDDataType::getFacet: caught an exception - sure this is the right data type class for this property?" ); + } + return aReturn; + } + + + namespace + { + void lcl_copyProperties( const Reference< XPropertySet >& _rxSource, const Reference< XPropertySet >& _rxDest ) + { + Reference< XPropertySetInfo > xSourceInfo; + if ( _rxSource.is() ) + xSourceInfo = _rxSource->getPropertySetInfo(); + Reference< XPropertySetInfo > xDestInfo; + if ( _rxDest.is() ) + xDestInfo = _rxDest->getPropertySetInfo(); + OSL_ENSURE( xSourceInfo.is() && xDestInfo.is(), "lcl_copyProperties: invalid property set( info)s!" ); + if ( !xSourceInfo.is() || !xDestInfo.is() ) + return; + + Sequence< Property > aProperties( xSourceInfo->getProperties() ); + const Property* pProperties = aProperties.getConstArray(); + const Property* pPropertiesEnd = pProperties + aProperties.getLength(); + for ( ; pProperties != pPropertiesEnd; ++pProperties ) + { + if ( xDestInfo->hasPropertyByName( pProperties->Name ) ) + _rxDest->setPropertyValue( pProperties->Name, _rxSource->getPropertyValue( pProperties->Name ) ); + } + } + } + + + void XSDDataType::copyFacetsFrom( const ::rtl::Reference< XSDDataType >& _pSourceType ) + { + OSL_ENSURE( _pSourceType.is(), "XSDDataType::copyFacetsFrom: invalid source type!" ); + if ( !_pSourceType.is() ) + return; + + try + { + Reference< XPropertySet > xSource = _pSourceType->getUnoDataType(); + Reference< XPropertySet > xDest = getUnoDataType(); + lcl_copyProperties( xSource, xDest ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "XSDDataType::copyFacetsFrom" ); + } + } + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/xsddatatypes.hxx b/extensions/source/propctrlr/xsddatatypes.hxx new file mode 100644 index 0000000000..0413d0250e --- /dev/null +++ b/extensions/source/propctrlr/xsddatatypes.hxx @@ -0,0 +1,89 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/Reference.hxx> +#include <rtl/ref.hxx> +#include <salhelper/simplereferenceobject.hxx> + +namespace com::sun::star { + namespace xsd { + class XDataType; + } + namespace beans { + class XPropertySetInfo; + } +} + + +namespace pcr +{ + + + //= XSDDataType + + class XSDDataType : public salhelper::SimpleReferenceObject + { + private: + css::uno::Reference< css::xsd::XDataType > + m_xDataType; + css::uno::Reference< css::beans::XPropertySetInfo > + m_xFacetInfo; + + public: + explicit XSDDataType( + const css::uno::Reference< css::xsd::XDataType >& _rxDataType + ); + + /// retrieves the underlying UNO component + const css::uno::Reference< css::xsd::XDataType >& + getUnoDataType() const { return m_xDataType; } + + /// classifies the data typ + sal_Int16 classify() const; + + // attribute access + OUString getName() const; + bool isBasicType() const; + + /// determines whether a given facet exists at the type + bool hasFacet( const OUString& _rFacetName ) const; + /// retrieves a facet value + css::uno::Any getFacet( const OUString& _rFacetName ); + /// sets a facet value + void setFacet( const OUString& _rFacetName, const css::uno::Any& _rFacetValue ); + + /** copies as much facets (values, respectively) from a give data type instance + */ + void copyFacetsFrom( const ::rtl::Reference< XSDDataType >& _pSourceType ); + + protected: + virtual ~XSDDataType() override; + + private: + XSDDataType( const XSDDataType& ) = delete; + XSDDataType& operator=( const XSDDataType& ) = delete; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/xsdvalidationhelper.cxx b/extensions/source/propctrlr/xsdvalidationhelper.cxx new file mode 100644 index 0000000000..5580a8ec3a --- /dev/null +++ b/extensions/source/propctrlr/xsdvalidationhelper.cxx @@ -0,0 +1,406 @@ +/* -*- 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 "xsdvalidationhelper.hxx" +#include "xsddatatypes.hxx" +#include "formstrings.hxx" + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/xsd/DataTypeClass.hpp> +#include <com/sun/star/util/NumberFormat.hpp> +#include <com/sun/star/util/XNumberFormatTypes.hpp> +#include <com/sun/star/util/XNumberFormatsSupplier.hpp> +#include <com/sun/star/xforms/XDataTypeRepository.hpp> +#include <unotools/syslocale.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <comphelper/diagnose_ex.hxx> + + +namespace pcr +{ + + + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::xsd; + using namespace ::com::sun::star::util; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::xforms; + + namespace NumberFormat = ::com::sun::star::util::NumberFormat; + + + //= XSDValidationHelper + + + XSDValidationHelper::XSDValidationHelper( ::osl::Mutex& _rMutex, const Reference< XPropertySet >& _rxIntrospectee, const Reference< frame::XModel >& _rxContextDocument ) + :EFormsHelper( _rMutex, _rxIntrospectee, _rxContextDocument ) + ,m_bInspectingFormattedField( false ) + { + try + { + Reference< XPropertySetInfo > xPSI; + Reference< XServiceInfo > xSI( _rxIntrospectee, UNO_QUERY ); + if ( m_xControlModel.is() ) + xPSI = m_xControlModel->getPropertySetInfo(); + if ( xPSI.is() + && xPSI->hasPropertyByName( PROPERTY_FORMATKEY ) + && xPSI->hasPropertyByName( PROPERTY_FORMATSSUPPLIER ) + && xSI.is() + && xSI->supportsService( SERVICE_COMPONENT_FORMATTEDFIELD ) + ) + m_bInspectingFormattedField = true; + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION("extensions.propctrlr", + "caught an exception while examining the introspectee!"); + } + } + + + void XSDValidationHelper::getAvailableDataTypeNames( std::vector< OUString >& /* [out] */ _rNames ) const + { + _rNames.resize( 0 ); + + try + { + Reference< XDataTypeRepository > xRepository = getDataTypeRepository(); + if ( xRepository.is() ) + { + const Sequence<OUString> aElements = xRepository->getElementNames(); + + _rNames.resize( aElements.getLength() ); + std::copy( aElements.begin(), aElements.end(), _rNames.begin() ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "XSDValidationHelper::getAvailableDataTypeNames" ); + } + } + + + Reference< XDataTypeRepository > XSDValidationHelper::getDataTypeRepository() const + { + Reference< XDataTypeRepository > xRepository; + + Reference< xforms::XModel > xModel( getCurrentFormModel( ) ); + if ( xModel.is() ) + xRepository = xModel->getDataTypeRepository(); + + return xRepository; + } + + + Reference< XDataTypeRepository > XSDValidationHelper::getDataTypeRepository( const OUString& _rModelName ) const + { + Reference< XDataTypeRepository > xRepository; + + Reference< xforms::XModel > xModel( getFormModelByName( _rModelName ) ); + if ( xModel.is() ) + xRepository = xModel->getDataTypeRepository(); + + return xRepository; + } + + + Reference< XDataType > XSDValidationHelper::getDataType( const OUString& _rName ) const + { + Reference< XDataType > xDataType; + + if ( !_rName.isEmpty() ) + { + Reference< XDataTypeRepository > xRepository = getDataTypeRepository(); + if ( xRepository.is() ) + xDataType = xRepository->getDataType( _rName ); + } + return xDataType; + } + + + OUString XSDValidationHelper::getValidatingDataTypeName( ) const + { + OUString sDataTypeName; + try + { + Reference< XPropertySet > xBinding( getCurrentBinding() ); + // it's allowed here to not (yet) have a binding + if ( xBinding.is() ) + { + OSL_VERIFY( xBinding->getPropertyValue( PROPERTY_XSD_DATA_TYPE ) >>= sDataTypeName ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "XSDValidationHelper::getValidatingDataTypeName" ); + } + return sDataTypeName; + } + + + ::rtl::Reference< XSDDataType > XSDValidationHelper::getDataTypeByName( const OUString& _rName ) const + { + ::rtl::Reference< XSDDataType > pReturn; + + try + { + Reference< XDataType > xValidatedAgainst; + + if ( !_rName.isEmpty() ) + xValidatedAgainst = getDataType( _rName ); + + if ( xValidatedAgainst.is() ) + pReturn = new XSDDataType( xValidatedAgainst ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "XSDValidationHelper::getDataTypeByName" ); + } + + return pReturn; + } + + + ::rtl::Reference< XSDDataType > XSDValidationHelper::getValidatingDataType( ) const + { + return getDataTypeByName( getValidatingDataTypeName() ); + } + + + bool XSDValidationHelper::cloneDataType( const ::rtl::Reference< XSDDataType >& _pDataType, const OUString& _rNewName ) const + { + OSL_ENSURE( _pDataType.is(), "XSDValidationHelper::removeDataTypeFromRepository: invalid data type!" ); + if ( !_pDataType.is() ) + return false; + + try + { + Reference< XDataTypeRepository > xRepository( getDataTypeRepository() ); + OSL_ENSURE( xRepository.is(), "XSDValidationHelper::removeDataTypeFromRepository: invalid data type repository!" ); + if ( !xRepository.is() ) + return false; + + Reference< XDataType > xDataType( _pDataType->getUnoDataType() ); + OSL_ENSURE( xDataType.is(), "XSDValidationHelper::removeDataTypeFromRepository: invalid data type (II)!" ); + if ( !xDataType.is() ) + return false; + + xRepository->cloneDataType( xDataType->getName(), _rNewName ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "XSDValidationHelper::cloneDataType" ); + } + return true; + } + + + bool XSDValidationHelper::removeDataTypeFromRepository( const OUString& _rName ) const + { + try + { + Reference< XDataTypeRepository > xRepository( getDataTypeRepository() ); + OSL_ENSURE( xRepository.is(), "XSDValidationHelper::removeDataTypeFromRepository: invalid data type repository!" ); + if ( !xRepository.is() ) + return false; + + if ( !xRepository->hasByName( _rName ) ) + { + OSL_FAIL( "XSDValidationHelper::removeDataTypeFromRepository: invalid repository and/or data type!" ); + return false; + } + + xRepository->revokeDataType( _rName ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "XSDValidationHelper::removeDataTypeFromRepository" ); + return false; + } + return true; + } + + + void XSDValidationHelper::setValidatingDataTypeByName( const OUString& _rName ) const + { + try + { + Reference< XPropertySet > xBinding( getCurrentBinding() ); + OSL_ENSURE( xBinding.is(), "XSDValidationHelper::setValidatingDataTypeByName: no active binding - how this?" ); + + if ( xBinding.is() ) + { + // get the old data type - this is necessary for notifying property changes + OUString sOldDataTypeName; + OSL_VERIFY( xBinding->getPropertyValue( PROPERTY_XSD_DATA_TYPE ) >>= sOldDataTypeName ); + Reference< XPropertySet > xOldType; + try { + xOldType = getDataType( sOldDataTypeName ); + } catch( const Exception& ) { } + + // set the new data type name + xBinding->setPropertyValue( PROPERTY_XSD_DATA_TYPE, Any( _rName ) ); + + // retrieve the new data type object + Reference< XPropertySet > xNewType = getDataType( _rName ); + + // fire any changes in the properties which result from this new type + std::set< OUString > aFilter; aFilter.insert( PROPERTY_NAME ); + firePropertyChanges( xOldType, xNewType, aFilter ); + + // fire the change in the Data Type property + OUString sNewDataTypeName; + OSL_VERIFY( xBinding->getPropertyValue( PROPERTY_XSD_DATA_TYPE ) >>= sNewDataTypeName ); + firePropertyChange( PROPERTY_XSD_DATA_TYPE, Any( sOldDataTypeName ), Any( sNewDataTypeName ) ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("extensions.propctrlr"); + } + } + + + void XSDValidationHelper::copyDataType( const OUString& _rFromModel, const OUString& _rToModel, + const OUString& _rDataTypeName ) const + { + if ( _rFromModel == _rToModel ) + // nothing to do (me thinks) + return; + + try + { + Reference< XDataTypeRepository > xFromRepository, xToRepository; + if ( !_rFromModel.isEmpty() ) + xFromRepository = getDataTypeRepository( _rFromModel ); + if ( !_rToModel.isEmpty() ) + xToRepository = getDataTypeRepository( _rToModel ); + + if ( !xFromRepository.is() || !xToRepository.is() ) + return; + + if ( !xFromRepository->hasByName( _rDataTypeName ) || xToRepository->hasByName( _rDataTypeName ) ) + // not existent in the source, or already existent (by name) in the destination + return; + + // determine the built-in type belonging to the source type + ::rtl::Reference< XSDDataType > pSourceType = new XSDDataType( xFromRepository->getDataType( _rDataTypeName ) ); + OUString sTargetBaseType = getBasicTypeNameForClass( pSourceType->classify(), xToRepository ); + + // create the target type + Reference< XDataType > xTargetType = xToRepository->cloneDataType( sTargetBaseType, _rDataTypeName ); + ::rtl::Reference< XSDDataType > pTargetType = new XSDDataType( xTargetType ); + + // copy the facets + pTargetType->copyFacetsFrom( pSourceType ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "XSDValidationHelper::copyDataType" ); + } + } + + + void XSDValidationHelper::findDefaultFormatForIntrospectee() + { + try + { + ::rtl::Reference< XSDDataType > xDataType = getValidatingDataType(); + if ( xDataType.is() ) + { + // find a NumberFormat type corresponding to the DataTypeClass + sal_Int16 nNumberFormatType = NumberFormat::NUMBER; + switch ( xDataType->classify() ) + { + case DataTypeClass::DATETIME: + nNumberFormatType = NumberFormat::DATETIME; + break; + case DataTypeClass::DATE: + nNumberFormatType = NumberFormat::DATE; + break; + case DataTypeClass::TIME: + nNumberFormatType = NumberFormat::TIME; + break; + case DataTypeClass::STRING: + case DataTypeClass::anyURI: + case DataTypeClass::QName: + case DataTypeClass::NOTATION: + nNumberFormatType = NumberFormat::TEXT; + break; + } + + // get the number formatter from the introspectee + Reference< XNumberFormatsSupplier > xSupplier; + Reference< XNumberFormatTypes > xFormatTypes; + OSL_VERIFY( m_xControlModel->getPropertyValue( PROPERTY_FORMATSSUPPLIER ) >>= xSupplier ); + if ( xSupplier.is() ) + xFormatTypes.set(xSupplier->getNumberFormats(), css::uno::UNO_QUERY); + OSL_ENSURE( xFormatTypes.is(), "XSDValidationHelper::findDefaultFormatForIntrospectee: no number formats for the introspectee!" ); + if ( !xFormatTypes.is() ) + return; + + // and the standard format for the given NumberFormat type + sal_Int32 nDesiredFormat = xFormatTypes->getStandardFormat( nNumberFormatType, SvtSysLocale().GetLanguageTag().getLocale() ); + + // set this at the introspectee + m_xControlModel->setPropertyValue( PROPERTY_FORMATKEY, Any( nDesiredFormat ) ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "XSDValidationHelper::findDefaultFormatForIntrospectee" ); + } + } + + + OUString XSDValidationHelper::getBasicTypeNameForClass( sal_Int16 _nClass ) const + { + return getBasicTypeNameForClass( _nClass, getDataTypeRepository() ); + } + + + OUString XSDValidationHelper::getBasicTypeNameForClass( sal_Int16 _nClass, const Reference< XDataTypeRepository >& _rxRepository ) + { + OUString sReturn; + OSL_ENSURE( _rxRepository.is(), "XSDValidationHelper::getBasicTypeNameForClass: invalid repository!" ); + if ( !_rxRepository.is() ) + return sReturn; + + try + { + Reference< XDataType > xDataType = _rxRepository->getBasicDataType( _nClass ); + OSL_ENSURE( xDataType.is(), "XSDValidationHelper::getBasicTypeNameForClass: invalid data type returned!" ); + if ( xDataType.is() ) + sReturn = xDataType->getName(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "extensions.propctrlr", "XSDValidationHelper::getBasicTypeNameForClass" ); + } + + return sReturn; + } + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/xsdvalidationhelper.hxx b/extensions/source/propctrlr/xsdvalidationhelper.hxx new file mode 100644 index 0000000000..2a77ff584e --- /dev/null +++ b/extensions/source/propctrlr/xsdvalidationhelper.hxx @@ -0,0 +1,137 @@ +/* -*- 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 . + */ + +#pragma once + +#include "eformshelper.hxx" +#include "xsddatatypes.hxx" + +#include <com/sun/star/xsd/XDataType.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <rtl/ref.hxx> + + +namespace pcr +{ + + + class XSDDataType; + + //= XSDValidationHelper + + class XSDValidationHelper : public EFormsHelper + { + private: + bool m_bInspectingFormattedField; + public: + bool isInspectingFormattedField() const { return m_bInspectingFormattedField; } + + public: + XSDValidationHelper( + ::osl::Mutex& _rMutex, + const css::uno::Reference< css::beans::XPropertySet >& _rxIntrospectee, + const css::uno::Reference< css::frame::XModel >& _rxContextDocument + ); + + /** retrieves the names of all XForms models in the document the control lives in + */ + void getAvailableDataTypeNames( std::vector< OUString >& /* [out] */ _rNames ) const; + + /** retrieves a particular data type given by name + */ + ::rtl::Reference< XSDDataType > + getDataTypeByName( const OUString& _rName ) const; + + /** retrieves the DataType instance which the control model is currently validated against + + If there is a binding set at our control model, which at the same time acts as validator, + and if this validator is bound to an XDataType, then this data type is retrieved here. + */ + ::rtl::Reference< XSDDataType > + getValidatingDataType( ) const; + + /** retrieves the name of the data type which the control model is currently validated against + + @seealso getValidatingDataType + */ + OUString + getValidatingDataTypeName( ) const; + + /** binds the validator to a new data type + + To be called with an active binding only. + */ + void setValidatingDataTypeByName( const OUString& _rName ) const; + + /** removes the data type given by name from the data type repository + */ + bool removeDataTypeFromRepository( const OUString& _rName ) const; + + /** creates a new data type, which is a clone of an existing data type + */ + bool cloneDataType( const ::rtl::Reference< XSDDataType >& _pDataType, const OUString& _rNewName ) const; + + /** retrieves the name of the basic data type which has the given class + */ + OUString + getBasicTypeNameForClass( sal_Int16 _eClass ) const; + + /** copy a data type from one model to another + + If a data type with the given name already exists in the target model, then nothing + happens. In particular, the facets of the data type are not copied. + */ + void copyDataType( const OUString& _rFromModel, const OUString& _rToModel, + const OUString& _rDataTypeName ) const; + + /** finds (and sets) a default format for the formatted field we're inspecting, + according to the current data type the control value is evaluated against + */ + void findDefaultFormatForIntrospectee(); + + private: + /** retrieves the data type repository associated with the current model + */ + css::uno::Reference< css::xforms::XDataTypeRepository > + getDataTypeRepository() const; + + /** retrieves the data type repository associated with any model + */ + css::uno::Reference< css::xforms::XDataTypeRepository > + getDataTypeRepository( const OUString& _rModelName ) const; + + /** retrieves the data type object for the given name + */ + css::uno::Reference< css::xsd::XDataType > + getDataType( const OUString& _rName ) const; + + /** retrieves the name of the basic data type which has the given class, in the given repository + */ + static OUString + getBasicTypeNameForClass( + sal_Int16 _nClass, + const css::uno::Reference< css::xforms::XDataTypeRepository >& _rxRepository + ); + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/xsdvalidationpropertyhandler.cxx b/extensions/source/propctrlr/xsdvalidationpropertyhandler.cxx new file mode 100644 index 0000000000..08eb6d7e84 --- /dev/null +++ b/extensions/source/propctrlr/xsdvalidationpropertyhandler.cxx @@ -0,0 +1,673 @@ +/* -*- 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 "xsdvalidationpropertyhandler.hxx" +#include "formstrings.hxx" +#include "formmetadata.hxx" +#include "xsddatatypes.hxx" +#include "modulepcr.hxx" +#include <strings.hrc> +#include <propctrlr.h> +#include "newdatatype.hxx" +#include "xsdvalidationhelper.hxx" +#include "pcrcommon.hxx" +#include "handlerhelper.hxx" + +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/lang/NullPointerException.hpp> +#include <com/sun/star/xsd/WhiteSpaceTreatment.hpp> +#include <com/sun/star/xsd/DataTypeClass.hpp> +#include <com/sun/star/inspection/PropertyControlType.hpp> +#include <com/sun/star/beans/Optional.hpp> +#include <com/sun/star/inspection/XObjectInspectorUI.hpp> +#include <com/sun/star/inspection/PropertyLineElement.hpp> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <tools/debug.hxx> +#include <sal/macros.h> + +#include <algorithm> +#include <limits> + + +namespace pcr +{ + + + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::xforms; + using namespace ::com::sun::star::xsd; + using namespace ::com::sun::star::script; + using namespace ::com::sun::star::inspection; + + using ::com::sun::star::beans::PropertyAttribute::MAYBEVOID; + + + //= XSDValidationPropertyHandler + + XSDValidationPropertyHandler::XSDValidationPropertyHandler( const Reference< XComponentContext >& _rxContext ) + :PropertyHandlerComponent( _rxContext ) + { + } + + + XSDValidationPropertyHandler::~XSDValidationPropertyHandler() + { + } + + + OUString XSDValidationPropertyHandler::getImplementationName( ) + { + return "com.sun.star.comp.extensions.XSDValidationPropertyHandler"; + } + + + Sequence< OUString > XSDValidationPropertyHandler::getSupportedServiceNames( ) + { + return{ "com.sun.star.form.inspection.XSDValidationPropertyHandler" }; + } + + + Any SAL_CALL XSDValidationPropertyHandler::getPropertyValue( const OUString& _rPropertyName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + OSL_ENSURE(m_pHelper, "XSDValidationPropertyHandler::getPropertyValue: inconsistency!"); + // if we survived impl_getPropertyId_throwUnknownProperty, we should have a helper, since no helper implies no properties + + Any aReturn; + ::rtl::Reference< XSDDataType > pType = m_pHelper->getValidatingDataType(); + switch ( nPropId ) + { + // common facets + case PROPERTY_ID_XSD_DATA_TYPE: aReturn = pType.is() ? pType->getFacet( PROPERTY_NAME ) : Any( OUString() ); break; + case PROPERTY_ID_XSD_WHITESPACES:aReturn = pType.is() ? pType->getFacet( PROPERTY_XSD_WHITESPACES ) : Any( WhiteSpaceTreatment::Preserve ); break; + case PROPERTY_ID_XSD_PATTERN: aReturn = pType.is() ? pType->getFacet( PROPERTY_XSD_PATTERN ) : Any( OUString() ); break; + + // all other properties are simply forwarded, if they exist at the given type + default: + { + if ( pType.is() && pType->hasFacet( _rPropertyName ) ) + aReturn = pType->getFacet( _rPropertyName ); + } + break; + } + + return aReturn; + } + + + void SAL_CALL XSDValidationPropertyHandler::setPropertyValue( const OUString& _rPropertyName, const Any& _rValue ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + OSL_ENSURE(m_pHelper, "XSDValidationPropertyHandler::getPropertyValue: inconsistency!"); + // if we survived impl_getPropertyId_throwUnknownProperty, we should have a helper, since no helper implies no properties + + if ( PROPERTY_ID_XSD_DATA_TYPE == nPropId ) + { + OUString sTypeName; + OSL_VERIFY( _rValue >>= sTypeName ); + m_pHelper->setValidatingDataTypeByName( sTypeName ); + impl_setContextDocumentModified_nothrow(); + return; + } + + ::rtl::Reference< XSDDataType > pType = m_pHelper->getValidatingDataType(); + if ( !pType.is() ) + { + OSL_FAIL( "XSDValidationPropertyHandler::setPropertyValue: you're trying to set a type facet, without a current type!" ); + return; + } + + pType->setFacet( _rPropertyName, _rValue ); + impl_setContextDocumentModified_nothrow(); + } + + + void XSDValidationPropertyHandler::onNewComponent() + { + PropertyHandlerComponent::onNewComponent(); + + Reference< frame::XModel > xDocument( impl_getContextDocument_nothrow() ); + DBG_ASSERT( xDocument.is(), "XSDValidationPropertyHandler::onNewComponent: no document!" ); + if ( EFormsHelper::isEForm( xDocument ) ) + m_pHelper.reset( new XSDValidationHelper( m_aMutex, m_xComponent, xDocument ) ); + else + m_pHelper.reset(); + } + + + Sequence< Property > XSDValidationPropertyHandler::doDescribeSupportedProperties() const + { + std::vector< Property > aProperties; + + if (m_pHelper) + { + bool bAllowBinding = m_pHelper->canBindToAnyDataType(); + + if ( bAllowBinding ) + { + aProperties.reserve( 28 ); + + addStringPropertyDescription( aProperties, PROPERTY_XSD_DATA_TYPE ); + addInt16PropertyDescription ( aProperties, PROPERTY_XSD_WHITESPACES ); + addStringPropertyDescription( aProperties, PROPERTY_XSD_PATTERN ); + + // string facets + addInt32PropertyDescription( aProperties, PROPERTY_XSD_LENGTH, MAYBEVOID ); + addInt32PropertyDescription( aProperties, PROPERTY_XSD_MIN_LENGTH, MAYBEVOID ); + addInt32PropertyDescription( aProperties, PROPERTY_XSD_MAX_LENGTH, MAYBEVOID ); + + // decimal facets + addInt32PropertyDescription( aProperties, PROPERTY_XSD_TOTAL_DIGITS, MAYBEVOID ); + addInt32PropertyDescription( aProperties, PROPERTY_XSD_FRACTION_DIGITS, MAYBEVOID ); + + // facets for different types + addInt16PropertyDescription( aProperties, PROPERTY_XSD_MAX_INCLUSIVE_INT, MAYBEVOID ); + addInt16PropertyDescription( aProperties, PROPERTY_XSD_MAX_EXCLUSIVE_INT, MAYBEVOID ); + addInt16PropertyDescription( aProperties, PROPERTY_XSD_MIN_INCLUSIVE_INT, MAYBEVOID ); + addInt16PropertyDescription( aProperties, PROPERTY_XSD_MIN_EXCLUSIVE_INT, MAYBEVOID ); + addDoublePropertyDescription( aProperties, PROPERTY_XSD_MAX_INCLUSIVE_DOUBLE, MAYBEVOID ); + addDoublePropertyDescription( aProperties, PROPERTY_XSD_MAX_EXCLUSIVE_DOUBLE, MAYBEVOID ); + addDoublePropertyDescription( aProperties, PROPERTY_XSD_MIN_INCLUSIVE_DOUBLE, MAYBEVOID ); + addDoublePropertyDescription( aProperties, PROPERTY_XSD_MIN_EXCLUSIVE_DOUBLE, MAYBEVOID ); + addDatePropertyDescription( aProperties, PROPERTY_XSD_MAX_INCLUSIVE_DATE, MAYBEVOID ); + addDatePropertyDescription( aProperties, PROPERTY_XSD_MAX_EXCLUSIVE_DATE, MAYBEVOID ); + addDatePropertyDescription( aProperties, PROPERTY_XSD_MIN_INCLUSIVE_DATE, MAYBEVOID ); + addDatePropertyDescription( aProperties, PROPERTY_XSD_MIN_EXCLUSIVE_DATE, MAYBEVOID ); + addTimePropertyDescription( aProperties, PROPERTY_XSD_MAX_INCLUSIVE_TIME, MAYBEVOID ); + addTimePropertyDescription( aProperties, PROPERTY_XSD_MAX_EXCLUSIVE_TIME, MAYBEVOID ); + addTimePropertyDescription( aProperties, PROPERTY_XSD_MIN_INCLUSIVE_TIME, MAYBEVOID ); + addTimePropertyDescription( aProperties, PROPERTY_XSD_MIN_EXCLUSIVE_TIME, MAYBEVOID ); + addDateTimePropertyDescription( aProperties, PROPERTY_XSD_MAX_INCLUSIVE_DATE_TIME, MAYBEVOID ); + addDateTimePropertyDescription( aProperties, PROPERTY_XSD_MAX_EXCLUSIVE_DATE_TIME, MAYBEVOID ); + addDateTimePropertyDescription( aProperties, PROPERTY_XSD_MIN_INCLUSIVE_DATE_TIME, MAYBEVOID ); + addDateTimePropertyDescription( aProperties, PROPERTY_XSD_MIN_EXCLUSIVE_DATE_TIME, MAYBEVOID ); + } + } + + return comphelper::containerToSequence( aProperties ); + } + + + Sequence< OUString > SAL_CALL XSDValidationPropertyHandler::getSupersededProperties( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + std::vector< OUString > aSuperfluous; + if (m_pHelper) + { + aSuperfluous.push_back( PROPERTY_CONTROLSOURCE ); + aSuperfluous.push_back( PROPERTY_EMPTY_IS_NULL ); + aSuperfluous.push_back( PROPERTY_FILTERPROPOSAL ); + aSuperfluous.push_back( PROPERTY_LISTSOURCETYPE ); + aSuperfluous.push_back( PROPERTY_LISTSOURCE ); + aSuperfluous.push_back( PROPERTY_BOUNDCOLUMN ); + + bool bAllowBinding = m_pHelper->canBindToAnyDataType(); + + if ( bAllowBinding ) + { + aSuperfluous.push_back( PROPERTY_MAXTEXTLEN ); + aSuperfluous.push_back( PROPERTY_VALUEMIN ); + aSuperfluous.push_back( PROPERTY_VALUEMAX ); + aSuperfluous.push_back( PROPERTY_DECIMAL_ACCURACY ); + aSuperfluous.push_back( PROPERTY_TIMEMIN ); + aSuperfluous.push_back( PROPERTY_TIMEMAX ); + aSuperfluous.push_back( PROPERTY_DATEMIN ); + aSuperfluous.push_back( PROPERTY_DATEMAX ); + aSuperfluous.push_back( PROPERTY_EFFECTIVE_MIN ); + aSuperfluous.push_back( PROPERTY_EFFECTIVE_MAX ); + } + } + + return comphelper::containerToSequence( aSuperfluous ); + } + + + Sequence< OUString > SAL_CALL XSDValidationPropertyHandler::getActuatingProperties( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + std::vector< OUString > aInterestedInActuations; + if (m_pHelper) + { + aInterestedInActuations.push_back( PROPERTY_XSD_DATA_TYPE ); + aInterestedInActuations.push_back( PROPERTY_XML_DATA_MODEL ); + } + return comphelper::containerToSequence( aInterestedInActuations ); + } + + + namespace + { + void showPropertyUI( const Reference< XObjectInspectorUI >& _rxInspectorUI, const OUString& _rPropertyName, bool _bShow ) + { + if ( _bShow ) + _rxInspectorUI->showPropertyUI( _rPropertyName ); + else + _rxInspectorUI->hidePropertyUI( _rPropertyName ); + } + } + + + LineDescriptor SAL_CALL XSDValidationPropertyHandler::describePropertyLine( const OUString& _rPropertyName, + const Reference< XPropertyControlFactory >& _rxControlFactory ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !_rxControlFactory.is() ) + throw NullPointerException(); + if (!m_pHelper) + throw RuntimeException(); + + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + LineDescriptor aDescriptor; + if ( nPropId != PROPERTY_ID_XSD_DATA_TYPE ) + aDescriptor.IndentLevel = 1; + + // collect some information about the to-be-created control + sal_Int16 nControlType = PropertyControlType::TextField; + std::vector< OUString > aListEntries; + Optional< double > aMinValue( false, 0 ); + Optional< double > aMaxValue( false, 0 ); + + switch ( nPropId ) + { + case PROPERTY_ID_XSD_DATA_TYPE: + nControlType = PropertyControlType::ListBox; + + implGetAvailableDataTypeNames( aListEntries ); + + aDescriptor.PrimaryButtonId = UID_PROP_ADD_DATA_TYPE; + aDescriptor.SecondaryButtonId = UID_PROP_REMOVE_DATA_TYPE; + aDescriptor.HasPrimaryButton = aDescriptor.HasSecondaryButton = true; + aDescriptor.PrimaryButtonImageURL = "private:graphicrepository/extensions/res/buttonplus.png"; + aDescriptor.SecondaryButtonImageURL = "private:graphicrepository/extensions/res/buttonminus.png"; + break; + + case PROPERTY_ID_XSD_WHITESPACES: + { + nControlType = PropertyControlType::ListBox; + aListEntries = m_pInfoService->getPropertyEnumRepresentations( PROPERTY_ID_XSD_WHITESPACES ); + } + break; + + case PROPERTY_ID_XSD_PATTERN: + nControlType = PropertyControlType::TextField; + break; + + case PROPERTY_ID_XSD_LENGTH: + case PROPERTY_ID_XSD_MIN_LENGTH: + case PROPERTY_ID_XSD_MAX_LENGTH: + nControlType = PropertyControlType::NumericField; + break; + + case PROPERTY_ID_XSD_TOTAL_DIGITS: + case PROPERTY_ID_XSD_FRACTION_DIGITS: + nControlType = PropertyControlType::NumericField; + break; + + case PROPERTY_ID_XSD_MAX_INCLUSIVE_INT: + case PROPERTY_ID_XSD_MAX_EXCLUSIVE_INT: + case PROPERTY_ID_XSD_MIN_INCLUSIVE_INT: + case PROPERTY_ID_XSD_MIN_EXCLUSIVE_INT: + { + nControlType = PropertyControlType::NumericField; + + // handle limits for various 'INT' types according to + // their actual semantics (year, month, day) + + ::rtl::Reference< XSDDataType > xDataType( m_pHelper->getValidatingDataType() ); + sal_Int16 nTypeClass = xDataType.is() ? xDataType->classify() : DataTypeClass::STRING; + + aMinValue.IsPresent = aMaxValue.IsPresent = true; + aMinValue.Value = DataTypeClass::gYear == nTypeClass ? 0 : 1; + aMaxValue.Value = std::numeric_limits< sal_Int32 >::max(); + if ( DataTypeClass::gMonth == nTypeClass ) + aMaxValue.Value = 12; + else if ( DataTypeClass::gDay == nTypeClass ) + aMaxValue.Value = 31; + } + break; + + case PROPERTY_ID_XSD_MAX_INCLUSIVE_DOUBLE: + case PROPERTY_ID_XSD_MAX_EXCLUSIVE_DOUBLE: + case PROPERTY_ID_XSD_MIN_INCLUSIVE_DOUBLE: + case PROPERTY_ID_XSD_MIN_EXCLUSIVE_DOUBLE: + nControlType = PropertyControlType::NumericField; + // TODO/eForms: do we have "auto-digits"? + break; + + case PROPERTY_ID_XSD_MAX_INCLUSIVE_DATE: + case PROPERTY_ID_XSD_MAX_EXCLUSIVE_DATE: + case PROPERTY_ID_XSD_MIN_INCLUSIVE_DATE: + case PROPERTY_ID_XSD_MIN_EXCLUSIVE_DATE: + nControlType = PropertyControlType::DateField; + break; + + case PROPERTY_ID_XSD_MAX_INCLUSIVE_TIME: + case PROPERTY_ID_XSD_MAX_EXCLUSIVE_TIME: + case PROPERTY_ID_XSD_MIN_INCLUSIVE_TIME: + case PROPERTY_ID_XSD_MIN_EXCLUSIVE_TIME: + nControlType = PropertyControlType::TimeField; + break; + + case PROPERTY_ID_XSD_MAX_INCLUSIVE_DATE_TIME: + case PROPERTY_ID_XSD_MAX_EXCLUSIVE_DATE_TIME: + case PROPERTY_ID_XSD_MIN_INCLUSIVE_DATE_TIME: + case PROPERTY_ID_XSD_MIN_EXCLUSIVE_DATE_TIME: + nControlType = PropertyControlType::DateTimeField; + break; + + default: + OSL_FAIL( "XSDValidationPropertyHandler::describePropertyLine: cannot handle this property!" ); + break; + } + + switch ( nControlType ) + { + case PropertyControlType::ListBox: + aDescriptor.Control = PropertyHandlerHelper::createListBoxControl( _rxControlFactory, std::move(aListEntries), false, false ); + break; + case PropertyControlType::NumericField: + aDescriptor.Control = PropertyHandlerHelper::createNumericControl( _rxControlFactory, 0, aMinValue, aMaxValue ); + break; + default: + aDescriptor.Control = _rxControlFactory->createPropertyControl( nControlType, false ); + break; + } + + aDescriptor.Category = "Data"; + aDescriptor.DisplayName = m_pInfoService->getPropertyTranslation( nPropId ); + aDescriptor.HelpURL = HelpIdUrl::getHelpURL( m_pInfoService->getPropertyHelpId( nPropId ) ); + + return aDescriptor; + } + + + InteractiveSelectionResult SAL_CALL XSDValidationPropertyHandler::onInteractivePropertySelection( const OUString& _rPropertyName, sal_Bool _bPrimary, Any& /*_rData*/, const Reference< XObjectInspectorUI >& _rxInspectorUI ) + { + if ( !_rxInspectorUI.is() ) + throw NullPointerException(); + + ::osl::MutexGuard aGuard( m_aMutex ); + OSL_ENSURE(m_pHelper, "XSDValidationPropertyHandler::onInteractivePropertySelection: we " + "don't have any SupportedProperties!"); + if (!m_pHelper) + return InteractiveSelectionResult_Cancelled; + + PropertyId nPropId( impl_getPropertyId_throwUnknownProperty( _rPropertyName ) ); + + switch ( nPropId ) + { + case PROPERTY_ID_XSD_DATA_TYPE: + { + if ( _bPrimary ) + { + OUString sNewDataTypeName; + if ( implPrepareCloneDataCurrentType( sNewDataTypeName ) ) + { + implDoCloneCurrentDataType( sNewDataTypeName ); + return InteractiveSelectionResult_Success; + } + } + else + return implPrepareRemoveCurrentDataType() && implDoRemoveCurrentDataType() ? InteractiveSelectionResult_Success : InteractiveSelectionResult_Cancelled; + } + break; + + default: + OSL_FAIL( "XSDValidationPropertyHandler::onInteractivePropertySelection: unexpected property to build a dedicated UI!" ); + break; + } + return InteractiveSelectionResult_Cancelled; + } + + + void SAL_CALL XSDValidationPropertyHandler::addPropertyChangeListener( const Reference< XPropertyChangeListener >& _rxListener ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyHandlerComponent::addPropertyChangeListener( _rxListener ); + if (m_pHelper) + m_pHelper->registerBindingListener( _rxListener ); + } + + + void SAL_CALL XSDValidationPropertyHandler::removePropertyChangeListener( const Reference< XPropertyChangeListener >& _rxListener ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if (m_pHelper) + m_pHelper->revokeBindingListener( _rxListener ); + PropertyHandlerComponent::removePropertyChangeListener( _rxListener ); + } + + + bool XSDValidationPropertyHandler::implPrepareCloneDataCurrentType( OUString& _rNewName ) + { + OSL_PRECOND( + m_pHelper, + "XSDValidationPropertyHandler::implPrepareCloneDataCurrentType: this will crash!"); + + ::rtl::Reference< XSDDataType > pType = m_pHelper->getValidatingDataType(); + if ( !pType.is() ) + { + OSL_FAIL( "XSDValidationPropertyHandler::implPrepareCloneDataCurrentType: invalid current data type!" ); + return false; + } + + std::vector< OUString > aExistentNames; + m_pHelper->getAvailableDataTypeNames( aExistentNames ); + + NewDataTypeDialog aDialog( nullptr, pType->getName(), aExistentNames ); // TODO/eForms: proper parent + if (aDialog.run() != RET_OK) + return false; + + _rNewName = aDialog.GetName(); + return true; + } + + + void XSDValidationPropertyHandler::implDoCloneCurrentDataType( const OUString& _rNewName ) + { + OSL_PRECOND(m_pHelper, + "XSDValidationPropertyHandler::implDoCloneCurrentDataType: this will crash!"); + + ::rtl::Reference< XSDDataType > pType = m_pHelper->getValidatingDataType(); + if ( !pType.is() ) + return; + + if ( !m_pHelper->cloneDataType( pType, _rNewName ) ) + return; + + m_pHelper->setValidatingDataTypeByName( _rNewName ); + } + + bool XSDValidationPropertyHandler::implPrepareRemoveCurrentDataType() + { + OSL_PRECOND( + m_pHelper, + "XSDValidationPropertyHandler::implPrepareRemoveCurrentDataType: this will crash!"); + + ::rtl::Reference< XSDDataType > pType = m_pHelper->getValidatingDataType(); + if ( !pType.is() ) + { + OSL_FAIL( "XSDValidationPropertyHandler::implPrepareRemoveCurrentDataType: invalid current data type!" ); + return false; + } + + // confirmation message + OUString sConfirmation( PcrRes( RID_STR_CONFIRM_DELETE_DATA_TYPE ) ); + sConfirmation = sConfirmation.replaceFirst( "#type#", pType->getName() ); + + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr, // TODO/eForms: proper parent + VclMessageType::Question, VclButtonsType::YesNo, + sConfirmation)); + return xQueryBox->run() == RET_YES; + } + + bool XSDValidationPropertyHandler::implDoRemoveCurrentDataType() + { + OSL_PRECOND(m_pHelper, + "XSDValidationPropertyHandler::implDoRemoveCurrentDataType: this will crash!"); + + ::rtl::Reference< XSDDataType > pType = m_pHelper->getValidatingDataType(); + if ( !pType.is() ) + return false; + + // set a new data type at the binding, which is the "basic" type for the one + // we are going to delete + // (do this before the actual deletion, so the old type is still valid for property change + // notifications) + m_pHelper->setValidatingDataTypeByName( m_pHelper->getBasicTypeNameForClass( pType->classify() ) ); + // now remove the type + m_pHelper->removeDataTypeFromRepository( pType->getName() ); + + return true; + } + + + void SAL_CALL XSDValidationPropertyHandler::actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const Any& _rNewValue, const Any& _rOldValue, const Reference< XObjectInspectorUI >& _rxInspectorUI, sal_Bool _bFirstTimeInit ) + { + if ( !_rxInspectorUI.is() ) + throw NullPointerException(); + + ::osl::MutexGuard aGuard( m_aMutex ); + PropertyId nActuatingPropId( impl_getPropertyId_throwRuntime( _rActuatingPropertyName ) ); + if (!m_pHelper) + throw RuntimeException(); + // if we survived impl_getPropertyId_throwRuntime, we should have a helper, since no helper implies no properties + + switch ( nActuatingPropId ) + { + case PROPERTY_ID_XSD_DATA_TYPE: + { + ::rtl::Reference< XSDDataType > xDataType( m_pHelper->getValidatingDataType() ); + + // is removal of this type possible? + bool bIsBasicType = xDataType.is() && xDataType->isBasicType(); + _rxInspectorUI->enablePropertyUIElements( PROPERTY_XSD_DATA_TYPE, PropertyLineElement::PrimaryButton, xDataType.is() ); + _rxInspectorUI->enablePropertyUIElements( PROPERTY_XSD_DATA_TYPE, PropertyLineElement::SecondaryButton, xDataType.is() && !bIsBasicType ); + + + // show the facets which are available at the data type + OUString aFacets[] = { + PROPERTY_XSD_WHITESPACES, PROPERTY_XSD_PATTERN, + PROPERTY_XSD_LENGTH, PROPERTY_XSD_MIN_LENGTH, PROPERTY_XSD_MAX_LENGTH, PROPERTY_XSD_TOTAL_DIGITS, + PROPERTY_XSD_FRACTION_DIGITS, + PROPERTY_XSD_MAX_INCLUSIVE_INT, + PROPERTY_XSD_MAX_EXCLUSIVE_INT, + PROPERTY_XSD_MIN_INCLUSIVE_INT, + PROPERTY_XSD_MIN_EXCLUSIVE_INT, + PROPERTY_XSD_MAX_INCLUSIVE_DOUBLE, + PROPERTY_XSD_MAX_EXCLUSIVE_DOUBLE, + PROPERTY_XSD_MIN_INCLUSIVE_DOUBLE, + PROPERTY_XSD_MIN_EXCLUSIVE_DOUBLE, + PROPERTY_XSD_MAX_INCLUSIVE_DATE, + PROPERTY_XSD_MAX_EXCLUSIVE_DATE, + PROPERTY_XSD_MIN_INCLUSIVE_DATE, + PROPERTY_XSD_MIN_EXCLUSIVE_DATE, + PROPERTY_XSD_MAX_INCLUSIVE_TIME, + PROPERTY_XSD_MAX_EXCLUSIVE_TIME, + PROPERTY_XSD_MIN_INCLUSIVE_TIME, + PROPERTY_XSD_MIN_EXCLUSIVE_TIME, + PROPERTY_XSD_MAX_INCLUSIVE_DATE_TIME, + PROPERTY_XSD_MAX_EXCLUSIVE_DATE_TIME, + PROPERTY_XSD_MIN_INCLUSIVE_DATE_TIME, + PROPERTY_XSD_MIN_EXCLUSIVE_DATE_TIME + }; + + size_t i=0; + const OUString* pLoop = nullptr; + for ( i = 0, pLoop = aFacets; + i < SAL_N_ELEMENTS( aFacets ); + ++i, ++pLoop + ) + { + showPropertyUI( _rxInspectorUI, *pLoop, xDataType.is() && xDataType->hasFacet( *pLoop ) ); + _rxInspectorUI->enablePropertyUI( *pLoop, !bIsBasicType ); + } + } + break; + + case PROPERTY_ID_XML_DATA_MODEL: + { + // The data type which the current binding works with may not be present in the + // new model. Thus, transfer it. + OUString sOldModelName; _rOldValue >>= sOldModelName; + OUString sNewModelName; _rNewValue >>= sNewModelName; + OUString sDataType = m_pHelper->getValidatingDataTypeName(); + m_pHelper->copyDataType( sOldModelName, sNewModelName, sDataType ); + + // the list of available data types depends on the chosen model, so update this + if ( !_bFirstTimeInit ) + _rxInspectorUI->rebuildPropertyUI( PROPERTY_XSD_DATA_TYPE ); + } + break; + + default: + OSL_FAIL( "XSDValidationPropertyHandler::actuatingPropertyChanged: cannot handle this property!" ); + return; + } + + // in both cases, we need to care for the current value of the XSD_DATA_TYPE property, + // and update the FormatKey of the formatted field we're inspecting (if any) + if ( !_bFirstTimeInit && m_pHelper->isInspectingFormattedField() ) + m_pHelper->findDefaultFormatForIntrospectee(); + } + + + void XSDValidationPropertyHandler::implGetAvailableDataTypeNames( std::vector< OUString >& /* [out] */ _rNames ) const + { + OSL_PRECOND( + m_pHelper, + "XSDValidationPropertyHandler::implGetAvailableDataTypeNames: this will crash!"); + // start with *all* types which are available at the model + std::vector< OUString > aAllTypes; + m_pHelper->getAvailableDataTypeNames( aAllTypes ); + _rNames.clear(); + _rNames.reserve( aAllTypes.size() ); + + // then allow only those which are "compatible" with our control + for (auto const& dataType : aAllTypes) + { + ::rtl::Reference< XSDDataType > pType = m_pHelper->getDataTypeByName(dataType); + if ( pType.is() && m_pHelper->canBindToDataType( pType->classify() ) ) + _rNames.push_back(dataType); + } + } + + +} // namespace pcr + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_propctrlr_XSDValidationPropertyHandler_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new pcr::XSDValidationPropertyHandler(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/propctrlr/xsdvalidationpropertyhandler.hxx b/extensions/source/propctrlr/xsdvalidationpropertyhandler.hxx new file mode 100644 index 0000000000..32803b2156 --- /dev/null +++ b/extensions/source/propctrlr/xsdvalidationpropertyhandler.hxx @@ -0,0 +1,88 @@ +/* -*- 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 . + */ + +#pragma once + +#include "propertyhandler.hxx" + +#include <memory> + + +namespace pcr +{ + + + class XSDValidationHelper; + + //= XSDValidationPropertyHandler + + class XSDValidationPropertyHandler : public PropertyHandlerComponent + { + private: + std::unique_ptr< XSDValidationHelper > m_pHelper; + + public: + explicit XSDValidationPropertyHandler( + const css::uno::Reference< css::uno::XComponentContext >& _rxContext + ); + + protected: + virtual ~XSDValidationPropertyHandler() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames () override; + + // XPropertyHandler overriables + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& _rPropertyName ) override; + virtual void SAL_CALL setPropertyValue( const OUString& _rPropertyName, const css::uno::Any& _rValue ) override; + virtual css::uno::Sequence< OUString > + SAL_CALL getSupersededProperties( ) override; + virtual css::uno::Sequence< OUString > + SAL_CALL getActuatingProperties( ) override; + virtual css::inspection::LineDescriptor + SAL_CALL describePropertyLine( const OUString& _rPropertyName, const css::uno::Reference< css::inspection::XPropertyControlFactory >& _rxControlFactory ) override; + virtual css::inspection::InteractiveSelectionResult + SAL_CALL onInteractivePropertySelection( const OUString& _rPropertyName, sal_Bool _bPrimary, css::uno::Any& _rData, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI ) override; + virtual void SAL_CALL actuatingPropertyChanged( const OUString& _rActuatingPropertyName, const css::uno::Any& _rNewValue, const css::uno::Any& _rOldValue, const css::uno::Reference< css::inspection::XObjectInspectorUI >& _rxInspectorUI, sal_Bool ) override; + virtual void SAL_CALL addPropertyChangeListener( const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const css::uno::Reference< css::beans::XPropertyChangeListener >& _rxListener ) override; + + // PropertyHandler overridables + virtual css::uno::Sequence< css::beans::Property > + doDescribeSupportedProperties() const override; + virtual void onNewComponent() override; + + private: + bool implPrepareRemoveCurrentDataType(); + bool implDoRemoveCurrentDataType(); + + bool implPrepareCloneDataCurrentType( OUString& _rNewName ); + void implDoCloneCurrentDataType( const OUString& _rNewName ); + + /** retrieves the names of the data types which our introspectee can be validated against + */ + void implGetAvailableDataTypeNames( std::vector< OUString >& /* [out] */ _rNames ) const; + }; + + +} // namespace pcr + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/grid.cxx b/extensions/source/scanner/grid.cxx new file mode 100644 index 0000000000..7d87010ea1 --- /dev/null +++ b/extensions/source/scanner/grid.cxx @@ -0,0 +1,699 @@ +/* -*- 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/sprintf.hxx> +#include <osl/thread.h> +#include <rtl/math.hxx> + +#include <bitmaps.hlst> +#include <cmath> + +#include "grid.hxx" +#include <vcl/bitmapex.hxx> +#include <vcl/customweld.hxx> +#include <vcl/event.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> + +#include <algorithm> +#include <limits> +#include <memory> + +class GridWindow : public weld::CustomWidgetController +{ + // helper class for handles + struct impHandle + { + Point maPos; + sal_uInt16 mnOffX; + sal_uInt16 mnOffY; + + impHandle(const Point& rPos, sal_uInt16 nX, sal_uInt16 nY) + : maPos(rPos), mnOffX(nX), mnOffY(nY) + { + } + + bool operator<(const impHandle& rComp) const + { + return (maPos.X() < rComp.maPos.X()); + } + + void draw(vcl::RenderContext& rRenderContext, const BitmapEx& rBitmapEx) + { + const Point aOffset(rRenderContext.PixelToLogic(Point(mnOffX, mnOffY))); + rRenderContext.DrawBitmapEx(maPos - aOffset, rBitmapEx); + } + + bool isHit(OutputDevice const & rWin, const Point& rPos) + { + const Point aOffset(rWin.PixelToLogic(Point(mnOffX, mnOffY))); + const tools::Rectangle aTarget(maPos - aOffset, maPos + aOffset); + return aTarget.Contains(rPos); + } + }; + + tools::Rectangle m_aGridArea; + + double m_fMinX; + double m_fMinY; + double m_fMaxX; + double m_fMaxY; + + double m_fChunkX; + double m_fMinChunkX; + double m_fChunkY; + double m_fMinChunkY; + + double* m_pXValues; + double* m_pOrigYValues; + int m_nValues; + std::unique_ptr<double[]> m_pNewYValues; + + sal_uInt16 m_BmOffX; + sal_uInt16 m_BmOffY; + + bool m_bCutValues; + + // stuff for handles + using Handles = std::vector<impHandle>; + static constexpr auto npos = std::numeric_limits<Handles::size_type>::max(); + Handles m_aHandles; + Handles::size_type m_nDragIndex; + + BitmapEx m_aMarkerBitmap; + + Point transform( double x, double y ); + void transform( const Point& rOriginal, double& x, double& y ); + + double findMinX(); + double findMinY(); + double findMaxX(); + double findMaxY(); + + void drawGrid(vcl::RenderContext& rRenderContext); + void drawOriginal(vcl::RenderContext& rRenderContext); + void drawNew(vcl::RenderContext& rRenderContext); + void drawHandles(vcl::RenderContext& rRenderContext); + + void computeExtremes(); + static void computeChunk( double fMin, double fMax, double& fChunkOut, double& fMinChunkOut ); + void computeNew(); + static double interpolate( double x, double const * pNodeX, double const * pNodeY, int nNodes ); + + virtual bool MouseMove( const MouseEvent& ) override; + virtual bool MouseButtonDown( const MouseEvent& ) override; + virtual bool MouseButtonUp( const MouseEvent& ) override; + void onResize(); + virtual void Resize() override; + virtual void SetDrawingArea(weld::DrawingArea* pDrawingArea) override; + void drawLine(vcl::RenderContext& rRenderContext, double x1, double y1, double x2, double y2); +public: + GridWindow(); + void Init(double* pXValues, double* pYValues, int nValues, bool bCutValues, const BitmapEx &rMarkerBitmap); + virtual ~GridWindow() override; + + void setBoundings( double fMinX, double fMinY, double fMaxX, double fMaxY ); + + double* getNewYValues() { return m_pNewYValues.get(); } + + void ChangeMode(ResetType nType); + + virtual void Paint( vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& rRect ) override; +}; + +GridWindow::GridWindow() + : m_aGridArea(50, 15, 100, 100) + , m_fMinX(0.0) + , m_fMinY(0.0) + , m_fMaxX(0.0) + , m_fMaxY(0.0) + , m_fChunkX(0.0) + , m_fMinChunkX(0.0) + , m_fChunkY(0.0) + , m_fMinChunkY(0.0) + , m_pXValues(nullptr) + , m_pOrigYValues(nullptr) + , m_nValues(0) + , m_BmOffX(0) + , m_BmOffY(0) + , m_bCutValues(false) + , m_nDragIndex(npos) +{ +} + +void GridWindow::Init(double* pXValues, double* pYValues, int nValues, bool bCutValues, const BitmapEx &rMarkerBitmap) +{ + m_aMarkerBitmap = rMarkerBitmap; + m_pXValues = pXValues; + m_pOrigYValues = pYValues; + m_nValues = nValues; + m_bCutValues = bCutValues; + + onResize(); + + if (m_pOrigYValues && m_nValues) + { + m_pNewYValues.reset(new double[ m_nValues ]); + memcpy( m_pNewYValues.get(), m_pOrigYValues, sizeof( double ) * m_nValues ); + } + + setBoundings( 0, 0, 1023, 1023 ); + computeExtremes(); + + // create left and right marker as first and last entry + m_BmOffX = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Width() >> 1); + m_BmOffY = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Height() >> 1); + m_aHandles.push_back(impHandle(transform(findMinX(), findMinY()), m_BmOffX, m_BmOffY)); + m_aHandles.push_back(impHandle(transform(findMaxX(), findMaxY()), m_BmOffX, m_BmOffY)); +} + +void GridWindow::Resize() +{ + onResize(); +} + +void GridWindow::onResize() +{ + Size aSize = GetOutputSizePixel(); + m_aGridArea.setWidth( aSize.Width() - 80 ); + m_aGridArea.setHeight( aSize.Height() - 40 ); +} + +void GridWindow::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + Size aSize(pDrawingArea->get_ref_device().LogicToPixel(Size(240, 200), MapMode(MapUnit::MapAppFont))); + pDrawingArea->set_size_request(aSize.Width(), aSize.Height()); + CustomWidgetController::SetDrawingArea(pDrawingArea); + SetOutputSizePixel(aSize); +} + +GridDialog::GridDialog(weld::Window* pParent, double* pXValues, double* pYValues, int nValues) + : GenericDialogController(pParent, "modules/scanner/ui/griddialog.ui", "GridDialog") + , m_xResetTypeBox(m_xBuilder->weld_combo_box("resetTypeCombobox")) + , m_xResetButton(m_xBuilder->weld_button("resetButton")) + , m_xGridWindow(new GridWindow) + , m_xGridWindowWND(new weld::CustomWeld(*m_xBuilder, "gridwindow", *m_xGridWindow)) +{ + m_xGridWindow->Init(pXValues, pYValues, nValues, true/*bCutValues*/, BitmapEx(RID_SCANNER_HANDLE)); + m_xResetTypeBox->set_active(0); + m_xResetButton->connect_clicked( LINK( this, GridDialog, ClickButtonHdl ) ); +} + +GridDialog::~GridDialog() +{ +} + +GridWindow::~GridWindow() +{ + m_pNewYValues.reset(); +} + +double GridWindow::findMinX() +{ + if( ! m_pXValues ) + return 0.0; + double fMin = m_pXValues[0]; + for( int i = 1; i < m_nValues; i++ ) + if( m_pXValues[ i ] < fMin ) + fMin = m_pXValues[ i ]; + return fMin; +} + +double GridWindow::findMinY() +{ + if( ! m_pNewYValues ) + return 0.0; + double fMin = m_pNewYValues[0]; + for( int i = 1; i < m_nValues; i++ ) + if( m_pNewYValues[ i ] < fMin ) + fMin = m_pNewYValues[ i ]; + return fMin; +} + + +double GridWindow::findMaxX() +{ + if( ! m_pXValues ) + return 0.0; + double fMax = m_pXValues[0]; + for( int i = 1; i < m_nValues; i++ ) + if( m_pXValues[ i ] > fMax ) + fMax = m_pXValues[ i ]; + return fMax; +} + + +double GridWindow::findMaxY() +{ + if( ! m_pNewYValues ) + return 0.0; + double fMax = m_pNewYValues[0]; + for( int i = 1; i < m_nValues; i++ ) + if( m_pNewYValues[ i ] > fMax ) + fMax = m_pNewYValues[ i ]; + return fMax; +} + + +void GridWindow::computeExtremes() +{ + if( !(m_nValues && m_pXValues && m_pOrigYValues) ) + return; + + m_fMaxX = m_fMinX = m_pXValues[0]; + m_fMaxY = m_fMinY = m_pOrigYValues[0]; + for( int i = 1; i < m_nValues; i++ ) + { + if( m_pXValues[ i ] > m_fMaxX ) + m_fMaxX = m_pXValues[ i ]; + else if( m_pXValues[ i ] < m_fMinX ) + m_fMinX = m_pXValues[ i ]; + if( m_pOrigYValues[ i ] > m_fMaxY ) + m_fMaxY = m_pOrigYValues[ i ]; + else if( m_pOrigYValues[ i ] < m_fMinY ) + m_fMinY = m_pOrigYValues[ i ]; + } + setBoundings( m_fMinX, m_fMinY, m_fMaxX, m_fMaxY ); +} + + +Point GridWindow::transform( double x, double y ) +{ + Point aRet; + + aRet.setX( static_cast<tools::Long>( ( x - m_fMinX ) * + static_cast<double>(m_aGridArea.GetWidth()) / ( m_fMaxX - m_fMinX ) + + m_aGridArea.Left() ) ); + aRet.setY( static_cast<tools::Long>( + m_aGridArea.Bottom() - + ( y - m_fMinY ) * + static_cast<double>(m_aGridArea.GetHeight()) / ( m_fMaxY - m_fMinY ) ) ); + return aRet; +} + +void GridWindow::transform( const Point& rOriginal, double& x, double& y ) +{ + const tools::Long nWidth = m_aGridArea.GetWidth(); + const tools::Long nHeight = m_aGridArea.GetHeight(); + if (!nWidth || !nHeight) + return; + x = ( rOriginal.X() - m_aGridArea.Left() ) * (m_fMaxX - m_fMinX) / static_cast<double>(nWidth) + m_fMinX; + y = ( m_aGridArea.Bottom() - rOriginal.Y() ) * (m_fMaxY - m_fMinY) / static_cast<double>(nHeight) + m_fMinY; +} + +void GridWindow::drawLine(vcl::RenderContext& rRenderContext, double x1, double y1, double x2, double y2 ) +{ + rRenderContext.DrawLine(transform(x1, y1), transform(x2, y2)); +} + +void GridWindow::computeChunk( double fMin, double fMax, double& fChunkOut, double& fMinChunkOut ) +{ + // get a nice chunk size like 10, 100, 25 or such + fChunkOut = ( fMax - fMin ) / 6.0; + int logchunk = static_cast<int>(std::log10( fChunkOut )); + int nChunk = static_cast<int>( fChunkOut / std::exp( static_cast<double>(logchunk-1) * M_LN10 ) ); + if( nChunk >= 75 ) + nChunk = 100; + else if( nChunk >= 35 ) + nChunk = 50; + else if ( nChunk > 20 ) + nChunk = 25; + else if ( nChunk >= 13 ) + nChunk = 20; + else if( nChunk > 5 ) + nChunk = 10; + else + nChunk = 5; + fChunkOut = static_cast<double>(nChunk) * exp( static_cast<double>(logchunk-1) * M_LN10 ); + // compute whole chunks fitting into fMin + nChunk = static_cast<int>( fMin / fChunkOut ); + fMinChunkOut = static_cast<double>(nChunk) * fChunkOut; + while( fMinChunkOut < fMin ) + fMinChunkOut += fChunkOut; +} + + +void GridWindow::computeNew() +{ + if(2 == m_aHandles.size()) + { + // special case: only left and right markers + double xleft, yleft; + double xright, yright; + transform(m_aHandles[0].maPos, xleft, yleft); + transform(m_aHandles[1].maPos, xright, yright ); + double factor = (yright-yleft)/(xright-xleft); + for( int i = 0; i < m_nValues; i++ ) + { + m_pNewYValues[ i ] = yleft + ( m_pXValues[ i ] - xleft )*factor; + } + } + else + { + // sort markers + std::sort(m_aHandles.begin(), m_aHandles.end()); + const int nSorted = m_aHandles.size(); + int i; + + // get node arrays + std::unique_ptr<double[]> nodex(new double[ nSorted ]); + std::unique_ptr<double[]> nodey(new double[ nSorted ]); + + for( i = 0; i < nSorted; i++ ) + transform( m_aHandles[i].maPos, nodex[ i ], nodey[ i ] ); + + for( i = 0; i < m_nValues; i++ ) + { + double x = m_pXValues[ i ]; + m_pNewYValues[ i ] = interpolate( x, nodex.get(), nodey.get(), nSorted ); + if( m_bCutValues ) + { + if( m_pNewYValues[ i ] > m_fMaxY ) + m_pNewYValues[ i ] = m_fMaxY; + else if( m_pNewYValues[ i ] < m_fMinY ) + m_pNewYValues[ i ] = m_fMinY; + } + } + } +} + + +double GridWindow::interpolate( + double x, + double const * pNodeX, + double const * pNodeY, + int nNodes ) +{ + // compute Lagrange interpolation + double ret = 0; + for( int i = 0; i < nNodes; i++ ) + { + double sum = pNodeY[ i ]; + for( int n = 0; n < nNodes; n++ ) + { + if( n != i ) + { + sum *= x - pNodeX[ n ]; + sum /= pNodeX[ i ] - pNodeX[ n ]; + } + } + ret += sum; + } + return ret; +} + +void GridDialog::setBoundings(double fMinX, double fMinY, double fMaxX, double fMaxY) +{ + m_xGridWindow->setBoundings(fMinX, fMinY, fMaxX, fMaxY); +} + +void GridWindow::setBoundings(double fMinX, double fMinY, double fMaxX, double fMaxY) +{ + m_fMinX = fMinX; + m_fMinY = fMinY; + m_fMaxX = fMaxX; + m_fMaxY = fMaxY; + + computeChunk( m_fMinX, m_fMaxX, m_fChunkX, m_fMinChunkX ); + computeChunk( m_fMinY, m_fMaxY, m_fChunkY, m_fMinChunkY ); +} + +void GridWindow::drawGrid(vcl::RenderContext& rRenderContext) +{ + char pBuf[256]; + rRenderContext.SetLineColor(COL_BLACK); + // draw vertical lines + for (double fX = m_fMinChunkX; fX < m_fMaxX; fX += m_fChunkX) + { + drawLine(rRenderContext, fX, m_fMinY, fX, m_fMaxY); + // draw tickmarks + Point aPt = transform(fX, m_fMinY); + o3tl::sprintf(pBuf, "%g", fX); + OUString aMark(pBuf, strlen(pBuf), osl_getThreadTextEncoding()); + Size aTextSize(rRenderContext.GetTextWidth(aMark), rRenderContext.GetTextHeight()); + aPt.AdjustX( -(aTextSize.Width() / 2) ); + aPt.AdjustY(aTextSize.Height() / 2 ); + rRenderContext.DrawText(aPt, aMark); + } + // draw horizontal lines + for (double fY = m_fMinChunkY; fY < m_fMaxY; fY += m_fChunkY) + { + drawLine(rRenderContext, m_fMinX, fY, m_fMaxX, fY); + // draw tickmarks + Point aPt = transform(m_fMinX, fY); + o3tl::sprintf(pBuf, "%g", fY); + OUString aMark(pBuf, strlen(pBuf), osl_getThreadTextEncoding()); + Size aTextSize(rRenderContext.GetTextWidth(aMark), rRenderContext.GetTextHeight()); + aPt.AdjustX( -(aTextSize.Width() + 2) ); + aPt.AdjustY( -(aTextSize.Height() / 2) ); + rRenderContext.DrawText(aPt, aMark); + } + + // draw boundings + drawLine(rRenderContext, m_fMinX, m_fMinY, m_fMaxX, m_fMinY); + drawLine(rRenderContext, m_fMinX, m_fMaxY, m_fMaxX, m_fMaxY); + drawLine(rRenderContext, m_fMinX, m_fMinY, m_fMinX, m_fMaxY); + drawLine(rRenderContext, m_fMaxX, m_fMinY, m_fMaxX, m_fMaxY); +} + +void GridWindow::drawOriginal(vcl::RenderContext& rRenderContext) +{ + if (m_nValues && m_pXValues && m_pOrigYValues) + { + rRenderContext.SetLineColor(COL_RED); + for (int i = 0; i < m_nValues - 1; i++) + { + drawLine(rRenderContext, + m_pXValues[i], m_pOrigYValues[i], + m_pXValues[i + 1], m_pOrigYValues[i + 1]); + } + } +} + +void GridWindow::drawNew(vcl::RenderContext& rRenderContext) +{ + if (m_nValues && m_pXValues && m_pNewYValues) + { + rRenderContext.SetClipRegion(vcl::Region(m_aGridArea)); + rRenderContext.SetLineColor(COL_YELLOW); + for (int i = 0; i < m_nValues - 1; i++) + { + drawLine(rRenderContext, + m_pXValues[i], m_pNewYValues[i], + m_pXValues[i + 1], m_pNewYValues[i + 1]); + } + rRenderContext.SetClipRegion(); + } +} + +void GridWindow::drawHandles(vcl::RenderContext& rRenderContext) +{ + for(impHandle & rHandle : m_aHandles) + { + rHandle.draw(rRenderContext, m_aMarkerBitmap); + } +} + +void GridWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + rRenderContext.SetBackground(Wallpaper(Application::GetSettings().GetStyleSettings().GetDialogColor())); + drawGrid(rRenderContext); + drawOriginal(rRenderContext); + drawNew(rRenderContext); + drawHandles(rRenderContext); +} + +bool GridWindow::MouseMove( const MouseEvent& rEvt ) +{ + if( rEvt.GetButtons() != MOUSE_LEFT || m_nDragIndex == npos ) + return false; + + Point aPoint( rEvt.GetPosPixel() ); + + if( m_nDragIndex == 0 || m_nDragIndex == m_aHandles.size() - 1) + { + aPoint.setX( m_aHandles[m_nDragIndex].maPos.X() ); + } + else + { + if(aPoint.X() < m_aGridArea.Left()) + aPoint.setX( m_aGridArea.Left() ); + else if(aPoint.X() > m_aGridArea.Right()) + aPoint.setX( m_aGridArea.Right() ); + } + + if( aPoint.Y() < m_aGridArea.Top() ) + aPoint.setY( m_aGridArea.Top() ); + else if( aPoint.Y() > m_aGridArea.Bottom() ) + aPoint.setY( m_aGridArea.Bottom() ); + + if( aPoint != m_aHandles[m_nDragIndex].maPos ) + { + m_aHandles[m_nDragIndex].maPos = aPoint; + Invalidate( m_aGridArea ); + } + + return false; +} + +bool GridWindow::MouseButtonUp( const MouseEvent& rEvt ) +{ + if( rEvt.GetButtons() == MOUSE_LEFT ) + { + if( m_nDragIndex != npos ) + { + m_nDragIndex = npos; + computeNew(); + Invalidate(m_aGridArea); + } + } + + return false; +} + +bool GridWindow::MouseButtonDown( const MouseEvent& rEvt ) +{ + Point aPoint( rEvt.GetPosPixel() ); + Handles::size_type nMarkerIndex = npos; + + for(Handles::size_type a(0); nMarkerIndex == npos && a < m_aHandles.size(); a++) + { + if(m_aHandles[a].isHit(GetDrawingArea()->get_ref_device(), aPoint)) + { + nMarkerIndex = a; + } + } + + if( rEvt.GetButtons() == MOUSE_LEFT ) + { + // user wants to drag a button + if( nMarkerIndex != npos ) + { + m_nDragIndex = nMarkerIndex; + } + } + else if( rEvt.GetButtons() == MOUSE_RIGHT ) + { + // user wants to add/delete a button + if( nMarkerIndex != npos ) + { + if( nMarkerIndex != 0 && nMarkerIndex != m_aHandles.size() - 1) + { + // delete marker under mouse + if( m_nDragIndex == nMarkerIndex ) + m_nDragIndex = npos; + + m_aHandles.erase(m_aHandles.begin() + nMarkerIndex); + } + } + else + { + m_BmOffX = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Width() >> 1); + m_BmOffY = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Height() >> 1); + m_aHandles.push_back(impHandle(aPoint, m_BmOffX, m_BmOffY)); + } + + computeNew(); + Invalidate(m_aGridArea); + } + + return false; +} + +void GridWindow::ChangeMode(ResetType nType) +{ + switch( nType ) + { + case ResetType::LINEAR_ASCENDING: + { + for( int i = 0; i < m_nValues; i++ ) + { + m_pNewYValues[ i ] = m_fMinY + (m_fMaxY-m_fMinY)/(m_fMaxX-m_fMinX)*(m_pXValues[i]-m_fMinX); + } + } + break; + case ResetType::LINEAR_DESCENDING: + { + for( int i = 0; i < m_nValues; i++ ) + { + m_pNewYValues[ i ] = m_fMaxY - (m_fMaxY-m_fMinY)/(m_fMaxX-m_fMinX)*(m_pXValues[i]-m_fMinX); + } + } + break; + case ResetType::RESET: + { + if( m_pOrigYValues && m_pNewYValues && m_nValues ) + memcpy( m_pNewYValues.get(), m_pOrigYValues, m_nValues*sizeof(double) ); + } + break; + case ResetType::EXPONENTIAL: + { + for( int i = 0; i < m_nValues; i++ ) + { + m_pNewYValues[ i ] = m_fMinY + (m_fMaxY-m_fMinY)*(std::expm1((m_pXValues[i]-m_fMinX)/(m_fMaxX-m_fMinX)))/(M_E-1.0); + } + } + break; + + default: + break; + } + + if (m_pNewYValues) + { + for(size_t i(0); i < m_aHandles.size(); i++) + { + // find nearest xvalue + double x, y; + transform( m_aHandles[i].maPos, x, y ); + int nIndex = 0; + double delta = std::fabs( x-m_pXValues[0] ); + for( int n = 1; n < m_nValues; n++ ) + { + if( delta > std::fabs( x - m_pXValues[ n ] ) ) + { + delta = std::fabs( x - m_pXValues[ n ] ); + nIndex = n; + } + } + if( 0 == i ) + m_aHandles[i].maPos = transform( m_fMinX, m_pNewYValues[ nIndex ] ); + else if( m_aHandles.size() - 1 == i ) + m_aHandles[i].maPos = transform( m_fMaxX, m_pNewYValues[ nIndex ] ); + else + m_aHandles[i].maPos = transform( m_pXValues[ nIndex ], m_pNewYValues[ nIndex ] ); + } + } + + Invalidate(); +} + +IMPL_LINK_NOARG(GridDialog, ClickButtonHdl, weld::Button&, void) +{ + int nType = m_xResetTypeBox->get_active(); + m_xGridWindow->ChangeMode(static_cast<ResetType>(nType)); +} + +double* GridDialog::getNewYValues() +{ + return m_xGridWindow->getNewYValues(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/grid.hxx b/extensions/source/scanner/grid.hxx new file mode 100644 index 0000000000..319de77129 --- /dev/null +++ b/extensions/source/scanner/grid.hxx @@ -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 . + */ +#pragma once + +#include <vcl/customweld.hxx> +#include <vcl/weld.hxx> + +class GridWindow; + +enum class ResetType +{ + LINEAR_ASCENDING = 0, + LINEAR_DESCENDING = 1, + RESET = 2, + EXPONENTIAL = 3 +}; + +class GridDialog : public weld::GenericDialogController +{ + std::unique_ptr<weld::ComboBox> m_xResetTypeBox; + std::unique_ptr<weld::Button> m_xResetButton; + std::unique_ptr<GridWindow> m_xGridWindow; + std::unique_ptr<weld::CustomWeld> m_xGridWindowWND; + + DECL_LINK(ClickButtonHdl, weld::Button&, void); + +public: + GridDialog(weld::Window* pParent, double* pXValues, double* pYValues, int nValues); + virtual ~GridDialog() override; + void setBoundings(double fMinX, double fMinY, double fMaxX, double fMaxY); + double* getNewYValues(); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/sane.cxx b/extensions/source/scanner/sane.cxx new file mode 100644 index 0000000000..0e92a87964 --- /dev/null +++ b/extensions/source/scanner/sane.cxx @@ -0,0 +1,996 @@ +/* -*- 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 <type_traits> +#include <math.h> + +#include <o3tl/safeint.hxx> +#include <osl/file.h> +#include <sal/log.hxx> +#include <tools/stream.hxx> +#include <unotools/tempfile.hxx> +#include "sane.hxx" +#include <dlfcn.h> +#include <stdio.h> +#include <sys/time.h> +#include <sal/config.h> +#include <sal/macros.h> +#include <memory> + +#if (OSL_DEBUG_LEVEL > 0) || defined DBG_UTIL +#include <stdarg.h> +#define dump_state( a, b, c, d ) fprintf( stderr, a, b, c, d ); +#else +#define dump_state( a, b, c, d ) ; +#endif +static void dbg_msg( const char* pString, ... ) +{ +#if (OSL_DEBUG_LEVEL > 0) || defined DBG_UTIL + va_list ap; + va_start( ap, pString ); + vfprintf( stderr, pString, ap ); + va_end( ap ); +#else + (void)pString; +#endif +} + +#define FAIL_SHUTDOWN_STATE( x, y, z ) \ + if( x != SANE_STATUS_GOOD ) \ + { \ + dump_state( "%s returned error %d (%s)\n", \ + y, x, p_strstatus( x ) ); \ + DeInit(); \ + return z; \ + } + +#define FAIL_STATE( x, y, z ) \ + if( x != SANE_STATUS_GOOD ) \ + { \ + dump_state( "%s returned error %d (%s)\n", \ + y, x, p_strstatus( x ) ); \ + return z; \ + } + +#define DUMP_STATE( x, y ) \ + if( x != SANE_STATUS_GOOD ) \ + { \ + dump_state( "%s returned error %d (%s)\n", \ + y, x, p_strstatus( x ) ); \ + } + +int Sane::nRefCount = 0; +oslModule Sane::pSaneLib = nullptr; +SANE_Int Sane::nVersion = 0; +SANE_Device** Sane::ppDevices = nullptr; +int Sane::nDevices = 0; + +SANE_Status (*Sane::p_init)( SANE_Int*, + SANE_Auth_Callback ) = nullptr; +void (*Sane::p_exit)() = nullptr; +SANE_Status (*Sane::p_get_devices)( const SANE_Device***, + SANE_Bool ) = nullptr; +SANE_Status (*Sane::p_open)( SANE_String_Const, SANE_Handle ) = nullptr; +void (*Sane::p_close)( SANE_Handle ) = nullptr; +const SANE_Option_Descriptor* (*Sane::p_get_option_descriptor)( + SANE_Handle, SANE_Int ) = nullptr; +SANE_Status (*Sane::p_control_option)( SANE_Handle, SANE_Int, + SANE_Action, void*, + SANE_Int* ) = nullptr; +SANE_Status (*Sane::p_get_parameters)( SANE_Handle, + SANE_Parameters* ) = nullptr; +SANE_Status (*Sane::p_start)( SANE_Handle ) = nullptr; +SANE_Status (*Sane::p_read)( SANE_Handle, SANE_Byte*, SANE_Int, + SANE_Int* ) = nullptr; +void (*Sane::p_cancel)( SANE_Handle ) = nullptr; +SANE_Status (*Sane::p_set_io_mode)( SANE_Handle, SANE_Bool ) = nullptr; +SANE_Status (*Sane::p_get_select_fd)( SANE_Handle, SANE_Int* ) = nullptr; +SANE_String_Const (*Sane::p_strstatus)( SANE_Status ) = nullptr; + +static bool bSaneSymbolLoadFailed = false; + +inline oslGenericFunction Sane::LoadSymbol( const char* pSymbolname ) +{ + oslGenericFunction pFunction = osl_getAsciiFunctionSymbol( pSaneLib, pSymbolname ); + if( ! pFunction ) + { + fprintf( stderr, "Could not load symbol %s\n", + pSymbolname ); + bSaneSymbolLoadFailed = true; + } + return pFunction; +} + +SANE_Status Sane::ControlOption( int nOption, SANE_Action nAction, + void* pData ) +{ + SANE_Int nInfo = 0; + + SANE_Status nStatus = p_control_option( maHandle, static_cast<SANE_Int>(nOption), + nAction, pData, &nInfo ); + DUMP_STATE( nStatus, "sane_control_option" ); +#if OSL_DEBUG_LEVEL > 0 + if( nStatus != SANE_STATUS_GOOD ) + { + const char* pAction = "Unknown"; + switch( nAction ) + { + case SANE_ACTION_GET_VALUE: + pAction = "SANE_ACTION_GET_VALUE";break; + case SANE_ACTION_SET_VALUE: + pAction = "SANE_ACTION_SET_VALUE";break; + case SANE_ACTION_SET_AUTO: + pAction = "SANE_ACTION_SET_AUTO";break; + } + dbg_msg( "Option: \"%s\" action: %s\n", + OUStringToOString(GetOptionName(nOption), osl_getThreadTextEncoding()).getStr(), + pAction ); + } +#endif + if( nInfo & SANE_INFO_RELOAD_OPTIONS ) + ReloadOptions(); + return nStatus; +} + +Sane::Sane() : + mnOptions( 0 ), + mnDevice( -1 ), + maHandle( nullptr ) +{ + if( ! nRefCount || ! pSaneLib ) + Init(); + nRefCount++; +}; + +Sane::~Sane() +{ + if( IsOpen() ) + Close(); + nRefCount--; + if( ! nRefCount && pSaneLib ) + DeInit(); +} + +void Sane::Init() +{ +#ifndef DISABLE_DYNLOADING + OUString sSaneLibName( "libsane" SAL_DLLEXTENSION ); + pSaneLib = osl_loadModule( sSaneLibName.pData, SAL_LOADMODULE_LAZY ); + if( ! pSaneLib ) + { + sSaneLibName = "libsane" SAL_DLLEXTENSION ".1"; + pSaneLib = osl_loadModule( sSaneLibName.pData, SAL_LOADMODULE_LAZY ); + } + // try reasonable places that might not be in the library search path + if( ! pSaneLib ) + { + OUString sSaneLibSystemPath( "/usr/local/lib/libsane" SAL_DLLEXTENSION ); + osl_getFileURLFromSystemPath( sSaneLibSystemPath.pData, &sSaneLibName.pData ); + pSaneLib = osl_loadModule( sSaneLibName.pData, SAL_LOADMODULE_LAZY ); + } +#endif + if( pSaneLib ) + { + bSaneSymbolLoadFailed = false; + p_init = reinterpret_cast<SANE_Status(*)(SANE_Int*, SANE_Auth_Callback )>( + LoadSymbol( "sane_init" )); + p_exit = reinterpret_cast<void(*)()>( + LoadSymbol( "sane_exit" )); + p_get_devices = reinterpret_cast<SANE_Status(*)(const SANE_Device***, + SANE_Bool )>( + LoadSymbol( "sane_get_devices" )); + p_open = reinterpret_cast<SANE_Status(*)(SANE_String_Const, SANE_Handle )>( + LoadSymbol( "sane_open" )); + p_close = reinterpret_cast<void(*)(SANE_Handle)>( + LoadSymbol( "sane_close" )); + p_get_option_descriptor = reinterpret_cast<const SANE_Option_Descriptor*(*)(SANE_Handle, + SANE_Int)>( + LoadSymbol( "sane_get_option_descriptor" )); + p_control_option = reinterpret_cast<SANE_Status(*)(SANE_Handle, SANE_Int, + SANE_Action, void*, SANE_Int*)>( + LoadSymbol( "sane_control_option" )); + p_get_parameters = reinterpret_cast<SANE_Status(*)(SANE_Handle,SANE_Parameters*)>( + LoadSymbol( "sane_get_parameters" )); + p_start = reinterpret_cast<SANE_Status(*)(SANE_Handle)>( + LoadSymbol( "sane_start" )); + p_read = reinterpret_cast<SANE_Status(*)(SANE_Handle, SANE_Byte*, + SANE_Int, SANE_Int* )>( + LoadSymbol( "sane_read" )); + p_cancel = reinterpret_cast<void(*)(SANE_Handle)>( + LoadSymbol( "sane_cancel" )); + p_set_io_mode = reinterpret_cast<SANE_Status(*)(SANE_Handle, SANE_Bool)>( + LoadSymbol( "sane_set_io_mode" )); + p_get_select_fd = reinterpret_cast<SANE_Status(*)(SANE_Handle, SANE_Int*)>( + LoadSymbol( "sane_get_select_fd" )); + p_strstatus = reinterpret_cast<SANE_String_Const(*)(SANE_Status)>( + LoadSymbol( "sane_strstatus" )); + if( bSaneSymbolLoadFailed ) + DeInit(); + else + { + SANE_Status nStatus = p_init( &nVersion, nullptr ); + FAIL_SHUTDOWN_STATE( nStatus, "sane_init", ); + nStatus = p_get_devices( const_cast<const SANE_Device***>(&ppDevices), + SANE_FALSE ); + FAIL_SHUTDOWN_STATE( nStatus, "sane_get_devices", ); + for( nDevices = 0 ; ppDevices[ nDevices ]; nDevices++ ) ; + } + } +#if (OSL_DEBUG_LEVEL > 0) || defined DBG_UTIL + else + fprintf( stderr, "libsane%s could not be opened: %s\n", SAL_DLLEXTENSION, + dlerror() ); +#endif +} + +void Sane::DeInit() +{ + if( pSaneLib ) + { + p_exit(); +#ifndef DISABLE_DYNLOADING + osl_unloadModule( pSaneLib ); +#endif + pSaneLib = nullptr; + } +} + +void Sane::ReloadDevices() +{ + if( IsOpen() ) + Close(); + DeInit(); + Init(); +} + +void Sane::ReloadOptions() +{ + if( ! IsOpen() ) + return; + + const SANE_Option_Descriptor* pZero = p_get_option_descriptor( maHandle, 0 ); + SANE_Word pOptions[2]; + SANE_Status nStatus = p_control_option( maHandle, 0, SANE_ACTION_GET_VALUE, + static_cast<void*>(pOptions), nullptr ); + if( nStatus != SANE_STATUS_GOOD ) + fprintf( stderr, "Error: sane driver returned %s while reading number of options !\n", p_strstatus( nStatus ) ); + + mnOptions = pOptions[ 0 ]; + if( o3tl::make_unsigned(pZero->size) > sizeof( SANE_Word ) ) + fprintf( stderr, "driver returned number of options with larger size than SANE_Word!!!\n" ); + mppOptions.reset(new const SANE_Option_Descriptor*[ mnOptions ]); + mppOptions[ 0 ] = pZero; + for( int i = 1; i < mnOptions; i++ ) + mppOptions[ i ] = p_get_option_descriptor( maHandle, i ); + + CheckConsistency( nullptr, true ); + + maReloadOptionsLink.Call( *this ); +} + +bool Sane::Open( const char* name ) +{ + SANE_Status nStatus = p_open( reinterpret_cast<SANE_String_Const>(name), &maHandle ); + FAIL_STATE( nStatus, "sane_open", false ); + + ReloadOptions(); + + if( mnDevice == -1 ) + { + OString aDevice( name ); + for( int i = 0; i < nDevices; i++ ) + { + if( aDevice == ppDevices[i]->name ) + { + mnDevice = i; + break; + } + } + } + + return true; +} + +bool Sane::Open( int n ) +{ + if( n >= 0 && n < nDevices ) + { + mnDevice = n; + return Open( ppDevices[n]->name ); + } + return false; +} + +void Sane::Close() +{ + if( maHandle ) + { + p_close( maHandle ); + mppOptions.reset(); + maHandle = nullptr; + mnDevice = -1; + } +} + +int Sane::GetOptionByName( const char* rName ) +{ + int i; + OString aOption( rName ); + for( i = 0; i < mnOptions; i++ ) + { + if( mppOptions[i]->name && aOption == mppOptions[i]->name ) + return i; + } + return -1; +} + +bool Sane::GetOptionValue( int n, bool& rRet ) +{ + if( ! maHandle || mppOptions[n]->type != SANE_TYPE_BOOL ) + return false; + SANE_Word nRet; + SANE_Status nStatus = ControlOption( n, SANE_ACTION_GET_VALUE, &nRet ); + if( nStatus != SANE_STATUS_GOOD ) + return false; + + rRet = nRet; + return true; +} + +bool Sane::GetOptionValue( int n, OString& rRet ) +{ + bool bSuccess = false; + if( ! maHandle || mppOptions[n]->type != SANE_TYPE_STRING ) + return false; + std::unique_ptr<char[]> pRet(new char[mppOptions[n]->size+1]); + SANE_Status nStatus = ControlOption( n, SANE_ACTION_GET_VALUE, pRet.get() ); + if( nStatus == SANE_STATUS_GOOD ) + { + bSuccess = true; + rRet = pRet.get(); + } + return bSuccess; +} + +bool Sane::GetOptionValue( int n, double& rRet, int nElement ) +{ + bool bSuccess = false; + + if( ! maHandle || ( mppOptions[n]->type != SANE_TYPE_INT && + mppOptions[n]->type != SANE_TYPE_FIXED ) ) + return false; + + std::unique_ptr<SANE_Word[]> pRet(new SANE_Word[mppOptions[n]->size/sizeof(SANE_Word)]); + SANE_Status nStatus = ControlOption( n, SANE_ACTION_GET_VALUE, pRet.get() ); + if( nStatus == SANE_STATUS_GOOD ) + { + bSuccess = true; + if( mppOptions[n]->type == SANE_TYPE_INT ) + rRet = static_cast<double>(pRet[ nElement ]); + else + rRet = SANE_UNFIX( pRet[nElement] ); + } + return bSuccess; +} + +bool Sane::GetOptionValue( int n, double* pSet ) +{ + if( ! maHandle || ( mppOptions[n]->type != SANE_TYPE_FIXED && + mppOptions[n]->type != SANE_TYPE_INT ) ) + return false; + + std::unique_ptr<SANE_Word[]> pFixedSet(new SANE_Word[mppOptions[n]->size/sizeof(SANE_Word)]); + SANE_Status nStatus = ControlOption( n, SANE_ACTION_GET_VALUE, pFixedSet.get() ); + if( nStatus != SANE_STATUS_GOOD ) + return false; + for( size_t i = 0; i <mppOptions[n]->size/sizeof(SANE_Word); i++ ) + { + if( mppOptions[n]->type == SANE_TYPE_FIXED ) + pSet[i] = SANE_UNFIX( pFixedSet[i] ); + else + pSet[i] = static_cast<double>(pFixedSet[i]); + } + return true; +} + +void Sane::SetOptionValue( int n, bool bSet ) +{ + if( ! maHandle || mppOptions[n]->type != SANE_TYPE_BOOL ) + return; + SANE_Word nRet = bSet ? SANE_TRUE : SANE_FALSE; + ControlOption( n, SANE_ACTION_SET_VALUE, &nRet ); +} + +void Sane::SetOptionValue( int n, std::u16string_view rSet ) +{ + if( ! maHandle || mppOptions[n]->type != SANE_TYPE_STRING ) + return; + OString aSet(OUStringToOString(rSet, osl_getThreadTextEncoding())); + ControlOption( n, SANE_ACTION_SET_VALUE, const_cast<char *>(aSet.getStr()) ); +} + +void Sane::SetOptionValue( int n, double fSet, int nElement ) +{ + if( ! maHandle || ( mppOptions[n]->type != SANE_TYPE_INT && + mppOptions[n]->type != SANE_TYPE_FIXED ) ) + return; + + if( mppOptions[n]->size/sizeof(SANE_Word) > 1 ) + { + std::unique_ptr<SANE_Word[]> pSet(new SANE_Word[mppOptions[n]->size/sizeof(SANE_Word)]); + SANE_Status nStatus = ControlOption( n, SANE_ACTION_GET_VALUE, pSet.get() ); + if( nStatus == SANE_STATUS_GOOD ) + { + pSet[nElement] = mppOptions[n]->type == SANE_TYPE_INT ? + static_cast<SANE_Word>(fSet) : SANE_FIX( fSet ); + ControlOption( n, SANE_ACTION_SET_VALUE, pSet.get() ); + } + } + else + { + SANE_Word nSetTo = + mppOptions[n]->type == SANE_TYPE_INT ? + static_cast<SANE_Word>(fSet) : SANE_FIX( fSet ); + + ControlOption( n, SANE_ACTION_SET_VALUE, &nSetTo ); + } +} + +void Sane::SetOptionValue( int n, double const * pSet ) +{ + if( ! maHandle || ( mppOptions[n]->type != SANE_TYPE_INT && + mppOptions[n]->type != SANE_TYPE_FIXED ) ) + return; + std::unique_ptr<SANE_Word[]> pFixedSet(new SANE_Word[mppOptions[n]->size/sizeof(SANE_Word)]); + for( size_t i = 0; i < mppOptions[n]->size/sizeof(SANE_Word); i++ ) + { + if( mppOptions[n]->type == SANE_TYPE_FIXED ) + pFixedSet[i] = SANE_FIX( pSet[i] ); + else + pFixedSet[i] = static_cast<SANE_Word>(pSet[i]); + } + ControlOption( n, SANE_ACTION_SET_VALUE, pFixedSet.get() ); +} + +namespace { + +enum FrameStyleType { + FrameStyle_BW, FrameStyle_Gray, FrameStyle_RGB, FrameStyle_Separated +}; + +} + +#define BYTE_BUFFER_SIZE 32768 + +static sal_uInt8 ReadValue( FILE* fp, int depth ) +{ + if( depth == 16 ) + { + sal_uInt16 nWord; + // data always come in native byte order ! + // 16 bits is not really supported by backends as of now + // e.g. UMAX Astra 1200S delivers 16 bit but in BIGENDIAN + // against SANE documentation (xscanimage gets the same result + // as we do + size_t items_read = fread( &nWord, 1, 2, fp ); + + if (items_read != 2) + { + SAL_WARN( "extensions.scanner", "short read, abandoning" ); + return 0; + } + + return static_cast<sal_uInt8>( nWord / 256 ); + } + sal_uInt8 nByte; + size_t items_read = fread( &nByte, 1, 1, fp ); + if (items_read != 1) + { + SAL_WARN( "extensions.scanner", "short read, abandoning" ); + return 0; + } + return nByte; +} + +bool Sane::CheckConsistency( const char* pMes, bool bInit ) +{ + static const SANE_Option_Descriptor** pDescArray = nullptr; + static const SANE_Option_Descriptor* pZero = nullptr; + + if( bInit ) + { + pDescArray = mppOptions.get(); + if( mppOptions ) + pZero = mppOptions[0]; + return true; + } + + bool bConsistent = true; + + if( pDescArray != mppOptions.get() ) + bConsistent = false; + if( pZero != mppOptions[0] ) + bConsistent = false; + + if( ! bConsistent ) + dbg_msg( "Sane is not consistent. (%s)\n", pMes ); + + return bConsistent; +} + +bool Sane::Start( BitmapTransporter& rBitmap ) +{ + int nStream = 0, nLine = 0, i = 0; + SANE_Parameters aParams; + FrameStyleType eType = FrameStyle_Gray; + bool bSuccess = true; + bool bWidthSet = false; + + if( ! maHandle ) + return false; + + int nWidthMM = 0; + int nHeightMM = 0; + double fTLx, fTLy, fResl = 0.0; + int nOption; + nOption = GetOptionByName( "tl-x" ); + if( nOption != -1 && + GetOptionValue( nOption, fTLx ) && + GetOptionUnit( nOption ) == SANE_UNIT_MM ) + { + double fBRx; + nOption = GetOptionByName( "br-x" ); + if( nOption != -1 && + GetOptionValue( nOption, fBRx ) && + GetOptionUnit( nOption ) == SANE_UNIT_MM ) + { + nWidthMM = static_cast<int>(fabs(fBRx - fTLx)); + } + } + nOption = GetOptionByName( "tl-y" ); + if( nOption != -1 && + GetOptionValue( nOption, fTLy ) && + GetOptionUnit( nOption ) == SANE_UNIT_MM ) + { + double fBRy; + nOption = GetOptionByName( "br-y" ); + if( nOption != -1 && + GetOptionValue( nOption, fBRy ) && + GetOptionUnit( nOption ) == SANE_UNIT_MM ) + { + nHeightMM = static_cast<int>(fabs(fBRy - fTLy)); + } + } + if( ( nOption = GetOptionByName( "resolution" ) ) != -1 ) + (void)GetOptionValue( nOption, fResl ); + + std::unique_ptr<sal_uInt8[]> pBuffer; + + SANE_Status nStatus = SANE_STATUS_GOOD; + + rBitmap.lock(); + SvMemoryStream& aConverter = rBitmap.getStream(); + aConverter.Seek( 0 ); + aConverter.SetEndian( SvStreamEndian::LITTLE ); + + // write bitmap stream header + aConverter.WriteChar( 'B' ).WriteChar( 'M' ); + aConverter.WriteUInt32( 0 ); + aConverter.WriteUInt32( 0 ); + aConverter.WriteUInt32( 60 ); + + // write BITMAPINFOHEADER + aConverter.WriteUInt32( 40 ); + aConverter.WriteUInt32( 0 ); // fill in width later + aConverter.WriteUInt32( 0 ); // fill in height later + aConverter.WriteUInt16( 1 ); + // create header for 24 bits + // correct later if necessary + aConverter.WriteUInt16( 24 ); + aConverter.WriteUInt32( 0 ); + aConverter.WriteUInt32( 0 ); + aConverter.WriteUInt32( 0 ); + aConverter.WriteUInt32( 0 ); + aConverter.WriteUInt32( 0 ); + aConverter.WriteUInt32( 0 ); + + for( nStream=0; nStream < 3 && bSuccess ; nStream++ ) + { + nStatus = p_start( maHandle ); + DUMP_STATE( nStatus, "sane_start" ); + CheckConsistency( "sane_start" ); + if( nStatus == SANE_STATUS_GOOD ) + { + nStatus = p_get_parameters( maHandle, &aParams ); + DUMP_STATE( nStatus, "sane_get_parameters" ); + CheckConsistency( "sane_get_parameters" ); + if (nStatus != SANE_STATUS_GOOD || aParams.bytes_per_line == 0) + { + bSuccess = false; + break; + } +#if (OSL_DEBUG_LEVEL > 0) || defined DBG_UTIL + const char* const ppFormats[] = { "SANE_FRAME_GRAY", "SANE_FRAME_RGB", + "SANE_FRAME_RED", "SANE_FRAME_GREEN", + "SANE_FRAME_BLUE", "Unknown !!!" }; + fprintf( stderr, "Parameters for frame %d:\n", nStream ); + if( static_cast< + typename std::make_unsigned< + typename std::underlying_type<SANE_Frame>::type>::type>( + aParams.format) + > 4 ) + { + aParams.format = SANE_Frame(5); + } + fprintf( stderr, "format: %s\n", ppFormats[ static_cast<int>(aParams.format) ] ); + fprintf( stderr, "last_frame: %s\n", aParams.last_frame ? "TRUE" : "FALSE" ); + fprintf( stderr, "depth: %d\n", static_cast<int>(aParams.depth) ); + fprintf( stderr, "pixels_per_line: %d\n", static_cast<int>(aParams.pixels_per_line) ); + fprintf( stderr, "bytes_per_line: %d\n", static_cast<int>(aParams.bytes_per_line) ); +#endif + if( ! pBuffer ) + { + pBuffer.reset(new sal_uInt8[ BYTE_BUFFER_SIZE < 4*aParams.bytes_per_line ? 4*aParams.bytes_per_line : BYTE_BUFFER_SIZE ]); + } + + if( aParams.last_frame ) + nStream=3; + + switch( aParams.format ) + { + case SANE_FRAME_GRAY: + eType = FrameStyle_Gray; + if( aParams.depth == 1 ) + eType = FrameStyle_BW; + break; + case SANE_FRAME_RGB: + eType = FrameStyle_RGB; + break; + case SANE_FRAME_RED: + case SANE_FRAME_GREEN: + case SANE_FRAME_BLUE: + eType = FrameStyle_Separated; + break; + default: + fprintf( stderr, "Warning: unknown frame style !!!\n" ); + } + + bool bSynchronousRead = true; + + // should be fail safe, but ... ?? + nStatus = p_set_io_mode( maHandle, SANE_FALSE ); + CheckConsistency( "sane_set_io_mode" ); + if( nStatus != SANE_STATUS_GOOD ) + { + bSynchronousRead = false; + nStatus = p_set_io_mode(maHandle, SANE_TRUE); + CheckConsistency( "sane_set_io_mode" ); + if (nStatus != SANE_STATUS_GOOD) + { + SAL_WARN("extensions.scanner", "SANE driver status is: " << nStatus); + } + } + + SANE_Int nLen=0; + SANE_Int fd = 0; + + if( ! bSynchronousRead ) + { + nStatus = p_get_select_fd( maHandle, &fd ); + DUMP_STATE( nStatus, "sane_get_select_fd" ); + CheckConsistency( "sane_get_select_fd" ); + if( nStatus != SANE_STATUS_GOOD ) + bSynchronousRead = true; + } + utl::TempFileNamed aFrame; + aFrame.EnableKillingFile(); + FILE* pFrame = fopen(OUStringToOString(aFrame.GetFileName(), osl_getThreadTextEncoding()).getStr(), "w+b"); + if( ! pFrame ) + { + bSuccess = false; + break; + } + do { + if( ! bSynchronousRead ) + { + fd_set fdset; + struct timeval tv; + + FD_ZERO( &fdset ); + FD_SET( static_cast<int>(fd), &fdset ); + tv.tv_sec = 5; + tv.tv_usec = 0; + if( select( fd+1, &fdset, nullptr, nullptr, &tv ) == 0 ) + fprintf( stderr, "Timeout on sane_read descriptor\n" ); + } + nLen = 0; + nStatus = p_read( maHandle, pBuffer.get(), BYTE_BUFFER_SIZE, &nLen ); + CheckConsistency( "sane_read" ); + if( nLen && ( nStatus == SANE_STATUS_GOOD || + nStatus == SANE_STATUS_EOF ) ) + { + bSuccess = (static_cast<size_t>(nLen) == fwrite( pBuffer.get(), 1, nLen, pFrame )); + if (!bSuccess) + break; + } + else + DUMP_STATE( nStatus, "sane_read" ); + } while( nStatus == SANE_STATUS_GOOD ); + if (nStatus != SANE_STATUS_EOF || !bSuccess) + { + fclose( pFrame ); + bSuccess = false; + break; + } + + int nFrameLength = ftell( pFrame ); + fseek( pFrame, 0, SEEK_SET ); + sal_uInt32 nWidth = static_cast<sal_uInt32>(aParams.pixels_per_line); + sal_uInt32 nHeight = static_cast<sal_uInt32>(nFrameLength / aParams.bytes_per_line); + if( ! bWidthSet ) + { + if( ! fResl ) + fResl = 300; // if all else fails that's a good guess + if( ! nWidthMM ) + nWidthMM = static_cast<int>((static_cast<double>(nWidth) / fResl) * 25.4); + if( ! nHeightMM ) + nHeightMM = static_cast<int>((static_cast<double>(nHeight) / fResl) * 25.4); + SAL_INFO("extensions.scanner", "set dimensions to(" << nWidth << ", " << nHeight << ") Pixel, (" << nWidthMM << ", " << nHeightMM << + ") mm, resolution is " << fResl); + + aConverter.Seek( 18 ); + aConverter.WriteUInt32( nWidth ); + aConverter.WriteUInt32( nHeight ); + aConverter.Seek( 38 ); + aConverter.WriteUInt32( 1000*nWidth/nWidthMM ); + aConverter.WriteUInt32( 1000*nHeight/nHeightMM ); + bWidthSet = true; + } + aConverter.Seek(60); + + if( eType == FrameStyle_BW ) + { + aConverter.Seek( 10 ); + aConverter.WriteUInt32( 64 ); + aConverter.Seek( 28 ); + aConverter.WriteUInt16( 1 ); + aConverter.Seek( 54 ); + // write color table + aConverter.WriteUInt16( 0xffff ); + aConverter.WriteUChar( 0xff ); + aConverter.WriteUChar( 0 ); + aConverter.WriteUInt32( 0 ); + aConverter.Seek( 64 ); + } + else if( eType == FrameStyle_Gray ) + { + aConverter.Seek( 10 ); + aConverter.WriteUInt32( 1084 ); + aConverter.Seek( 28 ); + aConverter.WriteUInt16( 8 ); + aConverter.Seek( 54 ); + // write color table + for( nLine = 0; nLine < 256; nLine++ ) + { + aConverter.WriteUChar( nLine ); + aConverter.WriteUChar( nLine ); + aConverter.WriteUChar( nLine ); + aConverter.WriteUChar( 0 ); + } + aConverter.Seek( 1084 ); + } + + for (nLine = nHeight-1; nLine >= 0; --nLine) + { + if (fseek(pFrame, nLine * aParams.bytes_per_line, SEEK_SET) == -1) + { + bSuccess = false; + break; + } + if( eType == FrameStyle_BW || + ( eType == FrameStyle_Gray && aParams.depth == 8 ) + ) + { + SANE_Int items_read = fread( pBuffer.get(), 1, aParams.bytes_per_line, pFrame ); + if (items_read != aParams.bytes_per_line) + { + SAL_WARN( "extensions.scanner", "short read, padding with zeros" ); + memset(pBuffer.get() + items_read, 0, aParams.bytes_per_line - items_read); + } + aConverter.WriteBytes(pBuffer.get(), aParams.bytes_per_line); + } + else if( eType == FrameStyle_Gray ) + { + for( i = 0; i < (aParams.pixels_per_line); i++ ) + { + sal_uInt8 nGray = ReadValue( pFrame, aParams.depth ); + aConverter.WriteUChar( nGray ); + } + } + else if( eType == FrameStyle_RGB ) + { + for( i = 0; i < (aParams.pixels_per_line); i++ ) + { + sal_uInt8 nRed, nGreen, nBlue; + nRed = ReadValue( pFrame, aParams.depth ); + nGreen = ReadValue( pFrame, aParams.depth ); + nBlue = ReadValue( pFrame, aParams.depth ); + aConverter.WriteUChar( nBlue ); + aConverter.WriteUChar( nGreen ); + aConverter.WriteUChar( nRed ); + } + } + else if( eType == FrameStyle_Separated ) + { + for( i = 0; i < (aParams.pixels_per_line); i++ ) + { + sal_uInt8 nValue = ReadValue( pFrame, aParams.depth ); + switch( aParams.format ) + { + case SANE_FRAME_RED: + aConverter.SeekRel( 2 ); + aConverter.WriteUChar( nValue ); + break; + case SANE_FRAME_GREEN: + aConverter.SeekRel( 1 ); + aConverter.WriteUChar( nValue ); + aConverter.SeekRel( 1 ); + break; + case SANE_FRAME_BLUE: + aConverter.WriteUChar( nValue ); + aConverter.SeekRel( 2 ); + break; + case SANE_FRAME_GRAY: + case SANE_FRAME_RGB: + break; + } + } + } + int nGap = aConverter.Tell() & 3; + if (nGap) + aConverter.SeekRel( 4-nGap ); + } + fclose( pFrame ); // deletes tmpfile + if( eType != FrameStyle_Separated ) + break; + } + else + bSuccess = false; + } + // get stream length + int nPos = aConverter.TellEnd(); + + aConverter.Seek( 2 ); + aConverter.WriteUInt32( nPos+1 ); + aConverter.Seek( 0 ); + + rBitmap.unlock(); + + if( bSuccess ) + { + // only cancel a successful operation + // sane disrupts memory else + p_cancel( maHandle ); + CheckConsistency( "sane_cancel" ); + } + pBuffer.reset(); + + ReloadOptions(); + + + dbg_msg( "Sane::Start returns with %s\n", bSuccess ? "TRUE" : "FALSE" ); + + return bSuccess; +} + +int Sane::GetRange( int n, std::unique_ptr<double[]>& rpDouble ) +{ + if( mppOptions[n]->constraint_type != SANE_CONSTRAINT_RANGE && + mppOptions[n]->constraint_type != SANE_CONSTRAINT_WORD_LIST ) + { + return -1; + } + + rpDouble = nullptr; + int nItems, i; + bool bIsFixed = mppOptions[n]->type == SANE_TYPE_FIXED; + + dbg_msg( "Sane::GetRange of option %s ", mppOptions[n]->name ); + if(mppOptions[n]->constraint_type == SANE_CONSTRAINT_RANGE ) + { + double fMin, fMax, fQuant; + if( bIsFixed ) + { + fMin = SANE_UNFIX( mppOptions[n]->constraint.range->min ); + fMax = SANE_UNFIX( mppOptions[n]->constraint.range->max ); + fQuant = SANE_UNFIX( mppOptions[n]->constraint.range->quant ); + } + else + { + fMin = static_cast<double>(mppOptions[n]->constraint.range->min); + fMax = static_cast<double>(mppOptions[n]->constraint.range->max); + fQuant = static_cast<double>(mppOptions[n]->constraint.range->quant); + } + if( fQuant != 0.0 ) + { + dbg_msg( "quantum range [ %lg ; %lg ; %lg ]\n", + fMin, fQuant, fMax ); + nItems = static_cast<int>((fMax - fMin)/fQuant)+1; + rpDouble.reset(new double[ nItems ]); + double fValue = fMin; + for( i = 0; i < nItems; i++, fValue += fQuant ) + rpDouble[i] = fValue; + rpDouble[ nItems-1 ] = fMax; + return nItems; + } + else + { + dbg_msg( "normal range [ %lg %lg ]\n", + fMin, fMax ); + rpDouble.reset(new double[2]); + rpDouble[0] = fMin; + rpDouble[1] = fMax; + return 0; + } + } + else + { + nItems = mppOptions[n]->constraint.word_list[0]; + rpDouble.reset(new double[nItems]); + for( i=0; i<nItems; i++ ) + { + rpDouble[i] = bIsFixed ? + SANE_UNFIX( mppOptions[n]->constraint.word_list[i+1] ) : + static_cast<double>(mppOptions[n]->constraint.word_list[i+1]); + } + dbg_msg( "wordlist [ %lg ... %lg ]\n", + rpDouble[ 0 ], rpDouble[ nItems-1 ] ); + return nItems; + } +} + +static const char *ppUnits[] = { + "", + "[Pixel]", + "[Bit]", + "[mm]", + "[DPI]", + "[%]", + "[usec]" +}; + +OUString Sane::GetOptionUnitName( int n ) +{ + OUString aText; + SANE_Unit nUnit = mppOptions[n]->unit; + size_t nUnitAsSize = static_cast<size_t>(nUnit); + if (nUnitAsSize >= SAL_N_ELEMENTS( ppUnits )) + aText = "[unknown units]"; + else + aText = OUString( ppUnits[ nUnit ], strlen(ppUnits[ nUnit ]), osl_getThreadTextEncoding() ); + return aText; +} + +bool Sane::ActivateButtonOption( int n ) +{ + SANE_Status nStatus = ControlOption( n, SANE_ACTION_SET_VALUE, nullptr ); + return nStatus == SANE_STATUS_GOOD; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/sane.hxx b/extensions/source/scanner/sane.hxx new file mode 100644 index 0000000000..a0e631942a --- /dev/null +++ b/extensions/source/scanner/sane.hxx @@ -0,0 +1,190 @@ +/* -*- 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 . + */ +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <cppuhelper/implbase.hxx> +#include <osl/thread.h> +#include <osl/module.h> +#include <tools/stream.hxx> +#include <tools/link.hxx> +#include <sane/sane.h> + +#include <com/sun/star/awt/XBitmap.hpp> +#include <com/sun/star/uno/Sequence.hxx> + +using namespace com::sun::star::uno; + + +class BitmapTransporter: public cppu::WeakImplHelper<css::awt::XBitmap> +{ + SvMemoryStream m_aStream; + osl::Mutex m_aProtector; + +public: + + BitmapTransporter(); + virtual ~BitmapTransporter() override; + + virtual css::awt::Size SAL_CALL getSize() override; + virtual Sequence< sal_Int8 > SAL_CALL getDIB() override; + virtual Sequence< sal_Int8 > SAL_CALL getMaskDIB() override { return Sequence< sal_Int8 >(); } + + // Misc + void lock() { m_aProtector.acquire(); } + void unlock() { m_aProtector.release(); } + SvMemoryStream& getStream() { return m_aStream; } +}; + + +class Sane +{ +private: + static int nRefCount; + static oslModule pSaneLib; + + static SANE_Status (*p_init)( SANE_Int*, + SANE_Auth_Callback ); + static void (*p_exit)(); + static SANE_Status (*p_get_devices)( const SANE_Device***, + SANE_Bool ); + static SANE_Status (*p_open)( SANE_String_Const, SANE_Handle ); + static void (*p_close)( SANE_Handle ); + static const SANE_Option_Descriptor* (*p_get_option_descriptor)( + SANE_Handle, SANE_Int ); + static SANE_Status (*p_control_option)( SANE_Handle, SANE_Int, + SANE_Action, void*, + SANE_Int* ); + static SANE_Status (*p_get_parameters)( SANE_Handle, + SANE_Parameters* ); + static SANE_Status (*p_start)( SANE_Handle ); + static SANE_Status (*p_read)( SANE_Handle, SANE_Byte*, SANE_Int, + SANE_Int* ); + static void (*p_cancel)( SANE_Handle ); + static SANE_Status (*p_set_io_mode)( SANE_Handle, SANE_Bool ); + static SANE_Status (*p_get_select_fd)( SANE_Handle, SANE_Int* ); + static SANE_String_Const (*p_strstatus)( SANE_Status ); + + static SANE_Int nVersion; + static SANE_Device** ppDevices; + static int nDevices; + + std::unique_ptr<const SANE_Option_Descriptor*[]> mppOptions; + int mnOptions; + int mnDevice; + SANE_Handle maHandle; + + Link<Sane&,void> maReloadOptionsLink; + + static inline oslGenericFunction + LoadSymbol( const char* ); + static void Init(); + static void DeInit(); + + SANE_Status ControlOption( int, SANE_Action, void* ); + + bool CheckConsistency( const char*, bool bInit = false ); + +public: + Sane(); + ~Sane(); + + static bool IsSane() + { return pSaneLib != nullptr; } + bool IsOpen() const + { return maHandle != nullptr; } + static int CountDevices() + { return nDevices; } + static OUString GetName( int n ) + { return ppDevices[n]->name ? OUString( ppDevices[n]->name, strlen(ppDevices[n]->name), osl_getThreadTextEncoding() ) : OUString(); } + static OUString GetVendor( int n ) + { return ppDevices[n]->vendor ? OUString( ppDevices[n]->vendor, strlen(ppDevices[n]->vendor), osl_getThreadTextEncoding() ) : OUString(); } + static OUString GetModel( int n ) + { return ppDevices[n]->model ? OUString( ppDevices[n]->model, strlen(ppDevices[n]->model), osl_getThreadTextEncoding() ) : OUString(); } + static OUString GetType( int n ) + { return ppDevices[n]->type ? OUString( ppDevices[n]->type, strlen(ppDevices[n]->type), osl_getThreadTextEncoding() ) : OUString(); } + + OUString GetOptionName( int n ) + { return mppOptions[n]->name ? OUString( mppOptions[n]->name, strlen(mppOptions[n]->name), osl_getThreadTextEncoding() ) : OUString(); } + OUString GetOptionTitle( int n ) + { return mppOptions[n]->title ? OUString( mppOptions[n]->title, strlen(mppOptions[n]->title), osl_getThreadTextEncoding() ) : OUString(); } + SANE_Value_Type GetOptionType( int n ) + { return mppOptions[n]->type; } + SANE_Unit GetOptionUnit( int n ) + { return mppOptions[n]->unit; } + OUString GetOptionUnitName( int n ); + SANE_Int GetOptionCap( int n ) + { return mppOptions[n]->cap; } + SANE_Constraint_Type GetOptionConstraintType( int n ) + { return mppOptions[n]->constraint_type; } + const char** GetStringConstraint( int n ) + { return const_cast<const char**>(mppOptions[n]->constraint.string_list); } + int GetRange( int, std::unique_ptr<double[]>& ); + + inline int GetOptionElements( int n ); + int GetOptionByName( const char* ); + bool GetOptionValue( int, bool& ); + bool GetOptionValue( int, OString& ); + bool GetOptionValue( int, double&, int nElement = 0 ); + bool GetOptionValue( int, double* ); + + void SetOptionValue( int, bool ); + void SetOptionValue( int, std::u16string_view ); + void SetOptionValue( int, double, int nElement = 0 ); + void SetOptionValue( int, double const * ); + + bool ActivateButtonOption( int ); + + int CountOptions() { return mnOptions; } + int GetDeviceNumber() const { return mnDevice; } + + bool Open( const char* ); + bool Open( int ); + void Close(); + void ReloadDevices(); + void ReloadOptions(); + + bool Start( BitmapTransporter& ); + + inline Link<Sane&,void> SetReloadOptionsHdl( const Link<Sane&,void>& rLink ); +}; + + +inline int Sane::GetOptionElements( int n ) +{ + if( mppOptions[n]->type == SANE_TYPE_FIXED || + mppOptions[n]->type == SANE_TYPE_INT ) + { + return mppOptions[n]->size/sizeof( SANE_Word ); + } + return 1; +} + + +inline Link<Sane&,void> Sane::SetReloadOptionsHdl( const Link<Sane&,void>& rLink ) +{ + Link<Sane&,void> aRet = maReloadOptionsLink; + maReloadOptionsLink = rLink; + return aRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/sanedlg.cxx b/extensions/source/scanner/sanedlg.cxx new file mode 100644 index 0000000000..b4f5e9a36c --- /dev/null +++ b/extensions/source/scanner/sanedlg.cxx @@ -0,0 +1,1479 @@ +/* -*- 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 <stdlib.h> +#include <o3tl/sprintf.hxx> +#include <tools/config.hxx> +#include <unotools/resmgr.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/customweld.hxx> +#include <vcl/dibtools.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/weld.hxx> +#include <vcl/svapp.hxx> +#include <vcl/event.hxx> +#include "sanedlg.hxx" +#include "grid.hxx" +#include <math.h> +#include <sal/macros.h> +#include <sal/log.hxx> +#include <rtl/strbuf.hxx> +#include <memory> +#include <strings.hrc> + +#define PREVIEW_WIDTH 113 +#define PREVIEW_HEIGHT 160 + +#define RECT_SIZE_PIX 7 + +namespace { + +void DrawRect(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + tools::Rectangle aRect(rRect); + rRenderContext.SetFillColor(COL_BLACK); + rRenderContext.SetLineColor(); + rRenderContext.DrawRect(aRect); + aRect.Move(1, 1); + aRect.AdjustRight(-2); + aRect.AdjustBottom(-2); + rRenderContext.SetFillColor(); + rRenderContext.SetLineColor(COL_WHITE); + rRenderContext.DrawRect(aRect); +} + +void DrawRectangles(vcl::RenderContext& rRenderContext, Point const & rUL, Point const & rBR) +{ + Point aUR(rBR.X(), rUL.Y()); + Point aBL(rUL.X(), rBR.Y()); + int nMiddleX = (rBR.X() - rUL.X()) / 2 + rUL.X(); + int nMiddleY = (rBR.Y() - rUL.Y()) / 2 + rUL.Y(); + + rRenderContext.SetLineColor(COL_WHITE); + rRenderContext.DrawLine(rUL, aBL); + rRenderContext.DrawLine(aBL, rBR); + rRenderContext.DrawLine(rBR, aUR); + rRenderContext.DrawLine(aUR, rUL); + + rRenderContext.SetLineColor(COL_BLACK); + LineInfo aInfo(LineStyle::Dash, 1); + aInfo.SetDistance(8); + aInfo.SetDotLen(4); + aInfo.SetDotCount(1); + rRenderContext.DrawLine(rUL, aBL, aInfo); + rRenderContext.DrawLine(aBL, rBR, aInfo); + rRenderContext.DrawLine(rBR, aUR, aInfo); + rRenderContext.DrawLine(aUR, rUL, aInfo); + + Size aSize(RECT_SIZE_PIX, RECT_SIZE_PIX); + DrawRect(rRenderContext, tools::Rectangle(rUL, aSize)); + DrawRect(rRenderContext, tools::Rectangle(Point(aBL.X(), aBL.Y() - RECT_SIZE_PIX), aSize)); + DrawRect(rRenderContext, tools::Rectangle(Point(rBR.X() - RECT_SIZE_PIX, rBR.Y() - RECT_SIZE_PIX), aSize)); + DrawRect(rRenderContext, tools::Rectangle(Point(aUR.X() - RECT_SIZE_PIX, aUR.Y()), aSize)); + DrawRect(rRenderContext, tools::Rectangle(Point(nMiddleX - RECT_SIZE_PIX / 2, rUL.Y()), aSize)); + DrawRect(rRenderContext, tools::Rectangle(Point(nMiddleX - RECT_SIZE_PIX / 2, rBR.Y() - RECT_SIZE_PIX), aSize)); + DrawRect(rRenderContext, tools::Rectangle(Point(rUL.X(), nMiddleY - RECT_SIZE_PIX / 2), aSize)); + DrawRect(rRenderContext, tools::Rectangle(Point(rBR.X() - RECT_SIZE_PIX, nMiddleY - RECT_SIZE_PIX / 2), aSize)); +} + +} + +class ScanPreview : public weld::CustomWidgetController +{ +private: + enum DragDirection { TopLeft, Top, TopRight, Right, BottomRight, Bottom, + BottomLeft, Left }; + + BitmapEx maPreviewBitmapEx; + tools::Rectangle maPreviewRect; + Point maTopLeft, maBottomRight; + Point maMinTopLeft, maMaxBottomRight; + SaneDlg* mpParentDialog; + DragDirection meDragDirection; + bool mbDragEnable; + bool mbIsDragging; + +public: + ScanPreview() + : maMaxBottomRight(PREVIEW_WIDTH, PREVIEW_HEIGHT) + , mpParentDialog(nullptr) + , meDragDirection(TopLeft) + , mbDragEnable(false) + , mbIsDragging(false) + { + } + + void Init(SaneDlg *pParent) + { + mpParentDialog = pParent; + } + + void ResetForNewScanner() + { + maTopLeft = Point(); + maBottomRight = Point(); + maMinTopLeft = Point(); + maMaxBottomRight = Point(PREVIEW_WIDTH, PREVIEW_HEIGHT); + } + + void EnableDrag() + { + mbDragEnable = true; + } + + void DisableDrag() + { + mbDragEnable = false; + } + + bool IsDragEnabled() const + { + return mbDragEnable; + } + + virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override; + virtual bool MouseButtonDown(const MouseEvent& rMEvt) override; + virtual bool MouseMove(const MouseEvent& rMEvt) override; + virtual bool MouseButtonUp(const MouseEvent& rMEvt) override; + Point GetPixelPos(const Point& rIn) const; + Point GetLogicPos(const Point& rIn) const; + + void GetPreviewLogicRect(Point& rTopLeft, Point &rBottomRight) const + { + rTopLeft = GetLogicPos(maTopLeft); + rBottomRight = GetLogicPos(maBottomRight); + } + void GetMaxLogicRect(Point& rTopLeft, Point &rBottomRight) const + { + rTopLeft = maMinTopLeft; + rBottomRight = maMaxBottomRight; + + } + void ChangePreviewLogicTopLeftY(tools::Long Y) + { + Point aPoint(0, Y); + aPoint = GetPixelPos(aPoint); + maTopLeft.setY( aPoint.Y() ); + } + void ChangePreviewLogicTopLeftX(tools::Long X) + { + Point aPoint(X, 0); + aPoint = GetPixelPos(aPoint); + maTopLeft.setX( aPoint.X() ); + } + void ChangePreviewLogicBottomRightY(tools::Long Y) + { + Point aPoint(0, Y); + aPoint = GetPixelPos(aPoint); + maBottomRight.setY( aPoint.Y() ); + } + void ChangePreviewLogicBottomRightX(tools::Long X) + { + Point aPoint(X, 0); + aPoint = GetPixelPos(aPoint); + maBottomRight.setX( aPoint.X() ); + } + void SetPreviewLogicRect(const Point& rTopLeft, const Point &rBottomRight) + { + maTopLeft = GetPixelPos(rTopLeft); + maBottomRight = GetPixelPos(rBottomRight); + maPreviewRect = tools::Rectangle(maTopLeft, + Size(maBottomRight.X() - maTopLeft.X(), + maBottomRight.Y() - maTopLeft.Y())); + } + void SetPreviewMaxRect(const Point& rTopLeft, const Point &rBottomRight) + { + maMinTopLeft = rTopLeft; + maMaxBottomRight = rBottomRight; + } + void DrawDrag(vcl::RenderContext& rRenderContext); + void UpdatePreviewBounds(); + void SetBitmap(SvStream &rStream) + { + ReadDIBBitmapEx(maPreviewBitmapEx, rStream, true); + } + virtual void SetDrawingArea(weld::DrawingArea* pDrawingArea) override + { + Size aSize(pDrawingArea->get_ref_device().LogicToPixel(Size(PREVIEW_WIDTH, PREVIEW_HEIGHT), MapMode(MapUnit::MapAppFont))); + aSize.setWidth(aSize.getWidth()+1); + aSize.setHeight(aSize.getHeight()+1); + pDrawingArea->set_size_request(aSize.Width(), aSize.Height()); + CustomWidgetController::SetDrawingArea(pDrawingArea); + SetOutputSizePixel(aSize); + } +}; + +SaneDlg::SaneDlg(weld::Window* pParent, Sane& rSane, bool bScanEnabled) + : GenericDialogController(pParent, "modules/scanner/ui/sanedialog.ui", "SaneDialog") + , mpParent(pParent) + , mrSane(rSane) + , mbScanEnabled(bScanEnabled) + , mnCurrentOption(0) + , mnCurrentElement(0) + , mfMin(0.0) + , mfMax(0.0) + , doScan(false) + , mxCancelButton(m_xBuilder->weld_button("cancel")) + , mxDeviceInfoButton(m_xBuilder->weld_button("deviceInfoButton")) + , mxPreviewButton(m_xBuilder->weld_button("previewButton")) + , mxScanButton(m_xBuilder->weld_button("ok")) + , mxButtonOption(m_xBuilder->weld_button("optionsButton")) + , mxOptionTitle(m_xBuilder->weld_label("optionTitleLabel")) + , mxOptionDescTxt(m_xBuilder->weld_label("optionsDescLabel")) + , mxVectorTxt(m_xBuilder->weld_label("vectorLabel")) + , mxLeftField(m_xBuilder->weld_metric_spin_button("leftSpinbutton", FieldUnit::PIXEL)) + , mxTopField(m_xBuilder->weld_metric_spin_button("topSpinbutton", FieldUnit::PIXEL)) + , mxRightField(m_xBuilder->weld_metric_spin_button("rightSpinbutton", FieldUnit::PIXEL)) + , mxBottomField(m_xBuilder->weld_metric_spin_button("bottomSpinbutton", FieldUnit::PIXEL)) + , mxDeviceBox(m_xBuilder->weld_combo_box("deviceCombobox")) + , mxReslBox(m_xBuilder->weld_combo_box("reslCombobox")) + , mxAdvancedBox(m_xBuilder->weld_check_button("advancedCheckbutton")) + , mxVectorBox(m_xBuilder->weld_spin_button("vectorSpinbutton")) + , mxQuantumRangeBox(m_xBuilder->weld_combo_box("quantumRangeCombobox")) + , mxStringRangeBox(m_xBuilder->weld_combo_box("stringRangeCombobox")) + , mxBoolCheckBox(m_xBuilder->weld_check_button("boolCheckbutton")) + , mxStringEdit(m_xBuilder->weld_entry("stringEntry")) + , mxNumericEdit(m_xBuilder->weld_entry("numericEntry")) + , mxOptionBox(m_xBuilder->weld_tree_view("optionSvTreeListBox")) + , mxPreview(new ScanPreview) + , mxPreviewWnd(new weld::CustomWeld(*m_xBuilder, "preview", *mxPreview)) +{ + Size aSize(mxOptionBox->get_approximate_digit_width() * 32, mxOptionBox->get_height_rows(8)); + mxOptionTitle->set_size_request(aSize.Width(), aSize.Height() / 2); + mxOptionBox->set_size_request(aSize.Width(), aSize.Height()); + mxPreview->Init(this); + if( Sane::IsSane() ) + { + InitDevices(); // opens first sane device + DisableOption(); + InitFields(); + } + + mxDeviceInfoButton->connect_clicked( LINK( this, SaneDlg, ClickBtnHdl ) ); + mxPreviewButton->connect_clicked( LINK( this, SaneDlg, ClickBtnHdl ) ); + mxScanButton->connect_clicked( LINK( this, SaneDlg, ClickBtnHdl ) ); + mxButtonOption->connect_clicked( LINK( this, SaneDlg, ClickBtnHdl ) ); + mxDeviceBox->connect_changed( LINK( this, SaneDlg, SelectHdl ) ); + mxOptionBox->connect_changed( LINK( this, SaneDlg, OptionsBoxSelectHdl ) ); + mxCancelButton->connect_clicked( LINK( this, SaneDlg, ClickBtnHdl ) ); + mxBoolCheckBox->connect_toggled( LINK( this, SaneDlg, ToggleBtnHdl ) ); + mxStringEdit->connect_changed( LINK( this, SaneDlg, ModifyHdl ) ); + mxNumericEdit->connect_changed( LINK( this, SaneDlg, ModifyHdl ) ); + mxVectorBox->connect_changed( LINK( this, SaneDlg, ModifyHdl ) ); + mxReslBox->connect_changed( LINK( this, SaneDlg, ValueModifyHdl ) ); + mxStringRangeBox->connect_changed( LINK( this, SaneDlg, SelectHdl ) ); + mxQuantumRangeBox->connect_changed( LINK( this, SaneDlg, SelectHdl ) ); + mxLeftField->connect_value_changed( LINK( this, SaneDlg, MetricValueModifyHdl ) ); + mxRightField->connect_value_changed( LINK( this, SaneDlg, MetricValueModifyHdl) ); + mxTopField->connect_value_changed( LINK( this, SaneDlg, MetricValueModifyHdl) ); + mxBottomField->connect_value_changed( LINK( this, SaneDlg, MetricValueModifyHdl) ); + mxAdvancedBox->connect_toggled( LINK( this, SaneDlg, ToggleBtnHdl ) ); + + maOldLink = mrSane.SetReloadOptionsHdl( LINK( this, SaneDlg, ReloadSaneOptionsHdl ) ); +} + +SaneDlg::~SaneDlg() +{ + mrSane.SetReloadOptionsHdl(maOldLink); +} + +namespace { + +OUString SaneResId(TranslateId aID) +{ + return Translate::get(aID, Translate::Create("pcr")); +} + +} + +short SaneDlg::run() +{ + if (!Sane::IsSane()) + { + std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(mpParent, + VclMessageType::Warning, VclButtonsType::Ok, + SaneResId(STR_COULD_NOT_BE_INIT))); + xErrorBox->run(); + return RET_CANCEL; + } + LoadState(); + return GenericDialogController::run(); +} + +void SaneDlg::InitDevices() +{ + if( ! Sane::IsSane() ) + return; + + if( mrSane.IsOpen() ) + mrSane.Close(); + mrSane.ReloadDevices(); + mxDeviceBox->clear(); + for (int i = 0; i < Sane::CountDevices(); ++i) + mxDeviceBox->append_text(Sane::GetName(i)); + if( Sane::CountDevices() ) + { + mrSane.Open(0); + mxDeviceBox->set_active(0); + } +} + +void SaneDlg::InitFields() +{ + if( ! Sane::IsSane() ) + return; + + int nOption, i, nValue; + double fValue; + const char *ppSpecialOptions[] = { + "resolution", + "tl-x", + "tl-y", + "br-x", + "br-y", + "preview" + }; + + mxPreview->EnableDrag(); + mxReslBox->clear(); + Point aTopLeft, aBottomRight; + mxPreview->GetPreviewLogicRect(aTopLeft, aBottomRight); + Point aMinTopLeft, aMaxBottomRight; + mxPreview->GetMaxLogicRect(aMinTopLeft, aMaxBottomRight); + mxScanButton->set_visible( mbScanEnabled ); + + if( ! mrSane.IsOpen() ) + return; + + // set Resolution + nOption = mrSane.GetOptionByName( "resolution" ); + if( nOption != -1 ) + { + double fRes; + + if( mrSane.GetOptionValue( nOption, fRes ) ) + { + mxReslBox->set_sensitive(true); + + mxReslBox->set_entry_text(OUString::number(static_cast<sal_uInt32>(fRes))); + std::unique_ptr<double[]> pDouble; + nValue = mrSane.GetRange( nOption, pDouble ); + if( nValue > -1 ) + { + assert(pDouble); + if( nValue ) + { + for( i=0; i<nValue; i++ ) + { + if( i == 0 || i == nValue-1 || ! ( static_cast<int>(pDouble[i]) % 20) ) + mxReslBox->append_text(OUString::number(static_cast<sal_uInt32>(pDouble[i]))); + } + } + else + { + mxReslBox->append_text(OUString::number(static_cast<sal_uInt32>(pDouble[0]))); + // Can only select 75 and 2400 dpi in Scanner dialogue + // scanner allows random setting of dpi resolution, a slider might be useful + // support that + // workaround: offer at least some more standard dpi resolution between + // min and max value + int bGot300 = 0; + for (sal_uInt32 nRes = static_cast<sal_uInt32>(pDouble[0]) * 2; nRes < static_cast<sal_uInt32>(pDouble[1]); nRes = nRes * 2) + { + if ( !bGot300 && nRes > 300 ) { + nRes = 300; bGot300 = 1; + } + mxReslBox->append_text(OUString::number(nRes)); + } + mxReslBox->append_text(OUString::number(static_cast<sal_uInt32>(pDouble[1]))); + } + } + else + mxReslBox->set_sensitive( false ); + } + } + else + mxReslBox->set_sensitive( false ); + + // set scan area + for( i = 0; i < 4; i++ ) + { + char const *pOptionName = nullptr; + weld::MetricSpinButton* pField = nullptr; + switch( i ) + { + case 0: + pOptionName = "tl-x"; + pField = mxLeftField.get(); + break; + case 1: + pOptionName = "tl-y"; + pField = mxTopField.get(); + break; + case 2: + pOptionName = "br-x"; + pField = mxRightField.get(); + break; + case 3: + pOptionName = "br-y"; + pField = mxBottomField.get(); + } + nOption = pOptionName ? mrSane.GetOptionByName( pOptionName ) : -1; + if( nOption != -1 ) + { + if( mrSane.GetOptionValue( nOption, fValue ) ) + { + if( mrSane.GetOptionUnit( nOption ) == SANE_UNIT_MM ) + { + pField->set_unit( FieldUnit::MM ); + pField->set_value( static_cast<int>(fValue), FieldUnit::MM ); + } + else // SANE_UNIT_PIXEL + { + pField->set_unit( FieldUnit::PIXEL ); + pField->set_value( static_cast<int>(fValue), FieldUnit::PIXEL ); + } + switch( i ) { + case 0: aTopLeft.setX( static_cast<int>(fValue) );break; + case 1: aTopLeft.setY( static_cast<int>(fValue) );break; + case 2: aBottomRight.setX( static_cast<int>(fValue) );break; + case 3: aBottomRight.setY( static_cast<int>(fValue) );break; + } + } + std::unique_ptr<double[]> pDouble; + nValue = mrSane.GetRange( nOption, pDouble ); + if( nValue > -1 ) + { + if( pDouble ) + { + pField->set_min( static_cast<tools::Long>(pDouble[0]), FieldUnit::NONE ); + if( nValue ) + pField->set_max( static_cast<tools::Long>(pDouble[ nValue-1 ]), FieldUnit::NONE ); + else + pField->set_max( static_cast<tools::Long>(pDouble[ 1 ]), FieldUnit::NONE ); + } + switch( i ) { + case 0: aMinTopLeft.setX( pField->get_min(FieldUnit::NONE) );break; + case 1: aMinTopLeft.setY( pField->get_min(FieldUnit::NONE) );break; + case 2: aMaxBottomRight.setX( pField->get_max(FieldUnit::NONE) );break; + case 3: aMaxBottomRight.setY( pField->get_max(FieldUnit::NONE) );break; + } + } + else + { + switch( i ) { + case 0: aMinTopLeft.setX( static_cast<int>(fValue) );break; + case 1: aMinTopLeft.setY( static_cast<int>(fValue) );break; + case 2: aMaxBottomRight.setX( static_cast<int>(fValue) );break; + case 3: aMaxBottomRight.setY( static_cast<int>(fValue) );break; + } + } + pField->set_sensitive(true); + } + else + { + mxPreview->DisableDrag(); + pField->set_min( 0, FieldUnit::NONE ); + switch( i ) { + case 0: + aMinTopLeft.setX( 0 ); + aTopLeft.setX( 0 ); + pField->set_max( PREVIEW_WIDTH, FieldUnit::NONE ); + pField->set_value( 0, FieldUnit::NONE ); + break; + case 1: + aMinTopLeft.setY( 0 ); + aTopLeft.setY( 0 ); + pField->set_max( PREVIEW_HEIGHT, FieldUnit::NONE ); + pField->set_value( 0, FieldUnit::NONE ); + break; + case 2: + aMaxBottomRight.setX( PREVIEW_WIDTH ); + aBottomRight.setX( PREVIEW_WIDTH ); + pField->set_max( PREVIEW_WIDTH, FieldUnit::NONE ); + pField->set_value( PREVIEW_WIDTH, FieldUnit::NONE ); + break; + case 3: + aMaxBottomRight.setY( PREVIEW_HEIGHT ); + aBottomRight.setY( PREVIEW_HEIGHT ); + pField->set_max( PREVIEW_HEIGHT, FieldUnit::NONE ); + pField->set_value( PREVIEW_HEIGHT, FieldUnit::NONE ); + break; + } + pField->set_sensitive(false); + } + } + + mxPreview->SetPreviewMaxRect(aMinTopLeft, aMaxBottomRight); + mxPreview->SetPreviewLogicRect(aTopLeft, aBottomRight); + mxPreview->Invalidate(); + + // fill OptionBox + mxOptionBox->clear(); + std::unique_ptr<weld::TreeIter> xParentEntry(mxOptionBox->make_iterator()); + bool bGroupRejected = false; + for( i = 1; i < mrSane.CountOptions(); i++ ) + { + OUString aOption=mrSane.GetOptionName( i ); + bool bInsertAdvanced = + (mrSane.GetOptionCap( i ) & SANE_CAP_ADVANCED) == 0 || + mxAdvancedBox->get_active(); + if( mrSane.GetOptionType( i ) == SANE_TYPE_GROUP ) + { + if( bInsertAdvanced ) + { + aOption = mrSane.GetOptionTitle( i ); + mxOptionBox->append(xParentEntry.get()); + mxOptionBox->set_text(*xParentEntry, aOption, 0); + bGroupRejected = false; + } + else + bGroupRejected = true; + } + else if( !aOption.isEmpty() && + ! ( mrSane.GetOptionCap( i ) & + ( + SANE_CAP_HARD_SELECT | + SANE_CAP_INACTIVE + ) ) && + bInsertAdvanced && ! bGroupRejected ) + { + bool bIsSpecial = false; + for( size_t n = 0; !bIsSpecial && + n < SAL_N_ELEMENTS(ppSpecialOptions); n++ ) + { + if( aOption == OUString::createFromAscii(ppSpecialOptions[n]) ) + bIsSpecial=true; + } + if( ! bIsSpecial ) + { + if (xParentEntry) + mxOptionBox->append(xParentEntry.get(), aOption); + else + mxOptionBox->append_text(aOption); + } + } + } +} + +IMPL_LINK( SaneDlg, ClickBtnHdl, weld::Button&, rButton, void ) +{ + if( mrSane.IsOpen() ) + { + if( &rButton == mxDeviceInfoButton.get() ) + { + OUString aString(SaneResId(STR_DEVICE_DESC)); + aString = aString.replaceFirst( "%s", Sane::GetName( mrSane.GetDeviceNumber() ) ); + aString = aString.replaceFirst( "%s", Sane::GetVendor( mrSane.GetDeviceNumber() ) ); + aString = aString.replaceFirst( "%s", Sane::GetModel( mrSane.GetDeviceNumber() ) ); + aString = aString.replaceFirst( "%s", Sane::GetType( mrSane.GetDeviceNumber() ) ); + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Info, VclButtonsType::Ok, + aString)); + xInfoBox->run(); + } + else if( &rButton == mxPreviewButton.get() ) + AcquirePreview(); + else if( &rButton == mxButtonOption.get() ) + { + + SANE_Value_Type nType = mrSane.GetOptionType( mnCurrentOption ); + switch( nType ) + { + case SANE_TYPE_BUTTON: + mrSane.ActivateButtonOption( mnCurrentOption ); + break; + case SANE_TYPE_FIXED: + case SANE_TYPE_INT: + { + int nElements = mrSane.GetOptionElements( mnCurrentOption ); + std::unique_ptr<double[]> x(new double[ nElements ]); + std::unique_ptr<double[]> y(new double[ nElements ]); + for( int i = 0; i < nElements; i++ ) + x[ i ] = static_cast<double>(i); + mrSane.GetOptionValue( mnCurrentOption, y.get() ); + + GridDialog aGrid(m_xDialog.get(), x.get(), y.get(), nElements); + aGrid.set_title( mrSane.GetOptionName( mnCurrentOption ) ); + aGrid.setBoundings( 0, mfMin, nElements, mfMax ); + if (aGrid.run() && aGrid.getNewYValues()) + mrSane.SetOptionValue( mnCurrentOption, aGrid.getNewYValues() ); + } + break; + case SANE_TYPE_BOOL: + case SANE_TYPE_STRING: + case SANE_TYPE_GROUP: + break; + } + } + } + if (&rButton == mxScanButton.get()) + { + double fRes = static_cast<double>(mxReslBox->get_active_text().toUInt32()); + SetAdjustedNumericalValue( "resolution", fRes ); + UpdateScanArea(true); + SaveState(); + m_xDialog->response(mrSane.IsOpen() ? RET_OK : RET_CANCEL); + doScan = mrSane.IsOpen(); + } + else if( &rButton == mxCancelButton.get() ) + { + mrSane.Close(); + m_xDialog->response(RET_CANCEL); + } +} + +IMPL_LINK( SaneDlg, ToggleBtnHdl, weld::Toggleable&, rButton, void ) +{ + if( mrSane.IsOpen() ) + { + if( &rButton == mxBoolCheckBox.get() ) + { + mrSane.SetOptionValue( mnCurrentOption, + mxBoolCheckBox->get_active() ); + } + else if( &rButton == mxAdvancedBox.get() ) + { + ReloadSaneOptionsHdl( mrSane ); + } + } +} + +IMPL_LINK( SaneDlg, SelectHdl, weld::ComboBox&, rListBox, void ) +{ + if( &rListBox == mxDeviceBox.get() && Sane::IsSane() && Sane::CountDevices() ) + { + int nNewNumber = mxDeviceBox->get_active(); + int nOldNumber = mrSane.GetDeviceNumber(); + if (nNewNumber != nOldNumber) + { + mrSane.Close(); + mrSane.Open(nNewNumber); + mxPreview->ResetForNewScanner(); + InitFields(); + } + } + if( mrSane.IsOpen() ) + { + if( &rListBox == mxQuantumRangeBox.get() ) + { + double fValue = mxQuantumRangeBox->get_active_text().toDouble(); + mrSane.SetOptionValue(mnCurrentOption, fValue, mnCurrentElement); + } + else if( &rListBox == mxStringRangeBox.get() ) + { + mrSane.SetOptionValue(mnCurrentOption, mxStringRangeBox->get_active_text()); + } + } +} + +IMPL_LINK_NOARG(SaneDlg, OptionsBoxSelectHdl, weld::TreeView&, void) +{ + if (!Sane::IsSane()) + return; + + OUString aOption = mxOptionBox->get_selected_text(); + int nOption = mrSane.GetOptionByName(OUStringToOString(aOption, + osl_getThreadTextEncoding()).getStr()); + if( nOption == -1 || nOption == mnCurrentOption ) + return; + + DisableOption(); + mnCurrentOption = nOption; + mxOptionTitle->set_label(mrSane.GetOptionTitle(mnCurrentOption)); + SANE_Value_Type nType = mrSane.GetOptionType( mnCurrentOption ); + SANE_Constraint_Type nConstraint; + switch( nType ) + { + case SANE_TYPE_BOOL: EstablishBoolOption();break; + case SANE_TYPE_STRING: + nConstraint = mrSane.GetOptionConstraintType( mnCurrentOption ); + if( nConstraint == SANE_CONSTRAINT_STRING_LIST ) + EstablishStringRange(); + else + EstablishStringOption(); + break; + case SANE_TYPE_FIXED: + case SANE_TYPE_INT: + { + nConstraint = mrSane.GetOptionConstraintType( mnCurrentOption ); + int nElements = mrSane.GetOptionElements( mnCurrentOption ); + mnCurrentElement = 0; + if( nConstraint == SANE_CONSTRAINT_RANGE || + nConstraint == SANE_CONSTRAINT_WORD_LIST ) + EstablishQuantumRange(); + else + { + mfMin = mfMax = 0.0; + EstablishNumericOption(); + } + if( nElements > 1 ) + { + if( nElements <= 10 ) + { + mxVectorBox->set_range(1, mrSane.GetOptionElements(mnCurrentOption)); + mxVectorBox->set_value(1); + mxVectorBox->show(); + mxVectorTxt->show(); + } + else + { + DisableOption(); + // bring up dialog only on button click + EstablishButtonOption(); + } + } + } + break; + case SANE_TYPE_BUTTON: + EstablishButtonOption(); + break; + default: break; + } +} + +IMPL_LINK(SaneDlg, ModifyHdl, weld::Entry&, rEdit, void) +{ + if( !mrSane.IsOpen() ) + return; + + if (&rEdit == mxStringEdit.get()) + { + mrSane.SetOptionValue( mnCurrentOption, mxStringEdit->get_text() ); + } + else if (&rEdit == mxNumericEdit.get()) + { + double fValue = mxNumericEdit->get_text().toDouble(); + if( mfMin != mfMax && ( fValue < mfMin || fValue > mfMax ) ) + { + char pBuf[256]; + if( fValue < mfMin ) + fValue = mfMin; + else if( fValue > mfMax ) + fValue = mfMax; + o3tl::sprintf( pBuf, "%g", fValue ); + mxNumericEdit->set_text( OUString( pBuf, strlen(pBuf), osl_getThreadTextEncoding() ) ); + } + mrSane.SetOptionValue( mnCurrentOption, fValue, mnCurrentElement ); + } + else if (&rEdit == mxVectorBox.get()) + { + mnCurrentElement = mxVectorBox->get_value() - 1; + double fValue; + if( mrSane.GetOptionValue( mnCurrentOption, fValue, mnCurrentElement )) + { + char pBuf[256]; + o3tl::sprintf( pBuf, "%g", fValue ); + OUString aValue( pBuf, strlen(pBuf), osl_getThreadTextEncoding() ); + mxNumericEdit->set_text( aValue ); + mxQuantumRangeBox->set_active_text( aValue ); + } + } +} + +IMPL_LINK(SaneDlg, ValueModifyHdl, weld::ComboBox&, rEdit, void) +{ + if( !mrSane.IsOpen() ) + return; + + if (&rEdit != mxReslBox.get()) + return; + + double fRes = static_cast<double>(mxReslBox->get_active_text().toUInt32()); + int nOption = mrSane.GetOptionByName( "resolution" ); + if( nOption == -1 ) + return; + + std::unique_ptr<double[]> pDouble; + int nValues = mrSane.GetRange( nOption, pDouble ); + if( nValues > 0 ) + { + int i; + for( i = 0; i < nValues; i++ ) + { + if( fRes == pDouble[i] ) + break; + } + if( i >= nValues ) + fRes = pDouble[0]; + } + else if( nValues == 0 ) + { + if( fRes < pDouble[ 0 ] ) + fRes = pDouble[ 0 ]; + if( fRes > pDouble[ 1 ] ) + fRes = pDouble[ 1 ]; + } + mxReslBox->set_entry_text(OUString::number(static_cast<sal_uInt32>(fRes))); +} + +IMPL_LINK(SaneDlg, MetricValueModifyHdl, weld::MetricSpinButton&, rEdit, void) +{ + if( !mrSane.IsOpen() ) + return; + + if (&rEdit == mxTopField.get()) + { + mxPreview->ChangePreviewLogicTopLeftY(mxTopField->get_value(FieldUnit::NONE)); + mxPreview->Invalidate(); + } + else if (&rEdit == mxLeftField.get()) + { + mxPreview->ChangePreviewLogicTopLeftX(mxLeftField->get_value(FieldUnit::NONE)); + mxPreview->Invalidate(); + } + else if (&rEdit == mxBottomField.get()) + { + mxPreview->ChangePreviewLogicBottomRightY(mxBottomField->get_value(FieldUnit::NONE)); + mxPreview->Invalidate(); + } + else if (&rEdit == mxRightField.get()) + { + mxPreview->ChangePreviewLogicBottomRightX(mxRightField->get_value(FieldUnit::NONE)); + mxPreview->Invalidate(); + } +} + +IMPL_LINK_NOARG( SaneDlg, ReloadSaneOptionsHdl, Sane&, void ) +{ + mnCurrentOption = -1; + mnCurrentElement = 0; + DisableOption(); + InitFields(); + mxPreview->Invalidate(); +} + +void SaneDlg::AcquirePreview() +{ + if( ! mrSane.IsOpen() ) + return; + + UpdateScanArea( true ); + // set small resolution for preview + double fResl = static_cast<double>(mxReslBox->get_active_text().toUInt32()); + SetAdjustedNumericalValue( "resolution", 30.0 ); + + int nOption = mrSane.GetOptionByName( "preview" ); + if( nOption == -1 ) + { + OUString aString(SaneResId(STR_SLOW_PREVIEW)); + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::OkCancel, + aString)); + if (xBox->run() == RET_CANCEL) + return; + } + else + mrSane.SetOptionValue( nOption, true ); + + rtl::Reference<BitmapTransporter> xTransporter(new BitmapTransporter); + if (!mrSane.Start(*xTransporter)) + { + std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, + SaneResId(STR_ERROR_SCAN))); + xErrorBox->run(); + } + else + { +#if OSL_DEBUG_LEVEL > 0 + SAL_INFO("extensions.scanner", "Previewbitmapstream contains " << xTransporter->getStream().TellEnd() << "bytes"); +#endif + xTransporter->getStream().Seek( STREAM_SEEK_TO_BEGIN ); + mxPreview->SetBitmap(xTransporter->getStream()); + } + + SetAdjustedNumericalValue( "resolution", fResl ); + mxReslBox->set_entry_text(OUString::number(static_cast<sal_uInt32>(fResl))); + + mxPreview->UpdatePreviewBounds(); + mxPreview->Invalidate(); +} + +void ScanPreview::UpdatePreviewBounds() +{ + if( mbDragEnable ) + { + maPreviewRect = tools::Rectangle( maTopLeft, + Size( maBottomRight.X() - maTopLeft.X(), + maBottomRight.Y() - maTopLeft.Y() ) + ); + } + else + { + Size aBMSize( maPreviewBitmapEx.GetSizePixel() ); + if( aBMSize.Width() > aBMSize.Height() && aBMSize.Width() ) + { + int nVHeight = (maBottomRight.X() - maTopLeft.X()) * aBMSize.Height() / aBMSize.Width(); + maPreviewRect = tools::Rectangle( Point( maTopLeft.X(), ( maTopLeft.Y() + maBottomRight.Y() )/2 - nVHeight/2 ), + Size( maBottomRight.X() - maTopLeft.X(), + nVHeight ) ); + } + else if (aBMSize.Height()) + { + int nVWidth = (maBottomRight.Y() - maTopLeft.Y()) * aBMSize.Width() / aBMSize.Height(); + maPreviewRect = tools::Rectangle( Point( ( maTopLeft.X() + maBottomRight.X() )/2 - nVWidth/2, maTopLeft.Y() ), + Size( nVWidth, + maBottomRight.Y() - maTopLeft.Y() ) ); + } + } +} + +void ScanPreview::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + rRenderContext.SetMapMode(MapMode(MapUnit::MapAppFont)); + rRenderContext.SetFillColor(COL_WHITE); + rRenderContext.SetLineColor(COL_WHITE); + rRenderContext.DrawRect(tools::Rectangle(Point(0, 0), + Size(PREVIEW_WIDTH, PREVIEW_HEIGHT))); + rRenderContext.SetMapMode(MapMode(MapUnit::MapPixel)); + // check for sane values + rRenderContext.DrawBitmapEx(maPreviewRect.TopLeft(), maPreviewRect.GetSize(), maPreviewBitmapEx); + + DrawDrag(rRenderContext); +} + +void SaneDlg::DisableOption() +{ + mxBoolCheckBox->hide(); + mxStringEdit->hide(); + mxNumericEdit->hide(); + mxQuantumRangeBox->hide(); + mxStringRangeBox->hide(); + mxButtonOption->hide(); + mxVectorBox->hide(); + mxVectorTxt->hide(); + mxOptionDescTxt->hide(); +} + +void SaneDlg::EstablishBoolOption() +{ + bool bSuccess, bValue; + + bSuccess = mrSane.GetOptionValue( mnCurrentOption, bValue ); + if( bSuccess ) + { + mxBoolCheckBox->set_label( mrSane.GetOptionName( mnCurrentOption ) ); + mxBoolCheckBox->set_active( bValue ); + mxBoolCheckBox->show(); + } +} + +void SaneDlg::EstablishStringOption() +{ + bool bSuccess; + OString aValue; + + bSuccess = mrSane.GetOptionValue( mnCurrentOption, aValue ); + if( bSuccess ) + { + mxOptionDescTxt->set_label( mrSane.GetOptionName( mnCurrentOption ) ); + mxOptionDescTxt->show(); + mxStringEdit->set_text(OStringToOUString(aValue, osl_getThreadTextEncoding())); + mxStringEdit->show(); + } +} + +void SaneDlg::EstablishStringRange() +{ + const char** ppStrings = mrSane.GetStringConstraint( mnCurrentOption ); + mxStringRangeBox->clear(); + for( int i = 0; ppStrings[i] != nullptr; i++ ) + mxStringRangeBox->append_text( OUString( ppStrings[i], strlen(ppStrings[i]), osl_getThreadTextEncoding() ) ); + OString aValue; + mrSane.GetOptionValue( mnCurrentOption, aValue ); + mxStringRangeBox->set_active_text(OStringToOUString(aValue, osl_getThreadTextEncoding())); + mxStringRangeBox->show(); + mxOptionDescTxt->set_label( mrSane.GetOptionName( mnCurrentOption ) ); + mxOptionDescTxt->show(); +} + +void SaneDlg::EstablishQuantumRange() +{ + mpRange.reset(); + int nValues = mrSane.GetRange( mnCurrentOption, mpRange ); + if( nValues == 0 ) + { + mfMin = mpRange[ 0 ]; + mfMax = mpRange[ 1 ]; + mpRange.reset(); + EstablishNumericOption(); + } + else if( nValues > 0 ) + { + char pBuf[ 256 ]; + mxQuantumRangeBox->clear(); + mfMin = mpRange[ 0 ]; + mfMax = mpRange[ nValues-1 ]; + for( int i = 0; i < nValues; i++ ) + { + o3tl::sprintf( pBuf, "%g", mpRange[ i ] ); + mxQuantumRangeBox->append_text( OUString( pBuf, strlen(pBuf), osl_getThreadTextEncoding() ) ); + } + double fValue; + if( mrSane.GetOptionValue( mnCurrentOption, fValue, mnCurrentElement ) ) + { + o3tl::sprintf( pBuf, "%g", fValue ); + mxQuantumRangeBox->set_active_text( OUString( pBuf, strlen(pBuf), osl_getThreadTextEncoding() ) ); + } + mxQuantumRangeBox->show(); + OUString aText = mrSane.GetOptionName( mnCurrentOption ) + " " + + mrSane.GetOptionUnitName( mnCurrentOption ); + mxOptionDescTxt->set_label(aText); + mxOptionDescTxt->show(); + } +} + +void SaneDlg::EstablishNumericOption() +{ + bool bSuccess; + double fValue; + + bSuccess = mrSane.GetOptionValue( mnCurrentOption, fValue ); + if( ! bSuccess ) + return; + + char pBuf[256]; + OUString aText = mrSane.GetOptionName( mnCurrentOption ) + " " + + mrSane.GetOptionUnitName( mnCurrentOption ); + if( mfMin != mfMax ) + { + o3tl::sprintf( pBuf, " < %g ; %g >", mfMin, mfMax ); + aText += OUString( pBuf, strlen(pBuf), osl_getThreadTextEncoding() ); + } + mxOptionDescTxt->set_label( aText ); + mxOptionDescTxt->show(); + o3tl::sprintf( pBuf, "%g", fValue ); + mxNumericEdit->set_text( OUString( pBuf, strlen(pBuf), osl_getThreadTextEncoding() ) ); + mxNumericEdit->show(); +} + +void SaneDlg::EstablishButtonOption() +{ + mxOptionDescTxt->set_label(mrSane.GetOptionName(mnCurrentOption)); + mxOptionDescTxt->show(); + mxButtonOption->show(); +} + +bool ScanPreview::MouseMove(const MouseEvent& rMEvt) +{ + if( !mbIsDragging ) + return false; + + Point aMousePos = rMEvt.GetPosPixel(); + // move into valid area + Point aLogicPos = GetLogicPos( aMousePos ); + aMousePos = GetPixelPos( aLogicPos ); + switch( meDragDirection ) + { + case TopLeft: maTopLeft = aMousePos; break; + case Top: maTopLeft.setY( aMousePos.Y() ); break; + case TopRight: + maTopLeft.setY( aMousePos.Y() ); + maBottomRight.setX( aMousePos.X() ); + break; + case Right: maBottomRight.setX( aMousePos.X() ); break; + case BottomRight: maBottomRight = aMousePos; break; + case Bottom: maBottomRight.setY( aMousePos.Y() ); break; + case BottomLeft: + maTopLeft.setX( aMousePos.X() ); + maBottomRight.setY( aMousePos.Y() ); + break; + case Left: maTopLeft.setX( aMousePos.X() ); break; + default: break; + } + int nSwap; + if( maTopLeft.X() > maBottomRight.X() ) + { + nSwap = maTopLeft.X(); + maTopLeft.setX( maBottomRight.X() ); + maBottomRight.setX( nSwap ); + } + if( maTopLeft.Y() > maBottomRight.Y() ) + { + nSwap = maTopLeft.Y(); + maTopLeft.setY( maBottomRight.Y() ); + maBottomRight.setY( nSwap ); + } + Invalidate(); + mpParentDialog->UpdateScanArea(false); + return false; +} + +bool ScanPreview::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if (!mbIsDragging && mbDragEnable) + { + Point aMousePixel = rMEvt.GetPosPixel(); + + int nMiddleX = ( maBottomRight.X() - maTopLeft.X() ) / 2 - RECT_SIZE_PIX/2 + maTopLeft.X(); + int nMiddleY = ( maBottomRight.Y() - maTopLeft.Y() ) / 2 - RECT_SIZE_PIX/2 + maTopLeft.Y(); + if( aMousePixel.Y() >= maTopLeft.Y() && + aMousePixel.Y() < maTopLeft.Y() + RECT_SIZE_PIX ) + { + if( aMousePixel.X() >= maTopLeft.X() && + aMousePixel.X() < maTopLeft.X() + RECT_SIZE_PIX ) + { + meDragDirection = TopLeft; + mbIsDragging = true; + } + else if( aMousePixel.X() >= nMiddleX && + aMousePixel.X() < nMiddleX + RECT_SIZE_PIX ) + { + meDragDirection = Top; + mbIsDragging = true; + } + else if( aMousePixel.X() > maBottomRight.X() - RECT_SIZE_PIX && + aMousePixel.X() <= maBottomRight.X() ) + { + meDragDirection = TopRight; + mbIsDragging = true; + } + } + else if( aMousePixel.Y() >= nMiddleY && + aMousePixel.Y() < nMiddleY + RECT_SIZE_PIX ) + { + if( aMousePixel.X() >= maTopLeft.X() && + aMousePixel.X() < maTopLeft.X() + RECT_SIZE_PIX ) + { + meDragDirection = Left; + mbIsDragging = true; + } + else if( aMousePixel.X() > maBottomRight.X() - RECT_SIZE_PIX && + aMousePixel.X() <= maBottomRight.X() ) + { + meDragDirection = Right; + mbIsDragging = true; + } + } + else if( aMousePixel.Y() <= maBottomRight.Y() && + aMousePixel.Y() > maBottomRight.Y() - RECT_SIZE_PIX ) + { + if( aMousePixel.X() >= maTopLeft.X() && + aMousePixel.X() < maTopLeft.X() + RECT_SIZE_PIX ) + { + meDragDirection = BottomLeft; + mbIsDragging = true; + } + else if( aMousePixel.X() >= nMiddleX && + aMousePixel.X() < nMiddleX + RECT_SIZE_PIX ) + { + meDragDirection = Bottom; + mbIsDragging = true; + } + else if( aMousePixel.X() > maBottomRight.X() - RECT_SIZE_PIX && + aMousePixel.X() <= maBottomRight.X() ) + { + meDragDirection = BottomRight; + mbIsDragging = true; + } + } + } + + if( mbIsDragging ) + Invalidate(); + + return false; +} + +bool ScanPreview::MouseButtonUp(const MouseEvent&) +{ + if( mbIsDragging ) + mpParentDialog->UpdateScanArea(true); + mbIsDragging = false; + + return false; +} + +void ScanPreview::DrawDrag(vcl::RenderContext& rRenderContext) +{ + if (!mbDragEnable) + return; + + rRenderContext.SetMapMode(MapMode(MapUnit::MapPixel)); + + DrawRectangles(rRenderContext, maTopLeft, maBottomRight); + + rRenderContext.SetMapMode(MapMode(MapUnit::MapAppFont)); +} + +Point ScanPreview::GetPixelPos( const Point& rIn) const +{ + Point aConvert( + ( ( rIn.X() * PREVIEW_WIDTH ) / + ( maMaxBottomRight.X() - maMinTopLeft.X() ) ) + , + ( ( rIn.Y() * PREVIEW_HEIGHT ) + / ( maMaxBottomRight.Y() - maMinTopLeft.Y() ) ) + ); + + return GetDrawingArea()->get_ref_device().LogicToPixel(aConvert, MapMode(MapUnit::MapAppFont)); +} + +Point ScanPreview::GetLogicPos(const Point& rIn) const +{ + Point aConvert = GetDrawingArea()->get_ref_device().PixelToLogic(rIn, MapMode(MapUnit::MapAppFont)); + if( aConvert.X() < 0 ) + aConvert.setX( 0 ); + if( aConvert.X() >= PREVIEW_WIDTH ) + aConvert.setX( PREVIEW_WIDTH-1 ); + if( aConvert.Y() < 0 ) + aConvert.setY( 0 ); + if( aConvert.Y() >= PREVIEW_HEIGHT ) + aConvert.setY( PREVIEW_HEIGHT-1 ); + + aConvert.setX( aConvert.X() * ( maMaxBottomRight.X() - maMinTopLeft.X() ) ); + aConvert.setX( aConvert.X() / ( PREVIEW_WIDTH) ); + aConvert.setY( aConvert.Y() * ( maMaxBottomRight.Y() - maMinTopLeft.Y() ) ); + aConvert.setY( aConvert.Y() / ( PREVIEW_HEIGHT) ); + return aConvert; +} + +void SaneDlg::UpdateScanArea(bool bSend) +{ + if (!mxPreview->IsDragEnabled()) + return; + + Point aUL, aBR; + mxPreview->GetPreviewLogicRect(aUL, aBR); + + mxLeftField->set_value(aUL.X(), FieldUnit::NONE); + mxTopField->set_value(aUL.Y(), FieldUnit::NONE); + mxRightField->set_value(aBR.X(), FieldUnit::NONE); + mxBottomField->set_value(aBR.Y(), FieldUnit::NONE); + + if (!bSend) + return; + + if( mrSane.IsOpen() ) + { + SetAdjustedNumericalValue( "tl-x", static_cast<double>(aUL.X()) ); + SetAdjustedNumericalValue( "tl-y", static_cast<double>(aUL.Y()) ); + SetAdjustedNumericalValue( "br-x", static_cast<double>(aBR.X()) ); + SetAdjustedNumericalValue( "br-y", static_cast<double>(aBR.Y()) ); + } +} + +bool SaneDlg::LoadState() +{ + int i; + + if( ! Sane::IsSane() ) + return false; + + const char* pEnv = getenv("HOME"); + OUString aFileName = (pEnv ? OUString(pEnv, strlen(pEnv), osl_getThreadTextEncoding() ) : OUString()) + "/.so_sane_state"; + Config aConfig( aFileName ); + if( ! aConfig.HasGroup( "SANE" ) ) + return false; + + aConfig.SetGroup( "SANE"_ostr ); + OString aString = aConfig.ReadKey( "SO_LastSaneDevice"_ostr ); + for( i = 0; i < Sane::CountDevices() && aString != OUStringToOString(Sane::GetName(i), osl_getThreadTextEncoding()); i++ ) ; + if( i == Sane::CountDevices() ) + return false; + + mrSane.Close(); + mrSane.Open( aString.getStr() ); + + DisableOption(); + InitFields(); + + if( mrSane.IsOpen() ) + { + int iMax = aConfig.GetKeyCount(); + for (i = 0; i < iMax; ++i) + { + aString = aConfig.GetKeyName( i ); + OString aValue = aConfig.ReadKey( i ); + int nOption = mrSane.GetOptionByName( aString.getStr() ); + if( nOption == -1 ) + continue; + + if (aValue.startsWith("BOOL=")) + { + aValue = aValue.copy(RTL_CONSTASCII_LENGTH("BOOL=")); + bool aBOOL = aValue.toInt32() != 0; + mrSane.SetOptionValue( nOption, aBOOL ); + } + else if (aValue.startsWith("STRING=")) + { + aValue = aValue.copy(RTL_CONSTASCII_LENGTH("STRING=")); + mrSane.SetOptionValue(nOption,OStringToOUString(aValue, osl_getThreadTextEncoding()) ); + } + else if (aValue.startsWith("NUMERIC=")) + { + aValue = aValue.copy(RTL_CONSTASCII_LENGTH("NUMERIC=")); + + sal_Int32 nIndex = 0; + int n = 0; + do + { + OString aSub = aValue.getToken(0, ':', nIndex); + double fValue=0.0; + sscanf(aSub.getStr(), "%lg", &fValue); + SetAdjustedNumericalValue(aString.getStr(), fValue, n++); + } + while ( nIndex >= 0 ); + } + } + } + + DisableOption(); + InitFields(); + + return true; +} + +void SaneDlg::SaveState() +{ + if( ! Sane::IsSane() ) + return; + + const char* pEnv = getenv( "HOME" ); + OUString aFileName; + + if( pEnv ) + aFileName = OUString::createFromAscii(pEnv) + "/.so_sane_state"; + else + aFileName = OStringToOUString("", osl_getThreadTextEncoding()) + "/.so_sane_state"; + + Config aConfig( aFileName ); + aConfig.DeleteGroup( "SANE" ); + aConfig.SetGroup( "SANE"_ostr ); + aConfig.WriteKey( "SO_LastSANEDevice"_ostr, + OUStringToOString(mxDeviceBox->get_active_text(), RTL_TEXTENCODING_UTF8) ); + + static char const* pSaveOptions[] = { + "resolution", + "tl-x", + "tl-y", + "br-x", + "br-y" + }; + for(const char * pSaveOption : pSaveOptions) + { + OString aOption = pSaveOption; + int nOption = mrSane.GetOptionByName( pSaveOption ); + if( nOption > -1 ) + { + SANE_Value_Type nType = mrSane.GetOptionType( nOption ); + switch( nType ) + { + case SANE_TYPE_BOOL: + { + bool bValue; + if( mrSane.GetOptionValue( nOption, bValue ) ) + { + OString aString = "BOOL=" + OString::number(static_cast<sal_Int32>(bValue)); + aConfig.WriteKey(aOption, aString); + } + } + break; + case SANE_TYPE_STRING: + { + OString aValue; + if( mrSane.GetOptionValue( nOption, aValue ) ) + { + OString aString = "STRING=" + aValue; + aConfig.WriteKey( aOption, aString ); + } + } + break; + case SANE_TYPE_FIXED: + case SANE_TYPE_INT: + { + OStringBuffer aString("NUMERIC="); + double fValue; + char buf[256]; + int n; + + for( n = 0; n < mrSane.GetOptionElements( nOption ); n++ ) + { + if( ! mrSane.GetOptionValue( nOption, fValue, n ) ) + break; + if( n > 0 ) + aString.append(':'); + o3tl::sprintf( buf, "%lg", fValue ); + aString.append(buf); + } + if( n >= mrSane.GetOptionElements( nOption ) ) + aConfig.WriteKey( aOption, aString.makeStringAndClear() ); + } + break; + default: + break; + } + } + } +} + +bool SaneDlg::SetAdjustedNumericalValue( + const char* pOption, + double fValue, + int nElement ) +{ + if (! Sane::IsSane() || ! mrSane.IsOpen()) + return false; + int const nOption(mrSane.GetOptionByName(pOption)); + if (nOption == -1) + return false; + + if( nElement < 0 || nElement >= mrSane.GetOptionElements( nOption ) ) + return false; + + std::unique_ptr<double[]> pValues; + int nValues; + if( ( nValues = mrSane.GetRange( nOption, pValues ) ) < 0 ) + { + return false; + } + + SAL_INFO("extensions.scanner", "SaneDlg::SetAdjustedNumericalValue(\"" << pOption << "\", " << fValue << ") "); + + if( nValues ) + { + int nNearest = 0; + double fNearest = 1e6; + for( int i = 0; i < nValues; i++ ) + { + if( fabs( fValue - pValues[ i ] ) < fNearest ) + { + fNearest = fabs( fValue - pValues[ i ] ); + nNearest = i; + } + } + fValue = pValues[ nNearest ]; + } + else + { + if( fValue < pValues[0] ) + fValue = pValues[0]; + if( fValue > pValues[1] ) + fValue = pValues[1]; + } + mrSane.SetOptionValue( nOption, fValue, nElement ); + SAL_INFO("extensions.scanner", "yields " << fValue); + + + return true; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/sanedlg.hxx b/extensions/source/scanner/sanedlg.hxx new file mode 100644 index 0000000000..e9ba1540da --- /dev/null +++ b/extensions/source/scanner/sanedlg.hxx @@ -0,0 +1,111 @@ +/* -*- 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 . + */ +#pragma once + +#include <vcl/customweld.hxx> +#include <vcl/weld.hxx> + +#include "sane.hxx" + +class ScanPreview; + +class SaneDlg : public weld::GenericDialogController +{ +private: + weld::Window* mpParent; + Sane& mrSane; + bool mbScanEnabled; + + Link<Sane&,void> maOldLink; + + int mnCurrentOption; + int mnCurrentElement; + std::unique_ptr<double[]> mpRange; + double mfMin, mfMax; + + bool doScan; + + std::unique_ptr<weld::Button> mxCancelButton; + std::unique_ptr<weld::Button> mxDeviceInfoButton; + std::unique_ptr<weld::Button> mxPreviewButton; + std::unique_ptr<weld::Button> mxScanButton; + std::unique_ptr<weld::Button> mxButtonOption; + + std::unique_ptr<weld::Label> mxOptionTitle; + std::unique_ptr<weld::Label> mxOptionDescTxt; + std::unique_ptr<weld::Label> mxVectorTxt; + + std::unique_ptr<weld::MetricSpinButton> mxLeftField; + std::unique_ptr<weld::MetricSpinButton> mxTopField; + std::unique_ptr<weld::MetricSpinButton> mxRightField; + std::unique_ptr<weld::MetricSpinButton> mxBottomField; + + std::unique_ptr<weld::ComboBox> mxDeviceBox; + std::unique_ptr<weld::ComboBox> mxReslBox; + std::unique_ptr<weld::CheckButton> mxAdvancedBox; + + std::unique_ptr<weld::SpinButton> mxVectorBox; + std::unique_ptr<weld::ComboBox> mxQuantumRangeBox; + std::unique_ptr<weld::ComboBox> mxStringRangeBox; + + std::unique_ptr<weld::CheckButton> mxBoolCheckBox; + + std::unique_ptr<weld::Entry> mxStringEdit; + std::unique_ptr<weld::Entry> mxNumericEdit; + + std::unique_ptr<weld::TreeView> mxOptionBox; + + std::unique_ptr<ScanPreview> mxPreview; + std::unique_ptr<weld::CustomWeld> mxPreviewWnd; + + DECL_LINK( ClickBtnHdl, weld::Button&, void ); + DECL_LINK( ToggleBtnHdl, weld::Toggleable&, void ); + DECL_LINK( SelectHdl, weld::ComboBox&, void ); + DECL_LINK( ModifyHdl, weld::Entry&, void ); + DECL_LINK( MetricValueModifyHdl, weld::MetricSpinButton&, void ); + DECL_LINK( ValueModifyHdl, weld::ComboBox&, void ); + DECL_LINK( ReloadSaneOptionsHdl, Sane&, void ); + DECL_LINK( OptionsBoxSelectHdl, weld::TreeView&, void ); + + void SaveState(); + bool LoadState(); + + void InitDevices(); + void InitFields(); + void AcquirePreview(); + void DisableOption(); + void EstablishBoolOption(); + void EstablishStringOption(); + void EstablishStringRange(); + void EstablishQuantumRange(); + void EstablishNumericOption(); + void EstablishButtonOption(); + + // helper + bool SetAdjustedNumericalValue( const char* pOption, double fValue, int nElement = 0 ); +public: + SaneDlg(weld::Window*, Sane&, bool); + virtual ~SaneDlg() override; + + virtual short run() override; + void UpdateScanArea( bool ); + bool getDoScan() const { return doScan;} +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/scanner.cxx b/extensions/source/scanner/scanner.cxx new file mode 100644 index 0000000000..b661a4f7e2 --- /dev/null +++ b/extensions/source/scanner/scanner.cxx @@ -0,0 +1,89 @@ +/* -*- 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 "scanner.hxx" + +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/namedvaluecollection.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> + +Reference< XInterface > ScannerManager_CreateInstance( const Reference< css::lang::XMultiServiceFactory >& /*rxFactory*/ ) +{ + return *( new ScannerManager() ); +} + + +ScannerManager::ScannerManager() : + mpData( nullptr ) +{ + AcquireData(); +} + + +ScannerManager::~ScannerManager() +{ + ReleaseData(); +} + + +Sequence< sal_Int8 > SAL_CALL ScannerManager::getMaskDIB() +{ + return Sequence< sal_Int8 >(); +} + + +OUString ScannerManager::getImplementationName() +{ + return "com.sun.star.scanner.ScannerManager"; +} + + +sal_Bool ScannerManager::supportsService(OUString const & ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + + +css::uno::Sequence<OUString> ScannerManager::getSupportedServiceNames() +{ + return { "com.sun.star.scanner.ScannerManager" }; +} + + +sal_Bool SAL_CALL ScannerManager::configureScanner( ScannerContext& rContext ) +{ + return configureScannerAndScan( rContext, nullptr ); +} + +void SAL_CALL ScannerManager::initialize(const css::uno::Sequence<css::uno::Any>& rArguments) +{ + ::comphelper::NamedValueCollection aProperties(rArguments); + if (aProperties.has("ParentWindow")) + aProperties.get("ParentWindow") >>= mxDialogParent; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_ScannerManager_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new ScannerManager()); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/scanner.hxx b/extensions/source/scanner/scanner.hxx new file mode 100644 index 0000000000..42e03a4533 --- /dev/null +++ b/extensions/source/scanner/scanner.hxx @@ -0,0 +1,85 @@ +/* -*- 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 . + */ + +#pragma once + +#include <osl/mutex.hxx> +#include <rtl/ustring.hxx> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/awt/XBitmap.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/scanner/XScannerManager2.hpp> + +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::scanner; + +class ScannerManager final : + public cppu::WeakImplHelper< + XScannerManager2, css::awt::XBitmap, css::lang::XServiceInfo, css::lang::XInitialization> +{ + osl::Mutex maProtector; + css::uno::Reference<css::awt::XWindow> mxDialogParent; + void* mpData; + + static void AcquireData(); + void ReleaseData(); + +public: + + ScannerManager(); + virtual ~ScannerManager() override; + + // XScannerManager + virtual Sequence< ScannerContext > SAL_CALL getAvailableScanners() override; + virtual sal_Bool SAL_CALL configureScanner( ScannerContext& scanner_context ) override; + virtual sal_Bool SAL_CALL configureScannerAndScan( ScannerContext& scanner_context, const Reference< css::lang::XEventListener >& rxListener ) override; + virtual void SAL_CALL startScan( const ScannerContext& scanner_context, const Reference< css::lang::XEventListener >& rxListener ) override; + virtual ScanError SAL_CALL getError( const ScannerContext& scanner_context ) override; + virtual Reference< css::awt::XBitmap > SAL_CALL getBitmap( const ScannerContext& scanner_context ) override; + + // XBitmap + virtual css::awt::Size SAL_CALL getSize() override; + virtual Sequence< sal_Int8 > SAL_CALL getDIB() override; + virtual Sequence< sal_Int8 > SAL_CALL getMaskDIB() override; + + OUString SAL_CALL getImplementationName() override; + + sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override; + + css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + virtual void SAL_CALL initialize(const css::uno::Sequence<css::uno::Any>& rArguments) override; + +#ifdef _WIN32 + void* GetData() const { return mpData; } +#endif + void SetData( void* pData ) { ReleaseData(); mpData = pData; } + }; + +/// @throws Exception +Reference< XInterface > ScannerManager_CreateInstance( const Reference< css::lang::XMultiServiceFactory >& rxFactory ); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/scanunx.cxx b/extensions/source/scanner/scanunx.cxx new file mode 100644 index 0000000000..0db920c4c9 --- /dev/null +++ b/extensions/source/scanner/scanunx.cxx @@ -0,0 +1,342 @@ +/* -*- 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 "scanner.hxx" +#include "sanedlg.hxx" +#include <o3tl/safeint.hxx> +#include <osl/thread.hxx> +#include <sal/log.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <memory> + +#include <com/sun/star/scanner/ScannerException.hpp> + +BitmapTransporter::BitmapTransporter() +{ + SAL_INFO("extensions.scanner", "BitmapTransporter"); +} + + +BitmapTransporter::~BitmapTransporter() +{ + SAL_INFO("extensions.scanner", "~BitmapTransporter"); +} + + +css::awt::Size BitmapTransporter::getSize() +{ + osl::MutexGuard aGuard( m_aProtector ); + css::awt::Size aRet; + + // ensure that there is at least a header + int nLen = m_aStream.TellEnd(); + if( nLen > 15 ) + { + int nPreviousPos = m_aStream.Tell(); + m_aStream.Seek( 4 ); + m_aStream.ReadInt32( aRet.Width ).ReadInt32( aRet.Height ); + m_aStream.Seek( nPreviousPos ); + } + else + aRet.Width = aRet.Height = 0; + + + return aRet; +} + + +Sequence< sal_Int8 > BitmapTransporter::getDIB() +{ + osl::MutexGuard aGuard( m_aProtector ); + int nPreviousPos = m_aStream.Tell(); + + // create return value + int nBytes = m_aStream.TellEnd(); + m_aStream.Seek( 0 ); + + Sequence< sal_Int8 > aValue( nBytes ); + m_aStream.ReadBytes( aValue.getArray(), nBytes ); + m_aStream.Seek( nPreviousPos ); + + return aValue; +} + +namespace { + +struct SaneHolder +{ + Sane m_aSane; + Reference< css::awt::XBitmap > m_xBitmap; + osl::Mutex m_aProtector; + ScanError m_nError; + bool m_bBusy; + + SaneHolder() : m_nError(ScanError_ScanErrorNone), m_bBusy(false) {} +}; + + typedef std::vector< std::shared_ptr<SaneHolder> > sanevec; + class allSanes + { + private: + int mnRefCount; + public: + sanevec m_aSanes; + allSanes() : mnRefCount(0) {} + void acquire(); + void release(); + }; + + void allSanes::acquire() + { + ++mnRefCount; + } + + void allSanes::release() + { + // was unused, now because of i99835: "Scanning interface not SANE API + // compliant" destroy all SaneHolder to get Sane Dtor called + --mnRefCount; + if (!mnRefCount) + m_aSanes.clear(); + } + + struct theSaneProtector : public rtl::Static<osl::Mutex, theSaneProtector> {}; + struct theSanes : public rtl::Static<allSanes, theSanes> {}; + +class ScannerThread : public osl::Thread +{ + std::shared_ptr<SaneHolder> m_pHolder; + Reference< css::lang::XEventListener > m_xListener; + ScannerManager* m_pManager; // just for the disposing call + +public: + virtual void SAL_CALL run() override; + virtual void SAL_CALL onTerminated() override { delete this; } +public: + ScannerThread( std::shared_ptr<SaneHolder> pHolder, + const Reference< css::lang::XEventListener >& listener, + ScannerManager* pManager ); + virtual ~ScannerThread() override; +}; + +} + +ScannerThread::ScannerThread(std::shared_ptr<SaneHolder> pHolder, + const Reference< css::lang::XEventListener >& listener, + ScannerManager* pManager) + : m_pHolder(std::move( pHolder )), m_xListener( listener ), m_pManager( pManager ) +{ + SAL_INFO("extensions.scanner", "ScannerThread"); +} + + +ScannerThread::~ScannerThread() +{ + SAL_INFO("extensions.scanner", "~ScannerThread"); +} + + +void ScannerThread::run() +{ + osl_setThreadName("ScannerThread"); + + osl::MutexGuard aGuard( m_pHolder->m_aProtector ); + rtl::Reference<BitmapTransporter> pTransporter = new BitmapTransporter; + + m_pHolder->m_xBitmap = pTransporter; + + m_pHolder->m_bBusy = true; + if( m_pHolder->m_aSane.IsOpen() ) + { + int nOption = m_pHolder->m_aSane.GetOptionByName( "preview" ); + if( nOption != -1 ) + m_pHolder->m_aSane.SetOptionValue( nOption, false ); + + m_pHolder->m_nError = + m_pHolder->m_aSane.Start( *pTransporter ) ? + ScanError_ScanErrorNone : ScanError_ScanCanceled; + } + else + m_pHolder->m_nError = ScanError_ScannerNotAvailable; + + + Reference< XInterface > xXInterface( static_cast< OWeakObject* >( m_pManager ) ); + m_xListener->disposing( css::lang::EventObject(xXInterface) ); + m_pHolder->m_bBusy = false; +} + + +void ScannerManager::AcquireData() +{ + osl::MutexGuard aGuard( theSaneProtector::get() ); + theSanes::get().acquire(); +} + + +void ScannerManager::ReleaseData() +{ + osl::MutexGuard aGuard( theSaneProtector::get() ); + theSanes::get().release(); +} + + +css::awt::Size ScannerManager::getSize() +{ + css::awt::Size aRet; + aRet.Width = aRet.Height = 0; + return aRet; +} + + +Sequence< sal_Int8 > ScannerManager::getDIB() +{ + return Sequence< sal_Int8 >(); +} + + +Sequence< ScannerContext > ScannerManager::getAvailableScanners() +{ + osl::MutexGuard aGuard( theSaneProtector::get() ); + sanevec &rSanes = theSanes::get().m_aSanes; + + if( rSanes.empty() ) + { + auto pSaneHolder = std::make_shared<SaneHolder>(); + if( Sane::IsSane() ) + rSanes.push_back( pSaneHolder ); + } + + if( Sane::IsSane() ) + { + Sequence< ScannerContext > aRet{ { /* ScannerName */ "SANE", /* InternalData */ 0 } }; + return aRet; + } + + return Sequence< ScannerContext >(); +} + + +sal_Bool ScannerManager::configureScannerAndScan( ScannerContext& scanner_context, + const Reference< css::lang::XEventListener >& listener ) +{ + bool bRet; + bool bScan; + { + osl::MutexGuard aGuard( theSaneProtector::get() ); + sanevec &rSanes = theSanes::get().m_aSanes; + + SAL_INFO("extensions.scanner", "ScannerManager::configureScanner"); + + if( scanner_context.InternalData < 0 || o3tl::make_unsigned(scanner_context.InternalData) >= rSanes.size() ) + throw ScannerException( + "Scanner does not exist", + Reference< XScannerManager >( this ), + ScanError_InvalidContext + ); + + std::shared_ptr<SaneHolder> pHolder = rSanes[scanner_context.InternalData]; + if( pHolder->m_bBusy ) + throw ScannerException( + "Scanner is busy", + Reference< XScannerManager >( this ), + ScanError_ScanInProgress + ); + + pHolder->m_bBusy = true; + SaneDlg aDlg(Application::GetFrameWeld(mxDialogParent), pHolder->m_aSane, listener.is()); + bRet = aDlg.run(); + bScan = aDlg.getDoScan(); + pHolder->m_bBusy = false; + } + if ( bScan ) + startScan( scanner_context, listener ); + + return bRet; +} + + +void ScannerManager::startScan( const ScannerContext& scanner_context, + const Reference< css::lang::XEventListener >& listener ) +{ + osl::MutexGuard aGuard( theSaneProtector::get() ); + sanevec &rSanes = theSanes::get().m_aSanes; + + SAL_INFO("extensions.scanner", "ScannerManager::startScan"); + + if( scanner_context.InternalData < 0 || o3tl::make_unsigned(scanner_context.InternalData) >= rSanes.size() ) + throw ScannerException( + "Scanner does not exist", + Reference< XScannerManager >( this ), + ScanError_InvalidContext + ); + std::shared_ptr<SaneHolder> pHolder = rSanes[scanner_context.InternalData]; + if( pHolder->m_bBusy ) + throw ScannerException( + "Scanner is busy", + Reference< XScannerManager >( this ), + ScanError_ScanInProgress + ); + pHolder->m_bBusy = true; + + ScannerThread* pThread = new ScannerThread( pHolder, listener, this ); + pThread->create(); +} + + +ScanError ScannerManager::getError( const ScannerContext& scanner_context ) +{ + osl::MutexGuard aGuard( theSaneProtector::get() ); + sanevec &rSanes = theSanes::get().m_aSanes; + + if( scanner_context.InternalData < 0 || o3tl::make_unsigned(scanner_context.InternalData) >= rSanes.size() ) + throw ScannerException( + "Scanner does not exist", + Reference< XScannerManager >( this ), + ScanError_InvalidContext + ); + + std::shared_ptr<SaneHolder> pHolder = rSanes[scanner_context.InternalData]; + + return pHolder->m_nError; +} + + +Reference< css::awt::XBitmap > ScannerManager::getBitmap( const ScannerContext& scanner_context ) +{ + osl::MutexGuard aGuard( theSaneProtector::get() ); + sanevec &rSanes = theSanes::get().m_aSanes; + + if( scanner_context.InternalData < 0 || o3tl::make_unsigned(scanner_context.InternalData) >= rSanes.size() ) + throw ScannerException( + "Scanner does not exist", + Reference< XScannerManager >( this ), + ScanError_InvalidContext + ); + std::shared_ptr<SaneHolder> pHolder = rSanes[scanner_context.InternalData]; + + osl::MutexGuard aProtGuard( pHolder->m_aProtector ); + + Reference< css::awt::XBitmap > xRet( pHolder->m_xBitmap ); + pHolder->m_xBitmap.clear(); + + return xRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/scanwin.cxx b/extensions/source/scanner/scanwin.cxx new file mode 100644 index 0000000000..109f2944a3 --- /dev/null +++ b/extensions/source/scanner/scanwin.cxx @@ -0,0 +1,646 @@ +/* -*- 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 <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/scanner/ScannerException.hpp> + +#include "twain32shim.hxx" + +#include <comphelper/processfactory.hxx> +#include <comphelper/scopeguard.hxx> +#include <config_folders.h> +#include <o3tl/char16_t2wchar_t.hxx> +#include <osl/conditn.hxx> +#include <osl/file.hxx> +#include <osl/mutex.hxx> +#include <rtl/bootstrap.hxx> +#include <salhelper/thread.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/stream.hxx> +#include <tools/helpers.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include "scanner.hxx" + +namespace +{ +enum TwainState +{ + TWAIN_STATE_NONE = 0, + TWAIN_STATE_SCANNING = 1, + TWAIN_STATE_DONE = 2, + TWAIN_STATE_CANCELED = 3 +}; + +struct HANDLEDeleter +{ + using pointer = HANDLE; + void operator()(HANDLE h) { CloseHandle(h); } +}; + +using ScopedHANDLE = std::unique_ptr<HANDLE, HANDLEDeleter>; + +class Twain +{ +public: + Twain(); + ~Twain(); + + bool SelectSource(ScannerManager& rMgr, const VclPtr<vcl::Window>& xTopWindow); + bool PerformTransfer(ScannerManager& rMgr, + const css::uno::Reference<css::lang::XEventListener>& rxListener, + const VclPtr<vcl::Window>& xTopWindow); + void WaitReadyForNextTask(); + + TwainState GetState() const { return meState; } + +private: + friend class ShimListenerThread; + class ShimListenerThread : public salhelper::Thread + { + public: + ShimListenerThread(const VclPtr<vcl::Window>& xTopWindow); + ~ShimListenerThread() override; + void execute() override; + const OUString& getError() { return msErrorReported; } + + // These methods are executed outside of own thread + bool WaitInitialization(); + bool WaitRequestResult(); + void DontNotify() { mbDontNotify = true; } + void RequestDestroy(); + bool RequestSelectSource(); + bool RequestInitXfer(); + + private: + VclPtr<vcl::Window> mxTopWindow; // the window that we made modal + bool mbDontNotify = false; + HWND mhWndShim = nullptr; // shim main window handle + OUString msErrorReported; + osl::Condition mcInitCompleted; // initially not set + bool mbInitSucceeded = false; + osl::Condition mcGotRequestResult; + bool mbRequestResult = false; + + void SendShimRequest(WPARAM nRequest); + bool SendShimRequestWithResult(WPARAM nRequest); + void NotificationHdl(WPARAM nEvent, LPARAM lParam); + void NotifyOwner(WPARAM nEvent); + void NotifyXFerOwner(LPARAM nHandle); + }; + css::uno::Reference<css::lang::XEventListener> mxListener; + css::uno::Reference<css::scanner::XScannerManager> mxMgr; + ScannerManager* mpCurMgr = nullptr; + TwainState meState = TWAIN_STATE_NONE; + rtl::Reference<ShimListenerThread> mpThread; + osl::Mutex maMutex; + + DECL_LINK(ImpNotifyHdl, void*, void); + DECL_LINK(ImpNotifyXferHdl, void*, void); + void Notify(WPARAM nEvent); // called by shim communication thread to notify me + void NotifyXFer(LPARAM nHandle); // called by shim communication thread to notify me + + bool InitializeNewShim(ScannerManager& rMgr, const VclPtr<vcl::Window>& xTopWindow); + + void Reset(); // cleanup thread and manager +}; + +Twain aTwain; + +Twain::ShimListenerThread::ShimListenerThread(const VclPtr<vcl::Window>& xTopWindow) + : salhelper::Thread("TWAINShimListenerThread") + , mxTopWindow(xTopWindow) +{ + if (mxTopWindow) + { + mxTopWindow->IncModalCount(); // the operation is modal to the frame + } +} + +Twain::ShimListenerThread::~ShimListenerThread() +{ + if (mxTopWindow) + { + mxTopWindow->DecModalCount(); // unblock the frame + } +} + +bool Twain::ShimListenerThread::WaitInitialization() +{ + mcInitCompleted.wait(); + return mbInitSucceeded; +} + +bool Twain::ShimListenerThread::WaitRequestResult() +{ + mcGotRequestResult.wait(); + return mbRequestResult; +} + +void Twain::ShimListenerThread::SendShimRequest(WPARAM nRequest) +{ + if (mhWndShim) + PostMessageW(mhWndShim, WM_TWAIN_REQUEST, nRequest, 0); +} + +bool Twain::ShimListenerThread::SendShimRequestWithResult(WPARAM nRequest) +{ + mcGotRequestResult.reset(); + mbRequestResult = false; + SendShimRequest(nRequest); + return WaitRequestResult(); +} + +void Twain::ShimListenerThread::RequestDestroy() { SendShimRequest(TWAIN_REQUEST_QUIT); } + +bool Twain::ShimListenerThread::RequestSelectSource() +{ + assert(mbInitSucceeded); + return SendShimRequestWithResult(TWAIN_REQUEST_SELECTSOURCE); +} + +bool Twain::ShimListenerThread::RequestInitXfer() +{ + assert(mbInitSucceeded); + return SendShimRequestWithResult(TWAIN_REQUEST_INITXFER); +} + +void Twain::ShimListenerThread::NotifyOwner(WPARAM nEvent) +{ + if (!mbDontNotify) + aTwain.Notify(nEvent); +} + +void Twain::ShimListenerThread::NotifyXFerOwner(LPARAM nHandle) +{ + if (!mbDontNotify) + aTwain.NotifyXFer(nHandle); +} + +// May only be called from the own thread, so no threading issues modifying self +void Twain::ShimListenerThread::NotificationHdl(WPARAM nEvent, LPARAM lParam) +{ + switch (nEvent) + { + case TWAIN_EVENT_NOTIFYHWND: // shim reported its main HWND for communications + if (!mcInitCompleted.check()) // only if not yet initialized! + { + // Owner is still waiting mcInitCompleted in its Twain::InitializeNewShim, + // holding its access mutex + mhWndShim = reinterpret_cast<HWND>(lParam); + + mbInitSucceeded = lParam != 0; + mcInitCompleted.set(); + } + break; + case TWAIN_EVENT_SCANNING: + NotifyOwner(nEvent); + break; + case TWAIN_EVENT_XFER: + NotifyXFerOwner(lParam); + break; + case TWAIN_EVENT_REQUESTRESULT: + mbRequestResult = lParam; + mcGotRequestResult.set(); + break; + // We don't handle TWAIN_EVENT_QUIT notification from shim, because we send it ourselves + // in the end of execute() + } +} + +// Spawn a separate 32-bit process to use TWAIN on Windows, and listen for its notifications +void Twain::ShimListenerThread::execute() +{ + MSG msg; + // Initialize thread message queue before launching shim process + PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE); + + try + { + ScopedHANDLE hShimProcess; + { + // Determine twain32shim executable URL: + OUString shimURL("$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/twain32shim.exe"); + rtl::Bootstrap::expandMacros(shimURL); + + OUString sCmdLine; + if (osl::FileBase::getSystemPathFromFileURL(shimURL, sCmdLine) != osl::FileBase::E_None) + throw std::exception("getSystemPathFromFileURL failed!"); + + HANDLE hDup; + if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), + &hDup, SYNCHRONIZE | THREAD_QUERY_LIMITED_INFORMATION, TRUE, 0)) + ThrowLastError("DuplicateHandle"); + // we will not need our copy as soon as shim has its own inherited one + ScopedHANDLE hScopedDup(hDup); + DWORD nDup = static_cast<DWORD>(reinterpret_cast<sal_uIntPtr>(hDup)); + if (reinterpret_cast<HANDLE>(nDup) != hDup) + throw std::exception("HANDLE does not fit to 32 bit - cannot pass to shim!"); + + // Send this thread handle as the first parameter + sCmdLine = "\"" + sCmdLine + "\" " + OUString::number(nDup); + + // We need a WinAPI HANDLE of the process to be able to wait on it and detect the process + // termination; so use WinAPI to start the process, not osl_executeProcess. + + STARTUPINFOW si{}; + si.cb = sizeof(si); + PROCESS_INFORMATION pi; + + if (!CreateProcessW(nullptr, const_cast<LPWSTR>(o3tl::toW(sCmdLine.getStr())), nullptr, + nullptr, TRUE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi)) + ThrowLastError("CreateProcessW"); + + CloseHandle(pi.hThread); + hShimProcess.reset(pi.hProcess); + } + HANDLE h = hShimProcess.get(); + while (true) + { + DWORD nWaitResult = MsgWaitForMultipleObjects(1, &h, FALSE, INFINITE, QS_POSTMESSAGE); + // Process any messages in queue before checking if we need to break, to not loose + // possible pending notifications + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) + { + // process it here + if (msg.message == WM_TWAIN_EVENT) + { + NotificationHdl(msg.wParam, msg.lParam); + } + } + if (nWaitResult == WAIT_OBJECT_0) + { + // shim process exited - return + break; + } + if (nWaitResult == WAIT_FAILED) + { + // Some Win32 error - report and return + ThrowLastError("MsgWaitForMultipleObjects"); + } + } + } + catch (const std::exception& e) + { + msErrorReported = OUString(e.what(), strlen(e.what()), RTL_TEXTENCODING_UTF8); + // allow owner to resume (in case the condition isn't set yet) + mcInitCompleted.set(); // let mbInitSucceeded keep its (maybe false) value! + } + // allow owner to resume (in case the conditions isn't set yet) + mcGotRequestResult.set(); + NotifyOwner(TWAIN_EVENT_QUIT); +} + +Twain::Twain() {} + +Twain::~Twain() +{ + osl::MutexGuard aGuard(maMutex); + if (mpThread) + { + mpThread->DontNotify(); + mpThread->RequestDestroy(); + mpThread->join(); + mpThread.clear(); + } +} + +void Twain::Reset() +{ + mpThread->join(); + if (!mpThread->getError().isEmpty()) + SAL_WARN("extensions.scanner", mpThread->getError()); + mpThread.clear(); + mpCurMgr = nullptr; + mxMgr.clear(); +} + +bool Twain::InitializeNewShim(ScannerManager& rMgr, const VclPtr<vcl::Window>& xTopWindow) +{ + osl::MutexGuard aGuard(maMutex); + if (mpThread) + return false; // Have a shim for another task already! + + // hold reference to ScannerManager, to prevent premature death + mxMgr = mpCurMgr = &rMgr; + + mpThread.set(new ShimListenerThread(xTopWindow)); + mpThread->launch(); + const bool bSuccess = mpThread->WaitInitialization(); + if (!bSuccess) + Reset(); + + return bSuccess; +} + +void Twain::Notify(WPARAM nEvent) +{ + Application::PostUserEvent(LINK(this, Twain, ImpNotifyHdl), reinterpret_cast<void*>(nEvent)); +} + +void Twain::NotifyXFer(LPARAM nHandle) +{ + Application::PostUserEvent(LINK(this, Twain, ImpNotifyXferHdl), + reinterpret_cast<void*>(nHandle)); +} + +bool Twain::SelectSource(ScannerManager& rMgr, const VclPtr<vcl::Window>& xTopWindow) +{ + osl::MutexGuard aGuard(maMutex); + bool bRet = false; + + if (InitializeNewShim(rMgr, xTopWindow)) + { + meState = TWAIN_STATE_NONE; + bRet = mpThread->RequestSelectSource(); + } + + return bRet; +} + +bool Twain::PerformTransfer(ScannerManager& rMgr, + const css::uno::Reference<css::lang::XEventListener>& rxListener, + const VclPtr<vcl::Window>& xTopWindow) +{ + osl::MutexGuard aGuard(maMutex); + bool bRet = false; + + if (InitializeNewShim(rMgr, xTopWindow)) + { + mxListener = rxListener; + meState = TWAIN_STATE_NONE; + bRet = mpThread->RequestInitXfer(); + } + + return bRet; +} + +void Twain::WaitReadyForNextTask() +{ + while ([&]() { + osl::MutexGuard aGuard(maMutex); + return bool(mpThread); + }()) + { + Application::Reschedule(true); + } +} + +IMPL_LINK(Twain, ImpNotifyHdl, void*, pParam, void) +{ + osl::MutexGuard aGuard(maMutex); + WPARAM nEvent = reinterpret_cast<WPARAM>(pParam); + switch (nEvent) + { + case TWAIN_EVENT_SCANNING: + meState = TWAIN_STATE_SCANNING; + break; + + case TWAIN_EVENT_QUIT: + { + if (meState != TWAIN_STATE_DONE) + meState = TWAIN_STATE_CANCELED; + + css::lang::EventObject event(mxMgr); // mxMgr will be cleared below + + if (mpThread) + Reset(); + + if (mxListener.is()) + { + mxListener->disposing(event); + mxListener.clear(); + } + } + break; + + default: + break; + } +} + +IMPL_LINK(Twain, ImpNotifyXferHdl, void*, pParam, void) +{ + osl::MutexGuard aGuard(maMutex); + if (mpThread) + { + mpCurMgr->SetData(pParam); + meState = pParam ? TWAIN_STATE_DONE : TWAIN_STATE_CANCELED; + + css::lang::EventObject event(mxMgr); // mxMgr will be cleared below + + Reset(); + + if (mxListener.is()) + mxListener->disposing(css::lang::EventObject(mxMgr)); + } + + mxListener.clear(); +} + +VclPtr<vcl::Window> ImplGetActiveFrameWindow() +{ + try + { + // query desktop instance + css::uno::Reference<css::frame::XDesktop2> xDesktop + = css::frame::Desktop::create(comphelper::getProcessComponentContext()); + if (css::uno::Reference<css::frame::XFrame> xActiveFrame = xDesktop->getActiveFrame()) + return VCLUnoHelper::GetWindow(xActiveFrame->getComponentWindow()); + } + catch (const css::uno::Exception&) + { + } + SAL_WARN("extensions.scanner", "ImplGetActiveFrame: Could not determine active frame!"); + return nullptr; +} + +} // namespace + +void ScannerManager::AcquireData() {} + +void ScannerManager::ReleaseData() +{ + if (mpData) + { + CloseHandle(static_cast<HANDLE>(mpData)); + mpData = nullptr; + } +} + +css::awt::Size ScannerManager::getSize() +{ + css::awt::Size aRet; + + if (mpData) + { + HANDLE hMap = static_cast<HANDLE>(mpData); + // map full size + const sal_Int8* pMap = static_cast<sal_Int8*>(MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0)); + if (pMap) + { + const BITMAPINFOHEADER* pBIH = reinterpret_cast<const BITMAPINFOHEADER*>(pMap + 4); + aRet.Width = pBIH->biWidth; + aRet.Height = pBIH->biHeight; + + UnmapViewOfFile(pMap); + } + } + + return aRet; +} + +css::uno::Sequence<sal_Int8> ScannerManager::getDIB() +{ + css::uno::Sequence<sal_Int8> aRet; + + if (mpData) + { + HANDLE hMap = static_cast<HANDLE>(mpData); + // map full size + const sal_Int8* pMap = static_cast<sal_Int8*>(MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0)); + if (pMap) + { + DWORD nDIBSize; + memcpy(&nDIBSize, pMap, 4); // size of the following DIB + + const BITMAPINFOHEADER* pBIH = reinterpret_cast<const BITMAPINFOHEADER*>(pMap + 4); + + sal_uInt32 nColEntries = 0; + + switch (pBIH->biBitCount) + { + case 1: + case 4: + case 8: + nColEntries = pBIH->biClrUsed ? pBIH->biClrUsed : (1 << pBIH->biBitCount); + break; + + case 24: + nColEntries = pBIH->biClrUsed ? pBIH->biClrUsed : 0; + break; + + case 16: + case 32: + nColEntries = pBIH->biClrUsed; + if (pBIH->biCompression == BI_BITFIELDS) + nColEntries += 3; + break; + } + + aRet = css::uno::Sequence<sal_Int8>(sizeof(BITMAPFILEHEADER) + nDIBSize); + + sal_Int8* pBuf = aRet.getArray(); + SvMemoryStream* pMemStm + = new SvMemoryStream(pBuf, sizeof(BITMAPFILEHEADER), StreamMode::WRITE); + + pMemStm->WriteChar('B').WriteChar('M').WriteUInt32(0).WriteUInt32(0); + pMemStm->WriteUInt32(sizeof(BITMAPFILEHEADER) + pBIH->biSize + + (nColEntries * sizeof(RGBQUAD))); + + delete pMemStm; + memcpy(pBuf + sizeof(BITMAPFILEHEADER), pBIH, nDIBSize); + + UnmapViewOfFile(pMap); + } + + ReleaseData(); + } + + return aRet; +} + +css::uno::Sequence<ScannerContext> SAL_CALL ScannerManager::getAvailableScanners() +{ + osl::MutexGuard aGuard(maProtector); + css::uno::Sequence<ScannerContext> aRet(1); + + aRet.getArray()[0].ScannerName = "TWAIN"; + aRet.getArray()[0].InternalData = 0; + + return aRet; +} + +sal_Bool SAL_CALL ScannerManager::configureScannerAndScan( + ScannerContext& rContext, const css::uno::Reference<css::lang::XEventListener>& rxListener) +{ + osl::MutexGuard aGuard(maProtector); + css::uno::Reference<XScannerManager> xThis(this); + + if (rContext.InternalData != 0 || rContext.ScannerName != "TWAIN") + throw ScannerException("Scanner does not exist", xThis, ScanError_InvalidContext); + + ReleaseData(); + + VclPtr<vcl::Window> xTopWindow = ImplGetActiveFrameWindow(); + if (xTopWindow) + xTopWindow + ->IncModalCount(); // to avoid changes between the two operations that each block the window + comphelper::ScopeGuard aModalGuard([xTopWindow]() { + if (xTopWindow) + xTopWindow->DecModalCount(); + }); + + const bool bSelected = aTwain.SelectSource(*this, xTopWindow); + if (bSelected) + { + aTwain.WaitReadyForNextTask(); + aTwain.PerformTransfer(*this, rxListener, xTopWindow); + } + return bSelected; +} + +void SAL_CALL +ScannerManager::startScan(const ScannerContext& rContext, + const css::uno::Reference<css::lang::XEventListener>& rxListener) +{ + osl::MutexGuard aGuard(maProtector); + css::uno::Reference<XScannerManager> xThis(this); + + if (rContext.InternalData != 0 || rContext.ScannerName != "TWAIN") + throw ScannerException("Scanner does not exist", xThis, ScanError_InvalidContext); + + ReleaseData(); + aTwain.PerformTransfer(*this, rxListener, ImplGetActiveFrameWindow()); +} + +ScanError SAL_CALL ScannerManager::getError(const ScannerContext& rContext) +{ + osl::MutexGuard aGuard(maProtector); + css::uno::Reference<XScannerManager> xThis(this); + + if (rContext.InternalData != 0 || rContext.ScannerName != "TWAIN") + throw ScannerException("Scanner does not exist", xThis, ScanError_InvalidContext); + + return ((aTwain.GetState() == TWAIN_STATE_CANCELED) ? ScanError_ScanCanceled + : ScanError_ScanErrorNone); +} + +css::uno::Reference<css::awt::XBitmap> + SAL_CALL ScannerManager::getBitmap(const ScannerContext& /*rContext*/) +{ + osl::MutexGuard aGuard(maProtector); + return css::uno::Reference<css::awt::XBitmap>(this); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/scn.component b/extensions/source/scanner/scn.component new file mode 100644 index 0000000000..19215a7c3a --- /dev/null +++ b/extensions/source/scanner/scn.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.scanner.ScannerManager" + constructor="extensions_ScannerManager_get_implementation"> + <service name="com.sun.star.scanner.ScannerManager"/> + </implementation> +</component> diff --git a/extensions/source/scanner/twain32shim.cxx b/extensions/source/scanner/twain32shim.cxx new file mode 100644 index 0000000000..6e0be81494 --- /dev/null +++ b/extensions/source/scanner/twain32shim.cxx @@ -0,0 +1,604 @@ +/* -*- 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 . + */ + +/* + * twain32shim.exe is a separate 32-bit executable that serves as a shim + * between LibreOffice and Windows' 32-bit TWAIN component. Without it, + * it's impossible for 64-bit program to use TWAIN on Windows. + * Using 64-bit TWAIN DSM library from twain.org to avoid using the shim + * is not an option, because scanner manufacturers only provide 32-bit + * drivers, and 64-bit drivers are only offered as 3rd-party commercial + * products. The shim is also used in 32-bit LibreOffice for uniformity. +*/ + +#include "twain32shim.hxx" +#include <systools/win32/comtools.hxx> +#include <tools/helpers.hxx> +#include <twain/twain.h> +#include <o3tl/unit_conversion.hxx> + +#define WM_TWAIN_FALLBACK (WM_SHIM_INTERNAL + 0) + +namespace +{ +double FixToDouble(const TW_FIX32& rFix) { return rFix.Whole + rFix.Frac / 65536.; } + +const wchar_t sTwainWndClass[] = L"TwainClass"; + +class ImpTwain +{ +public: + ImpTwain(HANDLE hParentThread); + ~ImpTwain(); + +private: + enum class TWAINState + { + DSMunloaded = 1, + DSMloaded = 2, + DSMopened = 3, + DSopened = 4, + DSenabled = 5, + DSreadyToXfer = 6, + Xferring = 7, + }; + + TW_IDENTITY m_aAppId; + TW_IDENTITY m_aSrcId; + DWORD m_nParentThreadId; + HANDLE m_hProc; + DSMENTRYPROC m_pDSM = nullptr; + HMODULE m_hMod = nullptr; + TWAINState m_nCurState = TWAINState::DSMunloaded; + HWND m_hTwainWnd = nullptr; + HHOOK m_hTwainHook = nullptr; + HANDLE m_hMap = nullptr; // the *duplicated* handle + + static bool IsTwainClassWnd(HWND hWnd); + static ImpTwain* GetImpFromWnd(HWND hWnd); + static void ImplCreateWnd(HWND hWnd, LPARAM lParam); + static LRESULT CALLBACK WndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK MsgHook(int nCode, WPARAM wParam, LPARAM lParam); + + void Destroy() { ImplFallback(TWAIN_EVENT_QUIT); } + bool SelectSource(); + bool InitXfer(); + + void NotifyParent(WPARAM nEvent, LPARAM lParam); + bool ImplHandleMsg(MSG* pMsg); + void ImplOpenSourceManager(); + void ImplOpenSource(); + bool ImplEnableSource(); + void ImplXfer(); + void ImplFallback(WPARAM nEvent); + + void ImplFallbackHdl(WPARAM nEvent); + void ImplRequestHdl(WPARAM nRequest); +}; + +//static +bool ImpTwain::IsTwainClassWnd(HWND hWnd) +{ + const int nBufSize = SAL_N_ELEMENTS(sTwainWndClass); + wchar_t sClassName[nBufSize]; + return (GetClassNameW(hWnd, sClassName, nBufSize) && wcscmp(sClassName, sTwainWndClass) == 0); +} + +//static +ImpTwain* ImpTwain::GetImpFromWnd(HWND hWnd) +{ + if (!IsTwainClassWnd(hWnd)) + return nullptr; + return reinterpret_cast<ImpTwain*>(GetWindowLongPtrW(hWnd, GWLP_USERDATA)); +} + +//static +void ImpTwain::ImplCreateWnd(HWND hWnd, LPARAM lParam) +{ + CREATESTRUCT* pCS = reinterpret_cast<CREATESTRUCT*>(lParam); + if (pCS && IsTwainClassWnd(hWnd)) + SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pCS->lpCreateParams)); +} + +// static +LRESULT CALLBACK ImpTwain::WndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) +{ + ImpTwain* pImpTwain = GetImpFromWnd(hWnd); + switch (nMsg) + { + case WM_CREATE: + ImplCreateWnd(hWnd, lParam); + break; + case WM_TWAIN_FALLBACK: + if (pImpTwain) + pImpTwain->ImplFallbackHdl(wParam); + break; + case WM_TWAIN_REQUEST: + if (pImpTwain) + pImpTwain->ImplRequestHdl(wParam); + break; + } + return DefWindowProcW(hWnd, nMsg, wParam, lParam); +} + +// static +LRESULT CALLBACK ImpTwain::MsgHook(int nCode, WPARAM wParam, LPARAM lParam) +{ + MSG* pMsg = reinterpret_cast<MSG*>(lParam); + if (nCode >= 0 && pMsg) + { + ImpTwain* pImpTwain = GetImpFromWnd(pMsg->hwnd); + if (pImpTwain && pImpTwain->ImplHandleMsg(pMsg)) + { + pMsg->message = WM_USER; + pMsg->lParam = 0; + + return 0; + } + } + + return CallNextHookEx(nullptr, nCode, wParam, lParam); +} + +HANDLE GetProcOfThread(HANDLE hThread) +{ + DWORD nProcId = GetProcessIdOfThread(hThread); + if (!nProcId) + ThrowLastError("GetProcessIdOfThread"); + HANDLE hRet = OpenProcess(PROCESS_DUP_HANDLE, FALSE, nProcId); + if (!hRet) + ThrowLastError("OpenProcess"); + return hRet; +} + +ImpTwain::ImpTwain(HANDLE hParentThread) + : m_aAppId{ /* Id */ 0, + { /* Version.MajorNum */ 1, + /* Version.MinorNum */ 0, + /* Version.Language */ TWLG_USA, + /* Version.Country */ TWCY_USA, + /* Version.Info */ "8.0" }, + /* ProtocolMajor */ TWON_PROTOCOLMAJOR, + /* ProtocolMinor */ TWON_PROTOCOLMINOR, + /* SupportedGroups */ DG_IMAGE | DG_CONTROL, + /* Manufacturer */ "Sun Microsystems", + /* ProductFamily */ "Office", + /* ProductName */ "Office" } + , m_nParentThreadId(GetThreadId(hParentThread)) + , m_hProc(GetProcOfThread(hParentThread)) +{ + WNDCLASSW aWc = { 0, &WndProc, 0, sizeof(WNDCLASSW), GetModuleHandleW(nullptr), + nullptr, nullptr, nullptr, nullptr, sTwainWndClass }; + if (!RegisterClassW(&aWc)) + ThrowLastError("RegisterClassW"); + m_hTwainWnd = CreateWindowExW(WS_EX_TOPMOST, aWc.lpszClassName, L"TWAIN", 0, 0, 0, 0, 0, + HWND_DESKTOP, nullptr, aWc.hInstance, this); + if (!m_hTwainWnd) + ThrowLastError("CreateWindowExW"); + m_hTwainHook = SetWindowsHookExW(WH_GETMESSAGE, &MsgHook, nullptr, GetCurrentThreadId()); + if (!m_hTwainHook) + ThrowLastError("SetWindowsHookExW"); + + NotifyParent(TWAIN_EVENT_NOTIFYHWND, reinterpret_cast<LPARAM>(m_hTwainWnd)); +} + +ImpTwain::~ImpTwain() +{ + DestroyWindow(m_hTwainWnd); + UnhookWindowsHookEx(m_hTwainHook); +} + +bool ImpTwain::SelectSource() +{ + TW_UINT16 nRet = TWRC_FAILURE; + + ImplOpenSourceManager(); + + if (TWAINState::DSMopened == m_nCurState) + { + TW_IDENTITY aIdent; + + aIdent.Id = 0; + aIdent.ProductName[0] = '\0'; + NotifyParent(TWAIN_EVENT_SCANNING, 0); + nRet = m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_USERSELECT, &aIdent); + } + + Destroy(); + return (TWRC_SUCCESS == nRet); +} + +bool ImpTwain::InitXfer() +{ + bool bRet = false; + + ImplOpenSourceManager(); + + if (TWAINState::DSMopened == m_nCurState) + { + ImplOpenSource(); + + if (TWAINState::DSopened == m_nCurState) + bRet = ImplEnableSource(); + } + + if (!bRet) + Destroy(); + + return bRet; +} + +void ImpTwain::ImplOpenSourceManager() +{ + if (TWAINState::DSMunloaded == m_nCurState) + { + m_hMod = LoadLibraryW(L"TWAIN_32.DLL"); + if (!m_hMod) + { + // Windows directory might not be in DLL search path sometimes, so try the full path + sal::systools::CoTaskMemAllocated<wchar_t> sPath; + if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Windows, 0, nullptr, &sPath))) + { + std::wstring sPathAndFile(sPath); + sPathAndFile += L"\\TWAIN_32.DLL"; + m_hMod = LoadLibraryW(sPathAndFile.c_str()); + } + } + if (m_hMod) + { + m_nCurState = TWAINState::DSMloaded; + + m_pDSM = reinterpret_cast<DSMENTRYPROC>(GetProcAddress(m_hMod, "DSM_Entry")); + if (m_pDSM + && (m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_PARENT, MSG_OPENDSM, &m_hTwainWnd) + == TWRC_SUCCESS)) + { + m_nCurState = TWAINState::DSMopened; + } + } + } +} + +void ImpTwain::ImplOpenSource() +{ + if (TWAINState::DSMopened == m_nCurState) + { + if ((m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_GETDEFAULT, &m_aSrcId) + == TWRC_SUCCESS) + && (m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_OPENDS, &m_aSrcId) + == TWRC_SUCCESS)) + { + TW_CAPABILITY aCap + = { CAP_XFERCOUNT, TWON_ONEVALUE, GlobalAlloc(GHND, sizeof(TW_ONEVALUE)) }; + TW_ONEVALUE* pVal = static_cast<TW_ONEVALUE*>(GlobalLock(aCap.hContainer)); + + pVal->ItemType = TWTY_INT16; + pVal->Item = 1; + GlobalUnlock(aCap.hContainer); + m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_CAPABILITY, MSG_SET, &aCap); + GlobalFree(aCap.hContainer); + m_nCurState = TWAINState::DSopened; + } + } +} + +bool ImpTwain::ImplEnableSource() +{ + bool bRet = false; + + if (TWAINState::DSopened == m_nCurState) + { + TW_USERINTERFACE aUI = { true, true, m_hTwainWnd }; + + NotifyParent(TWAIN_EVENT_SCANNING, 0); + m_nCurState = TWAINState::DSenabled; + + if (m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_USERINTERFACE, MSG_ENABLEDS, &aUI) + == TWRC_SUCCESS) + { + bRet = true; + } + else + { + // dialog failed + m_nCurState = TWAINState::DSopened; + } + } + + return bRet; +} + +void ImpTwain::NotifyParent(WPARAM nEvent, LPARAM lParam) +{ + PostThreadMessageW(m_nParentThreadId, WM_TWAIN_EVENT, nEvent, lParam); +} + +bool ImpTwain::ImplHandleMsg(MSG* pMsg) +{ + if (!m_pDSM) + return false; + + TW_EVENT aEvt = { pMsg, MSG_NULL }; + TW_UINT16 nRet = m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_EVENT, MSG_PROCESSEVENT, &aEvt); + + switch (aEvt.TWMessage) + { + case MSG_XFERREADY: + { + WPARAM nEvent = TWAIN_EVENT_QUIT; + + if (TWAINState::DSenabled == m_nCurState) + { + m_nCurState = TWAINState::DSreadyToXfer; + ImplXfer(); + + if (m_hMap) + nEvent = TWAIN_EVENT_XFER; + } + else if (TWAINState::Xferring == m_nCurState && m_hMap) + { + // Already sent TWAIN_EVENT_XFER; not processed yet; + // duplicate event + nEvent = TWAIN_EVENT_NONE; + } + + ImplFallback(nEvent); + } + break; + + case MSG_CLOSEDSREQ: + Destroy(); + break; + + case MSG_NULL: + nRet = TWRC_NOTDSEVENT; + break; + } + + return (TWRC_DSEVENT == nRet); +} + +void ImpTwain::ImplXfer() +{ + if (m_nCurState == TWAINState::DSreadyToXfer) + { + TW_IMAGEINFO aInfo; + HANDLE hDIB = nullptr; + double nXRes, nYRes; + + if (m_pDSM(&m_aAppId, &m_aSrcId, DG_IMAGE, DAT_IMAGEINFO, MSG_GET, &aInfo) == TWRC_SUCCESS) + { + nXRes = FixToDouble(aInfo.XResolution); + nYRes = FixToDouble(aInfo.YResolution); + } + else + nXRes = nYRes = -1; + + switch (m_pDSM(&m_aAppId, &m_aSrcId, DG_IMAGE, DAT_IMAGENATIVEXFER, MSG_GET, &hDIB)) + { + case TWRC_CANCEL: + m_nCurState = TWAINState::Xferring; + break; + + case TWRC_XFERDONE: + { + if (hDIB) + { + m_hMap = nullptr; + const HGLOBAL hGlob = static_cast<HGLOBAL>(hDIB); + const SIZE_T nDIBSize = GlobalSize(hGlob); + const DWORD nMapSize = nDIBSize + 4; // leading 4 bytes for size + if (nMapSize > nDIBSize) // check for wrap + { + if (LPVOID pBmpMem = GlobalLock(hGlob)) + { + if ((nXRes > 0) && (nYRes > 0)) + { + // set resolution of bitmap + BITMAPINFOHEADER* pBIH = static_cast<BITMAPINFOHEADER*>(pBmpMem); + + const auto[m, d] + = getConversionMulDiv(o3tl::Length::in, o3tl::Length::m); + pBIH->biXPelsPerMeter = std::round(o3tl::convert(nXRes, d, m)); + pBIH->biYPelsPerMeter = std::round(o3tl::convert(nYRes, d, m)); + } + + HANDLE hMap = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, + PAGE_READWRITE, 0, nMapSize, nullptr); + if (hMap) + { + LPVOID pMap = MapViewOfFile(hMap, FILE_MAP_WRITE, 0, 0, nMapSize); + if (pMap) + { + memcpy(pMap, &nMapSize, 4); // size of the following DIB + memcpy(static_cast<char*>(pMap) + 4, pBmpMem, nDIBSize); + FlushViewOfFile(pMap, nDIBSize); + UnmapViewOfFile(pMap); + + DuplicateHandle(GetCurrentProcess(), hMap, m_hProc, &m_hMap, 0, + FALSE, DUPLICATE_SAME_ACCESS); + } + + CloseHandle(hMap); + } + + GlobalUnlock(hGlob); + } + } + } + + GlobalFree(static_cast<HGLOBAL>(hDIB)); + + m_nCurState = TWAINState::Xferring; + } + break; + + default: + break; + } + } +} + +void ImpTwain::ImplFallback(WPARAM nEvent) +{ + PostMessageW(m_hTwainWnd, WM_TWAIN_FALLBACK, nEvent, 0); +} + +void ImpTwain::ImplFallbackHdl(WPARAM nEvent) +{ + bool bFallback = true; + + switch (m_nCurState) + { + case TWAINState::Xferring: + case TWAINState::DSreadyToXfer: + { + TW_PENDINGXFERS aXfers; + + if (m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_PENDINGXFERS, MSG_ENDXFER, &aXfers) + == TWRC_SUCCESS) + { + if (aXfers.Count != 0) + m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_PENDINGXFERS, MSG_RESET, &aXfers); + } + + m_nCurState = TWAINState::DSenabled; + } + break; + + case TWAINState::DSenabled: + { + TW_USERINTERFACE aUI = { true, true, m_hTwainWnd }; + + m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_USERINTERFACE, MSG_DISABLEDS, &aUI); + m_nCurState = TWAINState::DSopened; + } + break; + + case TWAINState::DSopened: + { + m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_CLOSEDS, &m_aSrcId); + m_nCurState = TWAINState::DSMopened; + } + break; + + case TWAINState::DSMopened: + { + m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_PARENT, MSG_CLOSEDSM, &m_hTwainWnd); + m_nCurState = TWAINState::DSMloaded; + } + break; + + case TWAINState::DSMloaded: + { + m_pDSM = nullptr; + FreeLibrary(m_hMod); + m_hMod = nullptr; + m_nCurState = TWAINState::DSMunloaded; + } + break; + + case TWAINState::DSMunloaded: + { + if (nEvent > TWAIN_EVENT_NONE) + NotifyParent(nEvent, reinterpret_cast<LPARAM>(m_hMap)); + PostQuitMessage(0); + + bFallback = false; + } + break; + } + + if (bFallback) + ImplFallback(nEvent); +} + +void ImpTwain::ImplRequestHdl(WPARAM nRequest) +{ + switch (nRequest) + { + case TWAIN_REQUEST_QUIT: + Destroy(); + break; + case TWAIN_REQUEST_SELECTSOURCE: + NotifyParent(TWAIN_EVENT_REQUESTRESULT, LPARAM(SelectSource())); + break; + case TWAIN_REQUEST_INITXFER: + NotifyParent(TWAIN_EVENT_REQUESTRESULT, LPARAM(InitXfer())); + break; + } +} +} // namespace + +int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) +{ + int argc = 0; + LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); + if (argc != 2) + return 1; // Wrong argument count + // 1st argument is parent thread handle; must be inherited. + // HANDLE is 32-bit in 32-bit applications, so wcstoul is OK. + HANDLE hParentThread = reinterpret_cast<HANDLE>(wcstoul(argv[1], nullptr, 10)); + LocalFree(argv); + if (!hParentThread) + return 2; // Invalid parent thread handle argument value + + int nRet = 0; + try + { + ImpTwain aImpTwain(hParentThread); // creates main window + + MSG msg; + while (true) + { + DWORD nWaitResult + = MsgWaitForMultipleObjects(1, &hParentThread, FALSE, INFINITE, QS_ALLINPUT); + if (nWaitResult == WAIT_OBJECT_0) + return 5; // Parent process' thread died before we exited + if (nWaitResult == WAIT_FAILED) + return 6; // Some Win32 error + // nWaitResult == WAIT_OBJECT_0 + nCount => an event is in queue + bool bQuit = false; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) + { + // process it here + if (msg.message == WM_QUIT) + { + bQuit = true; + nRet = msg.wParam; + } + else + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + if (bQuit) + break; + } + } + catch (const std::exception& e) + { + printf("Exception thrown: %s", e.what()); + nRet = 7; + } + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/twain32shim.hxx b/extensions/source/scanner/twain32shim.hxx new file mode 100644 index 0000000000..c9e87ee8bb --- /dev/null +++ b/extensions/source/scanner/twain32shim.hxx @@ -0,0 +1,68 @@ +/* -*- 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/. + * + */ + +#pragma once + +#include <prewin.h> +#include <Shlobj.h> +#include <postwin.h> +#include <exception> +#include <string> +#include <sstream> +#include <iomanip> + +// Don't use WM_USER + +// Notifications from shim to parent; wParam = event id +#define WM_TWAIN_EVENT (WM_USER + 1) + +// lParam is HWND +#define TWAIN_EVENT_NOTIFYHWND 0 + +// lParam is result (bool indicating success) +#define TWAIN_EVENT_REQUESTRESULT 1 + +// lParam is ignored +#define TWAIN_EVENT_NONE 10 +#define TWAIN_EVENT_QUIT 11 +#define TWAIN_EVENT_SCANNING 12 + +// lParam is HANDLE to shared file mapping valid in context of parent process +#define TWAIN_EVENT_XFER 13 + +// Requests from parent to shim; wParam = request id +#define WM_TWAIN_REQUEST (WM_USER + 2) + +#define TWAIN_REQUEST_QUIT 0 // Destroy() +#define TWAIN_REQUEST_SELECTSOURCE 1 +#define TWAIN_REQUEST_INITXFER 2 + +// messages starting from this are not to be used for interprocess communications +#define WM_SHIM_INTERNAL (WM_USER + 200) + +template <typename IntType> std::string Num2Hex(IntType n) +{ + std::stringstream sMsg; + sMsg << "0x" << std::uppercase << std::setfill('0') << std::setw(sizeof(n) * 2) << std::hex + << n; + return sMsg.str(); +} + +void ThrowWin32Error(const char* sFunc, DWORD nWin32Error) +{ + std::stringstream sMsg; + sMsg << sFunc << " failed with Win32 error code " << Num2Hex(nWin32Error) << "!"; + + throw std::exception(sMsg.str().c_str()); +} + +void ThrowLastError(const char* sFunc) { ThrowWin32Error(sFunc, GetLastError()); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/actionlistener.hxx b/extensions/source/update/check/actionlistener.hxx new file mode 100644 index 0000000000..63c5ac1cb0 --- /dev/null +++ b/extensions/source/update/check/actionlistener.hxx @@ -0,0 +1,38 @@ +/* -*- 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 . + */ + +#pragma once + +#include <salhelper/simplereferenceobject.hxx> + +class IActionListener : public virtual salhelper::SimpleReferenceObject +{ + public: + + virtual void cancel() = 0; + virtual void download() = 0; + virtual void pause() = 0; + virtual void resume() = 0; + virtual void closeAfterFailure() = 0; + +protected: + virtual ~IActionListener() override {} +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/download.cxx b/extensions/source/update/check/download.cxx new file mode 100644 index 0000000000..f96d6ecd49 --- /dev/null +++ b/extensions/source/update/check/download.cxx @@ -0,0 +1,426 @@ +/* -*- 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 <string_view> + +#include <curl/curl.h> + +#include <systools/curlinit.hxx> + +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> +#include <osl/file.h> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> + +#include "download.hxx" + +namespace beans = com::sun::star::beans ; +namespace container = com::sun::star::container ; +namespace lang = com::sun::star::lang ; +namespace uno = com::sun::star::uno ; + +namespace { + +struct OutData +{ + rtl::Reference< DownloadInteractionHandler >Handler; + OUString File; + OUString DestinationDir; + oslFileHandle FileHandle; + sal_uInt64 Offset; + osl::Condition& StopCondition; + CURL *curl; + + explicit OutData(osl::Condition& rCondition) : FileHandle(nullptr), Offset(0), StopCondition(rCondition), curl(nullptr) {}; +}; + +} + +static void openFile( OutData& out ) +{ + char * effective_url; + curl_easy_getinfo(out.curl, CURLINFO_EFFECTIVE_URL, &effective_url); + + curl_off_t nDownloadSize; + curl_easy_getinfo(out.curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &nDownloadSize); + + OString aURL(effective_url); + + // ensure no trailing '/' + sal_Int32 nLen = aURL.getLength(); + while( (nLen > 0) && ('/' == aURL[nLen-1]) ) + aURL = aURL.copy(0, --nLen); + + // extract file name last '/' + sal_Int32 nIndex = aURL.lastIndexOf('/'); + if( nIndex > 0 ) + { + out.File = out.DestinationDir + + OStringToOUString(aURL.subView(nIndex), RTL_TEXTENCODING_UTF8); + + oslFileError rc; + + // Give the user an overwrite warning if the target file exists + const sal_Int32 openFlags = osl_File_OpenFlag_Write | osl_File_OpenFlag_Create; + do + { + rc = osl_openFile(out.File.pData, &out.FileHandle, openFlags); + + if( osl_File_E_EXIST == rc && ! out.Handler->downloadTargetExists(out.File) ) + { + out.StopCondition.set(); + break; + } + + } while( osl_File_E_EXIST == rc ); + + if( osl_File_E_None == rc ) + out.Handler->downloadStarted(out.File, static_cast<sal_Int64>(nDownloadSize)); + } +} + + +static OString +getStringValue(const uno::Reference< container::XNameAccess >& xNameAccess, const OUString& aName) +{ + OSL_ASSERT(xNameAccess->hasByName(aName)); + uno::Any aValue = xNameAccess->getByName(aName); + + return OUStringToOString(aValue.get<OUString>(), RTL_TEXTENCODING_UTF8); +} + + +static sal_Int32 +getInt32Value(const uno::Reference< container::XNameAccess >& xNameAccess, + const OUString& aName) +{ + OSL_ASSERT(xNameAccess->hasByName(aName)); + uno::Any aValue = xNameAccess->getByName(aName); + + sal_Int32 n = -1; + aValue >>= n; + return n; +} + + +static size_t +write_function( void *ptr, size_t size, size_t nmemb, void *stream ) +{ + OutData *out = static_cast < OutData * > (stream); + + if( nullptr == out->FileHandle ) + openFile(*out); + + sal_uInt64 nBytesWritten = 0; + + if( nullptr != out->FileHandle ) + osl_writeFile(out->FileHandle, ptr, size * nmemb, &nBytesWritten); + + return static_cast<size_t>(nBytesWritten); +} + + +static int +progress_callback( void *clientp, curl_off_t dltotal, curl_off_t dlnow, SAL_UNUSED_PARAMETER curl_off_t, SAL_UNUSED_PARAMETER curl_off_t) +{ + OutData *out = static_cast < OutData * > (clientp); + + assert(out); + + if (out && !out->StopCondition.check()) + { + float fPercent = 0; + if ( dltotal + out->Offset ) + fPercent = (dlnow + out->Offset) * 100 / (dltotal + out->Offset); + if( fPercent < 0 ) + fPercent = 0; + + // Do not report progress for redirection replies + long nCode; + curl_easy_getinfo(out->curl, CURLINFO_RESPONSE_CODE, &nCode); + if( (nCode != 302) && (nCode != 303) && (dltotal > 0) ) + out->Handler->downloadProgressAt(static_cast<sal_Int8>(fPercent)); + + return 0; + } + + // If stop condition is set, return non 0 value to abort + return -1; +} + + +void +Download::getProxyForURL(std::u16string_view rURL, OString& rHost, sal_Int32& rPort) const +{ + uno::Reference< lang::XMultiServiceFactory > xConfigProvider( + css::configuration::theDefaultProvider::get( m_xContext ) ); + + beans::PropertyValue aProperty; + aProperty.Name = "nodepath"; + aProperty.Value <<= OUString("org.openoffice.Inet/Settings"); + + uno::Sequence< uno::Any > aArgumentList{ uno::Any(aProperty) }; + + uno::Reference< container::XNameAccess > xNameAccess( + xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", aArgumentList ), + uno::UNO_QUERY_THROW ); + + OSL_ASSERT(xNameAccess->hasByName("ooInetProxyType")); + uno::Any aValue = xNameAccess->getByName("ooInetProxyType"); + + sal_Int32 nProxyType = aValue.get< sal_Int32 >(); + if( 0 != nProxyType ) // type 0 means "direct connection to the internet + { + if( o3tl::starts_with(rURL, u"http:") ) + { + rHost = getStringValue(xNameAccess, "ooInetHTTPProxyName"); + rPort = getInt32Value(xNameAccess, "ooInetHTTPProxyPort"); + } + else if( o3tl::starts_with(rURL, u"https:") ) + { + rHost = getStringValue(xNameAccess, "ooInetHTTPSProxyName"); + rPort = getInt32Value(xNameAccess, "ooInetHTTPSProxyPort"); + } + } +} + + +static bool curl_run(std::u16string_view rURL, OutData& out, const OString& aProxyHost, sal_Int32 nProxyPort) +{ + /* Need to investigate further whether it is necessary to call + * curl_global_init or not - leave it for now (as the ftp UCB content + * provider does as well). + */ + + CURL * pCURL = curl_easy_init(); + bool ret = false; + + if( nullptr != pCURL ) + { + ::InitCurl_easy(pCURL); + + out.curl = pCURL; + + OString aURL(OUStringToOString(rURL, RTL_TEXTENCODING_UTF8)); + (void)curl_easy_setopt(pCURL, CURLOPT_URL, aURL.getStr()); + + // abort on http errors + (void)curl_easy_setopt(pCURL, CURLOPT_FAILONERROR, 1); + + // enable redirection + (void)curl_easy_setopt(pCURL, CURLOPT_FOLLOWLOCATION, 1); + // only allow redirect to https:// +#if (LIBCURL_VERSION_MAJOR > 7) || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 85) + (void)curl_easy_setopt(pCURL, CURLOPT_REDIR_PROTOCOLS_STR, "https"); +#else + (void)curl_easy_setopt(pCURL, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS); +#endif + + // write function + (void)curl_easy_setopt(pCURL, CURLOPT_WRITEDATA, &out); + (void)curl_easy_setopt(pCURL, CURLOPT_WRITEFUNCTION, &write_function); + + // progress handler - Condition::check unfortunately is not defined const + (void)curl_easy_setopt(pCURL, CURLOPT_NOPROGRESS, 0); + (void)curl_easy_setopt(pCURL, CURLOPT_XFERINFOFUNCTION, &progress_callback); + (void)curl_easy_setopt(pCURL, CURLOPT_PROGRESSDATA, &out); + + // proxy + (void)curl_easy_setopt(pCURL, CURLOPT_PROXY, aProxyHost.getStr()); + (void)curl_easy_setopt(pCURL, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); + if( -1 != nProxyPort ) + (void)curl_easy_setopt(pCURL, CURLOPT_PROXYPORT, nProxyPort); + + if( out.Offset > 0 ) + { + // curl_off_t offset = nOffset; libcurl seems to be compiled with large + // file support (and we not) .. + sal_Int64 offset = static_cast<sal_Int64>(out.Offset); + (void)curl_easy_setopt(pCURL, CURLOPT_RESUME_FROM_LARGE, offset); + } + + CURLcode cc = curl_easy_perform(pCURL); + + // treat zero byte downloads as errors + if( nullptr == out.FileHandle ) + openFile(out); + + if( CURLE_OK == cc ) + { + out.Handler->downloadFinished(out.File); + ret = true; + } + + if ( CURLE_PARTIAL_FILE == cc ) + { + // this sometimes happens, when a user throws away his user data, but has already + // completed the download of an update. + curl_off_t nDownloadSize; + curl_easy_getinfo( pCURL, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &nDownloadSize ); + if ( -1 == nDownloadSize ) + { + out.Handler->downloadFinished(out.File); + ret = true; + } + } + + // Avoid target file being removed + else if( (CURLE_ABORTED_BY_CALLBACK == cc) || out.StopCondition.check() ) + ret = true; + + // Only report errors when not stopped + else + { + OString aMessage("Unknown error"_ostr); + + const char * error_message = curl_easy_strerror(cc); + if( nullptr != error_message ) + aMessage = error_message; + + if ( CURLE_HTTP_RETURNED_ERROR == cc ) + { + long nError; + curl_easy_getinfo( pCURL, CURLINFO_RESPONSE_CODE, &nError ); + + if ( 403 == nError ) + aMessage += " 403: Access denied!"; + else if ( 404 == nError ) + aMessage += " 404: File not found!"; + else if ( 416 == nError ) + { + // we got this error probably, because we already downloaded the file + out.Handler->downloadFinished(out.File); + ret = true; + } + else + { + aMessage += ":error code = " + OString::number( nError ) + " !"; + } + } + if ( !ret ) + out.Handler->downloadStalled( OStringToOUString(aMessage, RTL_TEXTENCODING_UTF8) ); + } + + curl_easy_cleanup(pCURL); + } + + return ret; +} + + +bool +Download::start(const OUString& rURL, const OUString& rFile, const OUString& rDestinationDir) +{ + OSL_ASSERT( m_aHandler.is() ); + + OutData out(m_aCondition); + OUString aFile( rFile ); + + // when rFile is empty, there is no remembered file name. If there is already a file with the + // same name ask the user if she wants to resume a download or restart the download + if ( aFile.isEmpty() ) + { + // GetFileName() + OUString aURL( rURL ); + // ensure no trailing '/' + sal_Int32 nLen = aURL.getLength(); + while( (nLen > 0) && ('/' == aURL[ nLen-1 ]) ) + aURL = aURL.copy( 0, --nLen ); + + // extract file name last '/' + sal_Int32 nIndex = aURL.lastIndexOf('/'); + aFile = rDestinationDir + aURL.subView( nIndex ); + + // check for existing file + oslFileError rc = osl_openFile( aFile.pData, &out.FileHandle, osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ); + osl_closeFile(out.FileHandle); + out.FileHandle = nullptr; + + if( osl_File_E_EXIST == rc ) + { + if ( m_aHandler->checkDownloadDestination( aURL.copy( nIndex+1 ) ) ) + { + osl_removeFile( aFile.pData ); + aFile.clear(); + } + else + m_aHandler->downloadStarted( aFile, 0 ); + } + else + { + osl_removeFile( aFile.pData ); + aFile.clear(); + } + } + + out.File = aFile; + out.DestinationDir = rDestinationDir; + out.Handler = m_aHandler; + + if( !aFile.isEmpty() ) + { + oslFileError rc = osl_openFile(aFile.pData, &out.FileHandle, osl_File_OpenFlag_Write); + + if( osl_File_E_None == rc ) + { + // Set file pointer to the end of the file on resume + if( osl_File_E_None == osl_setFilePos(out.FileHandle, osl_Pos_End, 0) ) + { + osl_getFilePos(out.FileHandle, &out.Offset); + } + } + else if( osl_File_E_NOENT == rc ) // file has been deleted meanwhile .. + out.File.clear(); + } + + OString aProxyHost; + sal_Int32 nProxyPort = -1; + getProxyForURL(rURL, aProxyHost, nProxyPort); + + bool ret = curl_run(rURL, out, aProxyHost, nProxyPort); + + if( nullptr != out.FileHandle ) + { + osl_syncFile(out.FileHandle); + osl_closeFile(out.FileHandle); + +// #i90930# Don't remove already downloaded bits, when curl_run reports an error +// because later calls might be successful +// if( ! ret ) +// osl_removeFile(out.File.pData); + } + + m_aCondition.reset(); + return ret; +} + + +void +Download::stop() +{ + m_aCondition.set(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/download.hxx b/extensions/source/update/check/download.hxx new file mode 100644 index 0000000000..12a11ddde0 --- /dev/null +++ b/extensions/source/update/check/download.hxx @@ -0,0 +1,80 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <osl/conditn.hxx> +#include <salhelper/simplereferenceobject.hxx> + +struct DownloadInteractionHandler : public virtual salhelper::SimpleReferenceObject +{ + virtual bool checkDownloadDestination(const OUString& rFileName) = 0; + + // called if the destination file already exists, but resume is false + virtual bool downloadTargetExists(const OUString& rFileName) = 0; + + // called when curl reports an error + virtual void downloadStalled(const OUString& rErrorMessage) = 0; + + // progress handler + virtual void downloadProgressAt(sal_Int8 nPercent) = 0; + + // called on first progress notification + virtual void downloadStarted(const OUString& rFileName, sal_Int64 nFileSize) = 0; + + // called when download has been finished + virtual void downloadFinished(const OUString& rFileName) = 0; + +protected: + virtual ~DownloadInteractionHandler() override {} +}; + +class Download +{ +public: + Download(const css::uno::Reference<css::uno::XComponentContext>& xContext, + const rtl::Reference<DownloadInteractionHandler>& rHandler) + : m_xContext(xContext) + , m_aHandler(rHandler){}; + + // returns true when the content of rURL was successfully written to rLocalFile + bool start(const OUString& rURL, const OUString& rFile, const OUString& rDestinationDir); + + // stops the download after the next write operation + void stop(); + +protected: + // Determines the appropriate proxy settings for the given URL. Returns true if a proxy should be used + void getProxyForURL(std::u16string_view rURL, OString& rHost, sal_Int32& rPort) const; + +private: + osl::Condition m_aCondition; + const css::uno::Reference<css::uno::XComponentContext>& m_xContext; + const rtl::Reference<DownloadInteractionHandler> m_aHandler; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/onlinecheck.cxx b/extensions/source/update/check/onlinecheck.cxx new file mode 100644 index 0000000000..af6ade879f --- /dev/null +++ b/extensions/source/update/check/onlinecheck.cxx @@ -0,0 +1,49 @@ +/* -*- 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/types.h> +#include <sal/macros.h> + +#include "onlinecheck.hxx" + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <wininet.h> + +// #i71984 +extern "C" bool WNT_hasInternetConnection() +{ + DWORD dwFlags; + WCHAR szConnectionName[1024]; + + __try { + bool fIsConnected = InternetGetConnectedStateExW( + &dwFlags, + szConnectionName, + SAL_N_ELEMENTS(szConnectionName), + 0 ); + + return fIsConnected; + + } __except( EXCEPTION_EXECUTE_HANDLER ) { + return false; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/onlinecheck.hxx b/extensions/source/update/check/onlinecheck.hxx new file mode 100644 index 0000000000..fcfedcf480 --- /dev/null +++ b/extensions/source/update/check/onlinecheck.hxx @@ -0,0 +1,28 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#if defined(_WIN32) +extern "C" bool WNT_hasInternetConnection(); +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/org/openoffice/Office/Addons.xcu b/extensions/source/update/check/org/openoffice/Office/Addons.xcu new file mode 100644 index 0000000000..29aa55e47f --- /dev/null +++ b/extensions/source/update/check/org/openoffice/Office/Addons.xcu @@ -0,0 +1,42 @@ +<?xml version='1.0' encoding='utf-8'?> +<!-- + * 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 . +--> +<oor:component-data oor:name="Addons" oor:package="org.openoffice.Office" xmlns:install="http://openoffice.org/2004/installation" xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <node oor:name="AddonUI" install:module="onlineupdate"> + <node oor:name="OfficeHelp"> + <node oor:name="UpdateCheckJob" oor:op="replace"> + <prop oor:name="URL" oor:type="xs:string"> + <value>vnd.sun.star.job:alias=UpdateCheck</value> + </prop> + <prop oor:name="ImageIdentifier" oor:type="xs:string"> + <value/> + </prop> + <prop oor:name="Title" oor:type="xs:string"> + <value xml:lang="en-US">~Check for Updates...</value> + </prop> + <prop oor:name="Target" oor:type="xs:string"> + <value>_self</value> + </prop> + <prop oor:name="Context" oor:type="xs:string"> + <value/> + </prop> + </node> + </node> + </node> +</oor:component-data> + diff --git a/extensions/source/update/check/org/openoffice/Office/Jobs.xcu b/extensions/source/update/check/org/openoffice/Office/Jobs.xcu new file mode 100644 index 0000000000..58db404809 --- /dev/null +++ b/extensions/source/update/check/org/openoffice/Office/Jobs.xcu @@ -0,0 +1,66 @@ +<?xml version='1.0' encoding='utf-8'?> +<!-- + * 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 . +--> +<oor:component-data oor:name="Jobs" oor:package="org.openoffice.Office" xmlns:install="http://openoffice.org/2004/installation" xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <node oor:name="Jobs" install:module="onlineupdate"> + <node oor:name="UpdateCheck" oor:op="replace"> + <prop oor:name="Service"> + <value>com.sun.star.setup.UpdateCheck</value> + </prop> + <node oor:name="Arguments"> + <prop oor:name="AutoCheckEnabled" oor:type="xs:boolean" oor:op="replace"> + <value>true</value> + </prop> + <prop oor:name="LastCheck" oor:type="xs:long" oor:op="replace"> + <value>0</value> + </prop> + <prop oor:name="CheckInterval" oor:type="xs:long" oor:op="replace"> + <!-- + Every Day = 86400 + Every Week = 604800 + Every Month = 2592000 + --> + <value>604800</value> + </prop> + <prop oor:name="DownloadDestination" oor:type="xs:string" oor:op="replace"> + <value></value> + </prop> + <prop oor:name="AutoDownloadEnabled" oor:type="xs:boolean" oor:op="replace"> + <value>false</value> + </prop> + <prop oor:name="DownloadSupported" oor:type="xs:boolean" oor:op="replace"> + <value>true</value> + </prop> + <prop oor:name="DownloadPaused" oor:type="xs:boolean" oor:op="replace"> + <value>false</value> + </prop> + <prop oor:name="ExtendedUserAgent" oor:type="xs:boolean" oor:op="replace"> + <value>false</value> + </prop> + </node> + </node> + </node> + <node oor:name="Events" install:module="onlineupdate"> + <node oor:name="onFirstVisibleTask" oor:op="fuse"> + <node oor:name="JobList"> + <node oor:name="UpdateCheck" oor:op="replace"/> + </node> + </node> + </node> +</oor:component-data> + diff --git a/extensions/source/update/check/updatecheck.cxx b/extensions/source/update/check/updatecheck.cxx new file mode 100644 index 0000000000..26b83f9d83 --- /dev/null +++ b/extensions/source/update/check/updatecheck.cxx @@ -0,0 +1,1507 @@ +/* -*- 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 <string_view> + +#include <comphelper/scopeguard.hxx> +#include <config_folders.h> + +#include "updatecheck.hxx" + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <com/sun/star/deployment/UpdateInformationProvider.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/office/Quickstart.hpp> +#include <com/sun/star/system/SystemShellExecute.hpp> +#include <com/sun/star/system/SystemShellExecuteException.hpp> +#include <com/sun/star/system/SystemShellExecuteFlags.hpp> +#include <com/sun/star/task/XJob.hpp> +#include <com/sun/star/task/XJobExecutor.hpp> + +#include <rtl/bootstrap.hxx> +#include <osl/process.h> +#include <osl/file.hxx> +#include <sal/macros.h> +#include <sal/log.hxx> +#include <comphelper/diagnose_ex.hxx> + +#ifdef _WIN32 +#include <o3tl/safeCoInitUninit.hxx> +#include <objbase.h> +#endif + +#include "onlinecheck.hxx" +#include "updateprotocol.hxx" +#include "updatecheckconfig.hxx" + +namespace beans = com::sun::star::beans ; +namespace deployment = com::sun::star::deployment ; +namespace frame = com::sun::star::frame ; +namespace lang = com::sun::star::lang ; +namespace c3s = com::sun::star::system ; +namespace task = com::sun::star::task ; +namespace uno = com::sun::star::uno ; + +constexpr OUStringLiteral PROPERTY_TITLE = u"BubbleHeading"; +constexpr OUStringLiteral PROPERTY_TEXT = u"BubbleText"; +constexpr OUStringLiteral PROPERTY_SHOW_BUBBLE = u"BubbleVisible"; +constexpr OUStringLiteral PROPERTY_CLICK_HDL = u"MenuClickHDL"; +constexpr OUString PROPERTY_SHOW_MENUICON = u"MenuIconVisible"_ustr; + +// Returns the URL of the release note for the given position +OUString getReleaseNote(const UpdateInfo& rInfo, sal_uInt8 pos, bool autoDownloadEnabled) +{ + for (auto const& elem : rInfo.ReleaseNotes) + { + if( pos == elem.Pos ) + { + if( (pos > 2) || !autoDownloadEnabled || elem.URL2.isEmpty() ) + return elem.URL; + } + else if( (pos == elem.Pos2) && ((1 == elem.Pos) || (2 == elem.Pos)) && autoDownloadEnabled ) + return elem.URL2; + } + + return OUString(); +} + + +namespace +{ + +OUString getBuildId() +{ + OUString aPathVal("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}"); + rtl::Bootstrap::expandMacros(aPathVal); + return aPathVal; +} + + +bool isObsoleteUpdateInfo(std::u16string_view rBuildId) +{ + return rBuildId != getBuildId() && !rBuildId.empty(); +} + + +OUString getImageFromFileName(const OUString& aFile) +{ +#ifndef _WIN32 + OUString aUnpackPath; + if( osl_getExecutableFile(&aUnpackPath.pData) == osl_Process_E_None ) + { + sal_uInt32 lastIndex = aUnpackPath.lastIndexOf('/'); + if ( lastIndex > 0 ) + { + aUnpackPath = OUString::Concat(aUnpackPath.subView( 0, lastIndex+1 )) + + "unpack_update"; + } + + oslFileHandle hOut = nullptr; + oslProcess hProcess = nullptr; + + OUString aSystemPath; + osl::File::getSystemPathFromFileURL(aFile, aSystemPath); + + oslProcessError rc = osl_executeProcess_WithRedirectedIO( + aUnpackPath.pData, // [in] Image name + &aSystemPath.pData, 1, // [in] Arguments + osl_Process_WAIT | osl_Process_NORMAL, // [in] Options + nullptr, // [in] Security + nullptr, // [in] Working directory + nullptr, 0, // [in] Environment variables + &hProcess, // [out] Process handle + nullptr, &hOut, nullptr // [out] File handles for redirected I/O + ); + + if( osl_Process_E_None == rc ) + { + // Create a guard to ensure correct cleanup in its dtor in any case + comphelper::ScopeGuard g([hOut, hProcess] () { + osl_closeFile(hOut); + osl_freeProcessHandle(hProcess); + }); + + oslProcessInfo aInfo; + aInfo.Size = sizeof(oslProcessInfo); + + if( osl_Process_E_None == osl_getProcessInfo(hProcess, osl_Process_EXITCODE, &aInfo) ) + { + if( 0 == aInfo.Code ) + { + char szBuffer[4096]; + sal_uInt64 nBytesRead = 0; + const sal_uInt64 nBytesToRead = sizeof(szBuffer) - 1; + + OUString aImageName; + while( osl_File_E_None == osl_readFile(hOut, szBuffer, nBytesToRead, &nBytesRead) ) + { + char *pc = szBuffer + nBytesRead; + do + { + *pc = '\0'; --pc; + } + while( ('\n' == *pc) || ('\r' == *pc) ); + + aImageName += OUString(szBuffer, pc - szBuffer + 1, osl_getThreadTextEncoding()); + + if( nBytesRead < nBytesToRead ) + break; + } + + if( osl::FileBase::E_None == osl::FileBase::getFileURLFromSystemPath(aImageName, aImageName) ) + return aImageName; + } + } + } + } +#endif + + return aFile; +} + + +uno::Reference< beans::XPropertySet > createMenuBarUI( + const uno::Reference< uno::XComponentContext >& xContext, + const uno::Reference< task::XJob >& xJob) +{ + if( !xContext.is() ) + throw uno::RuntimeException( + "UpdateCheckJob: empty component context", uno::Reference< uno::XInterface > () ); + + uno::Reference< lang::XMultiComponentFactory > xServiceManager(xContext->getServiceManager()); + if( !xServiceManager.is() ) + throw uno::RuntimeException( + "UpdateCheckJob: unable to obtain service manager from component context", uno::Reference< uno::XInterface > () ); + + uno::Reference< beans::XPropertySet > xMenuBarUI( + xServiceManager->createInstanceWithContext( "com.sun.star.setup.UpdateCheckUI", xContext ), + uno::UNO_QUERY_THROW); + + xMenuBarUI->setPropertyValue( PROPERTY_CLICK_HDL, uno::Any( xJob ) ); + + return xMenuBarUI; +} + + +typedef sal_Bool (* OnlineCheckFunc) (); + +class UpdateCheckThread : public WorkerThread +{ + +public: + UpdateCheckThread( osl::Condition& rCondition, + const uno::Reference<uno::XComponentContext>& xContext, + rtl::Reference<UpdateCheck> const & controller ); + + virtual void SAL_CALL join() override; + virtual void SAL_CALL terminate() override; + virtual void cancel() override; + + void cancelAsSoonAsPossible(); + +protected: + virtual ~UpdateCheckThread() override; + + virtual void SAL_CALL run() override; + virtual void SAL_CALL onTerminated() override; + + /* Wrapper around checkForUpdates */ + bool runCheck( bool & rbExtensionsChecked ); + +private: + + /* Used to avoid dialup login windows (on platforms we know how to double this) */ + static bool hasInternetConnection() + { +#ifdef _WIN32 + return WNT_hasInternetConnection(); +#else + return true; +#endif + } + + /* Creates a new instance of UpdateInformationProvider and returns this instance */ + uno::Reference<deployment::XUpdateInformationProvider> createProvider() + { + osl::MutexGuard aGuard(m_aMutex); + m_xProvider = deployment::UpdateInformationProvider::create(m_xContext); + return m_xProvider; + }; + + /* Returns the remembered instance of UpdateInformationProvider if any */ + uno::Reference<deployment::XUpdateInformationProvider> getProvider() + { osl::MutexGuard aGuard(m_aMutex); return m_xProvider; }; + + /* Releases the remembered instance of UpdateInformationProvider if any */ + void clearProvider() + { osl::MutexGuard aGuard(m_aMutex); m_xProvider.clear(); }; + + osl::Mutex m_aMutex; + +protected: + osl::Condition& m_aCondition; + +private: + const uno::Reference<uno::XComponentContext> m_xContext; + uno::Reference<deployment::XUpdateInformationProvider> m_xProvider; + rtl::Reference<UpdateCheck> m_controller; + bool m_cancelAsSoonAsPossible; +}; + + +class ManualUpdateCheckThread : public UpdateCheckThread +{ +public: + ManualUpdateCheckThread( osl::Condition& rCondition, const uno::Reference<uno::XComponentContext>& xContext ) : + UpdateCheckThread(rCondition, xContext, {}) {}; + + virtual void SAL_CALL run() override; +}; + + +class MenuBarButtonJob : public ::cppu::WeakImplHelper< task::XJob > +{ +public: + explicit MenuBarButtonJob(const rtl::Reference< UpdateCheck >& rUpdateCheck); + + // XJob + virtual uno::Any SAL_CALL execute(const uno::Sequence<beans::NamedValue>&) override; + +private: + rtl::Reference< UpdateCheck > m_aUpdateCheck; +}; + +class DownloadThread : public WorkerThread +{ +public: + DownloadThread( + osl::Condition& rCondition, + const uno::Reference<uno::XComponentContext>& xContext, + const rtl::Reference< DownloadInteractionHandler >& rHandler, + const OUString& rURL ); + + virtual void SAL_CALL run() override; + virtual void cancel() override; + virtual void SAL_CALL suspend() override; + virtual void SAL_CALL onTerminated() override; + +protected: + virtual ~DownloadThread() override; + +private: + osl::Condition& m_aCondition; + const uno::Reference<uno::XComponentContext> m_xContext; + const OUString m_aURL; + Download m_aDownload; +}; + + + +UpdateCheckThread::UpdateCheckThread( osl::Condition& rCondition, + const uno::Reference<uno::XComponentContext>& xContext, + rtl::Reference<UpdateCheck> const & controller ) : + m_aCondition(rCondition), + m_xContext(xContext), + m_controller(controller), + m_cancelAsSoonAsPossible(false) +{ + createSuspended(); + + // actually run the thread + resume(); +} + + +UpdateCheckThread::~UpdateCheckThread() +{ +} + + +void SAL_CALL +UpdateCheckThread::terminate() +{ + // Cancel potentially hanging http request .. + cancel(); + // .. before terminating + osl::Thread::terminate(); +} + + +void SAL_CALL +UpdateCheckThread::join() +{ + uno::Reference< deployment::XUpdateInformationProvider > xProvider(getProvider()); + + // do not join during an update check until #i73893# is fixed + if( ! xProvider.is() ) + { + osl::Thread::join(); + } +} + + +void +UpdateCheckThread::cancel() +{ + uno::Reference< deployment::XUpdateInformationProvider > xProvider(getProvider()); + + if( xProvider.is() ) + xProvider->cancel(); +} + +void UpdateCheckThread::cancelAsSoonAsPossible() { + { + osl::MutexGuard g(m_aMutex); + m_cancelAsSoonAsPossible = true; + } + m_aCondition.set(); +} + +bool +UpdateCheckThread::runCheck( bool & rbExtensionsChecked ) +{ + bool ret = false; + UpdateState eUIState = UPDATESTATE_NO_UPDATE_AVAIL; + + UpdateInfo aInfo; + rtl::Reference< UpdateCheck > aController(UpdateCheck::get()); + + if( checkForUpdates(aInfo, m_xContext, aController->getInteractionHandler(), createProvider()) ) + { + aController->setUpdateInfo(aInfo); + eUIState = UpdateCheck::getUIState(aInfo); + ret = true; + } + else + aController->setCheckFailedState(); + + // We will only look for extension updates, when there is no 'check for office updates' dialog open + // and when there was no office update found + if ( ( eUIState != UPDATESTATE_UPDATE_AVAIL ) && + ( eUIState != UPDATESTATE_UPDATE_NO_DOWNLOAD ) && + !aController->isDialogShowing() && + !rbExtensionsChecked ) + { + bool bHasExtensionUpdates = checkForExtensionUpdates( m_xContext ); + aController->setHasExtensionUpdates( bHasExtensionUpdates ); + if ( bHasExtensionUpdates ) + aController->setUIState( UPDATESTATE_EXT_UPD_AVAIL ); + rbExtensionsChecked = true; + } + + // joining with this thread is safe again + clearProvider(); + return ret; +} + + +void SAL_CALL +UpdateCheckThread::onTerminated() +{ + delete this; +} + + +void SAL_CALL +UpdateCheckThread::run() +{ + osl_setThreadName("UpdateCheckThread"); + + TimeValue systime; + TimeValue nExtCheckTime; + osl_getSystemTime( &nExtCheckTime ); + + osl::Condition::Result aResult = osl::Condition::result_timeout; + TimeValue tv = { 10, 0 }; + + // Initial wait to avoid doing further time consuming tasks during start-up + aResult = m_aCondition.wait(&tv); + { + osl::MutexGuard g(m_aMutex); + if (m_cancelAsSoonAsPossible) { + goto done; + } + } + + try { + bool bExtensionsChecked = false; + + while( schedule() ) + { + /* Use cases: + * a) manual check requested from auto check thread - "last check" should not be checked (one time) + * a1) manual check was requested in the middle of a running auto check, + * condition is set + * a2) manual check was requested while waiting for a retry, + * condition is set + * a3) manual check was requested while waiting for time to next + * scheduled check elapsing, condition is set + * a4) manual check was requested during initial wait, condition is set + * b) check interval got changed, condition may be set - same sub-cases as a), + * but "last check" should be honored + * c) normal auto check mode, condition not set - "last check" should be honored + */ + + // Accessing const members without synchronization + rtl::Reference< UpdateCheck > aController(UpdateCheck::get()); + rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext, *aController); + + // FIXME: remember last & offset ? + sal_Int64 last = rModel->getLastChecked(); + sal_Int64 offset = rModel->getCheckInterval(); + + rModel.clear(); + + // last == 0 means check immediately + bool checkNow = last <= 0; + + // Reset the condition to avoid busy loops + if( osl::Condition::result_ok == aResult ) + { + m_aCondition.reset(); + aResult = osl::Condition::result_timeout; + checkNow = aController->isDialogShowing(); + } + + if( ! checkNow ) + { + osl_getSystemTime(&systime); + + // Go back to sleep until time has elapsed + sal_Int64 next = last + offset; + if( last + offset > systime.Seconds ) + { + // This can not be > 32 Bit for now .. + tv.Seconds = static_cast< sal_Int32 > (next - systime.Seconds); + aResult = m_aCondition.wait(&tv); + { + osl::MutexGuard g(m_aMutex); + if (m_cancelAsSoonAsPossible) { + goto done; + } + } + continue; + } + } + + static sal_uInt8 n = 0; + + if( ! hasInternetConnection() || ! runCheck( bExtensionsChecked ) ) + { + // the extension update check should be independent from the office update check + + osl_getSystemTime( &systime ); + if ( nExtCheckTime.Seconds + offset < systime.Seconds ) + bExtensionsChecked = false; + + // Increase next by 15, 60, .. minutes + static const sal_Int32 nRetryInterval[] = { 900, 3600, 14400, 86400 }; + + if( n < std::size(nRetryInterval) ) + ++n; + + tv.Seconds = nRetryInterval[n-1]; + aResult = m_aCondition.wait(&tv); + { + osl::MutexGuard g(m_aMutex); + if (m_cancelAsSoonAsPossible) { + goto done; + } + } + } + else // reset retry counter + { + n = 0; + bExtensionsChecked = false; + } + } + } + + catch(const uno::Exception&) { + // Silently catch all errors + TOOLS_WARN_EXCEPTION("extensions.update", "Caught exception, thread terminated" ); + } + +done: + if (m_controller.is()) { + m_controller->notifyUpdateCheckFinished(); + } +} + + +void SAL_CALL +ManualUpdateCheckThread::run() +{ + try { + bool bExtensionsChecked = false; + runCheck( bExtensionsChecked ); + m_aCondition.reset(); + } + catch(const uno::Exception&) { + // Silently catch all errors + TOOLS_WARN_EXCEPTION("extensions.update", "Caught exception, thread terminated" ); + } +} + + +MenuBarButtonJob::MenuBarButtonJob(const rtl::Reference< UpdateCheck >& rUpdateCheck) : + m_aUpdateCheck(rUpdateCheck) +{ +}; + + +uno::Any SAL_CALL +MenuBarButtonJob::execute(const uno::Sequence<beans::NamedValue>& ) +{ + if ( m_aUpdateCheck->shouldShowExtUpdDlg() ) + m_aUpdateCheck->showExtensionDialog(); + else + m_aUpdateCheck->showDialog(); + + return uno::Any(); +} + + +DownloadThread::DownloadThread(osl::Condition& rCondition, + const uno::Reference<uno::XComponentContext>& xContext, + const rtl::Reference< DownloadInteractionHandler >& rHandler, + const OUString& rURL) : + m_aCondition(rCondition), + m_xContext(xContext), + m_aURL(rURL), + m_aDownload(xContext, rHandler) +{ + createSuspended(); +} + + +DownloadThread::~DownloadThread() +{ +} + + +void SAL_CALL +DownloadThread::run() +{ + osl_setThreadName("DownloadThread"); + +#ifdef _WIN32 + int nNbCallCoInitializeExForReinit = 0; + // for SystemShellExecute + o3tl::safeCoInitializeEx(COINIT_APARTMENTTHREADED, nNbCallCoInitializeExForReinit); +#endif + + while( schedule() ) + { + rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext); + + OUString aLocalFile = rModel->getLocalFileName(); + OUString aDownloadDest = rModel->getDownloadDestination(); + + // release config class for now + rModel.clear(); + + static sal_uInt8 n = 0; + if( ! m_aDownload.start(m_aURL, aLocalFile, aDownloadDest ) ) + { + // retry every 15s unless the dialog is not visible + TimeValue tv(15, 0); + + if( ! UpdateCheck::get()->isDialogShowing() ) + { + // Increase next by 1, 5, 15, 60, .. minutes + static const sal_Int16 nRetryInterval[] = { 60, 300, 900, 3600 }; + + if( n < std::size(nRetryInterval) ) + ++n; + + tv.Seconds = nRetryInterval[n-1]; + } + m_aCondition.wait(&tv); + } + else + { + // reset wait period after successful download + n=0; + } + } +#ifdef _WIN32 + o3tl::safeCoUninitializeReinit(COINIT_MULTITHREADED, nNbCallCoInitializeExForReinit); +#endif +} + + +void DownloadThread::cancel() +{ + m_aDownload.stop(); + resume(); + + rtl::Reference< UpdateCheck > aController(UpdateCheck::get()); + aController->cancelDownload(); +} + + +void SAL_CALL DownloadThread::suspend() +{ + osl::Thread::suspend(); + m_aDownload.stop(); +} + + +void SAL_CALL DownloadThread::onTerminated() +{ + delete this; +} + + +} // anonymous namespace + +UpdateCheck::UpdateCheck() + : m_eState(NOT_INITIALIZED) + , m_eUpdateState(UPDATESTATES_COUNT) + , m_pThread(nullptr) + , m_bHasExtensionUpdate(false) + , m_bShowExtUpdDlg(false) + , m_updateCheckRunning(false) +{ +} + +UpdateCheck::~UpdateCheck() {} + +void +UpdateCheck::initialize(const uno::Sequence< beans::NamedValue >& rValues, + const uno::Reference<uno::XComponentContext>& xContext) +{ + std::scoped_lock aGuard(m_aMutex); + + if( NOT_INITIALIZED == m_eState ) + { + NamedValueByNameAccess aNameAccess(rValues); + UpdateCheckROModel aModel( aNameAccess ); + m_xContext = xContext; + + OUString aUpdateEntryVersion = aModel.getUpdateEntryVersion(); + + aModel.getUpdateEntry(m_aUpdateInfo); + + bool obsoleteUpdateInfo = isObsoleteUpdateInfo(aUpdateEntryVersion); + bool bContinueDownload = false; + bool bDownloadAvailable = false; + + m_bHasExtensionUpdate = checkForPendingUpdates( xContext ); + m_bShowExtUpdDlg = false; + + OUString aLocalFileName = aModel.getLocalFileName(); + + if( !aLocalFileName.isEmpty() ) + { + bContinueDownload = true; + + // Try to get the number of bytes already on disk + osl::DirectoryItem aDirectoryItem; + if( osl::DirectoryItem::E_None == osl::DirectoryItem::get(aLocalFileName, aDirectoryItem) ) + { + osl::FileStatus aFileStatus(osl_FileStatus_Mask_FileSize); + if( osl::DirectoryItem::E_None == aDirectoryItem.getFileStatus(aFileStatus) ) + { + sal_Int64 nDownloadSize = aModel.getDownloadSize(); + sal_Int64 nFileSize = aFileStatus.getFileSize(); + + if( nDownloadSize > 0 ) + { + if ( nDownloadSize <= nFileSize ) // we have already downloaded everything + { + bContinueDownload = false; + bDownloadAvailable = true; + m_aImageName = getImageFromFileName( aLocalFileName ); + } + else // Calculate initial percent value. + { + sal_Int32 nPercent = static_cast<sal_Int32>(100 * nFileSize / nDownloadSize); + getUpdateHandler()->setProgress( nPercent ); + } + } + } + } + + if ( bContinueDownload ) + { + bool downloadPaused = aModel.isDownloadPaused(); + + enableDownload(true, downloadPaused); + // coverity[lock_order : FALSE] - incorrect report of lock order error with std::recursive_mutex + setUIState(downloadPaused ? UPDATESTATE_DOWNLOAD_PAUSED : UPDATESTATE_DOWNLOADING); + } + + } + if ( !bContinueDownload ) + { + // We do this intentionally only if no download is in progress .. + if( obsoleteUpdateInfo ) + { + // Bring-up release note for position 5 .. + const OUString aURL(getReleaseNote(m_aUpdateInfo, 5)); + if( !aURL.isEmpty() ) + showReleaseNote(aURL); + + // Data is outdated, probably due to installed update + rtl::Reference< UpdateCheckConfig > aConfig = UpdateCheckConfig::get( xContext, *this ); + aConfig->clearUpdateFound(); + aConfig->clearLocalFileName(); + + + m_aUpdateInfo = UpdateInfo(); + // Remove outdated release notes + storeReleaseNote( 1, OUString() ); + storeReleaseNote( 2, OUString() ); + } + else + { + enableAutoCheck(aModel.isAutoCheckEnabled()); + if ( bDownloadAvailable ) + setUIState( UPDATESTATE_DOWNLOAD_AVAIL ); + else + { + // coverity[lock_order : FALSE] - incorrect report of lock order error with std::recursive_mutex + setUIState(getUIState(m_aUpdateInfo)); + } + } + } + } +} + + +void +UpdateCheck::cancel() +{ + std::unique_lock aGuard(m_aMutex); + + WorkerThread *pThread = m_pThread; + UpdateState eUIState = getUIState(m_aUpdateInfo); + + aGuard.unlock(); + + if( nullptr != pThread ) + pThread->cancel(); + + setUIState(eUIState); +} + + +void +UpdateCheck::download() +{ + std::unique_lock aGuard(m_aMutex); + UpdateInfo aInfo(m_aUpdateInfo); + State eState = m_eState; + aGuard.unlock(); + + if (aInfo.Sources.empty()) + { + SAL_WARN("extensions.update", "download called without source"); + return; + } + + if( aInfo.Sources[0].IsDirect ) + { + // Ignore second click of a double click + if( DOWNLOADING != eState ) + { + shutdownThread(true); + + { + std::scoped_lock aGuard2(m_aMutex); + enableDownload(true); + } + setUIState(UPDATESTATE_DOWNLOADING); + } + } + else + { + showReleaseNote(aInfo.Sources[0].URL); // Display in browser + } +} + + +void +UpdateCheck::pause() +{ + std::unique_lock aGuard(m_aMutex); + + if( nullptr != m_pThread ) + m_pThread->suspend(); + + rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext); + aGuard.unlock(); + + rModel->storeDownloadPaused(true); + setUIState(UPDATESTATE_DOWNLOAD_PAUSED); +} + + +void +UpdateCheck::resume() +{ + std::unique_lock aGuard(m_aMutex); + + if( nullptr != m_pThread ) + m_pThread->resume(); + + rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext); + aGuard.unlock(); + + rModel->storeDownloadPaused(false); + setUIState(UPDATESTATE_DOWNLOADING); +} + + +void +UpdateCheck::closeAfterFailure() +{ + std::unique_lock aGuard(m_aMutex); + + if ( ( m_eState == DISABLED ) || ( m_eState == CHECK_SCHEDULED ) ) + { + const UpdateState eUIState = getUIState( m_aUpdateInfo ); + aGuard.unlock(); + setUIState( eUIState, true ); + } +} + +void UpdateCheck::notifyUpdateCheckFinished() { + std::scoped_lock l(m_aMutex); + m_updateCheckRunning = false; + m_updateCheckFinished.notify_all(); +} + +void UpdateCheck::waitForUpdateCheckFinished() { + UpdateCheckThread * thread; + { + std::scoped_lock l(m_aMutex); + thread = dynamic_cast<UpdateCheckThread *>(m_pThread); + } + if (thread != nullptr) { + thread->cancelAsSoonAsPossible(); + } + for (;;) { + std::unique_lock lock(m_aMutex); + if (!m_updateCheckRunning) { + return; + } + m_updateCheckFinished.wait(lock); + } +} + +void +UpdateCheck::shutdownThread(bool join) +{ + std::unique_lock aGuard(m_aMutex); + + // copy thread object pointer to stack + osl::Thread *pThread = m_pThread; + m_pThread = nullptr; + aGuard.unlock(); + + if( nullptr != pThread ) + { + pThread->terminate(); + if( join ) + { + m_aCondition.set(); + pThread->join(); + m_aCondition.reset(); + } + } +} + + +void +UpdateCheck::enableAutoCheck(bool enable) +{ + if( enable ) + { + m_updateCheckRunning = true; + m_pThread = new UpdateCheckThread(m_aCondition, m_xContext, this); + } + + m_eState = enable ? CHECK_SCHEDULED : DISABLED; +} + + +void +UpdateCheck::enableDownload(bool enable, bool paused) +{ + OSL_ASSERT(nullptr == m_pThread); + + if( enable ) + { + m_pThread = new DownloadThread(m_aCondition, m_xContext, this, m_aUpdateInfo.Sources[0].URL ); + State eState = DISABLED; + if( !paused ) + { + eState = DOWNLOADING; + m_pThread->resume(); + } + else + eState = DOWNLOAD_PAUSED; + + m_eState = eState; + } + else { + enableAutoCheck(UpdateCheckConfig::get(m_xContext)->isAutoCheckEnabled()); + } + +} + + +bool +UpdateCheck::downloadTargetExists(const OUString& rFileName) +{ + std::unique_lock aGuard(m_aMutex); + + rtl::Reference< UpdateHandler > aUpdateHandler(getUpdateHandler()); + UpdateState eUIState = UPDATESTATE_DOWNLOADING; + + bool cont = false; + + if( aUpdateHandler->isVisible() ) + { + cont = aUpdateHandler->showOverwriteWarning(); + if( cont ) + { + if( osl_File_E_None != osl_removeFile(rFileName.pData) ) + { + // FIXME: error message + cont = false; + } + } + else + eUIState = getUIState(m_aUpdateInfo); + } + else + { + m_aImageName = getImageFromFileName(rFileName); + eUIState = UPDATESTATE_DOWNLOAD_AVAIL; + } + + if( !cont ) + { + shutdownThread(false); + enableDownload(false); + + aGuard.unlock(); + setUIState(eUIState); + } + + return cont; +} + + +bool UpdateCheck::checkDownloadDestination( const OUString& rFileName ) +{ + std::scoped_lock aGuard(m_aMutex); + + rtl::Reference< UpdateHandler > aUpdateHandler( getUpdateHandler() ); + + bool bReload = false; + + if( aUpdateHandler->isVisible() ) + { + bReload = aUpdateHandler->showOverwriteWarning( rFileName ); + } + + return bReload; +} + + +void +UpdateCheck::downloadStalled(const OUString& rErrorMessage) +{ + std::unique_lock aGuard(m_aMutex); + rtl::Reference< UpdateHandler > aUpdateHandler(getUpdateHandler()); + aGuard.unlock(); + + aUpdateHandler->setErrorMessage(rErrorMessage); + setUIState(UPDATESTATE_ERROR_DOWNLOADING); +} + + +void +UpdateCheck::downloadProgressAt(sal_Int8 nPercent) +{ + std::unique_lock aGuard(m_aMutex); + rtl::Reference< UpdateHandler > aUpdateHandler(getUpdateHandler()); + aGuard.unlock(); + + aUpdateHandler->setProgress(nPercent); + setUIState(UPDATESTATE_DOWNLOADING); +} + + +void +UpdateCheck::downloadStarted(const OUString& rLocalFileName, sal_Int64 nFileSize) +{ + if ( nFileSize > 0 ) + { + std::scoped_lock aGuard(m_aMutex); + + rtl::Reference< UpdateCheckConfig > aModel(UpdateCheckConfig::get(m_xContext)); + aModel->storeLocalFileName(rLocalFileName, nFileSize); + + // Bring-up release note for position 1 .. + const OUString aURL(getReleaseNote(m_aUpdateInfo, 1, aModel->isAutoDownloadEnabled())); + if( !aURL.isEmpty() ) + showReleaseNote(aURL); + } +} + + +void +UpdateCheck::downloadFinished(const OUString& rLocalFileName) +{ + std::unique_lock aGuard(m_aMutex); + + // no more retries + m_pThread->terminate(); + + m_aImageName = getImageFromFileName(rLocalFileName); + UpdateInfo aUpdateInfo(m_aUpdateInfo); + + aGuard.unlock(); + setUIState(UPDATESTATE_DOWNLOAD_AVAIL); + + // Bring-up release note for position 2 .. + rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get( m_xContext ); + const OUString aURL(getReleaseNote(aUpdateInfo, 2, rModel->isAutoDownloadEnabled())); + if( !aURL.isEmpty() ) + showReleaseNote(aURL); +} + + +void +UpdateCheck::cancelDownload() +{ + shutdownThread(true); + + std::scoped_lock aGuard(m_aMutex); + enableDownload(false); + + rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext); + + OUString aLocalFile(rModel->getLocalFileName()); + rModel->clearLocalFileName(); + rModel->storeDownloadPaused(false); + + if( isObsoleteUpdateInfo(rModel->getUpdateEntryVersion()) ) + { + rModel->clearUpdateFound(); // This wasn't done during init yet .. + m_aUpdateInfo = UpdateInfo(); + } + + /*oslFileError rc =*/ osl_removeFile(aLocalFile.pData); + // FIXME: error handling .. + +} + + +void +UpdateCheck::showDialog(bool forceCheck) +{ + std::unique_lock aGuard(m_aMutex); + + bool update_found = !m_aUpdateInfo.BuildId.isEmpty(); + bool bSetUIState = ! m_aUpdateHandler.is(); + + UpdateState eDialogState = UPDATESTATES_COUNT; + + switch( m_eState ) + { + case DISABLED: + case CHECK_SCHEDULED: + if( forceCheck || ! update_found ) // Run check when forced or if we did not find an update yet + { + eDialogState = UPDATESTATE_CHECKING; + bSetUIState = true; + } + else if(m_aUpdateInfo.Sources[0].IsDirect) + eDialogState = UPDATESTATE_UPDATE_AVAIL; + else + eDialogState = UPDATESTATE_UPDATE_NO_DOWNLOAD; + break; + + case DOWNLOADING: + eDialogState = UPDATESTATE_DOWNLOADING; + break; + + case DOWNLOAD_PAUSED: + eDialogState = UPDATESTATE_DOWNLOAD_PAUSED; + break; + + case NOT_INITIALIZED: + OSL_ASSERT( false ); + break; + } + + if( bSetUIState ) + { + aGuard.unlock(); + setUIState(eDialogState, true); // suppress bubble as Dialog will be visible soon + aGuard.lock(); + } + + getUpdateHandler()->setVisible(); + + // Run check in separate thread .. + if( UPDATESTATE_CHECKING == eDialogState ) + { + if( DISABLED == m_eState ) + { + // destructs itself when done, not cancellable for now .. + new ManualUpdateCheckThread(m_aCondition, m_xContext); + } + + m_aCondition.set(); + } +} + + +void +UpdateCheck::setUpdateInfo(const UpdateInfo& aInfo) +{ + std::unique_lock aGuard(m_aMutex); + + bool bSuppressBubble = aInfo.BuildId == m_aUpdateInfo.BuildId; + m_aUpdateInfo = aInfo; + + OSL_ASSERT(DISABLED == m_eState || CHECK_SCHEDULED == m_eState); + + // Ignore leading non direct download if we get direct ones + std::vector< DownloadSource >::iterator iter = std::find_if(m_aUpdateInfo.Sources.begin(), m_aUpdateInfo.Sources.end(), + [](const DownloadSource& rSource) { return rSource.IsDirect; }); + + if( (iter != m_aUpdateInfo.Sources.begin()) && + (iter != m_aUpdateInfo.Sources.end()) && + iter->IsDirect ) + { + m_aUpdateInfo.Sources.erase(m_aUpdateInfo.Sources.begin(), --iter); + } + + rtl::Reference< UpdateCheckConfig > rModel = UpdateCheckConfig::get(m_xContext, *this); + OSL_ASSERT( rModel.is() ); + + // Decide whether to use alternate release note pos .. + bool autoDownloadEnabled = rModel->isAutoDownloadEnabled(); + + for (auto & elem : m_aUpdateInfo.ReleaseNotes) + { + if( ((1 == elem.Pos) || (2 == elem.Pos)) && autoDownloadEnabled && !elem.URL2.isEmpty()) + { + elem.URL = elem.URL2; + elem.URL2.clear(); + elem.Pos = elem.Pos2; + elem.Pos2 = 0; + } + } + + // do not move below store/clear .. + rModel->updateLastChecked(); + + UpdateState eUIState; + if( !m_aUpdateInfo.Sources.empty() ) + { + rModel->storeUpdateFound(aInfo, getBuildId()); + + if( m_aUpdateInfo.Sources[0].IsDirect ) + { + eUIState = UPDATESTATE_UPDATE_AVAIL; + + if( rModel->isAutoDownloadEnabled() ) + { + shutdownThread(false); + eUIState = UPDATESTATE_DOWNLOADING; + enableDownload(true); + } + } + else + eUIState = UPDATESTATE_UPDATE_NO_DOWNLOAD; + } + else + { + eUIState = UPDATESTATE_NO_UPDATE_AVAIL; + rModel->clearUpdateFound(); + } + + aGuard.unlock(); + setUIState(eUIState, bSuppressBubble); +} + + +void +UpdateCheck::setCheckFailedState() +{ + setUIState(UPDATESTATE_ERROR_CHECKING); +} + + +void UpdateCheck::handleMenuBarUI( const rtl::Reference< UpdateHandler >& rUpdateHandler, + UpdateState& eState, + bool suppressBubble ) +{ + uno::Reference<beans::XPropertySet> xMenuBarUI( m_xMenuBarUI ); + + if ( ( UPDATESTATE_NO_UPDATE_AVAIL == eState ) && m_bHasExtensionUpdate ) + eState = UPDATESTATE_EXT_UPD_AVAIL; + + if ( UPDATESTATE_EXT_UPD_AVAIL == eState ) + m_bShowExtUpdDlg = true; + else + m_bShowExtUpdDlg = false; + + if( xMenuBarUI.is() ) + { + if( UPDATESTATE_NO_UPDATE_AVAIL == eState ) + { + xMenuBarUI->setPropertyValue( PROPERTY_SHOW_MENUICON, uno::Any(false) ); + } + else + { + xMenuBarUI->setPropertyValue( PROPERTY_TITLE, uno::Any(rUpdateHandler->getBubbleTitle(eState)) ); + xMenuBarUI->setPropertyValue( PROPERTY_TEXT, uno::Any(rUpdateHandler->getBubbleText(eState)) ); + + if( ! suppressBubble && ( ! rUpdateHandler->isVisible() || rUpdateHandler->isMinimized() ) ) + xMenuBarUI->setPropertyValue( PROPERTY_SHOW_BUBBLE, uno::Any( true ) ); + + if( UPDATESTATE_CHECKING != eState ) + xMenuBarUI->setPropertyValue( PROPERTY_SHOW_MENUICON, uno::Any(true) ); + } + } +} + + +void UpdateCheck::setUIState(UpdateState eState, bool suppressBubble) +{ + std::unique_lock aGuard(m_aMutex); + + if( ! m_xMenuBarUI.is() && + (DISABLED != m_eState) && + ( m_bHasExtensionUpdate || (UPDATESTATE_NO_UPDATE_AVAIL != eState)) && + (UPDATESTATE_CHECKING != eState) && + (UPDATESTATE_ERROR_CHECKING != eState) + ) + { + m_xMenuBarUI = createMenuBarUI(m_xContext, new MenuBarButtonJob(this)); + } + + // Show bubble only when the status has changed + if ( eState == m_eUpdateState ) + suppressBubble = true; + else + m_eUpdateState = eState; + + rtl::Reference<UpdateHandler> aUpdateHandler(getUpdateHandler()); + OSL_ASSERT( aUpdateHandler.is() ); + + UpdateInfo aUpdateInfo(m_aUpdateInfo); + OUString aImageName(m_aImageName); + + aGuard.unlock(); + + handleMenuBarUI( aUpdateHandler, eState, suppressBubble ); + + if( (UPDATESTATE_UPDATE_AVAIL == eState) + || (UPDATESTATE_DOWNLOAD_PAUSED == eState) + || (UPDATESTATE_DOWNLOADING == eState) ) + { + uno::Reference< uno::XComponentContext > xContext(m_xContext); + + OUString aDownloadDestination = + UpdateCheckConfig::get(xContext, this)->getDownloadDestination(); + + osl_getSystemPathFromFileURL(aDownloadDestination.pData, &aDownloadDestination.pData); + + aUpdateHandler->setDownloadPath(aDownloadDestination); + } + else if( UPDATESTATE_DOWNLOAD_AVAIL == eState ) + { + aUpdateHandler->setDownloadFile(aImageName); + } + + aUpdateHandler->setDescription(aUpdateInfo.Description); + aUpdateHandler->setNextVersion(aUpdateInfo.Version); + aUpdateHandler->setState(eState); +} + + +UpdateState +UpdateCheck::getUIState(const UpdateInfo& rInfo) +{ + UpdateState eUIState = UPDATESTATE_NO_UPDATE_AVAIL; + + if( !rInfo.BuildId.isEmpty() ) + { + if( rInfo.Sources[0].IsDirect ) + eUIState = UPDATESTATE_UPDATE_AVAIL; + else + eUIState = UPDATESTATE_UPDATE_NO_DOWNLOAD; + } + + return eUIState; +} + + +void +UpdateCheck::showReleaseNote(const OUString& rURL) const +{ + const uno::Reference< c3s::XSystemShellExecute > xShellExecute( + c3s::SystemShellExecute::create( m_xContext ) ); + + try { + xShellExecute->execute(rURL, OUString(), c3s::SystemShellExecuteFlags::URIS_ONLY); + } catch(const c3s::SystemShellExecuteException&) { + } +} + + +bool +UpdateCheck::storeReleaseNote(sal_Int8 nNum, const OUString &rURL) +{ + osl::FileBase::RC rc; + OUString aTargetDir( UpdateCheckConfig::getAllUsersDirectory() + "/sun" ); + + osl::Directory::createPath( aTargetDir ); + + OUString aFileName = "releasenote" + + OUString::number( nNum ) + + ".url"; + + OUString aFilePath; + rc = osl::FileBase::getAbsoluteFileURL( aTargetDir, aFileName, aFilePath ); + if ( rc != osl::FileBase::E_None ) return false; + + osl::File::remove( aFilePath ); + + // don't store empty release notes, but delete old ones + if ( rURL.isEmpty() ) + return true; + + osl::File aFile( aFilePath ); + rc = aFile.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ); + if ( rc != osl::FileBase::E_None ) return false; + + OString aLineBuf("[InternetShortcut]\r\n"_ostr); + sal_uInt64 nWritten = 0; + + OUString aURL( rURL ); +#ifdef _WIN32 + rc = aFile.write( aLineBuf.getStr(), aLineBuf.getLength(), nWritten ); + if ( rc != osl::FileBase::E_None ) return false; + aURL = "URL=" + rURL; +#endif + aLineBuf = OUStringToOString( aURL, RTL_TEXTENCODING_UTF8 ); + rc = aFile.write( aLineBuf.getStr(), aLineBuf.getLength(), nWritten ); + if ( rc != osl::FileBase::E_None ) return false; + + aFile.close(); + return true; +} + + +void UpdateCheck::showExtensionDialog() +{ + uno::Reference< uno::XInterface > xService; + + if( ! m_xContext.is() ) + throw uno::RuntimeException( + "UpdateCheck::showExtensionDialog(): empty component context", uno::Reference< uno::XInterface > () ); + + uno::Reference< lang::XMultiComponentFactory > xServiceManager( m_xContext->getServiceManager() ); + if( !xServiceManager.is() ) + throw uno::RuntimeException( + "UpdateCheck::showExtensionDialog(): unable to obtain service manager from component context", uno::Reference< uno::XInterface > () ); + + xService = xServiceManager->createInstanceWithContext( "com.sun.star.deployment.ui.PackageManagerDialog", m_xContext ); + uno::Reference< task::XJobExecutor > xExecutable( xService, uno::UNO_QUERY ); + if ( xExecutable.is() ) + xExecutable->trigger( "SHOW_UPDATE_DIALOG" ); +} + + +rtl::Reference<UpdateHandler> +UpdateCheck::getUpdateHandler() +{ + std::scoped_lock aGuard(m_aMutex); + + if( ! m_aUpdateHandler.is() ) + m_aUpdateHandler = new UpdateHandler(m_xContext, this); + + return m_aUpdateHandler; +} + + +uno::Reference< task::XInteractionHandler > +UpdateCheck::getInteractionHandler() const +{ + std::scoped_lock aGuard(m_aMutex); + + uno::Reference< task::XInteractionHandler > xHandler; + + if( m_aUpdateHandler.is() && m_aUpdateHandler->isVisible() ) + xHandler = m_aUpdateHandler.get(); + + return xHandler; +} + + +bool +UpdateCheck::isDialogShowing() const +{ + std::scoped_lock aGuard(m_aMutex); + return m_aUpdateHandler.is() && m_aUpdateHandler->isVisible(); +}; + + +void +UpdateCheck::autoCheckStatusChanged(bool enabled) +{ + std::unique_lock aGuard(m_aMutex); + + if( (CHECK_SCHEDULED == m_eState) && !enabled ) + shutdownThread(false); + + if( (DISABLED == m_eState) || (CHECK_SCHEDULED == m_eState) ) + { + enableAutoCheck(enabled); + UpdateState eState = getUIState(m_aUpdateInfo); + aGuard.unlock(); + setUIState(eState); + } +}; + + +void +UpdateCheck::autoCheckIntervalChanged() +{ + // just wake-up + m_aCondition.set(); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updatecheck.hxx b/extensions/source/update/check/updatecheck.hxx new file mode 100644 index 0000000000..7af355bc41 --- /dev/null +++ b/extensions/source/update/check/updatecheck.hxx @@ -0,0 +1,188 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <condition_variable> +#include <mutex> + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <osl/conditn.hxx> +#include <osl/thread.hxx> +#include <rtl/instance.hxx> + +#include "updateinfo.hxx" +#include "updatecheckconfiglistener.hxx" +#include "actionlistener.hxx" +#include "updatehdl.hxx" +#include "download.hxx" + + +class UpdateCheck; + +class UpdateCheckInitData { + +public: + inline rtl::Reference< UpdateCheck > operator() () const; +}; + +class WorkerThread : public osl::Thread +{ +public: + virtual void cancel() = 0; +}; + +class UpdateCheck : + public UpdateCheckConfigListener, + public IActionListener, + public DownloadInteractionHandler, + public rtl::StaticWithInit< rtl::Reference< UpdateCheck >, UpdateCheckInitData > +{ + UpdateCheck(); + + virtual ~UpdateCheck() override; + +public: + operator rtl::Reference< UpdateCheckConfigListener > () + { return static_cast< UpdateCheckConfigListener * > (this); } + + void initialize(const css::uno::Sequence<css::beans::NamedValue>& rValues, + const css::uno::Reference<css::uno::XComponentContext>& xContext); + + // Update internal update info member + void setUpdateInfo(const UpdateInfo& aInfo); + + /* This method turns on the menubar icon, triggers the bubble window or + * updates the dialog text when appropriate + */ + void setUIState(UpdateState eState, bool suppressBubble = false); + + // Returns the UI state that matches rInfo best + static UpdateState getUIState(const UpdateInfo& rInfo); + + // Check for updates failed + void setCheckFailedState(); + + // Executes the update check dialog for manual checks and downloads interaction + void showDialog(bool forceCheck = false); + + // Returns true if the update dialog is currently showing + bool isDialogShowing() const; + bool shouldShowExtUpdDlg() const { return ( m_bShowExtUpdDlg && m_bHasExtensionUpdate ); } + void showExtensionDialog(); + void setHasExtensionUpdates( bool bHasUpdates ) { m_bHasExtensionUpdate = bHasUpdates; } + bool hasOfficeUpdate() const { return (m_aUpdateInfo.BuildId.getLength() > 0); } + + // DownloadInteractionHandler + virtual bool downloadTargetExists(const OUString& rFileName) override; + virtual void downloadStalled(const OUString& rErrorMessage) override; + virtual void downloadProgressAt(sal_Int8 nProcent) override; + virtual void downloadStarted(const OUString& rLocalFileName, sal_Int64 nFileSize) override; + virtual void downloadFinished(const OUString& rLocalFileName) override; + // checks if the download target already exists and asks user what to do next + virtual bool checkDownloadDestination( const OUString& rFile ) override; + + // Cancels the download action (and resumes checking if enabled) + void cancelDownload(); + + // Returns the XInteractionHandler of the UpdateHandler instance if present (and visible) + css::uno::Reference< css::task::XInteractionHandler > getInteractionHandler() const; + + // UpdateCheckConfigListener + virtual void autoCheckStatusChanged(bool enabled) override; + virtual void autoCheckIntervalChanged() override; + + // IActionListener + void cancel() override; + void download() override; + void pause() override; + void resume() override; + void closeAfterFailure() override; + + void notifyUpdateCheckFinished(); + + void waitForUpdateCheckFinished(); + +private: + + // Schedules or cancels next automatic check for updates + void enableAutoCheck(bool enable); + + // Starts/resumes or stops a download + void enableDownload(bool enable, bool paused=false); + + // Shuts down the currently running thread + void shutdownThread(bool join); + + // Returns the update handler instance + rtl::Reference<UpdateHandler> getUpdateHandler(); + + // Open the given URL in a browser + void showReleaseNote(const OUString& rURL) const; + + // stores the release note url on disk to be used by setup app + static bool storeReleaseNote(sal_Int8 nNum, const OUString &rURL); + + /* This method turns on the menubar icon and triggers the bubble window + */ + void handleMenuBarUI( const rtl::Reference< UpdateHandler >& rUpdateHandler, + UpdateState& eState, bool suppressBubble ); + enum State { + NOT_INITIALIZED, + DISABLED, + CHECK_SCHEDULED, + DOWNLOADING, + DOWNLOAD_PAUSED + }; + + State m_eState; + UpdateState m_eUpdateState; + + mutable std::recursive_mutex m_aMutex; + WorkerThread *m_pThread; + osl::Condition m_aCondition; + + UpdateInfo m_aUpdateInfo; + OUString m_aImageName; + bool m_bHasExtensionUpdate; + bool m_bShowExtUpdDlg; + + rtl::Reference<UpdateHandler> m_aUpdateHandler; + css::uno::Reference<css::beans::XPropertySet> m_xMenuBarUI; + css::uno::Reference<css::uno::XComponentContext> m_xContext; + + bool m_updateCheckRunning = false; + std::condition_variable_any m_updateCheckFinished; + + friend class UpdateCheckInitData; +}; + +inline rtl::Reference< UpdateCheck > +UpdateCheckInitData::operator() () const +{ + return rtl::Reference< UpdateCheck > (new UpdateCheck()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updatecheckconfig.cxx b/extensions/source/update/check/updatecheckconfig.cxx new file mode 100644 index 0000000000..e728d91e77 --- /dev/null +++ b/extensions/source/update/check/updatecheckconfig.cxx @@ -0,0 +1,660 @@ +/* -*- 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 "updatecheckconfig.hxx" +#include "updatecheck.hxx" +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <osl/security.hxx> +#include <osl/time.h> +#include <osl/file.hxx> +#include <sal/macros.h> +#include <o3tl/char16_t2wchar_t.hxx> + +#ifdef _WIN32 +#include <objbase.h> +#include <shlobj.h> +#endif + +namespace container = com::sun::star::container ; +namespace beans = com::sun::star::beans ; +namespace lang = com::sun::star::lang ; +namespace util = com::sun::star::util ; +namespace uno = com::sun::star::uno ; + +#define LAST_CHECK "LastCheck" +#define UPDATE_VERSION "UpdateVersion" +#define UPDATE_BUILDID "UpdateBuildId" +#define UPDATE_DESCRIPTION "UpdateDescription" +#define DOWNLOAD_URL "DownloadURL" +#define IS_DIRECT_DOWNLOAD "IsDirectDownload" +#define OLD_VERSION "UpdateFoundFor" +#define AUTOCHECK_ENABLED "AutoCheckEnabled" +#define AUTODOWNLOAD_ENABLED "AutoDownloadEnabled" +#define CHECK_INTERVAL "CheckInterval" +#define LOCAL_FILE "LocalFile" +#define DOWNLOAD_SIZE "DownloadSize" +#define DOWNLOAD_PAUSED "DownloadPaused" +#define DOWNLOAD_DESTINATION "DownloadDestination" +#define RELEASE_NOTE "ReleaseNote" + +#define PROPERTY_VERSION "Version" + +const char * const aUpdateEntryProperties[] = { + UPDATE_VERSION, + UPDATE_BUILDID, + UPDATE_DESCRIPTION, + DOWNLOAD_URL, + IS_DIRECT_DOWNLOAD, + RELEASE_NOTE"1", + RELEASE_NOTE"2", + RELEASE_NOTE"3", + RELEASE_NOTE"4", + RELEASE_NOTE"5", + OLD_VERSION +}; + +const sal_uInt32 nUpdateEntryProperties = SAL_N_ELEMENTS(aUpdateEntryProperties); + +css::uno::Any NamedValueByNameAccess::getValue(const char * pName) +{ + const sal_Int32 nLen = m_rValues.getLength(); + for( sal_Int32 n=0; n < nLen; ++n ) + { + if( m_rValues[n].Name.equalsAscii( pName ) ) + return m_rValues[n].Value; + } + return css::uno::Any(); +} + +bool +UpdateCheckROModel::isAutoCheckEnabled() const +{ + return m_aNameAccess.getValue(AUTOCHECK_ENABLED).get<bool>(); +} + +bool +UpdateCheckROModel::isDownloadPaused() const +{ + return m_aNameAccess.getValue(DOWNLOAD_PAUSED).get<bool>(); +} + +OUString +UpdateCheckROModel::getStringValue(const char * pStr) const +{ + uno::Any aAny( m_aNameAccess.getValue(pStr) ); + OUString aRet; + + aAny >>= aRet; + + return aRet; +} + +OUString UpdateCheckROModel::getLocalFileName() const +{ + return getStringValue(LOCAL_FILE); +}; + +sal_Int64 UpdateCheckROModel::getDownloadSize() const +{ + uno::Any aAny( m_aNameAccess.getValue(DOWNLOAD_SIZE) ); + sal_Int64 nRet = -1; + + aAny >>= nRet; + return nRet; +}; + +OUString +UpdateCheckROModel::getUpdateEntryVersion() const +{ + return getStringValue(OLD_VERSION); +} + +void +UpdateCheckROModel::getUpdateEntry(UpdateInfo& rInfo) const +{ + rInfo.BuildId = getStringValue(UPDATE_BUILDID); + rInfo.Version = getStringValue(UPDATE_VERSION); + rInfo.Description = getStringValue(UPDATE_DESCRIPTION); + + bool isDirectDownload = false; + m_aNameAccess.getValue(IS_DIRECT_DOWNLOAD) >>= isDirectDownload; + + rInfo.Sources.push_back( DownloadSource( isDirectDownload, getStringValue(DOWNLOAD_URL) ) ); + + for(sal_Int32 n=1; n < 6; ++n ) + { + OUString aUStr = getStringValue( + OString(OString::Concat(RELEASE_NOTE) + OString::number(n)).getStr()); + if( !aUStr.isEmpty() ) + rInfo.ReleaseNotes.push_back(ReleaseNote(static_cast<sal_Int8>(n), aUStr)); + } +} + +OUString UpdateCheckConfig::getDownloadsDirectory() +{ + OUString aRet; + +#ifdef _WIN32 + PWSTR szPath; + + if (SHGetKnownFolderPath(FOLDERID_Downloads, 0, nullptr, &szPath) == S_OK) + { + aRet = o3tl::toU(szPath); + CoTaskMemFree(szPath); + osl::FileBase::getFileURLFromSystemPath( aRet, aRet ); + } +#else + // This should become a desktop specific setting in some system backend .. + OUString aHomeDir; + osl::Security().getHomeDir( aHomeDir ); + aRet = aHomeDir + "/Desktop"; + + // Set path to home directory when there is no /Desktop directory + osl::Directory aDocumentsDir( aRet ); + if( osl::FileBase::E_None != aDocumentsDir.open() ) + aRet = aHomeDir; +#endif + + return aRet; +} + +OUString UpdateCheckConfig::getAllUsersDirectory() +{ + OUString aRet; + +#ifdef _WIN32 + WCHAR szPath[MAX_PATH]; + + if (TRUE == SHGetSpecialFolderPathW(nullptr, szPath, CSIDL_COMMON_DOCUMENTS, true)) + { + aRet = o3tl::toU(szPath); + osl::FileBase::getFileURLFromSystemPath( aRet, aRet ); + } +#else + osl::FileBase::getTempDirURL(aRet); +#endif + + return aRet; +} + +UpdateCheckConfig::UpdateCheckConfig( const uno::Reference<container::XNameContainer>& xContainer, + const uno::Reference<container::XNameContainer>& xAvailableUpdates, + const uno::Reference<container::XNameContainer>& xIgnoredUpdates, + const ::rtl::Reference< UpdateCheckConfigListener >& rListener ) : + m_xContainer( xContainer ), + m_xAvailableUpdates( xAvailableUpdates ), + m_xIgnoredUpdates( xIgnoredUpdates ), + m_rListener( rListener ) +{} + +UpdateCheckConfig::~UpdateCheckConfig() +{} + +::rtl::Reference< UpdateCheckConfig > +UpdateCheckConfig::get( + const uno::Reference<uno::XComponentContext>& xContext, + const ::rtl::Reference< UpdateCheckConfigListener >& rListener) +{ + uno::Reference< lang::XMultiServiceFactory > xConfigProvider( + css::configuration::theDefaultProvider::get( xContext ) ); + + beans::PropertyValue aProperty; + aProperty.Name = "nodepath"; + aProperty.Value <<= OUString("org.openoffice.Office.Jobs/Jobs/UpdateCheck/Arguments"); + + uno::Sequence< uno::Any > aArgumentList{ uno::Any(aProperty) }; + + uno::Reference< container::XNameContainer > xContainer( + xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationUpdateAccess", aArgumentList ), + uno::UNO_QUERY_THROW ); + + aProperty.Value <<= OUString("/org.openoffice.Office.ExtensionManager/ExtensionUpdateData/IgnoredUpdates"); + aArgumentList = { uno::Any(aProperty) }; + uno::Reference< container::XNameContainer > xIgnoredExt( xConfigProvider->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationUpdateAccess", aArgumentList ), uno::UNO_QUERY_THROW ); + + aProperty.Value <<= OUString("/org.openoffice.Office.ExtensionManager/ExtensionUpdateData/AvailableUpdates"); + aArgumentList = { uno::Any(aProperty) }; + uno::Reference< container::XNameContainer > xUpdateAvail( xConfigProvider->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationUpdateAccess", aArgumentList ), uno::UNO_QUERY_THROW ); + + return new UpdateCheckConfig( xContainer, xUpdateAvail, xIgnoredExt, rListener ); +} + +bool +UpdateCheckConfig::isAutoCheckEnabled() const +{ + bool nValue = false; + const_cast < UpdateCheckConfig *> (this)->getByName( AUTOCHECK_ENABLED ) >>= nValue; + return nValue; +} + +bool +UpdateCheckConfig::isAutoDownloadEnabled() const +{ + bool nValue = false; + const_cast < UpdateCheckConfig *> (this)->getByName( AUTODOWNLOAD_ENABLED ) >>= nValue; + return nValue; +} + +OUString +UpdateCheckConfig::getUpdateEntryVersion() const +{ + OUString aValue; + + // getByName is defined as non const in XNameAccess + const_cast < UpdateCheckConfig *> (this)->getByName( OLD_VERSION ) >>= aValue; + + return aValue; +} + +sal_Int64 +UpdateCheckConfig::getLastChecked() const +{ + sal_Int64 nValue = 0; + + // getByName is defined as non const in XNameAccess + const_cast < UpdateCheckConfig *> (this)->getByName( LAST_CHECK ) >>= nValue; + + return nValue; +} + +sal_Int64 +UpdateCheckConfig::getCheckInterval() const +{ + sal_Int64 nValue = 0; + + // getByName is defined as non const in XNameAccess + const_cast < UpdateCheckConfig *> (this)->getByName( CHECK_INTERVAL ) >>= nValue; + + return nValue; +} + +OUString +UpdateCheckConfig::getLocalFileName() const +{ + OUString aName = LOCAL_FILE; + OUString aRet; + + if( m_xContainer->hasByName(aName) ) + m_xContainer->getByName(aName) >>= aRet; + + return aRet; +} + +OUString +UpdateCheckConfig::getDownloadDestination() const +{ + OUString aRet; + + const_cast <UpdateCheckConfig *> (this)->getByName(DOWNLOAD_DESTINATION) >>= aRet; + + return aRet; +} + +void +UpdateCheckConfig::storeLocalFileName(const OUString& rLocalFileName, sal_Int64 nFileSize) +{ + const sal_uInt8 nItems = 2; + const OUString aNameList[nItems] = { OUString(LOCAL_FILE), OUString(DOWNLOAD_SIZE) }; + const uno::Any aValueList[nItems] = { uno::Any(rLocalFileName), uno::Any(nFileSize) }; + + for( sal_uInt8 i=0; i < nItems; ++i ) + { + if( m_xContainer->hasByName(aNameList[i]) ) + m_xContainer->replaceByName(aNameList[i], aValueList[i]); + else + m_xContainer->insertByName(aNameList[i], aValueList[i]); + } + + commitChanges(); +} + +void +UpdateCheckConfig::clearLocalFileName() +{ + const sal_uInt8 nItems = 2; + const OUString aNameList[nItems] = { OUString(LOCAL_FILE), OUString(DOWNLOAD_SIZE) }; + + for(const auto & i : aNameList) + { + if( m_xContainer->hasByName(i) ) + m_xContainer->removeByName(i); + } + + commitChanges(); +} + +void +UpdateCheckConfig::storeDownloadPaused(bool paused) +{ + replaceByName(DOWNLOAD_PAUSED , uno::Any(paused)); + commitChanges(); +} + +void +UpdateCheckConfig::updateLastChecked() +{ + TimeValue systime; + osl_getSystemTime(&systime); + + sal_Int64 lastCheck = systime.Seconds; + + replaceByName(LAST_CHECK, uno::Any(lastCheck)); +} + +void +UpdateCheckConfig::storeUpdateFound( const UpdateInfo& rInfo, const OUString& aCurrentBuild) + +{ + bool autoDownloadEnabled = isAutoDownloadEnabled(); + + uno::Any aValues[nUpdateEntryProperties] = + { + uno::Any(rInfo.Version), + uno::Any(rInfo.BuildId), + uno::Any(rInfo.Description), + uno::Any(rInfo.Sources[0].URL), + uno::Any(rInfo.Sources[0].IsDirect), + uno::Any(getReleaseNote(rInfo, 1, autoDownloadEnabled) ), + uno::Any(getReleaseNote(rInfo, 2, autoDownloadEnabled) ), + uno::Any(getReleaseNote(rInfo, 3, autoDownloadEnabled) ), + uno::Any(getReleaseNote(rInfo, 4, autoDownloadEnabled) ), + uno::Any(getReleaseNote(rInfo, 5, autoDownloadEnabled) ), + uno::Any(aCurrentBuild) + }; + + OUString aName; + for( sal_uInt32 n=0; n < nUpdateEntryProperties; ++n ) + { + aName = OUString::createFromAscii(aUpdateEntryProperties[n]); + + if( m_xContainer->hasByName(aName) ) + m_xContainer->replaceByName(aName, aValues[n]); + else + m_xContainer->insertByName(aName,aValues[n]); + } + + commitChanges(); +} + +void +UpdateCheckConfig::clearUpdateFound() +{ + OUString aName; + + for(const char* aUpdateEntryProperty : aUpdateEntryProperties) + { + aName = OUString::createFromAscii(aUpdateEntryProperty); + + try { + if( m_xContainer->hasByName(aName) ) + m_xContainer->removeByName(aName); + } catch(const lang::WrappedTargetException& ) { + // Can not remove value, probably in share layer + OSL_ASSERT(false); + m_xContainer->replaceByName(aName, uno::Any(OUString())); + } + } + + /* As we have removed UpdateVersionFound from the shared configuration + * existing entries in the user layer do not have a oor operation and + * thus are completely ignored (which also means they can not be removed). + */ + + commitChanges(); +} + +uno::Type SAL_CALL +UpdateCheckConfig::getElementType() +{ + return m_xContainer->getElementType(); +} + +sal_Bool SAL_CALL +UpdateCheckConfig::hasElements() +{ + return m_xContainer->hasElements(); +} + +uno::Any SAL_CALL +UpdateCheckConfig::getByName( const OUString& aName ) +{ + uno::Any aValue = m_xContainer->getByName( aName ); + + // Provide dynamic default value + if( aName == DOWNLOAD_DESTINATION ) + { + OUString aStr; + aValue >>= aStr; + + if( aStr.isEmpty() ) + aValue <<= getDownloadsDirectory(); + } + return aValue; +} + +uno::Sequence< OUString > SAL_CALL +UpdateCheckConfig::getElementNames() +{ + return m_xContainer->getElementNames(); +} + +sal_Bool SAL_CALL +UpdateCheckConfig::hasByName( const OUString& aName ) +{ + return m_xContainer->hasByName( aName ); +} + +void SAL_CALL +UpdateCheckConfig::replaceByName( const OUString& aName, const uno::Any& aElement ) +{ + return m_xContainer->replaceByName( aName, aElement ); +} + +// XChangesBatch + +void SAL_CALL +UpdateCheckConfig::commitChanges() +{ + uno::Reference< util::XChangesBatch > xChangesBatch(m_xContainer, uno::UNO_QUERY); + if( xChangesBatch.is() && xChangesBatch->hasPendingChanges() ) + { + util::ChangesSet aChangesSet = xChangesBatch->getPendingChanges(); + xChangesBatch->commitChanges(); + + if( m_rListener.is() ) + { + const sal_Int32 nChanges = aChangesSet.getLength(); + OUString aString; + + for( sal_Int32 i=0; i<nChanges; ++i ) + { + aChangesSet[i].Accessor >>= aString; + if( aString.endsWith(AUTOCHECK_ENABLED "']") ) + { + bool bEnabled = false; + aChangesSet[i].Element >>= bEnabled; + m_rListener->autoCheckStatusChanged(bEnabled); + } + else if( aString.endsWith(CHECK_INTERVAL "']") ) + { + m_rListener->autoCheckIntervalChanged(); + } + } + } + } + + xChangesBatch.set( m_xAvailableUpdates, uno::UNO_QUERY ); + if( xChangesBatch.is() && xChangesBatch->hasPendingChanges() ) + { + xChangesBatch->commitChanges(); + } + xChangesBatch.set( m_xIgnoredUpdates, uno::UNO_QUERY ); + if( xChangesBatch.is() && xChangesBatch->hasPendingChanges() ) + { + xChangesBatch->commitChanges(); + } +} + +sal_Bool SAL_CALL +UpdateCheckConfig::hasPendingChanges( ) +{ + uno::Reference< util::XChangesBatch > xChangesBatch(m_xContainer, uno::UNO_QUERY); + if( xChangesBatch.is() ) + return xChangesBatch->hasPendingChanges(); + + return false; +} + +uno::Sequence< util::ElementChange > SAL_CALL +UpdateCheckConfig::getPendingChanges( ) +{ + uno::Reference< util::XChangesBatch > xChangesBatch(m_xContainer, uno::UNO_QUERY); + if( xChangesBatch.is() ) + return xChangesBatch->getPendingChanges(); + + return uno::Sequence< util::ElementChange >(); +} + +bool UpdateCheckConfig::storeExtensionVersion( const OUString& rExtensionName, + const OUString& rVersion ) +{ + bool bNotify = true; + + if ( m_xAvailableUpdates->hasByName( rExtensionName ) ) + uno::Reference< beans::XPropertySet >( m_xAvailableUpdates->getByName( rExtensionName ), uno::UNO_QUERY_THROW )->setPropertyValue( PROPERTY_VERSION, uno::Any( rVersion ) ); + else + { + uno::Reference< beans::XPropertySet > elem( uno::Reference< lang::XSingleServiceFactory >( m_xAvailableUpdates, uno::UNO_QUERY_THROW )->createInstance(), uno::UNO_QUERY_THROW ); + elem->setPropertyValue( PROPERTY_VERSION, uno::Any( rVersion ) ); + m_xAvailableUpdates->insertByName( rExtensionName, uno::Any( elem ) ); + } + + if ( m_xIgnoredUpdates->hasByName( rExtensionName ) ) + { + OUString aIgnoredVersion; + uno::Any aValue( uno::Reference< beans::XPropertySet >( m_xIgnoredUpdates->getByName( rExtensionName ), uno::UNO_QUERY_THROW )->getPropertyValue( PROPERTY_VERSION ) ); + aValue >>= aIgnoredVersion; + if ( aIgnoredVersion.isEmpty() ) // no version means ignore all updates + bNotify = false; + else if ( aIgnoredVersion == rVersion ) // the user wanted to ignore this update + bNotify = false; + } + + commitChanges(); + + return bNotify; +} + +bool UpdateCheckConfig::checkExtensionVersion( const OUString& rExtensionName, + const OUString& rVersion ) +{ + if ( m_xAvailableUpdates->hasByName( rExtensionName ) ) + { + OUString aStoredVersion; + uno::Any aValue( uno::Reference< beans::XPropertySet >( m_xAvailableUpdates->getByName( rExtensionName ), uno::UNO_QUERY_THROW )->getPropertyValue( PROPERTY_VERSION ) ); + aValue >>= aStoredVersion; + + if ( m_xIgnoredUpdates->hasByName( rExtensionName ) ) + { + OUString aIgnoredVersion; + uno::Any aValue2( uno::Reference< beans::XPropertySet >( m_xIgnoredUpdates->getByName( rExtensionName ), uno::UNO_QUERY_THROW )->getPropertyValue( PROPERTY_VERSION ) ); + aValue2 >>= aIgnoredVersion; + if ( aIgnoredVersion.isEmpty() ) // no version means ignore all updates + return false; + else if ( aIgnoredVersion == aStoredVersion ) // the user wanted to ignore this update + return false; + // TODO: else delete ignored entry? + } + if ( isVersionGreater( rVersion, aStoredVersion ) ) + return true; + else + { + m_xAvailableUpdates->removeByName( rExtensionName ); + commitChanges(); + } + } + + return false; +} + +OUString UpdateCheckConfig::getSubVersion( const OUString& rVersion, + sal_Int32 *nIndex ) +{ + while ( *nIndex < rVersion.getLength() && rVersion[*nIndex] == '0') + { + ++*nIndex; + } + + return rVersion.getToken( 0, '.', *nIndex ); +} + +/// checks if the second version string is greater than the first one +bool UpdateCheckConfig::isVersionGreater( const OUString& rVersion1, + const OUString& rVersion2 ) +{ + for ( sal_Int32 i1 = 0, i2 = 0; i1 >= 0 || i2 >= 0; ) + { + OUString sSub1( getSubVersion( rVersion1, &i1 ) ); + OUString sSub2( getSubVersion( rVersion2, &i2 ) ); + + if ( sSub1.getLength() < sSub2.getLength() ) { + return true; + } else if ( sSub1.getLength() > sSub2.getLength() ) { + return false; + } else if ( sSub1 < sSub2 ) { + return true; + } else if ( sSub1 > sSub2 ) { + return false; + } + } + return false; +} + +OUString SAL_CALL +UpdateCheckConfig::getImplementationName() +{ + return "vnd.sun.UpdateCheckConfig"; +} + +sal_Bool SAL_CALL +UpdateCheckConfig::supportsService(OUString const & serviceName) +{ + return cppu::supportsService(this, serviceName); +} + +uno::Sequence< OUString > SAL_CALL +UpdateCheckConfig::getSupportedServiceNames() +{ + return { "com.sun.star.setup.UpdateCheckConfig" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_update_UpdateCheckConfig_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(UpdateCheckConfig::get(context, *UpdateCheck::get()).get()); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updatecheckconfig.hxx b/extensions/source/update/check/updatecheckconfig.hxx new file mode 100644 index 0000000000..a9836c6248 --- /dev/null +++ b/extensions/source/update/check/updatecheckconfig.hxx @@ -0,0 +1,205 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> +#include <rtl/ref.hxx> + +#include "updatecheckconfiglistener.hxx" +#include "updateinfo.hxx" + +/* This helper class provides by name access to a sequence of named values */ +class NamedValueByNameAccess +{ + const css::uno::Sequence< css::beans::NamedValue >& m_rValues; + +public: + explicit NamedValueByNameAccess( + const css::uno::Sequence< css::beans::NamedValue >& rValues) : + m_rValues(rValues) {} ; + + css::uno::Any getValue(const char * pName); +}; + + +/* This class encapsulates the configuration item actually used for storing the state + * the update check is actually in. + */ +class UpdateCheckROModel +{ +public: + explicit UpdateCheckROModel(NamedValueByNameAccess& aNameAccess) : m_aNameAccess(aNameAccess) {}; + + bool isAutoCheckEnabled() const; + bool isDownloadPaused() const; + OUString getLocalFileName() const; + sal_Int64 getDownloadSize() const; + + OUString getUpdateEntryVersion() const; + void getUpdateEntry(UpdateInfo& rInfo) const; + +private: + + OUString getStringValue(const char *) const; + + NamedValueByNameAccess& m_aNameAccess; +}; + + +/* This class implements the non published UNO service com.sun.star.setup.UpdateCheckConfig, + * which primary use is to be able to track changes done in the Tools -> Options page of this + * component, as this is not supported by the OOo configuration for extendable groups. + */ + +class UpdateCheckConfig : public ::cppu::WeakImplHelper< + css::container::XNameReplace, + css::util::XChangesBatch, + css::lang::XServiceInfo > +{ + UpdateCheckConfig( const css::uno::Reference< css::container::XNameContainer >& xContainer, + const css::uno::Reference< css::container::XNameContainer >& xAvailableUpdates, + const css::uno::Reference< css::container::XNameContainer >& xIgnoredUpdates, + const ::rtl::Reference< UpdateCheckConfigListener >& rListener ); + + virtual ~UpdateCheckConfig() override; + +public: + + static ::rtl::Reference< UpdateCheckConfig > get( + const css::uno::Reference< css::uno::XComponentContext >& xContext, + const ::rtl::Reference< UpdateCheckConfigListener >& rListener = ::rtl::Reference< UpdateCheckConfigListener >()); + + // Should really implement ROModel... + bool isAutoCheckEnabled() const; + bool isAutoDownloadEnabled() const; + OUString getUpdateEntryVersion() const; + + /* Updates the timestamp of last check, but does not commit the change + * as either clearUpdateFound() or setUpdateFound() are expected to get + * called next. + */ + void updateLastChecked(); + + /* Returns the date of the last successful check in seconds since 1970 */ + sal_Int64 getLastChecked() const; + + /* Returns configured check interval in seconds */ + sal_Int64 getCheckInterval() const; + + /* Reset values of previously remembered update + */ + void clearUpdateFound(); + + /* Stores the specified data of an available update + */ + void storeUpdateFound(const UpdateInfo& rInfo, const OUString& aCurrentBuild); + + // Returns the local file name of a started download + OUString getLocalFileName() const; + + // Returns the local file name of a started download + OUString getDownloadDestination() const; + + // stores the local file name of a just started download + void storeLocalFileName(const OUString& rFileName, sal_Int64 nFileSize); + + // Removes the local file name of a download + void clearLocalFileName(); + + // Stores the bool value for manually paused downloads + void storeDownloadPaused(bool paused); + + // Returns the directory for downloaded files + static OUString getDownloadsDirectory(); + + // Returns a directory accessible for all users + static OUString getAllUsersDirectory(); + + // store and retrieve information about extensions + bool storeExtensionVersion( const OUString& rExtensionName, + const OUString& rVersion ); + bool checkExtensionVersion( const OUString& rExtensionName, + const OUString& rVersion ); + + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType( ) override; + virtual sal_Bool SAL_CALL hasElements( ) override; + + // XNameAccess + virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames( ) override; + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + + // XNameReplace + virtual void SAL_CALL replaceByName( const OUString& aName, const css::uno::Any& aElement ) override; + + // XChangesBatch + virtual void SAL_CALL commitChanges( ) override; + virtual sal_Bool SAL_CALL hasPendingChanges( ) override; + virtual css::uno::Sequence< css::util::ElementChange > SAL_CALL getPendingChanges( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(OUString const & serviceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + +private: + + static OUString getSubVersion( const OUString& rVersion, sal_Int32 *nIndex ); + static bool isVersionGreater( const OUString& rVersion1, const OUString& rVersion2 ); + + const css::uno::Reference< css::container::XNameContainer > m_xContainer; + const css::uno::Reference< css::container::XNameContainer > m_xAvailableUpdates; + const css::uno::Reference< css::container::XNameContainer > m_xIgnoredUpdates; + const ::rtl::Reference< UpdateCheckConfigListener > m_rListener; +}; + +/// @throws css::uno::RuntimeException +template <typename T> +T getValue( const css::uno::Sequence< css::beans::NamedValue >& rNamedValues, const char * pszName ) +{ + for( css::beans::NamedValue const & nv : rNamedValues ) + { + // Unfortunately gcc-3.3 does not like Any.get<T>(); + if( nv.Name.equalsAscii( pszName ) ) + { + T value = T(); + if( ! (nv.Value >>= value) ) + throw css::uno::RuntimeException( + OUString( + cppu_Any_extraction_failure_msg( + &nv.Value, + ::cppu::getTypeFavourUnsigned(&value).getTypeLibType() ), + SAL_NO_ACQUIRE ), + css::uno::Reference< css::uno::XInterface >() ); + + return value; + } + } + + return T(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updatecheckconfiglistener.hxx b/extensions/source/update/check/updatecheckconfiglistener.hxx new file mode 100644 index 0000000000..903200f68c --- /dev/null +++ b/extensions/source/update/check/updatecheckconfiglistener.hxx @@ -0,0 +1,37 @@ +/* -*- 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 . + */ + +#pragma once + +#include <salhelper/simplereferenceobject.hxx> + +/* This interface should be implemented by classes acting + * as controller (as in the MVC pattern). + */ + +struct UpdateCheckConfigListener : public virtual salhelper::SimpleReferenceObject +{ + virtual void autoCheckStatusChanged(bool enabled) = 0; + virtual void autoCheckIntervalChanged() = 0; + +protected: + virtual ~UpdateCheckConfigListener() override {} +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updatecheckjob.cxx b/extensions/source/update/check/updatecheckjob.cxx new file mode 100644 index 0000000000..b79c438108 --- /dev/null +++ b/extensions/source/update/check/updatecheckjob.cxx @@ -0,0 +1,336 @@ +/* -*- 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 "updatecheck.hxx" +#include "updatecheckconfig.hxx" +#include "updatehdl.hxx" +#include "updateprotocol.hxx" + +#include <memory> +#include <mutex> +#include <utility> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XTerminateListener.hpp> +#include <com/sun/star/task/XJob.hpp> + +namespace beans = com::sun::star::beans ; +namespace frame = com::sun::star::frame ; +namespace lang = com::sun::star::lang ; +namespace task = com::sun::star::task ; +namespace uno = com::sun::star::uno ; + +namespace +{ + +class InitUpdateCheckJobThread : public osl::Thread +{ +public: + InitUpdateCheckJobThread( const uno::Reference< uno::XComponentContext > &xContext, + const uno::Sequence< beans::NamedValue > &xParameters, + bool bShowDialog ); + + virtual void SAL_CALL run() override; + + void setTerminating(); + +private: + osl::Condition m_aCondition; + uno::Reference<uno::XComponentContext> m_xContext; + uno::Sequence<beans::NamedValue> m_xParameters; + bool m_bShowDialog; + bool m_bTerminating; + + std::mutex m_mutex; + rtl::Reference<UpdateCheck> m_controller; +}; + +class UpdateCheckJob : + public ::cppu::WeakImplHelper< task::XJob, lang::XServiceInfo, frame::XTerminateListener > +{ + virtual ~UpdateCheckJob() override; + +public: + + UpdateCheckJob( + css::uno::Reference<css::uno::XComponentContext> const & context, + css::uno::Reference<css::frame::XDesktop2> const & desktop): + m_xContext(context), m_xDesktop(desktop) + {} + + // XJob + virtual uno::Any SAL_CALL execute(const uno::Sequence<beans::NamedValue>&) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(OUString const & serviceName) override; + virtual uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XEventListener + virtual void SAL_CALL disposing( css::lang::EventObject const & evt ) override; + + // XTerminateListener + virtual void SAL_CALL queryTermination( lang::EventObject const & evt ) override; + virtual void SAL_CALL notifyTermination( lang::EventObject const & evt ) override; + +private: + uno::Reference<uno::XComponentContext> m_xContext; + + std::mutex m_mutex; + uno::Reference< frame::XDesktop2 > m_xDesktop; + std::unique_ptr< InitUpdateCheckJobThread > m_pInitThread; + + void handleExtensionUpdates( const uno::Sequence< beans::NamedValue > &rListProp ); + void terminateAndJoinThread(); +}; + +InitUpdateCheckJobThread::InitUpdateCheckJobThread( + const uno::Reference< uno::XComponentContext > &xContext, + const uno::Sequence< beans::NamedValue > &xParameters, + bool bShowDialog ) : + m_xContext( xContext ), + m_xParameters( xParameters ), + m_bShowDialog( bShowDialog ), + m_bTerminating( false ) +{ + create(); +} + + +void SAL_CALL InitUpdateCheckJobThread::run() +{ + osl_setThreadName("InitUpdateCheckJobThread"); + + if (!m_bShowDialog) { + TimeValue tv = { 25, 0 }; + m_aCondition.wait( &tv ); + if ( m_bTerminating ) + return; + } + + try { + rtl::Reference< UpdateCheck > aController( UpdateCheck::get() ); + // At least for the automatic ("onFirstVisibleTask", i.e., !m_bShowDialog) check, wait for + // m_controller during setTerminating, to prevent m_controller from still having threads + // running during exit (ideally, we would make sure that all threads are joined before exit, + // but the UpdateCheck logic is rather convoluted, so play it safe for now and only address + // the automatic update check that is known to cause issues during `make check`, not the + // manually triggered update check scenario): + if (!m_bShowDialog) { + std::scoped_lock l(m_mutex); + m_controller = aController; + } + aController->initialize( m_xParameters, m_xContext ); + + if ( m_bShowDialog ) + aController->showDialog( true ); + } catch (const uno::Exception &) { + // fdo#64962 - don't bring the app down on some unexpected exception. + TOOLS_WARN_EXCEPTION("extensions.update", "Caught init update exception, thread terminated" ); + { + std::scoped_lock l(m_mutex); + m_controller.clear(); + } + } +} + +void InitUpdateCheckJobThread::setTerminating() { + m_bTerminating = true; + m_aCondition.set(); + rtl::Reference<UpdateCheck> controller; + { + std::scoped_lock l(m_mutex); + std::swap(controller, m_controller); + } + if (controller.is()) { + controller->waitForUpdateCheckFinished(); + } +} + +UpdateCheckJob::~UpdateCheckJob() +{ +} + +uno::Any +UpdateCheckJob::execute(const uno::Sequence<beans::NamedValue>& namedValues) +{ + for ( sal_Int32 n=namedValues.getLength(); n-- > 0; ) + { + if ( namedValues[ n ].Name == "DynamicData" ) + { + uno::Sequence<beans::NamedValue> aListProp; + if ( namedValues[n].Value >>= aListProp ) + { + for ( sal_Int32 i=aListProp.getLength(); i-- > 0; ) + { + if ( aListProp[ i ].Name == "updateList" ) + { + handleExtensionUpdates( aListProp ); + return uno::Any(); + } + } + } + } + } + + uno::Sequence<beans::NamedValue> aConfig = + getValue< uno::Sequence<beans::NamedValue> > (namedValues, "JobConfig"); + + /* Determine the way we got invoked here - + * see Developers Guide Chapter "4.7.2 Jobs" to understand the magic + */ + + uno::Sequence<beans::NamedValue> aEnvironment = + getValue< uno::Sequence<beans::NamedValue> > (namedValues, "Environment"); + + OUString aEventName = getValue< OUString > (aEnvironment, "EventName"); + + auto thread = std::make_unique<InitUpdateCheckJobThread >( + m_xContext, aConfig, + aEventName != "onFirstVisibleTask"); + { + std::scoped_lock l(m_mutex); + m_pInitThread = std::move(thread); + } + + return uno::Any(); +} + + +void UpdateCheckJob::handleExtensionUpdates( const uno::Sequence< beans::NamedValue > &rListProp ) +{ + try { + uno::Sequence< uno::Sequence< OUString > > aList = + getValue< uno::Sequence< uno::Sequence< OUString > > > ( rListProp, "updateList" ); + bool bPrepareOnly = getValue< bool > ( rListProp, "prepareOnly" ); + + // we will first store any new found updates and then check, if there are any + // pending updates. + storeExtensionUpdateInfos( m_xContext, aList ); + + if ( bPrepareOnly ) + return; + + bool bHasUpdates = checkForPendingUpdates( m_xContext ); + + rtl::Reference<UpdateCheck> aController( UpdateCheck::get() ); + if ( ! aController.is() ) + return; + + aController->setHasExtensionUpdates( bHasUpdates ); + + if ( ! aController->hasOfficeUpdate() ) + { + if ( bHasUpdates ) + aController->setUIState( UPDATESTATE_EXT_UPD_AVAIL, true ); + else + aController->setUIState( UPDATESTATE_NO_UPDATE_AVAIL, true ); + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("extensions.update", "Caught exception, thread terminated"); + } +} + + +OUString SAL_CALL +UpdateCheckJob::getImplementationName() +{ + return "vnd.sun.UpdateCheck"; +} + + +uno::Sequence< OUString > SAL_CALL +UpdateCheckJob::getSupportedServiceNames() +{ + return { "com.sun.star.setup.UpdateCheck" }; +} + +sal_Bool SAL_CALL +UpdateCheckJob::supportsService( OUString const & serviceName ) +{ + return cppu::supportsService(this, serviceName); +} + + +// XEventListener +void SAL_CALL UpdateCheckJob::disposing( lang::EventObject const & rEvt ) +{ + css::uno::Reference<css::frame::XDesktop2> desktop; + { + std::scoped_lock l(m_mutex); + if ( rEvt.Source == m_xDesktop ) { + std::swap(m_xDesktop, desktop); + } + } + + if ( desktop.is() ) + { + terminateAndJoinThread(); + desktop->removeTerminateListener( this ); + } +} + + +// XTerminateListener +void SAL_CALL UpdateCheckJob::queryTermination( lang::EventObject const & ) +{ +} + +void UpdateCheckJob::terminateAndJoinThread() +{ + std::unique_ptr<InitUpdateCheckJobThread> thread; + { + std::scoped_lock l(m_mutex); + std::swap(m_pInitThread, thread); + } + if (thread != nullptr) + { + thread->setTerminating(); + thread->join(); + } +} + +void SAL_CALL UpdateCheckJob::notifyTermination( lang::EventObject const & ) +{ + terminateAndJoinThread(); +} + +} // anonymous namespace + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_update_UpdateCheckJob_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + css::uno::Reference<css::frame::XDesktop2> desktop( + css::frame::Desktop::create(context)); + rtl::Reference<UpdateCheckJob> job(new UpdateCheckJob(context, desktop)); + desktop->addTerminateListener(job); + return cppu::acquire(job.get()); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updatehdl.cxx b/extensions/source/update/check/updatehdl.cxx new file mode 100644 index 0000000000..299a4f215f --- /dev/null +++ b/extensions/source/update/check/updatehdl.cxx @@ -0,0 +1,1249 @@ +/* -*- 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 <cstddef> + +#include "updatehdl.hxx" +#include <helpids.h> + +#include <osl/diagnose.h> +#include <osl/file.hxx> +#include <rtl/ustring.hxx> + +#include <com/sun/star/uno/Sequence.h> + +#include <com/sun/star/awt/ActionEvent.hpp> +#include <com/sun/star/awt/PushButtonType.hpp> +#include <com/sun/star/awt/UnoControlDialog.hpp> +#include <com/sun/star/awt/VclWindowPeerAttribute.hpp> +#include <com/sun/star/awt/WindowAttribute.hpp> +#include <com/sun/star/awt/XButton.hpp> +#include <com/sun/star/awt/XControl.hpp> +#include <com/sun/star/awt/XControlContainer.hpp> +#include <com/sun/star/awt/XMessageBox.hpp> +#include <com/sun/star/awt/XAnimation.hpp> +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/awt/XVclWindowPeer.hpp> +#include <com/sun/star/awt/XVclContainer.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/awt/XWindow2.hpp> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + +#include <com/sun/star/configuration/theDefaultProvider.hpp> + +#include <com/sun/star/container/XNameContainer.hpp> + +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/TerminationVetoException.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/task/InteractionRequestStringResolver.hpp> + +#include <strings.hrc> +#include <unotools/resmgr.hxx> +#include <tools/urlobj.hxx> +#include <comphelper/diagnose_ex.hxx> + +constexpr OUString COMMAND_CLOSE = u"close"_ustr; + +constexpr OUString CTRL_THROBBER = u"throbber"_ustr; +constexpr OUString CTRL_PROGRESS = u"progress"_ustr; + +constexpr OUString TEXT_STATUS = u"text_status"_ustr; +constexpr OUString TEXT_PERCENT = u"text_percent"_ustr; +constexpr OUString TEXT_DESCRIPTION = u"text_description"_ustr; + +constexpr OUStringLiteral FIXED_LINE_MODEL = u"com.sun.star.awt.UnoControlFixedLineModel"; +constexpr OUString FIXED_TEXT_MODEL = u"com.sun.star.awt.UnoControlFixedTextModel"_ustr; +constexpr OUString EDIT_FIELD_MODEL = u"com.sun.star.awt.UnoControlEditModel"_ustr; +constexpr OUString BUTTON_MODEL = u"com.sun.star.awt.UnoControlButtonModel"_ustr; +constexpr OUString GROUP_BOX_MODEL = u"com.sun.star.awt.UnoControlGroupBoxModel"_ustr; + +using namespace com::sun::star; + + +UpdateHandler::UpdateHandler( const uno::Reference< uno::XComponentContext > & rxContext, + const rtl::Reference< IActionListener > & rxActionListener ) : + mxContext( rxContext ), + mxActionListener( rxActionListener ), + meCurState( UPDATESTATES_COUNT ), + meLastState( UPDATESTATES_COUNT ), + mnPercent( 0 ), + mnLastCtrlState( -1 ), + mbDownloadBtnHasDots( false ), + mbVisible( false ), + mbStringsLoaded( false ), + mbMinimized( false ), + mbListenerAdded(false), + mbShowsMessageBox(false) +{ +} + + +UpdateHandler::~UpdateHandler() +{ + mxContext = nullptr; + mxUpdDlg = nullptr; + mxInteractionHdl = nullptr; + mxActionListener = nullptr; +} + + +void UpdateHandler::enableControls( short nCtrlState ) +{ + osl::MutexGuard aGuard( maMutex ); + + if ( nCtrlState == mnLastCtrlState ) + return; + + // the help button should always be the last button in the + // enum list and must never be disabled + for ( int i=0; i<HELP_BUTTON; i++ ) + { + short nCurStateVal = static_cast<short>(nCtrlState >> i); + short nOldStateVal = static_cast<short>(mnLastCtrlState >> i); + if ( ( nCurStateVal & 0x01 ) != ( nOldStateVal & 0x01 ) ) + { + bool bEnableControl = ( ( nCurStateVal & 0x01 ) == 0x01 ); + setControlProperty( msButtonIDs[i], "Enabled", uno::Any( bEnableControl ) ); + } + } + + mnLastCtrlState = nCtrlState; +} + + +void UpdateHandler::setDownloadBtnLabel( bool bAppendDots ) +{ + osl::MutexGuard aGuard( maMutex ); + + if ( mbDownloadBtnHasDots != bAppendDots ) + { + OUString aLabel( msDownload ); + + if ( bAppendDots ) + aLabel += "..."; + + setControlProperty( msButtonIDs[DOWNLOAD_BUTTON], "Label", uno::Any( aLabel ) ); + setControlProperty( msButtonIDs[DOWNLOAD_BUTTON], "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_DOWNLOAD2 )) ); + + mbDownloadBtnHasDots = bAppendDots; + } +} + + +void UpdateHandler::setState( UpdateState eState ) +{ + osl::MutexGuard aGuard( maMutex ); + + meCurState = eState; + + if ( mxUpdDlg.is() && mbVisible ) + updateState( meCurState ); +} + + +bool UpdateHandler::isVisible() const +{ + if ( !mxUpdDlg.is() ) return false; + + uno::Reference< awt::XWindow2 > xWindow( mxUpdDlg, uno::UNO_QUERY ); + + if ( xWindow.is() ) + return xWindow->isVisible(); + else + return false; +} + + +void UpdateHandler::setVisible( bool bVisible ) +{ + osl::MutexGuard aGuard( maMutex ); + + mbVisible = bVisible; + + if ( bVisible ) + { + if ( !mxUpdDlg.is() ) + createDialog(); + + // this should never happen, but if it happens we better return here + if ( !mxUpdDlg.is() ) + return; + + updateState( meCurState ); + + uno::Reference< awt::XWindow > xWindow( mxUpdDlg, uno::UNO_QUERY ); + + if ( xWindow.is() ) + xWindow->setVisible( bVisible ); + + uno::Reference< awt::XTopWindow > xTopWindow( mxUpdDlg, uno::UNO_QUERY ); + if ( xTopWindow.is() ) + { + xTopWindow->toFront(); + if ( !mbListenerAdded ) + { + xTopWindow->addTopWindowListener( this ); + mbListenerAdded = true; + } + } + } + else if ( mxUpdDlg.is() ) + { + uno::Reference< awt::XWindow > xWindow( mxUpdDlg, uno::UNO_QUERY ); + + if ( xWindow.is() ) + xWindow->setVisible( bVisible ); + } +} + + +void UpdateHandler::setProgress( sal_Int32 nPercent ) +{ + if ( nPercent > 100 ) + nPercent = 100; + else if ( nPercent < 0 ) + nPercent = 0; + + if ( nPercent != mnPercent ) + { + osl::MutexGuard aGuard( maMutex ); + + mnPercent = nPercent; + setControlProperty( CTRL_PROGRESS, "ProgressValue", uno::Any( nPercent ) ); + setControlProperty( TEXT_PERCENT, "Text", uno::Any( substVariables(msPercent) ) ); + } +} + + +void UpdateHandler::setErrorMessage( const OUString& rErrorMsg ) +{ + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( rErrorMsg ) ); +} + + +void UpdateHandler::setDownloadFile( std::u16string_view rFilePath ) +{ + std::size_t nLast = rFilePath.rfind( '/' ); + if ( nLast != std::u16string_view::npos ) + { + msDownloadFile = rFilePath.substr( nLast+1 ); + const OUString aDownloadURL(rFilePath.substr( 0, nLast )); + osl::FileBase::getSystemPathFromFileURL( aDownloadURL, msDownloadPath ); + } +} + + +OUString UpdateHandler::getBubbleText( UpdateState eState ) +{ + osl::MutexGuard aGuard( maMutex ); + + OUString sText; + sal_Int32 nIndex = static_cast<sal_Int32>(eState); + + loadStrings(); + + if ( ( UPDATESTATE_UPDATE_AVAIL <= nIndex ) && ( nIndex < UPDATESTATES_COUNT ) ) + sText = substVariables( msBubbleTexts[ nIndex - UPDATESTATE_UPDATE_AVAIL ] ); + + return sText; +} + + +OUString UpdateHandler::getBubbleTitle( UpdateState eState ) +{ + osl::MutexGuard aGuard( maMutex ); + + OUString sText; + sal_Int32 nIndex = static_cast<sal_Int32>(eState); + + loadStrings(); + + if ( ( UPDATESTATE_UPDATE_AVAIL <= nIndex ) && ( nIndex < UPDATESTATES_COUNT ) ) + sText = substVariables( msBubbleTitles[ nIndex - UPDATESTATE_UPDATE_AVAIL] ); + + return sText; +} + + +// XActionListener + +void SAL_CALL UpdateHandler::disposing( const lang::EventObject& rEvt ) +{ + if ( rEvt.Source == mxUpdDlg ) + mxUpdDlg.clear(); +} + + +void SAL_CALL UpdateHandler::actionPerformed( awt::ActionEvent const & rEvent ) +{ + DialogControls eButton = BUTTON_COUNT; + for ( int i = 0; i < BUTTON_COUNT; i++ ) + { + if ( rEvent.ActionCommand == msButtonIDs[i] ) + { + eButton = static_cast<DialogControls>(i); + break; + } + } + + if ( rEvent.ActionCommand == COMMAND_CLOSE ) + { + if ( ( mnLastCtrlState & ( 1 << CLOSE_BUTTON ) ) == ( 1 << CLOSE_BUTTON ) ) + eButton = CLOSE_BUTTON; + else + eButton = CANCEL_BUTTON; + } + + switch ( eButton ) { + case CANCEL_BUTTON: + { + bool bCancel = true; + + if ( ( meCurState == UPDATESTATE_DOWNLOADING ) || + ( meCurState == UPDATESTATE_DOWNLOAD_PAUSED ) || + ( meCurState == UPDATESTATE_ERROR_DOWNLOADING ) ) + bCancel = showWarning( msCancelMessage ); + + if ( bCancel ) + { + mxActionListener->cancel(); + setVisible( false ); + } + break; + } + case CLOSE_BUTTON: + setVisible( false ); + if ( meCurState == UPDATESTATE_ERROR_CHECKING ) + mxActionListener->closeAfterFailure(); + break; + case DOWNLOAD_BUTTON: + mxActionListener->download(); + break; + case PAUSE_BUTTON: + mxActionListener->pause(); + break; + case RESUME_BUTTON: + mxActionListener->resume(); + break; + case HELP_BUTTON: + break; + default: + OSL_FAIL( "UpdateHandler::actionPerformed: unknown command!" ); + } +} + +// XTopWindowListener + +void SAL_CALL UpdateHandler::windowOpened( const lang::EventObject& ) +{ +} + + +void SAL_CALL UpdateHandler::windowClosing( const lang::EventObject& e ) +{ + awt::ActionEvent aActionEvt; + aActionEvt.ActionCommand = COMMAND_CLOSE; + aActionEvt.Source = e.Source; + + actionPerformed( aActionEvt ); +} + + +void SAL_CALL UpdateHandler::windowClosed( const lang::EventObject& ) +{ +} + + +void SAL_CALL UpdateHandler::windowMinimized( const lang::EventObject& ) +{ + mbMinimized = true; +} + + +void SAL_CALL UpdateHandler::windowNormalized( const lang::EventObject& ) +{ + mbMinimized = false; +} + + +void SAL_CALL UpdateHandler::windowActivated( const lang::EventObject& ) +{ +} + + +void SAL_CALL UpdateHandler::windowDeactivated( const lang::EventObject& ) +{ +} + +// XInteractionHandler + +void SAL_CALL UpdateHandler::handle( uno::Reference< task::XInteractionRequest > const & rRequest) +{ + if ( !mxInteractionHdl.is() ) + { + if( !mxContext.is() ) + throw uno::RuntimeException( "UpdateHandler:: empty component context", *this ); + + uno::Reference< lang::XMultiComponentFactory > xServiceManager(mxContext->getServiceManager()); + + if( !xServiceManager.is() ) + throw uno::RuntimeException( "UpdateHandler: unable to obtain service manager from component context", *this ); + + mxInteractionHdl.set( + task::InteractionHandler::createWithParent(mxContext, nullptr), + uno::UNO_QUERY_THROW); + } + uno::Reference< task::XInteractionRequestStringResolver > xStrResolver = + task::InteractionRequestStringResolver::create( mxContext ); + beans::Optional< OUString > aErrorText = xStrResolver->getStringFromInformationalRequest( rRequest ); + if ( aErrorText.IsPresent ) + { + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( aErrorText.Value ) ); + + uno::Sequence< uno::Reference< task::XInteractionContinuation > > xContinuations = rRequest->getContinuations(); + if ( xContinuations.getLength() == 1 ) + { + if ( meCurState == UPDATESTATE_CHECKING ) + setState( UPDATESTATE_ERROR_CHECKING ); + else if ( meCurState == UPDATESTATE_DOWNLOADING ) + setState( UPDATESTATE_ERROR_DOWNLOADING ); + + xContinuations[0]->select(); + } + else + mxInteractionHdl->handle( rRequest ); + } + else + mxInteractionHdl->handle( rRequest ); +} + + +// XTerminateListener + +void SAL_CALL UpdateHandler::queryTermination( const lang::EventObject& ) +{ + if ( mbShowsMessageBox ) + { + uno::Reference< awt::XTopWindow > xTopWindow( mxUpdDlg, uno::UNO_QUERY ); + if ( xTopWindow.is() ) + xTopWindow->toFront(); + + throw frame::TerminationVetoException( + "The office cannot be closed while displaying a warning!", + static_cast<frame::XTerminateListener*>(this)); + } + else + setVisible( false ); +} + + +void SAL_CALL UpdateHandler::notifyTermination( const lang::EventObject& ) +{ + osl::MutexGuard aGuard( maMutex ); + + if ( mxUpdDlg.is() ) + { + uno::Reference< awt::XTopWindow > xTopWindow( mxUpdDlg, uno::UNO_QUERY ); + if ( xTopWindow.is() ) + xTopWindow->removeTopWindowListener( this ); + + uno::Reference< lang::XComponent > xComponent( mxUpdDlg, uno::UNO_QUERY ); + if ( xComponent.is() ) + xComponent->dispose(); + + mxUpdDlg.clear(); + } +} + + +void UpdateHandler::updateState( UpdateState eState ) +{ + if ( meLastState == eState ) + return; + + OUString sText; + + switch ( eState ) + { + case UPDATESTATE_CHECKING: + showControls( (1<<CANCEL_BUTTON) + (1<<THROBBER_CTRL) ); + enableControls( 1<<CANCEL_BUTTON ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msChecking) ) ); + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( OUString() ) ); + focusControl( CANCEL_BUTTON ); + break; + case UPDATESTATE_ERROR_CHECKING: + showControls( 0 ); + enableControls( 1 << CLOSE_BUTTON ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msCheckingError) ) ); + focusControl( CLOSE_BUTTON ); + break; + case UPDATESTATE_UPDATE_AVAIL: + showControls( 0 ); + enableControls( ( 1 << CLOSE_BUTTON ) + ( 1 << DOWNLOAD_BUTTON ) ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msUpdFound) ) ); + + sText = substVariables(msDownloadWarning); + if ( !msDescriptionMsg.isEmpty() ) + sText += "\n\n" + msDescriptionMsg; + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( sText ) ); + + setDownloadBtnLabel( false ); + focusControl( DOWNLOAD_BUTTON ); + break; + case UPDATESTATE_UPDATE_NO_DOWNLOAD: + showControls( 0 ); + enableControls( ( 1 << CLOSE_BUTTON ) + ( 1 << DOWNLOAD_BUTTON ) ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msUpdFound) ) ); + + sText = substVariables(msDownloadNotAvail); + if ( !msDescriptionMsg.isEmpty() ) + sText += "\n\n" + msDescriptionMsg; + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( sText ) ); + + setDownloadBtnLabel( true ); + focusControl( DOWNLOAD_BUTTON ); + break; + case UPDATESTATE_NO_UPDATE_AVAIL: + case UPDATESTATE_EXT_UPD_AVAIL: // will only be set, when there are no office updates avail + showControls( 0 ); + enableControls( 1 << CLOSE_BUTTON ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msNoUpdFound) ) ); + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( OUString() ) ); + focusControl( CLOSE_BUTTON ); + break; + case UPDATESTATE_DOWNLOADING: + showControls( (1<<PROGRESS_CTRL) + (1<<CANCEL_BUTTON) + (1<<PAUSE_BUTTON) + (1<<RESUME_BUTTON) ); + enableControls( (1<<CLOSE_BUTTON) + (1<<CANCEL_BUTTON) + (1<<PAUSE_BUTTON) ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msDownloading) ) ); + setControlProperty( TEXT_PERCENT, "Text", uno::Any( substVariables(msPercent) ) ); + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( substVariables(msDownloadWarning) ) ); + setControlProperty( CTRL_PROGRESS, "ProgressValue", uno::Any( mnPercent ) ); + focusControl( CLOSE_BUTTON ); + break; + case UPDATESTATE_DOWNLOAD_PAUSED: + showControls( (1<<PROGRESS_CTRL) + (1<<CANCEL_BUTTON) + (1<<PAUSE_BUTTON) + (1<<RESUME_BUTTON) ); + enableControls( (1<<CLOSE_BUTTON) + (1<<CANCEL_BUTTON) + (1<<RESUME_BUTTON) ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msDownloadPause) ) ); + setControlProperty( TEXT_PERCENT, "Text", uno::Any( substVariables(msPercent) ) ); + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( substVariables(msDownloadWarning) ) ); + setControlProperty( CTRL_PROGRESS, "ProgressValue", uno::Any( mnPercent ) ); + focusControl( CLOSE_BUTTON ); + break; + case UPDATESTATE_ERROR_DOWNLOADING: + showControls( (1<<PROGRESS_CTRL) + (1<<CANCEL_BUTTON) + (1<<PAUSE_BUTTON) + (1<<RESUME_BUTTON) ); + enableControls( (1<<CLOSE_BUTTON) + (1<<CANCEL_BUTTON) ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msDownloadError) ) ); + focusControl( CLOSE_BUTTON ); + break; + case UPDATESTATE_DOWNLOAD_AVAIL: + showControls( 0 ); + setControlProperty( TEXT_STATUS, "Text", uno::Any( substVariables(msReady2Install) ) ); + setControlProperty( TEXT_DESCRIPTION, "Text", uno::Any( substVariables(msDownloadDescr) ) ); + break; + case UPDATESTATE_AUTO_START: + case UPDATESTATES_COUNT: + //do nothing, only count! + break; + } + + meLastState = eState; +} + +OUString UpdateHandler::loadString(const std::locale& rLocale, + TranslateId pResourceId) +{ + return Translate::get(pResourceId, rLocale); +} + +OUString UpdateHandler::substVariables( const OUString &rSource ) const +{ + return rSource + .replaceAll( "%NEXTVERSION", msNextVersion ) + .replaceAll( "%DOWNLOAD_PATH", msDownloadPath ) + .replaceAll( "%FILE_NAME", msDownloadFile ) + .replaceAll( "%PERCENT", OUString::number( mnPercent ) ); +} + +void UpdateHandler::loadStrings() +{ + if ( mbStringsLoaded ) + return; + else + mbStringsLoaded = true; + + std::locale loc = Translate::Create("pcr"); + + msChecking = loadString( loc, RID_UPDATE_STR_CHECKING ); + msCheckingError = loadString( loc, RID_UPDATE_STR_CHECKING_ERR ); + msNoUpdFound = loadString( loc, RID_UPDATE_STR_NO_UPD_FOUND ); + + msUpdFound = loadString( loc, RID_UPDATE_STR_UPD_FOUND ); + setFullVersion( msUpdFound ); + + msDlgTitle = loadString( loc, RID_UPDATE_STR_DLG_TITLE ); + msDownloadPause = loadString( loc, RID_UPDATE_STR_DOWNLOAD_PAUSE ); + msDownloadError = loadString( loc, RID_UPDATE_STR_DOWNLOAD_ERR ); + msDownloadWarning = loadString( loc, RID_UPDATE_STR_DOWNLOAD_WARN ); + msDownloadDescr = loadString( loc, RID_UPDATE_STR_DOWNLOAD_DESCR ); + msDownloadNotAvail = loadString( loc, RID_UPDATE_STR_DOWNLOAD_UNAVAIL ); + msDownloading = loadString( loc, RID_UPDATE_STR_DOWNLOADING ); + msReady2Install = loadString( loc, RID_UPDATE_STR_READY_INSTALL ); + msCancelMessage = loadString( loc, RID_UPDATE_STR_CANCEL_DOWNLOAD ); + msOverwriteWarning = loadString( loc, RID_UPDATE_STR_OVERWRITE_WARNING ); + msPercent = loadString( loc, RID_UPDATE_STR_PERCENT ); + msReloadWarning = loadString( loc, RID_UPDATE_STR_RELOAD_WARNING ); + msReloadReload = loadString( loc, RID_UPDATE_STR_RELOAD_RELOAD ); + msReloadContinue = loadString( loc, RID_UPDATE_STR_RELOAD_CONTINUE ); + + msStatusFL = loadString( loc, RID_UPDATE_FT_STATUS ); + msDescription = loadString( loc, RID_UPDATE_FT_DESCRIPTION ); + + msClose = loadString( loc, RID_UPDATE_BTN_CLOSE ); + msDownload = loadString( loc, RID_UPDATE_BTN_DOWNLOAD ); + msPauseBtn = loadString( loc, RID_UPDATE_BTN_PAUSE ); + msResumeBtn = loadString( loc, RID_UPDATE_BTN_RESUME ); + msCancelBtn = loadString( loc, RID_UPDATE_BTN_CANCEL ); + + std::pair<TranslateId, TranslateId> RID_UPDATE_BUBBLE[] = + { + { RID_UPDATE_BUBBLE_UPDATE_AVAIL, RID_UPDATE_BUBBLE_T_UPDATE_AVAIL }, + { RID_UPDATE_BUBBLE_UPDATE_NO_DOWN, RID_UPDATE_BUBBLE_T_UPDATE_NO_DOWN }, + { RID_UPDATE_BUBBLE_AUTO_START, RID_UPDATE_BUBBLE_T_AUTO_START }, + { RID_UPDATE_BUBBLE_DOWNLOADING, RID_UPDATE_BUBBLE_T_DOWNLOADING }, + { RID_UPDATE_BUBBLE_DOWNLOAD_PAUSED, RID_UPDATE_BUBBLE_T_DOWNLOAD_PAUSED }, + { RID_UPDATE_BUBBLE_ERROR_DOWNLOADING, RID_UPDATE_BUBBLE_T_ERROR_DOWNLOADING }, + { RID_UPDATE_BUBBLE_DOWNLOAD_AVAIL, RID_UPDATE_BUBBLE_T_DOWNLOAD_AVAIL }, + { RID_UPDATE_BUBBLE_EXT_UPD_AVAIL, RID_UPDATE_BUBBLE_T_EXT_UPD_AVAIL } + }; + + static_assert(SAL_N_ELEMENTS(RID_UPDATE_BUBBLE) == UPDATESTATES_COUNT - UPDATESTATE_UPDATE_AVAIL, "mismatch"); + + // all update states before UPDATESTATE_UPDATE_AVAIL don't have a bubble + // so we can ignore them + for (size_t i = 0; i < SAL_N_ELEMENTS(RID_UPDATE_BUBBLE); ++i) + { + msBubbleTexts[i] = loadString(loc, RID_UPDATE_BUBBLE[i].first); + msBubbleTitles[i] = loadString(loc, RID_UPDATE_BUBBLE[i].second); + } + + for ( int i=0; i < BUTTON_COUNT; i++ ) + { + msButtonIDs[ i ] = "BUTTON_" + OUString::number( i ); + } +} + + +void UpdateHandler::startThrobber( bool bStart ) +{ + uno::Reference< awt::XControlContainer > xContainer( mxUpdDlg, uno::UNO_QUERY ); + uno::Reference< awt::XAnimation > xThrobber( xContainer->getControl( CTRL_THROBBER ), uno::UNO_QUERY ); + + if ( xThrobber.is() ) + { + if ( bStart ) + xThrobber->startAnimation(); + else + xThrobber->stopAnimation(); + } + + uno::Reference< awt::XWindow > xWindow( xContainer->getControl( CTRL_THROBBER ), uno::UNO_QUERY ); + if (xWindow.is() ) + xWindow->setVisible( bStart ); +} + + +void UpdateHandler::setControlProperty( const OUString &rCtrlName, + const OUString &rPropName, + const uno::Any &rPropValue ) +{ + if ( !mxUpdDlg.is() ) return; + + uno::Reference< awt::XControlContainer > xContainer( mxUpdDlg, uno::UNO_QUERY ); + uno::Reference< awt::XControl > xControl( xContainer->getControl( rCtrlName ), uno::UNO_SET_THROW ); + uno::Reference< awt::XControlModel > xControlModel( xControl->getModel(), uno::UNO_SET_THROW ); + uno::Reference< beans::XPropertySet > xPropSet( xControlModel, uno::UNO_QUERY_THROW ); + + try { + xPropSet->setPropertyValue( rPropName, rPropValue ); + } + catch( const beans::UnknownPropertyException& ) + { + TOOLS_WARN_EXCEPTION( "extensions.update", "UpdateHandler::setControlProperty" ); + } +} + + +void UpdateHandler::showControl( const OUString &rCtrlName, bool bShow ) +{ + uno::Reference< awt::XControlContainer > xContainer( mxUpdDlg, uno::UNO_QUERY ); + + if ( !xContainer.is() ) + { + OSL_FAIL( "UpdateHandler::showControl: could not get control container!" ); + return; + } + + uno::Reference< awt::XWindow > xWindow( xContainer->getControl( rCtrlName ), uno::UNO_QUERY ); + if ( xWindow.is() ) + xWindow->setVisible( bShow ); +} + + +void UpdateHandler::focusControl( DialogControls eID ) +{ + uno::Reference< awt::XControlContainer > xContainer( mxUpdDlg, uno::UNO_QUERY ); + + if ( !xContainer.is() ) + { + OSL_FAIL( "UpdateHandler::focusControl: could not get control container!" ); + return; + } + + OSL_ENSURE( (eID < BUTTON_COUNT), "UpdateHandler::focusControl: id too big!" ); + + uno::Reference< awt::XWindow > xWindow( xContainer->getControl( msButtonIDs[static_cast<short>(eID)] ), uno::UNO_QUERY ); + if ( xWindow.is() ) + xWindow->setFocus(); +} + + +void UpdateHandler::insertControlModel( uno::Reference< awt::XControlModel > const & rxDialogModel, + OUString const & rServiceName, + OUString const & rControlName, + awt::Rectangle const & rPosSize, + uno::Sequence< beans::NamedValue > const & rProps ) +{ + uno::Reference< lang::XMultiServiceFactory > xFactory (rxDialogModel, uno::UNO_QUERY_THROW); + uno::Reference< awt::XControlModel > xModel (xFactory->createInstance (rServiceName), uno::UNO_QUERY_THROW); + uno::Reference< beans::XPropertySet > xPropSet (xModel, uno::UNO_QUERY_THROW); + + for (beans::NamedValue const & prop : rProps) + { + xPropSet->setPropertyValue (prop.Name, prop.Value); + } + + // @see awt/UnoControlDialogElement.idl + xPropSet->setPropertyValue( "Name", uno::Any (rControlName) ); + xPropSet->setPropertyValue( "PositionX", uno::Any (rPosSize.X) ); + xPropSet->setPropertyValue( "PositionY", uno::Any (rPosSize.Y) ); + xPropSet->setPropertyValue( "Height", uno::Any (rPosSize.Height) ); + xPropSet->setPropertyValue( "Width", uno::Any (rPosSize.Width) ); + + // insert by Name into DialogModel container + uno::Reference< container::XNameContainer > xContainer (rxDialogModel, uno::UNO_QUERY_THROW); + xContainer->insertByName( rControlName, uno::Any (uno::Reference< uno::XInterface >(xModel, uno::UNO_QUERY))); +} + + +void UpdateHandler::setFullVersion( OUString& rString ) +{ + uno::Reference< lang::XMultiServiceFactory > xConfigurationProvider( + css::configuration::theDefaultProvider::get( mxContext ) ); + + beans::PropertyValue aProperty; + aProperty.Name = "nodepath"; + aProperty.Value <<= OUString("org.openoffice.Setup/Product"); + + uno::Sequence< uno::Any > aArgumentList{ uno::Any(aProperty) }; + + uno::Reference< uno::XInterface > xConfigAccess = xConfigurationProvider->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationAccess", + aArgumentList ); + + uno::Reference< container::XNameAccess > xNameAccess( xConfigAccess, uno::UNO_QUERY_THROW ); + + OUString aProductVersion; + xNameAccess->getByName("ooSetupVersion") >>= aProductVersion; + OUString aProductFullVersion; + xNameAccess->getByName("ooSetupVersionAboutBox") >>= aProductFullVersion; + rString = rString.replaceFirst( aProductVersion, aProductFullVersion ); +} + + +bool UpdateHandler::showWarning( const OUString &rWarningText ) const +{ + bool bRet = false; + + uno::Reference< awt::XControl > xControl( mxUpdDlg, uno::UNO_QUERY ); + if ( !xControl.is() ) return bRet; + + uno::Reference< awt::XWindowPeer > xPeer = xControl->getPeer(); + if ( !xPeer.is() ) return bRet; + + uno::Reference< awt::XToolkit > xToolkit = xPeer->getToolkit(); + if ( !xToolkit.is() ) return bRet; + + awt::WindowDescriptor aDescriptor; + + sal_Int32 nWindowAttributes = awt::WindowAttribute::BORDER | awt::WindowAttribute::MOVEABLE | awt::WindowAttribute::CLOSEABLE; + nWindowAttributes |= awt::VclWindowPeerAttribute::YES_NO; + nWindowAttributes |= awt::VclWindowPeerAttribute::DEF_NO; + + aDescriptor.Type = awt::WindowClass_MODALTOP; + aDescriptor.WindowServiceName = "warningbox"; + aDescriptor.ParentIndex = -1; + aDescriptor.Parent = xPeer; + aDescriptor.Bounds = awt::Rectangle( 10, 10, 250, 150 ); + aDescriptor.WindowAttributes = nWindowAttributes; + + uno::Reference< awt::XMessageBox > xMsgBox( xToolkit->createWindow( aDescriptor ), uno::UNO_QUERY ); + if ( xMsgBox.is() ) + { + mbShowsMessageBox = true; + sal_Int16 nRet; + // xMsgBox->setCaptionText( msCancelTitle ); + xMsgBox->setMessageText( rWarningText ); + nRet = xMsgBox->execute(); + if ( nRet == 2 ) // RET_YES == 2 + bRet = true; + mbShowsMessageBox = false; + } + + uno::Reference< lang::XComponent > xComponent( xMsgBox, uno::UNO_QUERY ); + if ( xComponent.is() ) + xComponent->dispose(); + + return bRet; +} + + +bool UpdateHandler::showWarning( const OUString &rWarningText, + const OUString &rBtnText_1, + const OUString &rBtnText_2 ) const +{ + bool bRet = false; + + uno::Reference< awt::XControl > xControl( mxUpdDlg, uno::UNO_QUERY ); + if ( !xControl.is() ) return bRet; + + uno::Reference< awt::XWindowPeer > xPeer = xControl->getPeer(); + if ( !xPeer.is() ) return bRet; + + uno::Reference< awt::XToolkit > xToolkit = xPeer->getToolkit(); + if ( !xToolkit.is() ) return bRet; + + awt::WindowDescriptor aDescriptor; + + sal_Int32 nWindowAttributes = awt::WindowAttribute::BORDER | awt::WindowAttribute::MOVEABLE | awt::WindowAttribute::CLOSEABLE; + nWindowAttributes |= awt::VclWindowPeerAttribute::YES_NO; + nWindowAttributes |= awt::VclWindowPeerAttribute::DEF_NO; + + aDescriptor.Type = awt::WindowClass_MODALTOP; + aDescriptor.WindowServiceName = "warningbox"; + aDescriptor.ParentIndex = -1; + aDescriptor.Parent = xPeer; + aDescriptor.Bounds = awt::Rectangle( 10, 10, 250, 150 ); + aDescriptor.WindowAttributes = nWindowAttributes; + + uno::Reference< awt::XMessageBox > xMsgBox( xToolkit->createWindow( aDescriptor ), uno::UNO_QUERY ); + if ( xMsgBox.is() ) + { + uno::Reference< awt::XVclContainer > xMsgBoxCtrls( xMsgBox, uno::UNO_QUERY ); + if ( xMsgBoxCtrls.is() ) + { + uno::Sequence< uno::Reference< awt::XWindow > > xChildren = xMsgBoxCtrls->getWindows(); + + for ( uno::Reference< awt::XWindow > const & child : std::as_const(xChildren) ) + { + uno::Reference< awt::XVclWindowPeer > xMsgBoxCtrl( child, uno::UNO_QUERY ); + if ( xMsgBoxCtrl.is() ) + { + bool bIsDefault = true; + uno::Any aValue = xMsgBoxCtrl->getProperty( "DefaultButton" ); + aValue >>= bIsDefault; + if ( bIsDefault ) + xMsgBoxCtrl->setProperty( "Text", uno::Any( rBtnText_1 ) ); + else + xMsgBoxCtrl->setProperty( "Text", uno::Any( rBtnText_2 ) ); + } + } + } + + sal_Int16 nRet; + // xMsgBox->setCaptionText( msCancelTitle ); + mbShowsMessageBox = true; + xMsgBox->setMessageText( rWarningText ); + nRet = xMsgBox->execute(); + if ( nRet == 2 ) // RET_YES == 2 + bRet = true; + + mbShowsMessageBox = false; + } + + uno::Reference< lang::XComponent > xComponent( xMsgBox, uno::UNO_QUERY ); + if ( xComponent.is() ) + xComponent->dispose(); + + return bRet; +} + + +bool UpdateHandler::showOverwriteWarning( std::u16string_view rFileName ) const +{ + return showWarning( + (msReloadWarning + .replaceAll( "%FILENAME", rFileName ) + .replaceAll( "%DOWNLOAD_PATH", msDownloadPath )), + msReloadContinue, msReloadReload ); +} + + +bool UpdateHandler::showOverwriteWarning() const +{ + return showWarning( msOverwriteWarning ); +} + + +#define BUTTON_HEIGHT 14 +#define BUTTON_WIDTH 50 +#define BUTTON_X_OFFSET 7 +#define BUTTON_Y_OFFSET 3 +#define LABEL_HEIGHT 10 + +#define DIALOG_WIDTH 300 +#define DIALOG_BORDER 5 +#define INNER_BORDER 3 +#define TEXT_OFFSET 1 +#define BOX_HEIGHT1 ( LABEL_HEIGHT + 3*BUTTON_HEIGHT + 2*BUTTON_Y_OFFSET + 2*INNER_BORDER ) +#define BOX_HEIGHT2 50 +#define EDIT_WIDTH ( DIALOG_WIDTH - 2 * DIALOG_BORDER ) +#define BOX1_BTN_X ( DIALOG_BORDER + EDIT_WIDTH - BUTTON_WIDTH - INNER_BORDER ) +#define BOX1_BTN_Y ( DIALOG_BORDER + LABEL_HEIGHT + INNER_BORDER) +#define THROBBER_WIDTH 16 +#define THROBBER_HEIGHT 16 +#define THROBBER_X_POS ( DIALOG_BORDER + 8 ) +#define THROBBER_Y_POS ( DIALOG_BORDER + 23 ) +#define BUTTON_BAR_HEIGHT 24 +#define LABEL_OFFSET ( LABEL_HEIGHT + 4 ) +#define DIALOG_HEIGHT ( BOX_HEIGHT1 + BOX_HEIGHT2 + LABEL_OFFSET + BUTTON_BAR_HEIGHT + 3 * DIALOG_BORDER ) +#define LABEL_Y_POS ( 2 * DIALOG_BORDER + BOX_HEIGHT1 ) +#define EDIT2_Y_POS ( LABEL_Y_POS + LABEL_HEIGHT ) +#define BUTTON_BAR_Y_POS ( EDIT2_Y_POS + DIALOG_BORDER + BOX_HEIGHT2 ) +#define BUTTON_Y_POS ( BUTTON_BAR_Y_POS + 8 ) +#define CLOSE_BTN_X ( DIALOG_WIDTH - DIALOG_BORDER - BUTTON_WIDTH ) +#define INSTALL_BTN_X ( CLOSE_BTN_X - 2 * BUTTON_X_OFFSET - BUTTON_WIDTH ) +#define DOWNLOAD_BTN_X ( INSTALL_BTN_X - BUTTON_X_OFFSET - BUTTON_WIDTH ) +#define PROGRESS_WIDTH 80 +#define PROGRESS_HEIGHT 10 +#define PROGRESS_X_POS ( DIALOG_BORDER + 8 ) +#define PROGRESS_Y_POS ( DIALOG_BORDER + 2*LABEL_OFFSET ) + + +void UpdateHandler::showControls( short nControls ) +{ + // The buttons from CANCEL_BUTTON to RESUME_BUTTON will be shown or + // hidden on demand + short nShiftMe; + for ( int i = 0; i <= int(RESUME_BUTTON); i++ ) + { + nShiftMe = static_cast<short>(nControls >> i); + showControl( msButtonIDs[i], static_cast<bool>(nShiftMe & 0x01) ); + } + + nShiftMe = static_cast<short>(nControls >> THROBBER_CTRL); + startThrobber( static_cast<bool>(nShiftMe & 0x01) ); + + nShiftMe = static_cast<short>(nControls >> PROGRESS_CTRL); + showControl( CTRL_PROGRESS, static_cast<bool>(nShiftMe & 0x01) ); + showControl( TEXT_PERCENT, static_cast<bool>(nShiftMe & 0x01) ); + + // Status text needs to be smaller, when there are buttons at the right side of the dialog + if ( ( nControls & ( (1<<CANCEL_BUTTON) + (1<<PAUSE_BUTTON) + (1<<RESUME_BUTTON) ) ) != 0 ) + setControlProperty( TEXT_STATUS, "Width", uno::Any( sal_Int32(EDIT_WIDTH - BUTTON_WIDTH - 2*INNER_BORDER - TEXT_OFFSET ) ) ); + else + setControlProperty( TEXT_STATUS, "Width", uno::Any( sal_Int32(EDIT_WIDTH - 2*TEXT_OFFSET ) ) ); + + // Status text needs to be taller, when we show the progress bar + if ( ( nControls & ( 1<<PROGRESS_CTRL ) ) != 0 ) + setControlProperty( TEXT_STATUS, "Height", uno::Any( sal_Int32(LABEL_HEIGHT) ) ); + else + setControlProperty( TEXT_STATUS, "Height", uno::Any( sal_Int32(BOX_HEIGHT1 - 4*TEXT_OFFSET - LABEL_HEIGHT ) ) ); +} + + +void UpdateHandler::createDialog() +{ + if ( !mxContext.is() ) + { + OSL_ASSERT( false ); + return; + } + + if( mxContext.is() ) + { + uno::Reference< frame::XDesktop2 > xDesktop = frame::Desktop::create( mxContext ); + xDesktop->addTerminateListener( this ); + } + + loadStrings(); + + uno::Reference< lang::XMultiComponentFactory > xFactory( mxContext->getServiceManager(), uno::UNO_SET_THROW ); + uno::Reference< awt::XControlModel > xControlModel( xFactory->createInstanceWithContext( + "com.sun.star.awt.UnoControlDialogModel", + mxContext), uno::UNO_QUERY_THROW ); + { + // @see awt/UnoControlDialogModel.idl + uno::Reference< beans::XPropertySet > xPropSet( xControlModel, uno::UNO_QUERY_THROW ); + + xPropSet->setPropertyValue( "Title", uno::Any( msDlgTitle ) ); + xPropSet->setPropertyValue( "Closeable", uno::Any( true ) ); + xPropSet->setPropertyValue( "Enabled", uno::Any( true ) ); + xPropSet->setPropertyValue( "Moveable", uno::Any( true ) ); + xPropSet->setPropertyValue( "Sizeable", uno::Any( true ) ); + xPropSet->setPropertyValue( "DesktopAsParent", uno::Any( true ) ); + xPropSet->setPropertyValue( "PositionX", uno::Any(sal_Int32( 100 )) ); + xPropSet->setPropertyValue( "PositionY", uno::Any(sal_Int32( 100 )) ); + xPropSet->setPropertyValue( "Width", uno::Any(sal_Int32( DIALOG_WIDTH )) ); + xPropSet->setPropertyValue( "Height", uno::Any(sal_Int32( DIALOG_HEIGHT )) ); + xPropSet->setPropertyValue( "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_DLG )) ); + } + { // Label (fixed text) <status> + uno::Sequence< beans::NamedValue > aProps { { "Label", uno::Any( msStatusFL ) } }; + + insertControlModel( xControlModel, FIXED_TEXT_MODEL, "fixedLineStatus", + awt::Rectangle( DIALOG_BORDER+1, DIALOG_BORDER, EDIT_WIDTH-2, LABEL_HEIGHT ), + aProps ); + } + { // box around <status> text + uno::Sequence< beans::NamedValue > aProps; + + insertControlModel( xControlModel, GROUP_BOX_MODEL, "StatusBox", + awt::Rectangle( DIALOG_BORDER, DIALOG_BORDER + LABEL_HEIGHT, EDIT_WIDTH, BOX_HEIGHT1 - LABEL_HEIGHT ), + aProps ); + } + { // Text (multiline edit) <status> + uno::Sequence< beans::NamedValue > aProps + { + { "Text", uno::Any( substVariables(msChecking) ) }, + { "Border", uno::Any( sal_Int16( 0 ) ) }, + { "PaintTransparent", uno::Any( true ) }, + { "MultiLine", uno::Any( true ) }, + { "ReadOnly", uno::Any( true ) }, + { "AutoVScroll", uno::Any( true ) }, + { "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_STATUS )) } + }; + + insertControlModel( xControlModel, EDIT_FIELD_MODEL, TEXT_STATUS, + awt::Rectangle( DIALOG_BORDER + TEXT_OFFSET, + DIALOG_BORDER + LABEL_HEIGHT + TEXT_OFFSET, + EDIT_WIDTH - 2*TEXT_OFFSET, + BOX_HEIGHT1 - 4*TEXT_OFFSET - LABEL_HEIGHT ), + aProps ); + } + { // Text (edit) <percent> + uno::Sequence< beans::NamedValue > aProps + { + { "Text", uno::Any( substVariables(msPercent) ) }, + { "Border", uno::Any( sal_Int16( 0 ) ) }, + { "PaintTransparent", uno::Any( true ) }, + { "ReadOnly", uno::Any( true ) }, + }; + + insertControlModel( xControlModel, EDIT_FIELD_MODEL, TEXT_PERCENT, + awt::Rectangle( PROGRESS_X_POS + PROGRESS_WIDTH + DIALOG_BORDER, + PROGRESS_Y_POS, + EDIT_WIDTH - PROGRESS_WIDTH - BUTTON_WIDTH - 2*DIALOG_BORDER, + LABEL_HEIGHT ), + aProps ); + } + { // pause button + uno::Sequence< beans::NamedValue > aProps + { + { "DefaultButton", uno::Any( false ) }, + { "Enabled", uno::Any( true ) }, + { "PushButtonType", uno::Any( sal_Int16(awt::PushButtonType_STANDARD) ) }, + { "Label", uno::Any( msPauseBtn ) }, + { "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_PAUSE )) } + }; + + insertControlModel ( xControlModel, BUTTON_MODEL, msButtonIDs[PAUSE_BUTTON], + awt::Rectangle( BOX1_BTN_X, BOX1_BTN_Y, BUTTON_WIDTH, BUTTON_HEIGHT ), + aProps ); + } + { // resume button + uno::Sequence< beans::NamedValue > aProps + { + { "DefaultButton", uno::Any( false ) }, + { "Enabled", uno::Any( true ) }, + { "PushButtonType", uno::Any( sal_Int16(awt::PushButtonType_STANDARD) ) }, + { "Label", uno::Any( msResumeBtn ) }, + { "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_RESUME )) } + }; + + insertControlModel ( xControlModel, BUTTON_MODEL, msButtonIDs[RESUME_BUTTON], + awt::Rectangle( BOX1_BTN_X, + BOX1_BTN_Y + BUTTON_Y_OFFSET + BUTTON_HEIGHT, + BUTTON_WIDTH, + BUTTON_HEIGHT ), + aProps ); + } + { // abort button + uno::Sequence< beans::NamedValue > aProps + { + { "DefaultButton", uno::Any( false ) }, + { "Enabled", uno::Any( true ) }, + { "PushButtonType", uno::Any( sal_Int16(awt::PushButtonType_STANDARD) ) }, + { "Label", uno::Any( msCancelBtn ) }, + { "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_CANCEL )) } + }; + + insertControlModel ( xControlModel, BUTTON_MODEL, msButtonIDs[CANCEL_BUTTON], + awt::Rectangle( BOX1_BTN_X, + BOX1_BTN_Y + (2*(BUTTON_HEIGHT+BUTTON_Y_OFFSET)), + BUTTON_WIDTH, + BUTTON_HEIGHT ), + aProps ); + } + { // Label (FixedText) <description> + uno::Sequence< beans::NamedValue > aProps { { "Label", uno::Any( msDescription ) } }; + + insertControlModel( xControlModel, FIXED_TEXT_MODEL, "fixedTextDescription", + awt::Rectangle( DIALOG_BORDER+1, LABEL_Y_POS, EDIT_WIDTH-2, LABEL_HEIGHT ), + aProps ); + } + { // box around <description> text + uno::Sequence< beans::NamedValue > aProps; + + insertControlModel( xControlModel, GROUP_BOX_MODEL, "DescriptionBox", + awt::Rectangle( DIALOG_BORDER, EDIT2_Y_POS, EDIT_WIDTH, BOX_HEIGHT2 ), + aProps ); + } + { // Text (MultiLineEdit) <description> + uno::Sequence< beans::NamedValue > aProps + { + { "Text", uno::Any( OUString() ) }, + { "Border", uno::Any( sal_Int16( 0 ) ) }, + { "PaintTransparent", uno::Any( true ) }, + { "MultiLine", uno::Any( true ) }, + { "ReadOnly", uno::Any( true ) }, + { "AutoVScroll", uno::Any( true ) }, + { "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_DESCRIPTION )) } + }; + + insertControlModel( xControlModel, EDIT_FIELD_MODEL, TEXT_DESCRIPTION, + awt::Rectangle( DIALOG_BORDER + TEXT_OFFSET, + EDIT2_Y_POS + 2*TEXT_OFFSET, + EDIT_WIDTH - 3*TEXT_OFFSET, + BOX_HEIGHT2 - 3*TEXT_OFFSET ), + aProps ); + } + { // @see awt/UnoControlFixedLineModel.idl + uno::Sequence< beans::NamedValue > aProps { { "Orientation", uno::Any( sal_Int32( 0 ) ) } }; + + insertControlModel( xControlModel, FIXED_LINE_MODEL, "fixedLine", + awt::Rectangle( 0, BUTTON_BAR_Y_POS, DIALOG_WIDTH, 5 ), + aProps ); + } + { // close button // @see awt/UnoControlButtonModel.idl + uno::Sequence< beans::NamedValue > aProps + { + { "DefaultButton", uno::Any( false ) }, + { "Enabled", uno::Any( true ) }, + // [property] short PushButtonType + // with own "ButtonActionListener" + { "PushButtonType", uno::Any( sal_Int16(awt::PushButtonType_STANDARD) ) }, + // with default ActionListener => endDialog(). + // setProperty( aProps, 2, "PushButtonType", uno::Any( sal_Int16(awt::PushButtonType_CANCEL) ) ); + // [property] string Label // only if PushButtonType_STANDARD + { "Label", uno::Any( msClose ) }, + { "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_CLOSE )) } + }; + + insertControlModel ( xControlModel, BUTTON_MODEL, msButtonIDs[ CLOSE_BUTTON ], + awt::Rectangle( CLOSE_BTN_X, BUTTON_Y_POS, BUTTON_WIDTH, BUTTON_HEIGHT ), + aProps ); + } + { // download button + uno::Sequence< beans::NamedValue > aProps + { + { "DefaultButton", uno::Any( false ) }, + { "Enabled", uno::Any( true ) }, + { "PushButtonType", uno::Any( sal_Int16(awt::PushButtonType_STANDARD) ) }, + { "Label", uno::Any( msDownload ) }, + { "HelpURL", uno::Any(OUString( INET_HID_SCHEME + HID_CHECK_FOR_UPD_DOWNLOAD )) } + }; + + insertControlModel ( xControlModel, BUTTON_MODEL, msButtonIDs[DOWNLOAD_BUTTON], + awt::Rectangle( DOWNLOAD_BTN_X, BUTTON_Y_POS, BUTTON_WIDTH, BUTTON_HEIGHT ), + aProps ); + } + { // help button + uno::Sequence< beans::NamedValue > aProps + { + { "DefaultButton", uno::Any( false ) }, + { "Enabled", uno::Any( true ) }, + { "PushButtonType", uno::Any( sal_Int16(awt::PushButtonType_HELP) ) } + }; + + insertControlModel( xControlModel, BUTTON_MODEL, msButtonIDs[HELP_BUTTON], + awt::Rectangle( DIALOG_BORDER, BUTTON_Y_POS, BUTTON_WIDTH, BUTTON_HEIGHT ), + aProps ); + } + { // @see awt/UnoControlThrobberModel.idl + uno::Sequence< beans::NamedValue > aProps; + + insertControlModel( xControlModel, "com.sun.star.awt.SpinningProgressControlModel", CTRL_THROBBER, + awt::Rectangle( THROBBER_X_POS, THROBBER_Y_POS, THROBBER_WIDTH, THROBBER_HEIGHT), + aProps ); + } + { // @see awt/UnoControlProgressBarModel.idl + uno::Sequence< beans::NamedValue > aProps + { + { "Enabled", uno::Any( true ) }, + { "ProgressValue", uno::Any( sal_Int32( 0 ) ) }, + { "ProgressValueMax", uno::Any( sal_Int32( 100 ) ) }, + { "ProgressValueMin", uno::Any( sal_Int32( 0 ) ) }, + }; + insertControlModel( xControlModel, "com.sun.star.awt.UnoControlProgressBarModel", CTRL_PROGRESS, + awt::Rectangle( PROGRESS_X_POS, PROGRESS_Y_POS, PROGRESS_WIDTH, PROGRESS_HEIGHT ), + aProps); + } + + uno::Reference< awt::XUnoControlDialog > xControl = awt::UnoControlDialog::create( mxContext ); + xControl->setModel( xControlModel ); + + if ( !mbVisible ) + { + xControl->setVisible( false ); + } + + xControl->createPeer( nullptr, nullptr ); + { + for ( int i = 0; i < HELP_BUTTON; i++ ) + { + uno::Reference< awt::XButton > xButton ( xControl->getControl( msButtonIDs[i] ), uno::UNO_QUERY); + if (xButton.is()) + { + xButton->setActionCommand( msButtonIDs[i] ); + xButton->addActionListener( this ); + } + } + } + + mxUpdDlg.set( xControl, uno::UNO_QUERY_THROW ); + mnLastCtrlState = -1; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updatehdl.hxx b/extensions/source/update/check/updatehdl.hxx new file mode 100644 index 0000000000..aa4e16fc76 --- /dev/null +++ b/extensions/source/update/check/updatehdl.hxx @@ -0,0 +1,202 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <osl/mutex.hxx> +#include <com/sun/star/uno/Any.h> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/awt/XActionListener.hpp> +#include <com/sun/star/awt/XControlModel.hpp> +#include <com/sun/star/awt/XDialog.hpp> +#include <com/sun/star/awt/XTopWindowListener.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/frame/XTerminateListener.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <cppuhelper/implbase.hxx> +#include <unotools/resmgr.hxx> +#include <rtl/ref.hxx> + +#include "actionlistener.hxx" + +enum DialogControls +{ + CANCEL_BUTTON = 0, + PAUSE_BUTTON, + RESUME_BUTTON, + DOWNLOAD_BUTTON, + CLOSE_BUTTON, + HELP_BUTTON, + BUTTON_COUNT, + THROBBER_CTRL, + PROGRESS_CTRL +}; + +enum UpdateState { + UPDATESTATE_CHECKING = 0, + UPDATESTATE_ERROR_CHECKING, + UPDATESTATE_NO_UPDATE_AVAIL, + UPDATESTATE_UPDATE_AVAIL, + UPDATESTATE_UPDATE_NO_DOWNLOAD, + UPDATESTATE_AUTO_START, + UPDATESTATE_DOWNLOADING, + UPDATESTATE_DOWNLOAD_PAUSED, + UPDATESTATE_ERROR_DOWNLOADING, + UPDATESTATE_DOWNLOAD_AVAIL, + UPDATESTATE_EXT_UPD_AVAIL, + UPDATESTATES_COUNT +}; + +class UpdateHandler : public cppu::WeakImplHelper< css::awt::XActionListener, + css::awt::XTopWindowListener, + css::task::XInteractionHandler, + css::frame::XTerminateListener > +{ +private: + css::uno::Reference< css::uno::XComponentContext > mxContext; + css::uno::Reference< css::awt::XDialog > mxUpdDlg; + css::uno::Reference< css::task::XInteractionHandler > mxInteractionHdl; + rtl::Reference< IActionListener > mxActionListener; + + UpdateState meCurState; + UpdateState meLastState; + sal_Int32 mnPercent; + short mnLastCtrlState; + bool mbDownloadBtnHasDots; + bool mbVisible; + bool mbStringsLoaded; + bool mbMinimized; + bool mbListenerAdded; + mutable bool mbShowsMessageBox; + + osl::Mutex maMutex; + + OUString msNextVersion; + OUString msDownloadPath; + OUString msDownloadFile; + OUString msDescriptionMsg; + OUString msChecking; // RID_UPDATE_STR_CHECKING + OUString msCheckingError; // RID_UPDATE_STR_CHECKING_ERR + OUString msNoUpdFound; // RID_UPDATE_STR_NO_UPD_FOUND + OUString msUpdFound; // RID_UPDATE_STR_UPD_FOUND + OUString msDlgTitle; // RID_UPDATE_STR_DLG_TITLE + OUString msDownloadPause; // RID_UPDATE_STR_DOWNLOAD_PAUSE + OUString msDownloadError; // RID_UPDATE_STR_DOWNLOAD_ERR + OUString msDownloadWarning; // RID_UPDATE_STR_DOWNLOAD_WARN + OUString msDownloadDescr; // RID_UPDATE_STR_DOWNLOAD_WARN + OUString msDownloadNotAvail; // RID_UPDATE_STR_DOWNLOAD_UNAVAIL + OUString msDownloading; // RID_UPDATE_STR_DOWNLOADING + OUString msReady2Install; // RID_UPDATE_STR_READY_INSTALL + OUString msCancelMessage; // RID_UPDATE_STR_CANCEL_DOWNLOAD + OUString msOverwriteWarning; // RID_UPDATE_STR_OVERWRITE_WARNING + OUString msPercent; // RID_UPDATE_STR_PERCENT + OUString msReloadWarning; // RID_UPDATE_STR_OVERWRITE_WARNING + OUString msReloadReload; // RID_UPDATE_STR_OVERWRITE_WARNING + OUString msReloadContinue; // RID_UPDATE_STR_OVERWRITE_WARNING + OUString msStatusFL; // RID_UPDATE_FT_STATUS + OUString msDescription; // RID_UPDATE_FT_DESCRIPTION + OUString msClose; // RID_UPDATE_BTN_CLOSE + OUString msDownload; // RID_UPDATE_BTN_DOWNLOAD + OUString msPauseBtn; // RID_UPDATE_BTN_PAUSE + OUString msResumeBtn; // RID_UPDATE_BTN_RESUME + OUString msCancelBtn; // RID_UPDATE_BTN_CANCEL + OUString msButtonIDs[ BUTTON_COUNT ]; + OUString msBubbleTexts[ UPDATESTATES_COUNT ]; + OUString msBubbleTitles[ UPDATESTATES_COUNT ]; + + void createDialog(); + void updateState( UpdateState eNewState ); + void startThrobber( bool bStart = true ); + void setControlProperty( const OUString &rCtrlName, + const OUString &rPropName, + const css::uno::Any &rPropValue ); + void showControl( const OUString &rCtrlName, bool bShow = true ); + void showControls( short nControls ); + void focusControl( DialogControls eID ); + void enableControls( short nCtrlState ); + void setDownloadBtnLabel( bool bAppendDots ); + void loadStrings(); + static OUString loadString(const std::locale& rLocale, + TranslateId pResourceId); + OUString substVariables( const OUString &rSource ) const; + static void insertControlModel( css::uno::Reference< css::awt::XControlModel > const & rxDialogModel, + OUString const & rServiceName, + OUString const & rControlName, + css::awt::Rectangle const & rPosSize, + css::uno::Sequence< css::beans::NamedValue > const & rProps ); + + void setFullVersion( OUString& rString ); + +public: + UpdateHandler( const css::uno::Reference< css::uno::XComponentContext > & rxContext, + const rtl::Reference< IActionListener > & rxActionListener ); + virtual ~UpdateHandler() override; + UpdateHandler(const UpdateHandler&) = delete; + UpdateHandler& operator=(const UpdateHandler&) = delete; + + bool isVisible() const; + bool isMinimized() const { return mbMinimized; } + void setVisible( bool bVisible = true ); + void setProgress( sal_Int32 nPercent ); + void setNextVersion( const OUString &rNextVersion ) { msNextVersion = rNextVersion; } + void setDownloadPath( const OUString &rPath ) { msDownloadPath = rPath; } + void setDownloadFile( std::u16string_view rPath ); + void setErrorMessage( const OUString &rErrorMsg ); + void setDescription( const OUString &rDescription ){ msDescriptionMsg = rDescription; } + + void setState( UpdateState eState ); + OUString getBubbleText( UpdateState eState ); + OUString getBubbleTitle( UpdateState eState ); + bool showWarning( const OUString &rWarning ) const; + bool showWarning( const OUString &rWarning, const OUString& rBtnText_1, const OUString& rBtnText_2 ) const; + bool showOverwriteWarning( std::u16string_view rFileName ) const; + bool showOverwriteWarning() const; + + // Allows runtime exceptions to be thrown by const methods + operator css::uno::Reference< css::uno::XInterface > () const + { return const_cast< cppu::OWeakObject * > (static_cast< cppu::OWeakObject const * > (this)); }; + + // XActionListener + virtual void SAL_CALL disposing( const css::lang::EventObject &rObj ) override; + virtual void SAL_CALL actionPerformed( css::awt::ActionEvent const & rEvent) override; + + // XTopWindowListener + virtual void SAL_CALL windowOpened( const css::lang::EventObject& e ) override; + virtual void SAL_CALL windowClosing( const css::lang::EventObject& e ) override; + virtual void SAL_CALL windowClosed( const css::lang::EventObject& e ) override; + virtual void SAL_CALL windowMinimized( const css::lang::EventObject& e ) override; + virtual void SAL_CALL windowNormalized( const css::lang::EventObject& e ) override; + virtual void SAL_CALL windowActivated( const css::lang::EventObject& e ) override; + virtual void SAL_CALL windowDeactivated( const css::lang::EventObject& e ) override; + + // XInteractionHandler + virtual void SAL_CALL handle( const css::uno::Reference< css::task::XInteractionRequest >& Request ) override; + + // XTerminateListener + virtual void SAL_CALL queryTermination( const css::lang::EventObject& e ) override; + virtual void SAL_CALL notifyTermination( const css::lang::EventObject& e ) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updateinfo.hxx b/extensions/source/update/check/updateinfo.hxx new file mode 100644 index 0000000000..79387b3585 --- /dev/null +++ b/extensions/source/update/check/updateinfo.hxx @@ -0,0 +1,61 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <vector> + +struct DownloadSource +{ + bool IsDirect; + OUString URL; + + DownloadSource(bool bIsDirect, const OUString& aURL) : IsDirect(bIsDirect), URL(aURL) {}; + DownloadSource(const DownloadSource& ds) : IsDirect(ds.IsDirect), URL(ds.URL) {}; + + DownloadSource & operator=( const DownloadSource & ds ) { IsDirect = ds.IsDirect; URL = ds.URL; return *this; }; +}; + +struct ReleaseNote +{ + sal_uInt8 Pos; + OUString URL; + sal_uInt8 Pos2; + OUString URL2; + + ReleaseNote(sal_uInt8 pos, const OUString& aURL) : Pos(pos), URL(aURL), Pos2(0), URL2() {}; + + ReleaseNote(const ReleaseNote& rn) :Pos(rn.Pos), URL(rn.URL), Pos2(rn.Pos2), URL2(rn.URL2) {}; + ReleaseNote & operator=( const ReleaseNote& rn) { Pos=rn.Pos; URL=rn.URL; Pos2=rn.Pos2; URL2=rn.URL2; return *this; }; +}; + +struct UpdateInfo +{ + OUString BuildId; + OUString Version; + OUString Description; + std::vector< DownloadSource > Sources; + std::vector< ReleaseNote > ReleaseNotes; +}; + +// Returns the URL of the release note for the given position +OUString getReleaseNote(const UpdateInfo& rInfo, sal_uInt8 pos, bool autoDownloadEnabled=false); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updateprotocol.cxx b/extensions/source/update/check/updateprotocol.cxx new file mode 100644 index 0000000000..db8319c799 --- /dev/null +++ b/extensions/source/update/check/updateprotocol.cxx @@ -0,0 +1,317 @@ +/* -*- 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_folders.h> + +#include <com/sun/star/xml/xpath/XPathAPI.hpp> +#include <com/sun/star/xml/xpath/XPathException.hpp> + +#include "updateprotocol.hxx" +#include "updatecheckconfig.hxx" + +#include <com/sun/star/deployment/UpdateInformationEntry.hpp> +#include <com/sun/star/deployment/XPackageInformationProvider.hpp> + + +#include <rtl/ref.hxx> +#include <rtl/bootstrap.hxx> +#include <osl/diagnose.h> + +namespace container = css::container ; +namespace deployment = css::deployment ; +namespace uno = css::uno ; +namespace task = css::task ; +namespace xml = css::xml ; + + +static bool +getBootstrapData( + uno::Sequence< OUString > & rRepositoryList, + OUString & rGitID, + OUString & rInstallSetID) +{ + rGitID = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}"; + rtl::Bootstrap::expandMacros( rGitID ); + if ( rGitID.isEmpty() ) + return false; + + rInstallSetID = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":UpdateID}"; + rtl::Bootstrap::expandMacros( rInstallSetID ); + if ( rInstallSetID.isEmpty() ) + return false; + + OUString aValue( "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":UpdateURL}" ); + rtl::Bootstrap::expandMacros( aValue ); + + if( !aValue.isEmpty() ) + { + rRepositoryList = { aValue }; + } + + return true; +} + + +// Returns 'true' if successfully connected to the update server +bool +checkForUpdates( + UpdateInfo& o_rUpdateInfo, + uno::Reference< uno::XComponentContext > const & rxContext, + uno::Reference< task::XInteractionHandler > const & rxInteractionHandler, + const uno::Reference< deployment::XUpdateInformationProvider >& rUpdateInfoProvider) +{ + OUString myArch; + OUString myOS; + + rtl::Bootstrap::get("_OS", myOS); + rtl::Bootstrap::get("_ARCH", myArch); + + uno::Sequence< OUString > aRepositoryList; + OUString aGitID; + OUString aInstallSetID; + + if( ! ( getBootstrapData(aRepositoryList, aGitID, aInstallSetID) && (aRepositoryList.getLength() > 0) ) ) + return false; + + return checkForUpdates( o_rUpdateInfo, rxContext, rxInteractionHandler, rUpdateInfoProvider, + myOS, myArch, + aRepositoryList, aGitID, aInstallSetID ); +} + +bool +checkForUpdates( + UpdateInfo& o_rUpdateInfo, + const uno::Reference< uno::XComponentContext > & rxContext, + const uno::Reference< task::XInteractionHandler > & rxInteractionHandler, + const uno::Reference< deployment::XUpdateInformationProvider >& rUpdateInfoProvider, + std::u16string_view rOS, + std::u16string_view rArch, + const uno::Sequence< OUString > &rRepositoryList, + std::u16string_view rGitID, + const OUString &rInstallSetID ) +{ + if( !rxContext.is() ) + throw uno::RuntimeException( "checkForUpdates: empty component context" ); + + OSL_ASSERT( rxContext->getServiceManager().is() ); + + // XPath implementation + uno::Reference< xml::xpath::XXPathAPI > xXPath = xml::xpath::XPathAPI::create(rxContext); + + xXPath->registerNS( "inst", "http://update.libreoffice.org/description" ); + + if( rxInteractionHandler.is() ) + rUpdateInfoProvider->setInteractionHandler(rxInteractionHandler); + + try + { + uno::Reference< container::XEnumeration > aUpdateInfoEnumeration = + rUpdateInfoProvider->getUpdateInformationEnumeration( rRepositoryList, rInstallSetID ); + + if ( !aUpdateInfoEnumeration.is() ) + return false; // something went wrong .. + + OUString aXPathExpression = + OUString::Concat("/child::inst:description[inst:os=\'")+ + rOS + + "\' and inst:arch=\'"+ + rArch + + "\' and inst:gitid!=\'"+ + rGitID + + "\']"; + + + while( aUpdateInfoEnumeration->hasMoreElements() ) + { + deployment::UpdateInformationEntry aEntry; + + if( aUpdateInfoEnumeration->nextElement() >>= aEntry ) + { + uno::Reference< xml::dom::XNode > xNode( aEntry.UpdateDocument ); + uno::Reference< xml::dom::XNodeList > xNodeList; + try { + xNodeList = xXPath->selectNodeList(xNode, aXPathExpression + + "/inst:update/attribute::src"); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + + sal_Int32 i, imax = xNodeList->getLength(); + for( i = 0; i < imax; ++i ) + { + uno::Reference< xml::dom::XNode > xNode2( xNodeList->item(i) ); + + if( xNode2.is() ) + { + uno::Reference< xml::dom::XElement > xParent(xNode2->getParentNode(), uno::UNO_QUERY_THROW); + OUString aType = xParent->getAttribute("type"); + bool bIsDirect = !aType.equalsIgnoreAsciiCase("text/html"); + + o_rUpdateInfo.Sources.push_back( DownloadSource(bIsDirect, xNode2->getNodeValue()) ); + } + } + + uno::Reference< xml::dom::XNode > xNode2; + try { + xNode2 = xXPath->selectSingleNode(xNode, aXPathExpression + + "/inst:version/text()"); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + + if( xNode2.is() ) + o_rUpdateInfo.Version = xNode2->getNodeValue(); + + try { + xNode2 = xXPath->selectSingleNode(xNode, aXPathExpression + + "/inst:buildid/text()"); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + + if( xNode2.is() ) + o_rUpdateInfo.BuildId = xNode2->getNodeValue(); + + o_rUpdateInfo.Description = aEntry.Description; + + // Release Notes + try { + xNodeList = xXPath->selectNodeList(xNode, aXPathExpression + + "/inst:relnote"); + } catch (const css::xml::xpath::XPathException &) { + // ignore + } + imax = xNodeList->getLength(); + for( i = 0; i < imax; ++i ) + { + uno::Reference< xml::dom::XElement > xRelNote(xNodeList->item(i), uno::UNO_QUERY); + if( xRelNote.is() ) + { + sal_Int32 pos = xRelNote->getAttribute("pos").toInt32(); + + ReleaseNote aRelNote(static_cast<sal_uInt8>(pos), xRelNote->getAttribute("src")); + + if( xRelNote->hasAttribute("src2") ) + { + pos = xRelNote->getAttribute("pos2").toInt32(); + aRelNote.Pos2 = static_cast<sal_Int8>(pos); + aRelNote.URL2 = xRelNote->getAttribute("src2"); + } + + o_rUpdateInfo.ReleaseNotes.push_back(aRelNote); + } + } + + if( !o_rUpdateInfo.Sources.empty() ) + return true; + } + } + } + catch( ... ) + { + return false; + } + + return true; +} + + +bool storeExtensionUpdateInfos( const uno::Reference< uno::XComponentContext > & rxContext, + const uno::Sequence< uno::Sequence< OUString > > &rUpdateInfos ) +{ + bool bNotify = false; + + if ( rUpdateInfos.hasElements() ) + { + rtl::Reference< UpdateCheckConfig > aConfig = UpdateCheckConfig::get( rxContext ); + + for ( sal_Int32 i = rUpdateInfos.getLength() - 1; i >= 0; i-- ) + { + bNotify |= aConfig->storeExtensionVersion( rUpdateInfos[i][0], rUpdateInfos[i][1] ); + } + } + return bNotify; +} + + +// Returns 'true' if there are updates for any extension + +bool checkForExtensionUpdates( const uno::Reference< uno::XComponentContext > & rxContext ) +{ + uno::Sequence< uno::Sequence< OUString > > aUpdateList; + + uno::Reference< deployment::XPackageInformationProvider > xInfoProvider; + try + { + uno::Any aValue( rxContext->getValueByName( + "/singletons/com.sun.star.deployment.PackageInformationProvider" ) ); + OSL_VERIFY( aValue >>= xInfoProvider ); + } + catch( const uno::Exception& ) + { + OSL_FAIL( "checkForExtensionUpdates: could not create the PackageInformationProvider!" ); + } + + if ( !xInfoProvider.is() ) return false; + + aUpdateList = xInfoProvider->isUpdateAvailable( OUString() ); + bool bNotify = storeExtensionUpdateInfos( rxContext, aUpdateList ); + + return bNotify; +} + + +// Returns 'true' if there are any pending updates for any extension (offline check) + +bool checkForPendingUpdates( const uno::Reference< uno::XComponentContext > & rxContext ) +{ + uno::Sequence< uno::Sequence< OUString > > aExtensionList; + uno::Reference< deployment::XPackageInformationProvider > xInfoProvider; + try + { + uno::Any aValue( rxContext->getValueByName( + "/singletons/com.sun.star.deployment.PackageInformationProvider" ) ); + OSL_VERIFY( aValue >>= xInfoProvider ); + } + catch( const uno::Exception& ) + { + OSL_FAIL( "checkForExtensionUpdates: could not create the PackageInformationProvider!" ); + } + + if ( !xInfoProvider.is() ) return false; + + bool bPendingUpdateFound = false; + + aExtensionList = xInfoProvider->getExtensionList(); + if ( aExtensionList.hasElements() ) + { + rtl::Reference< UpdateCheckConfig > aConfig = UpdateCheckConfig::get( rxContext ); + + for ( sal_Int32 i = aExtensionList.getLength() - 1; i >= 0; i-- ) + { + bPendingUpdateFound = aConfig->checkExtensionVersion( aExtensionList[i][0], aExtensionList[i][1] ); + if ( bPendingUpdateFound ) + break; + } + } + + return bPendingUpdateFound; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updateprotocol.hxx b/extensions/source/update/check/updateprotocol.hxx new file mode 100644 index 0000000000..4dedeb0d6c --- /dev/null +++ b/extensions/source/update/check/updateprotocol.hxx @@ -0,0 +1,68 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/deployment/XUpdateInformationProvider.hpp> + +#include "updateinfo.hxx" + +// Returns 'true' if successfully connected to the update server +bool checkForUpdates( + UpdateInfo& o_rUpdateInfo, + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Reference< css::task::XInteractionHandler >& rxInteractionHandler, + const css::uno::Reference< css::deployment::XUpdateInformationProvider >& rxProvider +); + +// The same as above, that does not read the info from bootstrap +SAL_DLLPUBLIC_EXPORT bool +checkForUpdates( + UpdateInfo& o_rUpdateInfo, + const css::uno::Reference< css::uno::XComponentContext > & rxContext, + const css::uno::Reference< css::task::XInteractionHandler > & rxInteractionHandler, + const css::uno::Reference< css::deployment::XUpdateInformationProvider >& rUpdateInfoProvider, + std::u16string_view rOS, + std::u16string_view rArch, + const css::uno::Sequence< OUString > &rRepositoryList, + std::u16string_view rGitID, + const OUString &rInstallID +); + +// Returns 'true' if there are updates for any extension +bool checkForExtensionUpdates( + const css::uno::Reference< css::uno::XComponentContext >& rxContext +); + +bool checkForPendingUpdates( + const css::uno::Reference< css::uno::XComponentContext >& rxContext +); + +bool storeExtensionUpdateInfos( + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Sequence< css::uno::Sequence< OUString > > &rUpdateInfos +); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updateprotocoltest.cxx b/extensions/source/update/check/updateprotocoltest.cxx new file mode 100644 index 0000000000..070f930af1 --- /dev/null +++ b/extensions/source/update/check/updateprotocoltest.cxx @@ -0,0 +1,76 @@ +/* -*- 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 <cppuhelper/bootstrap.hxx> + +#include "updateprotocol.hxx" +#include <com/sun/star/ucb/UniversalContentBroker.hpp> + +#include <sal/main.h> +#include <osl/process.h> +#include <stdio.h> +#include "sal/log.hxx" + +namespace task = ::com::sun::star::task; +namespace uno = ::com::sun::star::uno; + + +SAL_IMPLEMENT_MAIN() +{ + (void) argv; + (void) argc; + + if( osl_getCommandArgCount() != 0 ) + { + fprintf(stderr, "Usage: updateprotocoltest\n"); + return -1; + } + + // create the initial component context + uno::Reference< uno::XComponentContext > rComponentContext = cppu::defaultBootstrap_InitialComponentContext(); + + // initialize UCB (for backwards compatibility, in case some code still uses + // plain createInstance w/o args directly to obtain an instance): + css::ucb::UniversalContentBroker::create(rComponentContext); + + + OUString aURL; + OUString aVersion; + + try + { + if( checkForUpdates(rComponentContext, uno::Reference< task::XInteractionHandler > (), aURL, aVersion) ) + { + SAL_INFO("extensions.update", "Update found: " << aVersion << " on " << aURL); + } + else + { + SAL_INFO("extensions.update", "no updates found" ); + } + } + catch( ... ) + { + SAL_INFO("extensions.update", "unhandled exception caught" ); + } + + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/check/updchk.uno.component b/extensions/source/update/check/updchk.uno.component new file mode 100644 index 0000000000..f147e3065d --- /dev/null +++ b/extensions/source/update/check/updchk.uno.component @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="vnd.sun.UpdateCheck" + constructor="extensions_update_UpdateCheckJob_get_implementation"> + <service name="com.sun.star.setup.UpdateCheck"/> + </implementation> + <implementation name="vnd.sun.UpdateCheckConfig" + constructor="extensions_update_UpdateCheckConfig_get_implementation"> + <service name="com.sun.star.setup.UpdateCheckConfig"/> + </implementation> +</component> diff --git a/extensions/source/update/feed/test/updatefeedtest.cxx b/extensions/source/update/feed/test/updatefeedtest.cxx new file mode 100644 index 0000000000..119aab24a3 --- /dev/null +++ b/extensions/source/update/feed/test/updatefeedtest.cxx @@ -0,0 +1,86 @@ +/* -*- 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 <cppuhelper/servicefactory.hxx> +#include <cppuhelper/bootstrap.hxx> + +#include <com/sun/star/lang/XInitialization.hpp> + + +#include <com/sun/star/ucb/UniversalContentBroker.hpp> +#include <com/sun/star/deployment/UpdateInformationProvider.hpp> + +#include <sal/main.h> +#include <osl/process.h> +#include <sal/log.hxx> +#include <stdio.h> + +namespace deployment = ::com::sun::star::deployment; +namespace lang = ::com::sun::star::lang; +namespace uno = ::com::sun::star::uno; +namespace xml = ::com::sun::star::xml; + + +SAL_IMPLEMENT_MAIN() +{ + (void) argv; + (void) argc; + + if( osl_getCommandArgCount() != 1 ) + { + fprintf(stderr, "Usage: updatefeedtest <url>\n"); + return -1; + } + + // create the initial component context + uno::Reference< uno::XComponentContext > rComponentContext = cppu::defaultBootstrap_InitialComponentContext(); + + // initialize UCB (for backwards compatibility, in case some code still uses + // plain createInstance w/o args directly to obtain an instance): + ucb::UniversalContentBroker::create(rComponentContext); + + // retrieve the update information provider + uno::Reference< deployment::XUpdateInformationProvider > rUpdateInformationProvider = + deployment::UpdateInformationProvider::create( rComponentContext ); + + uno::Sequence< OUString > theURLs(1); + osl_getCommandArg( 0, &theURLs[0].pData ); + // theURLs[0] = "http://localhost/~olli/atomfeed.xml"; + + OUString aExtension = "MyExtension"; + + try + { + uno::Sequence< uno::Reference< xml::dom::XElement > > theUpdateInfo = + rUpdateInformationProvider->getUpdateInformation( theURLs, aExtension ); + } + catch( const uno::Exception & ) + { + TOOLS_WARN_EXCEPTION("extensions.update", ""); + } + catch( ... ) + { + SAL_WARN("extensions.update", "exception of undetermined type caught" ); + } + + + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/feed/updatefeed.component b/extensions/source/update/feed/updatefeed.component new file mode 100644 index 0000000000..83e30a8c24 --- /dev/null +++ b/extensions/source/update/feed/updatefeed.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="vnd.sun.UpdateInformationProvider" + constructor="extensions_update_UpdateInformationProvider_get_implementation"> + <service name="com.sun.star.deployment.UpdateInformationProvider"/> + </implementation> +</component> diff --git a/extensions/source/update/feed/updatefeed.cxx b/extensions/source/update/feed/updatefeed.cxx new file mode 100644 index 0000000000..b1bc6851ed --- /dev/null +++ b/extensions/source/update/feed/updatefeed.cxx @@ -0,0 +1,758 @@ +/* -*- 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 <string_view> + +#include <config_folders.h> + +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/sequence.hxx> +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/deployment/UpdateInformationEntry.hpp> +#include <com/sun/star/deployment/XUpdateInformationProvider.hpp> +#include <com/sun/star/io/XActiveDataSink.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/UniversalContentBroker.hpp> +#include <com/sun/star/ucb/XWebDAVCommandEnvironment.hpp> +#include <com/sun/star/ucb/XCommandProcessor2.hpp> +#include <com/sun/star/ucb/OpenCommandArgument3.hpp> +#include <com/sun/star/ucb/OpenMode.hpp> +#include <com/sun/star/task/PasswordContainerInteractionHandler.hpp> +#include <com/sun/star/xml/dom/DocumentBuilder.hpp> +#include <com/sun/star/xml/xpath/XPathAPI.hpp> +#include <com/sun/star/xml/xpath/XPathException.hpp> +#include <rtl/ref.hxx> +#include <rtl/bootstrap.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <osl/conditn.hxx> +#include <utility> +#include <vcl/svapp.hxx> + +namespace beans = com::sun::star::beans ; +namespace container = com::sun::star::container ; +namespace deployment = com::sun::star::deployment ; +namespace io = com::sun::star::io ; +namespace lang = com::sun::star::lang ; +namespace task = com::sun::star::task ; +namespace ucb = com::sun::star::ucb ; +namespace uno = com::sun::star::uno ; +namespace xml = com::sun::star::xml ; + + +namespace +{ + +#ifdef DEBUG + +class InputStreamWrapper : public ::cppu::WeakImplHelper< io::XInputStream > +{ + uno::Reference< io::XInputStream > m_xStream; + +public: + explicit InputStreamWrapper(const uno::Reference< io::XInputStream >& rxStream) : + m_xStream(rxStream) {}; + + virtual sal_Int32 SAL_CALL readBytes(uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead) + { + sal_Int32 n = m_xStream->readBytes(aData, nBytesToRead); + return n; + }; + virtual sal_Int32 SAL_CALL readSomeBytes(uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead) + { + sal_Int32 n = m_xStream->readSomeBytes(aData, nMaxBytesToRead); + return n; + }; + virtual void SAL_CALL skipBytes( sal_Int32 nBytesToSkip ) + { m_xStream->skipBytes(nBytesToSkip); }; + virtual sal_Int32 SAL_CALL available() + { return m_xStream->available(); }; + virtual void SAL_CALL closeInput( ) + {}; +}; + +#define INPUT_STREAM(i) new InputStreamWrapper(i) +#else +#define INPUT_STREAM(i) i +#endif + + +class ActiveDataSink : public ::cppu::WeakImplHelper< io::XActiveDataSink > +{ + uno::Reference< io::XInputStream > m_xStream; + +public: + ActiveDataSink() {}; + + virtual uno::Reference< io::XInputStream > SAL_CALL getInputStream() override { return m_xStream; }; + virtual void SAL_CALL setInputStream( uno::Reference< io::XInputStream > const & rStream ) override { m_xStream = rStream; }; +}; + + +class UpdateInformationProvider : + public ::cppu::WeakImplHelper< deployment::XUpdateInformationProvider, + ucb::XWebDAVCommandEnvironment, + lang::XServiceInfo > +{ + OUString getUserAgent(bool bExtended); + bool isUserAgentExtended() const; +public: + uno::Reference< xml::dom::XElement > getDocumentRoot(const uno::Reference< xml::dom::XNode >& rxNode); + uno::Reference< xml::dom::XNode > getChildNode(const uno::Reference< xml::dom::XNode >& rxNode, std::u16string_view rName); + + + // XUpdateInformationService + virtual uno::Sequence< uno::Reference< xml::dom::XElement > > SAL_CALL + getUpdateInformation( + uno::Sequence< OUString > const & repositories, + OUString const & extensionId + ) override; + + virtual void SAL_CALL cancel() override; + + virtual void SAL_CALL setInteractionHandler( + uno::Reference< task::XInteractionHandler > const & handler ) override; + + virtual uno::Reference< container::XEnumeration > SAL_CALL + getUpdateInformationEnumeration( + uno::Sequence< OUString > const & repositories, + OUString const & extensionId + ) override; + + // XCommandEnvironment + virtual uno::Reference< task::XInteractionHandler > SAL_CALL getInteractionHandler() override; + + virtual uno::Reference< ucb::XProgressHandler > SAL_CALL getProgressHandler() override { return uno::Reference< ucb::XProgressHandler >(); }; + + // XWebDAVCommandEnvironment + virtual uno::Sequence< beans::StringPair > SAL_CALL getUserRequestHeaders( + const OUString&, ucb::WebDAVHTTPMethod ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(OUString const & serviceName) override; + virtual uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + UpdateInformationProvider(uno::Reference<uno::XComponentContext> xContext, + uno::Reference< ucb::XUniversalContentBroker > xUniversalContentBroker, + uno::Reference< xml::dom::XDocumentBuilder > xDocumentBuilder, + uno::Reference< xml::xpath::XXPathAPI > xXPathAPI); + +protected: + + virtual ~UpdateInformationProvider() override; + static OUString getConfigurationItem(uno::Reference<lang::XMultiServiceFactory> const & configurationProvider, OUString const & node, OUString const & item); + static uno::Any getConfigurationItemAny(uno::Reference<lang::XMultiServiceFactory> const & configurationProvider, OUString const & node, OUString const & item); + +private: + uno::Reference< io::XInputStream > load(const OUString& rURL); + + void storeCommandInfo( sal_Int32 nCommandId, + uno::Reference< ucb::XCommandProcessor > const & rxCommandProcessor); + + const uno::Reference< uno::XComponentContext> m_xContext; + + const uno::Reference< ucb::XUniversalContentBroker > m_xUniversalContentBroker; + const uno::Reference< xml::dom::XDocumentBuilder > m_xDocumentBuilder; + const uno::Reference< xml::xpath::XXPathAPI > m_xXPathAPI; + + uno::Sequence< beans::StringPair > m_aRequestHeaderList; + + uno::Reference< ucb::XCommandProcessor > m_xCommandProcessor; + uno::Reference< task::XInteractionHandler > m_xInteractionHandler; + uno::Reference< task::XInteractionHandler > m_xPwContainerInteractionHandler; + + osl::Mutex m_aMutex; + osl::Condition m_bCancelled; + + sal_Int32 m_nCommandId; +}; + + +class UpdateInformationEnumeration : public ::cppu::WeakImplHelper< container::XEnumeration > +{ +public: + UpdateInformationEnumeration(const uno::Reference< xml::dom::XNodeList >& xNodeList, + rtl::Reference< UpdateInformationProvider > xUpdateInformationProvider) : + m_xUpdateInformationProvider(std::move(xUpdateInformationProvider)), + m_xNodeList(xNodeList), + m_nNodes(xNodeList.is() ? xNodeList->getLength() : 0), + m_nCount(0) + { + }; + + // XEnumeration + sal_Bool SAL_CALL hasMoreElements() override { return m_nCount < m_nNodes; }; + uno::Any SAL_CALL nextElement() override + { + OSL_ASSERT( m_xNodeList.is() ); + OSL_ASSERT( m_xUpdateInformationProvider.is() ); + + if( m_nCount >= m_nNodes ) + throw container::NoSuchElementException(OUString::number(m_nCount), *this); + + try + { + deployment::UpdateInformationEntry aEntry; + + uno::Reference< xml::dom::XNode > xAtomEntryNode( m_xNodeList->item(m_nCount++) ); + + uno::Reference< xml::dom::XNode > xSummaryNode( + m_xUpdateInformationProvider->getChildNode( xAtomEntryNode, u"summary/text()" ) + ); + + if( xSummaryNode.is() ) + aEntry.Description = xSummaryNode->getNodeValue(); + + uno::Reference< xml::dom::XNode > xContentNode( + m_xUpdateInformationProvider->getChildNode( xAtomEntryNode, u"content" ) ); + + if( xContentNode.is() ) + aEntry.UpdateDocument = m_xUpdateInformationProvider->getDocumentRoot(xContentNode); + + return uno::Any(aEntry); + } + catch( ucb::CommandAbortedException const &) + { + // action has been aborted + css::uno::Any anyEx = cppu::getCaughtException(); + throw lang::WrappedTargetException( "Command aborted", *this, anyEx ); + } + catch( uno::RuntimeException const & ) + { + // let runtime exception pass + throw; + } + catch( uno::Exception const &) + { + // document not accessible + css::uno::Any anyEx = cppu::getCaughtException(); + throw lang::WrappedTargetException( "Document not accessible", *this, anyEx ); + } + } + +private: + const rtl::Reference< UpdateInformationProvider > m_xUpdateInformationProvider; + const uno::Reference< xml::dom::XNodeList > m_xNodeList; + const sal_Int32 m_nNodes; + sal_Int32 m_nCount; +}; + + +class SingleUpdateInformationEnumeration : public ::cppu::WeakImplHelper< container::XEnumeration > +{ +public: + explicit SingleUpdateInformationEnumeration(const uno::Reference< xml::dom::XElement >& xElement) + : m_nCount(0) { m_aEntry.UpdateDocument = xElement; }; + + // XEnumeration + sal_Bool SAL_CALL hasMoreElements() override { return 0 == m_nCount; }; + uno::Any SAL_CALL nextElement() override + { + if( m_nCount > 0 ) + throw container::NoSuchElementException(OUString::number(m_nCount), *this); + + ++m_nCount; + return uno::Any(m_aEntry); + }; + +private: + sal_Int32 m_nCount; + deployment::UpdateInformationEntry m_aEntry; +}; + +UpdateInformationProvider::UpdateInformationProvider( + uno::Reference<uno::XComponentContext> xContext, + uno::Reference< ucb::XUniversalContentBroker > xUniversalContentBroker, + uno::Reference< xml::dom::XDocumentBuilder > xDocumentBuilder, + uno::Reference< xml::xpath::XXPathAPI > xXPathAPI) + : m_xContext(std::move(xContext)) + , m_xUniversalContentBroker(std::move(xUniversalContentBroker)) + , m_xDocumentBuilder(std::move(xDocumentBuilder)) + , m_xXPathAPI(std::move(xXPathAPI)) + , m_aRequestHeaderList(2) + , m_nCommandId(0) +{ + uno::Reference< lang::XMultiServiceFactory > xConfigurationProvider( + css::configuration::theDefaultProvider::get(m_xContext)); + + auto pRequestHeaderList = m_aRequestHeaderList.getArray(); + pRequestHeaderList[0].First = "Accept-Language"; + pRequestHeaderList[0].Second = getConfigurationItem( xConfigurationProvider, "org.openoffice.Setup/L10N", "ooLocale" ); +} + +bool +UpdateInformationProvider::isUserAgentExtended() const +{ + bool bExtendedUserAgent = false; + try { + uno::Reference< lang::XMultiServiceFactory > xConfigurationProvider( + css::configuration::theDefaultProvider::get(m_xContext)); + + uno::Any aExtended = getConfigurationItemAny( + xConfigurationProvider, + "org.openoffice.Office.Jobs/Jobs/UpdateCheck/Arguments", + "ExtendedUserAgent"); + aExtended >>= bExtendedUserAgent; + } catch (const uno::RuntimeException &) { + SAL_WARN("extensions.update", "Online update disabled"); + } + return bExtendedUserAgent; +} + +OUString UpdateInformationProvider::getUserAgent(bool bExtended) +{ + uno::Reference< lang::XMultiServiceFactory > xConfigurationProvider( + css::configuration::theDefaultProvider::get(m_xContext)); + + OUStringBuffer buf; + buf.append( + getConfigurationItem( + xConfigurationProvider, + "org.openoffice.Setup/Product", + "ooName")); + buf.append(' '); + buf.append( + getConfigurationItem( + xConfigurationProvider, + "org.openoffice.Setup/Product", + "ooSetupVersion")); + + OUString extension( + getConfigurationItem( + xConfigurationProvider, + "org.openoffice.Setup/Product", + "ooSetupExtension")); + if (!extension.isEmpty()) + buf.append(extension); + + OUString product(buf.makeStringAndClear()); + + OUString aUserAgent( "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":UpdateUserAgent}" ); + OUString aExtended; + if( bExtended ) + { + aExtended = Application::GetHWOSConfInfo(); + } + rtl::Bootstrap::expandMacros( aUserAgent ); + aUserAgent = aUserAgent.replaceAll("<PRODUCT>", product); + aUserAgent = aUserAgent.replaceAll("<OPTIONAL_OS_HW_DATA>", aExtended); + SAL_INFO("extensions.update", "UpdateUserAgent: " << aUserAgent); + // if you want to debug online updates from a dev version, then uncommenting this (adjust for platform) + // might be helpful + // return "LibreOffice 7.3.5.2 (184fe81b8c8c30d8b5082578aee2fed2ea847c01; Linux; X86_64; )"; + return aUserAgent; +} + +uno::Sequence< beans::StringPair > SAL_CALL UpdateInformationProvider::getUserRequestHeaders( + const OUString &aURL, ucb::WebDAVHTTPMethod ) +{ + bool bExtendedUserAgent; + uno::Sequence< beans::StringPair > aPair = m_aRequestHeaderList; + + // Internal use from cui/ some magic URLs + if( aURL.startsWith( "useragent:" ) ) + bExtendedUserAgent = (aURL == "useragent:extended"); + else + bExtendedUserAgent = isUserAgentExtended(); + + OUString aUserAgent = getUserAgent(bExtendedUserAgent); + + if( aUserAgent.isEmpty() ) + aPair.realloc(1); + else + { + auto pPair = aPair.getArray(); + pPair[1].First = "User-Agent"; + pPair[1].Second = aUserAgent; + } + + return aPair; +}; + +UpdateInformationProvider::~UpdateInformationProvider() +{ +} + +uno::Any +UpdateInformationProvider::getConfigurationItemAny(uno::Reference<lang::XMultiServiceFactory> const & configurationProvider, OUString const & node, OUString const & item) +{ + beans::PropertyValue aProperty; + aProperty.Name = "nodepath"; + aProperty.Value <<= node; + + uno::Sequence< uno::Any > aArgumentList{ uno::Any(aProperty) }; + uno::Reference< container::XNameAccess > xNameAccess( + configurationProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", + aArgumentList ), + uno::UNO_QUERY_THROW); + + return xNameAccess->getByName(item); +} + +OUString +UpdateInformationProvider::getConfigurationItem(uno::Reference<lang::XMultiServiceFactory> const & configurationProvider, OUString const & node, OUString const & item) +{ + OUString sRet; + getConfigurationItemAny(configurationProvider, node, item) >>= sRet; + return sRet; +} + +void +UpdateInformationProvider::storeCommandInfo( + sal_Int32 nCommandId, + uno::Reference< ucb::XCommandProcessor > const & rxCommandProcessor) +{ + osl::MutexGuard aGuard(m_aMutex); + + m_nCommandId = nCommandId; + m_xCommandProcessor = rxCommandProcessor; +} + +uno::Reference< io::XInputStream > +UpdateInformationProvider::load(const OUString& rURL) +{ + uno::Reference< ucb::XContentIdentifier > xId = m_xUniversalContentBroker->createContentIdentifier(rURL); + + if( !xId.is() ) + throw uno::RuntimeException( + "unable to obtain universal content id", *this); + + uno::Reference< ucb::XCommandProcessor > xCommandProcessor(m_xUniversalContentBroker->queryContent(xId), uno::UNO_QUERY_THROW); + rtl::Reference< ActiveDataSink > aSink(new ActiveDataSink()); + + // Disable KeepAlive in webdav - don't want millions of office + // instances phone home & clog up servers + uno::Sequence< beans::NamedValue > aProps { { "KeepAlive", uno::Any(false) } }; + + ucb::OpenCommandArgument3 aOpenArgument; + aOpenArgument.Mode = ucb::OpenMode::DOCUMENT; + aOpenArgument.Priority = 32768; + aOpenArgument.Sink = *aSink; + aOpenArgument.OpeningFlags = aProps; + + ucb::Command aCommand; + aCommand.Name = "open"; + aCommand.Argument <<= aOpenArgument; + + sal_Int32 nCommandId = xCommandProcessor->createCommandIdentifier(); + + storeCommandInfo(nCommandId, xCommandProcessor); + try + { + xCommandProcessor->execute(aCommand, nCommandId, + static_cast < XCommandEnvironment *> (this)); + } + catch( const uno::Exception & /* e */ ) + { + storeCommandInfo(0, uno::Reference< ucb::XCommandProcessor > ()); + + uno::Reference< ucb::XCommandProcessor2 > xCommandProcessor2(xCommandProcessor, uno::UNO_QUERY); + if( xCommandProcessor2.is() ) + xCommandProcessor2->releaseCommandIdentifier(nCommandId); + + throw; + } + storeCommandInfo(0, uno::Reference< ucb::XCommandProcessor > ()); + + uno::Reference< ucb::XCommandProcessor2 > xCommandProcessor2(xCommandProcessor, uno::UNO_QUERY); + if( xCommandProcessor2.is() ) + xCommandProcessor2->releaseCommandIdentifier(nCommandId); + + return INPUT_STREAM(aSink->getInputStream()); +} + + +// TODO: docu content node + +uno::Reference< xml::dom::XElement > +UpdateInformationProvider::getDocumentRoot(const uno::Reference< xml::dom::XNode >& rxNode) +{ + OSL_ASSERT(m_xDocumentBuilder.is()); + + uno::Reference< xml::dom::XElement > xElement(rxNode, uno::UNO_QUERY_THROW); + + // load the document referenced in 'src' attribute .. + if( xElement->hasAttribute( "src" ) ) + { + uno::Reference< xml::dom::XDocument > xUpdateXML = + m_xDocumentBuilder->parse(load(xElement->getAttribute( "src" ))); + + OSL_ASSERT( xUpdateXML.is() ); + + if( xUpdateXML.is() ) + return xUpdateXML->getDocumentElement(); + } + // .. or return the (single) child element + else + { + uno::Reference< xml::dom::XNodeList> xChildNodes = rxNode->getChildNodes(); + + // ignore possible #text nodes + sal_Int32 nmax = xChildNodes->getLength(); + for(sal_Int32 n=0; n < nmax; n++) + { + uno::Reference< xml::dom::XElement > xChildElement(xChildNodes->item(n), uno::UNO_QUERY); + if( xChildElement.is() ) + { + /* Copy the content to a dedicated document since XXPathAPI->selectNodeList + * seems to evaluate expression always relative to the root node. + */ + uno::Reference< xml::dom::XDocument > xUpdateXML = m_xDocumentBuilder->newDocument(); + xUpdateXML->appendChild( xUpdateXML->importNode(xChildElement, true ) ); + return xUpdateXML->getDocumentElement(); + } + } + } + + return uno::Reference< xml::dom::XElement > (); +} + + +uno::Reference< xml::dom::XNode > +UpdateInformationProvider::getChildNode(const uno::Reference< xml::dom::XNode >& rxNode, + std::u16string_view rName) +{ + OSL_ASSERT(m_xXPathAPI.is()); + try { + return m_xXPathAPI->selectSingleNode(rxNode, OUString::Concat("./atom:") + rName); + } catch (const xml::xpath::XPathException &) { + // ignore + return nullptr; + } +} + + +uno::Reference< container::XEnumeration > SAL_CALL +UpdateInformationProvider::getUpdateInformationEnumeration( + uno::Sequence< OUString > const & repositories, + OUString const & extensionId +) +{ + OSL_ASSERT(m_xDocumentBuilder.is()); + + // reset cancelled flag + m_bCancelled.reset(); + + for(sal_Int32 n=0; n<repositories.getLength(); n++) + { + try + { + uno::Reference< xml::dom::XDocument > xDocument = m_xDocumentBuilder->parse(load(repositories[n])); + uno::Reference< xml::dom::XElement > xElement; + + if( xDocument.is() ) + xElement = xDocument->getDocumentElement(); + + if( xElement.is() ) + { + if( xElement->getNodeName() == "feed" ) + { + OUString aXPathExpression; + + if( !extensionId.isEmpty() ) + aXPathExpression = "//atom:entry/atom:category[@term=\'" + extensionId + "\']/.."; + else + aXPathExpression = "//atom:entry"; + + uno::Reference< xml::dom::XNodeList > xNodeList; + try { + xNodeList = m_xXPathAPI->selectNodeList(xDocument, + aXPathExpression); + } catch (const xml::xpath::XPathException &) { + // ignore + } + + return new UpdateInformationEnumeration(xNodeList, this); + } + else + { + return new SingleUpdateInformationEnumeration(xElement); + } + } + + if( m_bCancelled.check() ) + break; + } + catch( uno::RuntimeException const& /*e*/) + { + // #i118675# ignore runtime exceptions for now + // especially the "unsatisfied query for interface of + // type com.sun.star.ucb.XCommandProcessor!" exception + } + + // rethrow only if last url in the list + catch( uno::Exception const & ) + { + if( n+1 >= repositories.getLength() ) + throw; + } + } + + return uno::Reference< container::XEnumeration >(); +} + + +uno::Sequence< uno::Reference< xml::dom::XElement > > SAL_CALL +UpdateInformationProvider::getUpdateInformation( + uno::Sequence< OUString > const & repositories, + OUString const & extensionId +) +{ + uno::Reference< container::XEnumeration > xEnumeration( + getUpdateInformationEnumeration(repositories, extensionId) + ); + + std::vector< uno::Reference< xml::dom::XElement > > aRet; + + if( xEnumeration.is() ) + { + while( xEnumeration->hasMoreElements() ) + { + try + { + deployment::UpdateInformationEntry aEntry; + if( (xEnumeration->nextElement() >>= aEntry ) && aEntry.UpdateDocument.is() ) + { + aRet.push_back(aEntry.UpdateDocument); + } + } + + catch( const lang::WrappedTargetException& e ) + { + // command aborted, return what we have got so far + if( e.TargetException.isExtractableTo( ::cppu::UnoType< css::ucb::CommandAbortedException >::get() ) ) + { + break; + } + + // ignore files that can't be loaded + } + } + } + + return comphelper::containerToSequence(aRet); +} + + +void SAL_CALL +UpdateInformationProvider::cancel() +{ + m_bCancelled.set(); + + osl::MutexGuard aGuard(m_aMutex); + if( m_xCommandProcessor.is() ) + m_xCommandProcessor->abort(m_nCommandId); +} + + +void SAL_CALL +UpdateInformationProvider::setInteractionHandler( + uno::Reference< task::XInteractionHandler > const & handler ) +{ + osl::MutexGuard aGuard(m_aMutex); + m_xInteractionHandler = handler; +} + + +uno::Reference< task::XInteractionHandler > SAL_CALL +UpdateInformationProvider::getInteractionHandler() +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( m_xInteractionHandler.is() ) + return m_xInteractionHandler; + else + { + try + { + // Supply an interaction handler that uses the password container + // service to obtain credentials without displaying a password gui. + + if ( !m_xPwContainerInteractionHandler.is() ) + m_xPwContainerInteractionHandler + = task::PasswordContainerInteractionHandler::create( + m_xContext ); + } + catch ( uno::RuntimeException const & ) + { + throw; + } + catch ( uno::Exception const & ) + { + } + return m_xPwContainerInteractionHandler; + } +} + + + +OUString SAL_CALL +UpdateInformationProvider::getImplementationName() +{ + return "vnd.sun.UpdateInformationProvider"; +} + + +uno::Sequence< OUString > SAL_CALL +UpdateInformationProvider::getSupportedServiceNames() +{ + return { "com.sun.star.deployment.UpdateInformationProvider" }; +} + +sal_Bool SAL_CALL +UpdateInformationProvider::supportsService( OUString const & serviceName ) +{ + return cppu::supportsService(this, serviceName); +} + +} // anonymous namespace + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_update_UpdateInformationProvider_get_implementation( + css::uno::XComponentContext* xContext , css::uno::Sequence<css::uno::Any> const&) +{ + uno::Reference< ucb::XUniversalContentBroker > xUniversalContentBroker = + ucb::UniversalContentBroker::create(xContext); + + uno::Reference< xml::dom::XDocumentBuilder > xDocumentBuilder( + xml::dom::DocumentBuilder::create(xContext)); + + uno::Reference< xml::xpath::XXPathAPI > xXPath = xml::xpath::XPathAPI::create( xContext ); + + xXPath->registerNS( "atom", "http://www.w3.org/2005/Atom" ); + + return cppu::acquire( + new UpdateInformationProvider(xContext, xUniversalContentBroker, xDocumentBuilder, xXPath)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/ui/updatecheckui.cxx b/extensions/source/update/ui/updatecheckui.cxx new file mode 100644 index 0000000000..492cb92669 --- /dev/null +++ b/extensions/source/update/ui/updatecheckui.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 <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/document/XDocumentEventListener.hpp> +#include <com/sun/star/document/XDocumentEventBroadcaster.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/theGlobalEventBroadcaster.hpp> +#include <com/sun/star/graphic/GraphicProvider.hpp> +#include <com/sun/star/graphic/XGraphicProvider.hpp> +#include <com/sun/star/task/XJob.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <unotools/resmgr.hxx> +#include <vcl/image.hxx> +#include <vcl/menubarupdateicon.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <sfx2/strings.hrc> + +#include <bitmaps.hlst> + +constexpr OUString PROPERTY_TITLE = u"BubbleHeading"_ustr; +constexpr OUString PROPERTY_TEXT = u"BubbleText"_ustr; +constexpr OUString PROPERTY_IMAGE = u"BubbleImageURL"_ustr; +constexpr OUString PROPERTY_SHOW_BUBBLE = u"BubbleVisible"_ustr; +constexpr OUString PROPERTY_CLICK_HDL = u"MenuClickHDL"_ustr; +constexpr OUString PROPERTY_SHOW_MENUICON = u"MenuIconVisible"_ustr; + +using namespace ::com::sun::star; + + +namespace +{ + +class UpdateCheckUI : public ::cppu::WeakImplHelper + < lang::XServiceInfo, document::XDocumentEventListener, beans::XPropertySet > +{ + uno::Reference< uno::XComponentContext > m_xContext; + uno::Reference< task::XJob > mrJob; + OUString maBubbleImageURL; + MenuBarUpdateIconManager maBubbleManager; + std::locale maSfxLocale; + +private: + DECL_LINK(ClickHdl, LinkParamNone*, void); + + Image GetBubbleImage( OUString const &rURL ); + +public: + explicit UpdateCheckUI(const uno::Reference<uno::XComponentContext>&); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(OUString const & serviceName) override; + virtual uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XDocumentEventListener + virtual void SAL_CALL documentEventOccured(const document::DocumentEvent& Event) override; + virtual void SAL_CALL disposing(const lang::EventObject& Event) override; + + //XPropertySet + virtual uno::Reference< beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue(const OUString& PropertyName, const uno::Any& aValue) override; + virtual uno::Any SAL_CALL getPropertyValue(const OUString& PropertyName) override; + virtual void SAL_CALL addPropertyChangeListener(const OUString& PropertyName, + const uno::Reference< beans::XPropertyChangeListener > & aListener) override; + virtual void SAL_CALL removePropertyChangeListener(const OUString& PropertyName, + const uno::Reference< beans::XPropertyChangeListener > & aListener) override; + virtual void SAL_CALL addVetoableChangeListener(const OUString& PropertyName, + const uno::Reference< beans::XVetoableChangeListener > & aListener) override; + virtual void SAL_CALL removeVetoableChangeListener(const OUString& PropertyName, + const uno::Reference< beans::XVetoableChangeListener > & aListener) override; +}; + +UpdateCheckUI::UpdateCheckUI(const uno::Reference<uno::XComponentContext>& xContext) + : m_xContext(xContext) +{ + maSfxLocale = Translate::Create("sfx"); + + uno::Reference< document::XDocumentEventBroadcaster > xBroadcaster( frame::theGlobalEventBroadcaster::get(m_xContext) ); + xBroadcaster->addDocumentEventListener( this ); + + SolarMutexGuard aGuard; + + maBubbleManager.SetBubbleImage(GetBubbleImage(maBubbleImageURL)); + maBubbleManager.SetClickHdl(LINK(this, UpdateCheckUI, ClickHdl)); +} + +OUString SAL_CALL +UpdateCheckUI::getImplementationName() +{ + return "vnd.sun.UpdateCheckUI"; +} + +uno::Sequence< OUString > SAL_CALL +UpdateCheckUI::getSupportedServiceNames() +{ + return { "com.sun.star.setup.UpdateCheckUI" }; +} + +sal_Bool SAL_CALL +UpdateCheckUI::supportsService( OUString const & serviceName ) +{ + return cppu::supportsService(this, serviceName); +} + +Image UpdateCheckUI::GetBubbleImage( OUString const &rURL ) +{ + Image aImage; + + if ( !maBubbleImageURL.isEmpty() ) + { + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + + if( !xContext.is() ) + throw uno::RuntimeException( + "UpdateCheckUI: unable to obtain service manager from component context" ); + + try + { + uno::Reference< graphic::XGraphicProvider > xGraphProvider(graphic::GraphicProvider::create(xContext)); + uno::Sequence< beans::PropertyValue > aMediaProps{ comphelper::makePropertyValue("URL", + rURL) }; + uno::Reference< graphic::XGraphic > xGraphic = xGraphProvider->queryGraphic( aMediaProps ); + if ( xGraphic.is() ) + { + aImage = Image( xGraphic ); + } + } + catch( const uno::Exception& ) + { + } + } + + if ( aImage.GetSizePixel().Width() == 0 ) + aImage = Image(StockImage::Yes, SV_RESID_BITMAP_INFOBOX); + + return aImage; +} + +void SAL_CALL UpdateCheckUI::documentEventOccured(const document::DocumentEvent& rEvent) +{ + SolarMutexGuard aGuard; + + if( rEvent.EventName == "OnPrepareViewClosing" ) + maBubbleManager.RemoveBubbleWindow(); +} + +void SAL_CALL UpdateCheckUI::disposing(const lang::EventObject&) +{ +} + +uno::Reference< beans::XPropertySetInfo > UpdateCheckUI::getPropertySetInfo() +{ + return nullptr; +} + +void UpdateCheckUI::setPropertyValue(const OUString& rPropertyName, + const uno::Any& rValue) +{ + SolarMutexGuard aGuard; + + OUString aString; + + if( rPropertyName == PROPERTY_TITLE ) { + rValue >>= aString; + maBubbleManager.SetBubbleTitle(aString); + } + else if( rPropertyName == PROPERTY_TEXT ) { + rValue >>= aString; + maBubbleManager.SetBubbleText(aString); + } + else if( rPropertyName == PROPERTY_IMAGE ) { + rValue >>= aString; + if ( aString != maBubbleImageURL ) { + maBubbleImageURL = aString; + maBubbleManager.SetBubbleImage(GetBubbleImage(maBubbleImageURL)); + } + } + else if( rPropertyName == PROPERTY_SHOW_BUBBLE ) { + bool bShowBubble= false; + rValue >>= bShowBubble; + maBubbleManager.SetShowBubble(bShowBubble); + } + else if( rPropertyName == PROPERTY_CLICK_HDL ) { + uno::Reference< task::XJob > aJob; + rValue >>= aJob; + if ( !aJob.is() ) + throw lang::IllegalArgumentException(); + mrJob = aJob; + } + else if (rPropertyName == PROPERTY_SHOW_MENUICON ) { + bool bShowMenuIcon = false; + rValue >>= bShowMenuIcon; + maBubbleManager.SetShowMenuIcon(bShowMenuIcon); + } + else + throw beans::UnknownPropertyException(rPropertyName); +} + +uno::Any UpdateCheckUI::getPropertyValue(const OUString& rPropertyName) +{ + SolarMutexGuard aGuard; + + uno::Any aRet; + + if( rPropertyName == PROPERTY_TITLE ) + aRet <<= maBubbleManager.GetBubbleTitle(); + else if( rPropertyName == PROPERTY_TEXT ) + aRet <<= maBubbleManager.GetBubbleText(); + else if( rPropertyName == PROPERTY_SHOW_BUBBLE ) + aRet <<= maBubbleManager.GetShowBubble(); + else if( rPropertyName == PROPERTY_IMAGE ) + aRet <<= maBubbleImageURL; + else if( rPropertyName == PROPERTY_CLICK_HDL ) + aRet <<= mrJob; + else if( rPropertyName == PROPERTY_SHOW_MENUICON ) + aRet <<= maBubbleManager.GetShowMenuIcon(); + else + throw beans::UnknownPropertyException(rPropertyName); + + return aRet; +} + + +void UpdateCheckUI::addPropertyChangeListener( const OUString& /*aPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener > & /*aListener*/) +{ + //no bound properties +} + + +void UpdateCheckUI::removePropertyChangeListener( const OUString& /*aPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener > & /*aListener*/) +{ + //no bound properties +} + +void UpdateCheckUI::addVetoableChangeListener( const OUString& /*aPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener > & /*aListener*/) +{ + //no vetoable properties +} + +void UpdateCheckUI::removeVetoableChangeListener( const OUString& /*aPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener > & /*aListener*/) +{ + //no vetoable properties +} + +IMPL_LINK_NOARG(UpdateCheckUI, ClickHdl, LinkParamNone*, void) +{ + SolarMutexGuard aGuard; + + if ( mrJob.is() ) + { + try { + uno::Sequence<beans::NamedValue> aEmpty; + mrJob->execute( aEmpty ); + } + catch(const uno::Exception&) { + std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Warning, VclButtonsType::Ok, + Translate::get(STR_NO_WEBBROWSER_FOUND, maSfxLocale))); + xErrorBox->run(); + } + } +} + +} // anonymous namespace + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_update_UpdateCheckUI_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + SolarMutexGuard aGuard; + return cppu::acquire(new UpdateCheckUI(context)); +} + + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/update/ui/updchk.component b/extensions/source/update/ui/updchk.component new file mode 100644 index 0000000000..19c7dd86e6 --- /dev/null +++ b/extensions/source/update/ui/updchk.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="vnd.sun.UpdateCheckUI" + constructor="extensions_update_UpdateCheckUI_get_implementation"> + <service name="com.sun.star.setup.UpdateCheckUI"/> + </implementation> +</component> |