blob: 3264e16eb9b1762aebe438cf4c51b21314753625 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_framework.hxx"
//_______________________________________________
// my own includes
#include <dispatch/closedispatcher.hxx>
#include <pattern/frame.hxx>
#include <threadhelp/readguard.hxx>
#include <threadhelp/writeguard.hxx>
#include <framework/framelistanalyzer.hxx>
#include <services.h>
#include <general.h>
//_______________________________________________
// interface includes
#include <com/sun/star/frame/XDesktop.hpp>
#include <com/sun/star/frame/XController.hpp>
#include <com/sun/star/frame/CommandGroup.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>
//_______________________________________________
// includes of other projects
#include <vcl/window.hxx>
#include <vcl/svapp.hxx>
#include <vos/mutex.hxx>
#include <unotools/moduleoptions.hxx>
//_______________________________________________
// namespace
namespace framework{
#ifdef fpf
#error "Who uses \"fpf\" as define. It will overwrite my namespace alias ..."
#endif
namespace fpf = ::framework::pattern::frame;
//_______________________________________________
// non exported const
static ::rtl::OUString URL_CLOSEDOC = DECLARE_ASCII(".uno:CloseDoc" );
static ::rtl::OUString URL_CLOSEWIN = DECLARE_ASCII(".uno:CloseWin" );
static ::rtl::OUString URL_CLOSEFRAME = DECLARE_ASCII(".uno:CloseFrame");
//_______________________________________________
// declarations
DEFINE_XINTERFACE_4(CloseDispatcher ,
OWeakObject ,
DIRECT_INTERFACE(css::lang::XTypeProvider ),
DIRECT_INTERFACE(css::frame::XNotifyingDispatch ),
DIRECT_INTERFACE(css::frame::XDispatch ),
DIRECT_INTERFACE(css::frame::XDispatchInformationProvider))
// Note: XStatusListener is an implementation detail. Hide it for scripting!
DEFINE_XTYPEPROVIDER_4(CloseDispatcher ,
css::lang::XTypeProvider ,
css::frame::XDispatchInformationProvider,
css::frame::XNotifyingDispatch ,
css::frame::XDispatch )
//-----------------------------------------------
CloseDispatcher::CloseDispatcher(const css::uno::Reference< css::lang::XMultiServiceFactory >& xSMGR ,
const css::uno::Reference< css::frame::XFrame >& xFrame ,
const ::rtl::OUString& sTarget)
: ThreadHelpBase (&Application::GetSolarMutex() )
, ::cppu::OWeakObject( )
, m_xSMGR (xSMGR )
, m_aAsyncCallback (LINK( this, CloseDispatcher, impl_asyncCallback))
, m_lStatusListener (m_aLock.getShareableOslMutex() )
{
m_xCloseFrame = CloseDispatcher::static_impl_searchRightTargetFrame(xFrame, sTarget);
}
//-----------------------------------------------
CloseDispatcher::~CloseDispatcher()
{
}
//-----------------------------------------------
void SAL_CALL CloseDispatcher::dispatch(const css::util::URL& aURL ,
const css::uno::Sequence< css::beans::PropertyValue >& lArguments)
throw(css::uno::RuntimeException)
{
dispatchWithNotification(aURL, lArguments, css::uno::Reference< css::frame::XDispatchResultListener >());
}
//-----------------------------------------------
css::uno::Sequence< sal_Int16 > SAL_CALL CloseDispatcher::getSupportedCommandGroups()
throw(css::uno::RuntimeException)
{
css::uno::Sequence< sal_Int16 > lGroups(2);
lGroups[0] = css::frame::CommandGroup::VIEW;
lGroups[1] = css::frame::CommandGroup::DOCUMENT;
return lGroups;
}
//-----------------------------------------------
css::uno::Sequence< css::frame::DispatchInformation > SAL_CALL CloseDispatcher::getConfigurableDispatchInformation(sal_Int16 nCommandGroup)
throw(css::uno::RuntimeException)
{
if (nCommandGroup == css::frame::CommandGroup::VIEW)
{
/* Attention: Dont add .uno:CloseFrame here. Because its not realy
a configurable feature ... and further it does not have
a valid UIName entry inside the GenericCommands.xcu ... */
css::uno::Sequence< css::frame::DispatchInformation > lViewInfos(1);
lViewInfos[0].Command = URL_CLOSEWIN;
lViewInfos[0].GroupId = css::frame::CommandGroup::VIEW;
return lViewInfos;
}
else
if (nCommandGroup == css::frame::CommandGroup::DOCUMENT)
{
css::uno::Sequence< css::frame::DispatchInformation > lDocInfos(1);
lDocInfos[0].Command = URL_CLOSEDOC;
lDocInfos[0].GroupId = 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*/ )
throw(css::uno::RuntimeException)
{
}
//-----------------------------------------------
void SAL_CALL CloseDispatcher::removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/,
const css::util::URL& /*aURL*/ )
throw(css::uno::RuntimeException)
{
}
//-----------------------------------------------
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 )
throw(css::uno::RuntimeException)
{
// SAFE -> ----------------------------------
WriteGuard aWriteLock(m_aLock);
// This reference indicates, that we was already called before and
// our asynchronous process was not finished yet.
// We have to reject double calls. Otherwhise we risk,
// that we try to close an already closed resource ...
// And its no problem to do nothing then. The UI user will try it again, if
// non of these jobs was successfully.
if (m_xSelfHold.is())
{
aWriteLock.unlock();
// <- 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.equals(URL_CLOSEDOC))
m_eOperation = E_CLOSE_DOC;
else
if (aURL.Complete.equals(URL_CLOSEWIN))
m_eOperation = E_CLOSE_WIN;
else
if (aURL.Complete.equals(URL_CLOSEFRAME))
m_eOperation = E_CLOSE_FRAME;
else
{
aWriteLock.unlock();
// <- SAFE ------------------------------
implts_notifyResultListener(
xListener,
css::frame::DispatchResultState::FAILURE,
css::uno::Any());
return;
}
// OK - URLs are the right ones.
// But we cant execute synchronously :-)
// May we are called from a generic key-input handler,
// which isnt aware that this call kill its own environment ...
// Do it asynchronous everytimes!
// But dont forget to hold usself alive.
// We are called back from an environment, which doesnt know an uno reference.
// They call us back by using our c++ interface.
m_xResultListener = xListener;
m_xSelfHold = css::uno::Reference< css::uno::XInterface >(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY);
aWriteLock.unlock();
// <- SAFE ----------------------------------
sal_Bool bIsSynchron = sal_False;
for (sal_Int32 nArgs=0; nArgs<lArguments.getLength(); nArgs++ )
{
if ( lArguments[nArgs].Name.equalsAscii("SynchronMode") )
{
lArguments[nArgs].Value >>= bIsSynchron;
break;
}
}
if ( bIsSynchron )
impl_asyncCallback(0);
else
m_aAsyncCallback.Post(0);
}
//-----------------------------------------------
/**
@short asynchronous callback
@descr We start all actions inside this object asnychronoue.
(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 neccessary 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 (menas count open frames - exlcuding our
current one)
- decide then, if we must close this frame only, establish the backing mode
or shutdown the whole application.
*/
IMPL_LINK( CloseDispatcher, impl_asyncCallback, void*, EMPTYARG )
{
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 ...
sal_Bool bAllowSuspend = sal_True;
sal_Bool bControllerSuspended = sal_False;
// SAFE -> ----------------------------------
ReadGuard aReadLock(m_aLock);
// Closing of all views, related to the same document, is allowed
// only if the dispatched URL was ".uno:CloseDoc"!
sal_Bool bCloseAllViewsToo = (m_eOperation == E_CLOSE_DOC);
// BTW: Make some copies, which are needed later ...
EOperation eOperation = m_eOperation;
css::uno::Reference< css::lang::XMultiServiceFactory > xSMGR = m_xSMGR;
css::uno::Reference< css::frame::XFrame > xCloseFrame (m_xCloseFrame.get(), css::uno::UNO_QUERY);
css::uno::Reference< css::frame::XDispatchResultListener > xListener = m_xResultListener;
aReadLock.unlock();
// <- SAFE ----------------------------------
// frame already dead ?!
// Nothing to do !
if (! xCloseFrame.is())
return 0;
sal_Bool bCloseFrame = sal_False;
sal_Bool bEstablishBackingMode = sal_False;
sal_Bool bTerminateApp = sal_False;
// Analyze the environment a first time.
// If we found some special cases, we can
// make some decisions erliar!
css::uno::Reference< css::frame::XFramesSupplier > xDesktop(xSMGR->createInstance(SERVICENAME_DESKTOP), css::uno::UNO_QUERY_THROW);
FrameListAnalyzer aCheck1(xDesktop, xCloseFrame, FrameListAnalyzer::E_HELP | FrameListAnalyzer::E_BACKINGCOMPONENT);
// a) If the curent frame (where the close dispatch was requested for) does not have
// any parent frame ... it will close this frame only. Such frame isnt 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 = sal_True;
else
// b) The help window cant disagree with any request.
// Because it doesnt implement a controller - it uses a window only.
// Further t cant be the last open frame - if we do all other things
// right inside this CloseDispatcher implementation.
// => close it!
if (aCheck1.m_bReferenceIsHelp)
bCloseFrame = sal_True;
else
// c) If we are already in "backing mode", we have to terminate
// the application, if this special frame is closed.
// It doesnt matter, how many other frames (can be the help or hidden frames only)
// are open then.
// => terminate the application!
if (aCheck1.m_bReferenceIsBacking)
bTerminateApp = sal_True;
else
// d) Otherwhise 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!
{
if (implts_prepareFrameForClosing(m_xCloseFrame, bAllowSuspend, bCloseAllViewsToo, bControllerSuspended))
{
// OK; this frame is empty now.
// Check the environment again to decide, what is the next step.
FrameListAnalyzer aCheck2(xDesktop, xCloseFrame, FrameListAnalyzer::E_ALL);
// c1) there is as minimum 1 frame open, which is visible and contains a document
// different from our one. And its not the help!
// => close our frame only - nothing else.
if (aCheck2.m_lOtherVisibleFrames.getLength()>0)
bCloseFrame = sal_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.getLength() > 0)
)
bCloseFrame = sal_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)
bTerminateApp = sal_True;
else if( SvtModuleOptions().IsModuleInstalled(SvtModuleOptions::E_SSTARTMODULE) )
bEstablishBackingMode = sal_True;
else
bTerminateApp = sal_True;
}
}
}
// Do it now ...
sal_Bool bSuccess = sal_False;
if (bCloseFrame)
bSuccess = implts_closeFrame();
else
if (bEstablishBackingMode)
#if defined QUARTZ
{
// 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( xSMGR->createInstance(IMPLEMENTATIONNAME_QUICKLAUNCHER), css::uno::UNO_QUERY_THROW );
if( xSet.is() )
{
css::uno::Any aVal( xSet->getFastPropertyValue( 0 ) );
sal_Bool bState = sal_False;
if( aVal >>= bState )
bQuickstarterRunning = bState;
}
}
catch( 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(sal_False);
}
// inform listener
sal_Int16 nState = css::frame::DispatchResultState::FAILURE;
if (bSuccess)
nState = css::frame::DispatchResultState::SUCCESS;
implts_notifyResultListener(xListener, nState, css::uno::Any());
// SAFE -> ----------------------------------
WriteGuard aWriteLock(m_aLock);
// 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
// ansynchronous operations. So its time now to release this reference.
// But hold it temp alive. Otherwhise we die before we can finish this method realy :-))
css::uno::Reference< css::uno::XInterface > xTempHold = m_xSelfHold;
m_xSelfHold.clear();
m_xResultListener.clear();
aWriteLock.unlock();
// <- SAFE ----------------------------------
}
catch(const css::lang::DisposedException&)
{
LOG_ERROR("CloseDispatcher::impl_asyncCallback", "Congratulation! You found the reason for bug #120310#. Please contact the right developer and show him a scenario, which trigger this bug. THX.")
}
return 0;
}
//-----------------------------------------------
sal_Bool CloseDispatcher::implts_prepareFrameForClosing(const css::uno::Reference< css::frame::XFrame >& xFrame ,
sal_Bool bAllowSuspend ,
sal_Bool bCloseAllOtherViewsToo,
sal_Bool& bControllerSuspended )
{
// Frame already dead ... so this view is closed ... is closed ... is ... .-)
if (! xFrame.is())
return sal_True;
// Close all views to the same document ... if forced to do so.
// But dont 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)
{
// SAFE -> ----------------------------------
ReadGuard aReadLock(m_aLock);
css::uno::Reference< css::lang::XMultiServiceFactory > xSMGR = m_xSMGR;
aReadLock.unlock();
// <- SAFE ----------------------------------
css::uno::Reference< css::frame::XFramesSupplier > xDesktop(xSMGR->createInstance(SERVICENAME_DESKTOP), css::uno::UNO_QUERY_THROW);
FrameListAnalyzer aCheck(xDesktop, xFrame, FrameListAnalyzer::E_ALL);
sal_Int32 c = aCheck.m_lModelFrames.getLength();
sal_Int32 i = 0;
for (i=0; i<c; ++i)
{
if (!fpf::closeIt(aCheck.m_lModelFrames[i], sal_False))
return sal_False;
}
}
// If allowed - inform user about modified documents or
// still running jobs (e.g. printing).
if (bAllowSuspend)
{
css::uno::Reference< css::frame::XController > xController = xFrame->getController();
if (xController.is()) // some views dont uses a controller .-( (e.g. the help window)
{
bControllerSuspended = xController->suspend(sal_True);
if (! bControllerSuspended)
return sal_False;
}
}
// dont remove the component realy by e.g. calling setComponent(null, null).
// It's enough to suspend the controller.
// If we close the frame later this controller doesnt show the same dialog again.
return sal_True;
}
//-----------------------------------------------
sal_Bool CloseDispatcher::implts_closeFrame()
{
// SAFE -> ----------------------------------
ReadGuard aReadLock(m_aLock);
css::uno::Reference< css::frame::XFrame > xFrame (m_xCloseFrame.get(), css::uno::UNO_QUERY);
aReadLock.unlock();
// <- SAFE ----------------------------------
// frame already dead ? => so it's closed ... it's closed ...
if ( ! xFrame.is() )
return sal_True;
// dont deliver owner ship; our "UI user" will try it again if it failed.
// OK - he will get an empty frame then. But normaly an empty frame
// should be closeable always :-)
if (!fpf::closeIt(xFrame, sal_False))
return sal_False;
// SAFE -> ----------------------------------
WriteGuard aWriteLock(m_aLock);
m_xCloseFrame = css::uno::WeakReference< css::frame::XFrame >();
aWriteLock.unlock();
// <- SAFE ----------------------------------
return sal_True;
}
//-----------------------------------------------
sal_Bool CloseDispatcher::implts_establishBackingMode()
{
// SAFE -> ----------------------------------
ReadGuard aReadLock(m_aLock);
css::uno::Reference< css::lang::XMultiServiceFactory > xSMGR = m_xSMGR;
css::uno::Reference< css::frame::XFrame > xFrame (m_xCloseFrame.get(), css::uno::UNO_QUERY);
aReadLock.unlock();
// <- SAFE ----------------------------------
if (!xFrame.is())
return sal_False;
css::uno::Reference < css::document::XActionLockable > xLock( xFrame, css::uno::UNO_QUERY );
if ( xLock.is() && xLock->isActionLocked() )
return sal_False;
css::uno::Reference< css::awt::XWindow > xContainerWindow = xFrame->getContainerWindow();
css::uno::Sequence< css::uno::Any > lArgs(1);
lArgs[0] <<= xContainerWindow;
css::uno::Reference< css::frame::XController > xBackingComp(
xSMGR->createInstanceWithArguments(SERVICENAME_STARTMODULE, lArgs),
css::uno::UNO_QUERY_THROW);
// Attention: You MUST(!) call setComponent() before you call attachFrame().
css::uno::Reference< css::awt::XWindow > xBackingWin(xBackingComp, css::uno::UNO_QUERY);
xFrame->setComponent(xBackingWin, xBackingComp);
xBackingComp->attachFrame(xFrame);
xContainerWindow->setVisible(sal_True);
return sal_True;
}
//-----------------------------------------------
sal_Bool CloseDispatcher::implts_terminateApplication()
{
// SAFE -> ----------------------------------
ReadGuard aReadLock(m_aLock);
css::uno::Reference< css::lang::XMultiServiceFactory > xSMGR = m_xSMGR;
aReadLock.unlock();
// <- SAFE ----------------------------------
css::uno::Reference< css::frame::XDesktop > xDesktop(
xSMGR->createInstance(SERVICENAME_DESKTOP), css::uno::UNO_QUERY_THROW);
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 ,
const ::rtl::OUString& sTarget)
{
if (sTarget.equalsIgnoreAsciiCaseAscii("_self"))
return xFrame;
OSL_ENSURE((sTarget.getLength() < 1), "CloseDispatch used for unexpected target. Magic things will happen now .-)");
css::uno::Reference< css::frame::XFrame > xTarget = xFrame;
while(sal_True)
{
// a) top frames wil 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 realy a "top system window".
// Attention ! Checking Window->GetParent() isnt the right approach here.
// Because sometimes VCL create "implicit border windows" as parents even we created
// a simple XWindow using the toolkit only .-(
::vos::OGuard aSolarLock(&Application::GetSolarMutex());
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(), css::uno::UNO_QUERY);
if ( ! xParent.is())
return xTarget;
// c1) check parent frame inside next loop ...
xTarget = xParent;
}
}
} // namespace framework