Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  closedispatcher.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this 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 OUString URL_CLOSEDOC = u".uno:CloseDoc"_ustr;
constexpr OUString URL_CLOSEWIN = u".uno:CloseWin"_ustr;
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!
            //     (tdf#30920 consider that closing a frame which is not the backing window (start center) while there is
            //      another frame that is the backing window open only closes the frame, and not terminate the app, so
            //      closing the license frame doesn't terminate the app if launched from the start center)
            //     => close our frame only - nothing else.
            if (!aCheck2.m_lOtherVisibleFrames.empty() || (!aCheck2.m_bReferenceIsBacking && aCheck2.m_xBackingComponent.is()))
                bCloseFrame = true;

            // 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;
        }
        catchconst 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 = std::move(xParent);
    }
}

// namespace framework

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Messung V0.5
C=91 H=99 G=94

¤ Dauer der Verarbeitung: 0.16 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge