1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <dispatch/closedispatcher.hxx>
21 #include <pattern/frame.hxx>
22 #include <framework/framelistanalyzer.hxx>
25 #include <com/sun/star/bridge/BridgeFactory.hpp>
26 #include <com/sun/star/bridge/XBridgeFactory2.hpp>
27 #include <com/sun/star/frame/Desktop.hpp>
28 #include <com/sun/star/frame/DispatchResultState.hpp>
29 #include <com/sun/star/frame/XController.hpp>
30 #include <com/sun/star/frame/CommandGroup.hpp>
31 #include <com/sun/star/frame/StartModule.hpp>
32 #include <com/sun/star/lang/DisposedException.hpp>
33 #include <com/sun/star/awt/XTopWindow.hpp>
34 #include <com/sun/star/document/XActionLockable.hpp>
35 #include <com/sun/star/beans/XFastPropertySet.hpp>
36 #include <toolkit/helper/vclunohelper.hxx>
38 #include <osl/diagnose.h>
40 #include <vcl/window.hxx>
41 #include <vcl/svapp.hxx>
42 #include <vcl/syswin.hxx>
43 #include <unotools/moduleoptions.hxx>
44 #include <o3tl/string_view.hxx>
46 using namespace com::sun::star
;
51 #error "Who uses \"fpf\" as define. It will overwrite my namespace alias ..."
53 namespace fpf
= ::framework::pattern::frame
;
55 constexpr OUString URL_CLOSEDOC
= u
".uno:CloseDoc"_ustr
;
56 constexpr OUString URL_CLOSEWIN
= u
".uno:CloseWin"_ustr
;
57 const char URL_CLOSEFRAME
[] = ".uno:CloseFrame";
59 CloseDispatcher::CloseDispatcher(css::uno::Reference
< css::uno::XComponentContext
> xContext
,
60 const css::uno::Reference
< css::frame::XFrame
>& xFrame
,
61 std::u16string_view sTarget
)
62 : m_xContext(std::move(xContext
))
64 new vcl::EventPoster(LINK(this, CloseDispatcher
, impl_asyncCallback
)))
65 , m_eOperation(E_CLOSE_DOC
)
66 , m_pSysWindow(nullptr)
68 uno::Reference
<frame::XFrame
> xTarget
= static_impl_searchRightTargetFrame(xFrame
, sTarget
);
69 m_xCloseFrame
= xTarget
;
71 // Try to retrieve the system window instance of the closing frame.
72 uno::Reference
<awt::XWindow
> xWindow
= xTarget
->getContainerWindow();
75 VclPtr
<vcl::Window
> pWindow
= VCLUnoHelper::GetWindow(xWindow
);
76 if (pWindow
->IsSystemWindow())
77 m_pSysWindow
= dynamic_cast<SystemWindow
*>(pWindow
.get());
81 CloseDispatcher::~CloseDispatcher()
84 m_aAsyncCallback
.reset();
88 void SAL_CALL
CloseDispatcher::dispatch(const css::util::URL
& aURL
,
89 const css::uno::Sequence
< css::beans::PropertyValue
>& lArguments
)
91 dispatchWithNotification(aURL
, lArguments
, css::uno::Reference
< css::frame::XDispatchResultListener
>());
94 css::uno::Sequence
< sal_Int16
> SAL_CALL
CloseDispatcher::getSupportedCommandGroups()
96 return css::uno::Sequence
< sal_Int16
>{css::frame::CommandGroup::VIEW
, css::frame::CommandGroup::DOCUMENT
};
99 css::uno::Sequence
< css::frame::DispatchInformation
> SAL_CALL
CloseDispatcher::getConfigurableDispatchInformation(sal_Int16 nCommandGroup
)
101 if (nCommandGroup
== css::frame::CommandGroup::VIEW
)
103 /* Attention: Don't add .uno:CloseFrame here. Because it's not really
104 a configurable feature ... and further it does not have
105 a valid UIName entry inside the GenericCommands.xcu ... */
106 css::uno::Sequence
< css::frame::DispatchInformation
> lViewInfos
{
107 { URL_CLOSEWIN
, css::frame::CommandGroup::VIEW
}
111 else if (nCommandGroup
== css::frame::CommandGroup::DOCUMENT
)
113 css::uno::Sequence
< css::frame::DispatchInformation
> lDocInfos
{
114 { URL_CLOSEDOC
, css::frame::CommandGroup::DOCUMENT
}
119 return css::uno::Sequence
< css::frame::DispatchInformation
>();
122 void SAL_CALL
CloseDispatcher::addStatusListener(const css::uno::Reference
< css::frame::XStatusListener
>& /*xListener*/,
123 const css::util::URL
& /*aURL*/ )
127 void SAL_CALL
CloseDispatcher::removeStatusListener(const css::uno::Reference
< css::frame::XStatusListener
>& /*xListener*/,
128 const css::util::URL
& /*aURL*/ )
132 void SAL_CALL
CloseDispatcher::dispatchWithNotification(const css::util::URL
& aURL
,
133 const css::uno::Sequence
< css::beans::PropertyValue
>& lArguments
,
134 const css::uno::Reference
< css::frame::XDispatchResultListener
>& xListener
)
136 // SAFE -> ----------------------------------
137 SolarMutexClearableGuard aWriteLock
;
139 // This reference indicates, that we were already called before and
140 // our asynchronous process was not finished yet.
141 // We have to reject double calls. Otherwise we risk,
142 // that we try to close an already closed resource...
143 // And it is no problem to do nothing then. The UI user will try it again, if
144 // non of these jobs was successful.
145 if (m_xSelfHold
.is())
148 // <- SAFE ------------------------------
150 implts_notifyResultListener(
152 css::frame::DispatchResultState::DONTKNOW
,
157 // First we have to check, if this dispatcher is used right. Means if valid URLs are used.
158 // If not - we have to break this operation. But an optional listener must be informed.
159 // BTW: We save the information about the requested operation. Because
161 if ( aURL
.Complete
== URL_CLOSEDOC
)
162 m_eOperation
= E_CLOSE_DOC
;
163 else if ( aURL
.Complete
== URL_CLOSEWIN
)
164 m_eOperation
= E_CLOSE_WIN
;
165 else if ( aURL
.Complete
== URL_CLOSEFRAME
)
166 m_eOperation
= E_CLOSE_FRAME
;
170 // <- SAFE ------------------------------
172 implts_notifyResultListener(
174 css::frame::DispatchResultState::FAILURE
,
179 if (m_pSysWindow
&& m_pSysWindow
->GetCloseHdl().IsSet())
181 // The closing frame has its own close handler. Call it instead.
182 m_pSysWindow
->GetCloseHdl().Call(*m_pSysWindow
);
185 // <- SAFE ------------------------------
187 implts_notifyResultListener(
189 css::frame::DispatchResultState::SUCCESS
,
195 // OK - URLs are the right ones.
196 // But we can't execute synchronously :-)
197 // May we are called from a generic key-input handler,
198 // which isn't aware that this call kill its own environment...
199 // Do it asynchronous everytimes!
201 // But don't forget to hold ourselves alive.
202 // We are called back from an environment, which doesn't know a uno reference.
203 // They call us back by using our c++ interface.
205 m_xResultListener
= xListener
;
206 m_xSelfHold
.set(static_cast< ::cppu::OWeakObject
* >(this), css::uno::UNO_QUERY
);
209 // <- SAFE ----------------------------------
211 bool bIsSynchron
= false;
212 for (const css::beans::PropertyValue
& rArg
: lArguments
)
214 if ( rArg
.Name
== "SynchronMode" )
216 rArg
.Value
>>= bIsSynchron
;
222 impl_asyncCallback(nullptr);
226 m_aAsyncCallback
->Post();
231 @short asynchronous callback
232 @descr We start all actions inside this object asynchronous
233 (see comments there).
234 Now we do the following:
235 - close all views to the same document, if needed and possible
236 - make the current frame empty
237 ! This step is necessary to handle errors during closing the
238 document inside the frame. May the document shows a dialog and
239 the user ignore it. Then the state of the office can be changed
240 during we try to close frame and document.
241 - check the environment (means count open frames - excluding our
243 - decide then, if we must close this frame only, establish the backing mode
244 or shutdown the whole application.
246 IMPL_LINK_NOARG(CloseDispatcher
, impl_asyncCallback
, LinkParamNone
*, void)
251 // Allow calling of XController->suspend() everytimes.
252 // Dispatch is an UI functionality. We implement such dispatch object here.
253 // And further XController->suspend() was designed to bring an UI ...
254 bool bControllerSuspended
= false;
256 bool bCloseAllViewsToo
;
257 EOperation eOperation
;
258 css::uno::Reference
< css::uno::XComponentContext
> xContext
;
259 css::uno::Reference
< css::frame::XFrame
> xCloseFrame
;
260 css::uno::Reference
< css::frame::XDispatchResultListener
> xListener
;
264 // Closing of all views, related to the same document, is allowed
265 // only if the dispatched URL was ".uno:CloseDoc"!
266 bCloseAllViewsToo
= (m_eOperation
== E_CLOSE_DOC
);
268 eOperation
= m_eOperation
;
269 xContext
= m_xContext
;
270 xCloseFrame
.set(m_xCloseFrame
.get(), css::uno::UNO_QUERY
);
271 xListener
= m_xResultListener
;
274 // frame already dead ?!
276 if (! xCloseFrame
.is())
279 bool bCloseFrame
= false;
280 bool bEstablishBackingMode
= false;
281 bool bTerminateApp
= false;
283 // Analyze the environment a first time.
284 // If we found some special cases, we can
285 // make some decisions earlier!
286 css::uno::Reference
< css::frame::XFramesSupplier
> xDesktop( css::frame::Desktop::create(xContext
), css::uno::UNO_QUERY_THROW
);
287 FrameListAnalyzer
aCheck1(xDesktop
, xCloseFrame
, FrameAnalyzerFlags::Help
| FrameAnalyzerFlags::BackingComponent
);
289 // Check for existing UNO connections.
290 // NOTE: There is a race between checking this and connections being created/destroyed before
291 // we close the frame / terminate the app.
292 css::uno::Reference
<css::bridge::XBridgeFactory2
> bridgeFac( css::bridge::BridgeFactory::create(xContext
) );
293 bool bHasActiveConnections
= bridgeFac
->getExistingBridges().hasElements();
295 // a) If the current frame (where the close dispatch was requested for) does not have
296 // any parent frame ... it will close this frame only. Such frame isn't part of the
297 // global desktop tree ... and such frames are used as "implementation details" only.
298 // E.g. the live previews of our wizards doing such things. And then the owner of the frame
299 // is responsible for closing the application or accepting closing of the application
301 if ( ! xCloseFrame
->getCreator().is())
304 // b) The help window can't disagree with any request.
305 // Because it doesn't implement a controller - it uses a window only.
306 // Further it can't be the last open frame - if we do all other things
307 // right inside this CloseDispatcher implementation.
309 else if (aCheck1
.m_bReferenceIsHelp
)
312 // c) If we are already in "backing mode", we terminate the application, if no active UNO connections are found.
313 // If there is an active UNO connection, we only close the frame and leave the application alive.
314 // It doesn't matter, how many other frames (can be the help or hidden frames only) are open then.
315 else if (aCheck1
.m_bReferenceIsBacking
) {
316 if (bHasActiveConnections
)
319 bTerminateApp
= true;
322 // d) Otherwise we have to: close all views to the same document, close the
323 // document inside our own frame and decide then again, what has to be done!
326 if (implts_prepareFrameForClosing(m_xCloseFrame
, bCloseAllViewsToo
, bControllerSuspended
))
328 // OK; this frame is empty now.
329 // Check the environment again to decide, what is the next step.
330 FrameListAnalyzer
aCheck2(xDesktop
, xCloseFrame
, FrameAnalyzerFlags::All
);
332 // c1) there is as minimum 1 frame open, which is visible and contains a document
333 // different from our one. And it's not the help!
334 // (tdf#30920 consider that closing a frame which is not the backing window (start center) while there is
335 // another frame that is the backing window open only closes the frame, and not terminate the app, so
336 // closing the license frame doesn't terminate the app if launched from the start center)
337 // => close our frame only - nothing else.
338 if (!aCheck2
.m_lOtherVisibleFrames
.empty() || (!aCheck2
.m_bReferenceIsBacking
&& aCheck2
.m_xBackingComponent
.is()))
341 // c2) if we close the current view ... but not all other views
342 // to the same document, we must close the current frame only!
343 // Because implts_closeView() suspended this view only - does not
346 (!bCloseAllViewsToo
) &&
347 (!aCheck2
.m_lModelFrames
.empty())
352 // c3) there is no other (visible) frame open ...
353 // The help module will be ignored everytimes!
354 // But we have to decide if we must terminate the
355 // application or establish the backing mode now.
356 // And that depends from the dispatched URL ...
358 if (eOperation
== E_CLOSE_FRAME
)
360 if (bHasActiveConnections
)
363 bTerminateApp
= true;
365 else if( SvtModuleOptions().IsModuleInstalled(SvtModuleOptions::EModule::STARTMODULE
) )
366 bEstablishBackingMode
= true;
367 else if (bHasActiveConnections
)
370 bTerminateApp
= true;
376 bool bSuccess
= false;
378 bSuccess
= implts_closeFrame();
379 else if (bEstablishBackingMode
)
382 // on mac close down, quickstarter keeps the process alive
383 // however if someone has shut down the quickstarter
384 // behave as any other platform
386 bool bQuickstarterRunning
= false;
387 // get quickstart service
390 css::uno::Reference
< css::beans::XFastPropertySet
> xSet( xContext
->getServiceManager()->createInstanceWithContext(IMPLEMENTATIONNAME_QUICKLAUNCHER
, xContext
), css::uno::UNO_QUERY_THROW
);
391 css::uno::Any
aVal( xSet
->getFastPropertyValue( 0 ) );
393 if( aVal
>>= bState
)
394 bQuickstarterRunning
= bState
;
396 catch( const css::uno::Exception
& )
399 bSuccess
= bQuickstarterRunning
? implts_terminateApplication() : implts_establishBackingMode();
402 bSuccess
= implts_establishBackingMode();
404 else if (bTerminateApp
)
405 bSuccess
= implts_terminateApplication();
407 if ( ! bSuccess
&& bControllerSuspended
)
409 css::uno::Reference
< css::frame::XController
> xController
= xCloseFrame
->getController();
410 if (xController
.is())
411 xController
->suspend(false);
415 sal_Int16 nState
= css::frame::DispatchResultState::FAILURE
;
417 nState
= css::frame::DispatchResultState::SUCCESS
;
418 implts_notifyResultListener(xListener
, nState
, css::uno::Any());
421 // This method was called asynchronous from our main thread by using a pointer.
422 // We reached this method only, by using a reference to ourself :-)
423 // Further this member is used to detect still running and not yet finished
424 // asynchronous operations. So it's time now to release this reference.
425 // But hold it temp alive. Otherwise we die before we can finish this method really :-))
426 css::uno::Reference
< css::uno::XInterface
> xTempHold
= m_xSelfHold
;
428 m_xResultListener
.clear();
430 catch(const css::lang::DisposedException
&)
435 bool CloseDispatcher::implts_prepareFrameForClosing(const css::uno::Reference
< css::frame::XFrame
>& xFrame
,
436 bool bCloseAllOtherViewsToo
,
437 bool& bControllerSuspended
)
439 // Frame already dead ... so this view is closed ... is closed ... is ... .-)
443 // Close all views to the same document ... if forced to do so.
444 // But don't touch our own frame here!
445 // We must do so ... because the may be following controller->suspend()
446 // will show the "save/discard/cancel" dialog for the last view only!
447 if (bCloseAllOtherViewsToo
)
449 css::uno::Reference
< css::uno::XComponentContext
> xContext
;
452 xContext
= m_xContext
;
455 css::uno::Reference
< css::frame::XFramesSupplier
> xDesktop( css::frame::Desktop::create( xContext
), css::uno::UNO_QUERY_THROW
);
456 FrameListAnalyzer
aCheck(xDesktop
, xFrame
, FrameAnalyzerFlags::All
);
458 size_t c
= aCheck
.m_lModelFrames
.size();
462 if (!fpf::closeIt(aCheck
.m_lModelFrames
[i
]))
467 // Inform user about modified documents or still running jobs (e.g. printing).
469 css::uno::Reference
< css::frame::XController
> xController
= xFrame
->getController();
470 if (xController
.is()) // some views don't uses a controller .-( (e.g. the help window)
472 bControllerSuspended
= xController
->suspend(true);
473 if (! bControllerSuspended
)
478 // don't remove the component really by e.g. calling setComponent(null, null).
479 // It's enough to suspend the controller.
480 // If we close the frame later this controller doesn't show the same dialog again.
484 bool CloseDispatcher::implts_closeFrame()
486 css::uno::Reference
< css::frame::XFrame
> xFrame
;
489 xFrame
.set(m_xCloseFrame
.get(), css::uno::UNO_QUERY
);
492 // frame already dead ? => so it's closed ... it's closed ...
496 // don't deliver ownership; our "UI user" will try it again if it failed.
497 // OK - he will get an empty frame then. But normally an empty frame
498 // should be closeable always :-)
499 if (!fpf::closeIt(xFrame
))
504 m_xCloseFrame
.clear();
510 bool CloseDispatcher::implts_establishBackingMode()
512 css::uno::Reference
< css::uno::XComponentContext
> xContext
;
513 css::uno::Reference
< css::frame::XFrame
> xFrame
;
516 xContext
= m_xContext
;
517 xFrame
.set(m_xCloseFrame
.get(), css::uno::UNO_QUERY
);
523 css::uno::Reference
< css::document::XActionLockable
> xLock( xFrame
, css::uno::UNO_QUERY
);
524 if ( xLock
.is() && xLock
->isActionLocked() )
527 css::uno::Reference
< css::awt::XWindow
> xContainerWindow
= xFrame
->getContainerWindow();
529 css::uno::Reference
< css::frame::XController
> xStartModule
= css::frame::StartModule::createWithParentWindow(
530 xContext
, xContainerWindow
);
532 // Attention: You MUST(!) call setComponent() before you call attachFrame().
533 css::uno::Reference
< css::awt::XWindow
> xBackingWin(xStartModule
, css::uno::UNO_QUERY
);
534 xFrame
->setComponent(xBackingWin
, xStartModule
);
535 xStartModule
->attachFrame(xFrame
);
536 xContainerWindow
->setVisible(true);
541 bool CloseDispatcher::implts_terminateApplication()
543 css::uno::Reference
< css::uno::XComponentContext
> xContext
;
546 xContext
= m_xContext
;
549 css::uno::Reference
< css::frame::XDesktop2
> xDesktop
= css::frame::Desktop::create( xContext
);
551 return xDesktop
->terminate();
554 void CloseDispatcher::implts_notifyResultListener(const css::uno::Reference
< css::frame::XDispatchResultListener
>& xListener
,
556 const css::uno::Any
& aResult
)
561 css::frame::DispatchResultEvent
aEvent(
562 css::uno::Reference
< css::uno::XInterface
>(static_cast< ::cppu::OWeakObject
* >(this), css::uno::UNO_QUERY
),
566 xListener
->dispatchFinished(aEvent
);
569 css::uno::Reference
< css::frame::XFrame
> CloseDispatcher::static_impl_searchRightTargetFrame(const css::uno::Reference
< css::frame::XFrame
>& xFrame
,
570 std::u16string_view sTarget
)
572 if (o3tl::equalsIgnoreAsciiCase(sTarget
, u
"_self"))
575 OSL_ENSURE(sTarget
.empty(), "CloseDispatch used for unexpected target. Magic things will happen now .-)");
577 css::uno::Reference
< css::frame::XFrame
> xTarget
= xFrame
;
580 // a) top frames will be closed
581 if (xTarget
->isTop())
584 // b) even child frame containing top level windows (e.g. query designer of database) will be closed
585 css::uno::Reference
< css::awt::XWindow
> xWindow
= xTarget
->getContainerWindow();
586 css::uno::Reference
< css::awt::XTopWindow
> xTopWindowCheck(xWindow
, css::uno::UNO_QUERY
);
587 if (xTopWindowCheck
.is())
589 // b1) Note: Toolkit interface XTopWindow sometimes is used by real VCL-child-windows also .-)
590 // Be sure that these window is really a "top system window".
591 // Attention ! Checking Window->GetParent() isn't the right approach here.
592 // Because sometimes VCL create "implicit border windows" as parents even we created
593 // a simple XWindow using the toolkit only .-(
594 SolarMutexGuard aSolarLock
;
595 VclPtr
<vcl::Window
> pWindow
= VCLUnoHelper::GetWindow( xWindow
);
596 if ( pWindow
&& pWindow
->IsSystemWindow() )
600 // c) try to find better results on parent frame
601 // If no parent frame exists (because this frame is used outside the desktop tree)
602 // the given frame must be used directly.
603 css::uno::Reference
< css::frame::XFrame
> xParent
= xTarget
->getCreator();
607 // c1) check parent frame inside next loop ...
608 xTarget
= std::move(xParent
);
612 } // namespace framework
614 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */