diff options
Diffstat (limited to 'framework/source/dispatch/closedispatcher.cxx')
-rw-r--r-- | framework/source/dispatch/closedispatcher.cxx | 612 |
1 files changed, 612 insertions, 0 deletions
diff --git a/framework/source/dispatch/closedispatcher.cxx b/framework/source/dispatch/closedispatcher.cxx new file mode 100644 index 000000000..a6ff39d45 --- /dev/null +++ b/framework/source/dispatch/closedispatcher.cxx @@ -0,0 +1,612 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <dispatch/closedispatcher.hxx> +#include <pattern/frame.hxx> +#include <framework/framelistanalyzer.hxx> +#include <services.h> + +#include <com/sun/star/bridge/BridgeFactory.hpp> +#include <com/sun/star/bridge/XBridgeFactory2.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/frame/CommandGroup.hpp> +#include <com/sun/star/frame/StartModule.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/document/XActionLockable.hpp> +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <toolkit/helper/vclunohelper.hxx> + +#include <osl/diagnose.h> +#include <utility> +#include <vcl/window.hxx> +#include <vcl/svapp.hxx> +#include <vcl/syswin.hxx> +#include <unotools/moduleoptions.hxx> +#include <o3tl/string_view.hxx> + +using namespace com::sun::star; + +namespace framework{ + +#ifdef fpf + #error "Who uses \"fpf\" as define. It will overwrite my namespace alias ..." +#endif +namespace fpf = ::framework::pattern::frame; + +constexpr OUStringLiteral URL_CLOSEDOC = u".uno:CloseDoc"; +constexpr OUStringLiteral URL_CLOSEWIN = u".uno:CloseWin"; +const char URL_CLOSEFRAME[] = ".uno:CloseFrame"; + +CloseDispatcher::CloseDispatcher(css::uno::Reference< css::uno::XComponentContext > xContext , + const css::uno::Reference< css::frame::XFrame >& xFrame , + std::u16string_view sTarget) + : m_xContext(std::move(xContext)) + , m_aAsyncCallback( + new vcl::EventPoster(LINK(this, CloseDispatcher, impl_asyncCallback))) + , m_eOperation(E_CLOSE_DOC) + , m_pSysWindow(nullptr) +{ + uno::Reference<frame::XFrame> xTarget = static_impl_searchRightTargetFrame(xFrame, sTarget); + m_xCloseFrame = xTarget; + + // Try to retrieve the system window instance of the closing frame. + uno::Reference<awt::XWindow> xWindow = xTarget->getContainerWindow(); + if (xWindow.is()) + { + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindow); + if (pWindow->IsSystemWindow()) + m_pSysWindow = dynamic_cast<SystemWindow*>(pWindow.get()); + } +} + +CloseDispatcher::~CloseDispatcher() +{ + SolarMutexGuard g; + m_aAsyncCallback.reset(); + m_pSysWindow.reset(); +} + +void SAL_CALL CloseDispatcher::dispatch(const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments) +{ + dispatchWithNotification(aURL, lArguments, css::uno::Reference< css::frame::XDispatchResultListener >()); +} + +css::uno::Sequence< sal_Int16 > SAL_CALL CloseDispatcher::getSupportedCommandGroups() +{ + return css::uno::Sequence< sal_Int16 >{css::frame::CommandGroup::VIEW, css::frame::CommandGroup::DOCUMENT}; +} + +css::uno::Sequence< css::frame::DispatchInformation > SAL_CALL CloseDispatcher::getConfigurableDispatchInformation(sal_Int16 nCommandGroup) +{ + if (nCommandGroup == css::frame::CommandGroup::VIEW) + { + /* Attention: Don't add .uno:CloseFrame here. Because it's not really + a configurable feature ... and further it does not have + a valid UIName entry inside the GenericCommands.xcu ... */ + css::uno::Sequence< css::frame::DispatchInformation > lViewInfos{ + { URL_CLOSEWIN, css::frame::CommandGroup::VIEW } + }; + return lViewInfos; + } + else if (nCommandGroup == css::frame::CommandGroup::DOCUMENT) + { + css::uno::Sequence< css::frame::DispatchInformation > lDocInfos{ + { URL_CLOSEDOC, css::frame::CommandGroup::DOCUMENT } + }; + return lDocInfos; + } + + return css::uno::Sequence< css::frame::DispatchInformation >(); +} + +void SAL_CALL CloseDispatcher::addStatusListener(const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/, + const css::util::URL& /*aURL*/ ) +{ +} + +void SAL_CALL CloseDispatcher::removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/, + const css::util::URL& /*aURL*/ ) +{ +} + +void SAL_CALL CloseDispatcher::dispatchWithNotification(const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments, + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) +{ + // SAFE -> ---------------------------------- + SolarMutexClearableGuard aWriteLock; + + // This reference indicates, that we were already called before and + // our asynchronous process was not finished yet. + // We have to reject double calls. Otherwise we risk, + // that we try to close an already closed resource... + // And it is no problem to do nothing then. The UI user will try it again, if + // non of these jobs was successful. + if (m_xSelfHold.is()) + { + aWriteLock.clear(); + // <- SAFE ------------------------------ + + implts_notifyResultListener( + xListener, + css::frame::DispatchResultState::DONTKNOW, + css::uno::Any()); + return; + } + + // First we have to check, if this dispatcher is used right. Means if valid URLs are used. + // If not - we have to break this operation. But an optional listener must be informed. + // BTW: We save the information about the requested operation. Because + // we need it later. + if ( aURL.Complete == URL_CLOSEDOC ) + m_eOperation = E_CLOSE_DOC; + else if ( aURL.Complete == URL_CLOSEWIN ) + m_eOperation = E_CLOSE_WIN; + else if ( aURL.Complete == URL_CLOSEFRAME ) + m_eOperation = E_CLOSE_FRAME; + else + { + aWriteLock.clear(); + // <- SAFE ------------------------------ + + implts_notifyResultListener( + xListener, + css::frame::DispatchResultState::FAILURE, + css::uno::Any()); + return; + } + + if (m_pSysWindow && m_pSysWindow->GetCloseHdl().IsSet()) + { + // The closing frame has its own close handler. Call it instead. + m_pSysWindow->GetCloseHdl().Call(*m_pSysWindow); + + aWriteLock.clear(); + // <- SAFE ------------------------------ + + implts_notifyResultListener( + xListener, + css::frame::DispatchResultState::SUCCESS, + css::uno::Any()); + + return; + } + + // OK - URLs are the right ones. + // But we can't execute synchronously :-) + // May we are called from a generic key-input handler, + // which isn't aware that this call kill its own environment... + // Do it asynchronous everytimes! + + // But don't forget to hold ourselves alive. + // We are called back from an environment, which doesn't know a uno reference. + // They call us back by using our c++ interface. + + m_xResultListener = xListener; + m_xSelfHold.set(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY); + + aWriteLock.clear(); + // <- SAFE ---------------------------------- + + bool bIsSynchron = false; + for (const css::beans::PropertyValue& rArg : lArguments ) + { + if ( rArg.Name == "SynchronMode" ) + { + rArg.Value >>= bIsSynchron; + break; + } + } + + if ( bIsSynchron ) + impl_asyncCallback(nullptr); + else + { + SolarMutexGuard g; + m_aAsyncCallback->Post(); + } +} + +/** + @short asynchronous callback + @descr We start all actions inside this object asynchronous + (see comments there). + Now we do the following: + - close all views to the same document, if needed and possible + - make the current frame empty + ! This step is necessary to handle errors during closing the + document inside the frame. May the document shows a dialog and + the user ignore it. Then the state of the office can be changed + during we try to close frame and document. + - check the environment (means count open frames - excluding our + current one) + - decide then, if we must close this frame only, establish the backing mode + or shutdown the whole application. +*/ +IMPL_LINK_NOARG(CloseDispatcher, impl_asyncCallback, LinkParamNone*, void) +{ + try + { + + // Allow calling of XController->suspend() everytimes. + // Dispatch is an UI functionality. We implement such dispatch object here. + // And further XController->suspend() was designed to bring an UI ... + bool bControllerSuspended = false; + + bool bCloseAllViewsToo; + EOperation eOperation; + css::uno::Reference< css::uno::XComponentContext > xContext; + css::uno::Reference< css::frame::XFrame > xCloseFrame; + css::uno::Reference< css::frame::XDispatchResultListener > xListener; + { + SolarMutexGuard g; + + // Closing of all views, related to the same document, is allowed + // only if the dispatched URL was ".uno:CloseDoc"! + bCloseAllViewsToo = (m_eOperation == E_CLOSE_DOC); + + eOperation = m_eOperation; + xContext = m_xContext; + xCloseFrame.set(m_xCloseFrame.get(), css::uno::UNO_QUERY); + xListener = m_xResultListener; + } + + // frame already dead ?! + // Nothing to do ! + if (! xCloseFrame.is()) + return; + + bool bCloseFrame = false; + bool bEstablishBackingMode = false; + bool bTerminateApp = false; + + // Analyze the environment a first time. + // If we found some special cases, we can + // make some decisions earlier! + css::uno::Reference< css::frame::XFramesSupplier > xDesktop( css::frame::Desktop::create(xContext), css::uno::UNO_QUERY_THROW); + FrameListAnalyzer aCheck1(xDesktop, xCloseFrame, FrameAnalyzerFlags::Help | FrameAnalyzerFlags::BackingComponent); + + // Check for existing UNO connections. + // NOTE: There is a race between checking this and connections being created/destroyed before + // we close the frame / terminate the app. + css::uno::Reference<css::bridge::XBridgeFactory2> bridgeFac( css::bridge::BridgeFactory::create(xContext) ); + bool bHasActiveConnections = bridgeFac->getExistingBridges().hasElements(); + + // a) If the current frame (where the close dispatch was requested for) does not have + // any parent frame ... it will close this frame only. Such frame isn't part of the + // global desktop tree ... and such frames are used as "implementation details" only. + // E.g. the live previews of our wizards doing such things. And then the owner of the frame + // is responsible for closing the application or accepting closing of the application + // by others. + if ( ! xCloseFrame->getCreator().is()) + bCloseFrame = true; + + // b) The help window can't disagree with any request. + // Because it doesn't implement a controller - it uses a window only. + // Further it can't be the last open frame - if we do all other things + // right inside this CloseDispatcher implementation. + // => close it! + else if (aCheck1.m_bReferenceIsHelp) + bCloseFrame = true; + + // c) If we are already in "backing mode", we terminate the application, if no active UNO connections are found. + // If there is an active UNO connection, we only close the frame and leave the application alive. + // It doesn't matter, how many other frames (can be the help or hidden frames only) are open then. + else if (aCheck1.m_bReferenceIsBacking) { + if (bHasActiveConnections) + bCloseFrame = true; + else + bTerminateApp = true; + } + + // d) Otherwise we have to: close all views to the same document, close the + // document inside our own frame and decide then again, what has to be done! + else + { + if (implts_prepareFrameForClosing(m_xCloseFrame, bCloseAllViewsToo, bControllerSuspended)) + { + // OK; this frame is empty now. + // Check the environment again to decide, what is the next step. + FrameListAnalyzer aCheck2(xDesktop, xCloseFrame, FrameAnalyzerFlags::All); + + // c1) there is as minimum 1 frame open, which is visible and contains a document + // different from our one. And it's not the help! + // => close our frame only - nothing else. + if (!aCheck2.m_lOtherVisibleFrames.empty()) + bCloseFrame = true; + else + + // c2) if we close the current view ... but not all other views + // to the same document, we must close the current frame only! + // Because implts_closeView() suspended this view only - does not + // close the frame. + if ( + (!bCloseAllViewsToo ) && + (!aCheck2.m_lModelFrames.empty()) + ) + bCloseFrame = true; + + else + // c3) there is no other (visible) frame open ... + // The help module will be ignored everytimes! + // But we have to decide if we must terminate the + // application or establish the backing mode now. + // And that depends from the dispatched URL ... + { + if (eOperation == E_CLOSE_FRAME) + { + if (bHasActiveConnections) + bCloseFrame = true; + else + bTerminateApp = true; + } + else if( SvtModuleOptions().IsModuleInstalled(SvtModuleOptions::EModule::STARTMODULE) ) + bEstablishBackingMode = true; + else if (bHasActiveConnections) + bCloseFrame = true; + else + bTerminateApp = true; + } + } + } + + // Do it now ... + bool bSuccess = false; + if (bCloseFrame) + bSuccess = implts_closeFrame(); + else if (bEstablishBackingMode) + #if defined MACOSX + { + // on mac close down, quickstarter keeps the process alive + // however if someone has shut down the quickstarter + // behave as any other platform + + bool bQuickstarterRunning = false; + // get quickstart service + try + { + css::uno::Reference< css::beans::XFastPropertySet > xSet( xContext->getServiceManager()->createInstanceWithContext(IMPLEMENTATIONNAME_QUICKLAUNCHER, xContext), css::uno::UNO_QUERY_THROW ); + css::uno::Any aVal( xSet->getFastPropertyValue( 0 ) ); + bool bState = false; + if( aVal >>= bState ) + bQuickstarterRunning = bState; + } + catch( const css::uno::Exception& ) + { + } + bSuccess = bQuickstarterRunning ? implts_terminateApplication() : implts_establishBackingMode(); + } + #else + bSuccess = implts_establishBackingMode(); + #endif + else if (bTerminateApp) + bSuccess = implts_terminateApplication(); + + if ( ! bSuccess && bControllerSuspended ) + { + css::uno::Reference< css::frame::XController > xController = xCloseFrame->getController(); + if (xController.is()) + xController->suspend(false); + } + + // inform listener + sal_Int16 nState = css::frame::DispatchResultState::FAILURE; + if (bSuccess) + nState = css::frame::DispatchResultState::SUCCESS; + implts_notifyResultListener(xListener, nState, css::uno::Any()); + + SolarMutexGuard g; + // This method was called asynchronous from our main thread by using a pointer. + // We reached this method only, by using a reference to ourself :-) + // Further this member is used to detect still running and not yet finished + // asynchronous operations. So it's time now to release this reference. + // But hold it temp alive. Otherwise we die before we can finish this method really :-)) + css::uno::Reference< css::uno::XInterface > xTempHold = m_xSelfHold; + m_xSelfHold.clear(); + m_xResultListener.clear(); + } + catch(const css::lang::DisposedException&) + { + } +} + +bool CloseDispatcher::implts_prepareFrameForClosing(const css::uno::Reference< css::frame::XFrame >& xFrame, + bool bCloseAllOtherViewsToo, + bool& bControllerSuspended ) +{ + // Frame already dead ... so this view is closed ... is closed ... is ... .-) + if (! xFrame.is()) + return true; + + // Close all views to the same document ... if forced to do so. + // But don't touch our own frame here! + // We must do so ... because the may be following controller->suspend() + // will show the "save/discard/cancel" dialog for the last view only! + if (bCloseAllOtherViewsToo) + { + css::uno::Reference< css::uno::XComponentContext > xContext; + { + SolarMutexGuard g; + xContext = m_xContext; + } + + css::uno::Reference< css::frame::XFramesSupplier > xDesktop( css::frame::Desktop::create( xContext ), css::uno::UNO_QUERY_THROW); + FrameListAnalyzer aCheck(xDesktop, xFrame, FrameAnalyzerFlags::All); + + size_t c = aCheck.m_lModelFrames.size(); + size_t i = 0; + for (i=0; i<c; ++i) + { + if (!fpf::closeIt(aCheck.m_lModelFrames[i])) + return false; + } + } + + // Inform user about modified documents or still running jobs (e.g. printing). + { + css::uno::Reference< css::frame::XController > xController = xFrame->getController(); + if (xController.is()) // some views don't uses a controller .-( (e.g. the help window) + { + bControllerSuspended = xController->suspend(true); + if (! bControllerSuspended) + return false; + } + } + + // don't remove the component really by e.g. calling setComponent(null, null). + // It's enough to suspend the controller. + // If we close the frame later this controller doesn't show the same dialog again. + return true; +} + +bool CloseDispatcher::implts_closeFrame() +{ + css::uno::Reference< css::frame::XFrame > xFrame; + { + SolarMutexGuard g; + xFrame.set(m_xCloseFrame.get(), css::uno::UNO_QUERY); + } + + // frame already dead ? => so it's closed ... it's closed ... + if ( ! xFrame.is() ) + return true; + + // don't deliver ownership; our "UI user" will try it again if it failed. + // OK - he will get an empty frame then. But normally an empty frame + // should be closeable always :-) + if (!fpf::closeIt(xFrame)) + return false; + + { + SolarMutexGuard g; + m_xCloseFrame.clear(); + } + + return true; +} + +bool CloseDispatcher::implts_establishBackingMode() +{ + css::uno::Reference< css::uno::XComponentContext > xContext; + css::uno::Reference< css::frame::XFrame > xFrame; + { + SolarMutexGuard g; + xContext = m_xContext; + xFrame.set(m_xCloseFrame.get(), css::uno::UNO_QUERY); + } + + if (!xFrame.is()) + return false; + + css::uno::Reference < css::document::XActionLockable > xLock( xFrame, css::uno::UNO_QUERY ); + if ( xLock.is() && xLock->isActionLocked() ) + return false; + + css::uno::Reference< css::awt::XWindow > xContainerWindow = xFrame->getContainerWindow(); + + css::uno::Reference< css::frame::XController > xStartModule = css::frame::StartModule::createWithParentWindow( + xContext, xContainerWindow); + + // Attention: You MUST(!) call setComponent() before you call attachFrame(). + css::uno::Reference< css::awt::XWindow > xBackingWin(xStartModule, css::uno::UNO_QUERY); + xFrame->setComponent(xBackingWin, xStartModule); + xStartModule->attachFrame(xFrame); + xContainerWindow->setVisible(true); + + return true; +} + +bool CloseDispatcher::implts_terminateApplication() +{ + css::uno::Reference< css::uno::XComponentContext > xContext; + { + SolarMutexGuard g; + xContext = m_xContext; + } + + css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create( xContext ); + + return xDesktop->terminate(); +} + +void CloseDispatcher::implts_notifyResultListener(const css::uno::Reference< css::frame::XDispatchResultListener >& xListener, + sal_Int16 nState , + const css::uno::Any& aResult ) +{ + if (!xListener.is()) + return; + + css::frame::DispatchResultEvent aEvent( + css::uno::Reference< css::uno::XInterface >(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY), + nState, + aResult); + + xListener->dispatchFinished(aEvent); +} + +css::uno::Reference< css::frame::XFrame > CloseDispatcher::static_impl_searchRightTargetFrame(const css::uno::Reference< css::frame::XFrame >& xFrame , + std::u16string_view sTarget) +{ + if (o3tl::equalsIgnoreAsciiCase(sTarget, u"_self")) + return xFrame; + + OSL_ENSURE(sTarget.empty(), "CloseDispatch used for unexpected target. Magic things will happen now .-)"); + + css::uno::Reference< css::frame::XFrame > xTarget = xFrame; + while(true) + { + // a) top frames will be closed + if (xTarget->isTop()) + return xTarget; + + // b) even child frame containing top level windows (e.g. query designer of database) will be closed + css::uno::Reference< css::awt::XWindow > xWindow = xTarget->getContainerWindow(); + css::uno::Reference< css::awt::XTopWindow > xTopWindowCheck(xWindow, css::uno::UNO_QUERY); + if (xTopWindowCheck.is()) + { + // b1) Note: Toolkit interface XTopWindow sometimes is used by real VCL-child-windows also .-) + // Be sure that these window is really a "top system window". + // Attention ! Checking Window->GetParent() isn't the right approach here. + // Because sometimes VCL create "implicit border windows" as parents even we created + // a simple XWindow using the toolkit only .-( + SolarMutexGuard aSolarLock; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && pWindow->IsSystemWindow() ) + return xTarget; + } + + // c) try to find better results on parent frame + // If no parent frame exists (because this frame is used outside the desktop tree) + // the given frame must be used directly. + css::uno::Reference< css::frame::XFrame > xParent = xTarget->getCreator(); + if ( ! xParent.is()) + return xTarget; + + // c1) check parent frame inside next loop ... + xTarget = xParent; + } +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |