diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /desktop/source/deployment/gui | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'desktop/source/deployment/gui')
20 files changed, 7680 insertions, 0 deletions
diff --git a/desktop/source/deployment/gui/deploymentgui.component b/desktop/source/deployment/gui/deploymentgui.component new file mode 100644 index 000000000..3b56863c1 --- /dev/null +++ b/desktop/source/deployment/gui/deploymentgui.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="com.sun.star.comp.deployment.ui.LicenseDialog" + constructor="desktop_LicenseDialog_get_implementation"> + <service name="com.sun.star.deployment.ui.LicenseDialog"/> + </implementation> + <implementation name="com.sun.star.comp.deployment.ui.PackageManagerDialog" + constructor="desktop_ServiceImpl_get_implementation"> + <service name="com.sun.star.deployment.ui.PackageManagerDialog"/> + </implementation> + <implementation name="com.sun.star.comp.deployment.ui.UpdateRequiredDialog" + constructor="desktop_UpdateRequiredDialogService_get_implementation"> + <service name="com.sun.star.deployment.ui.UpdateRequiredDialog"/> + </implementation> +</component> diff --git a/desktop/source/deployment/gui/dp_gui.h b/desktop/source/deployment/gui/dp_gui.h new file mode 100644 index 000000000..cb2932ca4 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui.h @@ -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 + +namespace dp_gui { + +enum PackageState { REGISTERED, NOT_REGISTERED, AMBIGUOUS, NOT_AVAILABLE }; + +} // namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_dependencydialog.cxx b/desktop/source/deployment/gui/dp_gui_dependencydialog.cxx new file mode 100644 index 000000000..f94b29891 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_dependencydialog.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 <sal/config.h> + +#include <rtl/ustring.hxx> +#include <vcl/weld.hxx> + +#include "dp_gui_dependencydialog.hxx" + +using dp_gui::DependencyDialog; + +DependencyDialog::DependencyDialog(weld::Window* parent, std::vector<OUString> const& dependencies) + : GenericDialogController(parent, "desktop/ui/dependenciesdialog.ui", "Dependencies") + , m_xList(m_xBuilder->weld_tree_view("depListTreeview")) +{ + m_xList->set_size_request(-1, m_xList->get_height_rows(10)); + for (auto const& dependency : dependencies) + { + m_xList->append_text(dependency); + } +} + +DependencyDialog::~DependencyDialog() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_dependencydialog.hxx b/desktop/source/deployment/gui/dp_gui_dependencydialog.hxx new file mode 100644 index 000000000..bdbab41d1 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_dependencydialog.hxx @@ -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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <vcl/weld.hxx> + +#include <vector> + +namespace vcl +{ +class Window; +} + +namespace dp_gui +{ +class DependencyDialog : public weld::GenericDialogController +{ +public: + DependencyDialog(weld::Window* parent, std::vector<OUString> const& dependencies); + virtual ~DependencyDialog() override; + +private: + DependencyDialog(DependencyDialog const&) = delete; + DependencyDialog& operator=(DependencyDialog const&) = delete; + + std::unique_ptr<weld::TreeView> m_xList; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_dialog2.cxx b/desktop/source/deployment/gui/dp_gui_dialog2.cxx new file mode 100644 index 000000000..7fd0bdac3 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_dialog2.cxx @@ -0,0 +1,1403 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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_extensions.h> + +#include <strings.hrc> +#include <helpids.h> + +#include "dp_gui.h" +#include "dp_gui_dialog2.hxx" +#include "dp_gui_extlistbox.hxx" +#include <dp_shared.hxx> +#include "dp_gui_theextmgr.hxx" +#include "dp_gui_extensioncmdqueue.hxx" +#include <dp_misc.h> +#include <dp_update.hxx> +#include <dp_identifier.hxx> + +#include <fpicker/strings.hrc> + +#include <utility> +#include <vcl/commandevent.hxx> +#include <vcl/svapp.hxx> + +#include <osl/mutex.hxx> +#include <sal/log.hxx> +#include <rtl/ustrbuf.hxx> + +#include <svtools/restartdialog.hxx> + +#include <sfx2/filedlghelper.hxx> +#include <sfx2/sfxdlg.hxx> + +#include <comphelper/anytostring.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <comphelper/processfactory.hxx> +#include <tools/diagnose_ex.h> +#include <unotools/configmgr.hxx> + +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/system/SystemShellExecuteFlags.hpp> +#include <com/sun/star/system/SystemShellExecute.hpp> + +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/ui/dialogs/XFilePicker3.hpp> + +#include <officecfg/Office/ExtensionManager.hxx> + +#include <map> +#include <memory> +#include <vector> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::system; + + +namespace dp_gui { + +constexpr OUStringLiteral USER_PACKAGE_MANAGER = u"user"; +constexpr OUStringLiteral SHARED_PACKAGE_MANAGER = u"shared"; +constexpr OUStringLiteral BUNDLED_PACKAGE_MANAGER = u"bundled"; + +// ExtBoxWithBtns_Impl +class ExtBoxWithBtns_Impl : public ExtensionBox_Impl +{ + bool m_bInterfaceLocked; + + ExtMgrDialog* m_pParent; + + void SetButtonStatus( const TEntry_Impl& rEntry ); + OString ShowPopupMenu( const Point &rPos, const tools::Long nPos ); + +public: + explicit ExtBoxWithBtns_Impl(std::unique_ptr<weld::ScrolledWindow> xScroll); + + void InitFromDialog(ExtMgrDialog *pParentDialog); + + virtual bool MouseButtonDown( const MouseEvent& rMEvt ) override; + virtual bool Command( const CommandEvent& rCEvt ) override; + + virtual void RecalcAll() override; + virtual void selectEntry( const tools::Long nPos ) override; + + void enableButtons( bool bEnable ); +}; + +ExtBoxWithBtns_Impl::ExtBoxWithBtns_Impl(std::unique_ptr<weld::ScrolledWindow> xScroll) + : ExtensionBox_Impl(std::move(xScroll)) + , m_bInterfaceLocked(false) + , m_pParent(nullptr) +{ +} + +void ExtBoxWithBtns_Impl::InitFromDialog(ExtMgrDialog *pParentDialog) +{ + setExtensionManager(pParentDialog->getExtensionManager()); + + m_pParent = pParentDialog; +} + +void ExtBoxWithBtns_Impl::RecalcAll() +{ + const sal_Int32 nActive = getSelIndex(); + + if ( nActive != ExtensionBox_Impl::ENTRY_NOTFOUND ) + { + SetButtonStatus( GetEntryData( nActive) ); + } + else + { + m_pParent->enableOptionsButton( false ); + m_pParent->enableRemoveButton( false ); + m_pParent->enableEnableButton( false ); + } + + ExtensionBox_Impl::RecalcAll(); +} + + +//This function may be called with nPos < 0 +void ExtBoxWithBtns_Impl::selectEntry( const tools::Long nPos ) +{ + if ( HasActive() && ( nPos == getSelIndex() ) ) + return; + + ExtensionBox_Impl::selectEntry( nPos ); +} + +void ExtBoxWithBtns_Impl::SetButtonStatus(const TEntry_Impl& rEntry) +{ + bool bShowOptionBtn = true; + + rEntry->m_bHasButtons = false; + if ( ( rEntry->m_eState == REGISTERED ) || ( rEntry->m_eState == NOT_AVAILABLE ) ) + { + m_pParent->enableButtontoEnable( false ); + } + else + { + m_pParent->enableButtontoEnable( true ); + bShowOptionBtn = false; + } + + if ( ( !rEntry->m_bUser || ( rEntry->m_eState == NOT_AVAILABLE ) || rEntry->m_bMissingDeps ) + && !rEntry->m_bMissingLic ) + { + m_pParent->enableEnableButton( false ); + } + else + { + m_pParent->enableEnableButton( !rEntry->m_bLocked ); + rEntry->m_bHasButtons = true; + } + + if ( rEntry->m_bHasOptions && bShowOptionBtn ) + { + m_pParent->enableOptionsButton( true ); + rEntry->m_bHasButtons = true; + } + else + { + m_pParent->enableOptionsButton( false ); + } + + if ( rEntry->m_bUser || rEntry->m_bShared ) + { + m_pParent->enableRemoveButton( !rEntry->m_bLocked ); + rEntry->m_bHasButtons = true; + } + else + { + m_pParent->enableRemoveButton( false ); + } +} + +bool ExtBoxWithBtns_Impl::Command(const CommandEvent& rCEvt) +{ + if (rCEvt.GetCommand() != CommandEventId::ContextMenu) + return ExtensionBox_Impl::Command(rCEvt); + + const Point aMousePos(rCEvt.GetMousePosPixel()); + const auto nPos = PointToPos(aMousePos); + OString sCommand = ShowPopupMenu(aMousePos, nPos); + + if (sCommand == "CMD_ENABLE") + m_pParent->enablePackage( GetEntryData( nPos )->m_xPackage, true ); + else if (sCommand == "CMD_DISABLE") + m_pParent->enablePackage( GetEntryData( nPos )->m_xPackage, false ); + else if (sCommand == "CMD_UPDATE") + m_pParent->updatePackage( GetEntryData( nPos )->m_xPackage ); + else if (sCommand == "CMD_REMOVE") + m_pParent->removePackage( GetEntryData( nPos )->m_xPackage ); + else if (sCommand == "CMD_SHOW_LICENSE") + { + m_pParent->incBusy(); + ShowLicenseDialog aLicenseDlg(m_pParent->getDialog(), GetEntryData(nPos)->m_xPackage); + aLicenseDlg.run(); + m_pParent->decBusy(); + } + + return true; +} + +OString ExtBoxWithBtns_Impl::ShowPopupMenu( const Point & rPos, const tools::Long nPos ) +{ + if ( nPos >= static_cast<tools::Long>(getItemCount()) ) + return "CMD_NONE"; + + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(nullptr, "desktop/ui/extensionmenu.ui")); + std::unique_ptr<weld::Menu> xPopup(xBuilder->weld_menu("menu")); + +#if ENABLE_EXTENSION_UPDATE + xPopup->append("CMD_UPDATE", DpResId( RID_CTX_ITEM_CHECK_UPDATE ) ); +#endif + + if ( ! GetEntryData( nPos )->m_bLocked ) + { + if ( GetEntryData( nPos )->m_bUser ) + { + if ( GetEntryData( nPos )->m_eState == REGISTERED ) + xPopup->append("CMD_DISABLE", DpResId(RID_CTX_ITEM_DISABLE)); + else if ( GetEntryData( nPos )->m_eState != NOT_AVAILABLE ) + xPopup->append("CMD_ENABLE", DpResId(RID_CTX_ITEM_ENABLE)); + } + if (!officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionRemoval::get()) + { + xPopup->append("CMD_REMOVE", DpResId(RID_CTX_ITEM_REMOVE)); + } + } + + if ( !GetEntryData( nPos )->m_sLicenseText.isEmpty() ) + xPopup->append("CMD_SHOW_LICENSE", DpResId(RID_STR_SHOW_LICENSE_CMD)); + + return xPopup->popup_at_rect(GetDrawingArea(), tools::Rectangle(rPos, Size(1, 1))); +} + +bool ExtBoxWithBtns_Impl::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if (m_bInterfaceLocked) + return false; + return ExtensionBox_Impl::MouseButtonDown(rMEvt); +} + +void ExtBoxWithBtns_Impl::enableButtons( bool bEnable ) +{ + m_bInterfaceLocked = ! bEnable; + + if ( bEnable ) + { + sal_Int32 nIndex = getSelIndex(); + if ( nIndex != ExtensionBox_Impl::ENTRY_NOTFOUND ) + SetButtonStatus( GetEntryData( nIndex ) ); + } + else + { + m_pParent->enableEnableButton( false ); + m_pParent->enableOptionsButton( false ); + m_pParent->enableRemoveButton( false ); + } +} + +// DialogHelper + +DialogHelper::DialogHelper(const uno::Reference< uno::XComponentContext > &xContext, + weld::Window* pWindow) + : m_pWindow(pWindow) + , m_nEventID(nullptr) +{ + m_xContext = xContext; +} + +DialogHelper::~DialogHelper() +{ + if ( m_nEventID ) + Application::RemoveUserEvent( m_nEventID ); +} + + +bool DialogHelper::IsSharedPkgMgr( const uno::Reference< deployment::XPackage > &xPackage ) +{ + return xPackage->getRepositoryName() == SHARED_PACKAGE_MANAGER; +} + +bool DialogHelper::continueOnSharedExtension( const uno::Reference< deployment::XPackage > &xPackage, + weld::Widget* pParent, + TranslateId pResID, + bool &bHadWarning ) +{ + if ( !bHadWarning && IsSharedPkgMgr( xPackage ) ) + { + const SolarMutexGuard guard; + incBusy(); + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent, + VclMessageType::Warning, VclButtonsType::OkCancel, DpResId(pResID))); + bHadWarning = true; + + bool bRet = RET_OK == xBox->run(); + xBox.reset(); + decBusy(); + return bRet; + } + else + return true; +} + +void DialogHelper::openWebBrowser(const OUString& sURL, const OUString& sTitle) +{ + if ( sURL.isEmpty() ) // Nothing to do, when the URL is empty + return; + + try + { + uno::Reference< XSystemShellExecute > xSystemShellExecute( + SystemShellExecute::create(m_xContext)); + //throws css::lang::IllegalArgumentException, css::system::SystemShellExecuteException + xSystemShellExecute->execute( sURL, OUString(), SystemShellExecuteFlags::URIS_ONLY ); + } + catch ( const uno::Exception& ) + { + uno::Any exc( ::cppu::getCaughtException() ); + OUString msg( ::comphelper::anyToString( exc ) ); + const SolarMutexGuard guard; + incBusy(); + std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(getFrameWeld(), + VclMessageType::Warning, VclButtonsType::Ok, msg)); + xErrorBox->set_title(sTitle); + xErrorBox->run(); + xErrorBox.reset(); + decBusy(); + } +} + +bool DialogHelper::installExtensionWarn(std::u16string_view rExtensionName) +{ + const SolarMutexGuard guard; + + // Check if extension installation is disabled in the expert configurations + if (officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionInstallation::get()) + { + incBusy(); + std::unique_ptr<weld::MessageDialog> xWarnBox(Application::CreateMessageDialog(getFrameWeld(), + VclMessageType::Warning, VclButtonsType::Ok, + DpResId(RID_STR_WARNING_INSTALL_EXTENSION_DISABLED))); + xWarnBox->run(); + xWarnBox.reset(); + decBusy(); + + return false; + } + + incBusy(); + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(getFrameWeld(), + VclMessageType::Warning, VclButtonsType::OkCancel, + DpResId(RID_STR_WARNING_INSTALL_EXTENSION))); + OUString sText(xInfoBox->get_primary_text()); + sText = sText.replaceAll("%NAME", rExtensionName); + xInfoBox->set_primary_text(sText); + + bool bRet = RET_OK == xInfoBox->run(); + xInfoBox.reset(); + decBusy(); + return bRet; +} + +bool DialogHelper::installForAllUsers(bool &bInstallForAll) +{ + const SolarMutexGuard guard; + incBusy(); + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(getFrameWeld(), "desktop/ui/installforalldialog.ui")); + std::unique_ptr<weld::MessageDialog> xQuery(xBuilder->weld_message_dialog("InstallForAllDialog")); + short nRet = xQuery->run(); + xQuery.reset(); + decBusy(); + if (nRet == RET_CANCEL) + return false; + + bInstallForAll = ( nRet == RET_NO ); + return true; +} + +void DialogHelper::PostUserEvent( const Link<void*,void>& rLink, void* pCaller ) +{ + if ( m_nEventID ) + Application::RemoveUserEvent( m_nEventID ); + + m_nEventID = Application::PostUserEvent(rLink, pCaller); +} + +// ExtMgrDialog +ExtMgrDialog::ExtMgrDialog(weld::Window *pParent, TheExtensionManager *pManager) + : GenericDialogController(pParent, "desktop/ui/extensionmanager.ui", "ExtensionManagerDialog") + , DialogHelper(pManager->getContext(), m_xDialog.get()) + , m_sAddPackages(DpResId(RID_STR_ADD_PACKAGES)) + , m_bHasProgress(false) + , m_bProgressChanged(false) + , m_bStartProgress(false) + , m_bStopProgress(false) + , m_bEnableWarning(false) + , m_bDisableWarning(false) + , m_bDeleteWarning(false) + , m_bClosed(false) + , m_nProgress(0) + , m_aIdle( "ExtMgrDialog m_aIdle TimeOutHdl" ) + , m_pManager(pManager) + , m_xExtensionBox(new ExtBoxWithBtns_Impl(m_xBuilder->weld_scrolled_window("scroll", true))) + , m_xExtensionBoxWnd(new weld::CustomWeld(*m_xBuilder, "extensions", *m_xExtensionBox)) + , m_xOptionsBtn(m_xBuilder->weld_button("optionsbtn")) + , m_xAddBtn(m_xBuilder->weld_button("addbtn")) + , m_xRemoveBtn(m_xBuilder->weld_button("removebtn")) + , m_xEnableBtn(m_xBuilder->weld_button("enablebtn")) + , m_xUpdateBtn(m_xBuilder->weld_button("updatebtn")) + , m_xCloseBtn(m_xBuilder->weld_button("close")) + , m_xBundledCbx(m_xBuilder->weld_check_button("bundled")) + , m_xSharedCbx(m_xBuilder->weld_check_button("shared")) + , m_xUserCbx(m_xBuilder->weld_check_button("user")) + , m_xGetExtensions(m_xBuilder->weld_link_button("getextensions")) + , m_xProgressText(m_xBuilder->weld_label("progressft")) + , m_xProgressBar(m_xBuilder->weld_progress_bar("progressbar")) + , m_xCancelBtn(m_xBuilder->weld_button("cancel")) + , m_xSearchEntry(m_xBuilder->weld_entry("search")) +{ + m_xExtensionBox->InitFromDialog(this); + + m_xEnableBtn->set_help_id(HID_EXTENSION_MANAGER_LISTBOX_ENABLE); + + m_xOptionsBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleOptionsBtn ) ); + m_xAddBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleAddBtn ) ); + m_xRemoveBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleRemoveBtn ) ); + m_xEnableBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleEnableBtn ) ); + m_xCloseBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleCloseBtn ) ); + + m_xCancelBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleCancelBtn ) ); + + m_xBundledCbx->connect_toggled( LINK( this, ExtMgrDialog, HandleExtTypeCbx ) ); + m_xSharedCbx->connect_toggled( LINK( this, ExtMgrDialog, HandleExtTypeCbx ) ); + m_xUserCbx->connect_toggled( LINK( this, ExtMgrDialog, HandleExtTypeCbx ) ); + + m_xSearchEntry->connect_changed( LINK( this, ExtMgrDialog, HandleSearch ) ); + + m_xBundledCbx->set_active(true); + m_xSharedCbx->set_active(true); + m_xUserCbx->set_active(true); + + m_xProgressBar->hide(); + +#if ENABLE_EXTENSION_UPDATE + m_xUpdateBtn->connect_clicked( LINK( this, ExtMgrDialog, HandleUpdateBtn ) ); + m_xUpdateBtn->set_sensitive(false); +#else + m_xUpdateBtn->hide(); +#endif + + if (officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionInstallation::get()) + { + m_xAddBtn->set_sensitive(false); + m_xAddBtn->set_tooltip_text(DpResId(RID_STR_WARNING_INSTALL_EXTENSION_DISABLED)); + } + if (officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionRemoval::get()) + { + m_xRemoveBtn->set_sensitive(false); + m_xRemoveBtn->set_tooltip_text(DpResId(RID_STR_WARNING_REMOVE_EXTENSION_DISABLED)); + } + + m_aIdle.SetPriority(TaskPriority::LOWEST); + m_aIdle.SetInvokeHandler( LINK( this, ExtMgrDialog, TimeOutHdl ) ); +} + +ExtMgrDialog::~ExtMgrDialog() +{ + m_aIdle.Stop(); +} + +void ExtMgrDialog::setGetExtensionsURL( const OUString &rURL ) +{ + m_xGetExtensions->set_uri( rURL ); +} + +void ExtMgrDialog::addPackageToList( const uno::Reference< deployment::XPackage > &xPackage, + bool bLicenseMissing ) +{ + const SolarMutexGuard aGuard; + m_xUpdateBtn->set_sensitive(true); + + bool bSearchMatch = m_xSearchEntry->get_text().isEmpty(); + if (!m_xSearchEntry->get_text().isEmpty() + && xPackage->getDisplayName().toAsciiLowerCase().indexOf( + m_xSearchEntry->get_text().toAsciiLowerCase()) + >= 0) + { + bSearchMatch = true; + } + + if (!bSearchMatch) + return; + + if (m_xBundledCbx->get_active() && (xPackage->getRepositoryName() == BUNDLED_PACKAGE_MANAGER) ) + { + m_xExtensionBox->addEntry( xPackage, bLicenseMissing ); + } + else if (m_xSharedCbx->get_active() && (xPackage->getRepositoryName() == SHARED_PACKAGE_MANAGER) ) + { + m_xExtensionBox->addEntry( xPackage, bLicenseMissing ); + } + else if (m_xUserCbx->get_active() && (xPackage->getRepositoryName() == USER_PACKAGE_MANAGER )) + { + m_xExtensionBox->addEntry( xPackage, bLicenseMissing ); + } +} + +void ExtMgrDialog::updateList() +{ + // re-creates the list of packages with addEntry selecting the packages + prepareChecking(); + m_pManager->createPackageList(); + checkEntries(); +} + +void ExtMgrDialog::prepareChecking() +{ + m_xExtensionBox->prepareChecking(); +} + +void ExtMgrDialog::checkEntries() +{ + const SolarMutexGuard guard; + m_xExtensionBox->checkEntries(); +} + +bool ExtMgrDialog::removeExtensionWarn(std::u16string_view rExtensionName) +{ + const SolarMutexGuard guard; + incBusy(); + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::OkCancel, + DpResId(RID_STR_WARNING_REMOVE_EXTENSION))); + + OUString sText(xInfoBox->get_primary_text()); + sText = sText.replaceAll("%NAME", rExtensionName); + xInfoBox->set_primary_text(sText); + + bool bRet = RET_OK == xInfoBox->run(); + xInfoBox.reset(); + decBusy(); + + return bRet; +} + +void ExtMgrDialog::enablePackage( const uno::Reference< deployment::XPackage > &xPackage, + bool bEnable ) +{ + if ( !xPackage.is() ) + return; + + if ( bEnable ) + { + if (!continueOnSharedExtension(xPackage, m_xDialog.get(), RID_STR_WARNING_ENABLE_SHARED_EXTENSION, m_bEnableWarning)) + return; + } + else + { + if (!continueOnSharedExtension(xPackage, m_xDialog.get(), RID_STR_WARNING_DISABLE_SHARED_EXTENSION, m_bDisableWarning)) + return; + } + + m_pManager->getCmdQueue()->enableExtension( xPackage, bEnable ); +} + + +void ExtMgrDialog::removePackage( const uno::Reference< deployment::XPackage > &xPackage ) +{ + if ( !xPackage.is() ) + return; + + if ( !IsSharedPkgMgr( xPackage ) || m_bDeleteWarning ) + { + if ( ! removeExtensionWarn( xPackage->getDisplayName() ) ) + return; + } + + if (!continueOnSharedExtension(xPackage, m_xDialog.get(), RID_STR_WARNING_REMOVE_SHARED_EXTENSION, m_bDeleteWarning)) + return; + + m_pManager->getCmdQueue()->removeExtension( xPackage ); +} + + +void ExtMgrDialog::updatePackage( const uno::Reference< deployment::XPackage > &xPackage ) +{ + if ( !xPackage.is() ) + return; + + // get the extension with highest version + uno::Sequence<uno::Reference<deployment::XPackage> > seqExtensions = + m_pManager->getExtensionManager()->getExtensionsWithSameIdentifier( + dp_misc::getIdentifier(xPackage), xPackage->getName(), uno::Reference<ucb::XCommandEnvironment>()); + uno::Reference<deployment::XPackage> extension = + dp_misc::getExtensionWithHighestVersion(seqExtensions); + OSL_ASSERT(extension.is()); + std::vector< css::uno::Reference< css::deployment::XPackage > > vEntries { extension }; + + m_pManager->getCmdQueue()->checkForUpdates( std::move(vEntries) ); +} + + +bool ExtMgrDialog::acceptLicense( const uno::Reference< deployment::XPackage > &xPackage ) +{ + if ( !xPackage.is() ) + return false; + + m_pManager->getCmdQueue()->acceptLicense( xPackage ); + + return true; +} + + +uno::Sequence< OUString > ExtMgrDialog::raiseAddPicker() +{ + sfx2::FileDialogHelper aDlgHelper(ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE, FileDialogFlags::NONE, m_xDialog.get()); + aDlgHelper.SetContext(sfx2::FileDialogHelper::ExtensionManager); + const uno::Reference<ui::dialogs::XFilePicker3>& xFilePicker = aDlgHelper.GetFilePicker(); + xFilePicker->setTitle( m_sAddPackages ); + + // collect and set filter list: + typedef std::map< OUString, OUString > t_string2string; + t_string2string title2filter; + OUStringBuffer supportedFilters; + + const uno::Sequence< uno::Reference< deployment::XPackageTypeInfo > > packageTypes( + m_pManager->getExtensionManager()->getSupportedPackageTypes() ); + + for ( uno::Reference< deployment::XPackageTypeInfo > const & xPackageType : packageTypes ) + { + const OUString filter( xPackageType->getFileFilter() ); + if (!filter.isEmpty()) + { + const OUString title( xPackageType->getShortDescription() ); + const std::pair< t_string2string::iterator, bool > insertion( + title2filter.emplace( title, filter ) ); + if (!supportedFilters.isEmpty()) + supportedFilters.append(';'); + supportedFilters.append(filter); + if ( ! insertion.second ) + { // already existing, append extensions: + insertion.first->second = insertion.first->second + + ";" + filter; + } + } + } + + static const OUString StrAllFiles = []() + { + const SolarMutexGuard guard; + std::locale loc = Translate::Create("fps"); + return Translate::get(STR_FILTERNAME_ALL, loc); + }(); + + // All files at top: + xFilePicker->appendFilter( StrAllFiles, "*.*" ); + xFilePicker->appendFilter( DpResId(RID_STR_ALL_SUPPORTED), supportedFilters.makeStringAndClear() ); + // then supported ones: + for (auto const& elem : title2filter) + { + try + { + xFilePicker->appendFilter( elem.first, elem.second ); + } + catch (const lang::IllegalArgumentException &) + { + TOOLS_WARN_EXCEPTION( "desktop", "" ); + } + } + xFilePicker->setCurrentFilter( DpResId(RID_STR_ALL_SUPPORTED) ); + + if ( xFilePicker->execute() != ui::dialogs::ExecutableDialogResults::OK ) + return uno::Sequence<OUString>(); // cancelled + + uno::Sequence< OUString > files( xFilePicker->getSelectedFiles() ); + OSL_ASSERT( files.hasElements() ); + return files; +} + +void ExtMgrDialog::enableOptionsButton( bool bEnable ) +{ + m_xOptionsBtn->set_sensitive( bEnable ); +} + +void ExtMgrDialog::enableRemoveButton( bool bEnable ) +{ + m_xRemoveBtn->set_sensitive( bEnable && !officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionRemoval::get()); + + if (officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionRemoval::get()) + { + m_xRemoveBtn->set_tooltip_text(DpResId(RID_STR_WARNING_REMOVE_EXTENSION_DISABLED)); + } + else + { + m_xRemoveBtn->set_tooltip_text(""); + } +} + +void ExtMgrDialog::enableEnableButton( bool bEnable ) +{ + m_xEnableBtn->set_sensitive( bEnable ); +} + +void ExtMgrDialog::enableButtontoEnable( bool bEnable ) +{ + if (bEnable) + { + m_xEnableBtn->set_label( DpResId( RID_CTX_ITEM_ENABLE ) ); + m_xEnableBtn->set_help_id( HID_EXTENSION_MANAGER_LISTBOX_ENABLE ); + } + else + { + m_xEnableBtn->set_label( DpResId( RID_CTX_ITEM_DISABLE ) ); + m_xEnableBtn->set_help_id( HID_EXTENSION_MANAGER_LISTBOX_DISABLE ); + } +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleCancelBtn, weld::Button&, void) +{ + if ( m_xAbortChannel.is() ) + { + try + { + m_xAbortChannel->sendAbort(); + } + catch ( const uno::RuntimeException & ) + { + TOOLS_WARN_EXCEPTION( "dbaccess", "" ); + } + } +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleCloseBtn, weld::Button&, void) +{ + bool bCallClose = true; + + //only suggest restart if modified and this is the first close attempt + if (!m_bClosed && m_pManager->isModified()) + { + m_pManager->clearModified(); + + //only suggest restart if we're actually running, e.g. not from standalone unopkg gui + if (dp_misc::office_is_running()) + { + SolarMutexGuard aGuard; + bCallClose = !::svtools::executeRestartDialog(comphelper::getProcessComponentContext(), + m_xDialog.get(), + svtools::RESTART_REASON_EXTENSION_INSTALL); + } + } + + if (bCallClose) + m_xDialog->response(RET_CANCEL); +} + +IMPL_LINK( ExtMgrDialog, startProgress, void*, _bLockInterface, void ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + bool bLockInterface = static_cast<bool>(_bLockInterface); + + if ( m_bStartProgress && !m_bHasProgress ) + m_aIdle.Start(); + + if ( m_bStopProgress ) + { + if ( m_xProgressBar->get_visible() ) + m_xProgressBar->set_percentage( 100 ); + m_xAbortChannel.clear(); + + SAL_INFO( "desktop.deployment", " startProgress handler: stop" ); + } + else + { + SAL_INFO( "desktop.deployment", " startProgress handler: start" ); + } + + m_xCancelBtn->set_sensitive( bLockInterface ); + m_xAddBtn->set_sensitive( !bLockInterface && !officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionInstallation::get()); + if (officecfg::Office::ExtensionManager::ExtensionSecurity::DisableExtensionInstallation::get()) + { + m_xAddBtn->set_tooltip_text(DpResId(RID_STR_WARNING_INSTALL_EXTENSION_DISABLED)); + } + else + { + m_xAddBtn->set_tooltip_text(""); + } + + m_xUpdateBtn->set_sensitive( !bLockInterface && m_xExtensionBox->getItemCount() ); + m_xExtensionBox->enableButtons( !bLockInterface ); + + clearEventID(); +} + + +void ExtMgrDialog::showProgress( bool _bStart ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + bool bStart = _bStart; + + if ( bStart ) + { + m_nProgress = 0; + m_bStartProgress = true; + SAL_INFO( "desktop.deployment", "showProgress start" ); + } + else + { + m_nProgress = 100; + m_bStopProgress = true; + SAL_INFO( "desktop.deployment", "showProgress stop!" ); + } + + DialogHelper::PostUserEvent( LINK( this, ExtMgrDialog, startProgress ), reinterpret_cast<void*>(bStart) ); + m_aIdle.Start(); +} + + +void ExtMgrDialog::updateProgress( const tools::Long nProgress ) +{ + if ( m_nProgress != nProgress ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + m_nProgress = nProgress; + m_aIdle.Start(); + } +} + + +void ExtMgrDialog::updateProgress( const OUString &rText, + const uno::Reference< task::XAbortChannel > &xAbortChannel) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + m_xAbortChannel = xAbortChannel; + m_sProgressText = rText; + m_bProgressChanged = true; + m_aIdle.Start(); +} + + +void ExtMgrDialog::updatePackageInfo( const uno::Reference< deployment::XPackage > &xPackage ) +{ + const SolarMutexGuard aGuard; + m_xExtensionBox->updateEntry( xPackage ); +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleOptionsBtn, weld::Button&, void) +{ + const sal_Int32 nActive = m_xExtensionBox->getSelIndex(); + + if ( nActive != ExtensionBox_Impl::ENTRY_NOTFOUND ) + { + SfxAbstractDialogFactory* pFact = SfxAbstractDialogFactory::Create(); + + OUString sExtensionId = m_xExtensionBox->GetEntryData( nActive )->m_xPackage->getIdentifier().Value; + ScopedVclPtr<VclAbstractDialog> pDlg(pFact->CreateOptionsDialog(m_xDialog.get(), sExtensionId)); + + pDlg->Execute(); + } +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleAddBtn, weld::Button&, void) +{ + incBusy(); + + uno::Sequence< OUString > aFileList = raiseAddPicker(); + + if ( aFileList.hasElements() ) + { + m_pManager->installPackage( aFileList[0] ); + } + + decBusy(); +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleRemoveBtn, weld::Button&, void) +{ + const sal_Int32 nActive = m_xExtensionBox->getSelIndex(); + + if ( nActive != ExtensionBox_Impl::ENTRY_NOTFOUND ) + { + TEntry_Impl pEntry = m_xExtensionBox->GetEntryData( nActive ); + removePackage( pEntry->m_xPackage ); + } +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleEnableBtn, weld::Button&, void) +{ + const sal_Int32 nActive = m_xExtensionBox->getSelIndex(); + + if ( nActive != ExtensionBox_Impl::ENTRY_NOTFOUND ) + { + TEntry_Impl pEntry = m_xExtensionBox->GetEntryData( nActive ); + + if ( pEntry->m_bMissingLic ) + acceptLicense( pEntry->m_xPackage ); + else + { + const bool bEnable( pEntry->m_eState != REGISTERED ); + enablePackage( pEntry->m_xPackage, bEnable ); + } + } +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleExtTypeCbx, weld::Toggleable&, void) +{ + updateList(); +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleSearch, weld::Entry&, void) +{ + updateList(); +} + +IMPL_LINK_NOARG(ExtMgrDialog, HandleUpdateBtn, weld::Button&, void) +{ +#if ENABLE_EXTENSION_UPDATE + m_pManager->checkUpdates(); +#else + (void) this; +#endif +} + +IMPL_LINK_NOARG(ExtMgrDialog, TimeOutHdl, Timer *, void) +{ + if ( m_bStopProgress ) + { + m_bHasProgress = false; + m_bStopProgress = false; + m_xProgressText->hide(); + m_xProgressBar->hide(); + m_xCancelBtn->hide(); + } + else + { + if ( m_bProgressChanged ) + { + m_bProgressChanged = false; + m_xProgressText->set_label(m_sProgressText); + } + + if ( m_bStartProgress ) + { + m_bStartProgress = false; + m_bHasProgress = true; + m_xProgressBar->show(); + m_xProgressText->show(); + m_xCancelBtn->set_sensitive(true); + m_xCancelBtn->show(); + } + + if ( m_xProgressBar->get_visible() ) + m_xProgressBar->set_percentage( static_cast<sal_uInt16>(m_nProgress) ); + } +} + +void ExtMgrDialog::Close() +{ + m_pManager->terminateDialog(); + m_bClosed = true; +} + +//UpdateRequiredDialog +UpdateRequiredDialog::UpdateRequiredDialog(weld::Window *pParent, TheExtensionManager *pManager) + : GenericDialogController(pParent, "desktop/ui/updaterequireddialog.ui", "UpdateRequiredDialog") + , DialogHelper(pManager->getContext(), m_xDialog.get()) + , m_sCloseText(DpResId(RID_STR_CLOSE_BTN)) + , m_bHasProgress(false) + , m_bProgressChanged(false) + , m_bStartProgress(false) + , m_bStopProgress(false) + , m_bHasLockedEntries(false) + , m_nProgress(0) + , m_aIdle( "UpdateRequiredDialog m_aIdle TimeOutHdl" ) + , m_pManager(pManager) + , m_xExtensionBox(new ExtensionBox_Impl(m_xBuilder->weld_scrolled_window("scroll", true))) + , m_xExtensionBoxWnd(new weld::CustomWeld(*m_xBuilder, "extensions", *m_xExtensionBox)) + , m_xUpdateNeeded(m_xBuilder->weld_label("updatelabel")) + , m_xUpdateBtn(m_xBuilder->weld_button("ok")) + , m_xCloseBtn(m_xBuilder->weld_button("disable")) + , m_xCancelBtn(m_xBuilder->weld_button("cancel")) + , m_xProgressText(m_xBuilder->weld_label("progresslabel")) + , m_xProgressBar(m_xBuilder->weld_progress_bar("progress")) +{ + m_xExtensionBox->setExtensionManager(pManager); + + m_xUpdateBtn->connect_clicked( LINK( this, UpdateRequiredDialog, HandleUpdateBtn ) ); + m_xCloseBtn->connect_clicked( LINK( this, UpdateRequiredDialog, HandleCloseBtn ) ); + m_xCancelBtn->connect_clicked( LINK( this, UpdateRequiredDialog, HandleCancelBtn ) ); + + OUString aText = m_xUpdateNeeded->get_label(); + aText = aText.replaceAll( + "%PRODUCTNAME", utl::ConfigManager::getProductName()); + m_xUpdateNeeded->set_label(aText); + + m_xProgressBar->hide(); + m_xUpdateBtn->set_sensitive( false ); + m_xCloseBtn->grab_focus(); + + m_aIdle.SetPriority( TaskPriority::LOWEST ); + m_aIdle.SetInvokeHandler( LINK( this, UpdateRequiredDialog, TimeOutHdl ) ); +} + +UpdateRequiredDialog::~UpdateRequiredDialog() +{ + m_aIdle.Stop(); +} + +void UpdateRequiredDialog::addPackageToList( const uno::Reference< deployment::XPackage > &xPackage, + bool bLicenseMissing ) +{ + // We will only add entries to the list with unsatisfied dependencies + if ( !bLicenseMissing && !checkDependencies( xPackage ) ) + { + m_bHasLockedEntries |= m_pManager->isReadOnly( xPackage ); + const SolarMutexGuard aGuard; + m_xUpdateBtn->set_sensitive(true); + m_xExtensionBox->addEntry( xPackage ); + } +} + + +void UpdateRequiredDialog::prepareChecking() +{ + m_xExtensionBox->prepareChecking(); +} + + +void UpdateRequiredDialog::checkEntries() +{ + const SolarMutexGuard guard; + m_xExtensionBox->checkEntries(); + + if ( ! hasActiveEntries() ) + { + m_xCloseBtn->set_label( m_sCloseText ); + m_xCloseBtn->grab_focus(); + } +} + + +IMPL_LINK_NOARG(UpdateRequiredDialog, HandleCancelBtn, weld::Button&, void) +{ + if ( m_xAbortChannel.is() ) + { + try + { + m_xAbortChannel->sendAbort(); + } + catch ( const uno::RuntimeException & ) + { + TOOLS_WARN_EXCEPTION( "desktop", "" ); + } + } +} + + +IMPL_LINK( UpdateRequiredDialog, startProgress, void*, _bLockInterface, void ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + bool bLockInterface = static_cast<bool>(_bLockInterface); + + if ( m_bStartProgress && !m_bHasProgress ) + m_aIdle.Start(); + + if ( m_bStopProgress ) + { + if ( m_xProgressBar->get_visible() ) + m_xProgressBar->set_percentage( 100 ); + m_xAbortChannel.clear(); + SAL_INFO( "desktop.deployment", " startProgress handler: stop" ); + } + else + { + SAL_INFO( "desktop.deployment", " startProgress handler: start" ); + } + + m_xCancelBtn->set_sensitive( bLockInterface ); + m_xUpdateBtn->set_sensitive( false ); + clearEventID(); +} + + +void UpdateRequiredDialog::showProgress( bool _bStart ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + bool bStart = _bStart; + + if ( bStart ) + { + m_nProgress = 0; + m_bStartProgress = true; + SAL_INFO( "desktop.deployment", "showProgress start" ); + } + else + { + m_nProgress = 100; + m_bStopProgress = true; + SAL_INFO( "desktop.deployment", "showProgress stop!" ); + } + + DialogHelper::PostUserEvent( LINK( this, UpdateRequiredDialog, startProgress ), reinterpret_cast<void*>(bStart) ); + m_aIdle.Start(); +} + + +void UpdateRequiredDialog::updateProgress( const tools::Long nProgress ) +{ + if ( m_nProgress != nProgress ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + m_nProgress = nProgress; + m_aIdle.Start(); + } +} + + +void UpdateRequiredDialog::updateProgress( const OUString &rText, + const uno::Reference< task::XAbortChannel > &xAbortChannel) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + m_xAbortChannel = xAbortChannel; + m_sProgressText = rText; + m_bProgressChanged = true; + m_aIdle.Start(); +} + + +void UpdateRequiredDialog::updatePackageInfo( const uno::Reference< deployment::XPackage > &xPackage ) +{ + // We will remove all updated packages with satisfied dependencies, but + // we will show all disabled entries so the user sees the result + // of the 'disable all' button + const SolarMutexGuard aGuard; + if ( isEnabled( xPackage ) && checkDependencies( xPackage ) ) + m_xExtensionBox->removeEntry( xPackage ); + else + m_xExtensionBox->updateEntry( xPackage ); + + if ( ! hasActiveEntries() ) + { + m_xCloseBtn->set_label( m_sCloseText ); + m_xCloseBtn->grab_focus(); + } +} + + +IMPL_LINK_NOARG(UpdateRequiredDialog, HandleUpdateBtn, weld::Button&, void) +{ + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + std::vector< uno::Reference< deployment::XPackage > > vUpdateEntries; + sal_Int32 nCount = m_xExtensionBox->GetEntryCount(); + + for ( sal_Int32 i = 0; i < nCount; ++i ) + { + TEntry_Impl pEntry = m_xExtensionBox->GetEntryData( i ); + vUpdateEntries.push_back( pEntry->m_xPackage ); + } + + aGuard.clear(); + + m_pManager->getCmdQueue()->checkForUpdates( std::move(vUpdateEntries) ); +} + + +IMPL_LINK_NOARG(UpdateRequiredDialog, HandleCloseBtn, weld::Button&, void) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( !isBusy() ) + { + if ( m_bHasLockedEntries ) + m_xDialog->response(-1); + else if ( hasActiveEntries() ) + disableAllEntries(); + else + m_xDialog->response(RET_CANCEL); + } +} + + +IMPL_LINK_NOARG(UpdateRequiredDialog, TimeOutHdl, Timer *, void) +{ + if ( m_bStopProgress ) + { + m_bHasProgress = false; + m_bStopProgress = false; + m_xProgressText->hide(); + m_xProgressBar->hide(); + m_xCancelBtn->hide(); + } + else + { + if ( m_bProgressChanged ) + { + m_bProgressChanged = false; + m_xProgressText->set_label( m_sProgressText ); + } + + if ( m_bStartProgress ) + { + m_bStartProgress = false; + m_bHasProgress = true; + m_xProgressBar->show(); + m_xProgressText->show(); + m_xCancelBtn->set_sensitive(true); + m_xCancelBtn->show(); + } + + if (m_xProgressBar->get_visible()) + m_xProgressBar->set_percentage(m_nProgress); + } +} + +// VCL::Dialog +short UpdateRequiredDialog::run() +{ + //ToDo + //I believe m_bHasLockedEntries was used to prevent showing extensions which cannot + //be disabled because they are in a read only repository. However, disabling extensions + //is now always possible because the registration data of all repositories + //are in the user installation. + //Therefore all extensions could be displayed and all the handling around m_bHasLockedEntries + //could be removed. + if ( m_bHasLockedEntries ) + { + // Set other text, disable update btn, remove not shared entries from list; + m_xUpdateNeeded->set_label( DpResId( RID_STR_NO_ADMIN_PRIVILEGE ) ); + m_xCloseBtn->set_label( DpResId( RID_STR_EXIT_BTN ) ); + m_xUpdateBtn->set_sensitive( false ); + m_xExtensionBox->RemoveUnlocked(); + } + + return GenericDialogController::run(); +} + +// Check dependencies of all packages + +bool UpdateRequiredDialog::isEnabled( const uno::Reference< deployment::XPackage > &xPackage ) +{ + bool bRegistered = false; + try { + beans::Optional< beans::Ambiguous< sal_Bool > > option( xPackage->isRegistered( uno::Reference< task::XAbortChannel >(), + uno::Reference< ucb::XCommandEnvironment >() ) ); + if ( option.IsPresent ) + { + ::beans::Ambiguous< sal_Bool > const & reg = option.Value; + if ( reg.IsAmbiguous ) + bRegistered = false; + else + bRegistered = reg.Value; + } + else + bRegistered = false; + } + catch ( const uno::RuntimeException & ) { throw; } + catch (const uno::Exception & ) { + TOOLS_WARN_EXCEPTION( "desktop", "" ); + bRegistered = false; + } + + return bRegistered; +} + +// Checks the dependencies no matter if the extension is enabled or disabled! +bool UpdateRequiredDialog::checkDependencies( const uno::Reference< deployment::XPackage > &xPackage ) +{ + bool bDependenciesValid = false; + try { + bDependenciesValid = xPackage->checkDependencies( uno::Reference< ucb::XCommandEnvironment >() ); + } + catch ( const deployment::DeploymentException & ) {} + return bDependenciesValid; +} + + +bool UpdateRequiredDialog::hasActiveEntries() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + bool bRet = false; + tools::Long nCount = m_xExtensionBox->GetEntryCount(); + for ( tools::Long nIndex = 0; nIndex < nCount; nIndex++ ) + { + TEntry_Impl pEntry = m_xExtensionBox->GetEntryData( nIndex ); + + if ( isEnabled(pEntry->m_xPackage) && !checkDependencies( pEntry->m_xPackage ) ) + { + bRet = true; + break; + } + } + + return bRet; +} + + +void UpdateRequiredDialog::disableAllEntries() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + incBusy(); + + tools::Long nCount = m_xExtensionBox->GetEntryCount(); + for ( tools::Long nIndex = 0; nIndex < nCount; nIndex++ ) + { + TEntry_Impl pEntry = m_xExtensionBox->GetEntryData( nIndex ); + m_pManager->getCmdQueue()->enableExtension( pEntry->m_xPackage, false ); + } + + decBusy(); + + if ( ! hasActiveEntries() ) + m_xCloseBtn->set_label( m_sCloseText ); +} + +// ShowLicenseDialog +ShowLicenseDialog::ShowLicenseDialog(weld::Window* pParent, + const uno::Reference< deployment::XPackage> &xPackage) + : GenericDialogController(pParent, "desktop/ui/showlicensedialog.ui", "ShowLicenseDialog") + , m_xLicenseText(m_xBuilder->weld_text_view("textview")) +{ + m_xLicenseText->set_size_request(m_xLicenseText->get_approximate_digit_width() * 72, + m_xLicenseText->get_height_rows(21)); + m_xLicenseText->set_text(xPackage->getLicenseText()); +} + +ShowLicenseDialog::~ShowLicenseDialog() +{ +} + +// UpdateRequiredDialogService + +UpdateRequiredDialogService::UpdateRequiredDialogService( SAL_UNUSED_PARAMETER uno::Sequence< uno::Any > const&, + uno::Reference< uno::XComponentContext > xComponentContext ) + : m_xComponentContext(std::move( xComponentContext )) +{ +} + +// XServiceInfo +OUString UpdateRequiredDialogService::getImplementationName() +{ + return "com.sun.star.comp.deployment.ui.UpdateRequiredDialog"; +} + +sal_Bool UpdateRequiredDialogService::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > UpdateRequiredDialogService::getSupportedServiceNames() +{ + return { "com.sun.star.deployment.ui.UpdateRequiredDialog" }; +} + + +// XExecutableDialog + +void UpdateRequiredDialogService::setTitle( OUString const & ) +{ +} + + +sal_Int16 UpdateRequiredDialogService::execute() +{ + ::rtl::Reference< ::dp_gui::TheExtensionManager > xManager( TheExtensionManager::get( + m_xComponentContext) ); + xManager->createDialog( true ); + sal_Int16 nRet = xManager->execute(); + + return nRet; +} + + +} //namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_dialog2.hxx b/desktop/source/deployment/gui/dp_gui_dialog2.hxx new file mode 100644 index 000000000..a11ff494e --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_dialog2.hxx @@ -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 . + */ + +#pragma once + +#include <vcl/timer.hxx> +#include <vcl/idle.hxx> +#include <vcl/locktoplevels.hxx> +#include <vcl/customweld.hxx> +#include <vcl/weld.hxx> + +#include <osl/mutex.hxx> + +#include <rtl/ustring.hxx> + +#include <cppuhelper/implbase.hxx> +#include <unotools/resmgr.hxx> + +#include <com/sun/star/deployment/XPackage.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +struct ImplSVEvent; + +namespace dp_gui { + +class ExtBoxWithBtns_Impl; +class ExtensionBox_Impl; +class TheExtensionManager; + +class DialogHelper +{ + css::uno::Reference< css::uno::XComponentContext > m_xContext; + weld::Window* m_pWindow; + ImplSVEvent * m_nEventID; + TopLevelWindowLocker m_aBusy; + +public: + DialogHelper(const css::uno::Reference< css::uno::XComponentContext > &, + weld::Window* pWindow); + virtual ~DialogHelper(); + + void openWebBrowser(const OUString& rURL, const OUString& rTitle); + weld::Window* getFrameWeld() const { return m_pWindow; } + void PostUserEvent( const Link<void*,void>& rLink, void* pCaller ); + void clearEventID() { m_nEventID = nullptr; } + + virtual void showProgress( bool bStart ) = 0; + virtual void updateProgress( const OUString &rText, + const css::uno::Reference< css::task::XAbortChannel > &xAbortChannel) = 0; + virtual void updateProgress( const tools::Long nProgress ) = 0; + + virtual void updatePackageInfo( const css::uno::Reference< css::deployment::XPackage > &xPackage ) = 0; + virtual void addPackageToList( const css::uno::Reference< css::deployment::XPackage > &xPackage, + bool bLicenseMissing = false ) = 0; + + virtual void prepareChecking() = 0; + virtual void checkEntries() = 0; + + static bool IsSharedPkgMgr( const css::uno::Reference< css::deployment::XPackage > &); + bool continueOnSharedExtension( const css::uno::Reference< css::deployment::XPackage > &, + weld::Widget* pParent, + TranslateId pResID, + bool &bHadWarning ); + + void incBusy() { m_aBusy.incBusy(m_pWindow); } + void decBusy() { m_aBusy.decBusy(); } + bool isBusy() const { return m_aBusy.isBusy(); } + bool installExtensionWarn(std::u16string_view rExtensionURL); + bool installForAllUsers(bool &bInstallForAll); +}; + +class ExtMgrDialog : public weld::GenericDialogController + , public DialogHelper +{ + const OUString m_sAddPackages; + OUString m_sProgressText; + ::osl::Mutex m_aMutex; + bool m_bHasProgress; + bool m_bProgressChanged; + bool m_bStartProgress; + bool m_bStopProgress; + bool m_bEnableWarning; + bool m_bDisableWarning; + bool m_bDeleteWarning; + bool m_bClosed; + tools::Long m_nProgress; + Idle m_aIdle; + TheExtensionManager *m_pManager; + + css::uno::Reference< css::task::XAbortChannel > m_xAbortChannel; + + std::unique_ptr<ExtBoxWithBtns_Impl> m_xExtensionBox; + std::unique_ptr<weld::CustomWeld> m_xExtensionBoxWnd; + std::unique_ptr<weld::Button> m_xOptionsBtn; + std::unique_ptr<weld::Button> m_xAddBtn; + std::unique_ptr<weld::Button> m_xRemoveBtn; + std::unique_ptr<weld::Button> m_xEnableBtn; + std::unique_ptr<weld::Button> m_xUpdateBtn; + std::unique_ptr<weld::Button> m_xCloseBtn; + std::unique_ptr<weld::CheckButton> m_xBundledCbx; + std::unique_ptr<weld::CheckButton> m_xSharedCbx; + std::unique_ptr<weld::CheckButton> m_xUserCbx; + std::unique_ptr<weld::LinkButton> m_xGetExtensions; + std::unique_ptr<weld::Label> m_xProgressText; + std::unique_ptr<weld::ProgressBar> m_xProgressBar; + std::unique_ptr<weld::Button> m_xCancelBtn; + std::unique_ptr<weld::Entry> m_xSearchEntry; + + bool removeExtensionWarn(std::u16string_view rExtensionTitle); + + DECL_LINK( HandleOptionsBtn, weld::Button&, void ); + DECL_LINK( HandleAddBtn, weld::Button&, void ); + DECL_LINK( HandleRemoveBtn, weld::Button&, void ); + DECL_LINK( HandleEnableBtn, weld::Button&, void ); + DECL_LINK( HandleUpdateBtn, weld::Button&, void ); + DECL_LINK( HandleCancelBtn, weld::Button&, void ); + DECL_LINK( HandleCloseBtn, weld::Button&, void ); + DECL_LINK( HandleExtTypeCbx, weld::Toggleable&, void ); + DECL_LINK( HandleSearch, weld::Entry&, void ); + DECL_LINK( TimeOutHdl, Timer *, void ); + DECL_LINK( startProgress, void *, void ); + +public: + ExtMgrDialog(weld::Window * pParent, TheExtensionManager *pManager); + virtual ~ExtMgrDialog() override; + + virtual void showProgress( bool bStart ) override; + virtual void updateProgress( const OUString &rText, + const css::uno::Reference< css::task::XAbortChannel > &xAbortChannel) override; + virtual void updateProgress( const tools::Long nProgress ) override; + + virtual void updatePackageInfo( const css::uno::Reference< css::deployment::XPackage > &xPackage ) override; + + void setGetExtensionsURL( const OUString &rURL ); + virtual void addPackageToList( const css::uno::Reference< css::deployment::XPackage > &, + bool bLicenseMissing = false ) override; + void enablePackage(const css::uno::Reference< css::deployment::XPackage > &xPackage, + bool bEnable ); + void removePackage(const css::uno::Reference< css::deployment::XPackage > &xPackage ); + void updatePackage(const css::uno::Reference< css::deployment::XPackage > &xPackage ); + bool acceptLicense(const css::uno::Reference< css::deployment::XPackage > &xPackage ); + + void Close(); + + TheExtensionManager* getExtensionManager() const { return m_pManager; } + + virtual void updateList(); + virtual void prepareChecking() override; + virtual void checkEntries() override; + + css::uno::Sequence< OUString > raiseAddPicker(); + + void enableOptionsButton( bool bEnable ); + void enableRemoveButton( bool bEnable ); + void enableEnableButton( bool bEnable ); + /* + * Transform the button to "Enable", or to "Disable" + * based on the value of bEnable. + */ + void enableButtontoEnable( bool bEnable ); +}; + + +class UpdateRequiredDialog : public weld::GenericDialogController + , public DialogHelper +{ + const OUString m_sCloseText; + OUString m_sProgressText; + ::osl::Mutex m_aMutex; + bool m_bHasProgress; + bool m_bProgressChanged; + bool m_bStartProgress; + bool m_bStopProgress; + bool m_bHasLockedEntries; + tools::Long m_nProgress; + Idle m_aIdle; + TheExtensionManager *m_pManager; + + css::uno::Reference< css::task::XAbortChannel > m_xAbortChannel; + + std::unique_ptr<ExtensionBox_Impl> m_xExtensionBox; + std::unique_ptr<weld::CustomWeld> m_xExtensionBoxWnd; + std::unique_ptr<weld::Label> m_xUpdateNeeded; + std::unique_ptr<weld::Button> m_xUpdateBtn; + std::unique_ptr<weld::Button> m_xCloseBtn; + std::unique_ptr<weld::Button> m_xCancelBtn; + std::unique_ptr<weld::Label> m_xProgressText; + std::unique_ptr<weld::ProgressBar> m_xProgressBar; + + DECL_LINK( HandleUpdateBtn, weld::Button&, void ); + DECL_LINK( HandleCloseBtn, weld::Button&, void ); + DECL_LINK( HandleCancelBtn, weld::Button&, void ); + DECL_LINK( TimeOutHdl, Timer *, void ); + DECL_LINK( startProgress, void *, void ); + + static bool isEnabled( const css::uno::Reference< css::deployment::XPackage > &xPackage ); + static bool checkDependencies( const css::uno::Reference< css::deployment::XPackage > &xPackage ); + bool hasActiveEntries(); + void disableAllEntries(); + +public: + UpdateRequiredDialog(weld::Window * pParent, TheExtensionManager *pManager); + virtual ~UpdateRequiredDialog() override; + + virtual short run() override; + + virtual void showProgress( bool bStart ) override; + virtual void updateProgress( const OUString &rText, + const css::uno::Reference< css::task::XAbortChannel > &xAbortChannel) override; + virtual void updateProgress( const tools::Long nProgress ) override; + + virtual void updatePackageInfo( const css::uno::Reference< css::deployment::XPackage > &xPackage ) override; + + virtual void addPackageToList( const css::uno::Reference< css::deployment::XPackage > &, + bool bLicenseMissing = false ) override; + + virtual void prepareChecking() override; + virtual void checkEntries() override; +}; + + +class ShowLicenseDialog : public weld::GenericDialogController +{ + std::unique_ptr<weld::TextView> m_xLicenseText; +public: + ShowLicenseDialog(weld::Window * pParent, const css::uno::Reference< css::deployment::XPackage > &xPackage); + virtual ~ShowLicenseDialog() override; +}; + +class UpdateRequiredDialogService : public ::cppu::WeakImplHelper< css::ui::dialogs::XExecutableDialog, css::lang::XServiceInfo > +{ + css::uno::Reference< css::uno::XComponentContext > const m_xComponentContext; +public: + UpdateRequiredDialogService( css::uno::Sequence< css::uno::Any > const & args, + css::uno::Reference< css::uno::XComponentContext> xComponentContext ); + + // 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; + + // XExecutableDialog + virtual void SAL_CALL setTitle( OUString const & title ) override; + virtual sal_Int16 SAL_CALL execute() override; +}; + +} // namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.cxx b/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.cxx new file mode 100644 index 000000000..fd70b7982 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.cxx @@ -0,0 +1,1115 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/deployment/DependencyException.hpp> +#include <com/sun/star/deployment/LicenseException.hpp> +#include <com/sun/star/deployment/VersionException.hpp> +#include <com/sun/star/deployment/InstallException.hpp> +#include <com/sun/star/deployment/PlatformException.hpp> + +#include <com/sun/star/deployment/ui/LicenseDialog.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/XPackage.hpp> + +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/task/XAbortChannel.hpp> +#include <com/sun/star/task/XInteractionAbort.hpp> +#include <com/sun/star/task/XInteractionApprove.hpp> + +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> + +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/TypeClass.hpp> +#include <o3tl/any.hxx> +#include <osl/diagnose.h> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <salhelper/thread.hxx> +#include <ucbhelper/content.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/implbase.hxx> +#include <comphelper/anytostring.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +#include "dp_gui_extensioncmdqueue.hxx" +#include "dp_gui_dependencydialog.hxx" +#include "dp_gui_dialog2.hxx" +#include <dp_shared.hxx> +#include <strings.hrc> +#include "dp_gui_theextmgr.hxx" +#include "dp_gui_updatedialog.hxx" +#include "dp_gui_updateinstalldialog.hxx" +#include <dp_dependencies.hxx> +#include <dp_misc.h> +#include <dp_identifier.hxx> +#include <dp_version.hxx> + +#include <condition_variable> +#include <queue> +#include <memory> +#include <mutex> + +#ifdef _WIN32 +#include <o3tl/safeCoInitUninit.hxx> +#endif + + +using namespace ::com::sun::star; + +namespace { + +OUString getVersion( OUString const & sVersion ) +{ + return ( sVersion.isEmpty() ) ? OUString( "0" ) : sVersion; +} + +OUString getVersion( const uno::Reference< deployment::XPackage > &rPackage ) +{ + return getVersion( rPackage->getVersion()); +} +} + + +namespace dp_gui { + +namespace { + +class ProgressCmdEnv + : public ::cppu::WeakImplHelper< ucb::XCommandEnvironment, + task::XInteractionHandler, + ucb::XProgressHandler > +{ + uno::Reference< task::XInteractionHandler2> m_xHandler; + uno::Reference< uno::XComponentContext > m_xContext; + + DialogHelper* m_pDialogHelper; + OUString m_sTitle; + bool m_bWarnUser; + sal_Int32 m_nCurrentProgress; + + void updateProgress(); + + /// @throws uno::RuntimeException + void update_( uno::Any const & Status ); + +public: + /** When param bAskWhenInstalling = true, then the user is asked if he + agrees to install this extension. In case this extension is already installed + then the user is also notified and asked if he wants to replace that existing + extension. In first case an interaction request with an InstallException + will be handled and in the second case a VersionException will be handled. + */ + + ProgressCmdEnv( uno::Reference< uno::XComponentContext > xContext, + DialogHelper* pDialogHelper, + OUString aTitle ) + : m_xContext(std::move( xContext )) + , m_pDialogHelper( pDialogHelper ) + , m_sTitle(std::move( aTitle )) + , m_bWarnUser( false ) + , m_nCurrentProgress(0) + {} + + weld::Window* activeDialog() { return m_pDialogHelper ? m_pDialogHelper->getFrameWeld() : nullptr; } + + void startProgress(); + void stopProgress(); + void progressSection( const OUString &rText, + const uno::Reference< task::XAbortChannel > &xAbortChannel ); + void setWarnUser( bool bNewVal ) { m_bWarnUser = bNewVal; } + + // XCommandEnvironment + virtual uno::Reference< task::XInteractionHandler > SAL_CALL getInteractionHandler() override; + virtual uno::Reference< ucb::XProgressHandler > SAL_CALL getProgressHandler() override; + + // XInteractionHandler + virtual void SAL_CALL handle( uno::Reference< task::XInteractionRequest > const & xRequest ) override; + + // XProgressHandler + virtual void SAL_CALL push( uno::Any const & Status ) override; + virtual void SAL_CALL update( uno::Any const & Status ) override; + virtual void SAL_CALL pop() override; +}; + + +struct ExtensionCmd +{ + enum E_CMD_TYPE { ADD, ENABLE, DISABLE, REMOVE, CHECK_FOR_UPDATES, ACCEPT_LICENSE }; + + E_CMD_TYPE m_eCmdType; + bool m_bWarnUser; + OUString m_sExtensionURL; + OUString m_sRepository; + uno::Reference< deployment::XPackage > m_xPackage; + std::vector< uno::Reference< deployment::XPackage > > m_vExtensionList; + + ExtensionCmd( const E_CMD_TYPE eCommand, + OUString aExtensionURL, + OUString aRepository, + const bool bWarnUser ) + : m_eCmdType( eCommand ), + m_bWarnUser( bWarnUser ), + m_sExtensionURL(std::move( aExtensionURL )), + m_sRepository(std::move( aRepository )) {}; + ExtensionCmd( const E_CMD_TYPE eCommand, + uno::Reference< deployment::XPackage > xPackage ) + : m_eCmdType( eCommand ), + m_bWarnUser( false ), + m_xPackage(std::move( xPackage )) {}; + ExtensionCmd( const E_CMD_TYPE eCommand, + std::vector<uno::Reference<deployment::XPackage > >&&vExtensionList ) + : m_eCmdType( eCommand ), + m_bWarnUser( false ), + m_vExtensionList( std::move(vExtensionList) ) {}; +}; + +} + +typedef std::shared_ptr< ExtensionCmd > TExtensionCmd; + + +class ExtensionCmdQueue::Thread: public salhelper::Thread +{ +public: + Thread( DialogHelper *pDialogHelper, + TheExtensionManager *pManager, + uno::Reference< uno::XComponentContext > xContext ); + + void addExtension( const OUString &rExtensionURL, + const OUString &rRepository, + const bool bWarnUser ); + void removeExtension( const uno::Reference< deployment::XPackage > &rPackage ); + void enableExtension( const uno::Reference< deployment::XPackage > &rPackage, + const bool bEnable ); + void checkForUpdates( std::vector<uno::Reference<deployment::XPackage > > && vExtensionList ); + void acceptLicense( const uno::Reference< deployment::XPackage > &rPackage ); + void stop(); + bool isBusy(); + +private: + virtual ~Thread() override; + + virtual void execute() override; + + void _insert(const TExtensionCmd& rExtCmd); + + void _addExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const OUString &rPackageURL, + const OUString &rRepository, + const bool bWarnUser ); + void _removeExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const uno::Reference< deployment::XPackage > &xPackage ); + void _enableExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const uno::Reference< deployment::XPackage > &xPackage ); + void _disableExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const uno::Reference< deployment::XPackage > &xPackage ); + void _checkForUpdates( std::vector<uno::Reference<deployment::XPackage > > &&vExtensionList ); + void _acceptLicense( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const uno::Reference< deployment::XPackage > &xPackage ); + + enum Input { NONE, START, STOP }; + + uno::Reference< uno::XComponentContext > m_xContext; + std::queue< TExtensionCmd > m_queue; + + DialogHelper *m_pDialogHelper; + TheExtensionManager *m_pManager; + + const OUString m_sEnablingPackages; + const OUString m_sDisablingPackages; + const OUString m_sAddingPackages; + const OUString m_sRemovingPackages; + const OUString m_sDefaultCmd; + const OUString m_sAcceptLicense; + std::condition_variable m_wakeup; + std::mutex m_mutex; + Input m_eInput; + bool m_bStopped; + bool m_bWorking; +}; + + +void ProgressCmdEnv::startProgress() +{ + m_nCurrentProgress = 0; + + if ( m_pDialogHelper ) + m_pDialogHelper->showProgress( true ); +} + + +void ProgressCmdEnv::stopProgress() +{ + if ( m_pDialogHelper ) + m_pDialogHelper->showProgress( false ); +} + + +void ProgressCmdEnv::progressSection( const OUString &rText, + const uno::Reference< task::XAbortChannel > &xAbortChannel ) +{ + m_nCurrentProgress = 0; + if ( m_pDialogHelper ) + { + m_pDialogHelper->updateProgress( rText, xAbortChannel ); + m_pDialogHelper->updateProgress( 5 ); + } +} + + +void ProgressCmdEnv::updateProgress() +{ + tools::Long nProgress = ((m_nCurrentProgress*5) % 100) + 5; + if ( m_pDialogHelper ) + m_pDialogHelper->updateProgress( nProgress ); +} + +// XCommandEnvironment + +uno::Reference< task::XInteractionHandler > ProgressCmdEnv::getInteractionHandler() +{ + return this; +} + + +uno::Reference< ucb::XProgressHandler > ProgressCmdEnv::getProgressHandler() +{ + return this; +} + + +// XInteractionHandler + +void ProgressCmdEnv::handle( uno::Reference< task::XInteractionRequest > const & xRequest ) +{ + uno::Any request( xRequest->getRequest() ); + OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION ); + dp_misc::TRACE( "[dp_gui_cmdenv.cxx] incoming request:\n" + + ::comphelper::anyToString(request) + "\n"); + + lang::WrappedTargetException wtExc; + deployment::DependencyException depExc; + deployment::LicenseException licExc; + deployment::VersionException verExc; + deployment::InstallException instExc; + deployment::PlatformException platExc; + + // selections: + bool approve = false; + bool abort = false; + + if (request >>= wtExc) { + // handable deployment error signalled, e.g. + // bundle item registration failed, notify cause only: + uno::Any cause; + deployment::DeploymentException dpExc; + if (wtExc.TargetException >>= dpExc) + cause = dpExc.Cause; + else { + ucb::CommandFailedException cfExc; + if (wtExc.TargetException >>= cfExc) + cause = cfExc.Reason; + else + cause = wtExc.TargetException; + } + update_( cause ); + + // ignore intermediate errors of legacy packages, i.e. + // former pkgchk behaviour: + const uno::Reference< deployment::XPackage > xPackage( wtExc.Context, uno::UNO_QUERY ); + OSL_ASSERT( xPackage.is() ); + if ( xPackage.is() ) + { + const uno::Reference< deployment::XPackageTypeInfo > xPackageType( xPackage->getPackageType() ); + OSL_ASSERT( xPackageType.is() ); + if (xPackageType.is()) + { + approve = ( xPackage->isBundle() && + xPackageType->getMediaType().match( + "application/vnd.sun.star.legacy-package-bundle" )); + } + } + abort = !approve; + } + else if (request >>= depExc) + { + std::vector< OUString > deps; + deps.reserve(depExc.UnsatisfiedDependencies.getLength()); + for (auto const & i : std::as_const(depExc.UnsatisfiedDependencies)) + { + deps.push_back( dp_misc::Dependencies::getErrorText(i) ); + } + { + SolarMutexGuard guard; + if (m_pDialogHelper) + m_pDialogHelper->incBusy(); + DependencyDialog aDlg(activeDialog(), deps); + short n = aDlg.run(); + if (m_pDialogHelper) + m_pDialogHelper->decBusy(); + // Distinguish between closing the dialog and programmatically + // canceling the dialog (headless VCL): + approve = n == RET_OK + || (n == RET_CANCEL && !Application::IsDialogCancelEnabled()); + } + } + else if (request >>= licExc) + { + SolarMutexGuard guard; + + weld::Window *pTopLevel = activeDialog(); + if (m_pDialogHelper) + m_pDialogHelper->incBusy(); + uno::Reference< ui::dialogs::XExecutableDialog > xDialog( + deployment::ui::LicenseDialog::create( + m_xContext, pTopLevel ? pTopLevel->GetXWindow() : nullptr, + licExc.ExtensionName, licExc.Text ) ); + sal_Int16 res = xDialog->execute(); + if (m_pDialogHelper) + m_pDialogHelper->decBusy(); + if ( res == ui::dialogs::ExecutableDialogResults::CANCEL ) + abort = true; + else if ( res == ui::dialogs::ExecutableDialogResults::OK ) + approve = true; + else + { + OSL_ASSERT(false); + } + } + else if (request >>= verExc) + { + TranslateId id; + switch (dp_misc::compareVersions( + verExc.NewVersion, verExc.Deployed->getVersion() )) + { + case dp_misc::LESS: + id = RID_STR_WARNING_VERSION_LESS; + break; + case dp_misc::EQUAL: + id = RID_STR_WARNING_VERSION_EQUAL; + break; + default: // dp_misc::GREATER + id = RID_STR_WARNING_VERSION_GREATER; + break; + } + OSL_ASSERT( verExc.Deployed.is() ); + bool bEqualNames = verExc.NewDisplayName == + verExc.Deployed->getDisplayName(); + { + SolarMutexGuard guard; + + if (m_pDialogHelper) + m_pDialogHelper->incBusy(); + + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(activeDialog(), + VclMessageType::Warning, VclButtonsType::OkCancel, DpResId(id))); + OUString s; + if (bEqualNames) + { + s = xBox->get_primary_text(); + } + else if (id != RID_STR_WARNING_VERSION_EQUAL) + { + //hypothetical: requires two instances of an extension with the same + //version to have different display names. Probably the developer forgot + //to change the version. + s = DpResId(RID_STR_WARNINGBOX_VERSION_EQUAL_DIFFERENT_NAMES); + } + else if (id != RID_STR_WARNING_VERSION_LESS) + { + s = DpResId(RID_STR_WARNINGBOX_VERSION_LESS_DIFFERENT_NAMES); + } + else if (id != RID_STR_WARNING_VERSION_GREATER) + { + s = DpResId(RID_STR_WARNINGBOX_VERSION_GREATER_DIFFERENT_NAMES); + } + s = s.replaceAll("$NAME", verExc.NewDisplayName); + s = s.replaceAll("$OLDNAME", verExc.Deployed->getDisplayName()); + s = s.replaceAll("$NEW", getVersion(verExc.NewVersion)); + s = s.replaceAll("$DEPLOYED", getVersion(verExc.Deployed)); + xBox->set_primary_text(s); + approve = xBox->run() == RET_OK; + if (m_pDialogHelper) + m_pDialogHelper->decBusy(); + abort = !approve; + } + } + else if (request >>= instExc) + { + if ( ! m_bWarnUser ) + { + approve = true; + } + else + { + if ( m_pDialogHelper ) + { + SolarMutexGuard guard; + + approve = m_pDialogHelper->installExtensionWarn( instExc.displayName ); + } + else + approve = false; + abort = !approve; + } + } + else if (request >>= platExc) + { + SolarMutexGuard guard; + OUString sMsg(DpResId(RID_STR_UNSUPPORTED_PLATFORM)); + sMsg = sMsg.replaceAll("%Name", platExc.package->getDisplayName()); + if (m_pDialogHelper) + m_pDialogHelper->incBusy(); + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(activeDialog(), + VclMessageType::Warning, VclButtonsType::Ok, sMsg)); + xBox->run(); + if (m_pDialogHelper) + m_pDialogHelper->decBusy(); + approve = true; + } + + if (!approve && !abort) + { + // forward to UUI handler: + if (! m_xHandler.is()) { + // late init: + m_xHandler = task::InteractionHandler::createWithParentAndContext(m_xContext, nullptr, m_sTitle); + } + m_xHandler->handle( xRequest ); + } + else + { + // select: + uno::Sequence< uno::Reference< task::XInteractionContinuation > > conts( + xRequest->getContinuations() ); + uno::Reference< task::XInteractionContinuation > const * pConts = conts.getConstArray(); + sal_Int32 len = conts.getLength(); + for ( sal_Int32 pos = 0; pos < len; ++pos ) + { + if (approve) { + uno::Reference< task::XInteractionApprove > xInteractionApprove( pConts[ pos ], uno::UNO_QUERY ); + if (xInteractionApprove.is()) { + xInteractionApprove->select(); + // don't query again for ongoing continuations: + approve = false; + } + } + else if (abort) { + uno::Reference< task::XInteractionAbort > xInteractionAbort( pConts[ pos ], uno::UNO_QUERY ); + if (xInteractionAbort.is()) { + xInteractionAbort->select(); + // don't query again for ongoing continuations: + abort = false; + } + } + } + } +} + + +// XProgressHandler + +void ProgressCmdEnv::push( uno::Any const & rStatus ) +{ + update_( rStatus ); +} + + +void ProgressCmdEnv::update_( uno::Any const & rStatus ) +{ + OUString text; + if ( rStatus.hasValue() && !( rStatus >>= text) ) + { + if ( auto e = o3tl::tryAccess<uno::Exception>(rStatus) ) + text = e->Message; + if ( text.isEmpty() ) + text = ::comphelper::anyToString( rStatus ); // fallback + + const SolarMutexGuard aGuard; + if (m_pDialogHelper) + m_pDialogHelper->incBusy(); + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(activeDialog(), + VclMessageType::Warning, VclButtonsType::Ok, text)); + xBox->run(); + if (m_pDialogHelper) + m_pDialogHelper->decBusy(); + } + ++m_nCurrentProgress; + updateProgress(); +} + + +void ProgressCmdEnv::update( uno::Any const & rStatus ) +{ + update_( rStatus ); +} + + +void ProgressCmdEnv::pop() +{ + update_( uno::Any() ); // no message +} + + +ExtensionCmdQueue::Thread::Thread( DialogHelper *pDialogHelper, + TheExtensionManager *pManager, + uno::Reference< uno::XComponentContext > xContext ) : + salhelper::Thread( "dp_gui_extensioncmdqueue" ), + m_xContext(std::move( xContext )), + m_pDialogHelper( pDialogHelper ), + m_pManager( pManager ), + m_sEnablingPackages( DpResId( RID_STR_ENABLING_PACKAGES ) ), + m_sDisablingPackages( DpResId( RID_STR_DISABLING_PACKAGES ) ), + m_sAddingPackages( DpResId( RID_STR_ADDING_PACKAGES ) ), + m_sRemovingPackages( DpResId( RID_STR_REMOVING_PACKAGES ) ), + m_sDefaultCmd( DpResId( RID_STR_ADD_PACKAGES ) ), + m_sAcceptLicense( DpResId( RID_STR_ACCEPT_LICENSE ) ), + m_eInput( NONE ), + m_bStopped( false ), + m_bWorking( false ) +{ + OSL_ASSERT( pDialogHelper ); +} + + +void ExtensionCmdQueue::Thread::addExtension( const OUString &rExtensionURL, + const OUString &rRepository, + const bool bWarnUser ) +{ + if ( !rExtensionURL.isEmpty() ) + { + TExtensionCmd pEntry = std::make_shared<ExtensionCmd>( ExtensionCmd::ADD, rExtensionURL, rRepository, bWarnUser ); + _insert( pEntry ); + } +} + + +void ExtensionCmdQueue::Thread::removeExtension( const uno::Reference< deployment::XPackage > &rPackage ) +{ + if ( rPackage.is() ) + { + TExtensionCmd pEntry = std::make_shared<ExtensionCmd>( ExtensionCmd::REMOVE, rPackage ); + _insert( pEntry ); + } +} + + +void ExtensionCmdQueue::Thread::acceptLicense( const uno::Reference< deployment::XPackage > &rPackage ) +{ + if ( rPackage.is() ) + { + TExtensionCmd pEntry = std::make_shared<ExtensionCmd>( ExtensionCmd::ACCEPT_LICENSE, rPackage ); + _insert( pEntry ); + } +} + + +void ExtensionCmdQueue::Thread::enableExtension( const uno::Reference< deployment::XPackage > &rPackage, + const bool bEnable ) +{ + if ( rPackage.is() ) + { + TExtensionCmd pEntry = std::make_shared<ExtensionCmd>( bEnable ? ExtensionCmd::ENABLE : + ExtensionCmd::DISABLE, + rPackage ); + _insert( pEntry ); + } +} + + +void ExtensionCmdQueue::Thread::checkForUpdates( + std::vector<uno::Reference<deployment::XPackage > > && vExtensionList ) +{ + TExtensionCmd pEntry = std::make_shared<ExtensionCmd>( ExtensionCmd::CHECK_FOR_UPDATES, std::move(vExtensionList) ); + _insert( pEntry ); +} + + +//Stopping this thread will not abort the installation of extensions. +void ExtensionCmdQueue::Thread::stop() +{ + std::scoped_lock aGuard( m_mutex ); + m_bStopped = true; + m_eInput = STOP; + m_wakeup.notify_all(); +} + + +bool ExtensionCmdQueue::Thread::isBusy() +{ + std::scoped_lock aGuard( m_mutex ); + return m_bWorking; +} + + +ExtensionCmdQueue::Thread::~Thread() {} + + +void ExtensionCmdQueue::Thread::execute() +{ +#ifdef _WIN32 + //Needed for use of the service "com.sun.star.system.SystemShellExecute" in + //DialogHelper::openWebBrowser + int nNbCallCoInitializeExForReinit = 0; + o3tl::safeCoInitializeEx(COINIT_APARTMENTTHREADED, nNbCallCoInitializeExForReinit); +#endif + for (;;) + { + int nSize; + Input eInput; + { + std::unique_lock aGuard( m_mutex ); + while (m_eInput == NONE) { + m_wakeup.wait(aGuard); + } + eInput = m_eInput; + m_eInput = NONE; + nSize = m_queue.size(); + // coverity[missing_lock: FALSE] - maybe due to (by-design) unique_lock vs. scoped_lock? + m_bWorking = false; + } + + if ( eInput == STOP ) + break; + + // We only install the extension which are currently in the queue. + // The progressbar will be set to show the progress of the current number + // of extensions. If we allowed to add extensions now then the progressbar may + // have reached the end while we still install newly added extensions. + if ( nSize == 0 ) + continue; + + ::rtl::Reference< ProgressCmdEnv > currentCmdEnv( new ProgressCmdEnv( m_xContext, m_pDialogHelper, m_sDefaultCmd ) ); + + // Do not lock the following part with addExtension. addExtension may be called in the main thread. + // If the message box "Do you want to install the extension (or similar)" is shown and then + // addExtension is called, which then blocks the main thread, then we deadlock. + bool bStartProgress = true; + + while ( --nSize >= 0 ) + { + { + std::scoped_lock aGuard( m_mutex ); + m_bWorking = true; + } + + try + { + TExtensionCmd pEntry; + { + std::scoped_lock queueGuard( m_mutex ); + pEntry = m_queue.front(); + m_queue.pop(); + } + + if ( bStartProgress && ( pEntry->m_eCmdType != ExtensionCmd::CHECK_FOR_UPDATES ) ) + { + currentCmdEnv->startProgress(); + bStartProgress = false; + } + + switch ( pEntry->m_eCmdType ) { + case ExtensionCmd::ADD : + _addExtension( currentCmdEnv, pEntry->m_sExtensionURL, pEntry->m_sRepository, pEntry->m_bWarnUser ); + break; + case ExtensionCmd::REMOVE : + _removeExtension( currentCmdEnv, pEntry->m_xPackage ); + break; + case ExtensionCmd::ENABLE : + _enableExtension( currentCmdEnv, pEntry->m_xPackage ); + break; + case ExtensionCmd::DISABLE : + _disableExtension( currentCmdEnv, pEntry->m_xPackage ); + break; + case ExtensionCmd::CHECK_FOR_UPDATES : + _checkForUpdates( std::vector(pEntry->m_vExtensionList) ); + break; + case ExtensionCmd::ACCEPT_LICENSE : + _acceptLicense( currentCmdEnv, pEntry->m_xPackage ); + break; + } + } + catch ( const ucb::CommandAbortedException & ) + { + //This exception is thrown when the user clicks cancel on the progressbar. + //Then we cancel the installation of all extensions and remove them from + //the queue. + { + std::scoped_lock queueGuard2(m_mutex); + while ( --nSize >= 0 ) + m_queue.pop(); + } + break; + } + catch ( const ucb::CommandFailedException & ) + { + //This exception is thrown when a user clicked cancel in the messagebox which was + //started by the interaction handler. For example the user will be asked if he/she + //really wants to install the extension. + //These interactions run for exactly one extension at a time. Therefore we continue + //with installing the remaining extensions. + continue; + } + catch ( const uno::Exception & ) + { + //Todo display the user an error + //see also DialogImpl::SyncPushButton::Click() + uno::Any exc( ::cppu::getCaughtException() ); + OUString msg; + deployment::DeploymentException dpExc; + if (exc >>= dpExc) + { + if (auto e = o3tl::tryAccess<uno::Exception>(dpExc.Cause)) + { + // notify error cause only: + msg = e->Message; + } + } + if (msg.isEmpty()) // fallback for debugging purposes + msg = ::comphelper::anyToString(exc); + + const SolarMutexGuard guard; + if (m_pDialogHelper) + m_pDialogHelper->incBusy(); + + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(currentCmdEnv->activeDialog(), + VclMessageType::Warning, VclButtonsType::Ok, msg)); + if (m_pDialogHelper) + xBox->set_title(m_pDialogHelper->getFrameWeld()->get_title()); + xBox->run(); + if (m_pDialogHelper) + m_pDialogHelper->decBusy(); + //Continue with installation of the remaining extensions + } + { + std::scoped_lock aGuard( m_mutex ); + m_bWorking = false; + } + } + + { + // when leaving the while loop with break, we should set working to false, too + std::scoped_lock aGuard( m_mutex ); + m_bWorking = false; + } + + if ( !bStartProgress ) + currentCmdEnv->stopProgress(); + } + //end for +#ifdef _WIN32 + o3tl::safeCoUninitializeReinit(COINIT_MULTITHREADED, nNbCallCoInitializeExForReinit); +#endif +} + + +void ExtensionCmdQueue::Thread::_addExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const OUString &rPackageURL, + const OUString &rRepository, + const bool bWarnUser ) +{ + //check if we have a string in anyTitle. For example "unopkg gui \" caused anyTitle to be void + //and anyTitle.get<OUString> throws as RuntimeException. + uno::Any anyTitle; + try + { + anyTitle = ::ucbhelper::Content( rPackageURL, rCmdEnv, m_xContext ).getPropertyValue( "Title" ); + } + catch ( const uno::Exception & ) + { + return; + } + + OUString sName; + if ( ! (anyTitle >>= sName) ) + { + OSL_FAIL("Could not get file name for extension."); + return; + } + + rCmdEnv->setWarnUser( bWarnUser ); + uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager(); + uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() ); + OUString sTitle( + m_sAddingPackages.replaceAll("%EXTENSION_NAME", sName)); + rCmdEnv->progressSection( sTitle, xAbortChannel ); + + try + { + xExtMgr->addExtension(rPackageURL, uno::Sequence<beans::NamedValue>(), + rRepository, xAbortChannel, rCmdEnv ); + } + catch ( const ucb::CommandFailedException & ) + { + // When the extension is already installed we'll get a dialog asking if we want to overwrite. If we then press + // cancel this exception is thrown. + } + catch ( const ucb::CommandAbortedException & ) + { + // User clicked the cancel button + // TODO: handle cancel + } + rCmdEnv->setWarnUser( false ); +} + + +void ExtensionCmdQueue::Thread::_removeExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const uno::Reference< deployment::XPackage > &xPackage ) +{ + uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager(); + uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() ); + OUString sTitle( + m_sRemovingPackages.replaceAll("%EXTENSION_NAME", + xPackage->getDisplayName())); + rCmdEnv->progressSection( sTitle, xAbortChannel ); + + OUString id( dp_misc::getIdentifier( xPackage ) ); + try + { + xExtMgr->removeExtension( id, xPackage->getName(), xPackage->getRepositoryName(), xAbortChannel, rCmdEnv ); + } + catch ( const deployment::DeploymentException & ) + {} + catch ( const ucb::CommandFailedException & ) + {} + catch ( const ucb::CommandAbortedException & ) + {} + + // Check, if there are still updates to be notified via menu bar icon + uno::Sequence< uno::Sequence< OUString > > aItemList; + UpdateDialog::createNotifyJob( false, aItemList ); +} + + +void ExtensionCmdQueue::Thread::_checkForUpdates( + std::vector<uno::Reference<deployment::XPackage > > && vExtensionList ) +{ + const SolarMutexGuard guard; + + if (m_pDialogHelper) + m_pDialogHelper->incBusy(); + + std::vector< UpdateData > vData; + UpdateDialog aUpdateDialog(m_xContext, m_pDialogHelper ? m_pDialogHelper->getFrameWeld() : nullptr, std::move(vExtensionList), &vData); + + aUpdateDialog.notifyMenubar( true, false ); // prepare the checking, if there updates to be notified via menu bar icon + + bool bOk = aUpdateDialog.run() == RET_OK; + if (m_pDialogHelper) + m_pDialogHelper->decBusy(); + + if (bOk && !vData.empty()) + { + // If there is at least one directly downloadable extension then we + // open the install dialog. + std::vector< UpdateData > dataDownload; + + for (auto const& data : vData) + { + if ( data.sWebsiteURL.isEmpty() ) + dataDownload.push_back(data); + } + + short nDialogResult = RET_OK; + if ( !dataDownload.empty() ) + { + if (m_pDialogHelper) + m_pDialogHelper->incBusy(); + UpdateInstallDialog aDlg(m_pDialogHelper ? m_pDialogHelper->getFrameWeld() : nullptr, dataDownload, m_xContext); + nDialogResult = aDlg.run(); + if (m_pDialogHelper) + m_pDialogHelper->decBusy(); + aUpdateDialog.notifyMenubar( false, true ); // Check, if there are still pending updates to be notified via menu bar icon + } + else + aUpdateDialog.notifyMenubar( false, false ); // Check, if there are pending updates to be notified via menu bar icon + + //Now start the webbrowser and navigate to the websites where we get the updates + if ( RET_OK == nDialogResult ) + { + for (auto const& data : vData) + { + if ( m_pDialogHelper && ( !data.sWebsiteURL.isEmpty() ) ) + m_pDialogHelper->openWebBrowser( data.sWebsiteURL, m_pDialogHelper->getFrameWeld()->get_title() ); + } + } + } + else + aUpdateDialog.notifyMenubar( false, false ); // check if there updates to be notified via menu bar icon +} + + +void ExtensionCmdQueue::Thread::_enableExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const uno::Reference< deployment::XPackage > &xPackage ) +{ + if ( !xPackage.is() ) + return; + + uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager(); + uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() ); + OUString sTitle( + m_sEnablingPackages.replaceAll("%EXTENSION_NAME", + xPackage->getDisplayName())); + rCmdEnv->progressSection( sTitle, xAbortChannel ); + + try + { + xExtMgr->enableExtension( xPackage, xAbortChannel, rCmdEnv ); + if ( m_pDialogHelper ) + m_pDialogHelper->updatePackageInfo( xPackage ); + } + catch ( const ::ucb::CommandAbortedException & ) + {} +} + + +void ExtensionCmdQueue::Thread::_disableExtension( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const uno::Reference< deployment::XPackage > &xPackage ) +{ + if ( !xPackage.is() ) + return; + + uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager(); + uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() ); + OUString sTitle( + m_sDisablingPackages.replaceAll("%EXTENSION_NAME", + xPackage->getDisplayName())); + rCmdEnv->progressSection( sTitle, xAbortChannel ); + + try + { + xExtMgr->disableExtension( xPackage, xAbortChannel, rCmdEnv ); + if ( m_pDialogHelper ) + m_pDialogHelper->updatePackageInfo( xPackage ); + } + catch ( const ::ucb::CommandAbortedException & ) + {} +} + + +void ExtensionCmdQueue::Thread::_acceptLicense( ::rtl::Reference< ProgressCmdEnv > const &rCmdEnv, + const uno::Reference< deployment::XPackage > &xPackage ) +{ + if ( !xPackage.is() ) + return; + + uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager(); + uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() ); + OUString sTitle( + m_sAcceptLicense.replaceAll("%EXTENSION_NAME", + xPackage->getDisplayName())); + rCmdEnv->progressSection( sTitle, xAbortChannel ); + + try + { + xExtMgr->checkPrerequisitesAndEnable( xPackage, xAbortChannel, rCmdEnv ); + if ( m_pDialogHelper ) + m_pDialogHelper->updatePackageInfo( xPackage ); + } + catch ( const ::ucb::CommandAbortedException & ) + {} +} + +void ExtensionCmdQueue::Thread::_insert(const TExtensionCmd& rExtCmd) +{ + std::scoped_lock aGuard( m_mutex ); + + // If someone called stop then we do not process the command -> game over! + if ( m_bStopped ) + return; + + m_queue.push( rExtCmd ); + m_eInput = START; + m_wakeup.notify_all(); +} + + +ExtensionCmdQueue::ExtensionCmdQueue( DialogHelper * pDialogHelper, + TheExtensionManager *pManager, + const uno::Reference< uno::XComponentContext > &rContext ) + : m_thread( new Thread( pDialogHelper, pManager, rContext ) ) +{ + m_thread->launch(); +} + +ExtensionCmdQueue::~ExtensionCmdQueue() { + m_thread->stop(); + m_thread->join(); +} + +void ExtensionCmdQueue::addExtension( const OUString & extensionURL, + const OUString & repository, + const bool bWarnUser ) +{ + m_thread->addExtension( extensionURL, repository, bWarnUser ); +} + +void ExtensionCmdQueue::removeExtension( const uno::Reference< deployment::XPackage > &rPackage ) +{ + m_thread->removeExtension( rPackage ); +} + +void ExtensionCmdQueue::enableExtension( const uno::Reference< deployment::XPackage > &rPackage, + const bool bEnable ) +{ + m_thread->enableExtension( rPackage, bEnable ); +} + +void ExtensionCmdQueue::checkForUpdates( std::vector<uno::Reference<deployment::XPackage > > && vExtensionList ) +{ + m_thread->checkForUpdates( std::move(vExtensionList) ); +} + +void ExtensionCmdQueue::acceptLicense( const uno::Reference< deployment::XPackage > &rPackage ) +{ + m_thread->acceptLicense( rPackage ); +} + +void ExtensionCmdQueue::syncRepositories( const uno::Reference< uno::XComponentContext > &xContext ) +{ + dp_misc::syncRepositories( false, new ProgressCmdEnv( xContext, nullptr, "Extension Manager" ) ); +} + +bool ExtensionCmdQueue::isBusy() +{ + return m_thread->isBusy(); +} + +void handleInteractionRequest( const uno::Reference< uno::XComponentContext > & xContext, + const uno::Reference< task::XInteractionRequest > & xRequest ) +{ + ::rtl::Reference< ProgressCmdEnv > xCmdEnv( new ProgressCmdEnv( xContext, nullptr, "Extension Manager" ) ); + xCmdEnv->handle( xRequest ); +} + +} //namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.hxx b/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.hxx new file mode 100644 index 000000000..3703d1e8c --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.hxx @@ -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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <com/sun/star/uno/Reference.hxx> +#include <rtl/ref.hxx> + +#include <vector> + +#include "dp_gui_updatedata.hxx" + +/// @HTML + +namespace com::sun::star { + namespace task { class XInteractionRequest; } + namespace uno { class XComponentContext; } +} + +namespace dp_gui { + +class DialogHelper; +class TheExtensionManager; + +/** + Manages installing of extensions in the GUI mode. Requests for installing + Extensions can be asynchronous. For example, the Extension Manager is running + in an office process and someone uses the system integration to install an Extension. + That is, the user double clicks an extension symbol in a file browser, which then + causes an invocation of "unopkg gui ext". When at that time the Extension Manager + already performs a task, triggered by the user (for example, add, update, disable, + enable) then adding of the extension will be postponed until the user has finished + the task. + + This class also ensures that the extensions are not installed in the main thread. + Doing so would cause a deadlock because of the progress bar which needs to be constantly + updated. +*/ +class ExtensionCmdQueue { + +public: + /** + Create an instance. + */ + ExtensionCmdQueue( DialogHelper * pDialogHelper, + TheExtensionManager *pManager, + const css::uno::Reference< css::uno::XComponentContext > & rContext); + + ~ExtensionCmdQueue(); + + void addExtension( const OUString &rExtensionURL, + const OUString &rRepository, + const bool bWarnUser ); + void removeExtension( const css::uno::Reference< css::deployment::XPackage > &rPackage ); + void enableExtension( const css::uno::Reference< css::deployment::XPackage > &rPackage, + const bool bEnable ); + void checkForUpdates( std::vector< css::uno::Reference< css::deployment::XPackage > > && vList ); + void acceptLicense( const css::uno::Reference< css::deployment::XPackage > &rPackage ); + static void syncRepositories( const css::uno::Reference< css::uno::XComponentContext > & xContext ); + + bool isBusy(); +private: + ExtensionCmdQueue(ExtensionCmdQueue const &) = delete; + ExtensionCmdQueue& operator =(ExtensionCmdQueue const &) = delete; + + class Thread; + + rtl::Reference< Thread > m_thread; +}; + +void handleInteractionRequest( const css::uno::Reference< css::uno::XComponentContext > & xContext, + const css::uno::Reference< css::task::XInteractionRequest > & xRequest ); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_extlistbox.cxx b/desktop/source/deployment/gui/dp_gui_extlistbox.cxx new file mode 100644 index 000000000..d30d24f79 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_extlistbox.cxx @@ -0,0 +1,1144 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <dp_shared.hxx> +#include <strings.hrc> +#include "dp_gui.h" +#include "dp_gui_extlistbox.hxx" +#include "dp_gui_theextmgr.hxx" +#include <dp_dependencies.hxx> +#include <bitmaps.hlst> + +#include <comphelper/processfactory.hxx> +#include <com/sun/star/i18n/CollatorOptions.hpp> +#include <com/sun/star/deployment/DependencyException.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionRemovedException.hpp> +#include <com/sun/star/system/XSystemShellExecute.hpp> +#include <com/sun/star/system/SystemShellExecuteFlags.hpp> +#include <com/sun/star/system/SystemShellExecute.hpp> +#include <cppuhelper/weakref.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <rtl/ustrbuf.hxx> +#include <utility> +#include <vcl/event.hxx> +#include <vcl/ptrstyle.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/weldutils.hxx> +#include <algorithm> + +constexpr OUStringLiteral USER_PACKAGE_MANAGER = u"user"; +constexpr OUStringLiteral SHARED_PACKAGE_MANAGER = u"shared"; + +using namespace ::com::sun::star; + +namespace dp_gui { + +namespace { + +struct FindWeakRef +{ + const uno::Reference<deployment::XPackage> m_extension; + + explicit FindWeakRef( uno::Reference<deployment::XPackage> ext): m_extension(std::move(ext)) {} + bool operator () (uno::WeakReference< deployment::XPackage > const & ref); +}; + +bool FindWeakRef::operator () (uno::WeakReference< deployment::XPackage > const & ref) +{ + const uno::Reference<deployment::XPackage> ext(ref); + return ext == m_extension; +} + +} // end namespace + +// struct Entry_Impl + +Entry_Impl::Entry_Impl( const uno::Reference< deployment::XPackage > &xPackage, + const PackageState eState, const bool bReadOnly ) : + m_bActive( false ), + m_bLocked( bReadOnly ), + m_bHasOptions( false ), + m_bUser( false ), + m_bShared( false ), + m_bNew( false ), + m_bChecked( false ), + m_bMissingDeps( false ), + m_bHasButtons( false ), + m_bMissingLic( false ), + m_eState( eState ), + m_xPackage( xPackage ) +{ + try + { + m_sTitle = xPackage->getDisplayName(); + m_sVersion = xPackage->getVersion(); + m_sDescription = xPackage->getDescription(); + m_sLicenseText = xPackage->getLicenseText(); + + beans::StringPair aInfo( m_xPackage->getPublisherInfo() ); + m_sPublisher = aInfo.First; + m_sPublisherURL = aInfo.Second; + + // get the icons for the package if there are any + uno::Reference< graphic::XGraphic > xGraphic = xPackage->getIcon( false ); + if ( xGraphic.is() ) + m_aIcon = Image( xGraphic ); + + if ( eState == AMBIGUOUS ) + m_sErrorText = DpResId( RID_STR_ERROR_UNKNOWN_STATUS ); + else if ( eState == NOT_REGISTERED ) + checkDependencies(); + } + catch (const deployment::ExtensionRemovedException &) {} + catch (const uno::RuntimeException &) {} +} + + +Entry_Impl::~Entry_Impl() +{} + + +sal_Int32 Entry_Impl::CompareTo( const CollatorWrapper *pCollator, const TEntry_Impl& rEntry ) const +{ + sal_Int32 eCompare = pCollator->compareString( m_sTitle, rEntry->m_sTitle ); + if ( eCompare == 0 ) + { + eCompare = m_sVersion.compareTo( rEntry->m_sVersion ); + if ( eCompare == 0 ) + { + sal_Int32 nCompare = m_xPackage->getRepositoryName().compareTo( rEntry->m_xPackage->getRepositoryName() ); + if ( nCompare < 0 ) + eCompare = -1; + else if ( nCompare > 0 ) + eCompare = 1; + } + } + return eCompare; +} + + +void Entry_Impl::checkDependencies() +{ + try { + m_xPackage->checkDependencies( uno::Reference< ucb::XCommandEnvironment >() ); + } + catch ( const deployment::DeploymentException &e ) + { + deployment::DependencyException depExc; + if ( e.Cause >>= depExc ) + { + OUStringBuffer aMissingDep( DpResId( RID_STR_ERROR_MISSING_DEPENDENCIES ) ); + for ( const auto& i : std::as_const(depExc.UnsatisfiedDependencies) ) + { + aMissingDep.append("\n"); + aMissingDep.append(dp_misc::Dependencies::getErrorText(i)); + } + aMissingDep.append("\n"); + m_sErrorText = aMissingDep.makeStringAndClear(); + m_bMissingDeps = true; + } + } +} + +// ExtensionRemovedListener + +void ExtensionRemovedListener::disposing( lang::EventObject const & rEvt ) +{ + uno::Reference< deployment::XPackage > xPackage( rEvt.Source, uno::UNO_QUERY ); + + if ( xPackage.is() ) + { + m_pParent->removeEntry( xPackage ); + } +} + + +ExtensionRemovedListener::~ExtensionRemovedListener() +{ +} + + +// ExtensionBox_Impl +ExtensionBox_Impl::ExtensionBox_Impl(std::unique_ptr<weld::ScrolledWindow> xScroll) + : m_bHasScrollBar( false ) + , m_bHasActive( false ) + , m_bNeedsRecalc( true ) + , m_bInCheckMode( false ) + , m_bAdjustActive( false ) + , m_bInDelete( false ) + , m_nActive( 0 ) + , m_nTopIndex( 0 ) + , m_nStdHeight( 0 ) + , m_nActiveHeight( 0 ) + , m_aSharedImage(StockImage::Yes, RID_BMP_SHARED) + , m_aLockedImage(StockImage::Yes, RID_BMP_LOCKED) + , m_aWarningImage(StockImage::Yes, RID_BMP_WARNING) + , m_aDefaultImage(StockImage::Yes, RID_BMP_EXTENSION) + , m_pManager( nullptr ) + , m_xScrollBar(std::move(xScroll)) +{ +} + +void ExtensionBox_Impl::Init() +{ + m_xScrollBar->connect_vadjustment_changed( LINK( this, ExtensionBox_Impl, ScrollHdl ) ); + + auto nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE; + auto nTitleHeight = 2*TOP_OFFSET + GetTextHeight(); + if ( nIconHeight < nTitleHeight ) + m_nStdHeight = nTitleHeight; + else + m_nStdHeight = nIconHeight; + m_nStdHeight += GetTextHeight() + TOP_OFFSET; + + nIconHeight = ICON_HEIGHT + 2*TOP_OFFSET + 1; + if ( m_nStdHeight < nIconHeight ) + m_nStdHeight = nIconHeight; + + m_nActiveHeight = m_nStdHeight; + + m_xRemoveListener = new ExtensionRemovedListener( this ); + + m_pLocale.reset( new lang::Locale( Application::GetSettings().GetLanguageTag().getLocale() ) ); + m_pCollator.reset( new CollatorWrapper( ::comphelper::getProcessComponentContext() ) ); + m_pCollator->loadDefaultCollator( *m_pLocale, i18n::CollatorOptions::CollatorOptions_IGNORE_CASE ); +} + +ExtensionBox_Impl::~ExtensionBox_Impl() +{ + if ( ! m_bInDelete ) + DeleteRemoved(); + + m_bInDelete = true; + + for (auto const& entry : m_vEntries) + { + entry->m_xPackage->removeEventListener( m_xRemoveListener ); + } + + m_vEntries.clear(); + + m_xRemoveListener.clear(); + + m_pLocale.reset(); + m_pCollator.reset(); +} + +sal_Int32 ExtensionBox_Impl::getItemCount() const +{ + return static_cast< sal_Int32 >( m_vEntries.size() ); +} + + +sal_Int32 ExtensionBox_Impl::getSelIndex() const +{ + if ( m_bHasActive ) + { + OSL_ASSERT( m_nActive >= -1); + return static_cast< sal_Int32 >( m_nActive ); + } + else + return ENTRY_NOTFOUND; +} + + +// Title + description +void ExtensionBox_Impl::CalcActiveHeight( const tools::Long nPos ) +{ + const ::osl::MutexGuard aGuard( m_entriesMutex ); + + // get title height + tools::Long aTextHeight; + tools::Long nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE; + tools::Long nTitleHeight = 2*TOP_OFFSET + GetTextHeight(); + if ( nIconHeight < nTitleHeight ) + aTextHeight = nTitleHeight; + else + aTextHeight = nIconHeight; + + // calc description height + Size aSize = GetOutputSizePixel(); + + aSize.AdjustWidth( -(ICON_OFFSET) ); + aSize.setHeight( 10000 ); + + OUString aText( m_vEntries[ nPos ]->m_sErrorText ); + if ( !aText.isEmpty() ) + aText += "\n"; + aText += m_vEntries[ nPos ]->m_sDescription; + + tools::Rectangle aRect = GetDrawingArea()->get_ref_device().GetTextRect(tools::Rectangle( Point(), aSize ), aText, + DrawTextFlags::MultiLine | DrawTextFlags::WordBreak); + aTextHeight += aRect.GetHeight(); + + if ( aTextHeight < m_nStdHeight ) + aTextHeight = m_nStdHeight; + + m_nActiveHeight = aTextHeight; + + if ( m_vEntries[ nPos ]->m_bHasButtons ) + m_nActiveHeight += 2; +} + +tools::Rectangle ExtensionBox_Impl::GetEntryRect( const tools::Long nPos ) const +{ + const ::osl::MutexGuard aGuard( m_entriesMutex ); + + Size aSize( GetOutputSizePixel() ); + + if ( m_vEntries[ nPos ]->m_bActive ) + aSize.setHeight( m_nActiveHeight ); + else + aSize.setHeight( m_nStdHeight ); + + Point aPos( 0, -m_nTopIndex + nPos * m_nStdHeight ); + if ( m_bHasActive && ( nPos < m_nActive ) ) + aPos.AdjustY(m_nActiveHeight - m_nStdHeight ); + + return tools::Rectangle( aPos, aSize ); +} + + +void ExtensionBox_Impl::DeleteRemoved() +{ + const ::osl::MutexGuard aGuard( m_entriesMutex ); + + m_bInDelete = true; + + m_vRemovedEntries.clear(); + + m_bInDelete = false; +} + + +//This function may be called with nPos < 0 +void ExtensionBox_Impl::selectEntry( const tools::Long nPos ) +{ + bool invalidate = false; + { + //ToDo we should not use the guard at such a big scope here. + //Currently it is used to guard m_vEntries and m_nActive. m_nActive will be + //modified in this function. + //It would be probably best to always use a copy of m_vEntries + //and some other state variables from ExtensionBox_Impl for + //the whole painting operation. See issue i86993 + ::osl::MutexGuard guard(m_entriesMutex); + + if ( m_bInCheckMode ) + return; + + if ( m_bHasActive ) + { + if ( nPos == m_nActive ) + return; + + m_bHasActive = false; + m_vEntries[ m_nActive ]->m_bActive = false; + } + + if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) ) + { + m_bHasActive = true; + m_nActive = nPos; + m_vEntries[ nPos ]->m_bActive = true; + + if ( IsReallyVisible() ) + { + m_bAdjustActive = true; + } + } + + if ( IsReallyVisible() ) + { + m_bNeedsRecalc = true; + invalidate = true; + } + } + + if (invalidate) + { + SolarMutexGuard g; + Invalidate(); + } +} + + +void ExtensionBox_Impl::DrawRow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, const TEntry_Impl& rEntry) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + if (rEntry->m_bActive) + rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor()); + else if ((rEntry->m_eState != REGISTERED) && (rEntry->m_eState != NOT_AVAILABLE)) + rRenderContext.SetTextColor(rStyleSettings.GetDisableColor()); + else + rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor()); + + if (rEntry->m_bActive) + { + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(rStyleSettings.GetHighlightColor()); + rRenderContext.DrawRect(rRect); + } + else + { + rRenderContext.SetBackground(rStyleSettings.GetFieldColor()); + rRenderContext.SetTextFillColor(); + rRenderContext.Erase(rRect); + } + + // Draw extension icon + Point aPos( rRect.TopLeft() ); + aPos += Point(TOP_OFFSET, TOP_OFFSET); + Image aImage; + if (!rEntry->m_aIcon) + aImage = m_aDefaultImage; + else + aImage = rEntry->m_aIcon; + Size aImageSize = aImage.GetSizePixel(); + if ((aImageSize.Width() <= ICON_WIDTH ) && ( aImageSize.Height() <= ICON_HEIGHT ) ) + rRenderContext.DrawImage(Point(aPos.X() + ((ICON_WIDTH - aImageSize.Width()) / 2), + aPos.Y() + ((ICON_HEIGHT - aImageSize.Height()) / 2)), + aImage); + else + rRenderContext.DrawImage(aPos, Size(ICON_WIDTH, ICON_HEIGHT), aImage); + + // Setup fonts + // expand the point size of the desired font to the equivalent pixel size + weld::SetPointFont(rRenderContext, GetDrawingArea()->get_font()); + vcl::Font aStdFont(rRenderContext.GetFont()); + vcl::Font aBoldFont(aStdFont); + aBoldFont.SetWeight(WEIGHT_BOLD); + rRenderContext.SetFont(aBoldFont); + auto aTextHeight = rRenderContext.GetTextHeight(); + + // Get max title width + auto nMaxTitleWidth = rRect.GetWidth() - ICON_OFFSET; + nMaxTitleWidth -= (2 * SMALL_ICON_SIZE) + (4 * SPACE_BETWEEN); + rRenderContext.SetFont(aStdFont); + tools::Long nLinkWidth = 0; + if (!rEntry->m_sPublisher.isEmpty()) + { + nLinkWidth = rRenderContext.GetTextWidth(rEntry->m_sPublisher); + nMaxTitleWidth -= nLinkWidth + (2 * SPACE_BETWEEN); + } + tools::Long aVersionWidth = rRenderContext.GetTextWidth(rEntry->m_sVersion); + + aPos = rRect.TopLeft() + Point(ICON_OFFSET, TOP_OFFSET); + + rRenderContext.SetFont(aBoldFont); + tools::Long aTitleWidth = rRenderContext.GetTextWidth(rEntry->m_sTitle) + (aTextHeight / 3); + if (aTitleWidth > nMaxTitleWidth - aVersionWidth) + { + aTitleWidth = nMaxTitleWidth - aVersionWidth - (aTextHeight / 3); + OUString aShortTitle = rRenderContext.GetEllipsisString(rEntry->m_sTitle, aTitleWidth); + rRenderContext.DrawText(aPos, aShortTitle); + aTitleWidth += (aTextHeight / 3); + } + else + rRenderContext.DrawText(aPos, rEntry->m_sTitle); + + rRenderContext.SetFont(aStdFont); + rRenderContext.DrawText(Point(aPos.X() + aTitleWidth, aPos.Y()), rEntry->m_sVersion); + + tools::Long nIconHeight = TOP_OFFSET + SMALL_ICON_SIZE; + tools::Long nTitleHeight = TOP_OFFSET + GetTextHeight(); + if ( nIconHeight < nTitleHeight ) + aTextHeight = nTitleHeight; + else + aTextHeight = nIconHeight; + + // draw description + OUString sDescription; + if (!rEntry->m_sErrorText.isEmpty()) + { + if (rEntry->m_bActive) + sDescription = rEntry->m_sErrorText + "\n" + rEntry->m_sDescription; + else + sDescription = rEntry->m_sErrorText; + } + else + sDescription = rEntry->m_sDescription; + + aPos.AdjustY(aTextHeight ); + if (rEntry->m_bActive) + { + tools::Long nExtraHeight = 0; + + if (rEntry->m_bHasButtons) + nExtraHeight = 2; + + rRenderContext.DrawText(tools::Rectangle(aPos.X(), aPos.Y(), rRect.Right(), rRect.Bottom() - nExtraHeight), + sDescription, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak ); + } + else + { + //replace LF to space, so words do not stick together in one line view + sDescription = sDescription.replace(0x000A, ' '); + const tools::Long nWidth = rRenderContext.GetTextWidth( sDescription ); + if (nWidth > rRect.GetWidth() - aPos.X()) + sDescription = rRenderContext.GetEllipsisString(sDescription, rRect.GetWidth() - aPos.X()); + rRenderContext.DrawText(aPos, sDescription); + } + + // Draw publisher link + if (!rEntry->m_sPublisher.isEmpty()) + { + aPos = rRect.TopLeft() + Point( ICON_OFFSET + nMaxTitleWidth + (2*SPACE_BETWEEN), TOP_OFFSET ); + + rRenderContext.Push(vcl::PushFlags::FONT | vcl::PushFlags::TEXTCOLOR | vcl::PushFlags::TEXTFILLCOLOR); + rRenderContext.SetTextColor(rStyleSettings.GetLinkColor()); + rRenderContext.SetTextFillColor(rStyleSettings.GetFieldColor()); + vcl::Font aFont = rRenderContext.GetFont(); + // to underline + aFont.SetUnderline(LINESTYLE_SINGLE); + rRenderContext.SetFont(aFont); + rRenderContext.DrawText(aPos, rEntry->m_sPublisher); + rEntry->m_aLinkRect = tools::Rectangle(aPos, Size(nLinkWidth, aTextHeight)); + rRenderContext.Pop(); + } + + // Draw status icons + if (!rEntry->m_bUser) + { + aPos = rRect.TopRight() + Point( -(RIGHT_ICON_OFFSET + SMALL_ICON_SIZE), TOP_OFFSET ); + if (rEntry->m_bLocked) + rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aLockedImage); + else + rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aSharedImage); + } + if ((rEntry->m_eState == AMBIGUOUS ) || rEntry->m_bMissingDeps || rEntry->m_bMissingLic) + { + aPos = rRect.TopRight() + Point(-(RIGHT_ICON_OFFSET + SPACE_BETWEEN + 2 * SMALL_ICON_SIZE), TOP_OFFSET); + rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aWarningImage); + } + + rRenderContext.SetLineColor(COL_LIGHTGRAY); + rRenderContext.DrawLine(rRect.BottomLeft(), rRect.BottomRight()); +} + + +void ExtensionBox_Impl::RecalcAll() +{ + if ( m_bHasActive ) + CalcActiveHeight( m_nActive ); + + SetupScrollBar(); + + if ( m_bHasActive ) + { + tools::Rectangle aEntryRect = GetEntryRect( m_nActive ); + + if ( m_bAdjustActive ) + { + m_bAdjustActive = false; + + // If the top of the selected entry isn't visible, make it visible + if ( aEntryRect.Top() < 0 ) + { + m_nTopIndex += aEntryRect.Top(); + aEntryRect.Move( 0, -aEntryRect.Top() ); + } + + // If the bottom of the selected entry isn't visible, make it visible even if now the top + // isn't visible any longer ( the buttons are more important ) + Size aOutputSize = GetOutputSizePixel(); + if ( aEntryRect.Bottom() > aOutputSize.Height() ) + { + m_nTopIndex += ( aEntryRect.Bottom() - aOutputSize.Height() ); + aEntryRect.Move( 0, -( aEntryRect.Bottom() - aOutputSize.Height() ) ); + } + + // If there is unused space below the last entry but all entries don't fit into the box, + // move the content down to use the whole space + const tools::Long nTotalHeight = GetTotalHeight(); + if ( m_bHasScrollBar && ( aOutputSize.Height() + m_nTopIndex > nTotalHeight ) ) + { + tools::Long nOffset = m_nTopIndex; + m_nTopIndex = nTotalHeight - aOutputSize.Height(); + nOffset -= m_nTopIndex; + aEntryRect.Move( 0, nOffset ); + } + + if ( m_bHasScrollBar ) + m_xScrollBar->vadjustment_set_value( m_nTopIndex ); + } + } + + m_bNeedsRecalc = false; +} + + +bool ExtensionBox_Impl::HandleCursorKey( sal_uInt16 nKeyCode ) +{ + if ( m_vEntries.empty() ) + return true; + + tools::Long nSelect = 0; + + if ( m_bHasActive ) + { + tools::Long nPageSize = GetOutputSizePixel().Height() / m_nStdHeight; + if ( nPageSize < 2 ) + nPageSize = 2; + + if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_RIGHT ) ) + nSelect = m_nActive + 1; + else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_LEFT ) ) + nSelect = m_nActive - 1; + else if ( nKeyCode == KEY_HOME ) + nSelect = 0; + else if ( nKeyCode == KEY_END ) + nSelect = m_vEntries.size() - 1; + else if ( nKeyCode == KEY_PAGEUP ) + nSelect = m_nActive - nPageSize + 1; + else if ( nKeyCode == KEY_PAGEDOWN ) + nSelect = m_nActive + nPageSize - 1; + } + else // when there is no selected entry, we will select the first or the last. + { + if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_PAGEDOWN ) || ( nKeyCode == KEY_HOME ) ) + nSelect = 0; + else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_PAGEUP ) || ( nKeyCode == KEY_END ) ) + nSelect = m_vEntries.size() - 1; + } + + if ( nSelect < 0 ) + nSelect = 0; + if ( o3tl::make_unsigned(nSelect) >= m_vEntries.size() ) + nSelect = m_vEntries.size() - 1; + + selectEntry( nSelect ); + + return true; +} + + +void ExtensionBox_Impl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rPaintRect*/) +{ + if ( !m_bInDelete ) + DeleteRemoved(); + + if ( m_bNeedsRecalc ) + RecalcAll(); + + Point aStart( 0, -m_nTopIndex ); + Size aSize(GetOutputSizePixel()); + + const ::osl::MutexGuard aGuard( m_entriesMutex ); + + for (auto const& entry : m_vEntries) + { + aSize.setHeight( entry->m_bActive ? m_nActiveHeight : m_nStdHeight ); + tools::Rectangle aEntryRect( aStart, aSize ); + DrawRow(rRenderContext, aEntryRect, entry); + aStart.AdjustY(aSize.Height() ); + } +} + + +tools::Long ExtensionBox_Impl::GetTotalHeight() const +{ + tools::Long nHeight = m_vEntries.size() * m_nStdHeight; + + if ( m_bHasActive ) + { + nHeight += m_nActiveHeight - m_nStdHeight; + } + + return nHeight; +} + + +void ExtensionBox_Impl::SetupScrollBar() +{ + const Size aSize = GetOutputSizePixel(); + const auto nTotalHeight = GetTotalHeight(); + const bool bNeedsScrollBar = ( nTotalHeight > aSize.Height() ); + + if ( bNeedsScrollBar ) + { + if ( m_nTopIndex + aSize.Height() > nTotalHeight ) + m_nTopIndex = nTotalHeight - aSize.Height(); + + m_xScrollBar->vadjustment_configure(m_nTopIndex, 0, nTotalHeight, + m_nStdHeight, ( aSize.Height() * 4 ) / 5, + aSize.Height()); + + if (!m_bHasScrollBar) + m_xScrollBar->set_vpolicy(VclPolicyType::ALWAYS); + } + else if ( m_bHasScrollBar ) + { + m_xScrollBar->set_vpolicy(VclPolicyType::NEVER); + m_nTopIndex = 0; + } + + m_bHasScrollBar = bNeedsScrollBar; +} + + +void ExtensionBox_Impl::Resize() +{ + RecalcAll(); + Invalidate(); +} + +void ExtensionBox_Impl::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + Size aSize = pDrawingArea->get_ref_device().LogicToPixel(Size(250, 150), MapMode(MapUnit::MapAppFont)); + pDrawingArea->set_size_request(aSize.Width(), aSize.Height()); + CustomWidgetController::SetDrawingArea(pDrawingArea); + SetOutputSizePixel(aSize); + + Init(); +} + +tools::Long ExtensionBox_Impl::PointToPos( const Point& rPos ) +{ + tools::Long nPos = ( rPos.Y() + m_nTopIndex ) / m_nStdHeight; + + if ( m_bHasActive && ( nPos > m_nActive ) ) + { + if ( rPos.Y() + m_nTopIndex <= m_nActive*m_nStdHeight + m_nActiveHeight ) + nPos = m_nActive; + else + nPos = ( rPos.Y() + m_nTopIndex - (m_nActiveHeight - m_nStdHeight) ) / m_nStdHeight; + } + + return nPos; +} + +bool ExtensionBox_Impl::MouseMove( const MouseEvent& rMEvt ) +{ + bool bOverHyperlink = false; + + auto nPos = PointToPos( rMEvt.GetPosPixel() ); + if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) ) + { + const auto& rEntry = m_vEntries[nPos]; + bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rMEvt.GetPosPixel()); + } + + if (bOverHyperlink) + SetPointer(PointerStyle::RefHand); + else + SetPointer(PointerStyle::Arrow); + + return false; +} + +OUString ExtensionBox_Impl::RequestHelp(tools::Rectangle& rRect) +{ + auto nPos = PointToPos( rRect.TopLeft() ); + if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) ) + { + const auto& rEntry = m_vEntries[nPos]; + bool bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rRect); + if (bOverHyperlink) + { + rRect = rEntry->m_aLinkRect; + return rEntry->m_sPublisherURL; + } + } + + return OUString(); +} + +bool ExtensionBox_Impl::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( !rMEvt.IsLeft() ) + return false; + + if (rMEvt.IsMod1() && m_bHasActive) + selectEntry(ExtensionBox_Impl::ENTRY_NOTFOUND); // Selecting a not existing entry will deselect the current one + else + { + auto nPos = PointToPos( rMEvt.GetPosPixel() ); + + if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) ) + { + const auto& rEntry = m_vEntries[nPos]; + if (!rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rMEvt.GetPosPixel())) + { + try + { + css::uno::Reference<css::system::XSystemShellExecute> xSystemShellExecute( + css::system::SystemShellExecute::create(comphelper::getProcessComponentContext())); + //throws css::lang::IllegalArgumentException, css::system::SystemShellExecuteException + xSystemShellExecute->execute(rEntry->m_sPublisherURL, OUString(), css::system::SystemShellExecuteFlags::URIS_ONLY); + } + catch (...) + { + } + return true; + } + } + + selectEntry( nPos ); + } + return true; +} + +bool ExtensionBox_Impl::KeyInput(const KeyEvent& rKEvt) +{ + if ( !m_bInDelete ) + DeleteRemoved(); + + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + sal_uInt16 nKeyCode = aKeyCode.GetCode(); + + bool bHandled = false; + if (nKeyCode != KEY_TAB && aKeyCode.GetGroup() == KEYGROUP_CURSOR) + bHandled = HandleCursorKey(nKeyCode); + + return bHandled; +} + +bool ExtensionBox_Impl::FindEntryPos( const TEntry_Impl& rEntry, const tools::Long nStart, + const tools::Long nEnd, tools::Long &nPos ) +{ + nPos = nStart; + if ( nStart > nEnd ) + return false; + + sal_Int32 eCompare; + + if ( nStart == nEnd ) + { + eCompare = rEntry->CompareTo( m_pCollator.get(), m_vEntries[ nStart ] ); + if ( eCompare < 0 ) + return false; + else if ( eCompare == 0 ) + { + //Workaround. See i86963. + if (rEntry->m_xPackage != m_vEntries[nStart]->m_xPackage) + return false; + + if ( m_bInCheckMode ) + m_vEntries[ nStart ]->m_bChecked = true; + return true; + } + else + { + nPos = nStart + 1; + return false; + } + } + + const tools::Long nMid = nStart + ( ( nEnd - nStart ) / 2 ); + eCompare = rEntry->CompareTo( m_pCollator.get(), m_vEntries[ nMid ] ); + + if ( eCompare < 0 ) + return FindEntryPos( rEntry, nStart, nMid-1, nPos ); + else if ( eCompare > 0 ) + return FindEntryPos( rEntry, nMid+1, nEnd, nPos ); + else + { + //Workaround.See i86963. + if (rEntry->m_xPackage != m_vEntries[nMid]->m_xPackage) + return false; + + if ( m_bInCheckMode ) + m_vEntries[ nMid ]->m_bChecked = true; + nPos = nMid; + return true; + } +} + +void ExtensionBox_Impl::cleanVecListenerAdded() +{ + m_vListenerAdded.erase(std::remove_if(m_vListenerAdded.begin(), m_vListenerAdded.end(), + [](const uno::WeakReference<deployment::XPackage>& rxListener) { + const uno::Reference<deployment::XPackage> hardRef(rxListener); + return !hardRef.is(); + }), + m_vListenerAdded.end()); +} + +void ExtensionBox_Impl::addEventListenerOnce( + uno::Reference<deployment::XPackage > const & extension) +{ + //make sure to only add the listener once + cleanVecListenerAdded(); + if ( std::none_of(m_vListenerAdded.begin(), m_vListenerAdded.end(), + FindWeakRef(extension)) ) + { + extension->addEventListener( m_xRemoveListener ); + m_vListenerAdded.emplace_back(extension); + } +} + + +void ExtensionBox_Impl::addEntry( const uno::Reference< deployment::XPackage > &xPackage, + bool bLicenseMissing ) +{ + PackageState eState = TheExtensionManager::getPackageState( xPackage ); + bool bLocked = m_pManager->isReadOnly( xPackage ); + + TEntry_Impl pEntry = std::make_shared<Entry_Impl>( xPackage, eState, bLocked ); + + // Don't add empty entries + if ( pEntry->m_sTitle.isEmpty() ) + return; + + { + osl::MutexGuard guard(m_entriesMutex); + tools::Long nPos = 0; + if (m_vEntries.empty()) + { + addEventListenerOnce(xPackage); + m_vEntries.push_back(pEntry); + } + else + { + if (!FindEntryPos(pEntry, 0, m_vEntries.size() - 1, nPos)) + { + addEventListenerOnce(xPackage); + m_vEntries.insert(m_vEntries.begin() + nPos, pEntry); + } + else if (!m_bInCheckMode) + { + OSL_FAIL("ExtensionBox_Impl::addEntry(): Will not add duplicate entries"); + } + } + + pEntry->m_bHasOptions = m_pManager->supportsOptions(xPackage); + pEntry->m_bUser = (xPackage->getRepositoryName() == USER_PACKAGE_MANAGER); + pEntry->m_bShared = (xPackage->getRepositoryName() == SHARED_PACKAGE_MANAGER); + pEntry->m_bNew = m_bInCheckMode; + pEntry->m_bMissingLic = bLicenseMissing; + + if (bLicenseMissing) + pEntry->m_sErrorText = DpResId(RID_STR_ERROR_MISSING_LICENSE); + + //access to m_nActive must be guarded + if (!m_bInCheckMode && m_bHasActive && (m_nActive >= nPos)) + m_nActive += 1; + } + + if ( IsReallyVisible() ) + Invalidate(); + + m_bNeedsRecalc = true; +} + +void ExtensionBox_Impl::updateEntry( const uno::Reference< deployment::XPackage > &xPackage ) +{ + for (auto const& entry : m_vEntries) + { + if ( entry->m_xPackage == xPackage ) + { + PackageState eState = TheExtensionManager::getPackageState( xPackage ); + entry->m_bHasOptions = m_pManager->supportsOptions( xPackage ); + entry->m_eState = eState; + entry->m_sTitle = xPackage->getDisplayName(); + entry->m_sVersion = xPackage->getVersion(); + entry->m_sDescription = xPackage->getDescription(); + + if ( eState == REGISTERED ) + entry->m_bMissingLic = false; + + if ( eState == AMBIGUOUS ) + entry->m_sErrorText = DpResId( RID_STR_ERROR_UNKNOWN_STATUS ); + else if ( ! entry->m_bMissingLic ) + entry->m_sErrorText.clear(); + + if ( IsReallyVisible() ) + Invalidate(); + break; + } + } +} + +//This function is also called as a result of removing an extension. +//see PackageManagerImpl::removePackage +//The gui is a registered as listener on the package. Removing it will cause the +//listeners to be notified and then this function is called. At this moment xPackage +//is in the disposing state and all calls on it may result in a DisposedException. +void ExtensionBox_Impl::removeEntry( const uno::Reference< deployment::XPackage > &xPackage ) +{ + if ( m_bInDelete ) + return; + + bool invalidate = false; + { + ::osl::ClearableMutexGuard aGuard( m_entriesMutex ); + + auto iIndex = std::find_if(m_vEntries.begin(), m_vEntries.end(), + [&xPackage](const TEntry_Impl& rxEntry) { return rxEntry->m_xPackage == xPackage; }); + if (iIndex != m_vEntries.end()) + { + tools::Long nPos = iIndex - m_vEntries.begin(); + + // Entries mustn't be removed here, because they contain a hyperlink control + // which can only be deleted when the thread has the solar mutex. Therefore + // the entry will be moved into the m_vRemovedEntries list which will be + // cleared on the next paint event + m_vRemovedEntries.push_back( *iIndex ); + (*iIndex)->m_xPackage->removeEventListener(m_xRemoveListener); + m_vEntries.erase( iIndex ); + + m_bNeedsRecalc = true; + + if ( IsReallyVisible() ) + invalidate = true; + + if ( m_bHasActive ) + { + if ( nPos < m_nActive ) + m_nActive -= 1; + else if ( ( nPos == m_nActive ) && + ( nPos == static_cast<tools::Long>(m_vEntries.size()) ) ) + m_nActive -= 1; + + m_bHasActive = false; + //clear before calling out of this method + aGuard.clear(); + selectEntry( m_nActive ); + } + } + } + + if (invalidate) + { + SolarMutexGuard g; + Invalidate(); + } +} + + +void ExtensionBox_Impl::RemoveUnlocked() +{ + bool bAllRemoved = false; + + while ( ! bAllRemoved ) + { + bAllRemoved = true; + + ::osl::ClearableMutexGuard aGuard( m_entriesMutex ); + + for (auto const& entry : m_vEntries) + { + if ( !entry->m_bLocked ) + { + bAllRemoved = false; + uno::Reference< deployment::XPackage> xPackage = entry->m_xPackage; + aGuard.clear(); + removeEntry( xPackage ); + break; + } + } + } +} + + +void ExtensionBox_Impl::prepareChecking() +{ + m_bInCheckMode = true; + for (auto const& entry : m_vEntries) + { + entry->m_bChecked = false; + entry->m_bNew = false; + } +} + + +void ExtensionBox_Impl::checkEntries() +{ + tools::Long nNewPos = -1; + tools::Long nChangedActivePos = -1; + tools::Long nPos = 0; + bool bNeedsUpdate = false; + + { + osl::MutexGuard guard(m_entriesMutex); + auto iIndex = m_vEntries.begin(); + while (iIndex != m_vEntries.end()) + { + if (!(*iIndex)->m_bChecked) + { + (*iIndex)->m_bChecked = true; + bNeedsUpdate = true; + nPos = iIndex - m_vEntries.begin(); + if ((*iIndex)->m_bNew) + { // add entry to list and correct active pos + if (nNewPos == -1) + nNewPos = nPos; + if (nPos <= m_nActive) + m_nActive += 1; + ++iIndex; + } + else + { // remove entry from list + if (nPos < nNewPos) + { + --nNewPos; + } + if (nPos < nChangedActivePos) + { + --nChangedActivePos; + } + if (nPos < m_nActive) + m_nActive -= 1; + else if (nPos == m_nActive) + { + nChangedActivePos = nPos; + m_nActive = -1; + m_bHasActive = false; + } + m_vRemovedEntries.push_back(*iIndex); + (*iIndex)->m_xPackage->removeEventListener(m_xRemoveListener); + iIndex = m_vEntries.erase(iIndex); + } + } + else + ++iIndex; + } + } + + m_bInCheckMode = false; + + if ( nNewPos != - 1) + selectEntry( nNewPos ); + else if (nChangedActivePos != -1) { + selectEntry(nChangedActivePos); + } + + if ( bNeedsUpdate ) + { + m_bNeedsRecalc = true; + if ( IsReallyVisible() ) + Invalidate(); + } +} + +IMPL_LINK(ExtensionBox_Impl, ScrollHdl, weld::ScrolledWindow&, rScrBar, void) +{ + m_nTopIndex = rScrBar.vadjustment_get_value(); + Invalidate(); +} + +} //namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_extlistbox.hxx b/desktop/source/deployment/gui/dp_gui_extlistbox.hxx new file mode 100644 index 000000000..d706975dc --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_extlistbox.hxx @@ -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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <vcl/customweld.hxx> +#include <vcl/image.hxx> +#include <vcl/weld.hxx> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/weakref.hxx> +#include <unotools/collatorwrapper.hxx> + +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/deployment/XPackage.hpp> + +#include <memory> + +#include "dp_gui.h" + +namespace dp_gui { + +#define SMALL_ICON_SIZE 16 +#define TOP_OFFSET 5 +#define ICON_HEIGHT 42 +#define ICON_WIDTH 47 +#define ICON_OFFSET 72 +#define RIGHT_ICON_OFFSET 5 +#define SPACE_BETWEEN 3 + +class TheExtensionManager; + + +struct Entry_Impl; + +typedef std::shared_ptr< Entry_Impl > TEntry_Impl; + +struct Entry_Impl +{ + bool m_bActive :1; + bool m_bLocked :1; + bool m_bHasOptions :1; + bool m_bUser :1; + bool m_bShared :1; + bool m_bNew :1; + bool m_bChecked :1; + bool m_bMissingDeps :1; + bool m_bHasButtons :1; + bool m_bMissingLic :1; + PackageState m_eState; + OUString m_sTitle; + OUString m_sVersion; + OUString m_sDescription; + OUString m_sPublisher; + OUString m_sPublisherURL; + OUString m_sErrorText; + OUString m_sLicenseText; + Image m_aIcon; + tools::Rectangle m_aLinkRect; + + css::uno::Reference<css::deployment::XPackage> m_xPackage; + + Entry_Impl(const css::uno::Reference<css::deployment::XPackage> &xPackage, + const PackageState eState, const bool bReadOnly); + ~Entry_Impl(); + + sal_Int32 CompareTo(const CollatorWrapper *pCollator, const TEntry_Impl& rEntry) const; + void checkDependencies(); +}; + +class ExtensionBox_Impl; + + +class ExtensionRemovedListener : public ::cppu::WeakImplHelper<css::lang::XEventListener> +{ + ExtensionBox_Impl* m_pParent; + +public: + + explicit ExtensionRemovedListener( ExtensionBox_Impl *pParent ) { m_pParent = pParent; } + virtual ~ExtensionRemovedListener() override; + + + // XEventListener + virtual void SAL_CALL disposing(css::lang::EventObject const& evt) override; +}; + +class ExtensionBox_Impl : public weld::CustomWidgetController +{ + bool m_bHasScrollBar : 1; + bool m_bHasActive : 1; + bool m_bNeedsRecalc : 1; + bool m_bInCheckMode : 1; + bool m_bAdjustActive : 1; + bool m_bInDelete : 1; + //Must be guarded together with m_vEntries to ensure a valid index at all times. + //Use m_entriesMutex as guard. + tools::Long m_nActive; + tools::Long m_nTopIndex; + tools::Long m_nStdHeight; + tools::Long m_nActiveHeight; + Image m_aSharedImage; + Image m_aLockedImage; + Image m_aWarningImage; + Image m_aDefaultImage; + + rtl::Reference<ExtensionRemovedListener> m_xRemoveListener; + + TheExtensionManager *m_pManager; + //This mutex is used for synchronizing access to m_vEntries. + //Currently it is used to synchronize adding, removing entries and + //functions like getItemName, getItemDescription, etc. to prevent + //that m_vEntries is accessed at an invalid index. + //ToDo: There are many more places where m_vEntries is read and which may + //fail. For example the Paint method is probable called from the main thread + //while new entries are added / removed in a separate thread. + mutable ::osl::Mutex m_entriesMutex; + std::vector< TEntry_Impl > m_vEntries; + std::vector< TEntry_Impl > m_vRemovedEntries; + + std::unique_ptr<css::lang::Locale> m_pLocale; + std::unique_ptr<CollatorWrapper> m_pCollator; + + //Holds weak references to extensions to which is we have added an XEventListener + std::vector< css::uno::WeakReference< + css::deployment::XPackage> > m_vListenerAdded; + + std::unique_ptr<weld::ScrolledWindow> m_xScrollBar; + + //Removes the dead weak references from m_vListenerAdded + void cleanVecListenerAdded(); + void addEventListenerOnce(css::uno::Reference<css::deployment::XPackage> const & extension); + + void CalcActiveHeight( const tools::Long nPos ); + tools::Long GetTotalHeight() const; + void SetupScrollBar(); + void DrawRow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, const TEntry_Impl& rEntry); + bool HandleCursorKey( sal_uInt16 nKeyCode ); + bool FindEntryPos( const TEntry_Impl& rEntry, tools::Long nStart, tools::Long nEnd, tools::Long &nFound ); + void DeleteRemoved(); + + DECL_LINK( ScrollHdl, weld::ScrolledWindow&, void ); + + void Init(); +public: + explicit ExtensionBox_Impl(std::unique_ptr<weld::ScrolledWindow> xScroll); + virtual ~ExtensionBox_Impl() override; + + virtual bool MouseButtonDown( const MouseEvent& rMEvt ) override; + virtual bool MouseMove( const MouseEvent& rMEvt ) override; + virtual bool KeyInput(const KeyEvent& rKEvt) override; + virtual void Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle &rPaintRect ) override; + virtual void Resize() override; + virtual OUString RequestHelp(tools::Rectangle& rRect) override; + + virtual void SetDrawingArea(weld::DrawingArea* pDrawingArea) override; + + TEntry_Impl const & GetEntryData( tools::Long nPos ) { return m_vEntries[ nPos ]; } + tools::Long GetEntryCount() const { return static_cast<tools::Long>(m_vEntries.size()); } + tools::Rectangle GetEntryRect( const tools::Long nPos ) const; + bool HasActive() const { return m_bHasActive; } + tools::Long PointToPos( const Point& rPos ); + virtual void RecalcAll(); + void RemoveUnlocked(); + + + virtual void selectEntry( const tools::Long nPos ); + void addEntry(const css::uno::Reference<css::deployment::XPackage> &xPackage, + bool bLicenseMissing = false ); + void updateEntry(const css::uno::Reference<css::deployment::XPackage> &xPackage ); + void removeEntry(const css::uno::Reference<css::deployment::XPackage> &xPackage ); + + void prepareChecking(); + void checkEntries(); + + void setExtensionManager(TheExtensionManager* pManager) { m_pManager = pManager; } + + //These functions are used for automatic testing +public: + enum { ENTRY_NOTFOUND = -1 }; + + /** @return The count of the entries in the list box. */ + sal_Int32 getItemCount() const; + + /** @return The index of the first selected entry in the list box. + When nothing is selected, which is the case when getItemCount returns '0', + then this function returns ENTRY_NOTFOUND */ + /** @return The index of the first selected entry in the list box. + When nothing is selected, which is the case when getItemCount returns '0', + then this function returns ENTRY_NOTFOUND */ + sal_Int32 getSelIndex() const; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_service.cxx b/desktop/source/deployment/gui/dp_gui_service.cxx new file mode 100644 index 000000000..c359cb750 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_service.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 <memory> +#include "dp_gui_theextmgr.hxx" +#include <osl/diagnose.h> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <unotools/configmgr.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/unwrapargs.hxx> +#include <unotools/resmgr.hxx> +#include <vcl/weld.hxx> +#include <vcl/window.hxx> +#include <vcl/svapp.hxx> +#include <com/sun/star/task/XJobExecutor.hpp> +#include <com/sun/star/ui/dialogs/XAsynchronousExecutableDialog.hpp> + +#include <optional> +#include "license_dialog.hxx" +#include "dp_gui_dialog2.hxx" +#include "dp_gui_extensioncmdqueue.hxx" +#include <dp_misc.h> + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace dp_gui { + +namespace { + +class MyApp : public Application +{ +public: + MyApp(); + + MyApp(const MyApp&) = delete; + const MyApp& operator=(const MyApp&) = delete; + + // Application + virtual int Main() override; + virtual void DeInit() override; +}; + +} + +MyApp::MyApp() +{ +} + + +int MyApp::Main() +{ + return EXIT_SUCCESS; +} + +void MyApp::DeInit() +{ + css::uno::Reference< css::uno::XComponentContext > context( + comphelper::getProcessComponentContext()); + dp_misc::disposeBridges(context); + css::uno::Reference< css::lang::XComponent >( + context, css::uno::UNO_QUERY_THROW)->dispose(); + comphelper::setProcessServiceFactory(nullptr); +} + +static OUString ReplaceProductNameHookProc( const OUString& rStr ) +{ + if (rStr.indexOf( "%PRODUCT" ) == -1) + return rStr; + + static const OUString sProductName = utl::ConfigManager::getProductName(); + static const OUString sVersion = utl::ConfigManager::getProductVersion(); + static const OUString sAboutBoxVersion = utl::ConfigManager::getAboutBoxProductVersion(); + static const OUString sAboutBoxVersionSuffix = utl::ConfigManager::getAboutBoxProductVersionSuffix(); + static const OUString sExtension = utl::ConfigManager::getProductExtension(); + static const OUString sOOOVendor = utl::ConfigManager::getVendor(); + + OUString sRet = rStr.replaceAll( "%PRODUCTNAME", sProductName ); + sRet = sRet.replaceAll( "%PRODUCTVERSION", sVersion ); + sRet = sRet.replaceAll( "%ABOUTBOXPRODUCTVERSIONSUFFIX", sAboutBoxVersionSuffix ); + sRet = sRet.replaceAll( "%ABOUTBOXPRODUCTVERSION", sAboutBoxVersion ); + sRet = sRet.replaceAll( "%OOOVENDOR", sOOOVendor ); + sRet = sRet.replaceAll( "%PRODUCTEXTENSION", sExtension ); + return sRet; +} + +namespace { + +class ServiceImpl + : public ::cppu::WeakImplHelper<ui::dialogs::XAsynchronousExecutableDialog, + task::XJobExecutor, css::lang::XServiceInfo> +{ + Reference<XComponentContext> const m_xComponentContext; + std::optional< Reference<awt::XWindow> > /* const */ m_parent; + std::optional<OUString> m_extensionURL; + OUString m_initialTitle; + bool m_bShowUpdateOnly; + +public: + ServiceImpl( Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ); + + // 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; + + // XAsynchronousExecutableDialog + virtual void SAL_CALL setDialogTitle( OUString const & aTitle ) override; + virtual void SAL_CALL startExecuteModal( + Reference< ui::dialogs::XDialogClosedListener > const & xListener ) override; + + // XJobExecutor + virtual void SAL_CALL trigger( OUString const & event ) override; +}; + +} + +ServiceImpl::ServiceImpl( Sequence<Any> const& args, + Reference<XComponentContext> const& xComponentContext) + : m_xComponentContext(xComponentContext), + m_bShowUpdateOnly( false ) +{ + /* if true then this service is running in a unopkg process and not in an office process */ + std::optional<OUString> view; + try { + std::optional<sal_Bool> unopkg; + comphelper::unwrapArgs( args, m_parent, view, unopkg ); + return; + } catch ( const css::lang::IllegalArgumentException & ) { + } + try { + comphelper::unwrapArgs( args, m_extensionURL); + } catch ( const css::lang::IllegalArgumentException & ) { + } + + ResHookProc pProc = Translate::GetReadStringHook(); + if ( !pProc ) + Translate::SetReadStringHook(ReplaceProductNameHookProc); +} + +// XServiceInfo +OUString ServiceImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.ui.PackageManagerDialog"; +} + +sal_Bool ServiceImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > ServiceImpl::getSupportedServiceNames() +{ + return { "com.sun.star.deployment.ui.PackageManagerDialog" }; +} + +// XAsynchronousExecutableDialog + +void ServiceImpl::setDialogTitle( OUString const & title ) +{ + if ( dp_gui::TheExtensionManager::s_ExtMgr.is() ) + { + const SolarMutexGuard guard; + ::rtl::Reference< ::dp_gui::TheExtensionManager > dialog( + ::dp_gui::TheExtensionManager::get( m_xComponentContext, + m_parent ? *m_parent : Reference<awt::XWindow>(), + m_extensionURL ? *m_extensionURL : OUString() ) ); + dialog->SetText( title ); + } + else + m_initialTitle = title; +} + + +void ServiceImpl::startExecuteModal( + Reference< ui::dialogs::XDialogClosedListener > const & xListener ) +{ + bool bCloseDialog = true; // only used if m_bShowUpdateOnly is true + std::unique_ptr<Application> app; + //ToDo: synchronize access to s_dialog !!! + if (! dp_gui::TheExtensionManager::s_ExtMgr.is()) + { + const bool bAppUp = (GetpApp() != nullptr); + bool bOfficePipePresent; + try { + bOfficePipePresent = dp_misc::office_is_running(); + } + catch (const Exception & exc) { + if (bAppUp) { + const SolarMutexGuard guard; + vcl::Window* pWin = Application::GetActiveTopWindow(); + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin ? pWin->GetFrameWeld() : nullptr, + VclMessageType::Warning, VclButtonsType::Ok, exc.Message)); + xBox->run(); + } + throw; + } + + if (! bOfficePipePresent) { + OSL_ASSERT( ! bAppUp ); + app.reset( new MyApp ); + if (! InitVCL() ) + throw RuntimeException( "Cannot initialize VCL!", + static_cast<OWeakObject *>(this) ); + Application::SetDisplayName( + utl::ConfigManager::getProductName() + + " " + + utl::ConfigManager::getProductVersion()); + ExtensionCmdQueue::syncRepositories( m_xComponentContext ); + } + } + else + { + // When m_bShowUpdateOnly is set, we are inside the office and the user clicked + // the update notification icon in the menu bar. We must not close the extensions + // dialog after displaying the update dialog when it has been visible before + if ( m_bShowUpdateOnly ) + bCloseDialog = ! dp_gui::TheExtensionManager::s_ExtMgr->isVisible(); + } + + { + const SolarMutexGuard guard; + ::rtl::Reference< ::dp_gui::TheExtensionManager > myExtMgr( + ::dp_gui::TheExtensionManager::get( + m_xComponentContext, + m_parent ? *m_parent : Reference<awt::XWindow>(), + m_extensionURL ? *m_extensionURL : OUString() ) ); + myExtMgr->createDialog( false ); + if (!m_initialTitle.isEmpty()) { + myExtMgr->SetText( m_initialTitle ); + m_initialTitle.clear(); + } + if ( m_bShowUpdateOnly ) + { + myExtMgr->checkUpdates(); + if ( bCloseDialog ) + myExtMgr->Close(); + else + myExtMgr->ToTop(); + } + else + { + myExtMgr->Show(); + myExtMgr->ToTop(); + } + } + + if (app != nullptr) + { + Application::Execute(); + DeInitVCL(); + } + + if (xListener.is()) + xListener->dialogClosed( + ui::dialogs::DialogClosedEvent( + static_cast< ::cppu::OWeakObject * >(this), + sal_Int16(0)) ); +} + +// XJobExecutor + +void ServiceImpl::trigger( OUString const &rEvent ) +{ + if ( rEvent == "SHOW_UPDATE_DIALOG" ) + m_bShowUpdateOnly = true; + else + m_bShowUpdateOnly = false; + + startExecuteModal( Reference< ui::dialogs::XDialogClosedListener >() ); +} + +} // namespace dp_gui + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +desktop_LicenseDialog_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_gui::LicenseDialog(args, context)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +desktop_ServiceImpl_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_gui::ServiceImpl(args, context)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +desktop_UpdateRequiredDialogService_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_gui::UpdateRequiredDialogService(args, context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_theextmgr.cxx b/desktop/source/deployment/gui/dp_gui_theextmgr.cxx new file mode 100644 index 000000000..5ee3f53c6 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_theextmgr.cxx @@ -0,0 +1,537 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <utility> +#include <vcl/svapp.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionManager.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/TerminationVetoException.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <comphelper/propertysequence.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <osl/diagnose.h> +#include <tools/diagnose_ex.h> + +#include "dp_gui_dialog2.hxx" +#include "dp_gui_extensioncmdqueue.hxx" +#include "dp_gui_theextmgr.hxx" +#include <dp_misc.h> +#include <dp_update.hxx> + +constexpr OUStringLiteral USER_PACKAGE_MANAGER = u"user"; +constexpr OUStringLiteral SHARED_PACKAGE_MANAGER = u"shared"; + +using namespace ::com::sun::star; + +namespace dp_gui { + + +::rtl::Reference< TheExtensionManager > TheExtensionManager::s_ExtMgr; + + +// TheExtensionManager + + +TheExtensionManager::TheExtensionManager( uno::Reference< awt::XWindow > xParent, + const uno::Reference< uno::XComponentContext > &xContext ) : + m_xContext( xContext ), + m_xParent(std::move( xParent )), + m_bModified(false), + m_bExtMgrDialogExecuting(false) +{ + m_xExtensionManager = deployment::ExtensionManager::get( xContext ); + m_xExtensionManager->addModifyListener( this ); + + uno::Reference< lang::XMultiServiceFactory > xConfig( + configuration::theDefaultProvider::get(xContext)); + uno::Sequence<uno::Any> args(comphelper::InitAnyPropertySequence( + { + {"nodepath", uno::Any(OUString("/org.openoffice.Office.OptionsDialog/Nodes"))} + })); + m_xNameAccessNodes.set( + xConfig->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationAccess", args), + uno::UNO_QUERY_THROW); + + // get the 'get more extensions here' url + uno::Sequence<uno::Any> args2(comphelper::InitAnyPropertySequence( + { + {"nodepath", uno::Any(OUString("/org.openoffice.Office.ExtensionManager/ExtensionRepositories"))} + })); + uno::Reference< container::XNameAccess > xNameAccessRepositories; + xNameAccessRepositories.set( + xConfig->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationAccess", args2), + uno::UNO_QUERY_THROW); + try + { //throws css::container::NoSuchElementException, css::lang::WrappedTargetException + uno::Any value = xNameAccessRepositories->getByName("WebsiteLink"); + m_sGetExtensionsURL = value.get< OUString > (); + } + catch ( const uno::Exception& ) + {} + + if ( dp_misc::office_is_running() ) + { + // the registration should be done after the construction has been ended + // otherwise an exception prevents object creation, but it is registered as a listener + m_xDesktop.set( frame::Desktop::create(xContext), uno::UNO_SET_THROW ); + m_xDesktop->addTerminateListener( this ); + } +} + +TheExtensionManager::~TheExtensionManager() +{ + if (m_xUpdReqDialog) + m_xUpdReqDialog->response(RET_CANCEL); + assert(!m_xUpdReqDialog); + if (m_xExtMgrDialog) + { + if (m_bExtMgrDialogExecuting) + m_xExtMgrDialog->response(RET_CANCEL); + else + { + m_xExtMgrDialog->Close(); + m_xExtMgrDialog.reset(); + } + } + assert(!m_xExtMgrDialog); +} + +void TheExtensionManager::createDialog( const bool bCreateUpdDlg ) +{ + const SolarMutexGuard guard; + + if ( bCreateUpdDlg ) + { + if ( !m_xUpdReqDialog ) + { + m_xUpdReqDialog.reset(new UpdateRequiredDialog(Application::GetFrameWeld(m_xParent), this)); + m_xExecuteCmdQueue.reset( new ExtensionCmdQueue( m_xUpdReqDialog.get(), this, m_xContext ) ); + createPackageList(); + } + } + else if ( !m_xExtMgrDialog ) + { + m_xExtMgrDialog = std::make_shared<ExtMgrDialog>(Application::GetFrameWeld(m_xParent), this); + m_xExecuteCmdQueue.reset( new ExtensionCmdQueue( m_xExtMgrDialog.get(), this, m_xContext ) ); + m_xExtMgrDialog->setGetExtensionsURL( m_sGetExtensionsURL ); + createPackageList(); + } +} + +void TheExtensionManager::Show() +{ + const SolarMutexGuard guard; + + m_bExtMgrDialogExecuting = true; + + weld::DialogController::runAsync(m_xExtMgrDialog, [this](sal_Int32 /*nResult*/) { + m_bExtMgrDialogExecuting = false; + auto xExtMgrDialog = m_xExtMgrDialog; + m_xExtMgrDialog.reset(); + xExtMgrDialog->Close(); + }); +} + +void TheExtensionManager::SetText( const OUString &rTitle ) +{ + const SolarMutexGuard guard; + + getDialog()->set_title( rTitle ); +} + + +void TheExtensionManager::ToTop() +{ + const SolarMutexGuard guard; + + getDialog()->present(); +} + +void TheExtensionManager::Close() +{ + if (m_xExtMgrDialog) + { + if (m_bExtMgrDialogExecuting) + m_xExtMgrDialog->response(RET_CANCEL); + else + m_xExtMgrDialog->Close(); + } + else if (m_xUpdReqDialog) + m_xUpdReqDialog->response(RET_CANCEL); +} + +sal_Int16 TheExtensionManager::execute() +{ + sal_Int16 nRet = 0; + + if ( m_xUpdReqDialog ) + { + nRet = m_xUpdReqDialog->run(); + m_xUpdReqDialog.reset(); + } + + return nRet; +} + +bool TheExtensionManager::isVisible() +{ + weld::Window* pDialog = getDialog(); + return pDialog && pDialog->get_visible(); +} + +void TheExtensionManager::checkUpdates() +{ + std::vector< uno::Reference< deployment::XPackage > > vEntries; + uno::Sequence< uno::Sequence< uno::Reference< deployment::XPackage > > > xAllPackages; + + try { + xAllPackages = m_xExtensionManager->getAllExtensions( uno::Reference< task::XAbortChannel >(), + uno::Reference< ucb::XCommandEnvironment >() ); + } catch ( const deployment::DeploymentException & ) { + return; + } catch ( const ucb::CommandFailedException & ) { + return; + } catch ( const ucb::CommandAbortedException & ) { + return; + } catch ( const lang::IllegalArgumentException & e ) { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( e.Message, + e.Context, anyEx ); + } + + for ( auto const & i : std::as_const(xAllPackages) ) + { + uno::Reference< deployment::XPackage > xPackage = dp_misc::getExtensionWithHighestVersion(i); + OSL_ASSERT(xPackage.is()); + if ( xPackage.is() ) + { + vEntries.push_back( xPackage ); + } + } + + m_xExecuteCmdQueue->checkForUpdates( std::move(vEntries) ); +} + + +bool TheExtensionManager::installPackage( const OUString &rPackageURL, bool bWarnUser ) +{ + if ( rPackageURL.isEmpty() ) + return false; + + createDialog( false ); + + bool bInstall = true; + bool bInstallForAll = false; + + // DV! missing function is read only repository from extension manager + if ( !bWarnUser && ! m_xExtensionManager->isReadOnlyRepository( SHARED_PACKAGE_MANAGER ) ) + bInstall = getDialogHelper()->installForAllUsers( bInstallForAll ); + + if ( !bInstall ) + return false; + + if ( bInstallForAll ) + m_xExecuteCmdQueue->addExtension( rPackageURL, SHARED_PACKAGE_MANAGER, false ); + else + m_xExecuteCmdQueue->addExtension( rPackageURL, USER_PACKAGE_MANAGER, bWarnUser ); + + return true; +} + + +void TheExtensionManager::terminateDialog() +{ + if ( dp_misc::office_is_running() ) + return; + + const SolarMutexGuard guard; + if (m_xExtMgrDialog) + { + if (m_bExtMgrDialogExecuting) + m_xExtMgrDialog->response(RET_CANCEL); + else + { + m_xExtMgrDialog->Close(); + m_xExtMgrDialog.reset(); + } + } + assert(!m_xExtMgrDialog); + if (m_xUpdReqDialog) + m_xUpdReqDialog->response(RET_CANCEL); + assert(!m_xUpdReqDialog); + Application::Quit(); +} + + +void TheExtensionManager::createPackageList() +{ + uno::Sequence< uno::Sequence< uno::Reference< deployment::XPackage > > > xAllPackages; + + try { + xAllPackages = m_xExtensionManager->getAllExtensions( uno::Reference< task::XAbortChannel >(), + uno::Reference< ucb::XCommandEnvironment >() ); + } catch ( const deployment::DeploymentException & ) { + return; + } catch ( const ucb::CommandFailedException & ) { + return; + } catch ( const ucb::CommandAbortedException & ) { + return; + } catch ( const lang::IllegalArgumentException & e ) { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( e.Message, + e.Context, anyEx ); + } + + for ( uno::Sequence< uno::Reference< deployment::XPackage > > const & xPackageList : std::as_const(xAllPackages) ) + { + for ( uno::Reference< deployment::XPackage > const & xPackage : xPackageList ) + { + if ( xPackage.is() ) + { + PackageState eState = getPackageState( xPackage ); + getDialogHelper()->addPackageToList( xPackage ); + // When the package is enabled, we can stop here, otherwise we have to look for + // another version of this package + if ( ( eState == REGISTERED ) || ( eState == NOT_AVAILABLE ) ) + break; + } + } + } + + const uno::Sequence< uno::Reference< deployment::XPackage > > xNoLicPackages = m_xExtensionManager->getExtensionsWithUnacceptedLicenses( SHARED_PACKAGE_MANAGER, + uno::Reference< ucb::XCommandEnvironment >() ); + for ( uno::Reference< deployment::XPackage > const & xPackage : xNoLicPackages ) + { + if ( xPackage.is() ) + { + getDialogHelper()->addPackageToList( xPackage, true ); + } + } +} + + +PackageState TheExtensionManager::getPackageState( const uno::Reference< deployment::XPackage > &xPackage ) +{ + try { + beans::Optional< beans::Ambiguous< sal_Bool > > option( + xPackage->isRegistered( uno::Reference< task::XAbortChannel >(), + uno::Reference< ucb::XCommandEnvironment >() ) ); + if ( option.IsPresent ) + { + ::beans::Ambiguous< sal_Bool > const & reg = option.Value; + if ( reg.IsAmbiguous ) + return AMBIGUOUS; + else + return reg.Value ? REGISTERED : NOT_REGISTERED; + } + else + return NOT_AVAILABLE; + } + catch ( const uno::RuntimeException & ) { + throw; + } + catch (const uno::Exception &) { + TOOLS_WARN_EXCEPTION( "desktop", "" ); + return NOT_AVAILABLE; + } +} + + +bool TheExtensionManager::isReadOnly( const uno::Reference< deployment::XPackage > &xPackage ) const +{ + if ( m_xExtensionManager.is() && xPackage.is() ) + { + return m_xExtensionManager->isReadOnlyRepository( xPackage->getRepositoryName() ); + } + else + return true; +} + + +// The function investigates if the extension supports options. +bool TheExtensionManager::supportsOptions( const uno::Reference< deployment::XPackage > &xPackage ) const +{ + bool bOptions = false; + + if ( ! xPackage->isBundle() ) + return false; + + beans::Optional< OUString > aId = xPackage->getIdentifier(); + + //a bundle must always have an id + OSL_ASSERT( aId.IsPresent ); + + //iterate over all available nodes + const uno::Sequence< OUString > seqNames = m_xNameAccessNodes->getElementNames(); + + for ( OUString const & nodeName : seqNames ) + { + uno::Any anyNode = m_xNameAccessNodes->getByName( nodeName ); + //If we have a node then it must contain the set of leaves. This is part of OptionsDialog.xcs + uno::Reference< XInterface> xIntNode = anyNode.get< uno::Reference< XInterface > >(); + uno::Reference< container::XNameAccess > xNode( xIntNode, uno::UNO_QUERY_THROW ); + + uno::Any anyLeaves = xNode->getByName("Leaves"); + uno::Reference< XInterface > xIntLeaves = anyLeaves.get< uno::Reference< XInterface > >(); + uno::Reference< container::XNameAccess > xLeaves( xIntLeaves, uno::UNO_QUERY_THROW ); + + //iterate over all available leaves + const uno::Sequence< OUString > seqLeafNames = xLeaves->getElementNames(); + for ( OUString const & leafName : seqLeafNames ) + { + uno::Any anyLeaf = xLeaves->getByName( leafName ); + uno::Reference< XInterface > xIntLeaf = anyLeaf.get< uno::Reference< XInterface > >(); + uno::Reference< beans::XPropertySet > xLeaf( xIntLeaf, uno::UNO_QUERY_THROW ); + //investigate the Id property if it matches the extension identifier which + //has been passed in. + uno::Any anyValue = xLeaf->getPropertyValue("Id"); + + OUString sId = anyValue.get< OUString >(); + if ( sId == aId.Value ) + { + bOptions = true; + break; + } + } + if ( bOptions ) + break; + } + return bOptions; +} + + +// XEventListener +void TheExtensionManager::disposing( lang::EventObject const & rEvt ) +{ + bool shutDown = (rEvt.Source == m_xDesktop); + + if ( shutDown && m_xDesktop.is() ) + { + m_xDesktop->removeTerminateListener( this ); + m_xDesktop.clear(); + } + + if ( !shutDown ) + return; + + if ( dp_misc::office_is_running() ) + { + const SolarMutexGuard guard; + if (m_xExtMgrDialog) + { + if (m_bExtMgrDialogExecuting) + m_xExtMgrDialog->response(RET_CANCEL); + else + { + m_xExtMgrDialog->Close(); + m_xExtMgrDialog.reset(); + } + } + assert(!m_xExtMgrDialog); + if (m_xUpdReqDialog) + m_xUpdReqDialog->response(RET_CANCEL); + assert(!m_xUpdReqDialog); + } + s_ExtMgr.clear(); +} + +// XTerminateListener +void TheExtensionManager::queryTermination( ::lang::EventObject const & ) +{ + DialogHelper *pDialogHelper = getDialogHelper(); + + if ( m_xExecuteCmdQueue->isBusy() || ( pDialogHelper && pDialogHelper->isBusy() ) ) + { + ToTop(); + throw frame::TerminationVetoException( + "The office cannot be closed while the Extension Manager is running", + static_cast<frame::XTerminateListener*>(this)); + } + else + { + clearModified(); + if (m_xExtMgrDialog) + { + if (m_bExtMgrDialogExecuting) + m_xExtMgrDialog->response(RET_CANCEL); + else + { + m_xExtMgrDialog->Close(); + m_xExtMgrDialog.reset(); + } + } + if (m_xUpdReqDialog) + m_xUpdReqDialog->response(RET_CANCEL); + } +} + +void TheExtensionManager::notifyTermination( ::lang::EventObject const & rEvt ) +{ + disposing( rEvt ); +} + +// XModifyListener +void TheExtensionManager::modified( ::lang::EventObject const & /*rEvt*/ ) +{ + m_bModified = true; + DialogHelper *pDialogHelper = getDialogHelper(); + if (!pDialogHelper) + return; + pDialogHelper->prepareChecking(); + createPackageList(); + pDialogHelper->checkEntries(); +} + + +::rtl::Reference< TheExtensionManager > TheExtensionManager::get( const uno::Reference< uno::XComponentContext > &xContext, + const uno::Reference< awt::XWindow > &xParent, + const OUString & extensionURL ) +{ + if ( s_ExtMgr.is() ) + { + OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); + if ( !extensionURL.isEmpty() ) + s_ExtMgr->installPackage( extensionURL, true ); + return s_ExtMgr; + } + + ::rtl::Reference<TheExtensionManager> that( new TheExtensionManager( xParent, xContext ) ); + + const SolarMutexGuard guard; + if ( ! s_ExtMgr.is() ) + { + OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); + s_ExtMgr = that; + } + + if ( !extensionURL.isEmpty() ) + s_ExtMgr->installPackage( extensionURL, true ); + + return s_ExtMgr; +} + +} //namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_theextmgr.hxx b/desktop/source/deployment/gui/dp_gui_theextmgr.hxx new file mode 100644 index 000000000..13c329d6d --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_theextmgr.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 <cppuhelper/implbase.hxx> + +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/deployment/XExtensionManager.hpp> +#include <com/sun/star/frame/XDesktop2.hpp> +#include <com/sun/star/frame/XTerminateListener.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/util/XModifyListener.hpp> + +#include "dp_gui.h" +#include "dp_gui_dialog2.hxx" + + +namespace dp_gui { + + +class ExtensionCmdQueue; + + +class TheExtensionManager : + public ::cppu::WeakImplHelper< css::frame::XTerminateListener, + css::util::XModifyListener > +{ +private: + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::frame::XDesktop2 > m_xDesktop; + css::uno::Reference< css::deployment::XExtensionManager > m_xExtensionManager; + css::uno::Reference< css::container::XNameAccess > m_xNameAccessNodes; + css::uno::Reference< css::awt::XWindow > m_xParent; + std::shared_ptr<ExtMgrDialog> m_xExtMgrDialog; + std::unique_ptr<UpdateRequiredDialog> m_xUpdReqDialog; + std::unique_ptr<ExtensionCmdQueue> m_xExecuteCmdQueue; + + OUString m_sGetExtensionsURL; + bool m_bModified; + bool m_bExtMgrDialogExecuting; + +public: + static ::rtl::Reference<TheExtensionManager> s_ExtMgr; + + TheExtensionManager( css::uno::Reference< css::awt::XWindow > xParent, + const css::uno::Reference< css::uno::XComponentContext > &xContext ); + virtual ~TheExtensionManager() override; + + void createDialog( const bool bCreateUpdDlg ); + sal_Int16 execute(); + + bool isModified() const { return m_bModified; } + void clearModified() { m_bModified = false; } + + weld::Window* getDialog() + { + if (m_xExtMgrDialog) + return m_xExtMgrDialog->getDialog(); + if (m_xUpdReqDialog) + return m_xUpdReqDialog->getDialog(); + return nullptr; + } + DialogHelper* getDialogHelper() + { + if (m_xExtMgrDialog) + return m_xExtMgrDialog.get(); + return m_xUpdReqDialog.get(); + } + ExtensionCmdQueue* getCmdQueue() const { return m_xExecuteCmdQueue.get(); } + + void SetText( const OUString &rTitle ); + void Show(); + void ToTop(); + void Close(); + bool isVisible(); + + + void checkUpdates(); + bool installPackage( const OUString &rPackageURL, bool bWarnUser = false ); + void createPackageList(); + + void terminateDialog(); + + // Tools + bool supportsOptions( const css::uno::Reference< css::deployment::XPackage > &xPackage ) const; + static PackageState getPackageState( const css::uno::Reference< css::deployment::XPackage > &xPackage ); + const css::uno::Reference< css::uno::XComponentContext >& getContext() const { return m_xContext; } + const css::uno::Reference< css::deployment::XExtensionManager >& getExtensionManager() const { return m_xExtensionManager; } + bool isReadOnly( const css::uno::Reference< css::deployment::XPackage > &xPackage ) const; + + + static ::rtl::Reference<TheExtensionManager> get( + css::uno::Reference< css::uno::XComponentContext> const & xContext, + css::uno::Reference< css::awt::XWindow> const & xParent = nullptr, + OUString const & view = OUString() ); + + // XEventListener + virtual void SAL_CALL disposing( css::lang::EventObject const & evt ) override; + + // XTerminateListener + virtual void SAL_CALL queryTermination( css::lang::EventObject const & evt ) override; + virtual void SAL_CALL notifyTermination( css::lang::EventObject const & evt ) override; + + // XModifyListener + virtual void SAL_CALL modified( css::lang::EventObject const & evt ) override; +}; + +} // namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_updatedata.hxx b/desktop/source/deployment/gui/dp_gui_updatedata.hxx new file mode 100644 index 000000000..efac4c587 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_updatedata.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 <sal/config.h> +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <utility> + +namespace com::sun::star::deployment { + class XPackage; +} +namespace com::sun::star::xml::dom { + class XNode; +} + + +namespace dp_gui { + +struct UpdateData +{ + explicit UpdateData( css::uno::Reference< css::deployment::XPackage > xExt): + bIsShared(false), aInstalledPackage(std::move(xExt)) {}; + + //When entries added to the listbox then there can be one for the user update and one + //for the shared update. However, both list entries will contain the same UpdateData. + //isShared is used to indicate which one is used for the shared entry. + bool bIsShared; + + //The currently installed extension which is going to be updated. If the extension exist in + //multiple repositories then it is the one with the highest version. + css::uno::Reference< css::deployment::XPackage > aInstalledPackage; + + //The version of the update + OUString updateVersion; + + //For online update + + // The content of the update information. + //Only if aUpdateInfo is set then there is an online update available with a better version + //than any of the currently installed extensions with the same identifier. + css::uno::Reference< css::xml::dom::XNode > aUpdateInfo; + //The URL of the locally downloaded extension. It will only be set if there were no errors + //during the download + OUString sLocalURL; + //The URL of the website where the download can be obtained. + OUString sWebsiteURL; + + //For local update + + //The locale extension which is used as update for the user or shared repository. + //If set then the data for the online update (aUpdateInfo, sLocalURL, sWebsiteURL) + //are to be ignored. + css::uno::Reference< css::deployment::XPackage > aUpdateSource; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_updatedialog.cxx b/desktop/source/deployment/gui/dp_gui_updatedialog.cxx new file mode 100644 index 000000000..2861d7785 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_updatedialog.cxx @@ -0,0 +1,994 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <utility> +#include <vector> + + +#include <optional> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/deployment/UpdateInformationProvider.hpp> +#include <com/sun/star/deployment/ExtensionManager.hpp> +#include <com/sun/star/deployment/XUpdateInformationProvider.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/util/URL.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/xml/dom/XElement.hpp> +#include <osl/diagnose.h> +#include <rtl/ref.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <salhelper/thread.hxx> +#include <tools/gen.hxx> +#include <tools/link.hxx> +#include <unotools/configmgr.hxx> +#include <vcl/svapp.hxx> + +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> + +#include <dp_dependencies.hxx> +#include <dp_descriptioninfoset.hxx> +#include <dp_identifier.hxx> +#include <dp_misc.h> +#include <dp_update.hxx> + +#include <strings.hrc> +#include "dp_gui_updatedata.hxx" +#include "dp_gui_updatedialog.hxx" +#include <dp_shared.hxx> + +class KeyEvent; +class MouseEvent; +namespace com::sun::star::uno { + class XComponentContext; +} + +using namespace ::com::sun::star; +using dp_gui::UpdateDialog; + +namespace { + +sal_Unicode const LF = 0x000A; +sal_Unicode const CR = 0x000D; + +constexpr OUStringLiteral IGNORED_UPDATES = u"/org.openoffice.Office.ExtensionManager/ExtensionUpdateData/IgnoredUpdates"; +constexpr OUStringLiteral PROPERTY_VERSION = u"Version"; + +enum Kind { ENABLED_UPDATE, DISABLED_UPDATE, SPECIFIC_ERROR }; + +OUString confineToParagraph(OUString const & text) { + // Confine arbitrary text to a single paragraph in a VclMultiLineEdit + // This assumes that U+000A and U+000D are the only paragraph separators in + // a VclMultiLineEdit, and that replacing them with a single space + // each is acceptable: + return text.replace(LF, ' ').replace(CR, ' '); +} +} + +struct UpdateDialog::DisabledUpdate { + OUString name; + uno::Sequence< OUString > unsatisfiedDependencies; + // We also want to show release notes and publisher for disabled updates + css::uno::Reference< css::xml::dom::XNode > aUpdateInfo; +}; + +struct UpdateDialog::SpecificError { + OUString name; + OUString message; +}; + + +struct UpdateDialog::IgnoredUpdate { + OUString sExtensionID; + OUString sVersion; + + IgnoredUpdate( OUString aExtensionID, OUString aVersion ); +}; + + +UpdateDialog::IgnoredUpdate::IgnoredUpdate( OUString aExtensionID, OUString aVersion ): + sExtensionID(std::move( aExtensionID )), + sVersion(std::move( aVersion )) +{} + + +struct UpdateDialog::Index +{ + Kind m_eKind; + bool m_bIgnored; + sal_uInt16 m_nIndex; + OUString m_aName; + + Index( Kind theKind, sal_uInt16 nIndex, OUString aName ) : + m_eKind( theKind ), + m_bIgnored( false ), + m_nIndex( nIndex ), + m_aName(std::move( aName )) {} +}; + + +class UpdateDialog::Thread: public salhelper::Thread { +public: + Thread( + uno::Reference< uno::XComponentContext > const & context, + UpdateDialog & dialog, + std::vector< uno::Reference< deployment::XPackage > > && vExtensionList); + + void stop(); + +private: + virtual ~Thread() override; + + virtual void execute() override; + + void handleSpecificError( + uno::Reference< deployment::XPackage > const & package, + uno::Any const & exception) const; + + OUString getUpdateDisplayString( + dp_gui::UpdateData const & data, std::u16string_view version = std::u16string_view()) const; + + void prepareUpdateData( + css::uno::Reference< css::xml::dom::XNode > const & updateInfo, + UpdateDialog::DisabledUpdate & out_du, + dp_gui::UpdateData & out_data) const; + + bool update( + UpdateDialog::DisabledUpdate const & du, + dp_gui::UpdateData const & data) const; + + uno::Reference< uno::XComponentContext > m_context; + UpdateDialog & m_dialog; + std::vector< uno::Reference< deployment::XPackage > > m_vExtensionList; + uno::Reference< deployment::XUpdateInformationProvider > m_updateInformation; + uno::Reference< task::XInteractionHandler > m_xInteractionHdl; + + // guarded by Application::GetSolarMutex(): + bool m_stop; +}; + +UpdateDialog::Thread::Thread( + uno::Reference< uno::XComponentContext > const & context, + UpdateDialog & dialog, + std::vector< uno::Reference< deployment::XPackage > >&& vExtensionList): + salhelper::Thread("dp_gui_updatedialog"), + m_context(context), + m_dialog(dialog), + m_vExtensionList(std::move(vExtensionList)), + m_updateInformation( + deployment::UpdateInformationProvider::create(context)), + m_stop(false) +{ + if( m_context.is() ) + { + m_xInteractionHdl = + task::InteractionHandler::createWithParent(m_context, dialog.getDialog()->GetXWindow()); + m_updateInformation->setInteractionHandler( m_xInteractionHdl ); + } +} + +void UpdateDialog::Thread::stop() { + { + SolarMutexGuard g; + m_stop = true; + } + m_updateInformation->cancel(); +} + +UpdateDialog::Thread::~Thread() +{ + if ( m_xInteractionHdl.is() ) + m_updateInformation->setInteractionHandler( uno::Reference< task::XInteractionHandler > () ); +} + +void UpdateDialog::Thread::execute() +{ + { + SolarMutexGuard g; + if ( m_stop ) { + return; + } + } + uno::Reference<deployment::XExtensionManager> extMgr = + deployment::ExtensionManager::get(m_context); + + std::vector<std::pair<uno::Reference<deployment::XPackage>, uno::Any > > errors; + + dp_misc::UpdateInfoMap updateInfoMap = dp_misc::getOnlineUpdateInfos( + m_context, extMgr, m_updateInformation, &m_vExtensionList, errors); + + for (auto const& elem : errors) + handleSpecificError(elem.first, elem.second); + + for (auto const& updateInfo : updateInfoMap) + { + dp_misc::UpdateInfo const & info = updateInfo.second; + UpdateData updateData(info.extension); + DisabledUpdate disableUpdate; + //determine if online updates meet the requirements + prepareUpdateData(info.info, disableUpdate, updateData); + + //determine if the update is installed in the user or shared repository + OUString sOnlineVersion; + if (info.info.is()) + sOnlineVersion = info.version; + OUString sVersionUser; + OUString sVersionShared; + OUString sVersionBundled; + uno::Sequence< uno::Reference< deployment::XPackage> > extensions; + try { + extensions = extMgr->getExtensionsWithSameIdentifier( + dp_misc::getIdentifier(info.extension), info.extension->getName(), + uno::Reference<ucb::XCommandEnvironment>()); + } catch ( const lang::IllegalArgumentException& ) { + OSL_ASSERT(false); + continue; + } catch ( const css::ucb::CommandFailedException& ) { + OSL_ASSERT(false); + continue; + } + OSL_ASSERT(extensions.getLength() == 3); + if (extensions[0].is() ) + sVersionUser = extensions[0]->getVersion(); + if (extensions[1].is() ) + sVersionShared = extensions[1]->getVersion(); + if (extensions[2].is() ) + sVersionBundled = extensions[2]->getVersion(); + + bool bSharedReadOnly = extMgr->isReadOnlyRepository("shared"); + + dp_misc::UPDATE_SOURCE sourceUser = dp_misc::isUpdateUserExtension( + bSharedReadOnly, sVersionUser, sVersionShared, sVersionBundled, sOnlineVersion); + dp_misc::UPDATE_SOURCE sourceShared = dp_misc::isUpdateSharedExtension( + bSharedReadOnly, sVersionShared, sVersionBundled, sOnlineVersion); + + if (sourceUser != dp_misc::UPDATE_SOURCE_NONE) + { + if (sourceUser == dp_misc::UPDATE_SOURCE_SHARED) + { + updateData.aUpdateSource = extensions[1]; + updateData.updateVersion = extensions[1]->getVersion(); + } + else if (sourceUser == dp_misc::UPDATE_SOURCE_BUNDLED) + { + updateData.aUpdateSource = extensions[2]; + updateData.updateVersion = extensions[2]->getVersion(); + } + if (!update(disableUpdate, updateData)) + return; + } + + if (sourceShared != dp_misc::UPDATE_SOURCE_NONE) + { + if (sourceShared == dp_misc::UPDATE_SOURCE_BUNDLED) + { + updateData.aUpdateSource = extensions[2]; + updateData.updateVersion = extensions[2]->getVersion(); + } + updateData.bIsShared = true; + if (!update(disableUpdate, updateData)) + return; + } + } + + + SolarMutexGuard g; + if (!m_stop) { + m_dialog.checkingDone(); + } +} + +//Parameter package can be null +void UpdateDialog::Thread::handleSpecificError( + uno::Reference< deployment::XPackage > const & package, + uno::Any const & exception) const +{ + UpdateDialog::SpecificError data; + if (package.is()) + data.name = package->getDisplayName(); + uno::Exception e; + if (exception >>= e) { + data.message = e.Message; + } + SolarMutexGuard g; + if (!m_stop) { + m_dialog.addSpecificError(data); + } +} + +OUString UpdateDialog::Thread::getUpdateDisplayString( + dp_gui::UpdateData const & data, std::u16string_view version) const +{ + OSL_ASSERT(data.aInstalledPackage.is()); + OUStringBuffer b(data.aInstalledPackage->getDisplayName()); + b.append(' '); + { + SolarMutexGuard g; + if(!m_stop) + b.append(m_dialog.m_version); + } + b.append(' '); + if (!version.empty()) + b.append(version); + else + b.append(data.updateVersion); + + if (!data.sWebsiteURL.isEmpty()) + { + b.append(' '); + { + SolarMutexGuard g; + if(!m_stop) + b.append(m_dialog.m_browserbased); + } + } + return b.makeStringAndClear(); +} + +/** out_data will only be filled if all dependencies are ok. + */ +void UpdateDialog::Thread::prepareUpdateData( + uno::Reference< xml::dom::XNode > const & updateInfo, + UpdateDialog::DisabledUpdate & out_du, + dp_gui::UpdateData & out_data) const +{ + if (!updateInfo.is()) + return; + dp_misc::DescriptionInfoset infoset(m_context, updateInfo); + OSL_ASSERT(!infoset.getVersion().isEmpty()); + uno::Sequence< uno::Reference< xml::dom::XElement > > ds( + dp_misc::Dependencies::check(infoset)); + + out_du.aUpdateInfo = updateInfo; + out_du.unsatisfiedDependencies.realloc(ds.getLength()); + auto p_unsatisfiedDependencies = out_du.unsatisfiedDependencies.getArray(); + for (sal_Int32 i = 0; i < ds.getLength(); ++i) { + p_unsatisfiedDependencies[i] = dp_misc::Dependencies::getErrorText(ds[i]); + } + + const ::std::optional< OUString> updateWebsiteURL(infoset.getLocalizedUpdateWebsiteURL()); + + out_du.name = getUpdateDisplayString(out_data, infoset.getVersion()); + + if (!out_du.unsatisfiedDependencies.hasElements()) + { + out_data.aUpdateInfo = updateInfo; + out_data.updateVersion = infoset.getVersion(); + if (updateWebsiteURL) + out_data.sWebsiteURL = *updateWebsiteURL; + } +} + +bool UpdateDialog::Thread::update( + UpdateDialog::DisabledUpdate const & du, + dp_gui::UpdateData const & data) const +{ + bool ret = false; + if (!du.unsatisfiedDependencies.hasElements()) + { + SolarMutexGuard g; + if (!m_stop) { + m_dialog.addEnabledUpdate(getUpdateDisplayString(data), data); + } + ret = !m_stop; + } else { + SolarMutexGuard g; + if (!m_stop) { + m_dialog.addDisabledUpdate(du); + } + ret = !m_stop; + } + return ret; +} + +// UpdateDialog ---------------------------------------------------------- +UpdateDialog::UpdateDialog( + uno::Reference< uno::XComponentContext > const & context, + weld::Window * parent, std::vector<uno::Reference< deployment::XPackage > > && vExtensionList, + std::vector< dp_gui::UpdateData > * updateData) + : GenericDialogController(parent, "desktop/ui/updatedialog.ui", "UpdateDialog") + , m_context(context) + , m_none(DpResId(RID_DLG_UPDATE_NONE)) + , m_noInstallable(DpResId(RID_DLG_UPDATE_NOINSTALLABLE)) + , m_failure(DpResId(RID_DLG_UPDATE_FAILURE)) + , m_unknownError(DpResId(RID_DLG_UPDATE_UNKNOWNERROR)) + , m_noDescription(DpResId(RID_DLG_UPDATE_NODESCRIPTION)) + , m_noInstall(DpResId(RID_DLG_UPDATE_NOINSTALL)) + , m_noDependency(DpResId(RID_DLG_UPDATE_NODEPENDENCY)) + , m_noDependencyCurVer(DpResId(RID_DLG_UPDATE_NODEPENDENCY_CUR_VER)) + , m_browserbased(DpResId(RID_DLG_UPDATE_BROWSERBASED)) + , m_version(DpResId(RID_DLG_UPDATE_VERSION)) + , m_ignoredUpdate(DpResId(RID_DLG_UPDATE_IGNORED_UPDATE)) + , m_updateData(*updateData) + , m_thread(new UpdateDialog::Thread(context, *this, std::move(vExtensionList))) + , m_xChecking(m_xBuilder->weld_label("UPDATE_CHECKING")) + , m_xThrobber(m_xBuilder->weld_spinner("THROBBER")) + , m_xUpdate(m_xBuilder->weld_label("UPDATE_LABEL")) + , m_xUpdates(m_xBuilder->weld_tree_view("checklist")) + , m_xAll(m_xBuilder->weld_check_button("UPDATE_ALL")) + , m_xDescription(m_xBuilder->weld_label("DESCRIPTION_LABEL")) + , m_xPublisherLabel(m_xBuilder->weld_label("PUBLISHER_LABEL")) + , m_xPublisherLink(m_xBuilder->weld_link_button("PUBLISHER_LINK")) + , m_xReleaseNotesLabel(m_xBuilder->weld_label("RELEASE_NOTES_LABEL")) + , m_xReleaseNotesLink(m_xBuilder->weld_link_button("RELEASE_NOTES_LINK")) + , m_xDescriptions(m_xBuilder->weld_text_view("DESCRIPTIONS")) + , m_xOk(m_xBuilder->weld_button("ok")) + , m_xClose(m_xBuilder->weld_button("close")) + , m_xHelp(m_xBuilder->weld_button("help")) +{ + auto nWidth = m_xDescriptions->get_approximate_digit_width() * 62; + auto nHeight = m_xDescriptions->get_height_rows(8); + m_xDescriptions->set_size_request(nWidth, nHeight); + m_xUpdates->set_size_request(nWidth, nHeight); + + m_xUpdates->enable_toggle_buttons(weld::ColumnToggleType::Check); + + OSL_ASSERT(updateData != nullptr); + + m_xExtensionManager = deployment::ExtensionManager::get( context ); + + m_xUpdates->connect_changed(LINK(this, UpdateDialog, selectionHandler)); + m_xUpdates->connect_toggled(LINK(this, UpdateDialog, entryToggled)); + m_xAll->connect_toggled(LINK(this, UpdateDialog, allHandler)); + m_xOk->connect_clicked(LINK(this, UpdateDialog, okHandler)); + m_xClose->connect_clicked(LINK(this, UpdateDialog, closeHandler)); + if (!dp_misc::office_is_running()) + m_xHelp->set_sensitive(false); + + initDescription(); + getIgnoredUpdates(); +} + +UpdateDialog::~UpdateDialog() +{ +} + +short UpdateDialog::run() { + m_xThrobber->start(); + m_thread->launch(); + short nRet = GenericDialogController::run(); + m_thread->stop(); + return nRet; +} + +IMPL_LINK(UpdateDialog, entryToggled, const weld::TreeView::iter_col&, rRowCol, void) +{ + // error's can't be enabled + const UpdateDialog::Index* p = weld::fromId<UpdateDialog::Index const *>(m_xUpdates->get_id(rRowCol.first)); + if (p->m_eKind == SPECIFIC_ERROR) + m_xUpdates->set_toggle(rRowCol.first, TRISTATE_FALSE); + + enableOk(); +} + +void UpdateDialog::insertItem(const UpdateDialog::Index *pEntry, bool bEnabledCheckBox) +{ + int nEntry = m_xUpdates->n_children(); + m_xUpdates->append(); + m_xUpdates->set_toggle(nEntry, bEnabledCheckBox ? TRISTATE_TRUE : TRISTATE_FALSE); + m_xUpdates->set_text(nEntry, pEntry->m_aName, 0); + m_xUpdates->set_id(nEntry, weld::toId(pEntry)); +} + +void UpdateDialog::addAdditional(const UpdateDialog::Index * index, bool bEnabledCheckBox) +{ + m_xAll->set_sensitive(true); + if (m_xAll->get_active()) + { + insertItem(index, bEnabledCheckBox); + m_xUpdate->set_sensitive(true); + m_xUpdates->set_sensitive(true); + m_xDescription->set_sensitive(true); + m_xDescriptions->set_sensitive(true); + } +} + +void UpdateDialog::addEnabledUpdate( OUString const & name, + dp_gui::UpdateData const & data ) +{ + sal_uInt16 nIndex = sal::static_int_cast< sal_uInt16 >( m_enabledUpdates.size() ); + UpdateDialog::Index *pEntry = new UpdateDialog::Index( ENABLED_UPDATE, nIndex, name ); + + m_enabledUpdates.push_back( data ); + m_ListboxEntries.emplace_back( pEntry ); + + if (!isIgnoredUpdate(pEntry)) + { + insertItem(pEntry, true); + } + else + addAdditional(pEntry, false); + + m_xUpdate->set_sensitive(true); + m_xUpdates->set_sensitive(true); + m_xDescription->set_sensitive(true); + m_xDescriptions->set_sensitive(true); +} + +void UpdateDialog::addDisabledUpdate( UpdateDialog::DisabledUpdate const & data ) +{ + sal_uInt16 nIndex = sal::static_int_cast< sal_uInt16 >( m_disabledUpdates.size() ); + UpdateDialog::Index *pEntry = new UpdateDialog::Index( DISABLED_UPDATE, nIndex, data.name ); + + m_disabledUpdates.push_back( data ); + m_ListboxEntries.emplace_back( pEntry ); + + isIgnoredUpdate( pEntry ); + addAdditional(pEntry, false); +} + +void UpdateDialog::addSpecificError( UpdateDialog::SpecificError const & data ) +{ + sal_uInt16 nIndex = sal::static_int_cast< sal_uInt16 >( m_specificErrors.size() ); + UpdateDialog::Index *pEntry = new UpdateDialog::Index( SPECIFIC_ERROR, nIndex, data.name ); + + m_specificErrors.push_back( data ); + m_ListboxEntries.emplace_back( pEntry ); + + addAdditional(pEntry, false); +} + +void UpdateDialog::checkingDone() { + m_xChecking->hide(); + m_xThrobber->stop(); + m_xThrobber->hide(); + if (m_xUpdates->n_children() == 0) + { + clearDescription(); + m_xDescription->set_sensitive(true); + m_xDescriptions->set_sensitive(true); + + if ( m_disabledUpdates.empty() && m_specificErrors.empty() && m_ignoredUpdates.empty() ) + showDescription( m_none ); + else + showDescription( m_noInstallable ); + } + + enableOk(); +} + +void UpdateDialog::enableOk() { + if (!m_xChecking->get_visible()) { + int nChecked = 0; + for (int i = 0, nCount = m_xUpdates->n_children(); i < nCount; ++i) { + if (m_xUpdates->get_toggle(i) == TRISTATE_TRUE) + ++nChecked; + } + m_xOk->set_sensitive(nChecked != 0); + } +} + +// ********************************************************************************* +void UpdateDialog::createNotifyJob( bool bPrepareOnly, + uno::Sequence< uno::Sequence< OUString > > const &rItemList ) +{ + if ( !dp_misc::office_is_running() ) + return; + + // notify update check job + try + { + uno::Reference< lang::XMultiServiceFactory > xConfigProvider( + configuration::theDefaultProvider::get( + comphelper::getProcessComponentContext())); + + uno::Sequence< uno::Any > aArgumentList{ uno::Any(comphelper::makePropertyValue( + "nodepath", + OUString("org.openoffice.Office.Addons/AddonUI/OfficeHelp/UpdateCheckJob"))) }; + + uno::Reference< container::XNameAccess > xNameAccess( + xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", aArgumentList ), + uno::UNO_QUERY_THROW ); + + util::URL aURL; + xNameAccess->getByName("URL") >>= aURL.Complete; + + uno::Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + uno::Reference < util::XURLTransformer > xTransformer = util::URLTransformer::create(xContext); + + xTransformer->parseStrict(aURL); + + uno::Reference < frame::XDesktop2 > xDesktop = frame::Desktop::create( xContext ); + uno::Reference< frame::XDispatchProvider > xDispatchProvider( xDesktop->getCurrentFrame(), + uno::UNO_QUERY_THROW ); + uno::Reference< frame::XDispatch > xDispatch = xDispatchProvider->queryDispatch(aURL, OUString(), 0); + + if( xDispatch.is() ) + { + uno::Sequence aPropList{ comphelper::makePropertyValue("updateList", rItemList), + comphelper::makePropertyValue("prepareOnly", bPrepareOnly) }; + + xDispatch->dispatch(aURL, aPropList ); + } + } + catch( const uno::Exception& e ) + { + dp_misc::TRACE( "Caught exception: " + + e.Message + "\n thread terminated.\n\n"); + } +} + +// ********************************************************************************* +void UpdateDialog::notifyMenubar( bool bPrepareOnly, bool bRecheckOnly ) +{ + if ( !dp_misc::office_is_running() ) + return; + + uno::Sequence< uno::Sequence< OUString > > aItemList; + + if ( ! bRecheckOnly ) + { + sal_Int32 nCount = 0; + for (sal_uInt16 i = 0, nItemCount = m_xUpdates->n_children(); i < nItemCount; ++i) + { + + UpdateDialog::Index const * p = weld::fromId<UpdateDialog::Index const*>(m_xUpdates->get_id(i)); + + if ( p->m_eKind == ENABLED_UPDATE ) + { + dp_gui::UpdateData aUpdData = m_enabledUpdates[ p->m_nIndex ]; + + dp_misc::DescriptionInfoset aInfoset( m_context, aUpdData.aUpdateInfo ); + uno::Sequence< OUString > aItem + { + dp_misc::getIdentifier( aUpdData.aInstalledPackage ), + aInfoset.getVersion() + }; + aItemList.realloc( nCount + 1 ); + aItemList.getArray()[ nCount ] = aItem; + nCount += 1; + } + else + continue; + } + } + + createNotifyJob( bPrepareOnly, aItemList ); +} + +// ********************************************************************************* + +void UpdateDialog::initDescription() +{ + m_xPublisherLabel->hide(); + m_xPublisherLink->hide(); + m_xReleaseNotesLabel->hide(); + m_xReleaseNotesLink->hide(); +} + +void UpdateDialog::clearDescription() +{ + m_xPublisherLabel->hide(); + m_xPublisherLink->hide(); + m_xPublisherLink->set_label(""); + m_xPublisherLink->set_uri(""); + m_xReleaseNotesLabel->hide(); + m_xReleaseNotesLink->hide(); + m_xReleaseNotesLink->set_uri( "" ); + m_xDescriptions->set_text(""); +} + +bool UpdateDialog::showDescription(uno::Reference< xml::dom::XNode > const & aUpdateInfo) +{ + dp_misc::DescriptionInfoset infoset(m_context, aUpdateInfo); + return showDescription(infoset.getLocalizedPublisherNameAndURL(), + infoset.getLocalizedReleaseNotesURL()); +} + +bool UpdateDialog::showDescription(uno::Reference< deployment::XPackage > const & aExtension) +{ + OSL_ASSERT(aExtension.is()); + beans::StringPair pubInfo = aExtension->getPublisherInfo(); + return showDescription(std::make_pair(pubInfo.First, pubInfo.Second), + ""); +} + +bool UpdateDialog::showDescription(std::pair< OUString, OUString > const & pairPublisher, + OUString const & sReleaseNotes) +{ + OUString sPub = pairPublisher.first; + OUString sURL = pairPublisher.second; + + if ( sPub.isEmpty() && sURL.isEmpty() && sReleaseNotes.isEmpty() ) + // nothing to show + return false; + + if ( !sPub.isEmpty() ) + { + m_xPublisherLabel->show(); + m_xPublisherLink->show(); + m_xPublisherLink->set_label(sPub); + m_xPublisherLink->set_uri(sURL); + } + + if ( !sReleaseNotes.isEmpty() ) + { + m_xReleaseNotesLabel->show(); + m_xReleaseNotesLink->show(); + m_xReleaseNotesLink->set_uri( sReleaseNotes ); + } + return true; +} + +bool UpdateDialog::showDescription( const OUString& rDescription) +{ + if ( rDescription.isEmpty() ) + // nothing to show + return false; + + m_xDescriptions->set_text(rDescription); + return true; +} + +void UpdateDialog::getIgnoredUpdates() +{ + uno::Reference< lang::XMultiServiceFactory > xConfig( + configuration::theDefaultProvider::get(m_context)); + beans::NamedValue aValue( "nodepath", uno::Any( OUString(IGNORED_UPDATES) ) ); + uno::Sequence< uno::Any > args{ uno::Any(aValue) }; + + uno::Reference< container::XNameAccess > xNameAccess( xConfig->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationAccess", args), uno::UNO_QUERY_THROW ); + const uno::Sequence< OUString > aElementNames = xNameAccess->getElementNames(); + + for ( OUString const & aIdentifier : aElementNames ) + { + OUString aVersion; + + uno::Any aPropValue( uno::Reference< beans::XPropertySet >( xNameAccess->getByName( aIdentifier ), uno::UNO_QUERY_THROW )->getPropertyValue( PROPERTY_VERSION ) ); + aPropValue >>= aVersion; + IgnoredUpdate *pData = new IgnoredUpdate( aIdentifier, aVersion ); + m_ignoredUpdates.emplace_back( pData ); + } +} + + +bool UpdateDialog::isIgnoredUpdate( UpdateDialog::Index * index ) +{ + bool bIsIgnored = false; + + if (! m_ignoredUpdates.empty() ) + { + OUString aExtensionID; + OUString aVersion; + + if ( index->m_eKind == ENABLED_UPDATE ) + { + dp_gui::UpdateData aUpdData = m_enabledUpdates[ index->m_nIndex ]; + aExtensionID = dp_misc::getIdentifier( aUpdData.aInstalledPackage ); + aVersion = aUpdData.updateVersion; + } + else if ( index->m_eKind == DISABLED_UPDATE ) + { + DisabledUpdate &rData = m_disabledUpdates[ index->m_nIndex ]; + dp_misc::DescriptionInfoset aInfoset( m_context, rData.aUpdateInfo ); + ::std::optional< OUString > aID( aInfoset.getIdentifier() ); + if ( aID ) + aExtensionID = *aID; + aVersion = aInfoset.getVersion(); + } + + for (auto const& ignoredUpdate : m_ignoredUpdates) + { + if ( ignoredUpdate->sExtensionID == aExtensionID ) + { + if ( ( !ignoredUpdate->sVersion.isEmpty() ) || ( ignoredUpdate->sVersion == aVersion ) ) + { + bIsIgnored = true; + index->m_bIgnored = true; + } + break; + } + } + } + + return bIsIgnored; +} + + +IMPL_LINK_NOARG(UpdateDialog, selectionHandler, weld::TreeView&, void) +{ + OUStringBuffer b; + int nSelectedPos = m_xUpdates->get_selected_index(); + clearDescription(); + + const UpdateDialog::Index* p = nullptr; + if (nSelectedPos != -1) + p = weld::fromId<UpdateDialog::Index const*>(m_xUpdates->get_id(nSelectedPos)); + if (p != nullptr) + { + sal_uInt16 pos = p->m_nIndex; + + switch (p->m_eKind) + { + case ENABLED_UPDATE: + { + if ( m_enabledUpdates[ pos ].aUpdateSource.is() ) + showDescription( m_enabledUpdates[ pos ].aUpdateSource ); + else + showDescription( m_enabledUpdates[ pos ].aUpdateInfo ); + + if ( p->m_bIgnored ) + b.append( m_ignoredUpdate ); + + break; + } + case DISABLED_UPDATE: + { + if ( !m_disabledUpdates.empty() ) + showDescription( m_disabledUpdates[pos].aUpdateInfo ); + + if ( p->m_bIgnored ) + b.append( m_ignoredUpdate ); + + if ( m_disabledUpdates.empty() ) + break; + + UpdateDialog::DisabledUpdate & data = m_disabledUpdates[ pos ]; + if (data.unsatisfiedDependencies.hasElements()) + { + // create error string for version mismatch + OUString sVersion( "%VERSION" ); + OUString sProductName( "%PRODUCTNAME" ); + sal_Int32 nPos = m_noDependencyCurVer.indexOf( sVersion ); + if ( nPos >= 0 ) + { + m_noDependencyCurVer = m_noDependencyCurVer.replaceAt( nPos, sVersion.getLength(), utl::ConfigManager::getAboutBoxProductVersion() ); + } + nPos = m_noDependencyCurVer.indexOf( sProductName ); + if ( nPos >= 0 ) + { + m_noDependencyCurVer = m_noDependencyCurVer.replaceAt( nPos, sProductName.getLength(), utl::ConfigManager::getProductName() ); + } + nPos = m_noDependency.indexOf( sProductName ); + if ( nPos >= 0 ) + { + m_noDependency = m_noDependency.replaceAt( nPos, sProductName.getLength(), utl::ConfigManager::getProductName() ); + } + + b.append(m_noInstall); + b.append(LF); + b.append(m_noDependency); + for (sal_Int32 i = 0; + i < data.unsatisfiedDependencies.getLength(); ++i) + { + b.append(LF); + b.append(" "); + // U+2003 EM SPACE would be better than two spaces, + // but some fonts do not contain it + b.append( + confineToParagraph( + data.unsatisfiedDependencies[i])); + } + b.append(LF); + b.append(" "); + b.append(m_noDependencyCurVer); + } + break; + } + case SPECIFIC_ERROR: + { + UpdateDialog::SpecificError & data = m_specificErrors[ pos ]; + b.append(m_failure); + b.append(LF); + b.append( data.message.isEmpty() ? m_unknownError : data.message ); + break; + } + default: + OSL_ASSERT(false); + break; + } + } + + if ( b.isEmpty() ) + b.append( m_noDescription ); + + showDescription( b.makeStringAndClear() ); +} + +IMPL_LINK_NOARG(UpdateDialog, allHandler, weld::Toggleable&, void) +{ + if (m_xAll->get_active()) + { + m_xUpdate->set_sensitive(true); + m_xUpdates->set_sensitive(true); + m_xDescription->set_sensitive(true); + m_xDescriptions->set_sensitive(true); + + for (auto const& listboxEntry : m_ListboxEntries) + { + if ( listboxEntry->m_bIgnored || ( listboxEntry->m_eKind != ENABLED_UPDATE ) ) + insertItem(listboxEntry.get(), false); + } + } + else + { + for (sal_uInt16 i = m_xUpdates->n_children(); i != 0 ;) + { + i -= 1; + UpdateDialog::Index const * p = weld::fromId<UpdateDialog::Index const*>(m_xUpdates->get_id(i)); + if ( p->m_bIgnored || ( p->m_eKind != ENABLED_UPDATE ) ) + { + m_xUpdates->remove(i); + } + } + + if (m_xUpdates->n_children() == 0) + { + clearDescription(); + m_xUpdate->set_sensitive(false); + m_xUpdates->set_sensitive(false); + if (m_xChecking->get_visible()) + m_xDescription->set_sensitive(false); + else + showDescription(m_noInstallable); + } + } +} + +IMPL_LINK_NOARG(UpdateDialog, okHandler, weld::Button&, void) +{ + //If users are going to update a shared extension then we need + //to warn them + for (auto const& enableUpdate : m_enabledUpdates) + { + OSL_ASSERT(enableUpdate.aInstalledPackage.is()); + //If the user has no write access to the shared folder then the update + //for a shared extension is disable, that is it cannot be in m_enabledUpdates + } + + + for (sal_uInt16 i = 0, nCount = m_xUpdates->n_children(); i < nCount; ++i) + { + UpdateDialog::Index const * p = + weld::fromId<UpdateDialog::Index const*>(m_xUpdates->get_id(i)); + if (p->m_eKind == ENABLED_UPDATE && m_xUpdates->get_toggle(i) == TRISTATE_TRUE) { + m_updateData.push_back( m_enabledUpdates[ p->m_nIndex ] ); + } + } + + m_xDialog->response(RET_OK); +} + +IMPL_LINK_NOARG(UpdateDialog, closeHandler, weld::Button&, void) +{ + m_thread->stop(); + m_xDialog->response(RET_CANCEL); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_updatedialog.hxx b/desktop/source/deployment/gui/dp_gui_updatedialog.hxx new file mode 100644 index 000000000..cbf376955 --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_updatedialog.hxx @@ -0,0 +1,168 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <vector> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <tools/link.hxx> +#include <vcl/weld.hxx> + +#include "dp_gui_updatedata.hxx" + +/// @HTML + +class Image; +class KeyEvent; +class MouseEvent; +class ResId; + +namespace com::sun::star { + namespace deployment { class XExtensionManager; + class XPackage; } + namespace uno { class XComponentContext; } +} + +namespace dp_gui { +/** + The modal “Check for Updates” dialog. +*/ +class UpdateDialog: public weld::GenericDialogController { +public: + /** + Create an instance. + + <p>Exactly one of <code>selectedPackages</code> and + <code>packageManagers</code> must be non-null.</p> + + @param context + a non-null component context + + @param parent + the parent window, may be null + + @param vExtensionList + check for updates for the contained extensions. There must only be one extension with + a particular identifier. If one extension is installed in several repositories, then the + one with the highest version must be used, because it contains the latest known update + information. + */ + UpdateDialog( + css::uno::Reference< css::uno::XComponentContext > const & context, + weld::Window * parent, + std::vector< css::uno::Reference< css::deployment::XPackage > > && vExtensionList, + std::vector< dp_gui::UpdateData > * updateData); + + virtual ~UpdateDialog() override; + + virtual short run() override; + + void notifyMenubar( bool bPrepareOnly, bool bRecheckOnly ); + static void createNotifyJob( bool bPrepareOnly, + css::uno::Sequence< css::uno::Sequence< OUString > > const &rItemList ); + +private: + UpdateDialog(UpdateDialog const &) = delete; + UpdateDialog& operator =(UpdateDialog const &) = delete; + + struct DisabledUpdate; + struct SpecificError; + struct IgnoredUpdate; + struct Index; + friend struct Index; + class Thread; + friend class Thread; + + friend class CheckListBox; + + void insertItem(const UpdateDialog::Index *pIndex, bool bEnableCheckBox); + void addAdditional(const UpdateDialog::Index *pIndex, bool bEnableCheckBox); + bool isIgnoredUpdate( UpdateDialog::Index *pIndex ); + + void addEnabledUpdate( OUString const & name, dp_gui::UpdateData const & data ); + void addDisabledUpdate( UpdateDialog::DisabledUpdate const & data ); + void addSpecificError( UpdateDialog::SpecificError const & data ); + + void checkingDone(); + + void enableOk(); + + void getIgnoredUpdates(); + + void initDescription(); + void clearDescription(); + bool showDescription(css::uno::Reference< + css::deployment::XPackage > const & aExtension); + bool showDescription(std::pair< OUString, OUString > const & pairPublisher, + OUString const & sReleaseNotes); + bool showDescription( css::uno::Reference< + css::xml::dom::XNode > const & aUpdateInfo); + bool showDescription( const OUString& rDescription); + + DECL_LINK(selectionHandler, weld::TreeView&, void); + DECL_LINK(allHandler, weld::Toggleable&, void); + DECL_LINK(okHandler, weld::Button&, void); + DECL_LINK(closeHandler, weld::Button&, void); + DECL_LINK(entryToggled, const weld::TreeView::iter_col&, void); + + css::uno::Reference< css::uno::XComponentContext > m_context; + OUString m_none; + OUString m_noInstallable; + OUString m_failure; + OUString m_unknownError; + OUString m_noDescription; + OUString m_noInstall; + OUString m_noDependency; + OUString m_noDependencyCurVer; + OUString m_browserbased; + OUString m_version; + OUString m_ignoredUpdate; + std::vector< dp_gui::UpdateData > m_enabledUpdates; + std::vector< UpdateDialog::DisabledUpdate > m_disabledUpdates; + std::vector< UpdateDialog::SpecificError > m_specificErrors; + std::vector< std::unique_ptr<UpdateDialog::IgnoredUpdate> > m_ignoredUpdates; + std::vector< std::unique_ptr<Index> > m_ListboxEntries; + std::vector< dp_gui::UpdateData > & m_updateData; + rtl::Reference< UpdateDialog::Thread > m_thread; + css::uno::Reference< css::deployment::XExtensionManager > m_xExtensionManager; + + std::unique_ptr<weld::Label> m_xChecking; + std::unique_ptr<weld::Spinner> m_xThrobber; + std::unique_ptr<weld::Label> m_xUpdate; + std::unique_ptr<weld::TreeView> m_xUpdates; + std::unique_ptr<weld::CheckButton> m_xAll; + std::unique_ptr<weld::Label> m_xDescription; + std::unique_ptr<weld::Label> m_xPublisherLabel; + std::unique_ptr<weld::LinkButton> m_xPublisherLink; + std::unique_ptr<weld::Label> m_xReleaseNotesLabel; + std::unique_ptr<weld::LinkButton> m_xReleaseNotesLink; + std::unique_ptr<weld::TextView> m_xDescriptions; + std::unique_ptr<weld::Button> m_xOk; + std::unique_ptr<weld::Button> m_xClose; + std::unique_ptr<weld::Button> m_xHelp; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_updateinstalldialog.cxx b/desktop/source/deployment/gui/dp_gui_updateinstalldialog.cxx new file mode 100644 index 000000000..9b1aa423d --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_updateinstalldialog.cxx @@ -0,0 +1,665 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "dp_gui_updatedata.hxx" + +#include <sal/config.h> +#include <osl/file.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/lang/WrappedTargetException.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/ucb/XProgressHandler.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionManager.hpp> +#include <com/sun/star/deployment/LicenseException.hpp> +#include <com/sun/star/deployment/VersionException.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/task/XInteractionApprove.hpp> + +#include <dp_descriptioninfoset.hxx> +#include <strings.hrc> +#include "dp_gui_updateinstalldialog.hxx" +#include <dp_shared.hxx> +#include <dp_ucb.h> +#include <dp_misc.h> +#include "dp_gui_extensioncmdqueue.hxx" +#include <ucbhelper/content.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ref.hxx> +#include <salhelper/thread.hxx> +#include <com/sun/star/uno/Sequence.h> +#include <comphelper/anytostring.hxx> + +#include <string_view> +#include <vector> + +using dp_misc::StrTitle; + +namespace dp_gui { + +class UpdateInstallDialog::Thread: public salhelper::Thread { + friend class UpdateCommandEnv; +public: + Thread(css::uno::Reference< css::uno::XComponentContext > const & ctx, + UpdateInstallDialog & dialog, std::vector< dp_gui::UpdateData > & aVecUpdateData); + + void stop(); + +private: + virtual ~Thread() override; + + virtual void execute() override; + void downloadExtensions(); + bool download(OUString const & aUrls, UpdateData & aUpdatData); + void installExtensions(); + void removeTempDownloads(); + + UpdateInstallDialog & m_dialog; + + // guarded by Application::GetSolarMutex(): + css::uno::Reference< css::task::XAbortChannel > m_abort; + css::uno::Reference< css::uno::XComponentContext > m_xComponentContext; + std::vector< dp_gui::UpdateData > & m_aVecUpdateData; + ::rtl::Reference<UpdateCommandEnv> m_updateCmdEnv; + + //A folder which is created in the temp directory in which then the updates are downloaded + OUString m_sDownloadFolder; + + bool m_stop; + +}; + +class UpdateCommandEnv + : public ::cppu::WeakImplHelper< css::ucb::XCommandEnvironment, + css::task::XInteractionHandler, + css::ucb::XProgressHandler > +{ + friend class UpdateInstallDialog::Thread; + + ::rtl::Reference<UpdateInstallDialog::Thread> m_installThread; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + +public: + UpdateCommandEnv( css::uno::Reference< css::uno::XComponentContext > xCtx, + ::rtl::Reference<UpdateInstallDialog::Thread> thread); + + // XCommandEnvironment + virtual css::uno::Reference<css::task::XInteractionHandler > SAL_CALL + getInteractionHandler() override; + virtual css::uno::Reference<css::ucb::XProgressHandler > + SAL_CALL getProgressHandler() override; + + // XInteractionHandler + virtual void SAL_CALL handle( + css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override; + + // XProgressHandler + virtual void SAL_CALL push( css::uno::Any const & Status ) override; + virtual void SAL_CALL update( css::uno::Any const & Status ) override; + virtual void SAL_CALL pop() override; +}; + + +UpdateInstallDialog::Thread::Thread( + css::uno::Reference< css::uno::XComponentContext> const & xCtx, + UpdateInstallDialog & dialog, + std::vector< dp_gui::UpdateData > & aVecUpdateData): + salhelper::Thread("dp_gui_updateinstalldialog"), + m_dialog(dialog), + m_xComponentContext(xCtx), + m_aVecUpdateData(aVecUpdateData), + m_updateCmdEnv(new UpdateCommandEnv(xCtx, this)), + m_stop(false) +{} + +void UpdateInstallDialog::Thread::stop() { + css::uno::Reference< css::task::XAbortChannel > abort; + { + SolarMutexGuard g; + abort = m_abort; + m_stop = true; + } + if (abort.is()) { + abort->sendAbort(); + } +} + +UpdateInstallDialog::Thread::~Thread() {} + +void UpdateInstallDialog::Thread::execute() +{ + try { + downloadExtensions(); + installExtensions(); + } + catch (...) + { + } + + //clean up the temp directories + try { + removeTempDownloads(); + } catch( ... ) { + } + + { + //make sure m_dialog is still alive + SolarMutexGuard g; + if (! m_stop) + m_dialog.updateDone(); + } + //UpdateCommandEnv keeps a reference to Thread and prevents destruction. Therefore remove it. + m_updateCmdEnv->m_installThread.clear(); +} + +UpdateInstallDialog::UpdateInstallDialog( + weld::Window* pParent, + std::vector<dp_gui::UpdateData> & aVecUpdateData, + css::uno::Reference< css::uno::XComponentContext > const & xCtx) + : GenericDialogController(pParent, "desktop/ui/updateinstalldialog.ui", + "UpdateInstallDialog") + , m_thread(new Thread(xCtx, *this, aVecUpdateData)) + , m_bError(false) + , m_bNoEntry(true) + , m_sInstalling(DpResId(RID_DLG_UPDATE_INSTALL_INSTALLING)) + , m_sFinished(DpResId(RID_DLG_UPDATE_INSTALL_FINISHED)) + , m_sNoErrors(DpResId(RID_DLG_UPDATE_INSTALL_NO_ERRORS)) + , m_sErrorDownload(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_DOWNLOAD)) + , m_sErrorInstallation(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_INSTALLATION)) + , m_sErrorLicenseDeclined(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_LIC_DECLINED)) + , m_sNoInstall(DpResId(RID_DLG_UPDATE_INSTALL_EXTENSION_NOINSTALL)) + , m_sThisErrorOccurred(DpResId(RID_DLG_UPDATE_INSTALL_THIS_ERROR_OCCURRED)) + , m_xFt_action(m_xBuilder->weld_label("DOWNLOADING")) + , m_xStatusbar(m_xBuilder->weld_progress_bar("STATUSBAR")) + , m_xFt_extension_name(m_xBuilder->weld_label("EXTENSION_NAME")) + , m_xMle_info(m_xBuilder->weld_text_view("INFO")) + , m_xHelp(m_xBuilder->weld_button("help")) + , m_xOk(m_xBuilder->weld_button("ok")) + , m_xCancel(m_xBuilder->weld_button("cancel")) +{ + m_xMle_info->set_size_request(m_xMle_info->get_approximate_digit_width() * 52, + m_xMle_info->get_height_rows(5)); + + m_xExtensionManager = css::deployment::ExtensionManager::get( xCtx ); + + m_xCancel->connect_clicked(LINK(this, UpdateInstallDialog, cancelHandler)); + if ( ! dp_misc::office_is_running()) + m_xHelp->set_sensitive(false); +} + +UpdateInstallDialog::~UpdateInstallDialog() +{ +} + +short UpdateInstallDialog::run() +{ + m_thread->launch(); + short nRet = GenericDialogController::run(); + m_thread->stop(); + return nRet; +} + +// make sure the solar mutex is locked before calling +void UpdateInstallDialog::updateDone() +{ + if (!m_bError) + m_xMle_info->set_text(m_xMle_info->get_text() + m_sNoErrors); + m_xOk->set_sensitive(true); + m_xOk->grab_focus(); + m_xCancel->set_sensitive(false); +} + +// make sure the solar mutex is locked before calling +//sets an error message in the text area +void UpdateInstallDialog::setError(INSTALL_ERROR err, std::u16string_view sExtension, + std::u16string_view exceptionMessage) +{ + OUString sError; + m_bError = true; + + switch (err) + { + case ERROR_DOWNLOAD: + sError = m_sErrorDownload; + break; + case ERROR_INSTALLATION: + sError = m_sErrorInstallation; + break; + case ERROR_LICENSE_DECLINED: + sError = m_sErrorLicenseDeclined; + break; + + default: + OSL_ASSERT(false); + } + + OUString sMsg(m_xMle_info->get_text()); + sError = sError.replaceFirst("%NAME", sExtension); + //We want to have an empty line between the error messages. However, + //there shall be no empty line after the last entry. + if (m_bNoEntry) + m_bNoEntry = false; + else + sMsg += "\n"; + sMsg += sError; + //Insert more information about the error + if (!exceptionMessage.empty()) + sMsg += m_sThisErrorOccurred + exceptionMessage + "\n"; + + sMsg += m_sNoInstall + "\n"; + + m_xMle_info->set_text(sMsg); +} + +void UpdateInstallDialog::setError(std::u16string_view exceptionMessage) +{ + m_bError = true; + m_xMle_info->set_text(m_xMle_info->get_text() + exceptionMessage + "\n"); +} + +IMPL_LINK_NOARG(UpdateInstallDialog, cancelHandler, weld::Button&, void) +{ + m_xDialog->response(RET_CANCEL); +} + +void UpdateInstallDialog::Thread::downloadExtensions() +{ + try + { + //create the download directory in the temp folder + OUString sTempDir; + if (::osl::FileBase::getTempDirURL(sTempDir) != ::osl::FileBase::E_None) + throw css::uno::Exception("Could not get URL for the temp directory. No extensions will be installed.", nullptr); + + //create a unique name for the directory + OUString tempEntry, destFolder; + if (::osl::File::createTempFile(&sTempDir, nullptr, &tempEntry ) != ::osl::File::E_None) + throw css::uno::Exception("Could not create a temporary file in " + sTempDir + + ". No extensions will be installed", nullptr ); + + tempEntry = tempEntry.copy( tempEntry.lastIndexOf( '/' ) + 1 ); + + destFolder = dp_misc::makeURL( sTempDir, tempEntry ) + "_"; + m_sDownloadFolder = destFolder; + try + { + dp_misc::create_folder(nullptr, destFolder, m_updateCmdEnv ); + } catch (const css::uno::Exception & e) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetException( e.Message + " No extensions will be installed", + nullptr, anyEx ); + } + + + sal_uInt16 count = 0; + for (auto & updateData : m_aVecUpdateData) + { + if (!updateData.aUpdateInfo.is() || updateData.aUpdateSource.is()) + continue; + //We assume that m_aVecUpdateData contains only information about extensions which + //can be downloaded directly. + OSL_ASSERT(updateData.sWebsiteURL.isEmpty()); + + //update the name of the extension which is to be downloaded + { + SolarMutexGuard g; + if (m_stop) { + return; + } + m_dialog.m_xFt_extension_name->set_label(updateData.aInstalledPackage->getDisplayName()); + sal_uInt16 prog = (sal::static_int_cast<sal_uInt16>(100) * ++count) / + sal::static_int_cast<sal_uInt16>(m_aVecUpdateData.size()); + m_dialog.m_xStatusbar->set_percentage(prog); + } + dp_misc::DescriptionInfoset info(m_xComponentContext, updateData.aUpdateInfo); + //remember occurring exceptions in case we need to print out error information + std::vector< std::pair<OUString, css::uno::Exception> > vecExceptions; + css::uno::Sequence<OUString> seqDownloadURLs = info.getUpdateDownloadUrls(); + OSL_ENSURE(seqDownloadURLs.hasElements(), "No download URL provided!"); + for (sal_Int32 j = 0; j < seqDownloadURLs.getLength(); j++) + { + try + { + OSL_ENSURE(!seqDownloadURLs[j].isEmpty(), "Download URL is empty!"); + bool bCancelled = download(seqDownloadURLs[j], updateData); + if (bCancelled || !updateData.sLocalURL.isEmpty()) + break; + } + catch ( css::uno::Exception & e ) + { + vecExceptions.emplace_back(seqDownloadURLs[j], e); + //There can be several different errors, for example, the URL is wrong, webserver cannot be reached, + //name cannot be resolved. The UCB helper API does not specify different special exceptions for these + //cases. Therefore ignore and continue. + continue; + } + } + //update the progress and display download error + { + SolarMutexGuard g; + if (m_stop) { + return; + } + if (updateData.sLocalURL.isEmpty()) + { + //Construct a string of all messages contained in the exceptions plus the respective download URLs + OUStringBuffer buf(256); + size_t nPos = 0; + for (auto const& elem : vecExceptions) + { + if (nPos) + buf.append("\n"); + buf.append("Could not download "); + buf.append(elem.first); + buf.append(". "); + buf.append(elem.second.Message); + ++nPos; + } + m_dialog.setError(UpdateInstallDialog::ERROR_DOWNLOAD, updateData.aInstalledPackage->getDisplayName(), + buf); + } + } + + } + } + catch (const css::uno::Exception & e) + { + SolarMutexGuard g; + if (m_stop) { + return; + } + m_dialog.setError(e.Message); + } +} + +void UpdateInstallDialog::Thread::installExtensions() +{ + //Update the fix text in the dialog to "Installing extensions..." + { + SolarMutexGuard g; + if (m_stop) { + return; + } + m_dialog.m_xFt_action->set_label(m_dialog.m_sInstalling); + m_dialog.m_xStatusbar->set_percentage(0); + } + + sal_uInt16 count = 0; + for (auto const& updateData : m_aVecUpdateData) + { + //update the name of the extension which is to be installed + { + SolarMutexGuard g; + if (m_stop) { + return; + } + //we only show progress after an extension has been installed. + if (count > 0) { + m_dialog.m_xStatusbar->set_percentage( + (sal::static_int_cast<sal_uInt16>(100) * count) / + sal::static_int_cast<sal_uInt16>(m_aVecUpdateData.size())); + } + m_dialog.m_xFt_extension_name->set_label(updateData.aInstalledPackage->getDisplayName()); + } + bool bError = false; + bool bLicenseDeclined = false; + css::uno::Reference<css::deployment::XPackage> xExtension; + css::uno::Exception exc; + try + { + css::uno::Reference< css::task::XAbortChannel > xAbortChannel( + updateData.aInstalledPackage->createAbortChannel() ); + { + SolarMutexGuard g; + if (m_stop) { + return; + } + m_abort = xAbortChannel; + } + if (!updateData.aUpdateSource.is() && !updateData.sLocalURL.isEmpty()) + { + css::beans::NamedValue prop("EXTENSION_UPDATE", css::uno::Any(OUString("1"))); + if (!updateData.bIsShared) + xExtension = m_dialog.getExtensionManager()->addExtension( + updateData.sLocalURL, css::uno::Sequence<css::beans::NamedValue>(&prop, 1), + "user", xAbortChannel, m_updateCmdEnv); + else + xExtension = m_dialog.getExtensionManager()->addExtension( + updateData.sLocalURL, css::uno::Sequence<css::beans::NamedValue>(&prop, 1), + "shared", xAbortChannel, m_updateCmdEnv); + } + else if (updateData.aUpdateSource.is()) + { + OSL_ASSERT(updateData.aUpdateSource.is()); + //I am not sure if we should obtain the install properties and pass them into + //add extension. Currently it contains only "SUPPRESS_LICENSE". So it could happen + //that a license is displayed when updating from the shared repository, although the + //shared extension was installed using "SUPPRESS_LICENSE". + css::beans::NamedValue prop("EXTENSION_UPDATE", css::uno::Any(OUString("1"))); + if (!updateData.bIsShared) + xExtension = m_dialog.getExtensionManager()->addExtension( + updateData.aUpdateSource->getURL(), css::uno::Sequence<css::beans::NamedValue>(&prop, 1), + "user", xAbortChannel, m_updateCmdEnv); + else + xExtension = m_dialog.getExtensionManager()->addExtension( + updateData.aUpdateSource->getURL(), css::uno::Sequence<css::beans::NamedValue>(&prop, 1), + "shared", xAbortChannel, m_updateCmdEnv); + } + } + catch (css::deployment::DeploymentException & de) + { + if (de.Cause.has<css::deployment::LicenseException>()) + { + bLicenseDeclined = true; + } + else + { + exc = de.Cause.get<css::uno::Exception>(); + bError = true; + } + } + catch (css::uno::Exception& e) + { + exc = e; + bError = true; + } + + if (bLicenseDeclined) + { + SolarMutexGuard g; + if (m_stop) { + return; + } + m_dialog.setError(UpdateInstallDialog::ERROR_LICENSE_DECLINED, + updateData.aInstalledPackage->getDisplayName(), std::u16string_view()); + } + else if (!xExtension.is() || bError) + { + SolarMutexGuard g; + if (m_stop) { + return; + } + m_dialog.setError(UpdateInstallDialog::ERROR_INSTALLATION, + updateData.aInstalledPackage->getDisplayName(), exc.Message); + } + ++count; + } + { + SolarMutexGuard g; + if (m_stop) { + return; + } + m_dialog.m_xStatusbar->set_percentage(100); + m_dialog.m_xFt_extension_name->set_label(OUString()); + m_dialog.m_xFt_action->set_label(m_dialog.m_sFinished); + } +} + +void UpdateInstallDialog::Thread::removeTempDownloads() +{ + if (!m_sDownloadFolder.isEmpty()) + { + dp_misc::erase_path(m_sDownloadFolder, + css::uno::Reference<css::ucb::XCommandEnvironment>(),false /* no throw: ignore errors */ ); + //remove also the temp file which we have used to create the unique name + OUString tempFile = m_sDownloadFolder.copy(0, m_sDownloadFolder.getLength() - 1); + dp_misc::erase_path(tempFile, css::uno::Reference<css::ucb::XCommandEnvironment>(),false); + m_sDownloadFolder.clear(); + } +} + +bool UpdateInstallDialog::Thread::download(OUString const & sDownloadURL, UpdateData & aUpdateData) +{ + { + SolarMutexGuard g; + if (m_stop) { + return m_stop; + } + } + + OSL_ASSERT(m_sDownloadFolder.getLength()); + OUString destFolder, tempEntry; + if (::osl::File::createTempFile( + &m_sDownloadFolder, + nullptr, &tempEntry ) != ::osl::File::E_None) + { + //ToDo feedback in window that download of this component failed + throw css::uno::Exception("Could not create temporary file in folder " + destFolder + ".", nullptr); + } + tempEntry = tempEntry.copy( tempEntry.lastIndexOf( '/' ) + 1 ); + + destFolder = dp_misc::makeURL( m_sDownloadFolder, tempEntry ) + "_"; + + ::ucbhelper::Content destFolderContent; + dp_misc::create_folder( &destFolderContent, destFolder, m_updateCmdEnv ); + + ::ucbhelper::Content sourceContent; + (void)dp_misc::create_ucb_content(&sourceContent, sDownloadURL, m_updateCmdEnv); + + const OUString sTitle( StrTitle::getTitle( sourceContent ) ); + + destFolderContent.transferContent( + sourceContent, ::ucbhelper::InsertOperation::Copy, + sTitle, css::ucb::NameClash::OVERWRITE ); + + { + //the user may have cancelled the dialog because downloading took too long + SolarMutexGuard g; + if (m_stop) { + return m_stop; + } + //all errors should be handled by the command environment. + aUpdateData.sLocalURL = destFolder + "/" + sTitle; + } + + return m_stop; +} + +UpdateCommandEnv::UpdateCommandEnv( css::uno::Reference< css::uno::XComponentContext > xCtx, + ::rtl::Reference<UpdateInstallDialog::Thread> thread) + : m_installThread(std::move(thread)), + m_xContext(std::move(xCtx)) +{ +} + +// XCommandEnvironment +css::uno::Reference<css::task::XInteractionHandler> UpdateCommandEnv::getInteractionHandler() +{ + return this; +} + +css::uno::Reference<css::ucb::XProgressHandler> UpdateCommandEnv::getProgressHandler() +{ + return this; +} + +// XInteractionHandler +void UpdateCommandEnv::handle( + css::uno::Reference< css::task::XInteractionRequest> const & xRequest ) +{ + css::uno::Any request( xRequest->getRequest() ); + OSL_ASSERT( request.getValueTypeClass() == css::uno::TypeClass_EXCEPTION ); + dp_misc::TRACE("[dp_gui_cmdenv.cxx] incoming request:\n" + + ::comphelper::anyToString(request) + "\n\n"); + + css::deployment::VersionException verExc; + bool approve = false; + + if (request >>= verExc) + { //We must catch the version exception during the update, + //because otherwise the user would be confronted with the dialogs, asking + //them if they want to replace an already installed version of the same extension. + //During an update we assume that we always want to replace the old version with the + //new version. + approve = true; + } + + if (!approve) + { + //forward to interaction handler for main dialog. + handleInteractionRequest( m_xContext, xRequest ); + } + else + { + // select: + css::uno::Sequence< css::uno::Reference< css::task::XInteractionContinuation > > conts( + xRequest->getContinuations() ); + css::uno::Reference< css::task::XInteractionContinuation > const * pConts = + conts.getConstArray(); + sal_Int32 len = conts.getLength(); + for ( sal_Int32 pos = 0; pos < len; ++pos ) + { + if (approve) { + css::uno::Reference< css::task::XInteractionApprove > xInteractionApprove( + pConts[ pos ], css::uno::UNO_QUERY ); + if (xInteractionApprove.is()) { + xInteractionApprove->select(); + // don't query again for ongoing continuations: + approve = false; + } + } + } + } +} + +// XProgressHandler +void UpdateCommandEnv::push( css::uno::Any const & /*Status*/ ) +{ +} + +void UpdateCommandEnv::update( css::uno::Any const & /*Status */) +{ +} + +void UpdateCommandEnv::pop() +{ +} + + +} //end namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/dp_gui_updateinstalldialog.hxx b/desktop/source/deployment/gui/dp_gui_updateinstalldialog.hxx new file mode 100644 index 000000000..39b37ed4b --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_updateinstalldialog.hxx @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/weld.hxx> +#include <rtl/ref.hxx> + +#include <string_view> +#include <vector> + +/// @HTML +namespace com::sun::star::deployment { + class XExtensionManager; +} +namespace com::sun::star::uno { + class XComponentContext; +} + +namespace dp_gui { + + struct UpdateData; + class UpdateCommandEnv; + + +/** + The modal “Download and Installation” dialog. +*/ +class UpdateInstallDialog : public weld::GenericDialogController +{ +public: + /** + Create an instance. + + @param parent + the parent window, may be null + */ + UpdateInstallDialog(weld::Window* parent, std::vector<UpdateData> & aVecUpdateData, + css::uno::Reference< css::uno::XComponentContext > const & xCtx); + + virtual ~UpdateInstallDialog() override; + + virtual short run() override; + +private: + UpdateInstallDialog(UpdateInstallDialog const &) = delete; + UpdateInstallDialog& operator =(UpdateInstallDialog const &) = delete; + + class Thread; + friend class Thread; + friend class UpdateCommandEnv; + + DECL_LINK(cancelHandler, weld::Button&, void); + + //signals in the dialog that we have finished. + void updateDone(); + //Writes a particular error into the info listbox. + enum INSTALL_ERROR + { + ERROR_DOWNLOAD, + ERROR_INSTALLATION, + ERROR_LICENSE_DECLINED + }; + void setError(INSTALL_ERROR err, std::u16string_view sExtension, std::u16string_view exceptionMessage); + void setError(std::u16string_view exceptionMessage); + const css::uno::Reference< css::deployment::XExtensionManager >& getExtensionManager() const + { return m_xExtensionManager; } + + rtl::Reference< Thread > m_thread; + css::uno::Reference< css::deployment::XExtensionManager > m_xExtensionManager; + //Signals that an error occurred during download and installation + bool m_bError; + bool m_bNoEntry; + + OUString m_sInstalling; + OUString m_sFinished; + OUString m_sNoErrors; + OUString m_sErrorDownload; + OUString m_sErrorInstallation; + OUString m_sErrorLicenseDeclined; + OUString m_sNoInstall; + OUString m_sThisErrorOccurred; + + std::unique_ptr<weld::Label> m_xFt_action; + std::unique_ptr<weld::ProgressBar> m_xStatusbar; + std::unique_ptr<weld::Label> m_xFt_extension_name; + std::unique_ptr<weld::TextView> m_xMle_info; + std::unique_ptr<weld::Button> m_xHelp; + std::unique_ptr<weld::Button> m_xOk; + std::unique_ptr<weld::Button> m_xCancel; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/license_dialog.cxx b/desktop/source/deployment/gui/license_dialog.cxx new file mode 100644 index 000000000..23f184a33 --- /dev/null +++ b/desktop/source/deployment/gui/license_dialog.cxx @@ -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 . + */ + + +#include <comphelper/unwrapargs.hxx> +#include <vcl/event.hxx> +#include <vcl/idle.hxx> +#include <vcl/svapp.hxx> +#include <vcl/threadex.hxx> +#include <vcl/weld.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include "license_dialog.hxx" + +#include <functional> +#include <string_view> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace dp_gui { + +namespace { + +struct LicenseDialogImpl : public weld::GenericDialogController +{ + bool m_bLicenseRead; + Idle m_aResized; + AutoTimer m_aRepeat; + + std::unique_ptr<weld::Label> m_xFtHead; + std::unique_ptr<weld::Widget> m_xArrow1; + std::unique_ptr<weld::Widget> m_xArrow2; + std::unique_ptr<weld::TextView> m_xLicense; + std::unique_ptr<weld::Button> m_xDown; + std::unique_ptr<weld::Button> m_xAcceptButton; + std::unique_ptr<weld::Button> m_xDeclineButton; + + void PageDown(); + DECL_LINK(ScrollTimerHdl, Timer*, void); + DECL_LINK(ScrolledHdl, weld::TextView&, void); + DECL_LINK(ResizedHdl, Timer*, void); + DECL_LINK(CancelHdl, weld::Button&, void); + DECL_LINK(AcceptHdl, weld::Button&, void); + DECL_LINK(KeyInputHdl, const KeyEvent&, bool); + DECL_STATIC_LINK(LicenseDialogImpl, KeyReleaseHdl, const KeyEvent&, bool); + DECL_LINK(MousePressHdl, const MouseEvent&, bool); + DECL_LINK(MouseReleaseHdl, const MouseEvent&, bool); + DECL_LINK(SizeAllocHdl, const Size&, void); + + LicenseDialogImpl(weld::Window * pParent, + std::u16string_view sExtensionName, + const OUString & sLicenseText); + + bool IsEndReached() const; +}; + +} + +LicenseDialogImpl::LicenseDialogImpl( + weld::Window * pParent, + std::u16string_view sExtensionName, + const OUString & sLicenseText) + : GenericDialogController(pParent, "desktop/ui/licensedialog.ui", "LicenseDialog") + , m_bLicenseRead(false) + , m_aResized("desktop LicenseDialogImpl m_aResized") + , m_aRepeat("LicenseDialogImpl m_aRepeat") + , m_xFtHead(m_xBuilder->weld_label("head")) + , m_xArrow1(m_xBuilder->weld_widget("arrow1")) + , m_xArrow2(m_xBuilder->weld_widget("arrow2")) + , m_xLicense(m_xBuilder->weld_text_view("textview")) + , m_xDown(m_xBuilder->weld_button("down")) + , m_xAcceptButton(m_xBuilder->weld_button("ok")) + , m_xDeclineButton(m_xBuilder->weld_button("cancel")) +{ + m_xArrow1->show(); + m_xArrow2->hide(); + + m_xLicense->connect_size_allocate(LINK(this, LicenseDialogImpl, SizeAllocHdl)); + m_xLicense->set_size_request(m_xLicense->get_approximate_digit_width() * 72, + m_xLicense->get_height_rows(21)); + + m_xLicense->set_text(sLicenseText); + m_xFtHead->set_label(m_xFtHead->get_label() + "\n" + sExtensionName); + + m_xAcceptButton->connect_clicked( LINK(this, LicenseDialogImpl, AcceptHdl) ); + m_xDeclineButton->connect_clicked( LINK(this, LicenseDialogImpl, CancelHdl) ); + + m_xLicense->connect_vadjustment_changed(LINK(this, LicenseDialogImpl, ScrolledHdl)); + m_xDown->connect_mouse_press(LINK(this, LicenseDialogImpl, MousePressHdl)); + m_xDown->connect_mouse_release(LINK(this, LicenseDialogImpl, MouseReleaseHdl)); + m_xDown->connect_key_press(LINK(this, LicenseDialogImpl, KeyInputHdl)); + m_xDown->connect_key_release(LINK(this, LicenseDialogImpl, KeyReleaseHdl)); + + m_aRepeat.SetTimeout(Application::GetSettings().GetMouseSettings().GetButtonRepeat()); + m_aRepeat.SetInvokeHandler(LINK(this, LicenseDialogImpl, ScrollTimerHdl)); + + m_aResized.SetPriority(TaskPriority::LOWEST); + m_aResized.SetInvokeHandler(LINK(this, LicenseDialogImpl, ResizedHdl)); +} + +IMPL_LINK_NOARG(LicenseDialogImpl, SizeAllocHdl, const Size&, void) +{ + m_aResized.Start(); +} + +IMPL_LINK_NOARG(LicenseDialogImpl, AcceptHdl, weld::Button&, void) +{ + m_xDialog->response(RET_OK); +} + +IMPL_LINK_NOARG(LicenseDialogImpl, CancelHdl, weld::Button&, void) +{ + m_xDialog->response(RET_CANCEL); +} + +bool LicenseDialogImpl::IsEndReached() const +{ + return m_xLicense->vadjustment_get_value() + m_xLicense->vadjustment_get_page_size() >= m_xLicense->vadjustment_get_upper(); +} + +IMPL_LINK_NOARG(LicenseDialogImpl, ScrolledHdl, weld::TextView&, void) +{ + if (IsEndReached()) + { + m_xDown->set_sensitive(false); + m_aRepeat.Stop(); + + if (!m_bLicenseRead) + { + m_xAcceptButton->set_sensitive(true); + m_xAcceptButton->grab_focus(); + m_xArrow1->hide(); + m_xArrow2->show(); + m_bLicenseRead = true; + } + } + else + m_xDown->set_sensitive(true); +} + +void LicenseDialogImpl::PageDown() +{ + m_xLicense->vadjustment_set_value(m_xLicense->vadjustment_get_value() + + m_xLicense->vadjustment_get_page_size()); + ScrolledHdl(*m_xLicense); +} + +IMPL_LINK(LicenseDialogImpl, KeyInputHdl, const KeyEvent&, rKEvt, bool) +{ + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + if (aKeyCode.GetCode() == KEY_RETURN || aKeyCode.GetCode() == KEY_SPACE) + PageDown(); + return false; +} + +IMPL_LINK_NOARG(LicenseDialogImpl, ResizedHdl, Timer*, void) +{ + ScrolledHdl(*m_xLicense); +} + +IMPL_LINK_NOARG(LicenseDialogImpl, ScrollTimerHdl, Timer*, void) +{ + PageDown(); +} + +IMPL_STATIC_LINK_NOARG(LicenseDialogImpl, KeyReleaseHdl, const KeyEvent&, bool) +{ + return false; +} + +IMPL_LINK_NOARG(LicenseDialogImpl, MousePressHdl, const MouseEvent&, bool) +{ + PageDown(); + m_aRepeat.Start(); + return false; +} + +IMPL_LINK_NOARG(LicenseDialogImpl, MouseReleaseHdl, const MouseEvent&, bool) +{ + m_aRepeat.Stop(); + return false; +} + +LicenseDialog::LicenseDialog( Sequence<Any> const& args, + Reference<XComponentContext> const& ) +{ + comphelper::unwrapArgs( args, m_parent, m_sExtensionName, m_sLicenseText ); +} + +// XServiceInfo +OUString LicenseDialog::getImplementationName() +{ + return "com.sun.star.comp.deployment.ui.LicenseDialog"; +} + +sal_Bool LicenseDialog::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > LicenseDialog::getSupportedServiceNames() +{ + return { "com.sun.star.deployment.ui.LicenseDialog" }; +} + + +// XExecutableDialog + +void LicenseDialog::setTitle( OUString const & ) +{ +} + +sal_Int16 LicenseDialog::execute() +{ + return vcl::solarthread::syncExecute( + std::bind(&LicenseDialog::solar_execute, this)); +} + +sal_Int16 LicenseDialog::solar_execute() +{ + LicenseDialogImpl dlg(Application::GetFrameWeld(m_parent), m_sExtensionName, m_sLicenseText); + return dlg.run(); +} + +} // namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/gui/license_dialog.hxx b/desktop/source/deployment/gui/license_dialog.hxx new file mode 100644 index 000000000..3c628a880 --- /dev/null +++ b/desktop/source/deployment/gui/license_dialog.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 <cppuhelper/implbase.hxx> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +namespace dp_gui +{ +class LicenseDialog + : public ::cppu::WeakImplHelper<css::ui::dialogs::XExecutableDialog, css::lang::XServiceInfo> +{ + css::uno::Reference<css::awt::XWindow> /* const */ m_parent; + OUString m_sExtensionName; + OUString /* const */ m_sLicenseText; + + sal_Int16 solar_execute(); + +public: + LicenseDialog(css::uno::Sequence<css::uno::Any> const& args, + css::uno::Reference<css::uno::XComponentContext> const& xComponentContext); + + // 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; + + // XExecutableDialog + virtual void SAL_CALL setTitle(OUString const& title) override; + virtual sal_Int16 SAL_CALL execute() override; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |